disassemble.js
JavaScript source for the SWF disassembler, 142 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.
import { hex, registerNames } from './actions.js';
export function disassemble(list, base) {
const out = [];
const targets = new Set();
collectTargets(list, targets);
print(list, base, targets, new Map(), '', out);
return out.join('\n');
}
function collectTargets(list, targets) {
for (const ins of list) {
if (ins.target !== undefined)
targets.add(ins.target);
for (const body of [ins.body, ins.catchBody, ins.finallyBody])
if (body)
collectTargets(body, targets);
}
}
function print(list, base, targets, regs, indent, out) {
for (const ins of list) {
if (targets.has(ins.offset))
out.push(`${indent}${label(ins.offset, base)}:`);
const at = hex(ins.offset - base);
const line = (text) => out.push(`${indent} ${at} ${text}`);
switch (ins.code) {
case 0x88:
line(`constantPool ${ins.pool.length} strings`);
ins.pool.forEach((s, i) => out.push(`${indent} ${String(i).padStart(4)} ${quote(s)}`));
break;
case 0x96:
line(`push ${ins.values.map((v) => value(v, regs)).join(', ')}`);
break;
case 0x99:
case 0x9D:
line(`${ins.name.padEnd(13)} ${label(ins.target, base)}`);
break;
case 0x87:
line(`storeRegister ${register(ins.register, regs)}`);
break;
case 0x9B:
case 0x8E: {
const inner = new Map([...regs, ...registerNames(ins)]);
const params = ins.params.map((p) => (p.register ? `r:${p.register}=` : '') + p.name).join(', ');
let extra = '';
if (ins.code === 0x8E) {
const pre = Object.entries(ins.preload).filter(([k, v]) => v && !k.startsWith('suppress')).map(([k]) => k);
const sup = Object.entries(ins.preload).filter(([k, v]) => v && k.startsWith('suppress')).map(([k]) => 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: {
const 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(`${ins.name.padEnd(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:
line(ins.raw && ins.raw.length ? `${ins.name.padEnd(13)} ${[...ins.raw].map((b) => hex(b, 2)).join(' ')}` : ins.name);
}
}
}
export function label(offset, base) {
return `L${hex(offset - base)}`;
}
function register(n, regs) {
return regs.has(n) ? `r:${n} (${regs.get(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);
}
}
export function quote(s) {
return "'" + String(s).replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\t/g, '\\t') + "'";
}