disassemble.js

JavaScript source for the SWF disassembler, 184 lines: printing decoded instructions as a listing, with labels on branch targets and named registers. It's lib/disassemble.js in swf-disassembler.zip.

//  Prints decoded actions as a listing: one instruction per line with its offset, labels on every
//  branch target, and function, with and try bodies indented inside them.

var actions = require('./actions');
var hex = actions.hex;
var registerNames = actions.registerNames;

function disassemble(list, base) {
  var out = [];
  var targets = {};
  collectTargets(list, targets);
  print(list, base, targets, {}, '', out);
  return out.join('\n');
}

function collectTargets(list, targets) {
  list.forEach(function (ins) {
    if (ins.target !== undefined)
      targets[ins.target] = true;
    [ins.body, ins.catchBody, ins.finallyBody].forEach(function (body) {
      if (body)
        collectTargets(body, targets);
    });
  });
}

function print(list, base, targets, regs, indent, out) {
  list.forEach(function (ins) {
    if (targets[ins.offset])
      out.push(indent + label(ins.offset, base) + ':');
    var at = hex(ins.offset - base);
    var line = function (text) {
      out.push(indent + '  ' + at + '  ' + text);
    };

    switch (ins.code) {
      case 0x88:
        line('constantPool  ' + ins.pool.length + ' strings');
        ins.pool.forEach(function (s, i) {
          out.push(indent + '          ' + padLeft(String(i), 4) + '  ' + quote(s));
        });
        break;

      case 0x96:
        line('push          ' + ins.values.map(function (v) { return value(v, regs); }).join(', '));
        break;

      case 0x99:
      case 0x9D:
        line(padRight(ins.name, 13) + ' ' + label(ins.target, base));
        break;

      case 0x87:
        line('storeRegister ' + register(ins.register, regs));
        break;

      case 0x9B:
      case 0x8E:
        var inner = {};
        var names = registerNames(ins);
        var n;
        for (n in regs)
          inner[n] = regs[n];
        for (n in names)
          inner[n] = names[n];
        var params = ins.params.map(function (p) {
          return (p.register ? 'r:' + p.register + '=' : '') + p.name;
        }).join(', ');
        var extra = '';
        if (ins.code === 0x8E) {
          var pre = [], sup = [];
          Object.keys(ins.preload).forEach(function (k) {
            if (ins.preload[k])
              (k.indexOf('suppress') === 0 ? sup : pre).push(k);
          });
          extra = '  ; ' + ins.registerCount + ' registers' + (pre.length ? ', preload ' + pre.join(' ') : '') + (sup.length ? ', ' + sup.join(' ') : '');
        }
        line(ins.name + ' ' + (ins.fname || '(anonymous)') + '(' + params + ')' + extra);
        out.push(indent + '        {');
        print(ins.body, base, targets, inner, indent + '    ', out);
        out.push(indent + '        }');
        break;

      case 0x94:
        line('with');
        out.push(indent + '        {');
        print(ins.body, base, targets, regs, indent + '    ', out);
        out.push(indent + '        }');
        break;

      case 0x8F:
        var caught = ins.catchInRegister ? 'r:' + ins.catchRegister : ins.catchName;
        line('try');
        out.push(indent + '        {');
        print(ins.body, base, targets, regs, indent + '    ', out);
        out.push(indent + '        }');
        if (ins.hasCatch) {
          out.push(indent + '        catch (' + caught + ') {');
          print(ins.catchBody, base, targets, regs, indent + '    ', out);
          out.push(indent + '        }');
        }
        if (ins.hasFinally) {
          out.push(indent + '        finally {');
          print(ins.finallyBody, base, targets, regs, indent + '    ', out);
          out.push(indent + '        }');
        }
        break;

      case 0x81:
        line('gotoFrame     ' + ins.frame);
        break;
      case 0x83:
        line('getURL        ' + quote(ins.url) + ', ' + quote(ins.window));
        break;
      case 0x8A:
        line('waitForFrame  ' + ins.frame + ', skip ' + ins.skip);
        break;
      case 0x8D:
        line('waitForFrame2 skip ' + ins.skip);
        break;
      case 0x8B:
      case 0x8C:
        line(padRight(ins.name, 13) + ' ' + quote(ins.value));
        break;
      case 0x9A:
        line('getURL2       method ' + ins.method + (ins.loadTarget ? ', target' : '') + (ins.loadVariables ? ', variables' : ''));
        break;
      case 0x9F:
        line('gotoFrame2    ' + (ins.play ? 'play' : 'stop') + (ins.sceneBias ? ', scene bias ' + ins.sceneBias : ''));
        break;

      default:
        if (ins.raw && ins.raw.length) {
          var bytes = [];
          for (var b = 0; b < ins.raw.length; b++)
            bytes.push(hex(ins.raw[b], 2));
          line(padRight(ins.name, 13) + ' ' + bytes.join(' '));
        } else {
          line(ins.name);
        }
    }
  });
}

function label(offset, base) {
  return 'L' + hex(offset - base);
}

function register(n, regs) {
  return regs.hasOwnProperty(n) ? 'r:' + n + ' (' + regs[n] + ')' : 'r:' + n;
}

function value(v, regs) {
  switch (v.type) {
    case 'string': return quote(v.value);
    case 'register': return register(v.value, regs);
    case 'null': return 'null';
    case 'undefined': return 'undefined';
    default: return String(v.value);
  }
}

function quote(s) {
  return "'" + String(s).replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\t/g, '\\t') + "'";
}

//  Pads with spaces (or the character given) to at least width characters
function padLeft(s, width, ch) {
  while (s.length < width)
    s = (ch || ' ') + s;
  return s;
}

function padRight(s, width) {
  while (s.length < width)
    s += ' ';
  return s;
}

exports.disassemble = disassemble;
exports.label = label;
exports.quote = quote;
exports.padLeft = padLeft;
exports.padRight = padRight;