Skip bad buffers

This commit is contained in:
Bill 2021-08-12 02:48:27 -04:00
parent e681a316a1
commit 5603c4a010
4 changed files with 611 additions and 619 deletions

View File

@ -5,16 +5,16 @@
* MIT LICENCE * 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') function readSWFTags(buff, swf)
, 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 = [] var tags = []
, tag , tag
, tagHeader , tagHeader
@ -24,14 +24,16 @@ function readSWFTags(buff, swf) {
, fc; , fc;
/* Reads TagCodeAndLength from Tag's RECORDHEADER */ /* Reads TagCodeAndLength from Tag's RECORDHEADER */
while( (tagHeader = buff.readTagCodeAndLength()) ) { while( (tagHeader = buff.readTagCodeAndLength()) )
{
tag = { tag = {
header : tagHeader header : tagHeader
}; };
switch( tagHeader.code ) { switch( tagHeader.code )
case SWFTags.FileAttributes : {
flag = buff.readUIntLE(32); case SWFTags.FileAttributes: {
fileAttrs = {} const flag = buff.readUIntLE(32);
const fileAttrs = {};
fileAttrs.useNetwork = tag.useNetwork = !!(flag & 0x1); fileAttrs.useNetwork = tag.useNetwork = !!(flag & 0x1);
fileAttrs.as3 = tag.as3 = !!(flag & 0x8); fileAttrs.as3 = tag.as3 = !!(flag & 0x8);
@ -41,13 +43,16 @@ function readSWFTags(buff, swf) {
swf.fileAttributes = fileAttrs; swf.fileAttributes = fileAttrs;
break; break;
case SWFTags.Metadata : }
swf.metadata = tag.metadata = buff.readString() case SWFTags.Metadata: {
swf.metadata = tag.metadata = buff.readString();
break; break;
case SWFTags.SetBackgroundColor : }
case SWFTags.SetBackgroundColor: {
tag.RGB = buff.readRGB(); tag.RGB = buff.readRGB();
swf.backgroundColor = '#' + (tag.RGB[0]*65536 + tag.RGB[1]*256 + tag.RGB[0]).toString(16); swf.backgroundColor = '#' + (tag.RGB[0]*65536 + tag.RGB[1]*256 + tag.RGB[0]).toString(16);
break; break;
}
case SWFTags.Protect : case SWFTags.Protect :
swf.protect = tagHeader.length && buff.readString(); swf.protect = tagHeader.length && buff.readString();
break; break;
@ -101,7 +106,7 @@ function readSWFTags(buff, swf) {
// tag.shapes = buff.readShapeWithStyle(); // tag.shapes = buff.readShapeWithStyle();
// break; // break;
case SWFTags.FrameLabel : case SWFTags.FrameLabel :
tag.name = buff.readString() tag.name = buff.readString();
l = Buffer.byteLength(tag.name); l = Buffer.byteLength(tag.name);
/* check if it's an named anchor */ /* check if it's an named anchor */
if(l & (tagHeader.length - 1) != l) if(l & (tagHeader.length - 1) != l)
@ -154,7 +159,8 @@ function readSWFTags(buff, swf) {
case SWFTags.ImportAssets2 : case SWFTags.ImportAssets2 :
tag.url = buff.readString(); tag.url = buff.readString();
if ( !(1 === buff.readUInt8() && 0 === buff.readUInt8()) ) { if( !(1 === buff.readUInt8() && 0 === buff.readUInt8()) )
{
throw new Error('Reserved bits for ImportAssets2 used'); throw new Error('Reserved bits for ImportAssets2 used');
} }
@ -170,13 +176,14 @@ function readSWFTags(buff, swf) {
}); });
break; break;
case SWFTags.EnableDebbuger : case SWFTags.EnableDebbuger :
tag.password = buff.readString() tag.password = buff.readString();
break; break;
case SWFTags.EnableDebugger2 : case SWFTags.EnableDebugger2 :
if (0 !== buff.readUIntLE(16)) { if(0 !== buff.readUIntLE(16))
{
//throw new Error('Reserved bit for EnableDebugger2 used.'); //throw new Error('Reserved bit for EnableDebugger2 used.');
} }
tag.password = buff.readString() tag.password = buff.readString();
break; break;
case SWFTags.ScriptLimits : case SWFTags.ScriptLimits :
/** /**
@ -189,7 +196,7 @@ function readSWFTags(buff, swf) {
*/ */
tag.scriptTimeoutSeconds = buff.readUIntLE(16); tag.scriptTimeoutSeconds = buff.readUIntLE(16);
break; break;
case SWFTags.SymbolClass : case SWFTags.SymbolClass: {
tag.numSymbols = buff.readUIntLE(16); tag.numSymbols = buff.readUIntLE(16);
tag.symbols = []; tag.symbols = [];
@ -201,29 +208,35 @@ function readSWFTags(buff, swf) {
name : buff.readString() name : buff.readString()
}); });
break; break;
case SWFTags.DefineScalingGrid : }
case SWFTags.DefineScalingGrid: {
tag.characterId = buff.readUIntLE(16); tag.characterId = buff.readUIntLE(16);
tag.splitter = buff.readRect(); tag.splitter = buff.readRect();
break; break;
case SWFTags.setTabIndex : }
case SWFTags.setTabIndex: {
tag.depth = buff.readUIntLE(16); tag.depth = buff.readUIntLE(16);
tag.tabIndex = buff.readUIntLE(16); tag.tabIndex = buff.readUIntLE(16);
break; break;
case SWFTags.JPEGTables: }
case SWFTags.JPEGTables: {
tag.jpegData = buff.buffer.slice(buff.pointer, buff.pointer + tagHeader.length); tag.jpegData = buff.buffer.slice(buff.pointer, buff.pointer + tagHeader.length);
buff.pointer += tagHeader.length; buff.pointer += tagHeader.length;
break; break;
case SWFTags.DefineBits: }
case SWFTags.DefineBits: {
tag.characterId = buff.readUIntLE(16); tag.characterId = buff.readUIntLE(16);
tag.jpegData = buff.buffer.slice(buff.pointer, buff.pointer + tagHeader.length - 2); tag.jpegData = buff.buffer.slice(buff.pointer, buff.pointer + tagHeader.length - 2);
buff.pointer += tagHeader.length - 2; buff.pointer += tagHeader.length - 2;
break; break;
case SWFTags.DefineBitsJPEG2: }
case SWFTags.DefineBitsJPEG2: {
tag.characterId = buff.readUIntLE(16); tag.characterId = buff.readUIntLE(16);
tag.imageData = buff.buffer.slice(buff.pointer, buff.pointer + tagHeader.length - 2); tag.imageData = buff.buffer.slice(buff.pointer, buff.pointer + tagHeader.length - 2);
buff.pointer += tagHeader.length - 2; buff.pointer += tagHeader.length - 2;
break; break;
case SWFTags.DefineBitsJPEG3: }
case SWFTags.DefineBitsJPEG3: {
tag.characterId = buff.readUIntLE(16); tag.characterId = buff.readUIntLE(16);
var alphaDataOffset = buff.readUIntLE(32); var alphaDataOffset = buff.readUIntLE(32);
tag.imageData = buff.buffer.slice(buff.pointer, buff.pointer + alphaDataOffset); tag.imageData = buff.buffer.slice(buff.pointer, buff.pointer + alphaDataOffset);
@ -232,30 +245,34 @@ function readSWFTags(buff, swf) {
tag.bitmapAlphaData = buff.buffer.slice(buff.pointer, buff.pointer + restLength); tag.bitmapAlphaData = buff.buffer.slice(buff.pointer, buff.pointer + restLength);
buff.pointer += restLength; buff.pointer += restLength;
break; break;
case SWFTags.DefineBitsJPEG4: }
case SWFTags.DefineBitsJPEG4: {
tag.characterId = buff.readUIntLE(16); tag.characterId = buff.readUIntLE(16);
var alphaDataOffset = buff.readUIntLE(32); const alphaDataOffset = buff.readUIntLE(32);
tag.deblockParam = buff.readUIntLE(16); tag.deblockParam = buff.readUIntLE(16);
tag.imageData = buff.buffer.slice(buff.pointer, buff.pointer + alphaDataOffset); tag.imageData = buff.buffer.slice(buff.pointer, buff.pointer + alphaDataOffset);
buff.pointer += alphaDataOffset; buff.pointer += alphaDataOffset;
var restLength = tagHeader.length - 8 - alphaDataOffset; const restLength = tagHeader.length - 8 - alphaDataOffset;
tag.bitmapAlphaData = buff.buffer.slice(buff.pointer, buff.pointer + restLength); tag.bitmapAlphaData = buff.buffer.slice(buff.pointer, buff.pointer + restLength);
buff.pointer += restLength; buff.pointer += restLength;
break; break;
}
case SWFTags.DefineBitsLossless: case SWFTags.DefineBitsLossless:
case SWFTags.DefineBitsLossless2: case SWFTags.DefineBitsLossless2: {
tag.characterId = buff.readUIntLE(16); tag.characterId = buff.readUIntLE(16);
tag.bitmapFormat = buff.readUInt8(); tag.bitmapFormat = buff.readUInt8();
tag.bitmapWidth = buff.readUIntLE(16); tag.bitmapWidth = buff.readUIntLE(16);
tag.bitmapHeight = buff.readUIntLE(16); tag.bitmapHeight = buff.readUIntLE(16);
var restLength = tagHeader.length - 7; let restLength = tagHeader.length - 7;
if (tag.bitmapFormat == 3) { if(tag.bitmapFormat == 3)
{
tag.bitmapColorTableSize = buff.readUInt8(); tag.bitmapColorTableSize = buff.readUInt8();
restLength--; restLength--;
} }
tag.zlibBitmapData = buff.buffer.slice(buff.pointer, buff.pointer + restLength); tag.zlibBitmapData = buff.buffer.slice(buff.pointer, buff.pointer + restLength);
buff.pointer += restLength; buff.pointer += restLength;
break; break;
}
default: default:
tag.data = buff.buffer.slice(buff.pointer, buff.pointer + tagHeader.length); tag.data = buff.buffer.slice(buff.pointer, buff.pointer + tagHeader.length);
buff.pointer += tagHeader.length; buff.pointer += tagHeader.length;
@ -275,12 +292,14 @@ function readSWFTags(buff, swf) {
* @api private * @api private
* *
*/ */
function readSWFBuff(buff, compressed_buff, next) { function readSWFBuff(buff, compressed_buff, next)
{
buff.seek(3);// start buff.seek(3);// start
if (buff.length < 9) { 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(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 = { var swf = {
version : buff.readUInt8(), version : buff.readUInt8(),
@ -294,9 +313,12 @@ function readSWFBuff(buff, compressed_buff, next) {
} }
, isSync = 'function' !== typeof next; , isSync = 'function' !== typeof next;
try { try
{
swf.tags = readSWFTags(buff, swf); swf.tags = readSWFTags(buff, swf);
} catch(e) { }
catch (e)
{
if(isSync) throw e; if(isSync) throw e;
return next(e); return next(e);
} }
@ -310,7 +332,8 @@ function readSWFBuff(buff, compressed_buff, next) {
* @param {Buffer|ArrayBuffer} buff * @param {Buffer|ArrayBuffer} buff
* @param {Buffer|ArrayBuffer} swf * @param {Buffer|ArrayBuffer} swf
*/ */
function concatSWFHeader(buff, swf) { function concatSWFHeader(buff, swf)
{
return Buffer.concat([swf.slice(0, 8), buff]); return Buffer.concat([swf.slice(0, 8), buff]);
} }
@ -321,51 +344,73 @@ function concatSWFHeader(buff, swf) {
* @param {function} callback * @param {function} callback
* *
*/ */
function uncompress(swf, next) { function uncompress(swf, next)
{
var compressed_buff = swf.slice(8) var compressed_buff = swf.slice(8)
, uncompressed_buff , uncompressed_buff
, isSync = 'function' !== typeof next , isSync = 'function' !== typeof next
, e; , e;
// uncompress buffer // uncompress buffer
switch( swf[0] ) { switch(swf[0])
{
case 0x43 : // zlib compressed case 0x43 : // zlib compressed
if (isSync) { if(isSync)
{
uncompressed_buff = concatSWFHeader(zlib.unzipSync(compressed_buff), swf); uncompressed_buff = concatSWFHeader(zlib.unzipSync(compressed_buff), swf);
if(!Buffer.isBuffer(uncompressed_buff))
{
console.log('invalid_buffer');
return null;
}
return readSWFBuff(new SWFBuffer(uncompressed_buff), swf); return readSWFBuff(new SWFBuffer(uncompressed_buff), swf);
} }
zlib.inflate(compressed_buff, function(err, buf) { if(!Buffer.isBuffer(compressed_buff))
{
console.log('invalid_buffer');
return null;
}
zlib.inflate(compressed_buff, function(err, buf)
{
readSWFBuff(new SWFBuffer(buf), swf, next); readSWFBuff(new SWFBuffer(buf), swf, next);
}); });
break; break;
case 0x46 : // uncompressed case 0x46 : // uncompressed
return readSWFBuff(new SWFBuffer( swf ), swf, next); return readSWFBuff(new SWFBuffer( swf ), swf, next);
break;
case 0x5a : // LZMA compressed case 0x5a : // LZMA compressed
var lzmaProperties = compressed_buff.slice(4, 9); var lzmaProperties = compressed_buff.slice(4, 9);
compressed_buff = compressed_buff.slice(9); compressed_buff = compressed_buff.slice(9);
var input_stream = new Stream(); var input_stream = new Stream();
input_stream.pos = 0; input_stream.pos = 0;
input_stream.readByte = function() { input_stream.readByte = function()
{
return this.pos >= compressed_buff.length ? -1 : compressed_buff[this.pos++]; return this.pos >= compressed_buff.length ? -1 : compressed_buff[this.pos++];
}; };
var output_stream = new Stream(); var output_stream = new Stream();
output_stream.buffer = new Buffer(16384); output_stream.buffer = new Buffer(16384);
output_stream.pos = 0; output_stream.pos = 0;
output_stream.writeByte = function(_byte) { output_stream.writeByte = function(_byte)
if (this.pos >= this.buffer.length) { {
if(this.pos >= this.buffer.length)
{
var newBuffer = new Buffer(this.buffer.length * 2); var newBuffer = new Buffer(this.buffer.length * 2);
this.buffer.copy(newBuffer); this.buffer.copy(newBuffer);
this.buffer = newBuffer; this.buffer = newBuffer;
} }
this.buffer[this.pos++] = _byte; this.buffer[this.pos++] = _byte;
}; };
output_stream.getBuffer = function() { output_stream.getBuffer = function()
{
// trim buffer // trim buffer
if (this.pos !== this.buffer.length) { if(this.pos !== this.buffer.length)
{
var newBuffer = new Buffer(this.pos); var newBuffer = new Buffer(this.pos);
this.buffer.copy(newBuffer, 0, 0, this.pos); this.buffer.copy(newBuffer, 0, 0, this.pos);
this.buffer = newBuffer; this.buffer = newBuffer;
@ -377,17 +422,19 @@ function uncompress(swf, next) {
uncompressed_buff = Buffer.concat([swf.slice(0, 8), output_stream.getBuffer()]); uncompressed_buff = Buffer.concat([swf.slice(0, 8), output_stream.getBuffer()]);
return readSWFBuff(new SWFBuffer(uncompressed_buff), swf, next); return readSWFBuff(new SWFBuffer(uncompressed_buff), swf, next);
break;
default : default :
e = new Error('Unknown SWF compressions'); e = new Error('Unknown SWF compressions');
if (isSync) { if(isSync)
{
throw e; throw e;
} else { }
else
{
next(e); next(e);
} }
}; }
}; }
/** /**
* Check if file is Buffer or ArrayBuffer * Check if file is Buffer or ArrayBuffer
@ -396,8 +443,9 @@ function uncompress(swf, next) {
* @api private * @api private
* *
*/ */
function isBuffer(b) { function isBuffer(b)
return typeof Buffer !== "undefined" && Buffer.isBuffer(b) || b instanceof ArrayBuffer; {
return typeof Buffer !== 'undefined' && Buffer.isBuffer(b) || b instanceof ArrayBuffer;
} }
/* Exposes Tags constants */ /* Exposes Tags constants */
@ -411,21 +459,30 @@ SWFReader.TAGS = SWFTags;
* @api public * @api public
* *
*/ */
SWFReader.read = SWFReader.readSync = function(file, next) { SWFReader.read = SWFReader.readSync = function(file, next)
if (isBuffer(file)) { {
if(isBuffer(file))
{
/* File is already a buffer */ /* File is already a buffer */
return uncompress(file, next); return uncompress(file, next);
} else { }
else
{
/* Get the buffer */ /* Get the buffer */
if ('function' === typeof next) { if('function' === typeof next)
fs.readFile(file, function(err, swf) { {
if ( err ) { fs.readFile(file, function(err, swf)
{
if( err )
{
next(err); next(err);
return; return;
} }
uncompress(swf, next); uncompress(swf, next);
}); });
} else { }
else
{
return uncompress(fs.readFileSync(file)); return uncompress(fs.readFileSync(file));
} }
} }

View File

@ -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;
}
}

View File

@ -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;

View File

@ -5,7 +5,8 @@
* @param {Mixed} value * @param {Mixed} value
*/ */
function define(name, value) { function define(name, value)
{
Object.defineProperty(exports, name, { Object.defineProperty(exports, name, {
value : value, value : value,
enumerable : true enumerable : true