From 5603c4a01047c72a48a741d28d1d7efdb7f5f524 Mon Sep 17 00:00:00 2001 From: Bill Date: Thu, 12 Aug 2021 02:48:27 -0400 Subject: [PATCH] Skip bad buffers --- src/swf-reader/index.js | 703 +++++++++++++++++-------------- src/swf-reader/lib/SWFBuffer.ts | 225 ++++++++++ src/swf-reader/lib/swf-buffer.js | 291 ------------- src/swf-reader/lib/swf-tags.js | 11 +- 4 files changed, 611 insertions(+), 619 deletions(-) create mode 100644 src/swf-reader/lib/SWFBuffer.ts delete mode 100644 src/swf-reader/lib/swf-buffer.js diff --git a/src/swf-reader/index.js b/src/swf-reader/index.js index be77319..8085c51 100644 --- a/src/swf-reader/index.js +++ b/src/swf-reader/index.js @@ -5,265 +5,282 @@ * MIT LICENCE * */ +import { fs } from 'fs'; +import { lzma } from 'lzma-purejs'; +import { Stream } from 'stream'; +import { zlib } from 'zlib'; +import { SWFBuffer } from './lib/swf-buffer'; +import { SWFTags } from './lib/swf-tags'; +const SWFReader = exports; -var fs = require('fs') - , zlib = require('zlib') - , lzma = require('lzma-purejs') - , Stream = require('stream') - , SWFBuffer = require('./lib/swf-buffer') - , SWFTags = require('./lib/swf-tags') - , SWFReader = exports; +function readSWFTags(buff, swf) +{ + var tags = [] + , tag + , tagHeader + , flag + , l + , sc + , fc; -function readSWFTags(buff, swf) { - var tags = [] - , tag - , tagHeader - , flag - , l - , sc - , fc; + /* Reads TagCodeAndLength from Tag's RECORDHEADER */ + while( (tagHeader = buff.readTagCodeAndLength()) ) + { + tag = { + header : tagHeader + }; + switch( tagHeader.code ) + { + case SWFTags.FileAttributes: { + const flag = buff.readUIntLE(32); + const fileAttrs = {}; - /* Reads TagCodeAndLength from Tag's RECORDHEADER */ - while( (tagHeader = buff.readTagCodeAndLength()) ) { - tag = { - header : tagHeader - }; - switch( tagHeader.code ) { - case SWFTags.FileAttributes : - flag = buff.readUIntLE(32); - fileAttrs = {} + fileAttrs.useNetwork = tag.useNetwork = !!(flag & 0x1); + fileAttrs.as3 = tag.as3 = !!(flag & 0x8); + fileAttrs.hasMetaData = tag.hasMetaData = !!(flag & 0x10); + fileAttrs.useGPU = tag.useGPU = !!(flag & 0x20); + fileAttrs.useDirectBit = tag.useDirectBlit = !!(flag & 0x40); - fileAttrs.useNetwork = tag.useNetwork = !!(flag & 0x1); - fileAttrs.as3 = tag.as3 = !!(flag & 0x8); - fileAttrs.hasMetaData = tag.hasMetaData = !!(flag & 0x10); - fileAttrs.useGPU = tag.useGPU = !!(flag & 0x20); - fileAttrs.useDirectBit = tag.useDirectBlit = !!(flag & 0x40); + swf.fileAttributes = fileAttrs; + break; + } + case SWFTags.Metadata: { + swf.metadata = tag.metadata = buff.readString(); + break; + } + case SWFTags.SetBackgroundColor: { + tag.RGB = buff.readRGB(); + swf.backgroundColor = '#' + (tag.RGB[0]*65536 + tag.RGB[1]*256 + tag.RGB[0]).toString(16); + break; + } + case SWFTags.Protect : + swf.protect = tagHeader.length && buff.readString(); + break; + case SWFTags.DefineSceneAndFrameLabelData : + sc = tag.sceneCount = buff.readEncodedU32(); + tag.scenes = []; - swf.fileAttributes = fileAttrs; - break; - case SWFTags.Metadata : - swf.metadata = tag.metadata = buff.readString() - break; - case SWFTags.SetBackgroundColor : - tag.RGB = buff.readRGB(); - swf.backgroundColor = '#' + (tag.RGB[0]*65536 + tag.RGB[1]*256 + tag.RGB[0]).toString(16); - break; - case SWFTags.Protect : - swf.protect = tagHeader.length && buff.readString(); - break; - case SWFTags.DefineSceneAndFrameLabelData : - sc = tag.sceneCount = buff.readEncodedU32(); - tag.scenes = []; + while(sc--) + tag.scenes.push({ + offset : buff.readEncodedU32(), + name : buff.readString() + }); - while (sc--) - tag.scenes.push({ - offset : buff.readEncodedU32(), - name : buff.readString() - }); + fc = tag.frameLabelCount = buff.readEncodedU32(); + tag.labels = []; - fc = tag.frameLabelCount = buff.readEncodedU32(); - tag.labels = []; - - while (fc--) - tag.labels.push({ - frameNum : buff.readEncodedU32(), - frameLabel : buff.readString() - }); - break; - /** + while(fc--) + tag.labels.push({ + frameNum : buff.readEncodedU32(), + frameLabel : buff.readString() + }); + break; + /** * DefineShape4 extends the capabilities of * DefineShape3 by using a new line style * record in the shape */ - //case SWFTags.DefineShape4 : - // /* id for this character */ - // tag.ShapeId = buff.readUIntLE(16); - // /* bounds of the shape */ - // tag.ShapeBounds = buff.readRect(); - // /* bounds of the shape, excluding the strokes */ - // tag.EdgeBounds = buff.readRect(); - // /* reserved, must be 0 */ - // if (0 !== buff.readBits(5)) - // throw new Error('Reserved bit used.'); - // /* if 1, use fill winding. >= SWF 10 */ - // if (swf.version >= 10) - // tag.UsesFillWindingRule = buff.readBits(1); - // /** - // * if 1, shape contains at least one - // * non-scaling stroke. - // */ - // tag.UsesNonScallingStrokes = buff.readBits(1); - // /** - // * if 1, shape contains at least one - // * scaling stroke - // */ - // tag.UsesScalingStrokes = buff.readBits(1); - // tag.shapes = buff.readShapeWithStyle(); - // break; - case SWFTags.FrameLabel : - tag.name = buff.readString() - l = Buffer.byteLength(tag.name); - /* check if it's an named anchor */ - if (l & (tagHeader.length - 1) != l) - tag.anchor = buff.readUInt8(); - break; - case SWFTags.DefineSprite : - tag.SpriteID = buff.readUIntLE(16); - tag.FrameCount = buff.readUIntLE(16); - tag.ControlTags = readSWFTags(buff, swf); - break; - case SWFTags.ExportAssets : - tag.count = buff.readUIntLE(16); - tag.assets = []; + //case SWFTags.DefineShape4 : + // /* id for this character */ + // tag.ShapeId = buff.readUIntLE(16); + // /* bounds of the shape */ + // tag.ShapeBounds = buff.readRect(); + // /* bounds of the shape, excluding the strokes */ + // tag.EdgeBounds = buff.readRect(); + // /* reserved, must be 0 */ + // if (0 !== buff.readBits(5)) + // throw new Error('Reserved bit used.'); + // /* if 1, use fill winding. >= SWF 10 */ + // if (swf.version >= 10) + // tag.UsesFillWindingRule = buff.readBits(1); + // /** + // * if 1, shape contains at least one + // * non-scaling stroke. + // */ + // tag.UsesNonScallingStrokes = buff.readBits(1); + // /** + // * if 1, shape contains at least one + // * scaling stroke + // */ + // tag.UsesScalingStrokes = buff.readBits(1); + // tag.shapes = buff.readShapeWithStyle(); + // break; + case SWFTags.FrameLabel : + tag.name = buff.readString(); + l = Buffer.byteLength(tag.name); + /* check if it's an named anchor */ + if(l & (tagHeader.length - 1) != l) + tag.anchor = buff.readUInt8(); + break; + case SWFTags.DefineSprite : + tag.SpriteID = buff.readUIntLE(16); + tag.FrameCount = buff.readUIntLE(16); + tag.ControlTags = readSWFTags(buff, swf); + break; + case SWFTags.ExportAssets : + tag.count = buff.readUIntLE(16); + tag.assets = []; - l = 0; + l = 0; - while (l++ < tag.count) - tag.assets.push({ - id : buff.readUIntLE(16), - name : buff.readString() - }); - break; - case SWFTags.ImportAssets : - /** + while(l++ < tag.count) + tag.assets.push({ + id : buff.readUIntLE(16), + name : buff.readString() + }); + break; + case SWFTags.ImportAssets : + /** * URL where the source SWF file can be found */ - tag.url = buff.readString(); - /** + tag.url = buff.readString(); + /** * Number of assets to import */ - tag.count = buff.readUIntLE(16); - tag.assets = []; + tag.count = buff.readUIntLE(16); + tag.assets = []; - l = 0; + l = 0; - while (l++ < tag.count) - tag.assets.push({ - /** + while(l++ < tag.count) + tag.assets.push({ + /** * Character ID for the l-th item * in importing SWF file */ - id : buff.readUIntLE(16), - /** + id : buff.readUIntLE(16), + /** * Identifies for the l-th * imported character */ - name : buff.readString() - }); - break; - case SWFTags.ImportAssets2 : - tag.url = buff.readString(); + name : buff.readString() + }); + break; + case SWFTags.ImportAssets2 : + tag.url = buff.readString(); - if ( !(1 === buff.readUInt8() && 0 === buff.readUInt8()) ) { - throw new Error('Reserved bits for ImportAssets2 used'); - } + if( !(1 === buff.readUInt8() && 0 === buff.readUInt8()) ) + { + throw new Error('Reserved bits for ImportAssets2 used'); + } - tag.count = buff.readUIntLE(16); - tag.assets = []; + tag.count = buff.readUIntLE(16); + tag.assets = []; - l = 0; + l = 0; - while (l++ < tag.count) - tag.assets({ - id : buff.readUIntLE(16), - name : buff.readString() - }); - break; - case SWFTags.EnableDebbuger : - tag.password = buff.readString() - break; - case SWFTags.EnableDebugger2 : - if (0 !== buff.readUIntLE(16)) { - //throw new Error('Reserved bit for EnableDebugger2 used.'); - } - tag.password = buff.readString() - break; - case SWFTags.ScriptLimits : - /** + while(l++ < tag.count) + tag.assets({ + id : buff.readUIntLE(16), + name : buff.readString() + }); + break; + case SWFTags.EnableDebbuger : + tag.password = buff.readString(); + break; + case SWFTags.EnableDebugger2 : + if(0 !== buff.readUIntLE(16)) + { + //throw new Error('Reserved bit for EnableDebugger2 used.'); + } + tag.password = buff.readString(); + break; + case SWFTags.ScriptLimits : + /** * Maximum recursion Depth */ - tag.maxRecursionDepth = buff.readUIntLE(16); - /** + tag.maxRecursionDepth = buff.readUIntLE(16); + /** * Maximum ActionScript processing time before script * stuck dialog box displays */ - tag.scriptTimeoutSeconds = buff.readUIntLE(16); - break; - case SWFTags.SymbolClass : - tag.numSymbols = buff.readUIntLE(16); - tag.symbols = []; + tag.scriptTimeoutSeconds = buff.readUIntLE(16); + break; + case SWFTags.SymbolClass: { + tag.numSymbols = buff.readUIntLE(16); + tag.symbols = []; - l = 0; + l = 0; - while (l++ < tag.numSymbols) - tag.symbols.push({ - id : buff.readUIntLE(16), - name : buff.readString() - }); - break; - case SWFTags.DefineScalingGrid : - tag.characterId = buff.readUIntLE(16); - tag.splitter = buff.readRect(); - break; - case SWFTags.setTabIndex : - tag.depth = buff.readUIntLE(16); - tag.tabIndex = buff.readUIntLE(16); - break; - case SWFTags.JPEGTables: - tag.jpegData = buff.buffer.slice(buff.pointer, buff.pointer + tagHeader.length); - buff.pointer += tagHeader.length; - break; - case SWFTags.DefineBits: - tag.characterId = buff.readUIntLE(16); - tag.jpegData = buff.buffer.slice(buff.pointer, buff.pointer + tagHeader.length - 2); - buff.pointer += tagHeader.length - 2; - break; - case SWFTags.DefineBitsJPEG2: - tag.characterId = buff.readUIntLE(16); - tag.imageData = buff.buffer.slice(buff.pointer, buff.pointer + tagHeader.length - 2); - buff.pointer += tagHeader.length - 2; - break; - case SWFTags.DefineBitsJPEG3: - tag.characterId = buff.readUIntLE(16); - var alphaDataOffset = buff.readUIntLE(32); - tag.imageData = buff.buffer.slice(buff.pointer, buff.pointer + alphaDataOffset); - buff.pointer += alphaDataOffset; - var restLength = tagHeader.length - 6 - alphaDataOffset; - tag.bitmapAlphaData = buff.buffer.slice(buff.pointer, buff.pointer + restLength); - buff.pointer += restLength; - break; - case SWFTags.DefineBitsJPEG4: - tag.characterId = buff.readUIntLE(16); - var alphaDataOffset = buff.readUIntLE(32); - tag.deblockParam = buff.readUIntLE(16); - tag.imageData = buff.buffer.slice(buff.pointer, buff.pointer + alphaDataOffset); - buff.pointer += alphaDataOffset; - var restLength = tagHeader.length - 8 - alphaDataOffset; - tag.bitmapAlphaData = buff.buffer.slice(buff.pointer, buff.pointer + restLength); - buff.pointer += restLength; - break; - case SWFTags.DefineBitsLossless: - case SWFTags.DefineBitsLossless2: - tag.characterId = buff.readUIntLE(16); - tag.bitmapFormat = buff.readUInt8(); - tag.bitmapWidth = buff.readUIntLE(16); - tag.bitmapHeight = buff.readUIntLE(16); - var restLength = tagHeader.length - 7; - if (tag.bitmapFormat == 3) { - tag.bitmapColorTableSize = buff.readUInt8(); - restLength--; + while(l++ < tag.numSymbols) + tag.symbols.push({ + id : buff.readUIntLE(16), + name : buff.readString() + }); + break; + } + case SWFTags.DefineScalingGrid: { + tag.characterId = buff.readUIntLE(16); + tag.splitter = buff.readRect(); + break; + } + case SWFTags.setTabIndex: { + tag.depth = buff.readUIntLE(16); + tag.tabIndex = buff.readUIntLE(16); + break; + } + case SWFTags.JPEGTables: { + tag.jpegData = buff.buffer.slice(buff.pointer, buff.pointer + tagHeader.length); + buff.pointer += tagHeader.length; + break; + } + case SWFTags.DefineBits: { + tag.characterId = buff.readUIntLE(16); + tag.jpegData = buff.buffer.slice(buff.pointer, buff.pointer + tagHeader.length - 2); + buff.pointer += tagHeader.length - 2; + break; + } + case SWFTags.DefineBitsJPEG2: { + tag.characterId = buff.readUIntLE(16); + tag.imageData = buff.buffer.slice(buff.pointer, buff.pointer + tagHeader.length - 2); + buff.pointer += tagHeader.length - 2; + break; + } + case SWFTags.DefineBitsJPEG3: { + tag.characterId = buff.readUIntLE(16); + var alphaDataOffset = buff.readUIntLE(32); + tag.imageData = buff.buffer.slice(buff.pointer, buff.pointer + alphaDataOffset); + buff.pointer += alphaDataOffset; + var restLength = tagHeader.length - 6 - alphaDataOffset; + tag.bitmapAlphaData = buff.buffer.slice(buff.pointer, buff.pointer + restLength); + buff.pointer += restLength; + break; + } + case SWFTags.DefineBitsJPEG4: { + tag.characterId = buff.readUIntLE(16); + const alphaDataOffset = buff.readUIntLE(32); + tag.deblockParam = buff.readUIntLE(16); + tag.imageData = buff.buffer.slice(buff.pointer, buff.pointer + alphaDataOffset); + buff.pointer += alphaDataOffset; + const restLength = tagHeader.length - 8 - alphaDataOffset; + tag.bitmapAlphaData = buff.buffer.slice(buff.pointer, buff.pointer + restLength); + buff.pointer += restLength; + break; + } + case SWFTags.DefineBitsLossless: + case SWFTags.DefineBitsLossless2: { + tag.characterId = buff.readUIntLE(16); + tag.bitmapFormat = buff.readUInt8(); + tag.bitmapWidth = buff.readUIntLE(16); + tag.bitmapHeight = buff.readUIntLE(16); + let restLength = tagHeader.length - 7; + if(tag.bitmapFormat == 3) + { + tag.bitmapColorTableSize = buff.readUInt8(); + restLength--; + } + tag.zlibBitmapData = buff.buffer.slice(buff.pointer, buff.pointer + restLength); + buff.pointer += restLength; + break; + } + default: + tag.data = buff.buffer.slice(buff.pointer, buff.pointer + tagHeader.length); + buff.pointer += tagHeader.length; + break; } - tag.zlibBitmapData = buff.buffer.slice(buff.pointer, buff.pointer + restLength); - buff.pointer += restLength; - break; - default: - tag.data = buff.buffer.slice(buff.pointer, buff.pointer + tagHeader.length); - buff.pointer += tagHeader.length; - break; + tags.push(tag); } - tags.push(tag); - } - return tags; + return tags; } /** @@ -275,33 +292,38 @@ function readSWFTags(buff, swf) { * @api private * */ -function readSWFBuff(buff, compressed_buff, next) { - buff.seek(3);// start +function readSWFBuff(buff, compressed_buff, next) +{ + buff.seek(3);// start - if (buff.length < 9) { - if (isSync) throw new Error("Buffer is to small, must be greater than 9 bytes."); - return next(new Error("Buffer is to small, must be greater than 9 bytes.")); + if(buff.length < 9) + { + if(isSync) throw new Error('Buffer is to small, must be greater than 9 bytes.'); + return next(new Error('Buffer is to small, must be greater than 9 bytes.')); } - var swf = { - version : buff.readUInt8(), - fileLength : { - compressed : compressed_buff.length, - uncompressed : buff.readUIntLE(32) - }, - frameSize : buff.readRect(), // Returns a RECT object. i.e : { x : 0, y : 0, width : 200, height: 300 } - frameRate : buff.readUIntLE(16)/256, - frameCount : buff.readUIntLE(16) - } - , isSync = 'function' !== typeof next; + var swf = { + version : buff.readUInt8(), + fileLength : { + compressed : compressed_buff.length, + uncompressed : buff.readUIntLE(32) + }, + frameSize : buff.readRect(), // Returns a RECT object. i.e : { x : 0, y : 0, width : 200, height: 300 } + frameRate : buff.readUIntLE(16)/256, + frameCount : buff.readUIntLE(16) + } + , isSync = 'function' !== typeof next; - try { - swf.tags = readSWFTags(buff, swf); - } catch(e) { - if (isSync) throw e; - return next(e); - } + try + { + swf.tags = readSWFTags(buff, swf); + } + catch (e) + { + if(isSync) throw e; + return next(e); + } - return isSync && swf || next( null, swf ); + return isSync && swf || next( null, swf ); } /** @@ -310,8 +332,9 @@ function readSWFBuff(buff, compressed_buff, next) { * @param {Buffer|ArrayBuffer} buff * @param {Buffer|ArrayBuffer} swf */ -function concatSWFHeader(buff, swf) { - return Buffer.concat([swf.slice(0, 8), buff]); +function concatSWFHeader(buff, swf) +{ + return Buffer.concat([swf.slice(0, 8), buff]); } /** @@ -321,73 +344,97 @@ function concatSWFHeader(buff, swf) { * @param {function} callback * */ -function uncompress(swf, next) { - var compressed_buff = swf.slice(8) - , uncompressed_buff - , isSync = 'function' !== typeof next - , e; +function uncompress(swf, next) +{ + var compressed_buff = swf.slice(8) + , uncompressed_buff + , isSync = 'function' !== typeof next + , e; // uncompress buffer - switch( swf[0] ) { - case 0x43 : // zlib compressed - if (isSync) { - uncompressed_buff = concatSWFHeader(zlib.unzipSync(compressed_buff), swf); - return readSWFBuff(new SWFBuffer(uncompressed_buff), swf); - } + switch(swf[0]) + { + case 0x43 : // zlib compressed + if(isSync) + { + uncompressed_buff = concatSWFHeader(zlib.unzipSync(compressed_buff), swf); - zlib.inflate(compressed_buff, function(err, buf) { - readSWFBuff(new SWFBuffer(buf), swf, next); - }); - break; - case 0x46 : // uncompressed - return readSWFBuff(new SWFBuffer( swf ), swf, next); - break; - case 0x5a : // LZMA compressed - var lzmaProperties = compressed_buff.slice(4, 9); - compressed_buff = compressed_buff.slice(9); + if(!Buffer.isBuffer(uncompressed_buff)) + { + console.log('invalid_buffer'); - var input_stream = new Stream(); - input_stream.pos = 0; - input_stream.readByte = function() { - return this.pos >= compressed_buff.length ? -1 : compressed_buff[this.pos++]; - }; + return null; + } + return readSWFBuff(new SWFBuffer(uncompressed_buff), swf); + } - var output_stream = new Stream(); - output_stream.buffer = new Buffer(16384); - output_stream.pos = 0; - output_stream.writeByte = function(_byte) { - if (this.pos >= this.buffer.length) { - var newBuffer = new Buffer(this.buffer.length * 2); - this.buffer.copy(newBuffer); - this.buffer = newBuffer; - } - this.buffer[this.pos++] = _byte; - }; - output_stream.getBuffer = function() { - // trim buffer - if (this.pos !== this.buffer.length) { - var newBuffer = new Buffer(this.pos); - this.buffer.copy(newBuffer, 0, 0, this.pos); - this.buffer = newBuffer; - } - return this.buffer; - }; + if(!Buffer.isBuffer(compressed_buff)) + { + console.log('invalid_buffer'); - lzma.decompress(lzmaProperties, input_stream, output_stream, -1); - uncompressed_buff = Buffer.concat([swf.slice(0, 8), output_stream.getBuffer()]); + return null; + } - return readSWFBuff(new SWFBuffer(uncompressed_buff), swf, next); - break; - default : - e = new Error('Unknown SWF compressions'); + zlib.inflate(compressed_buff, function(err, buf) + { + readSWFBuff(new SWFBuffer(buf), swf, next); + }); + break; + case 0x46 : // uncompressed + return readSWFBuff(new SWFBuffer( swf ), swf, next); + case 0x5a : // LZMA compressed + var lzmaProperties = compressed_buff.slice(4, 9); + compressed_buff = compressed_buff.slice(9); - if (isSync) { - throw e; - } else { - next(e); - } - }; -}; + var input_stream = new Stream(); + input_stream.pos = 0; + input_stream.readByte = function() + { + return this.pos >= compressed_buff.length ? -1 : compressed_buff[this.pos++]; + }; + + var output_stream = new Stream(); + output_stream.buffer = new Buffer(16384); + output_stream.pos = 0; + output_stream.writeByte = function(_byte) + { + if(this.pos >= this.buffer.length) + { + var newBuffer = new Buffer(this.buffer.length * 2); + this.buffer.copy(newBuffer); + this.buffer = newBuffer; + } + this.buffer[this.pos++] = _byte; + }; + output_stream.getBuffer = function() + { + // trim buffer + if(this.pos !== this.buffer.length) + { + var newBuffer = new Buffer(this.pos); + this.buffer.copy(newBuffer, 0, 0, this.pos); + this.buffer = newBuffer; + } + return this.buffer; + }; + + lzma.decompress(lzmaProperties, input_stream, output_stream, -1); + uncompressed_buff = Buffer.concat([swf.slice(0, 8), output_stream.getBuffer()]); + + return readSWFBuff(new SWFBuffer(uncompressed_buff), swf, next); + default : + e = new Error('Unknown SWF compressions'); + + if(isSync) + { + throw e; + } + else + { + next(e); + } + } +} /** * Check if file is Buffer or ArrayBuffer @@ -396,8 +443,9 @@ function uncompress(swf, next) { * @api private * */ -function isBuffer(b) { - return typeof Buffer !== "undefined" && Buffer.isBuffer(b) || b instanceof ArrayBuffer; +function isBuffer(b) +{ + return typeof Buffer !== 'undefined' && Buffer.isBuffer(b) || b instanceof ArrayBuffer; } /* Exposes Tags constants */ @@ -411,22 +459,31 @@ SWFReader.TAGS = SWFTags; * @api public * */ -SWFReader.read = SWFReader.readSync = function(file, next) { - if (isBuffer(file)) { +SWFReader.read = SWFReader.readSync = function(file, next) +{ + if(isBuffer(file)) + { /* File is already a buffer */ - return uncompress(file, next); - } else { - /* Get the buffer */ - if ('function' === typeof next) { - fs.readFile(file, function(err, swf) { - if ( err ) { - next(err); - return; - } - uncompress(swf, next); - }); - } else { - return uncompress(fs.readFileSync(file)); + return uncompress(file, next); + } + else + { + /* Get the buffer */ + if('function' === typeof next) + { + fs.readFile(file, function(err, swf) + { + if( err ) + { + next(err); + return; + } + uncompress(swf, next); + }); + } + else + { + return uncompress(fs.readFileSync(file)); + } } - } }; diff --git a/src/swf-reader/lib/SWFBuffer.ts b/src/swf-reader/lib/SWFBuffer.ts new file mode 100644 index 0000000..bbc9012 --- /dev/null +++ b/src/swf-reader/lib/SWFBuffer.ts @@ -0,0 +1,225 @@ +export class SWFBuffer +{ + public static RECORDHEADER_LENTH_FULL: number = 0x3f; + public static EOS: number = 0x00; + public static STYLE_COUNT_EXT: number = 0xFF; + + public buffer: Buffer + public pointer: number = 0; + public position: number = 1; + public current: number = 0; + public length: number = 0; + + constructor(buffer: Buffer) + { + if(!Buffer.isBuffer(buffer)) + { + throw new Error('invalid_buffer'); + } + + this.buffer = buffer; + this.length = buffer.length; + } + + public readUIntLE(bits: number): number + { + let value = 0; + + switch(bits) + { + case 16: + value = this.buffer.readUInt16LE(this.pointer); + break; + case 32: + value = this.buffer.readUInt32LE(this.pointer); + break; + } + + this.pointer += bits / 8; + + return value; + } + + public readUInt8(): number + { + return this.buffer.readUInt8(this.pointer++); + } + + public readEncodedU32(): number + { + let i = 5; + let result = 0; + let nb = 0; + + do + { + result += (nb = this.nextByte()); + } while((nb & 128) && --i); + + return result; + } + + public readRGB(): [ number, number, number ] + { + return [ this.readUInt8(), this.readUInt8(), this.readUInt8() ]; + } + + public readRGBA(): [ number, number, number, number ] + { + return [ ...this.readRGB(), this.readUInt8() ]; + } + + public readString(encoding: BufferEncoding): string + { + const init = this.pointer; + + while(this.readUInt8() !== SWFBuffer.EOS); + + return this.buffer.toString(encoding || 'utf8', init, this.pointer - 1); + } + + public readStyleArray(buffer: SWFBuffer, next) + { + let styleArrayCount = buffer.readUInt8(); + const styles = []; + + if(styleArrayCount === SWFBuffer.STYLE_COUNT_EXT) styleArrayCount = buffer.readUIntLE(16); + + for(let i = 0; i < styleArrayCount; i++) styles.push(next(buffer)); + + return styles; + } + + public readFillStyle(buffer: SWFBuffer): { fillStyleType: number, color?: [ number, number, number, number ], bitmapId?: number } + { + const type = buffer.readUInt8(); + + const fillStyle: { fillStyleType: number, color?: [ number, number, number, number ], bitmapId?: number } = { + fillStyleType: type + }; + + switch(type) + { + case 0x00: + fillStyle.color = buffer.readRGBA(); + break; + case 0x10: + case 0x12: + case 0x13: + console.log('Gradient'); + break; + case 0x40: + case 0x41: + case 0x42: + case 0x43: + fillStyle.bitmapId = buffer.readUIntLE(16); + break; + } + + return fillStyle; + } + + public readLineStyle(buffer: SWFBuffer): { width: number, color: [ number, number, number, number ]} + { + return { + width: buffer.readUIntLE(16)/20, + color: buffer.readRGBA() + }; + } + + public readShapeRecords(buffer: SWFBuffer) + { + let shapeRecords = null; + const typeFlag = buffer.readBits(1); + let eos = 0; + + while((eos = buffer.readBits(5))) + { + if(0 === typeFlag) + { + shapeRecords = { + type: 'STYLECHANGERECORD' + }; + } + } + + return shapeRecords; + } + + public readShapeWithStyle() + { + return { + fillStyles : this.readStyleArray(this, this.readFillStyle), + lineStyles : this.readStyleArray(this, this.readLineStyle), + numFillBits : this.readBits(4), + numLineBits : this.readBits(4), + shapeRecords: this.readShapeRecords(this) + }; + } + + public readTagCodeAndLength(): { code: number, length: number } + { + if(this.pointer === this.length) return null; + + const n = this.readUIntLE(16); + const tagType = n >> 6; + let tagLength = n & SWFBuffer.RECORDHEADER_LENTH_FULL; + + if(n === 0) return null; + + if(tagLength === SWFBuffer.RECORDHEADER_LENTH_FULL ) tagLength = this.readUIntLE(32); + + return { code: tagType, length: tagLength }; + } + + public readRect(): { x: number, y: number, width: number, height: number } + { + this.start(); + + const NBits = this.readBits(5); + const Xmin = this.readBits(NBits, true) / 20; + const Xmax = this.readBits(NBits, true) / 20; + const Ymin = this.readBits(NBits, true) / 20; + const Ymax = this.readBits(NBits, true) / 20; + + return { + x : Xmin, + y : Ymin, + width : (Xmax > Xmin ? Xmax - Xmin : Xmin - Xmax), + height : (Ymax > Ymin ? Ymax - Ymin : Ymin - Ymax) + }; + } + + public seek(pos: number): void + { + this.pointer = pos % this.buffer.length; + } + + public start(): void + { + this.current = this.nextByte(); + this.position = 1; + } + + public nextByte(): number + { + return this.pointer > this.buffer.length ? null : this.buffer[ this.pointer++ ]; + } + + public readBits(b: number, signed: boolean = false): number + { + let n = 0; + let r = 0; + + const sign = signed && ++n && ((this.current >> (8 - this.position++)) & 1) ? -1 : 1; + + while(n++ < b) + { + if( this.position > 8 ) this.start(); + + r = (r << 1 ) + ((this.current >> (8 - this.position++)) & 1); + } + + return sign * r; + } +} diff --git a/src/swf-reader/lib/swf-buffer.js b/src/swf-reader/lib/swf-buffer.js deleted file mode 100644 index 3937709..0000000 --- a/src/swf-reader/lib/swf-buffer.js +++ /dev/null @@ -1,291 +0,0 @@ - -var RECORDHEADER_LENTH_FULL = 0x3f - // null-character - , EOS = 0x00 - , styleCountExt = 0xFF; - -function readStyleArray(buffer, next) { - var styleArrayCount = buffer.readUInt8() - , styles = []; - - if (styleArrayCount === styleCountExt) - styleArrayCount = buffer.readUIntLE(16); - - for (var i = 0; i < styleArrayCount; i++) - styles.push(next(buffer)); - - return styles; -} - -function readFillStyle(buffer) { - var type = buffer.readUInt8() - , fillStyle = { - /** - * 0x00 = solid - * 0x10 = linear gradient fill - * 0x12 = radial gradient fill - * 0x13 = focal radial gradient fill (SWF 8 or later) - * 0x40 = repeating bitmap fill - * 0x41 = clipped bitmap fill - * 0x42 = non-smoothed repeating bitmap - * 0x43 = non-smoothed clipped bitmap - */ - fillStyleType : type - }; - - switch (type) { - case 0x00: - fillStyle.color = buffer.readRGBA(); - break; - case 0x10, 0x12, 0x13: - console.log('Gradient'); - break; - case 0x40, 0x41, 0x42, 0x43: - fillStyle.bitmapId = buffer.readUIntLE(16); - break; - } - - return fillStyle; -} - -function readLineStyle(buffer) { - return { - width: buffer.readUIntLE(16)/20, - color: buffer.readRGBA() - }; -} - -function readShapeRecords(buffer) { - var shapeRecords = [] - , typeFlag = buffer.readBits(1) - , shapeRecord - , eos; - - while ((eos = buffer.readBits(5))) { - if (0 === typeFlag) { - shaperecord = { - type: 'STYLECHANGERECORD' - }; - } - } - - return shapeRecords; -} - -/** - * - * Constructor of SWFBuffer object - * - * @param {Buffer} buffer - * @return Instance of SWFBuffer - */ - -function SWFBuffer( buffer ) { - if ( !Buffer.isBuffer( buffer ) ) { - throw new Error('Invalid buffer'); - } - this.buffer = buffer; - this.pointer = 0; - this.position = 1; - this.current = 0; - this.length = buffer.length; -} - -/** - * Reads unsigned 16 or 32 Little Endian Bits - * and advance pointer to next bits / 8 bytes - * - * @param {Number} bits - * @return {Number} Value read from buffer - */ - -SWFBuffer.prototype.readUIntLE = function( bits ) { - var value = 0; - try { - value = this.buffer['readUInt' + bits + 'LE'](this.pointer); - this.pointer += bits / 8; - } catch ( e ) { - throw e; - } - return value; -}; - -/** - * Reads unsigned 8 bit from the buffer - * - * @return {Number} Value read from buffer - */ - -SWFBuffer.prototype.readUInt8 = function() { - return this.buffer.readUInt8( this.pointer++ ); -}; - -/** - * Reads 32-bit unsigned integers value encoded (1-5 bytes) - * - * @return {Number} 32-bit unsigned integer - */ - -SWFBuffer.prototype.readEncodedU32 = function() { - var i = 5 - , result = 0 - , nb; - - do - result += (nb = this.nextByte()); - while((nb & 128) && --i); - - return result; -}; - -/** - * Reads an encoded data from buffer and returns a - * string using the specified character set. - * - * @param {String} encoding - defaults to 'utf8' - * @returns {String} Decoded string - */ - -SWFBuffer.prototype.readString = function(encoding) { - var init = this.pointer; - while(this.readUInt8() !== EOS); - return this.buffer.toString(encoding || 'utf8', init, this.pointer - 1); -}; - -/** - * Reads RGB value - * - * @return {Array} Array of RGB value - */ - -SWFBuffer.prototype.readRGB = function() { - return [this.readUInt8(), this.readUInt8(), this.readUInt8()]; -}; - -/** - * Reads RGBA value - * - * @return {Array} Array of RGBA value - */ - -SWFBuffer.prototype.readRGBA = function() { - var rgba = this.readRGB(); - rgba.push(this.readUInt8()); - return rgba; -} - -/** - * Reads ShapeWithStyle structure - * used by the DefineShape tag. - * - * @return ShapeWithStyle structure - */ -SWFBuffer.prototype.readShapeWithStyle = function() { - return { - fillStyles : readStyleArray(this, readFillStyle), - lineStyles : readStyleArray(this, readLineStyle), - numFillBits : this.readBits(4), - numLineBits : this.readBits(4), - shapeRecords: readShapeRecords(this) - } -}; - -/** - * Reads RECORDHEADER from next tag in the buffer - * - * @return {Object} Tag code and length - */ - -SWFBuffer.prototype.readTagCodeAndLength = function() { - if (this.pointer === this.length) { - return false; - } - - var n = this.readUIntLE(16) - , tagType = n >> 6 - , tagLength = n & RECORDHEADER_LENTH_FULL; - - if ( n === 0 ) - return false; - - if ( tagLength === RECORDHEADER_LENTH_FULL ) - tagLength = this.readUIntLE(32); - - return { code : tagType, length : tagLength }; -}; - -/** - * Reads RECT format - * - * @return {Object} x, y, width and height of the RECT - */ - -SWFBuffer.prototype.readRect = function() { - - this.start(); - - var NBits = this.readBits(5) - , Xmin = this.readBits(NBits, true)/20 - , Xmax = this.readBits(NBits, true)/20 - , Ymin = this.readBits(NBits, true)/20 - , Ymax = this.readBits(NBits, true)/20; - - return { - x : Xmin, - y : Ymin, - width : (Xmax > Xmin ? Xmax - Xmin : Xmin - Xmax), - height : (Ymax > Ymin ? Ymax - Ymin : Ymin - Ymax) - }; - -} - -/** - * Sets internal pointer to the specified position; - * - * @param {Number} pos - */ - -SWFBuffer.prototype.seek = function( pos ) { - this.pointer = pos % this.buffer.length; -}; - -/** - * Resets position and sets current to next Byte in buffer - */ -SWFBuffer.prototype.start = function() { - this.current = this.nextByte(); - this.position = 1; -}; - -/** - * Gets next Byte in the buffer and Increment internal pointer - * - * @return {Number} Next byte in buffer - */ - -SWFBuffer.prototype.nextByte = function() { - return this.pointer > this.buffer.length ? null : this.buffer[ this.pointer++ ]; -}; - -/** - * Reads b bits from current byte in buffer - * - * @param {Number} b - * @return {Number} Bits read from buffer - */ - -SWFBuffer.prototype.readBits = function( b, signed ) { - var n = 0 - , r = 0 - , sign = signed && ++n && ((this.current >> (8-this.position++)) & 1) ? -1 : 1; - - while( n++ < b ) { - if ( this.position > 8 ) this.start(); - - r = (r << 1 ) + ((this.current >> (8-this.position++)) & 1); - } - return sign * r; -}; - -/* Exposes class */ -exports = module.exports = SWFBuffer; diff --git a/src/swf-reader/lib/swf-tags.js b/src/swf-reader/lib/swf-tags.js index c27f731..26a6dd0 100644 --- a/src/swf-reader/lib/swf-tags.js +++ b/src/swf-reader/lib/swf-tags.js @@ -5,11 +5,12 @@ * @param {Mixed} value */ -function define(name, value) { - Object.defineProperty(exports, name, { - value : value, - enumerable : true - }); +function define(name, value) +{ + Object.defineProperty(exports, name, { + value : value, + enumerable : true + }); } /* SWF Tags Type */