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;