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,642 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AVM1 (ActionScript 1/2) bytecode: decoding to a label-based action list,
|
|
3
|
+
* P-code text formatting and parsing, and re-encoding.
|
|
4
|
+
*/
|
|
5
|
+
import { ByteReader } from '../io/reader.js';
|
|
6
|
+
import { ByteWriter } from '../io/writer.js';
|
|
7
|
+
const NAMES = {
|
|
8
|
+
0x00: 'End', 0x04: 'NextFrame', 0x05: 'PrevFrame', 0x06: 'Play', 0x07: 'Stop', 0x08: 'ToggleQuality', 0x09: 'StopSounds',
|
|
9
|
+
0x0a: 'Add', 0x0b: 'Subtract', 0x0c: 'Multiply', 0x0d: 'Divide', 0x0e: 'Equals', 0x0f: 'Less', 0x10: 'And', 0x11: 'Or',
|
|
10
|
+
0x12: 'Not', 0x13: 'StringEquals', 0x14: 'StringLength', 0x15: 'StringExtract', 0x17: 'Pop', 0x18: 'ToInteger',
|
|
11
|
+
0x1c: 'GetVariable', 0x1d: 'SetVariable', 0x20: 'SetTarget2', 0x21: 'StringAdd', 0x22: 'GetProperty', 0x23: 'SetProperty',
|
|
12
|
+
0x24: 'CloneSprite', 0x25: 'RemoveSprite', 0x26: 'Trace', 0x27: 'StartDrag', 0x28: 'EndDrag', 0x29: 'StringLess',
|
|
13
|
+
0x2a: 'Throw', 0x2b: 'CastOp', 0x2c: 'ImplementsOp', 0x30: 'RandomNumber', 0x31: 'MBStringLength', 0x32: 'CharToAscii',
|
|
14
|
+
0x33: 'AsciiToChar', 0x34: 'GetTime', 0x35: 'MBStringExtract', 0x36: 'MBCharToAscii', 0x37: 'MBAsciiToChar',
|
|
15
|
+
0x3a: 'Delete', 0x3b: 'Delete2', 0x3c: 'DefineLocal', 0x3d: 'CallFunction', 0x3e: 'Return', 0x3f: 'Modulo',
|
|
16
|
+
0x40: 'NewObject', 0x41: 'DefineLocal2', 0x42: 'InitArray', 0x43: 'InitObject', 0x44: 'TypeOf', 0x45: 'TargetPath',
|
|
17
|
+
0x46: 'Enumerate', 0x47: 'Add2', 0x48: 'Less2', 0x49: 'Equals2', 0x4a: 'ToNumber', 0x4b: 'ToString', 0x4c: 'PushDuplicate',
|
|
18
|
+
0x4d: 'StackSwap', 0x4e: 'GetMember', 0x4f: 'SetMember', 0x50: 'Increment', 0x51: 'Decrement', 0x52: 'CallMethod',
|
|
19
|
+
0x53: 'NewMethod', 0x54: 'InstanceOf', 0x55: 'Enumerate2', 0x60: 'BitAnd', 0x61: 'BitOr', 0x62: 'BitXor',
|
|
20
|
+
0x63: 'BitLShift', 0x64: 'BitRShift', 0x65: 'BitURShift', 0x66: 'StrictEquals', 0x67: 'Greater', 0x68: 'StringGreater',
|
|
21
|
+
0x69: 'Extends', 0x81: 'GotoFrame', 0x83: 'GetURL', 0x87: 'StoreRegister', 0x88: 'ConstantPool', 0x8a: 'WaitForFrame',
|
|
22
|
+
0x8b: 'SetTarget', 0x8c: 'GoToLabel', 0x8d: 'WaitForFrame2', 0x8e: 'DefineFunction2', 0x8f: 'Try', 0x94: 'With',
|
|
23
|
+
0x96: 'Push', 0x99: 'Jump', 0x9a: 'GetURL2', 0x9b: 'DefineFunction', 0x9d: 'If', 0x9e: 'Call', 0x9f: 'GotoFrame2',
|
|
24
|
+
};
|
|
25
|
+
const CODES = new Map(Object.entries(NAMES).map(([k, v]) => [v.toLowerCase(), Number(k)]));
|
|
26
|
+
export function actionName(code) {
|
|
27
|
+
return NAMES[code] ?? `Action0x${code.toString(16).padStart(2, '0')}`;
|
|
28
|
+
}
|
|
29
|
+
function readDouble(r) {
|
|
30
|
+
// AVM1 doubles store the high 32-bit word first.
|
|
31
|
+
const hi = r.bytesView(4);
|
|
32
|
+
const lo = r.bytesView(4);
|
|
33
|
+
const b = new Uint8Array(8);
|
|
34
|
+
b.set(lo, 0);
|
|
35
|
+
b.set(hi, 4);
|
|
36
|
+
return new DataView(b.buffer).getFloat64(0, true);
|
|
37
|
+
}
|
|
38
|
+
function writeDouble(w, v) {
|
|
39
|
+
const b = new Uint8Array(8);
|
|
40
|
+
new DataView(b.buffer).setFloat64(0, v, true);
|
|
41
|
+
w.bytes(b.subarray(4, 8));
|
|
42
|
+
w.bytes(b.subarray(0, 4));
|
|
43
|
+
}
|
|
44
|
+
/** Decodes AVM1 action bytes. Branches and block sizes become labels. */
|
|
45
|
+
export function decodeActions(bytes) {
|
|
46
|
+
const actions = [];
|
|
47
|
+
const r = new ByteReader(bytes);
|
|
48
|
+
while (!r.eof) {
|
|
49
|
+
const offset = r.pos;
|
|
50
|
+
const code = r.u8();
|
|
51
|
+
const action = { type: 'action', code, args: [], labels: [] };
|
|
52
|
+
const targets = [];
|
|
53
|
+
if (code < 0x80) {
|
|
54
|
+
actions.push({ offset, end: r.pos, action, targets });
|
|
55
|
+
if (code === 0)
|
|
56
|
+
break;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const len = r.remaining >= 2 ? r.u16() : 0;
|
|
60
|
+
const bodyStart = r.pos;
|
|
61
|
+
const body = new ByteReader(bytes, bodyStart, Math.min(bytes.length, bodyStart + len));
|
|
62
|
+
const end = bodyStart + len;
|
|
63
|
+
try {
|
|
64
|
+
switch (code) {
|
|
65
|
+
case 0x81:
|
|
66
|
+
case 0x8a:
|
|
67
|
+
action.args.push(body.u16());
|
|
68
|
+
if (code === 0x8a)
|
|
69
|
+
action.args.push(body.u8());
|
|
70
|
+
break;
|
|
71
|
+
case 0x83:
|
|
72
|
+
action.args.push(body.cstring(), body.cstring());
|
|
73
|
+
break;
|
|
74
|
+
case 0x87:
|
|
75
|
+
case 0x8d:
|
|
76
|
+
case 0x9a:
|
|
77
|
+
action.args.push(body.u8());
|
|
78
|
+
break;
|
|
79
|
+
case 0x9f: {
|
|
80
|
+
const flags = body.u8();
|
|
81
|
+
action.args.push(flags);
|
|
82
|
+
if (flags & 2)
|
|
83
|
+
action.args.push(body.u16());
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case 0x88: {
|
|
87
|
+
const n = body.u16();
|
|
88
|
+
for (let i = 0; i < n; i++)
|
|
89
|
+
action.args.push(body.cstring());
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
case 0x8b:
|
|
93
|
+
case 0x8c:
|
|
94
|
+
action.args.push(body.cstring());
|
|
95
|
+
break;
|
|
96
|
+
case 0x96: {
|
|
97
|
+
const values = [];
|
|
98
|
+
while (!body.eof) {
|
|
99
|
+
const t = body.u8();
|
|
100
|
+
switch (t) {
|
|
101
|
+
case 0:
|
|
102
|
+
values.push({ t: 'string', v: body.cstring() });
|
|
103
|
+
break;
|
|
104
|
+
case 1:
|
|
105
|
+
values.push({ t: 'float', v: body.f32() });
|
|
106
|
+
break;
|
|
107
|
+
case 2:
|
|
108
|
+
values.push({ t: 'null' });
|
|
109
|
+
break;
|
|
110
|
+
case 3:
|
|
111
|
+
values.push({ t: 'undefined' });
|
|
112
|
+
break;
|
|
113
|
+
case 4:
|
|
114
|
+
values.push({ t: 'register', v: body.u8() });
|
|
115
|
+
break;
|
|
116
|
+
case 5:
|
|
117
|
+
values.push({ t: 'boolean', v: body.u8() !== 0 });
|
|
118
|
+
break;
|
|
119
|
+
case 6:
|
|
120
|
+
values.push({ t: 'double', v: readDouble(body) });
|
|
121
|
+
break;
|
|
122
|
+
case 7:
|
|
123
|
+
values.push({ t: 'integer', v: body.s32() });
|
|
124
|
+
break;
|
|
125
|
+
case 8:
|
|
126
|
+
values.push({ t: 'constant8', v: body.u8() });
|
|
127
|
+
break;
|
|
128
|
+
case 9:
|
|
129
|
+
values.push({ t: 'constant16', v: body.u16() });
|
|
130
|
+
break;
|
|
131
|
+
default: throw new Error(`Bad push type ${t}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
action.args.push(...values);
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
case 0x99:
|
|
138
|
+
case 0x9d:
|
|
139
|
+
targets.push(end + body.s16());
|
|
140
|
+
break;
|
|
141
|
+
case 0x94:
|
|
142
|
+
targets.push(end + body.u16());
|
|
143
|
+
break;
|
|
144
|
+
case 0x9b: {
|
|
145
|
+
action.args.push(body.cstring());
|
|
146
|
+
const n = body.u16();
|
|
147
|
+
const params = [];
|
|
148
|
+
for (let i = 0; i < n; i++)
|
|
149
|
+
params.push(body.cstring());
|
|
150
|
+
action.args.push(params);
|
|
151
|
+
targets.push(end + body.u16());
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
case 0x8e: {
|
|
155
|
+
action.args.push(body.cstring());
|
|
156
|
+
const n = body.u16();
|
|
157
|
+
action.args.push(body.u8(), body.u16());
|
|
158
|
+
const params = [];
|
|
159
|
+
for (let i = 0; i < n; i++)
|
|
160
|
+
params.push([body.u8(), body.cstring()]);
|
|
161
|
+
action.args.push(params);
|
|
162
|
+
targets.push(end + body.u16());
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
case 0x8f: {
|
|
166
|
+
const flags = body.u8();
|
|
167
|
+
const trySize = body.u16();
|
|
168
|
+
const catchSize = body.u16();
|
|
169
|
+
const finallySize = body.u16();
|
|
170
|
+
action.args.push(flags, flags & 4 ? body.u8() : body.cstring());
|
|
171
|
+
targets.push(end + trySize, end + trySize + catchSize, end + trySize + catchSize + finallySize);
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
default:
|
|
175
|
+
action.raw = bytes.slice(bodyStart, end);
|
|
176
|
+
}
|
|
177
|
+
if (!action.raw && !body.eof) {
|
|
178
|
+
// Trailing bytes we do not understand: keep the whole payload verbatim.
|
|
179
|
+
action.args = [];
|
|
180
|
+
targets.length = 0;
|
|
181
|
+
action.raw = bytes.slice(bodyStart, end);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
action.args = [];
|
|
186
|
+
targets.length = 0;
|
|
187
|
+
action.raw = bytes.slice(bodyStart, end);
|
|
188
|
+
}
|
|
189
|
+
r.pos = Math.min(bytes.length, end);
|
|
190
|
+
actions.push({ offset, end: r.pos, action, targets });
|
|
191
|
+
}
|
|
192
|
+
// Assign labels.
|
|
193
|
+
const offsets = new Set();
|
|
194
|
+
for (const a of actions)
|
|
195
|
+
for (const t of a.targets)
|
|
196
|
+
offsets.add(t);
|
|
197
|
+
const labelOf = new Map();
|
|
198
|
+
[...offsets].sort((a, b) => a - b).forEach((o, i) => labelOf.set(o, i));
|
|
199
|
+
const items = [];
|
|
200
|
+
const starts = new Set(actions.map((a) => a.offset));
|
|
201
|
+
const endOffset = actions.length ? actions[actions.length - 1].end : 0;
|
|
202
|
+
for (const a of actions) {
|
|
203
|
+
const l = labelOf.get(a.offset);
|
|
204
|
+
if (l !== undefined)
|
|
205
|
+
items.push({ type: 'label', id: l });
|
|
206
|
+
a.action.labels = a.targets.map((t) => labelOf.get(t));
|
|
207
|
+
items.push(a.action);
|
|
208
|
+
}
|
|
209
|
+
for (const [o, l] of labelOf) {
|
|
210
|
+
if (o >= endOffset)
|
|
211
|
+
items.push({ type: 'label', id: l });
|
|
212
|
+
else if (!starts.has(o))
|
|
213
|
+
throw new Error(`AVM1 branch to offset ${o} inside an action`);
|
|
214
|
+
}
|
|
215
|
+
return items;
|
|
216
|
+
}
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
// Encoding
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
function encodeBody(a, labelPos, endOfAction) {
|
|
221
|
+
if (a.raw)
|
|
222
|
+
return a.raw;
|
|
223
|
+
const w = new ByteWriter(32);
|
|
224
|
+
const A = a.args;
|
|
225
|
+
switch (a.code) {
|
|
226
|
+
case 0x81:
|
|
227
|
+
w.u16(A[0]);
|
|
228
|
+
break;
|
|
229
|
+
case 0x8a:
|
|
230
|
+
w.u16(A[0]).u8(A[1]);
|
|
231
|
+
break;
|
|
232
|
+
case 0x83:
|
|
233
|
+
w.cstring(A[0]).cstring(A[1]);
|
|
234
|
+
break;
|
|
235
|
+
case 0x87:
|
|
236
|
+
case 0x8d:
|
|
237
|
+
case 0x9a:
|
|
238
|
+
w.u8(A[0]);
|
|
239
|
+
break;
|
|
240
|
+
case 0x9f:
|
|
241
|
+
w.u8(A[0]);
|
|
242
|
+
if (A[0] & 2)
|
|
243
|
+
w.u16(A[1]);
|
|
244
|
+
break;
|
|
245
|
+
case 0x88:
|
|
246
|
+
w.u16(A.length);
|
|
247
|
+
for (const s of A)
|
|
248
|
+
w.cstring(s);
|
|
249
|
+
break;
|
|
250
|
+
case 0x8b:
|
|
251
|
+
case 0x8c:
|
|
252
|
+
w.cstring(A[0]);
|
|
253
|
+
break;
|
|
254
|
+
case 0x96:
|
|
255
|
+
for (const v of A) {
|
|
256
|
+
switch (v.t) {
|
|
257
|
+
case 'string':
|
|
258
|
+
w.u8(0).cstring(v.v);
|
|
259
|
+
break;
|
|
260
|
+
case 'float':
|
|
261
|
+
w.u8(1).f32(v.v);
|
|
262
|
+
break;
|
|
263
|
+
case 'null':
|
|
264
|
+
w.u8(2);
|
|
265
|
+
break;
|
|
266
|
+
case 'undefined':
|
|
267
|
+
w.u8(3);
|
|
268
|
+
break;
|
|
269
|
+
case 'register':
|
|
270
|
+
w.u8(4).u8(v.v);
|
|
271
|
+
break;
|
|
272
|
+
case 'boolean':
|
|
273
|
+
w.u8(5).u8(v.v ? 1 : 0);
|
|
274
|
+
break;
|
|
275
|
+
case 'double':
|
|
276
|
+
w.u8(6);
|
|
277
|
+
writeDouble(w, v.v);
|
|
278
|
+
break;
|
|
279
|
+
case 'integer':
|
|
280
|
+
w.u8(7).s32(v.v);
|
|
281
|
+
break;
|
|
282
|
+
case 'constant8':
|
|
283
|
+
w.u8(8).u8(v.v);
|
|
284
|
+
break;
|
|
285
|
+
case 'constant16':
|
|
286
|
+
w.u8(9).u16(v.v);
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
break;
|
|
291
|
+
case 0x99:
|
|
292
|
+
case 0x9d:
|
|
293
|
+
w.s16(labelPos(a.labels[0]) - endOfAction);
|
|
294
|
+
break;
|
|
295
|
+
case 0x94:
|
|
296
|
+
w.u16(labelPos(a.labels[0]) - endOfAction);
|
|
297
|
+
break;
|
|
298
|
+
case 0x9b: {
|
|
299
|
+
w.cstring(A[0]);
|
|
300
|
+
const params = A[1];
|
|
301
|
+
w.u16(params.length);
|
|
302
|
+
for (const p of params)
|
|
303
|
+
w.cstring(p);
|
|
304
|
+
w.u16(labelPos(a.labels[0]) - endOfAction);
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
case 0x8e: {
|
|
308
|
+
w.cstring(A[0]);
|
|
309
|
+
const params = A[3];
|
|
310
|
+
w.u16(params.length).u8(A[1]).u16(A[2]);
|
|
311
|
+
for (const [reg, name] of params)
|
|
312
|
+
w.u8(reg).cstring(name);
|
|
313
|
+
w.u16(labelPos(a.labels[0]) - endOfAction);
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
case 0x8f: {
|
|
317
|
+
const flags = A[0];
|
|
318
|
+
const t = labelPos(a.labels[0]);
|
|
319
|
+
const c = labelPos(a.labels[1]);
|
|
320
|
+
const f = labelPos(a.labels[2]);
|
|
321
|
+
w.u8(flags).u16(t - endOfAction).u16(c - t).u16(f - c);
|
|
322
|
+
if (flags & 4)
|
|
323
|
+
w.u8(A[1]);
|
|
324
|
+
else
|
|
325
|
+
w.cstring(A[1]);
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return w.toBytes();
|
|
330
|
+
}
|
|
331
|
+
/** Encodes an action list back to bytes. Sizes are fixed-point iterated (branches never change length). */
|
|
332
|
+
export function encodeActions(items) {
|
|
333
|
+
// Branch operand sizes are constant, so one sizing pass with dummy label positions suffices.
|
|
334
|
+
const sizes = items.map((it) => (it.type === 'label' ? 0 : it.code < 0x80 ? 1 : 3 + encodeBody(it, () => 0, 0).length));
|
|
335
|
+
const labelPos = new Map();
|
|
336
|
+
let off = 0;
|
|
337
|
+
items.forEach((it, i) => {
|
|
338
|
+
if (it.type === 'label')
|
|
339
|
+
labelPos.set(it.id, off);
|
|
340
|
+
off += sizes[i];
|
|
341
|
+
});
|
|
342
|
+
const pos = (id) => {
|
|
343
|
+
const p = labelPos.get(id);
|
|
344
|
+
if (p === undefined)
|
|
345
|
+
throw new Error(`Undefined AVM1 label L${id}`);
|
|
346
|
+
return p;
|
|
347
|
+
};
|
|
348
|
+
const w = new ByteWriter(off + 16);
|
|
349
|
+
for (let i = 0; i < items.length; i++) {
|
|
350
|
+
const it = items[i];
|
|
351
|
+
if (it.type === 'label')
|
|
352
|
+
continue;
|
|
353
|
+
w.u8(it.code);
|
|
354
|
+
if (it.code < 0x80)
|
|
355
|
+
continue;
|
|
356
|
+
const body = encodeBody(it, pos, w.length + sizes[i] - 1);
|
|
357
|
+
w.u16(body.length);
|
|
358
|
+
w.bytes(body);
|
|
359
|
+
}
|
|
360
|
+
return w.toBytes();
|
|
361
|
+
}
|
|
362
|
+
// ---------------------------------------------------------------------------
|
|
363
|
+
// Text format
|
|
364
|
+
// ---------------------------------------------------------------------------
|
|
365
|
+
function fmtValue(v) {
|
|
366
|
+
switch (v.t) {
|
|
367
|
+
case 'string': return JSON.stringify(v.v);
|
|
368
|
+
case 'float': return `f:${v.v}`;
|
|
369
|
+
case 'null': return 'null';
|
|
370
|
+
case 'undefined': return 'undefined';
|
|
371
|
+
case 'register': return `r:${v.v}`;
|
|
372
|
+
case 'boolean': return String(v.v);
|
|
373
|
+
case 'double': return `d:${Number.isNaN(v.v) ? 'NaN' : v.v}`;
|
|
374
|
+
case 'integer': return String(v.v);
|
|
375
|
+
case 'constant8': return `c:${v.v}`;
|
|
376
|
+
case 'constant16': return `C:${v.v}`;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/** Formats actions as editable AS1/AS2 P-code. */
|
|
380
|
+
export function formatActions(items, options = {}) {
|
|
381
|
+
const L = (id) => `L${id}`;
|
|
382
|
+
let pool = options.constantPool ?? [];
|
|
383
|
+
const lines = [];
|
|
384
|
+
for (const it of items) {
|
|
385
|
+
if (it.type === 'label') {
|
|
386
|
+
lines.push(`${L(it.id)}:`);
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const name = actionName(it.code);
|
|
390
|
+
if (it.raw) {
|
|
391
|
+
lines.push(` .action 0x${it.code.toString(16).padStart(2, '0')} ${Array.from(it.raw, (b) => b.toString(16).padStart(2, '0')).join(' ')}`.trimEnd());
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
const A = it.args;
|
|
395
|
+
let text;
|
|
396
|
+
switch (it.code) {
|
|
397
|
+
case 0x88:
|
|
398
|
+
pool = A;
|
|
399
|
+
text = A.map((s) => JSON.stringify(s)).join(', ');
|
|
400
|
+
break;
|
|
401
|
+
case 0x96: {
|
|
402
|
+
const vals = A;
|
|
403
|
+
text = vals.map(fmtValue).join(', ');
|
|
404
|
+
const hints = vals.filter((v) => v.t === 'constant8' || v.t === 'constant16').map((v) => `${fmtValue(v)}=${JSON.stringify(pool[v.v] ?? '?')}`);
|
|
405
|
+
if (hints.length)
|
|
406
|
+
text += ` ; ${hints.join(' ')}`;
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
case 0x99:
|
|
410
|
+
case 0x9d:
|
|
411
|
+
case 0x94:
|
|
412
|
+
text = L(it.labels[0]);
|
|
413
|
+
break;
|
|
414
|
+
case 0x9b:
|
|
415
|
+
text = `${JSON.stringify(A[0])}, [${A[1].map((p) => JSON.stringify(p)).join(', ')}], ${L(it.labels[0])}`;
|
|
416
|
+
break;
|
|
417
|
+
case 0x8e:
|
|
418
|
+
text = `${JSON.stringify(A[0])}, ${A[1]}, 0x${A[2].toString(16)}, [${A[3].map(([r, n]) => `${r}:${JSON.stringify(n)}`).join(', ')}], ${L(it.labels[0])}`;
|
|
419
|
+
break;
|
|
420
|
+
case 0x8f:
|
|
421
|
+
text = `0x${A[0].toString(16)}, ${typeof A[1] === 'string' ? JSON.stringify(A[1]) : `r:${A[1]}`}, ${it.labels.map(L).join(', ')}`;
|
|
422
|
+
break;
|
|
423
|
+
default:
|
|
424
|
+
text = A.map((x) => (typeof x === 'string' ? JSON.stringify(x) : String(x))).join(', ');
|
|
425
|
+
}
|
|
426
|
+
lines.push(` ${name}${text ? ' ' + text : ''}`);
|
|
427
|
+
}
|
|
428
|
+
return lines.join('\n') + '\n';
|
|
429
|
+
}
|
|
430
|
+
/** Parses P-code produced by {@link formatActions}. */
|
|
431
|
+
export function parseActions(text) {
|
|
432
|
+
const items = [];
|
|
433
|
+
const labels = new Map();
|
|
434
|
+
const label = (name) => {
|
|
435
|
+
let id = labels.get(name);
|
|
436
|
+
if (id === undefined)
|
|
437
|
+
labels.set(name, (id = labels.size));
|
|
438
|
+
return id;
|
|
439
|
+
};
|
|
440
|
+
text.split('\n').forEach((rawLine, lineIdx) => {
|
|
441
|
+
const fail = (msg) => {
|
|
442
|
+
throw new Error(`line ${lineIdx + 1}: ${msg}`);
|
|
443
|
+
};
|
|
444
|
+
// Strip comments outside strings.
|
|
445
|
+
let line = '';
|
|
446
|
+
let inStr = false;
|
|
447
|
+
for (let i = 0; i < rawLine.length; i++) {
|
|
448
|
+
const c = rawLine[i];
|
|
449
|
+
if (inStr) {
|
|
450
|
+
line += c;
|
|
451
|
+
if (c === '\\')
|
|
452
|
+
line += rawLine[++i] ?? '';
|
|
453
|
+
else if (c === '"')
|
|
454
|
+
inStr = false;
|
|
455
|
+
}
|
|
456
|
+
else if (c === ';')
|
|
457
|
+
break;
|
|
458
|
+
else {
|
|
459
|
+
if (c === '"')
|
|
460
|
+
inStr = true;
|
|
461
|
+
line += c;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
line = line.trim();
|
|
465
|
+
if (!line)
|
|
466
|
+
return;
|
|
467
|
+
const lm = /^([A-Za-z_$][\w$]*):$/.exec(line);
|
|
468
|
+
if (lm) {
|
|
469
|
+
items.push({ type: 'label', id: label(lm[1]) });
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const sp = line.search(/\s/);
|
|
473
|
+
const name = sp < 0 ? line : line.slice(0, sp);
|
|
474
|
+
const rest = sp < 0 ? '' : line.slice(sp + 1).trim();
|
|
475
|
+
if (name === '.action') {
|
|
476
|
+
const parts = rest.split(/\s+/);
|
|
477
|
+
items.push({ type: 'action', code: parseInt(parts[0], 16), args: [], labels: [], raw: new Uint8Array(parts.slice(1).map((h) => parseInt(h, 16))) });
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const code = CODES.get(name.toLowerCase()) ?? (/^Action0x[0-9a-f]{2}$/i.test(name) ? parseInt(name.slice(8), 16) : undefined);
|
|
481
|
+
if (code === undefined)
|
|
482
|
+
fail(`Unknown action '${name}'`);
|
|
483
|
+
// Tokenise operands: strings, [..] lists, plain words.
|
|
484
|
+
const toks = [];
|
|
485
|
+
{
|
|
486
|
+
let i = 0;
|
|
487
|
+
while (i < rest.length) {
|
|
488
|
+
const c = rest[i];
|
|
489
|
+
if (c === ' ' || c === ',' || c === '\t') {
|
|
490
|
+
i++;
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
if (c === '"') {
|
|
494
|
+
let j = i + 1;
|
|
495
|
+
while (j < rest.length && rest[j] !== '"')
|
|
496
|
+
j += rest[j] === '\\' ? 2 : 1;
|
|
497
|
+
toks.push(rest.slice(i, j + 1));
|
|
498
|
+
i = j + 1;
|
|
499
|
+
}
|
|
500
|
+
else if (c === '[') {
|
|
501
|
+
let depth = 0;
|
|
502
|
+
let j = i;
|
|
503
|
+
let s = false;
|
|
504
|
+
for (; j < rest.length; j++) {
|
|
505
|
+
const ch = rest[j];
|
|
506
|
+
if (s) {
|
|
507
|
+
if (ch === '\\')
|
|
508
|
+
j++;
|
|
509
|
+
else if (ch === '"')
|
|
510
|
+
s = false;
|
|
511
|
+
}
|
|
512
|
+
else if (ch === '"')
|
|
513
|
+
s = true;
|
|
514
|
+
else if (ch === '[')
|
|
515
|
+
depth++;
|
|
516
|
+
else if (ch === ']' && --depth === 0)
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
toks.push(rest.slice(i, j + 1));
|
|
520
|
+
i = j + 1;
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
let j = i;
|
|
524
|
+
while (j < rest.length && !/[\s,]/.test(rest[j]))
|
|
525
|
+
j++;
|
|
526
|
+
toks.push(rest.slice(i, j));
|
|
527
|
+
i = j;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
const str = (t) => {
|
|
532
|
+
if (!t || !t.startsWith('"'))
|
|
533
|
+
fail('Expected string');
|
|
534
|
+
return JSON.parse(t);
|
|
535
|
+
};
|
|
536
|
+
const num = (t) => {
|
|
537
|
+
const v = Number(t);
|
|
538
|
+
if (t === undefined || Number.isNaN(v))
|
|
539
|
+
fail(`Expected number, got '${t}'`);
|
|
540
|
+
return v;
|
|
541
|
+
};
|
|
542
|
+
const list = (t) => {
|
|
543
|
+
if (!t || !t.startsWith('['))
|
|
544
|
+
fail('Expected [list]');
|
|
545
|
+
const inner = t.slice(1, -1).trim();
|
|
546
|
+
if (!inner)
|
|
547
|
+
return [];
|
|
548
|
+
const out = [];
|
|
549
|
+
let i = 0;
|
|
550
|
+
while (i < inner.length) {
|
|
551
|
+
if (inner[i] === ',' || inner[i] === ' ') {
|
|
552
|
+
i++;
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
let j = i;
|
|
556
|
+
let s = false;
|
|
557
|
+
for (; j < inner.length; j++) {
|
|
558
|
+
const ch = inner[j];
|
|
559
|
+
if (s) {
|
|
560
|
+
if (ch === '\\')
|
|
561
|
+
j++;
|
|
562
|
+
else if (ch === '"')
|
|
563
|
+
s = false;
|
|
564
|
+
}
|
|
565
|
+
else if (ch === '"')
|
|
566
|
+
s = true;
|
|
567
|
+
else if (ch === ',')
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
out.push(inner.slice(i, j).trim());
|
|
571
|
+
i = j + 1;
|
|
572
|
+
}
|
|
573
|
+
return out;
|
|
574
|
+
};
|
|
575
|
+
const a = { type: 'action', code: code, args: [], labels: [] };
|
|
576
|
+
switch (code) {
|
|
577
|
+
case 0x88:
|
|
578
|
+
a.args = toks.map(str);
|
|
579
|
+
break;
|
|
580
|
+
case 0x96:
|
|
581
|
+
a.args = toks.map((t) => {
|
|
582
|
+
if (t.startsWith('"'))
|
|
583
|
+
return { t: 'string', v: str(t) };
|
|
584
|
+
if (t === 'null')
|
|
585
|
+
return { t: 'null' };
|
|
586
|
+
if (t === 'undefined')
|
|
587
|
+
return { t: 'undefined' };
|
|
588
|
+
if (t === 'true' || t === 'false')
|
|
589
|
+
return { t: 'boolean', v: t === 'true' };
|
|
590
|
+
const m = /^([frdcC]):(.*)$/.exec(t);
|
|
591
|
+
if (m) {
|
|
592
|
+
const v = m[2] === 'NaN' ? NaN : num(m[2]);
|
|
593
|
+
const types = { f: 'float', r: 'register', d: 'double', c: 'constant8', C: 'constant16' };
|
|
594
|
+
return { t: types[m[1]], v };
|
|
595
|
+
}
|
|
596
|
+
const v = num(t);
|
|
597
|
+
return Number.isInteger(v) && v >= -0x80000000 && v <= 0x7fffffff ? { t: 'integer', v } : { t: 'double', v };
|
|
598
|
+
});
|
|
599
|
+
break;
|
|
600
|
+
case 0x99:
|
|
601
|
+
case 0x9d:
|
|
602
|
+
case 0x94:
|
|
603
|
+
a.labels = [label(toks[0] ?? fail('Expected label'))];
|
|
604
|
+
break;
|
|
605
|
+
case 0x9b:
|
|
606
|
+
a.args = [str(toks[0]), list(toks[1]).map(str)];
|
|
607
|
+
a.labels = [label(toks[2] ?? fail('Expected label'))];
|
|
608
|
+
break;
|
|
609
|
+
case 0x8e:
|
|
610
|
+
a.args = [
|
|
611
|
+
str(toks[0]),
|
|
612
|
+
num(toks[1]),
|
|
613
|
+
num(toks[2]),
|
|
614
|
+
list(toks[3]).map((p) => {
|
|
615
|
+
const m = /^(\d+):(".*")$/.exec(p);
|
|
616
|
+
if (!m)
|
|
617
|
+
fail(`Expected register:"name", got '${p}'`);
|
|
618
|
+
return [Number(m[1]), JSON.parse(m[2])];
|
|
619
|
+
}),
|
|
620
|
+
];
|
|
621
|
+
a.labels = [label(toks[4] ?? fail('Expected label'))];
|
|
622
|
+
break;
|
|
623
|
+
case 0x8f:
|
|
624
|
+
a.args = [num(toks[0]), toks[1]?.startsWith('r:') ? num(toks[1].slice(2)) : str(toks[1])];
|
|
625
|
+
a.labels = toks.slice(2, 5).map(label);
|
|
626
|
+
if (a.labels.length !== 3)
|
|
627
|
+
fail('Try needs three labels (end of try, catch, finally)');
|
|
628
|
+
break;
|
|
629
|
+
case 0x83:
|
|
630
|
+
a.args = [str(toks[0]), str(toks[1])];
|
|
631
|
+
break;
|
|
632
|
+
case 0x8b:
|
|
633
|
+
case 0x8c:
|
|
634
|
+
a.args = [str(toks[0])];
|
|
635
|
+
break;
|
|
636
|
+
default:
|
|
637
|
+
a.args = toks.map(num);
|
|
638
|
+
}
|
|
639
|
+
items.push(a);
|
|
640
|
+
});
|
|
641
|
+
return items;
|
|
642
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Swf } from '../swf/swf.js';
|
|
2
|
+
import { RawTag } from '../swf/tags.js';
|
|
3
|
+
export interface ActionBlock {
|
|
4
|
+
/** Stable file key, e.g. "root_f3_t17" or "sprite12_f0_t2_init". */
|
|
5
|
+
key: string;
|
|
6
|
+
tag: RawTag;
|
|
7
|
+
/** Byte offset of the action stream inside the tag body (2 for DoInitAction). */
|
|
8
|
+
offset: number;
|
|
9
|
+
}
|
|
10
|
+
/** All AVM1 code blocks of an SWF (main timeline and sprites). */
|
|
11
|
+
export declare function actionBlocks(swf: Swf): ActionBlock[];
|
|
12
|
+
export declare function disassembleBlock(b: ActionBlock): string;
|
|
13
|
+
export declare function assembleBlock(b: ActionBlock, text: string): void;
|
|
14
|
+
export declare function exportAs2(swf: Swf, dir: string): Promise<string[]>;
|
|
15
|
+
/** Re-assembles blocks whose files changed. Returns the keys that were updated. */
|
|
16
|
+
export declare function importAs2(swf: Swf, dir: string): Promise<string[]>;
|