swf-toolkit 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +125 -2
- package/README.tr.md +126 -0
- package/dist/abc/abc-file.d.ts +168 -0
- package/dist/abc/abc-file.js +656 -0
- package/dist/abc/analysis.d.ts +36 -0
- package/dist/abc/analysis.js +213 -0
- package/dist/abc/code.d.ts +59 -0
- package/dist/abc/code.js +379 -0
- package/dist/abc/constants.d.ts +80 -0
- package/dist/abc/constants.js +93 -0
- package/dist/abc/interner.d.ts +46 -0
- package/dist/abc/interner.js +168 -0
- package/dist/abc/model.d.ts +40 -0
- package/dist/abc/model.js +146 -0
- package/dist/abc/opcodes.d.ts +39 -0
- package/dist/abc/opcodes.js +196 -0
- package/dist/abc/pcode/assembler.d.ts +42 -0
- package/dist/abc/pcode/assembler.js +635 -0
- package/dist/abc/pcode/disassembler.d.ts +17 -0
- package/dist/abc/pcode/disassembler.js +147 -0
- package/dist/abc/pcode/format.d.ts +23 -0
- package/dist/abc/pcode/format.js +131 -0
- package/dist/abc/pcode/lexer.d.ts +34 -0
- package/dist/abc/pcode/lexer.js +183 -0
- package/dist/assets/assets.d.ts +26 -0
- package/dist/assets/assets.js +151 -0
- package/dist/assets/images.d.ts +31 -0
- package/dist/assets/images.js +238 -0
- package/dist/assets/png.d.ts +12 -0
- package/dist/assets/png.js +196 -0
- package/dist/assets/sounds.d.ts +37 -0
- package/dist/assets/sounds.js +236 -0
- package/dist/avm1/avm1.d.ts +55 -0
- package/dist/avm1/avm1.js +642 -0
- package/dist/avm1/project.d.ts +16 -0
- package/dist/avm1/project.js +81 -0
- package/dist/cjs/abc/abc-file.d.ts +168 -0
- package/dist/cjs/abc/abc-file.js +663 -0
- package/dist/cjs/abc/analysis.d.ts +36 -0
- package/dist/cjs/abc/analysis.js +221 -0
- package/dist/cjs/abc/code.d.ts +59 -0
- package/dist/cjs/abc/code.js +387 -0
- package/dist/cjs/abc/constants.d.ts +80 -0
- package/dist/cjs/abc/constants.js +97 -0
- package/dist/cjs/abc/interner.d.ts +46 -0
- package/dist/cjs/abc/interner.js +172 -0
- package/dist/cjs/abc/model.d.ts +40 -0
- package/dist/cjs/abc/model.js +155 -0
- package/dist/cjs/abc/opcodes.d.ts +39 -0
- package/dist/cjs/abc/opcodes.js +201 -0
- package/dist/cjs/abc/pcode/assembler.d.ts +42 -0
- package/dist/cjs/abc/pcode/assembler.js +640 -0
- package/dist/cjs/abc/pcode/disassembler.d.ts +17 -0
- package/dist/cjs/abc/pcode/disassembler.js +152 -0
- package/dist/cjs/abc/pcode/format.d.ts +23 -0
- package/dist/cjs/abc/pcode/format.js +141 -0
- package/dist/cjs/abc/pcode/lexer.d.ts +34 -0
- package/dist/cjs/abc/pcode/lexer.js +188 -0
- package/dist/cjs/assets/assets.d.ts +26 -0
- package/dist/cjs/assets/assets.js +159 -0
- package/dist/cjs/assets/images.d.ts +31 -0
- package/dist/cjs/assets/images.js +246 -0
- package/dist/cjs/assets/png.d.ts +12 -0
- package/dist/cjs/assets/png.js +202 -0
- package/dist/cjs/assets/sounds.d.ts +37 -0
- package/dist/cjs/assets/sounds.js +246 -0
- package/dist/cjs/avm1/avm1.d.ts +55 -0
- package/dist/cjs/avm1/avm1.js +649 -0
- package/dist/cjs/avm1/project.d.ts +16 -0
- package/dist/cjs/avm1/project.js +88 -0
- package/dist/cjs/compiler/ast.d.ts +285 -0
- package/dist/cjs/compiler/ast.js +3 -0
- package/dist/cjs/compiler/class-compiler.d.ts +34 -0
- package/dist/cjs/compiler/class-compiler.js +440 -0
- package/dist/cjs/compiler/codegen.d.ts +144 -0
- package/dist/cjs/compiler/codegen.js +1589 -0
- package/dist/cjs/compiler/lexer.d.ts +27 -0
- package/dist/cjs/compiler/lexer.js +254 -0
- package/dist/cjs/compiler/parser.d.ts +58 -0
- package/dist/cjs/compiler/parser.js +989 -0
- package/dist/cjs/compiler/program.d.ts +29 -0
- package/dist/cjs/compiler/program.js +36 -0
- package/dist/cjs/compiler/scope.d.ts +22 -0
- package/dist/cjs/compiler/scope.js +242 -0
- package/dist/cjs/compression/lzma.d.ts +37 -0
- package/dist/cjs/compression/lzma.js +575 -0
- package/dist/cjs/compression/swf-container.d.ts +19 -0
- package/dist/cjs/compression/swf-container.js +92 -0
- package/dist/cjs/decompiler/ast.d.ts +228 -0
- package/dist/cjs/decompiler/ast.js +455 -0
- package/dist/cjs/decompiler/cfg.d.ts +40 -0
- package/dist/cjs/decompiler/cfg.js +236 -0
- package/dist/cjs/decompiler/class-printer.d.ts +9 -0
- package/dist/cjs/decompiler/class-printer.js +173 -0
- package/dist/cjs/decompiler/method.d.ts +41 -0
- package/dist/cjs/decompiler/method.js +436 -0
- package/dist/cjs/decompiler/simulate.d.ts +55 -0
- package/dist/cjs/decompiler/simulate.js +591 -0
- package/dist/cjs/decompiler/structure.d.ts +48 -0
- package/dist/cjs/decompiler/structure.js +764 -0
- package/dist/cjs/errors.d.ts +11 -0
- package/dist/cjs/errors.js +25 -0
- package/dist/cjs/index.d.ts +41 -0
- package/dist/cjs/index.js +82 -0
- package/dist/cjs/io/reader.d.ts +48 -0
- package/dist/cjs/io/reader.js +180 -0
- package/dist/cjs/io/writer.d.ts +37 -0
- package/dist/cjs/io/writer.js +175 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/project/pcode-project.d.ts +32 -0
- package/dist/cjs/project/pcode-project.js +210 -0
- package/dist/cjs/project/source-export.d.ts +12 -0
- package/dist/cjs/project/source-export.js +51 -0
- package/dist/cjs/project/source-import.d.ts +13 -0
- package/dist/cjs/project/source-import.js +73 -0
- package/dist/cjs/swf/swf.d.ts +63 -0
- package/dist/cjs/swf/swf.js +147 -0
- package/dist/cjs/swf/tag-codes.d.ts +77 -0
- package/dist/cjs/swf/tag-codes.js +109 -0
- package/dist/cjs/swf/tags.d.ts +118 -0
- package/dist/cjs/swf/tags.js +330 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +296 -0
- package/dist/compiler/ast.d.ts +285 -0
- package/dist/compiler/ast.js +2 -0
- package/dist/compiler/class-compiler.d.ts +34 -0
- package/dist/compiler/class-compiler.js +437 -0
- package/dist/compiler/codegen.d.ts +144 -0
- package/dist/compiler/codegen.js +1585 -0
- package/dist/compiler/lexer.d.ts +27 -0
- package/dist/compiler/lexer.js +249 -0
- package/dist/compiler/parser.d.ts +58 -0
- package/dist/compiler/parser.js +984 -0
- package/dist/compiler/program.d.ts +29 -0
- package/dist/compiler/program.js +32 -0
- package/dist/compiler/scope.d.ts +22 -0
- package/dist/compiler/scope.js +238 -0
- package/dist/compression/lzma.d.ts +37 -0
- package/dist/compression/lzma.js +569 -0
- package/dist/compression/swf-container.d.ts +19 -0
- package/dist/compression/swf-container.js +87 -0
- package/dist/decompiler/ast.d.ts +228 -0
- package/dist/decompiler/ast.js +441 -0
- package/dist/decompiler/cfg.d.ts +40 -0
- package/dist/decompiler/cfg.js +231 -0
- package/dist/decompiler/class-printer.d.ts +9 -0
- package/dist/decompiler/class-printer.js +170 -0
- package/dist/decompiler/method.d.ts +41 -0
- package/dist/decompiler/method.js +427 -0
- package/dist/decompiler/simulate.d.ts +55 -0
- package/dist/decompiler/simulate.js +586 -0
- package/dist/decompiler/structure.d.ts +48 -0
- package/dist/decompiler/structure.js +760 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +20 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +49 -0
- package/dist/io/reader.d.ts +48 -0
- package/dist/io/reader.js +176 -0
- package/dist/io/writer.d.ts +37 -0
- package/dist/io/writer.js +169 -0
- package/dist/project/pcode-project.d.ts +32 -0
- package/dist/project/pcode-project.js +203 -0
- package/dist/project/source-export.d.ts +12 -0
- package/dist/project/source-export.js +47 -0
- package/dist/project/source-import.d.ts +13 -0
- package/dist/project/source-import.js +70 -0
- package/dist/swf/swf.d.ts +63 -0
- package/dist/swf/swf.js +143 -0
- package/dist/swf/tag-codes.d.ts +77 -0
- package/dist/swf/tag-codes.js +105 -0
- package/dist/swf/tags.d.ts +118 -0
- package/dist/swf/tags.js +316 -0
- package/package.json +71 -4
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control-flow graph over decoded method code, with dominator and
|
|
3
|
+
* post-dominator trees.
|
|
4
|
+
*/
|
|
5
|
+
import type { AbcFile } from '../abc/abc-file.js';
|
|
6
|
+
import type { CodeException, CodeItem, Instruction } from '../abc/code.js';
|
|
7
|
+
export type BlockKind = 'fall' | 'jump' | 'cond' | 'switch' | 'return' | 'throw' | 'raw' | 'end';
|
|
8
|
+
export interface Block {
|
|
9
|
+
id: number;
|
|
10
|
+
/** Instructions (labels removed). */
|
|
11
|
+
insns: Instruction[];
|
|
12
|
+
labels: number[];
|
|
13
|
+
kind: BlockKind;
|
|
14
|
+
/** cond: [taken, fallthrough]; switch: [default, ...cases]; fall/jump: [next]. */
|
|
15
|
+
succs: number[];
|
|
16
|
+
preds: number[];
|
|
17
|
+
/** Stack height on entry (-1 if unreachable). */
|
|
18
|
+
entryHeight: number;
|
|
19
|
+
}
|
|
20
|
+
export interface Cfg {
|
|
21
|
+
blocks: Block[];
|
|
22
|
+
labelBlock: Map<number, number>;
|
|
23
|
+
exceptions: CodeException[];
|
|
24
|
+
/** Immediate dominator per block (-1 for entry / unreachable). */
|
|
25
|
+
idom: number[];
|
|
26
|
+
/** Immediate post-dominator per block (-1 = virtual exit). */
|
|
27
|
+
ipdom: number[];
|
|
28
|
+
/** Reverse post-order index (lower = earlier). */
|
|
29
|
+
order: number[];
|
|
30
|
+
}
|
|
31
|
+
export declare function buildCfg(abc: AbcFile, items: readonly CodeItem[], exceptions: readonly CodeException[]): Cfg;
|
|
32
|
+
/** True if `a` dominates `b`. */
|
|
33
|
+
export declare function dominates(cfg: Cfg, a: number, b: number): boolean;
|
|
34
|
+
export interface Loop {
|
|
35
|
+
header: number;
|
|
36
|
+
body: Set<number>;
|
|
37
|
+
latches: number[];
|
|
38
|
+
}
|
|
39
|
+
/** Natural loops keyed by header block. */
|
|
40
|
+
export declare function findLoops(cfg: Cfg): Map<number, Loop>;
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildCfg = buildCfg;
|
|
4
|
+
exports.dominates = dominates;
|
|
5
|
+
exports.findLoops = findLoops;
|
|
6
|
+
const analysis_js_1 = require("../abc/analysis.js");
|
|
7
|
+
const opcodes_js_1 = require("../abc/opcodes.js");
|
|
8
|
+
function buildCfg(abc, items, exceptions) {
|
|
9
|
+
const blocks = [];
|
|
10
|
+
const labelBlock = new Map();
|
|
11
|
+
let cur = null;
|
|
12
|
+
const newBlock = () => {
|
|
13
|
+
const b = { id: blocks.length, insns: [], labels: [], kind: 'fall', succs: [], preds: [], entryHeight: -1 };
|
|
14
|
+
blocks.push(b);
|
|
15
|
+
return b;
|
|
16
|
+
};
|
|
17
|
+
for (const it of items) {
|
|
18
|
+
if (it.type === 'label') {
|
|
19
|
+
if (!cur || cur.insns.length > 0)
|
|
20
|
+
cur = newBlock();
|
|
21
|
+
cur.labels.push(it.id);
|
|
22
|
+
labelBlock.set(it.id, cur.id);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (!cur)
|
|
26
|
+
cur = newBlock();
|
|
27
|
+
if (it.type === 'bytes') {
|
|
28
|
+
if (cur.insns.length > 0)
|
|
29
|
+
cur = newBlock();
|
|
30
|
+
cur.kind = 'raw';
|
|
31
|
+
cur = null;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
cur.insns.push(it);
|
|
35
|
+
const flow = (0, opcodes_js_1.opInfo)(it.op)?.flow;
|
|
36
|
+
if (flow) {
|
|
37
|
+
cur.kind = flow === 'return' ? 'return' : flow === 'throw' ? 'throw' : flow;
|
|
38
|
+
cur = null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (blocks.length === 0)
|
|
42
|
+
newBlock().kind = 'end';
|
|
43
|
+
// Successors
|
|
44
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
45
|
+
const b = blocks[i];
|
|
46
|
+
const last = b.insns[b.insns.length - 1];
|
|
47
|
+
const next = i + 1 < blocks.length ? i + 1 : -1;
|
|
48
|
+
switch (b.kind) {
|
|
49
|
+
case 'fall':
|
|
50
|
+
if (next >= 0)
|
|
51
|
+
b.succs = [next];
|
|
52
|
+
else
|
|
53
|
+
b.kind = 'end';
|
|
54
|
+
break;
|
|
55
|
+
case 'jump':
|
|
56
|
+
b.succs = [labelBlock.get(last.targets[0])];
|
|
57
|
+
break;
|
|
58
|
+
case 'cond':
|
|
59
|
+
b.succs = [labelBlock.get(last.targets[0]), ...(next >= 0 ? [next] : [])];
|
|
60
|
+
if (b.succs.length < 2)
|
|
61
|
+
b.succs.push(b.succs[0]);
|
|
62
|
+
break;
|
|
63
|
+
case 'switch':
|
|
64
|
+
b.succs = last.targets.map((t) => labelBlock.get(t));
|
|
65
|
+
break;
|
|
66
|
+
default:
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const b of blocks)
|
|
71
|
+
for (const s of new Set(b.succs))
|
|
72
|
+
blocks[s].preds.push(b.id);
|
|
73
|
+
// Stack heights
|
|
74
|
+
const handlerBlocks = exceptions.map((e) => labelBlock.get(e.target)).filter((x) => x !== undefined);
|
|
75
|
+
const work = [[0, 0], ...handlerBlocks.map((h) => [h, 1])];
|
|
76
|
+
while (work.length) {
|
|
77
|
+
const [id, h] = work.pop();
|
|
78
|
+
const b = blocks[id];
|
|
79
|
+
if (b.entryHeight >= 0)
|
|
80
|
+
continue;
|
|
81
|
+
b.entryHeight = h;
|
|
82
|
+
let height = h;
|
|
83
|
+
for (const ins of b.insns) {
|
|
84
|
+
const { pop, push } = (0, analysis_js_1.stackEffect)(abc, ins);
|
|
85
|
+
height = Math.max(0, height - pop) + push;
|
|
86
|
+
}
|
|
87
|
+
for (const s of b.succs)
|
|
88
|
+
work.push([s, height]);
|
|
89
|
+
}
|
|
90
|
+
// Drop edges from unreachable (dead) blocks so they do not disturb structuring.
|
|
91
|
+
const handlerSet = new Set(handlerBlocks);
|
|
92
|
+
for (const b of blocks) {
|
|
93
|
+
if (b.entryHeight >= 0 || handlerSet.has(b.id))
|
|
94
|
+
continue;
|
|
95
|
+
for (const s of b.succs) {
|
|
96
|
+
const t = blocks[s];
|
|
97
|
+
t.preds = t.preds.filter((p) => p !== b.id);
|
|
98
|
+
}
|
|
99
|
+
b.succs = [];
|
|
100
|
+
}
|
|
101
|
+
// Dominators (Cooper, Harvey & Kennedy) with exception edges from try starts.
|
|
102
|
+
const extraSuccs = new Map();
|
|
103
|
+
for (const e of exceptions) {
|
|
104
|
+
const from = labelBlock.get(e.from);
|
|
105
|
+
const target = labelBlock.get(e.target);
|
|
106
|
+
if (from === undefined || target === undefined)
|
|
107
|
+
continue;
|
|
108
|
+
// Every block inside the try range may throw; using the first one is
|
|
109
|
+
// enough for reachability and keeps handlers dominated by the try start.
|
|
110
|
+
extraSuccs.set(from, [...(extraSuccs.get(from) ?? []), target]);
|
|
111
|
+
}
|
|
112
|
+
const succsOf = (id) => [...blocks[id].succs, ...(extraSuccs.get(id) ?? [])];
|
|
113
|
+
const predsOf = blocks.map(() => []);
|
|
114
|
+
for (const b of blocks)
|
|
115
|
+
for (const s of new Set(succsOf(b.id)))
|
|
116
|
+
predsOf[s].push(b.id);
|
|
117
|
+
const order = rpo(blocks.length, [0], succsOf);
|
|
118
|
+
const idom = dominators(blocks.length, 0, order, (id) => predsOf[id]);
|
|
119
|
+
// Post-dominators: reverse graph with a virtual exit node (index n).
|
|
120
|
+
const n = blocks.length;
|
|
121
|
+
const exitPreds = [];
|
|
122
|
+
for (const b of blocks)
|
|
123
|
+
if (b.succs.length === 0)
|
|
124
|
+
exitPreds.push(b.id);
|
|
125
|
+
const rsuccs = (id) => (id === n ? exitPreds : blocks[id].preds);
|
|
126
|
+
const rpreds = (id) => {
|
|
127
|
+
if (id === n)
|
|
128
|
+
return [];
|
|
129
|
+
const b = blocks[id];
|
|
130
|
+
return b.succs.length === 0 ? [n] : [...new Set(b.succs)];
|
|
131
|
+
};
|
|
132
|
+
const rorder = rpo(n + 1, [n], rsuccs);
|
|
133
|
+
const pdom = dominators(n + 1, n, rorder, rpreds);
|
|
134
|
+
const ipdom = blocks.map((b) => {
|
|
135
|
+
const p = pdom[b.id];
|
|
136
|
+
return p === n || p === undefined ? -1 : p;
|
|
137
|
+
});
|
|
138
|
+
return { blocks, labelBlock, exceptions: [...exceptions], idom, ipdom, order };
|
|
139
|
+
}
|
|
140
|
+
function rpo(n, roots, succs) {
|
|
141
|
+
const order = new Array(n).fill(-1);
|
|
142
|
+
const visited = new Uint8Array(n);
|
|
143
|
+
const post = [];
|
|
144
|
+
for (const root of roots) {
|
|
145
|
+
const stack = [[root, 0]];
|
|
146
|
+
visited[root] = 1;
|
|
147
|
+
while (stack.length) {
|
|
148
|
+
const top = stack[stack.length - 1];
|
|
149
|
+
const ss = succs(top[0]);
|
|
150
|
+
if (top[1] < ss.length) {
|
|
151
|
+
const s = ss[top[1]++];
|
|
152
|
+
if (!visited[s]) {
|
|
153
|
+
visited[s] = 1;
|
|
154
|
+
stack.push([s, 0]);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
post.push(top[0]);
|
|
159
|
+
stack.pop();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
post.reverse().forEach((id, i) => (order[id] = i));
|
|
164
|
+
return order;
|
|
165
|
+
}
|
|
166
|
+
function dominators(n, root, order, preds) {
|
|
167
|
+
const idom = new Array(n).fill(-1);
|
|
168
|
+
idom[root] = root;
|
|
169
|
+
const nodes = [...Array(n).keys()].filter((i) => order[i] >= 0).sort((a, b) => order[a] - order[b]);
|
|
170
|
+
const intersect = (a, b) => {
|
|
171
|
+
while (a !== b) {
|
|
172
|
+
while (order[a] > order[b])
|
|
173
|
+
a = idom[a];
|
|
174
|
+
while (order[b] > order[a])
|
|
175
|
+
b = idom[b];
|
|
176
|
+
}
|
|
177
|
+
return a;
|
|
178
|
+
};
|
|
179
|
+
let changed = true;
|
|
180
|
+
while (changed) {
|
|
181
|
+
changed = false;
|
|
182
|
+
for (const b of nodes) {
|
|
183
|
+
if (b === root)
|
|
184
|
+
continue;
|
|
185
|
+
let nd = -1;
|
|
186
|
+
for (const p of preds(b)) {
|
|
187
|
+
if (idom[p] === -1 || order[p] < 0)
|
|
188
|
+
continue;
|
|
189
|
+
nd = nd === -1 ? p : intersect(p, nd);
|
|
190
|
+
}
|
|
191
|
+
if (nd !== -1 && idom[b] !== nd) {
|
|
192
|
+
idom[b] = nd;
|
|
193
|
+
changed = true;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
idom[root] = -1;
|
|
198
|
+
return idom;
|
|
199
|
+
}
|
|
200
|
+
/** True if `a` dominates `b`. */
|
|
201
|
+
function dominates(cfg, a, b) {
|
|
202
|
+
let x = b;
|
|
203
|
+
for (let guard = 0; x !== -1 && guard < 100000; guard++) {
|
|
204
|
+
if (x === a)
|
|
205
|
+
return true;
|
|
206
|
+
x = cfg.idom[x];
|
|
207
|
+
}
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
/** Natural loops keyed by header block. */
|
|
211
|
+
function findLoops(cfg) {
|
|
212
|
+
const loops = new Map();
|
|
213
|
+
for (const b of cfg.blocks) {
|
|
214
|
+
for (const s of b.succs) {
|
|
215
|
+
if (!dominates(cfg, s, b.id))
|
|
216
|
+
continue;
|
|
217
|
+
let loop = loops.get(s);
|
|
218
|
+
if (!loop) {
|
|
219
|
+
loop = { header: s, body: new Set([s]), latches: [] };
|
|
220
|
+
loops.set(s, loop);
|
|
221
|
+
}
|
|
222
|
+
if (!loop.latches.includes(b.id))
|
|
223
|
+
loop.latches.push(b.id);
|
|
224
|
+
const stack = [b.id];
|
|
225
|
+
while (stack.length) {
|
|
226
|
+
const x = stack.pop();
|
|
227
|
+
if (loop.body.has(x))
|
|
228
|
+
continue;
|
|
229
|
+
loop.body.add(x);
|
|
230
|
+
for (const p of cfg.blocks[x].preds)
|
|
231
|
+
stack.push(p);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return loops;
|
|
236
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prints a whole AS3 class (package, imports, fields, methods) from ABC.
|
|
3
|
+
*/
|
|
4
|
+
import type { AbcFile } from '../abc/abc-file.js';
|
|
5
|
+
export interface DecompileClassOptions {
|
|
6
|
+
indentUnit?: string;
|
|
7
|
+
}
|
|
8
|
+
/** Decompiles class `classIndex` to AS3 source text. */
|
|
9
|
+
export declare function decompileClass(abc: AbcFile, classIndex: number, options?: DecompileClassOptions): string;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.decompileClass = decompileClass;
|
|
4
|
+
const constants_js_1 = require("../abc/constants.js");
|
|
5
|
+
const model_js_1 = require("../abc/model.js");
|
|
6
|
+
const method_js_1 = require("./method.js");
|
|
7
|
+
function modifier(abc, mn, isInterface) {
|
|
8
|
+
if (isInterface)
|
|
9
|
+
return '';
|
|
10
|
+
const m = abc.multinames[mn];
|
|
11
|
+
const ns = m ? abc.namespaces[m.ns] : undefined;
|
|
12
|
+
switch (ns?.kind) {
|
|
13
|
+
case constants_js_1.NamespaceKind.PrivateNs:
|
|
14
|
+
return 'private ';
|
|
15
|
+
case constants_js_1.NamespaceKind.ProtectedNamespace:
|
|
16
|
+
case constants_js_1.NamespaceKind.StaticProtectedNs:
|
|
17
|
+
return 'protected ';
|
|
18
|
+
case constants_js_1.NamespaceKind.PackageInternalNs:
|
|
19
|
+
return 'internal ';
|
|
20
|
+
case constants_js_1.NamespaceKind.Namespace: {
|
|
21
|
+
const uri = abc.strings[ns.name] ?? '';
|
|
22
|
+
const tail = uri.split(/[/:]/).filter(Boolean).pop() ?? 'ns';
|
|
23
|
+
return `${/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(tail) ? tail : 'ns'} `;
|
|
24
|
+
}
|
|
25
|
+
default:
|
|
26
|
+
return 'public ';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function metadataLines(abc, t, indent) {
|
|
30
|
+
const out = [];
|
|
31
|
+
for (const mi of t.metadata) {
|
|
32
|
+
const md = abc.metadata[mi];
|
|
33
|
+
if (!md)
|
|
34
|
+
continue;
|
|
35
|
+
const items = md.keys.map((k, i) => {
|
|
36
|
+
const v = JSON.stringify(abc.strings[md.values[i]] ?? '');
|
|
37
|
+
return k ? `${abc.strings[k]}=${v}` : v;
|
|
38
|
+
});
|
|
39
|
+
out.push(`${indent}[${abc.strings[md.name]}${items.length ? `(${items.join(',')})` : ''}]`);
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
function traitLines(env, t, isStatic, isInterface, indent, initText) {
|
|
44
|
+
const { abc } = env;
|
|
45
|
+
const name = abc.multinameName(t.name);
|
|
46
|
+
const mods = `${t.attrs & constants_js_1.TraitAttr.Override ? 'override ' : ''}${t.attrs & constants_js_1.TraitAttr.Final && !isStatic ? 'final ' : ''}${modifier(abc, t.name, isInterface)}${isStatic ? 'static ' : ''}`;
|
|
47
|
+
const out = metadataLines(abc, t, indent);
|
|
48
|
+
switch (t.kind) {
|
|
49
|
+
case constants_js_1.TraitKind.Slot:
|
|
50
|
+
case constants_js_1.TraitKind.Const: {
|
|
51
|
+
const kw = t.kind === constants_js_1.TraitKind.Const ? 'const' : 'var';
|
|
52
|
+
const init = t.valueIndex ? ` = ${(0, method_js_1.formatDefault)(abc, t.valueKind, t.valueIndex)}` : initText !== undefined ? ` = ${initText}` : '';
|
|
53
|
+
out.push(`${indent}${mods}${kw} ${name}:${(0, method_js_1.typeNameOf)(env, t.typeName)}${init};`);
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
case constants_js_1.TraitKind.Method:
|
|
57
|
+
case constants_js_1.TraitKind.Getter:
|
|
58
|
+
case constants_js_1.TraitKind.Setter:
|
|
59
|
+
case constants_js_1.TraitKind.Function: {
|
|
60
|
+
const accessor = t.kind === constants_js_1.TraitKind.Getter ? 'get ' : t.kind === constants_js_1.TraitKind.Setter ? 'set ' : '';
|
|
61
|
+
const m = abc.methods[t.method];
|
|
62
|
+
const ret = m ? (0, method_js_1.typeNameOf)(env, m.returnType) : '*';
|
|
63
|
+
const sig = `${indent}${mods}function ${accessor}${name}(${(0, method_js_1.formatParams)(env, t.method)}):${ret}`;
|
|
64
|
+
if (isInterface || !abc.methodBody(t.method)) {
|
|
65
|
+
out.push(`${sig};`);
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
const body = (0, method_js_1.decompileMethodBody)(env, t.method, indent);
|
|
69
|
+
out.push(sig, `${indent}{`, ...body.lines, `${indent}}`);
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
case constants_js_1.TraitKind.Class:
|
|
73
|
+
out.push(`${indent}// class ${name}`);
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
/** Decompiles class `classIndex` to AS3 source text. */
|
|
79
|
+
function decompileClass(abc, classIndex, options = {}) {
|
|
80
|
+
const unit = options.indentUnit ?? ' ';
|
|
81
|
+
const inst = abc.instances[classIndex];
|
|
82
|
+
const cls = abc.classes[classIndex];
|
|
83
|
+
if (!inst || !cls)
|
|
84
|
+
throw new Error(`Class index ${classIndex} out of range`);
|
|
85
|
+
const qualified = abc.className(classIndex);
|
|
86
|
+
const dot = qualified.lastIndexOf('.');
|
|
87
|
+
const pkg = dot >= 0 ? qualified.slice(0, dot) : '';
|
|
88
|
+
const shortName = abc.multinameName(inst.name);
|
|
89
|
+
const isInterface = (inst.flags & constants_js_1.InstanceFlags.Interface) !== 0;
|
|
90
|
+
const script = (0, model_js_1.scriptOfClass)(abc, classIndex);
|
|
91
|
+
const imports = new Set();
|
|
92
|
+
const env = {
|
|
93
|
+
abc,
|
|
94
|
+
thisTraits: inst.traits,
|
|
95
|
+
globalTraits: script >= 0 ? abc.scripts[script].traits : [],
|
|
96
|
+
imports,
|
|
97
|
+
currentPackage: pkg,
|
|
98
|
+
indentUnit: unit,
|
|
99
|
+
};
|
|
100
|
+
const I1 = unit;
|
|
101
|
+
const I2 = unit + unit;
|
|
102
|
+
const body = [];
|
|
103
|
+
// Fields first (static, then instance), then methods.
|
|
104
|
+
const slots = (traits) => traits.filter((t) => t.kind === constants_js_1.TraitKind.Slot || t.kind === constants_js_1.TraitKind.Const);
|
|
105
|
+
const methods = (traits) => traits.filter((t) => t.kind !== constants_js_1.TraitKind.Slot && t.kind !== constants_js_1.TraitKind.Const);
|
|
106
|
+
// Static initialiser: leading `NAME = value` assignments become field initialisers.
|
|
107
|
+
const staticInits = new Map();
|
|
108
|
+
let cinitLines = [];
|
|
109
|
+
if (!isInterface && abc.methodBody(cls.cinit)) {
|
|
110
|
+
const cenv = { ...env, thisTraits: cls.traits };
|
|
111
|
+
const r = (0, method_js_1.decompileMethodStatements)(cenv, cls.cinit);
|
|
112
|
+
if ('error' in r) {
|
|
113
|
+
cinitLines = (0, method_js_1.decompileMethodBody)(cenv, cls.cinit, I2).lines;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
const slotNames = new Set(slots(cls.traits).filter((t) => !t.valueIndex).map((t) => abc.multinameName(t.name)));
|
|
117
|
+
let k = 0;
|
|
118
|
+
for (; k < r.stmts.length; k++) {
|
|
119
|
+
const s = r.stmts[k];
|
|
120
|
+
if (s.k !== 'expr' || s.e.k !== 'assign' || s.e.op)
|
|
121
|
+
break;
|
|
122
|
+
const tgt = s.e.target;
|
|
123
|
+
const name = tgt.k === 'name' ? tgt.name : tgt.k === 'member' && (tgt.obj.k === 'this' || tgt.obj.k === 'findprop' || tgt.obj.k === 'global') ? tgt.name : undefined;
|
|
124
|
+
if (!name || !slotNames.has(name) || staticInits.has(name))
|
|
125
|
+
break;
|
|
126
|
+
staticInits.set(name, r.printer.expr(s.e.v, 1, I2));
|
|
127
|
+
}
|
|
128
|
+
cinitLines = r.printer.stmts(r.stmts.slice(k), I2 + unit);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (const t of slots(cls.traits))
|
|
132
|
+
body.push(...traitLines(env, t, true, isInterface, I2, staticInits.get(abc.multinameName(t.name))));
|
|
133
|
+
if (slots(cls.traits).length)
|
|
134
|
+
body.push('');
|
|
135
|
+
for (const t of slots(inst.traits))
|
|
136
|
+
body.push(...traitLines(env, t, false, isInterface, I2));
|
|
137
|
+
if (slots(inst.traits).length)
|
|
138
|
+
body.push('');
|
|
139
|
+
if (cinitLines.length)
|
|
140
|
+
body.push(`${I2}{`, ...cinitLines, `${I2}}`, '');
|
|
141
|
+
// Constructor
|
|
142
|
+
if (!isInterface) {
|
|
143
|
+
const ctor = (0, method_js_1.decompileMethodBody)(env, inst.iinit, I2);
|
|
144
|
+
const lines = ctor.lines.filter((l, i, all) => !(all.length === 1 && /^\s*super\(\);$/.test(l)));
|
|
145
|
+
body.push(`${I2}public function ${shortName}(${(0, method_js_1.formatParams)(env, inst.iinit)})`, `${I2}{`, ...lines, `${I2}}`, '');
|
|
146
|
+
}
|
|
147
|
+
for (const t of methods(cls.traits))
|
|
148
|
+
body.push(...traitLines(env, t, true, isInterface, I2), '');
|
|
149
|
+
for (const t of methods(inst.traits))
|
|
150
|
+
body.push(...traitLines(env, t, false, isInterface, I2), '');
|
|
151
|
+
while (body.length && body[body.length - 1] === '')
|
|
152
|
+
body.pop();
|
|
153
|
+
// Header (computed after the body so imports are complete).
|
|
154
|
+
const superName = inst.superName ? (0, method_js_1.typeNameOf)(env, inst.superName) : '';
|
|
155
|
+
const ifaces = inst.interfaces.map((i) => (0, method_js_1.typeNameOf)(env, i));
|
|
156
|
+
imports.delete(qualified);
|
|
157
|
+
const mods = `${modifier(abc, inst.name, false)}${inst.flags & constants_js_1.InstanceFlags.Final ? 'final ' : ''}${!(inst.flags & constants_js_1.InstanceFlags.Sealed) && !isInterface ? 'dynamic ' : ''}`;
|
|
158
|
+
let header;
|
|
159
|
+
if (isInterface) {
|
|
160
|
+
header = `${I1}${mods}interface ${shortName}${ifaces.length ? ` extends ${ifaces.join(', ')}` : ''}`;
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
header = `${I1}${mods}class ${shortName}${superName && superName !== 'Object' ? ` extends ${superName}` : ''}${ifaces.length ? ` implements ${ifaces.join(', ')}` : ''}`;
|
|
164
|
+
}
|
|
165
|
+
const out = [`package${pkg ? ' ' + pkg : ''}`, '{'];
|
|
166
|
+
const sortedImports = [...imports].sort();
|
|
167
|
+
for (const imp of sortedImports)
|
|
168
|
+
out.push(`${I1}import ${imp};`);
|
|
169
|
+
if (sortedImports.length)
|
|
170
|
+
out.push('');
|
|
171
|
+
out.push(header, `${I1}{`, '', ...body, `${I1}}`, '}', '');
|
|
172
|
+
return out.join('\n');
|
|
173
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Method-level decompilation: bytecode → structured AS3 statements → text.
|
|
3
|
+
*/
|
|
4
|
+
import type { AbcFile, MethodInfo, Trait } from '../abc/abc-file.js';
|
|
5
|
+
import { Printer, type Stmt } from './ast.js';
|
|
6
|
+
export interface DecompileEnv {
|
|
7
|
+
abc: AbcFile;
|
|
8
|
+
/** Instance traits (for getslot on `this`). */
|
|
9
|
+
thisTraits?: Trait[];
|
|
10
|
+
/** Script traits (for getglobalslot). */
|
|
11
|
+
globalTraits?: Trait[];
|
|
12
|
+
/** Fully qualified names referenced by the code (filled in). */
|
|
13
|
+
imports?: Set<string>;
|
|
14
|
+
/** Package of the class being decompiled (no import needed for it). */
|
|
15
|
+
currentPackage?: string;
|
|
16
|
+
indentUnit?: string;
|
|
17
|
+
}
|
|
18
|
+
/** Short type name for a multiname; records an import when needed. */
|
|
19
|
+
export declare function typeNameOf(env: DecompileEnv, mn: number): string;
|
|
20
|
+
export declare function useNameOf(env: DecompileEnv, mn: number): void;
|
|
21
|
+
export declare function paramNames(abc: AbcFile, method: MethodInfo): string[];
|
|
22
|
+
/** Parameter list text: `a:int, b:String = "x", ...rest`. */
|
|
23
|
+
export declare function formatParams(env: DecompileEnv, methodIndex: number, printer?: Printer): string;
|
|
24
|
+
/** Default value literal for optional parameters and slots. */
|
|
25
|
+
export declare function formatDefault(abc: AbcFile, kind: number, index: number): string;
|
|
26
|
+
export interface DecompiledMethod {
|
|
27
|
+
/** Body lines (already indented with `indent + indentUnit`), without braces. */
|
|
28
|
+
lines: string[];
|
|
29
|
+
/** Set when decompilation failed and P-code was emitted instead. */
|
|
30
|
+
error?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface DecompiledStatements {
|
|
33
|
+
stmts: Stmt[];
|
|
34
|
+
printer: Printer;
|
|
35
|
+
}
|
|
36
|
+
/** Decompiles a method body into lines of AS3. */
|
|
37
|
+
export declare function decompileMethodBody(env: DecompileEnv, methodIndex: number, indent: string): DecompiledMethod;
|
|
38
|
+
/** Decompiles a method body to statements plus a printer bound to its local names. */
|
|
39
|
+
export declare function decompileMethodStatements(env: DecompileEnv, methodIndex: number): DecompiledStatements | {
|
|
40
|
+
error: string;
|
|
41
|
+
};
|