reader.js
JavaScript source for the SWF disassembler, 163 lines: reading the integers, floats, strings and bit fields a SWF file is made of. It's lib/reader.js in swf-disassembler.zip.
// Reads the values a SWF file is made of: little-endian integers and floats, null-terminated strings,
// and the bit fields used by rectangles and matrices.
function Reader(buf, pos, end) {
this.buf = buf;
this.pos = pos === undefined ? 0 : pos;
this.end = end === undefined ? buf.length : end;
this.bitBuf = 0;
this.bitCount = 0;
this.utf8 = true; // SWF 6 and later store strings as UTF-8, older files as Latin-1
}
Reader.prototype.check = function (n) {
if (this.pos + n > this.end)
throw new Error('read past end at ' + this.pos + ' (+' + n + ', end ' + this.end + ')');
};
Reader.prototype.u8 = function () {
this.check(1);
return this.buf[this.pos++];
};
Reader.prototype.u16 = function () {
this.check(2);
var v = this.buf.readUInt16LE(this.pos);
this.pos += 2;
return v;
};
Reader.prototype.s16 = function () {
this.check(2);
var v = this.buf.readInt16LE(this.pos);
this.pos += 2;
return v;
};
Reader.prototype.u32 = function () {
this.check(4);
var v = this.buf.readUInt32LE(this.pos);
this.pos += 4;
return v;
};
Reader.prototype.s32 = function () {
this.check(4);
var v = this.buf.readInt32LE(this.pos);
this.pos += 4;
return v;
};
Reader.prototype.f32 = function () {
this.check(4);
var v = this.buf.readFloatLE(this.pos);
this.pos += 4;
return v;
};
// AVM1 doubles are little-endian with their two 32-bit halves swapped: the high word comes first
Reader.prototype.f64swapped = function () {
this.check(8);
var b = Buffer.concat([this.buf.slice(this.pos + 4, this.pos + 8), this.buf.slice(this.pos, this.pos + 4)]);
this.pos += 8;
return b.readDoubleLE(0);
};
Reader.prototype.bytes = function (n) {
this.check(n);
var b = this.buf.slice(this.pos, this.pos + n);
this.pos += n;
return b;
};
Reader.prototype.skip = function (n) {
this.check(n);
this.pos += n;
};
// A null-terminated string
Reader.prototype.string = function () {
var start = this.pos;
while (this.pos < this.end && this.buf[this.pos] !== 0)
this.pos++;
if (this.pos >= this.end)
throw new Error('unterminated string at ' + start);
var s = this.buf.toString(this.utf8 ? 'utf8' : 'binary', start, this.pos);
this.pos++;
return s;
};
// Bit fields: read most significant bit first, and start again on a byte boundary after align()
Reader.prototype.bits = function (n) {
var v = 0;
for (var i = 0; i < n; i++) {
if (this.bitCount === 0) {
this.bitBuf = this.u8();
this.bitCount = 8;
}
this.bitCount--;
v = v * 2 + ((this.bitBuf >> this.bitCount) & 1);
}
return v;
};
Reader.prototype.sbits = function (n) {
if (n === 0)
return 0;
var v = this.bits(n);
return v >= Math.pow(2, n - 1) ? v - Math.pow(2, n) : v;
};
Reader.prototype.align = function () {
this.bitCount = 0;
};
// A RECT: four signed fields of the same width, in twips (1/20 of a pixel)
Reader.prototype.rect = function () {
this.align();
var n = this.bits(5);
var r = { xmin: this.sbits(n), xmax: this.sbits(n), ymin: this.sbits(n), ymax: this.sbits(n) };
this.align();
return r;
};
// A MATRIX: optional scale and rotate pairs as 16.16 fixed point, then the translation in twips
Reader.prototype.matrix = function () {
this.align();
var m = { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 };
var n;
if (this.bits(1)) {
n = this.bits(5);
m.a = this.sbits(n) / 65536;
m.d = this.sbits(n) / 65536;
}
if (this.bits(1)) {
n = this.bits(5);
m.b = this.sbits(n) / 65536;
m.c = this.sbits(n) / 65536;
}
n = this.bits(5);
m.tx = this.sbits(n);
m.ty = this.sbits(n);
this.align();
return m;
};
// A CXFORM or CXFORMWITHALPHA: optional multiply and add terms, read only to step over them
Reader.prototype.cxform = function (withAlpha) {
this.align();
var hasAdd = this.bits(1);
var hasMult = this.bits(1);
var n = this.bits(4);
var terms = withAlpha ? 4 : 3;
var i;
if (hasMult)
for (i = 0; i < terms; i++)
this.sbits(n);
if (hasAdd)
for (i = 0; i < terms; i++)
this.sbits(n);
this.align();
};
exports.Reader = Reader;