Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ jeff(options, function (error, stats, extractedData) {
* Will sequentially parse and process the SWF files.
* Will export JSON meta-data and images corresponding to the SWF files.

For a list of command line options:

```shell
jeff -h
```

### Roadmap for unsupported features

* Texts (Static/Dynamic)
Expand All @@ -98,4 +104,4 @@ jeff(options, function (error, stats, extractedData) {
* Option to extract shapes under vectorial format
* Option to extract meta-data under keyframe based format (as opposed to per frame transformation matrix)

For contributors, see [SWF File Format Specifications](http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/swf/pdf/swf-file-format-spec.pdf)
For contributors, see [SWF File Format Specifications](https://wwwimages2.adobe.com/content/dam/acom/en/devnet/pdf/swf-file-format-spec.pdf)
6 changes: 5 additions & 1 deletion bin/jeff
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ program
.option('-I, --ignoreImages <boolean>', 'Not to export images', 'false')
.option('-F, --filtering <filtering method>', 'Filtering that should be used when rendering the animation', 'linear')
.option('-e, --outlineEmphasis <coefficient>', 'Emphasis of outlines when rendering Flash vectorial drawings', '1')
.option('-V, --version <int>', 'If specified, force decompiler to attempt to decompile as if the swf was specified version', '-1')
.option('-L, --verbosity <int>', '(L)og Level. 1-10, 1 showing least, 10 showing the most. Default is 3', '3')

.parse(process.argv);

Expand Down Expand Up @@ -67,7 +69,9 @@ var exportParams = {
ignoreData: JSON.parse(program.ignoreData),
ignoreImages: JSON.parse(program.ignoreImages),
filtering: program.filtering,
outlineEmphasis: JSON.parse(program.outlineEmphasis)
outlineEmphasis: JSON.parse(program.outlineEmphasis),
forceVersion: JSON.parse(program.version),
verbosity: JSON.parse(program.verbosity)
};

// Creating a new Jeff
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "jeff",
"description": "Extracts Json meta-data and images from a SWF Flash file.",
"version": "0.2.10",
"version": "0.3.0",
"private": false,
"author": {
"name": "Brice Chevalier",
Expand All @@ -14,13 +14,13 @@
"dependencies": {
"async": "0.2.10",
"buffer-crc32": "^0.2.5",
"canvas": "1.1.6",
"canvas": "1.6.11",
"commander": "2.2.0",
"fs-extra": "0.8.1",
"glob": "4.3.1",
"image-optim": "0.3.0",
"js-beautify": "1.4.2",
"node-pngquant-native": "0.0.12"
"node-pngquant-native": "2.0.1"
},
"repository": {
"type": "git",
Expand All @@ -32,4 +32,4 @@
"url": "https://github.com/Wizcorp/jeff/blob/master/LICENSE"
}
]
}
}
19 changes: 18 additions & 1 deletion src/Gordon/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ var Base = {
},
validSignatures: {
SWF: 'FWS',
COMPRESSED_SWF: 'CWS'
COMPRESSED_ZLIB_SWF: 'CWS',
COMPRESSED_LZMA_SWF: 'ZWS'
},
readyStates: {
LOADING: 0,
Expand Down Expand Up @@ -303,6 +304,22 @@ var Base = {
13: 'overlay',
14: 'hardlight'
},
soundFormats: {
UNCOMPRESSED_NATIVE_ENDIAN: 0, //swf 1
ADPCM: 1, //swf 1
MP3: 2, //swf 4
UNCOMPRESSED_LITTLE_ENDIAN: 3, //swf 4
NELLYMOSER16KHZ: 4, //swf 10
NELLYMOSER8KHZ: 5, //swf 10
NELLYMOSER: 6, //swf 6
SPEEX: 11 //swf 10
},
soundRates: {
0: 5500,
1: 11000,
2: 22000,
3: 44000
}
};

(function () {
Expand Down
150 changes: 136 additions & 14 deletions src/Gordon/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

'use strict';

var util = require('util');
var Base = require('./base.js');
var Stream = require('./stream.js');

Expand Down Expand Up @@ -42,25 +43,41 @@ SwfParser.prototype = {
// swfData is a buffer containing the SWF data
// onData(object) will be called several times with the result of the parsing of 1 object
// cb(error) will be called at the end or upon error
parse: function (swfName, swfData, onData, cb) {
parse: function (swfName, swfData, options, onData, cb) {
this.dataStorage = [];
this.swfName = swfName;
this._options = options;
this.onData = onData;

var stream = new Stream(swfData);

var stream = new Stream(swfData, options);
this._readSwlTable(stream); // Symbol table not used
var sign = stream.readString(3);

this.version = stream.readUI8();
var version = stream.readUI8();
this.fileLen = stream.readUI32();

if(this._options.forceVersion == null || this._options.forceVersion == -1){
this._options.version = version;
}
else{
this._options.version = forceVersion;
}

if(this._options.verbosity > 3){
console.log('Attempting to decompile for SWF Version:', this._options.version);
}

var signatures = Base.validSignatures;
if (sign === signatures.COMPRESSED_SWF) {
if (sign === signatures.COMPRESSED_ZLIB_SWF) {
var self = this;
stream.decompressAsync(function () {
self._parseSwf(stream, cb);
});
} else if (sign === signatures.SWF) {
this._parseSwf(stream, cb);
} else if (sign === signatures.COMPRESSED_LZMA_SWF) {
cb('No Support for LZMA Compressed SWF file: ' + swfName);
} else {
cb('Invalid SWF file: ' + swfName);
}
Expand Down Expand Up @@ -104,7 +121,7 @@ SwfParser.prototype = {
//create a "header" object with file level info
this.onData({
type: 'header',
version: this.version,
version: this._options.version,
fileLength: this.fileLen,
frameSize: stream.readRect(),
frameRate: stream.readUI16() / 256
Expand Down Expand Up @@ -252,9 +269,8 @@ SwfParser.prototype = {
_handleCsmTextSettings: function (stream, offset, len) {
this._skipEndOfTag('CsmTextSettings', true, stream, offset, len);
},
_handleDefineBinaryData: function (stream, offset, len) {
this._skipEndOfTag('DefineBinaryData', true, stream, offset, len);
},


_handleDefineEditText: function (stream, offset, len) {
var id = stream.readUI16();
var edit = { type: 'fakeEditText', id: id };
Expand Down Expand Up @@ -351,7 +367,26 @@ SwfParser.prototype = {
if (code !== 0 || len !== 1) throw new Error('Unexpected value in undocumented1 tag: ' + code + ',' + len);
},
//---------------------------------------------------------
_handleDefineBinaryData: function (stream, offset, len) {

var headerSize = 6;
var info = {
binaryId: stream.readUI16(), //16bit id
reserved: stream.readUB(32) //reserved must be 0
}
var data = stream.readBytes(len - headerSize);

var fileToSave = {
id: info.binaryId,
type: 'binary',
fileName: info.binaryId+'.unknown',
info: info,
bytes: data
};

this.dataStorage.push(fileToSave);
this.onData(fileToSave);
},
_handleSymbolClass: function (stream, offset, len, frm) {
var count = stream.readUI16();
while (count--) {
Expand Down Expand Up @@ -902,11 +937,19 @@ SwfParser.prototype = {
while (true) {
var flags = stream.readUI8();
if (!flags) break;
var objId = stream.readUI16(),
depth = stream.readUI16(),
var objId = stream.readUI16();
var pointingTo;
if(d[objId] != null) {
pointingTo = d[objId].id;
}
else {
console.warn('Button '+id+' is pointing to a non existing resource');
}

var depth = stream.readUI16(),
state = 0x01,
character = {
id: d[objId].id,
id: pointingTo,
depth: depth,
matrix: stream.readMatrix()
};
Expand Down Expand Up @@ -943,6 +986,85 @@ SwfParser.prototype = {
frm.bgcolor = stream.readRGB();
},

_handleSoundStreamHead2: function (stream, offset, len) {
var f = Base.soundFormats;
var headerSize = 4;//7 bytes as defined next
if(len == 4)
{
//just the header... absolutly no data? how/why is this possible?
this._skipEndOfTag('SoundStreamHead2', true, stream, offset, len);
return;
}

var info = {
reserved: stream.readUB(4), //not used, always 0
playbackSoundRate: stream.readUB(2), //enum: soundRates
playbackSoundSize: stream.readUB(1), //0: '8Bit', 1: '16Bit'
playbackSoundType: stream.readUB(1), //0: 'Mono', 1: 'Stereo'
streamSoundFormat: stream.readUB(4), //enum: soundFormats
streamSoundRate: stream.readUB(2), //enum: soundRates
streamSoundSize: stream.readUB(1), //0: '8Bit', 1: '16Bit'
streamSoundType: stream.readUB(1), //0: 'Mono', 1: 'Stereo'
streamSoundSampleCount: stream.readUI16() //--- in a single frame, I'm assuming there can't be more than 16 bits worth of sample
};

if(info.streamSoundRate == f.MP3)
{
info.latencySeek = stream.readSI16(); //only used for mp3
}

var soundData = stream.readBytes(len - headerSize);//is only a tiny bit of the information, todo: needs to be stored and joined together with the other audios of this block

console.warn('SoundStreamHead2 has '+ (len - headerSize) +' bytes of audio data which have not been handled.');
},

_handleDefineSound: function (stream, offset, len) {
var f = Base.soundFormats;
var headerSize = 7;//7 bytes as defined next

var info = {
soundId: stream.readUI16(),
soundFormat: stream.readUB(4),
soundRate: stream.readUB(2), //ignored for nellymoser and speex -- enums found in "soundRates"
soundSize: stream.readUB(1), //only matters for uncompressed, compressed always becomes 16bit. 0: '8Bit', 1: '16Bit'
soundType: stream.readUB(1), //Mono or stereo, ignored for Nellymoser and Speex, 0: 'Mono', 1: 'Stereo'
soundSampleCount: stream.readUI32()
};

var fileToSave;

switch(info.soundFormat){
case f.UNCOMPRESSED_NATIVE_ENDIAN:
case f.ADPCM:
case f.UNCOMPRESSED_LITTLE_ENDIAN:
case f.NELLYMOSER16KHZ:
case f.NELLYMOSER8KHZ:
case f.NELLYMOSER:
case f.SPEEX:
console.warn('Sound format: '+info.soundFormat+' not supported');
break;
case f.MP3:
var soundData = stream.readBytes(len - headerSize);
fileToSave = {
id: info.soundId,
type: 'sound',
fileName: info.soundId+'.mp3',
info: info,
bytes: soundData
};
break;
default:
throw new Error('Sound format not known: '+info.soundFormat);
}

if(fileToSave != null)
{
this.dataStorage.push(fileToSave);
}

this.onData(fileToSave);
},

_handleDefineFont: function (stream) {
var id = stream.readUI16(),
numGlyphs = stream.readUI16() / 2,
Expand Down Expand Up @@ -977,7 +1099,7 @@ SwfParser.prototype = {
isUTF8: stream.readBool(),
isItalic: stream.readBool(),
isBold: stream.readBool(),
languageCode: (this.version > 5) ? stream.readLanguageCode() : 0,
languageCode: (this._options.version > 5) ? stream.readLanguageCode() : 0,
name: stream.readUI8() && stream.readString()
};

Expand Down Expand Up @@ -1022,7 +1144,7 @@ SwfParser.prototype = {

_handleDefineFont3: function (stream, offset, len) {
var startingOffset = stream.offset;
// console.error('version', this.version)
// console.error('version', this._options.version)
// console.error('offset -1', stream.offset)
var id = stream.readUI16();
// console.error('offset 0', stream.offset)
Expand All @@ -1037,7 +1159,7 @@ SwfParser.prototype = {
isUTF8: stream.readBool(),
isItalic: stream.readBool(),
isBold: stream.readBool(),
languageCode: (this.version > 5) ? stream.readLanguageCode() : 0,
languageCode: (this._options.version > 5) ? stream.readLanguageCode() : 0,
name: stream.readUI8() && stream.readString()
};

Expand Down
17 changes: 11 additions & 6 deletions src/Gordon/stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@

var zlib = require('zlib');

function Stream(data){
function Stream(data, options){
this._buffer = new Buffer(data);
this._nBytes = data.length;
this.offset = 0;

this._options = options;

this._bitBuffer = null;
this._bitOffset = 8;
this._lastOffset = 0;
Expand Down Expand Up @@ -217,14 +219,17 @@ module.exports = Stream;
// We simply skip this useless zeros here, but verify it was actually zeros we skipped.
// If one day we find something else than an all zero rect (e.g. a 0,0,1,1) we will
// have to adapt some more here...

// Edit: it looks like this hack is making the export fail on some swf
// (may be those from the latest flash versions?)
// So the bytes need to be tested in order to know whether the hack is necessary
if (this._buffer.readUInt16LE(this.offset) === 0) {
var empty = this.readBytes(16); //console.warn('numBits=1, rect:',rect)
for(var i = 0; i < 16; i++) {
if(empty[i] !== 0) { throw new Error('Unexpected rectangle data'); }
if(this._options.version < 30)//Not exactly sure which version makes this work/break
{
if (this._buffer.readUInt16LE(this.offset) === 0) {
var empty = this.readBytes(16); //console.warn('numBits=1, rect:',rect)
for(var i = 0; i < 16; i++) {
if(empty[i] !== 0) { throw new Error('Unexpected rectangle data'); }
}
}
}
}
Expand Down
Loading