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
package/dist/cli.js
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { Swf } from './swf/swf.js';
|
|
5
|
+
import { DefineSpriteTag, DoABCTag } from './swf/tags.js';
|
|
6
|
+
import { packSwf, unpackSwf } from './compression/swf-container.js';
|
|
7
|
+
import { classMethods, describeMethodRef, scriptMethods, scriptOfClass } from './abc/model.js';
|
|
8
|
+
import { disassembleMethod } from './abc/pcode/disassembler.js';
|
|
9
|
+
import { exportPcode, importPcode } from './project/pcode-project.js';
|
|
10
|
+
import { decompileClass } from './decompiler/class-printer.js';
|
|
11
|
+
import { exportSources } from './project/source-export.js';
|
|
12
|
+
import { importSources } from './project/source-import.js';
|
|
13
|
+
import { compileSources, createSwf } from './compiler/program.js';
|
|
14
|
+
import { exportAs2, importAs2 } from './avm1/project.js';
|
|
15
|
+
import { ALL_ASSET_KINDS, exportAssets, replaceAsset } from './assets/assets.js';
|
|
16
|
+
const HELP = `swf-toolkit <command> [options]
|
|
17
|
+
|
|
18
|
+
Inspect
|
|
19
|
+
info <file.swf> Header, tag statistics, ABC summary
|
|
20
|
+
tags <file.swf> List tags (nested sprite tags indented)
|
|
21
|
+
classes <file.swf> List AS3 classes
|
|
22
|
+
disasm <file.swf> <class> [method] Print P-code of a class or one of its methods
|
|
23
|
+
decompile <file.swf> <class> Print decompiled ActionScript 3 source of a class
|
|
24
|
+
|
|
25
|
+
Edit
|
|
26
|
+
export-pcode <file.swf> <dir> Write one editable .pcode file per class
|
|
27
|
+
import-pcode <file.swf> <dir> <out.swf> Apply edited .pcode files and save
|
|
28
|
+
export-as3 <file.swf> <dir> Write decompiled, editable .as sources
|
|
29
|
+
import-as3 <file.swf> <dir> <out.swf> Compile edited / new .as files and save
|
|
30
|
+
compile <file.swf> <out.swf> <a.as>... Compile the given .as files into the SWF
|
|
31
|
+
|
|
32
|
+
Build
|
|
33
|
+
build <out.swf> <Main.as> [more.as...] Compile sources into a new SWF
|
|
34
|
+
[--main <Class>] [--width N] [--height N]
|
|
35
|
+
|
|
36
|
+
AS1 / AS2
|
|
37
|
+
export-as2 <file.swf> <dir> Write DoAction / DoInitAction blocks as P-code
|
|
38
|
+
import-as2 <file.swf> <dir> <out.swf> Re-assemble edited AS1/AS2 P-code and save
|
|
39
|
+
|
|
40
|
+
Assets
|
|
41
|
+
export-assets <file.swf> <dir> [kinds] Export images, sounds, binary, texts (kinds: comma list)
|
|
42
|
+
replace <in.swf> <out.swf> <id> <file> [<id> <file>...]
|
|
43
|
+
Replace bitmaps (png/jpg/gif), sounds (mp3/wav),
|
|
44
|
+
binary data or edit-text strings by character id
|
|
45
|
+
|
|
46
|
+
Container
|
|
47
|
+
decompress <in.swf> <out.swf> Save uncompressed (FWS)
|
|
48
|
+
compress <in.swf> <out.swf> [zlib|lzma] Save compressed (default zlib)
|
|
49
|
+
|
|
50
|
+
Options
|
|
51
|
+
--compression <none|zlib|lzma> Output compression for import-pcode
|
|
52
|
+
-h, --help Show this help
|
|
53
|
+
`;
|
|
54
|
+
function fail(msg) {
|
|
55
|
+
process.stderr.write(`swf-toolkit: ${msg}\n`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
function compressionArg(v, fallback) {
|
|
59
|
+
if (v === undefined)
|
|
60
|
+
return fallback;
|
|
61
|
+
if (v === 'none' || v === 'zlib' || v === 'lzma')
|
|
62
|
+
return v;
|
|
63
|
+
fail(`invalid compression '${v}' (expected none, zlib or lzma)`);
|
|
64
|
+
}
|
|
65
|
+
function tagLine(tag, index, depth) {
|
|
66
|
+
const id = tag.characterId;
|
|
67
|
+
const len = tag.encodeBody().length;
|
|
68
|
+
const extra = tag instanceof DoABCTag ? ` name="${tag.abcName}"` : tag instanceof DefineSpriteTag ? ` frames=${tag.frameCount}` : '';
|
|
69
|
+
return `${' '.repeat(depth)}${String(index).padStart(5)} ${tag.name}${id !== undefined ? ` (id ${id})` : ''} len=${len}${extra}`;
|
|
70
|
+
}
|
|
71
|
+
async function main() {
|
|
72
|
+
const { values, positionals } = parseArgs({
|
|
73
|
+
allowPositionals: true,
|
|
74
|
+
options: {
|
|
75
|
+
help: { type: 'boolean', short: 'h' },
|
|
76
|
+
compression: { type: 'string' },
|
|
77
|
+
main: { type: 'string' },
|
|
78
|
+
width: { type: 'string' },
|
|
79
|
+
height: { type: 'string' },
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
const [cmd, ...args] = positionals;
|
|
83
|
+
if (values.help || !cmd) {
|
|
84
|
+
process.stdout.write(HELP);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const need = (n, usage) => {
|
|
88
|
+
if (args.length < n)
|
|
89
|
+
fail(`usage: swf-toolkit ${usage}`);
|
|
90
|
+
};
|
|
91
|
+
switch (cmd) {
|
|
92
|
+
case 'info': {
|
|
93
|
+
need(1, 'info <file.swf>');
|
|
94
|
+
const swf = await Swf.load(args[0]);
|
|
95
|
+
const counts = new Map();
|
|
96
|
+
for (const { tag } of swf.walkTags())
|
|
97
|
+
counts.set(tag.name, (counts.get(tag.name) ?? 0) + 1);
|
|
98
|
+
const out = [
|
|
99
|
+
`version ${swf.version}`,
|
|
100
|
+
`compression ${swf.compression}`,
|
|
101
|
+
`stage ${swf.width} x ${swf.height} px`,
|
|
102
|
+
`frame rate ${swf.frameRate}`,
|
|
103
|
+
`frames ${swf.frameCount}`,
|
|
104
|
+
`tags ${swf.tags.length} top-level`,
|
|
105
|
+
];
|
|
106
|
+
if (swf.documentClass)
|
|
107
|
+
out.push(`document ${swf.documentClass}`);
|
|
108
|
+
for (const tag of swf.abcTags) {
|
|
109
|
+
const abc = tag.abc;
|
|
110
|
+
out.push(`abc "${tag.abcName}" v${abc.majorVersion}.${abc.minorVersion} ${abc.instances.length} classes, ${abc.methods.length} methods, ${abc.bodies.length} bodies`);
|
|
111
|
+
}
|
|
112
|
+
out.push('', 'tag counts:');
|
|
113
|
+
for (const [name, n] of [...counts].sort((a, b) => b[1] - a[1]))
|
|
114
|
+
out.push(` ${String(n).padStart(6)} ${name}`);
|
|
115
|
+
process.stdout.write(out.join('\n') + '\n');
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case 'tags': {
|
|
119
|
+
need(1, 'tags <file.swf>');
|
|
120
|
+
const swf = await Swf.load(args[0]);
|
|
121
|
+
const lines = [];
|
|
122
|
+
const walk = (tags, depth) => {
|
|
123
|
+
tags.forEach((t, i) => {
|
|
124
|
+
lines.push(tagLine(t, i, depth));
|
|
125
|
+
if (t instanceof DefineSpriteTag)
|
|
126
|
+
walk(t.tags, depth + 1);
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
walk(swf.tags, 0);
|
|
130
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case 'classes': {
|
|
134
|
+
need(1, 'classes <file.swf>');
|
|
135
|
+
const swf = await Swf.load(args[0]);
|
|
136
|
+
const lines = [];
|
|
137
|
+
for (const tag of swf.abcTags)
|
|
138
|
+
for (const name of tag.abc.classNames())
|
|
139
|
+
lines.push(name);
|
|
140
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case 'disasm': {
|
|
144
|
+
need(2, 'disasm <file.swf> <class> [method]');
|
|
145
|
+
const swf = await Swf.load(args[0]);
|
|
146
|
+
const [, className, methodName] = args;
|
|
147
|
+
for (const tag of swf.abcTags) {
|
|
148
|
+
const abc = tag.abc;
|
|
149
|
+
const ci = abc.findClass(className);
|
|
150
|
+
if (ci < 0)
|
|
151
|
+
continue;
|
|
152
|
+
const s = scriptOfClass(abc, ci);
|
|
153
|
+
const refs = [...(s >= 0 ? scriptMethods(abc, s, false) : []), ...classMethods(abc, ci)];
|
|
154
|
+
const selected = methodName
|
|
155
|
+
? refs.filter((r) => r.name === methodName || (methodName === 'constructor' && r.role === 'constructor') || (methodName === 'cinit' && r.role === 'class-init'))
|
|
156
|
+
: refs;
|
|
157
|
+
if (!selected.length)
|
|
158
|
+
fail(`method '${methodName}' not found in ${className}`);
|
|
159
|
+
process.stdout.write(selected.map((r) => disassembleMethod(abc, r.methodIndex, { title: describeMethodRef(abc, className, r) })).join('\n'));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
fail(`class '${className}' not found`);
|
|
163
|
+
}
|
|
164
|
+
case 'decompile': {
|
|
165
|
+
need(2, 'decompile <file.swf> <class>');
|
|
166
|
+
const swf = await Swf.load(args[0]);
|
|
167
|
+
for (const tag of swf.abcTags) {
|
|
168
|
+
const ci = tag.abc.findClass(args[1]);
|
|
169
|
+
if (ci >= 0) {
|
|
170
|
+
process.stdout.write(decompileClass(tag.abc, ci));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
fail(`class '${args[1]}' not found`);
|
|
175
|
+
}
|
|
176
|
+
case 'export-pcode': {
|
|
177
|
+
need(2, 'export-pcode <file.swf> <dir>');
|
|
178
|
+
const swf = await Swf.load(args[0]);
|
|
179
|
+
const r = await exportPcode(swf, args[1]);
|
|
180
|
+
process.stdout.write(`Exported ${r.methodCount} methods in ${r.files.length} files to ${r.outDir}\n`);
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
case 'import-pcode': {
|
|
184
|
+
need(3, 'import-pcode <file.swf> <dir> <out.swf>');
|
|
185
|
+
const swf = await Swf.load(args[0]);
|
|
186
|
+
const r = await importPcode(swf, args[1]);
|
|
187
|
+
await swf.save(args[2], { compression: compressionArg(values.compression) });
|
|
188
|
+
for (const c of r.changed)
|
|
189
|
+
process.stdout.write(` updated method ${c.methodIndex} (${c.file})\n`);
|
|
190
|
+
process.stdout.write(`${r.changed.length} method(s) updated, ${r.unchanged} unchanged. Saved ${args[2]}\n`);
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
case 'export-as3': {
|
|
194
|
+
need(2, 'export-as3 <file.swf> <dir>');
|
|
195
|
+
const swf = await Swf.load(args[0]);
|
|
196
|
+
const r = await exportSources(swf, args[1]);
|
|
197
|
+
process.stdout.write(`Wrote ${r.files.length} files to ${args[1]}${r.failed.length ? ` (${r.failed.length} classes failed, see comments in files)` : ''}\n`);
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
case 'import-as3':
|
|
201
|
+
case 'compile': {
|
|
202
|
+
const isImport = cmd === 'import-as3';
|
|
203
|
+
need(3, isImport ? 'import-as3 <file.swf> <dir> <out.swf>' : 'compile <file.swf> <out.swf> <a.as> [b.as...]');
|
|
204
|
+
const swf = await Swf.load(args[0]);
|
|
205
|
+
const out = isImport ? args[2] : args[1];
|
|
206
|
+
const r = isImport ? await importSources(swf, args[1]) : await importSources(swf, process.cwd(), { files: args.slice(2) });
|
|
207
|
+
await swf.save(out, { compression: compressionArg(values.compression) });
|
|
208
|
+
for (const c of r.changes)
|
|
209
|
+
process.stdout.write(` ${c.action.padEnd(8)} ${c.className}${c.member === '<class>' ? '' : ' :: ' + c.member}\n`);
|
|
210
|
+
process.stdout.write(`${r.changes.length} change(s). Saved ${out}\n`);
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case 'build': {
|
|
214
|
+
need(2, 'build <out.swf> <Main.as> [more.as...]');
|
|
215
|
+
const files = await Promise.all(args.slice(1).map(async (f) => ({ name: f, text: await readFile(f, 'utf8') })));
|
|
216
|
+
const { abc } = compileSources(files);
|
|
217
|
+
const main = values.main ?? (() => {
|
|
218
|
+
const first = args[1].split('/').pop().replace(/\.as$/, '');
|
|
219
|
+
const ci = abc.instances.findIndex((_, i) => abc.className(i).split('.').pop() === first);
|
|
220
|
+
return ci >= 0 ? abc.className(ci) : first;
|
|
221
|
+
})();
|
|
222
|
+
const swf = createSwf(abc, {
|
|
223
|
+
documentClass: main,
|
|
224
|
+
width: values.width ? Number(values.width) : undefined,
|
|
225
|
+
height: values.height ? Number(values.height) : undefined,
|
|
226
|
+
});
|
|
227
|
+
await swf.save(args[0], { compression: compressionArg(values.compression) });
|
|
228
|
+
process.stdout.write(`Built ${args[0]} (${abc.instances.length} classes, document class ${main})\n`);
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
case 'export-as2': {
|
|
232
|
+
need(2, 'export-as2 <file.swf> <dir>');
|
|
233
|
+
const files = await exportAs2(await Swf.load(args[0]), args[1]);
|
|
234
|
+
process.stdout.write(`Wrote ${files.length} AS1/AS2 blocks to ${args[1]}\n`);
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
case 'import-as2': {
|
|
238
|
+
need(3, 'import-as2 <file.swf> <dir> <out.swf>');
|
|
239
|
+
const swf = await Swf.load(args[0]);
|
|
240
|
+
const changed = await importAs2(swf, args[1]);
|
|
241
|
+
await swf.save(args[2], { compression: compressionArg(values.compression) });
|
|
242
|
+
for (const k of changed)
|
|
243
|
+
process.stdout.write(` updated ${k}\n`);
|
|
244
|
+
process.stdout.write(`${changed.length} block(s) updated. Saved ${args[2]}\n`);
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
case 'export-assets': {
|
|
248
|
+
need(2, 'export-assets <file.swf> <dir> [images,sounds,binary,texts]');
|
|
249
|
+
const swf = await Swf.load(args[0]);
|
|
250
|
+
const kinds = (args[2] ? args[2].split(',') : ALL_ASSET_KINDS);
|
|
251
|
+
const r = await exportAssets(swf, args[1], kinds);
|
|
252
|
+
for (const s of r.skipped)
|
|
253
|
+
process.stdout.write(` skipped ${s.id}: ${s.reason}\n`);
|
|
254
|
+
process.stdout.write(`Exported ${r.files.length} files to ${args[1]}\n`);
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
case 'replace': {
|
|
258
|
+
need(4, 'replace <in.swf> <out.swf> <id> <file> [<id> <file>...]');
|
|
259
|
+
if ((args.length - 2) % 2)
|
|
260
|
+
fail('expected <id> <file> pairs');
|
|
261
|
+
const swf = await Swf.load(args[0]);
|
|
262
|
+
for (let i = 2; i < args.length; i += 2) {
|
|
263
|
+
const id = Number(args[i]);
|
|
264
|
+
if (!Number.isInteger(id))
|
|
265
|
+
fail(`invalid character id '${args[i]}'`);
|
|
266
|
+
const tag = replaceAsset(swf, id, new Uint8Array(await readFile(args[i + 1])));
|
|
267
|
+
process.stdout.write(` ${id} <- ${args[i + 1]} (${tag.name})\n`);
|
|
268
|
+
}
|
|
269
|
+
await swf.save(args[1], { compression: compressionArg(values.compression) });
|
|
270
|
+
process.stdout.write(`Saved ${args[1]}\n`);
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
case 'decompress':
|
|
274
|
+
case 'compress': {
|
|
275
|
+
need(2, `${cmd} <in.swf> <out.swf>`);
|
|
276
|
+
const c = unpackSwf(new Uint8Array(await readFile(args[0])));
|
|
277
|
+
const compression = cmd === 'decompress' ? 'none' : compressionArg(args[2], 'zlib');
|
|
278
|
+
const out = packSwf({ ...c, compression });
|
|
279
|
+
await writeFile(args[1], out);
|
|
280
|
+
process.stdout.write(`${args[0]} (${c.compression}) -> ${args[1]} (${compression}, ${out.length} bytes)\n`);
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
default:
|
|
284
|
+
fail(`unknown command '${cmd}' (see --help)`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// Exit quietly when output is piped into e.g. `head`.
|
|
288
|
+
process.stdout.on('error', (e) => {
|
|
289
|
+
if (e.code === 'EPIPE')
|
|
290
|
+
process.exit(0);
|
|
291
|
+
throw e;
|
|
292
|
+
});
|
|
293
|
+
main().catch((e) => {
|
|
294
|
+
const err = e;
|
|
295
|
+
fail(err.message ?? String(e));
|
|
296
|
+
});
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/** AST produced by the AS3 parser. */
|
|
2
|
+
export interface Pos {
|
|
3
|
+
line: number;
|
|
4
|
+
col: number;
|
|
5
|
+
}
|
|
6
|
+
export interface TypeRef {
|
|
7
|
+
name: string;
|
|
8
|
+
/** Vector.<T> parameters. */
|
|
9
|
+
params?: TypeRef[];
|
|
10
|
+
pos: Pos;
|
|
11
|
+
}
|
|
12
|
+
export type Expr = ({
|
|
13
|
+
k: 'num';
|
|
14
|
+
v: number;
|
|
15
|
+
} | {
|
|
16
|
+
k: 'str';
|
|
17
|
+
v: string;
|
|
18
|
+
} | {
|
|
19
|
+
k: 'bool';
|
|
20
|
+
v: boolean;
|
|
21
|
+
} | {
|
|
22
|
+
k: 'null';
|
|
23
|
+
} | {
|
|
24
|
+
k: 'undefined';
|
|
25
|
+
} | {
|
|
26
|
+
k: 'regex';
|
|
27
|
+
pattern: string;
|
|
28
|
+
flags: string;
|
|
29
|
+
} | {
|
|
30
|
+
k: 'id';
|
|
31
|
+
name: string;
|
|
32
|
+
ns?: string;
|
|
33
|
+
} | {
|
|
34
|
+
k: 'this';
|
|
35
|
+
} | {
|
|
36
|
+
k: 'super';
|
|
37
|
+
} | {
|
|
38
|
+
k: 'member';
|
|
39
|
+
obj: Expr;
|
|
40
|
+
name: string;
|
|
41
|
+
ns?: string;
|
|
42
|
+
attr?: boolean;
|
|
43
|
+
} | {
|
|
44
|
+
k: 'index';
|
|
45
|
+
obj: Expr;
|
|
46
|
+
index: Expr;
|
|
47
|
+
attr?: boolean;
|
|
48
|
+
} | {
|
|
49
|
+
k: 'descendants';
|
|
50
|
+
obj: Expr;
|
|
51
|
+
name: string;
|
|
52
|
+
attr?: boolean;
|
|
53
|
+
} | {
|
|
54
|
+
k: 'filter';
|
|
55
|
+
obj: Expr;
|
|
56
|
+
cond: Expr;
|
|
57
|
+
} | {
|
|
58
|
+
k: 'typeapply';
|
|
59
|
+
base: Expr;
|
|
60
|
+
params: TypeRef[];
|
|
61
|
+
} | {
|
|
62
|
+
k: 'call';
|
|
63
|
+
fn: Expr;
|
|
64
|
+
args: Expr[];
|
|
65
|
+
} | {
|
|
66
|
+
k: 'new';
|
|
67
|
+
ctor: Expr;
|
|
68
|
+
args: Expr[];
|
|
69
|
+
} | {
|
|
70
|
+
k: 'vector';
|
|
71
|
+
type: TypeRef;
|
|
72
|
+
items: Expr[];
|
|
73
|
+
} | {
|
|
74
|
+
k: 'unary';
|
|
75
|
+
op: string;
|
|
76
|
+
e: Expr;
|
|
77
|
+
} | {
|
|
78
|
+
k: 'update';
|
|
79
|
+
op: '++' | '--';
|
|
80
|
+
prefix: boolean;
|
|
81
|
+
e: Expr;
|
|
82
|
+
} | {
|
|
83
|
+
k: 'binary';
|
|
84
|
+
op: string;
|
|
85
|
+
a: Expr;
|
|
86
|
+
b: Expr;
|
|
87
|
+
} | {
|
|
88
|
+
k: 'cond';
|
|
89
|
+
c: Expr;
|
|
90
|
+
t: Expr;
|
|
91
|
+
f: Expr;
|
|
92
|
+
} | {
|
|
93
|
+
k: 'assign';
|
|
94
|
+
op: string;
|
|
95
|
+
target: Expr;
|
|
96
|
+
v: Expr;
|
|
97
|
+
} | {
|
|
98
|
+
k: 'array';
|
|
99
|
+
items: Array<Expr | null>;
|
|
100
|
+
} | {
|
|
101
|
+
k: 'object';
|
|
102
|
+
props: Array<{
|
|
103
|
+
key: string | number;
|
|
104
|
+
value: Expr;
|
|
105
|
+
}>;
|
|
106
|
+
} | {
|
|
107
|
+
k: 'function';
|
|
108
|
+
fn: FunctionDef;
|
|
109
|
+
} | {
|
|
110
|
+
k: 'comma';
|
|
111
|
+
list: Expr[];
|
|
112
|
+
} | {
|
|
113
|
+
k: 'xml';
|
|
114
|
+
text: string;
|
|
115
|
+
}) & {
|
|
116
|
+
pos: Pos;
|
|
117
|
+
};
|
|
118
|
+
export interface Param {
|
|
119
|
+
name: string;
|
|
120
|
+
type?: TypeRef;
|
|
121
|
+
init?: Expr;
|
|
122
|
+
}
|
|
123
|
+
export interface FunctionDef {
|
|
124
|
+
name?: string;
|
|
125
|
+
params: Param[];
|
|
126
|
+
rest?: {
|
|
127
|
+
name: string;
|
|
128
|
+
type?: TypeRef;
|
|
129
|
+
};
|
|
130
|
+
returnType?: TypeRef;
|
|
131
|
+
body?: Stmt[];
|
|
132
|
+
pos: Pos;
|
|
133
|
+
}
|
|
134
|
+
export interface VarDecl {
|
|
135
|
+
name: string;
|
|
136
|
+
type?: TypeRef;
|
|
137
|
+
init?: Expr;
|
|
138
|
+
pos: Pos;
|
|
139
|
+
}
|
|
140
|
+
export interface CatchClause {
|
|
141
|
+
name: string;
|
|
142
|
+
type?: TypeRef;
|
|
143
|
+
body: Stmt[];
|
|
144
|
+
}
|
|
145
|
+
export type Stmt = ({
|
|
146
|
+
k: 'expr';
|
|
147
|
+
e: Expr;
|
|
148
|
+
} | {
|
|
149
|
+
k: 'var';
|
|
150
|
+
isConst: boolean;
|
|
151
|
+
decls: VarDecl[];
|
|
152
|
+
} | {
|
|
153
|
+
k: 'function';
|
|
154
|
+
fn: FunctionDef;
|
|
155
|
+
} | {
|
|
156
|
+
k: 'if';
|
|
157
|
+
c: Expr;
|
|
158
|
+
then: Stmt;
|
|
159
|
+
else?: Stmt;
|
|
160
|
+
} | {
|
|
161
|
+
k: 'while';
|
|
162
|
+
c: Expr;
|
|
163
|
+
body: Stmt;
|
|
164
|
+
} | {
|
|
165
|
+
k: 'dowhile';
|
|
166
|
+
c: Expr;
|
|
167
|
+
body: Stmt;
|
|
168
|
+
} | {
|
|
169
|
+
k: 'for';
|
|
170
|
+
init?: Stmt;
|
|
171
|
+
c?: Expr;
|
|
172
|
+
update?: Expr;
|
|
173
|
+
body: Stmt;
|
|
174
|
+
} | {
|
|
175
|
+
k: 'forin';
|
|
176
|
+
each: boolean;
|
|
177
|
+
decl?: VarDecl;
|
|
178
|
+
target?: Expr;
|
|
179
|
+
obj: Expr;
|
|
180
|
+
body: Stmt;
|
|
181
|
+
} | {
|
|
182
|
+
k: 'switch';
|
|
183
|
+
disc: Expr;
|
|
184
|
+
cases: Array<{
|
|
185
|
+
test: Expr | null;
|
|
186
|
+
body: Stmt[];
|
|
187
|
+
}>;
|
|
188
|
+
} | {
|
|
189
|
+
k: 'try';
|
|
190
|
+
body: Stmt[];
|
|
191
|
+
catches: CatchClause[];
|
|
192
|
+
finally?: Stmt[];
|
|
193
|
+
} | {
|
|
194
|
+
k: 'return';
|
|
195
|
+
e?: Expr;
|
|
196
|
+
} | {
|
|
197
|
+
k: 'throw';
|
|
198
|
+
e: Expr;
|
|
199
|
+
} | {
|
|
200
|
+
k: 'break';
|
|
201
|
+
label?: string;
|
|
202
|
+
} | {
|
|
203
|
+
k: 'continue';
|
|
204
|
+
label?: string;
|
|
205
|
+
} | {
|
|
206
|
+
k: 'block';
|
|
207
|
+
body: Stmt[];
|
|
208
|
+
} | {
|
|
209
|
+
k: 'labeled';
|
|
210
|
+
label: string;
|
|
211
|
+
body: Stmt;
|
|
212
|
+
} | {
|
|
213
|
+
k: 'with';
|
|
214
|
+
obj: Expr;
|
|
215
|
+
body: Stmt;
|
|
216
|
+
} | {
|
|
217
|
+
k: 'dxns';
|
|
218
|
+
e: Expr;
|
|
219
|
+
} | {
|
|
220
|
+
k: 'empty';
|
|
221
|
+
}) & {
|
|
222
|
+
pos: Pos;
|
|
223
|
+
};
|
|
224
|
+
export interface Metadata {
|
|
225
|
+
name: string;
|
|
226
|
+
items: Array<{
|
|
227
|
+
key?: string;
|
|
228
|
+
value: string;
|
|
229
|
+
}>;
|
|
230
|
+
}
|
|
231
|
+
export interface FieldMember {
|
|
232
|
+
k: 'field';
|
|
233
|
+
name: string;
|
|
234
|
+
isStatic: boolean;
|
|
235
|
+
isConst: boolean;
|
|
236
|
+
access: string;
|
|
237
|
+
type?: TypeRef;
|
|
238
|
+
init?: Expr;
|
|
239
|
+
metadata: Metadata[];
|
|
240
|
+
pos: Pos;
|
|
241
|
+
/** Source span [start, end) of the declaration. */
|
|
242
|
+
span: [number, number];
|
|
243
|
+
}
|
|
244
|
+
export interface MethodMember {
|
|
245
|
+
k: 'method';
|
|
246
|
+
name: string;
|
|
247
|
+
kind: 'method' | 'get' | 'set' | 'constructor';
|
|
248
|
+
isStatic: boolean;
|
|
249
|
+
isOverride: boolean;
|
|
250
|
+
isFinal: boolean;
|
|
251
|
+
isNative: boolean;
|
|
252
|
+
access: string;
|
|
253
|
+
fn: FunctionDef;
|
|
254
|
+
metadata: Metadata[];
|
|
255
|
+
pos: Pos;
|
|
256
|
+
span: [number, number];
|
|
257
|
+
}
|
|
258
|
+
export interface StaticBlockMember {
|
|
259
|
+
k: 'static';
|
|
260
|
+
body: Stmt[];
|
|
261
|
+
pos: Pos;
|
|
262
|
+
span: [number, number];
|
|
263
|
+
}
|
|
264
|
+
export type Member = FieldMember | MethodMember | StaticBlockMember;
|
|
265
|
+
export interface ClassDef {
|
|
266
|
+
name: string;
|
|
267
|
+
isInterface: boolean;
|
|
268
|
+
isDynamic: boolean;
|
|
269
|
+
isFinal: boolean;
|
|
270
|
+
access: string;
|
|
271
|
+
extends?: TypeRef;
|
|
272
|
+
implements: TypeRef[];
|
|
273
|
+
members: Member[];
|
|
274
|
+
metadata: Metadata[];
|
|
275
|
+
pos: Pos;
|
|
276
|
+
}
|
|
277
|
+
export interface CompilationUnit {
|
|
278
|
+
package: string;
|
|
279
|
+
imports: string[];
|
|
280
|
+
/** `use namespace x` declarations. */
|
|
281
|
+
useNamespaces: string[];
|
|
282
|
+
classes: ClassDef[];
|
|
283
|
+
/** Package-level functions / variables (rare). */
|
|
284
|
+
functions: MethodMember[];
|
|
285
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compiles an edited AS3 class back into an existing ABC.
|
|
3
|
+
*
|
|
4
|
+
* Only members whose source text changed compared with a baseline (by
|
|
5
|
+
* default the decompiled text of the current bytecode) are recompiled; all
|
|
6
|
+
* other methods keep their original bytecode.
|
|
7
|
+
*/
|
|
8
|
+
import type { AbcFile } from '../abc/abc-file.js';
|
|
9
|
+
import { Interner } from '../abc/interner.js';
|
|
10
|
+
export interface CompileClassOptions {
|
|
11
|
+
/** File name used in error messages. */
|
|
12
|
+
source?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Baseline source the edit started from. Members identical to the baseline
|
|
15
|
+
* are not recompiled. Defaults to the decompiled class; pass `null` to
|
|
16
|
+
* recompile every member.
|
|
17
|
+
*/
|
|
18
|
+
baseline?: string | null;
|
|
19
|
+
/** Reuse an interner across calls on the same ABC. */
|
|
20
|
+
interner?: Interner;
|
|
21
|
+
/** Exact class index to compile into (for ABCs with duplicate class names). */
|
|
22
|
+
classIndex?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface CompiledMember {
|
|
25
|
+
className: string;
|
|
26
|
+
member: string;
|
|
27
|
+
action: 'replaced' | 'added';
|
|
28
|
+
methodIndex?: number;
|
|
29
|
+
}
|
|
30
|
+
export interface CompileClassResult {
|
|
31
|
+
changes: CompiledMember[];
|
|
32
|
+
}
|
|
33
|
+
/** Compiles every class in `source` into `abc`. */
|
|
34
|
+
export declare function compileClassSource(abc: AbcFile, source: string, options?: CompileClassOptions): CompileClassResult;
|