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;