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.
Files changed (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +125 -2
  3. package/README.tr.md +126 -0
  4. package/dist/abc/abc-file.d.ts +168 -0
  5. package/dist/abc/abc-file.js +656 -0
  6. package/dist/abc/analysis.d.ts +36 -0
  7. package/dist/abc/analysis.js +213 -0
  8. package/dist/abc/code.d.ts +59 -0
  9. package/dist/abc/code.js +379 -0
  10. package/dist/abc/constants.d.ts +80 -0
  11. package/dist/abc/constants.js +93 -0
  12. package/dist/abc/interner.d.ts +46 -0
  13. package/dist/abc/interner.js +168 -0
  14. package/dist/abc/model.d.ts +40 -0
  15. package/dist/abc/model.js +146 -0
  16. package/dist/abc/opcodes.d.ts +39 -0
  17. package/dist/abc/opcodes.js +196 -0
  18. package/dist/abc/pcode/assembler.d.ts +42 -0
  19. package/dist/abc/pcode/assembler.js +635 -0
  20. package/dist/abc/pcode/disassembler.d.ts +17 -0
  21. package/dist/abc/pcode/disassembler.js +147 -0
  22. package/dist/abc/pcode/format.d.ts +23 -0
  23. package/dist/abc/pcode/format.js +131 -0
  24. package/dist/abc/pcode/lexer.d.ts +34 -0
  25. package/dist/abc/pcode/lexer.js +183 -0
  26. package/dist/assets/assets.d.ts +26 -0
  27. package/dist/assets/assets.js +151 -0
  28. package/dist/assets/images.d.ts +31 -0
  29. package/dist/assets/images.js +238 -0
  30. package/dist/assets/png.d.ts +12 -0
  31. package/dist/assets/png.js +196 -0
  32. package/dist/assets/sounds.d.ts +37 -0
  33. package/dist/assets/sounds.js +236 -0
  34. package/dist/avm1/avm1.d.ts +55 -0
  35. package/dist/avm1/avm1.js +642 -0
  36. package/dist/avm1/project.d.ts +16 -0
  37. package/dist/avm1/project.js +81 -0
  38. package/dist/cjs/abc/abc-file.d.ts +168 -0
  39. package/dist/cjs/abc/abc-file.js +663 -0
  40. package/dist/cjs/abc/analysis.d.ts +36 -0
  41. package/dist/cjs/abc/analysis.js +221 -0
  42. package/dist/cjs/abc/code.d.ts +59 -0
  43. package/dist/cjs/abc/code.js +387 -0
  44. package/dist/cjs/abc/constants.d.ts +80 -0
  45. package/dist/cjs/abc/constants.js +97 -0
  46. package/dist/cjs/abc/interner.d.ts +46 -0
  47. package/dist/cjs/abc/interner.js +172 -0
  48. package/dist/cjs/abc/model.d.ts +40 -0
  49. package/dist/cjs/abc/model.js +155 -0
  50. package/dist/cjs/abc/opcodes.d.ts +39 -0
  51. package/dist/cjs/abc/opcodes.js +201 -0
  52. package/dist/cjs/abc/pcode/assembler.d.ts +42 -0
  53. package/dist/cjs/abc/pcode/assembler.js +640 -0
  54. package/dist/cjs/abc/pcode/disassembler.d.ts +17 -0
  55. package/dist/cjs/abc/pcode/disassembler.js +152 -0
  56. package/dist/cjs/abc/pcode/format.d.ts +23 -0
  57. package/dist/cjs/abc/pcode/format.js +141 -0
  58. package/dist/cjs/abc/pcode/lexer.d.ts +34 -0
  59. package/dist/cjs/abc/pcode/lexer.js +188 -0
  60. package/dist/cjs/assets/assets.d.ts +26 -0
  61. package/dist/cjs/assets/assets.js +159 -0
  62. package/dist/cjs/assets/images.d.ts +31 -0
  63. package/dist/cjs/assets/images.js +246 -0
  64. package/dist/cjs/assets/png.d.ts +12 -0
  65. package/dist/cjs/assets/png.js +202 -0
  66. package/dist/cjs/assets/sounds.d.ts +37 -0
  67. package/dist/cjs/assets/sounds.js +246 -0
  68. package/dist/cjs/avm1/avm1.d.ts +55 -0
  69. package/dist/cjs/avm1/avm1.js +649 -0
  70. package/dist/cjs/avm1/project.d.ts +16 -0
  71. package/dist/cjs/avm1/project.js +88 -0
  72. package/dist/cjs/compiler/ast.d.ts +285 -0
  73. package/dist/cjs/compiler/ast.js +3 -0
  74. package/dist/cjs/compiler/class-compiler.d.ts +34 -0
  75. package/dist/cjs/compiler/class-compiler.js +440 -0
  76. package/dist/cjs/compiler/codegen.d.ts +144 -0
  77. package/dist/cjs/compiler/codegen.js +1589 -0
  78. package/dist/cjs/compiler/lexer.d.ts +27 -0
  79. package/dist/cjs/compiler/lexer.js +254 -0
  80. package/dist/cjs/compiler/parser.d.ts +58 -0
  81. package/dist/cjs/compiler/parser.js +989 -0
  82. package/dist/cjs/compiler/program.d.ts +29 -0
  83. package/dist/cjs/compiler/program.js +36 -0
  84. package/dist/cjs/compiler/scope.d.ts +22 -0
  85. package/dist/cjs/compiler/scope.js +242 -0
  86. package/dist/cjs/compression/lzma.d.ts +37 -0
  87. package/dist/cjs/compression/lzma.js +575 -0
  88. package/dist/cjs/compression/swf-container.d.ts +19 -0
  89. package/dist/cjs/compression/swf-container.js +92 -0
  90. package/dist/cjs/decompiler/ast.d.ts +228 -0
  91. package/dist/cjs/decompiler/ast.js +455 -0
  92. package/dist/cjs/decompiler/cfg.d.ts +40 -0
  93. package/dist/cjs/decompiler/cfg.js +236 -0
  94. package/dist/cjs/decompiler/class-printer.d.ts +9 -0
  95. package/dist/cjs/decompiler/class-printer.js +173 -0
  96. package/dist/cjs/decompiler/method.d.ts +41 -0
  97. package/dist/cjs/decompiler/method.js +436 -0
  98. package/dist/cjs/decompiler/simulate.d.ts +55 -0
  99. package/dist/cjs/decompiler/simulate.js +591 -0
  100. package/dist/cjs/decompiler/structure.d.ts +48 -0
  101. package/dist/cjs/decompiler/structure.js +764 -0
  102. package/dist/cjs/errors.d.ts +11 -0
  103. package/dist/cjs/errors.js +25 -0
  104. package/dist/cjs/index.d.ts +41 -0
  105. package/dist/cjs/index.js +82 -0
  106. package/dist/cjs/io/reader.d.ts +48 -0
  107. package/dist/cjs/io/reader.js +180 -0
  108. package/dist/cjs/io/writer.d.ts +37 -0
  109. package/dist/cjs/io/writer.js +175 -0
  110. package/dist/cjs/package.json +1 -0
  111. package/dist/cjs/project/pcode-project.d.ts +32 -0
  112. package/dist/cjs/project/pcode-project.js +210 -0
  113. package/dist/cjs/project/source-export.d.ts +12 -0
  114. package/dist/cjs/project/source-export.js +51 -0
  115. package/dist/cjs/project/source-import.d.ts +13 -0
  116. package/dist/cjs/project/source-import.js +73 -0
  117. package/dist/cjs/swf/swf.d.ts +63 -0
  118. package/dist/cjs/swf/swf.js +147 -0
  119. package/dist/cjs/swf/tag-codes.d.ts +77 -0
  120. package/dist/cjs/swf/tag-codes.js +109 -0
  121. package/dist/cjs/swf/tags.d.ts +118 -0
  122. package/dist/cjs/swf/tags.js +330 -0
  123. package/dist/cli.d.ts +2 -0
  124. package/dist/cli.js +296 -0
  125. package/dist/compiler/ast.d.ts +285 -0
  126. package/dist/compiler/ast.js +2 -0
  127. package/dist/compiler/class-compiler.d.ts +34 -0
  128. package/dist/compiler/class-compiler.js +437 -0
  129. package/dist/compiler/codegen.d.ts +144 -0
  130. package/dist/compiler/codegen.js +1585 -0
  131. package/dist/compiler/lexer.d.ts +27 -0
  132. package/dist/compiler/lexer.js +249 -0
  133. package/dist/compiler/parser.d.ts +58 -0
  134. package/dist/compiler/parser.js +984 -0
  135. package/dist/compiler/program.d.ts +29 -0
  136. package/dist/compiler/program.js +32 -0
  137. package/dist/compiler/scope.d.ts +22 -0
  138. package/dist/compiler/scope.js +238 -0
  139. package/dist/compression/lzma.d.ts +37 -0
  140. package/dist/compression/lzma.js +569 -0
  141. package/dist/compression/swf-container.d.ts +19 -0
  142. package/dist/compression/swf-container.js +87 -0
  143. package/dist/decompiler/ast.d.ts +228 -0
  144. package/dist/decompiler/ast.js +441 -0
  145. package/dist/decompiler/cfg.d.ts +40 -0
  146. package/dist/decompiler/cfg.js +231 -0
  147. package/dist/decompiler/class-printer.d.ts +9 -0
  148. package/dist/decompiler/class-printer.js +170 -0
  149. package/dist/decompiler/method.d.ts +41 -0
  150. package/dist/decompiler/method.js +427 -0
  151. package/dist/decompiler/simulate.d.ts +55 -0
  152. package/dist/decompiler/simulate.js +586 -0
  153. package/dist/decompiler/structure.d.ts +48 -0
  154. package/dist/decompiler/structure.js +760 -0
  155. package/dist/errors.d.ts +11 -0
  156. package/dist/errors.js +20 -0
  157. package/dist/index.d.ts +41 -0
  158. package/dist/index.js +49 -0
  159. package/dist/io/reader.d.ts +48 -0
  160. package/dist/io/reader.js +176 -0
  161. package/dist/io/writer.d.ts +37 -0
  162. package/dist/io/writer.js +169 -0
  163. package/dist/project/pcode-project.d.ts +32 -0
  164. package/dist/project/pcode-project.js +203 -0
  165. package/dist/project/source-export.d.ts +12 -0
  166. package/dist/project/source-export.js +47 -0
  167. package/dist/project/source-import.d.ts +13 -0
  168. package/dist/project/source-import.js +70 -0
  169. package/dist/swf/swf.d.ts +63 -0
  170. package/dist/swf/swf.js +143 -0
  171. package/dist/swf/tag-codes.d.ts +77 -0
  172. package/dist/swf/tag-codes.js +105 -0
  173. package/dist/swf/tags.d.ts +118 -0
  174. package/dist/swf/tags.js +316 -0
  175. package/package.json +71 -4
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MANIFEST_NAME = void 0;
4
+ exports.safeSegment = safeSegment;
5
+ exports.exportPcode = exportPcode;
6
+ exports.splitMethodBlocks = splitMethodBlocks;
7
+ exports.importPcode = importPcode;
8
+ /**
9
+ * Export every class of an SWF to editable `.pcode` files and import the
10
+ * edited files back. Only methods whose text actually changed are
11
+ * re-assembled, so untouched code stays byte-identical.
12
+ */
13
+ const promises_1 = require("node:fs/promises");
14
+ const node_path_1 = require("node:path");
15
+ const abc_file_js_1 = require("../abc/abc-file.js");
16
+ const interner_js_1 = require("../abc/interner.js");
17
+ const model_js_1 = require("../abc/model.js");
18
+ const assembler_js_1 = require("../abc/pcode/assembler.js");
19
+ const disassembler_js_1 = require("../abc/pcode/disassembler.js");
20
+ const format_js_1 = require("../abc/pcode/format.js");
21
+ const lexer_js_1 = require("../abc/pcode/lexer.js");
22
+ const errors_js_1 = require("../errors.js");
23
+ exports.MANIFEST_NAME = 'swf-toolkit.json';
24
+ /** Make a class / package name safe as a path segment on all platforms. */
25
+ function safeSegment(s) {
26
+ if (s === '' || s === '.' || s === '..')
27
+ return `_${s.length}_`;
28
+ let out = '';
29
+ for (const ch of s) {
30
+ const cp = ch.codePointAt(0);
31
+ if (/[\\/:*?"<>|%]/.test(ch) || cp < 0x20 || cp === 0x7f || (cp >= 0xd800 && cp <= 0xdfff))
32
+ out += `%${cp.toString(16).padStart(2, '0')}`;
33
+ else
34
+ out += ch;
35
+ }
36
+ if (/^(con|prn|aux|nul|com\d|lpt\d)$/i.test(out))
37
+ out = `%${out}`;
38
+ if (/[. ]$/.test(out))
39
+ out += '%';
40
+ return out.length > 180 ? out.slice(0, 160) + '%' + hash(s) : out;
41
+ }
42
+ function hash(s) {
43
+ let h = 2166136261;
44
+ for (let i = 0; i < s.length; i++)
45
+ h = Math.imul(h ^ s.charCodeAt(i), 16777619);
46
+ return (h >>> 0).toString(16);
47
+ }
48
+ function classFilePath(className) {
49
+ const { pkg, name } = (0, abc_file_js_1.splitQualifiedName)(className);
50
+ const parts = pkg ? pkg.split('.').map(safeSegment) : [];
51
+ return [...parts, safeSegment(name) + '.pcode'].join('/');
52
+ }
53
+ function renderFile(abc, header, owner, refs, done) {
54
+ const chunks = [header.map((h) => `; ${h}`).join('\n') + '\n'];
55
+ const methods = [];
56
+ for (const r of refs) {
57
+ if (done.has(r.methodIndex) || !abc.methods[r.methodIndex])
58
+ continue;
59
+ done.add(r.methodIndex);
60
+ methods.push(r.methodIndex);
61
+ chunks.push((0, disassembler_js_1.disassembleMethod)(abc, r.methodIndex, { title: (0, model_js_1.describeMethodRef)(abc, owner, r) }));
62
+ }
63
+ return { text: chunks.join('\n'), methods };
64
+ }
65
+ /** Writes one `.pcode` file per class (plus a manifest) into `outDir`. */
66
+ async function exportPcode(swf, outDir) {
67
+ const tags = swf.abcTags;
68
+ const manifest = { format: 'swf-toolkit-pcode', version: 1, abcs: [], files: {} };
69
+ const written = [];
70
+ let methodCount = 0;
71
+ for (let k = 0; k < tags.length; k++) {
72
+ const tag = tags[k];
73
+ const abc = tag.abc;
74
+ const base = tags.length > 1 ? `_abc${k}_${safeSegment(tag.abcName || 'unnamed')}` : '';
75
+ manifest.abcs.push({ name: tag.abcName, dir: base });
76
+ const done = new Set();
77
+ const used = new Set();
78
+ const unique = (p) => {
79
+ let candidate = p;
80
+ for (let n = 2; used.has(candidate.toLowerCase()); n++)
81
+ candidate = p.replace(/\.pcode$/, `~${n}.pcode`);
82
+ used.add(candidate.toLowerCase());
83
+ return candidate;
84
+ };
85
+ const emit = async (rel, text, entry) => {
86
+ const path = base ? `${base}/${rel}` : rel;
87
+ const abs = (0, node_path_1.join)(outDir, ...path.split('/'));
88
+ await (0, promises_1.mkdir)((0, node_path_1.dirname)(abs), { recursive: true });
89
+ await (0, promises_1.writeFile)(abs, text, 'utf8');
90
+ manifest.files[path] = entry;
91
+ written.push(abs);
92
+ methodCount += entry.methods.length;
93
+ };
94
+ const scriptDone = new Set();
95
+ for (let c = 0; c < abc.instances.length; c++) {
96
+ const name = abc.className(c);
97
+ const inst = abc.instances[c];
98
+ const header = [
99
+ `class ${name} (class index ${c})`,
100
+ `extends ${inst.superName ? (0, format_js_1.formatMultiname)(abc, inst.superName) : '-'}`,
101
+ ...(inst.interfaces.length ? [`implements ${inst.interfaces.map((i) => (0, format_js_1.formatMultiname)(abc, i)).join(', ')}`] : []),
102
+ 'Edit method blocks and run import; blocks you do not change are left untouched.',
103
+ ];
104
+ const refs = [];
105
+ const s = (0, model_js_1.scriptOfClass)(abc, c);
106
+ if (s >= 0 && !scriptDone.has(s)) {
107
+ scriptDone.add(s);
108
+ refs.push(...(0, model_js_1.scriptMethods)(abc, s, false));
109
+ }
110
+ refs.push(...(0, model_js_1.classMethods)(abc, c));
111
+ const { text, methods } = renderFile(abc, header, name, refs, done);
112
+ await emit(unique(classFilePath(name)), text, { abc: k, className: name, methods });
113
+ }
114
+ for (let s = 0; s < abc.scripts.length; s++) {
115
+ if (scriptDone.has(s))
116
+ continue;
117
+ const refs = (0, model_js_1.scriptMethods)(abc, s);
118
+ const { text, methods } = renderFile(abc, [`script ${s}`], `script${s}`, refs, done);
119
+ if (methods.length)
120
+ await emit(unique(`_scripts/script_${s}.pcode`), text, { abc: k, methods });
121
+ }
122
+ // Anything not reachable from classes or scripts.
123
+ const rest = [];
124
+ for (let m = 0; m < abc.methods.length; m++)
125
+ if (!done.has(m))
126
+ rest.push({ methodIndex: m, role: 'closure', name: abc.strings[abc.methods[m].name] ?? '', isStatic: false });
127
+ if (rest.length) {
128
+ const { text, methods } = renderFile(abc, ['methods not referenced by any class or script'], '<orphan>', rest, done);
129
+ await emit(unique('_scripts/orphans.pcode'), text, { abc: k, methods });
130
+ }
131
+ }
132
+ const manifestPath = (0, node_path_1.join)(outDir, exports.MANIFEST_NAME);
133
+ await (0, promises_1.mkdir)(outDir, { recursive: true });
134
+ await (0, promises_1.writeFile)(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
135
+ return { outDir, files: written, methodCount };
136
+ }
137
+ /** Splits a P-code document into `method ... end` blocks with their method index. */
138
+ function splitMethodBlocks(text) {
139
+ const lines = text.split('\n');
140
+ const blocks = [];
141
+ let start = -1;
142
+ let index = 'new';
143
+ for (let i = 0; i < lines.length; i++) {
144
+ const m = /^\s*method\s+(\d+|new)\b/.exec(lines[i]);
145
+ if (m && start < 0) {
146
+ start = i;
147
+ index = m[1] === 'new' ? 'new' : Number(m[1]);
148
+ }
149
+ else if (start >= 0 && /^\s*end\s*(;.*)?$/.test(lines[i])) {
150
+ // Pad with blank lines so assembler line numbers match the file.
151
+ blocks.push({ text: '\n'.repeat(start) + lines.slice(start, i + 1).join('\n'), startLine: start + 1, methodIndex: index });
152
+ start = -1;
153
+ }
154
+ }
155
+ if (start >= 0)
156
+ throw new errors_js_1.AssemblerError("Missing 'end'", lines.length, 1);
157
+ return blocks;
158
+ }
159
+ /** Token-level normalisation: ignores comments, indentation and spacing. */
160
+ function normalize(text) {
161
+ return text
162
+ .split('\n')
163
+ .map((l, i) => (0, lexer_js_1.tokenizeLine)(l, i + 1).map((t) => t.text).join(' '))
164
+ .filter((l) => l.length > 0)
165
+ .join('\n');
166
+ }
167
+ /** Applies edited `.pcode` files from `dir` (created by {@link exportPcode}) to `swf`. */
168
+ async function importPcode(swf, dir, options = {}) {
169
+ const manifest = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(dir, exports.MANIFEST_NAME), 'utf8'));
170
+ if (manifest.format !== 'swf-toolkit-pcode')
171
+ throw new Error(`${exports.MANIFEST_NAME} is not a swf-toolkit P-code manifest`);
172
+ const tags = swf.abcTags;
173
+ const interners = new Map();
174
+ const result = { changed: [], unchanged: 0 };
175
+ const only = options.files ? new Set(options.files.map((f) => f.split(node_path_1.sep).join('/'))) : undefined;
176
+ for (const [rel, entry] of Object.entries(manifest.files)) {
177
+ if (only && !only.has(rel))
178
+ continue;
179
+ const tag = tags[entry.abc];
180
+ if (!tag)
181
+ throw new Error(`${rel}: ABC tag #${entry.abc} not found in SWF`);
182
+ const abc = tag.abc;
183
+ const path = (0, node_path_1.join)(dir, ...rel.split('/'));
184
+ let text;
185
+ try {
186
+ text = await (0, promises_1.readFile)(path, 'utf8');
187
+ }
188
+ catch {
189
+ continue; // file deleted: leave methods as they are
190
+ }
191
+ for (const block of splitMethodBlocks(text)) {
192
+ if (block.methodIndex !== 'new') {
193
+ const current = abc.methods[block.methodIndex] ? (0, disassembler_js_1.disassembleMethod)(abc, block.methodIndex) : '';
194
+ if (normalize(current) === normalize(block.text)) {
195
+ result.unchanged++;
196
+ continue;
197
+ }
198
+ }
199
+ let interner = interners.get(entry.abc);
200
+ if (!interner) {
201
+ interner = new interner_js_1.Interner(abc);
202
+ interners.set(entry.abc, interner);
203
+ }
204
+ const assembled = (0, assembler_js_1.assembleMethods)(abc, block.text, { source: (0, node_path_1.relative)(process.cwd(), path), interner });
205
+ for (const a of assembled)
206
+ result.changed.push({ file: rel, methodIndex: a.methodIndex });
207
+ }
208
+ }
209
+ return result;
210
+ }
@@ -0,0 +1,12 @@
1
+ import type { Swf } from '../swf/swf.js';
2
+ /** First-line marker identifying the exact class an exported file belongs to. */
3
+ export declare const CLASS_MARKER = "// swf-toolkit:";
4
+ export interface ExportSourcesResult {
5
+ files: string[];
6
+ /** Classes whose decompilation threw (a stub with the error was written). */
7
+ failed: string[];
8
+ }
9
+ /** Writes decompiled `.as` files (one per class) into `outDir`. */
10
+ export declare function exportSources(swf: Swf, outDir: string, options?: {
11
+ classes?: string[];
12
+ }): Promise<ExportSourcesResult>;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CLASS_MARKER = void 0;
4
+ exports.exportSources = exportSources;
5
+ const promises_1 = require("node:fs/promises");
6
+ const node_path_1 = require("node:path");
7
+ const abc_file_js_1 = require("../abc/abc-file.js");
8
+ const class_printer_js_1 = require("../decompiler/class-printer.js");
9
+ const pcode_project_js_1 = require("./pcode-project.js");
10
+ /** First-line marker identifying the exact class an exported file belongs to. */
11
+ exports.CLASS_MARKER = '// swf-toolkit:';
12
+ /** Writes decompiled `.as` files (one per class) into `outDir`. */
13
+ async function exportSources(swf, outDir, options = {}) {
14
+ const files = [];
15
+ const failed = [];
16
+ const used = new Set();
17
+ const only = options.classes ? new Set(options.classes) : undefined;
18
+ const tags = swf.abcTags;
19
+ for (let k = 0; k < tags.length; k++) {
20
+ const abc = tags[k].abc;
21
+ const counts = new Map();
22
+ for (let c = 0; c < abc.instances.length; c++)
23
+ counts.set(abc.className(c), (counts.get(abc.className(c)) ?? 0) + 1);
24
+ for (let c = 0; c < abc.instances.length; c++) {
25
+ const name = abc.className(c);
26
+ if (only && !only.has(name))
27
+ continue;
28
+ const { pkg, name: short } = (0, abc_file_js_1.splitQualifiedName)(name);
29
+ let rel = [...(pkg ? pkg.split('.').map(pcode_project_js_1.safeSegment) : []), (0, pcode_project_js_1.safeSegment)(short) + '.as'].join('/');
30
+ for (let n = 2; used.has(rel.toLowerCase()); n++)
31
+ rel = rel.replace(/(~\d+)?\.as$/, `~${n}.as`);
32
+ used.add(rel.toLowerCase());
33
+ let text;
34
+ try {
35
+ text = (0, class_printer_js_1.decompileClass)(abc, c);
36
+ }
37
+ catch (e) {
38
+ failed.push(name);
39
+ text = `// Failed to decompile ${name}: ${e.message}\n`;
40
+ }
41
+ // Ambiguous names (duplicate classes, several ABC tags) get a marker so import finds the right class.
42
+ if (counts.get(name) > 1 || tags.length > 1)
43
+ text = `${exports.CLASS_MARKER} abc=${k} class=${c}\n${text}`;
44
+ const abs = (0, node_path_1.join)(outDir, ...rel.split('/'));
45
+ await (0, promises_1.mkdir)((0, node_path_1.dirname)(abs), { recursive: true });
46
+ await (0, promises_1.writeFile)(abs, text, 'utf8');
47
+ files.push(abs);
48
+ }
49
+ }
50
+ return { files, failed };
51
+ }
@@ -0,0 +1,13 @@
1
+ import { type CompiledMember } from '../compiler/class-compiler.js';
2
+ import type { Swf } from '../swf/swf.js';
3
+ export interface ImportSourcesResult {
4
+ changes: CompiledMember[];
5
+ /** Files that were parsed and matched no change. */
6
+ unchanged: number;
7
+ }
8
+ export interface ImportSourcesOptions {
9
+ /** Only import these files (absolute, or relative to `dir`). */
10
+ files?: string[];
11
+ }
12
+ /** Compiles `.as` files from `dir` (or explicit file paths) into the SWF. */
13
+ export declare function importSources(swf: Swf, dir: string, options?: ImportSourcesOptions): Promise<ImportSourcesResult>;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.importSources = importSources;
4
+ /**
5
+ * Applies edited / new `.as` files to an SWF. Classes are matched by their
6
+ * fully qualified name; within existing classes only members whose text
7
+ * differs from the decompiled original are recompiled.
8
+ */
9
+ const promises_1 = require("node:fs/promises");
10
+ const node_path_1 = require("node:path");
11
+ const interner_js_1 = require("../abc/interner.js");
12
+ const class_compiler_js_1 = require("../compiler/class-compiler.js");
13
+ const parser_js_1 = require("../compiler/parser.js");
14
+ const source_export_js_1 = require("./source-export.js");
15
+ async function listAsFiles(dir) {
16
+ const out = [];
17
+ const walk = async (d) => {
18
+ for (const e of await (0, promises_1.readdir)(d, { withFileTypes: true })) {
19
+ const p = (0, node_path_1.join)(d, e.name);
20
+ if (e.isDirectory())
21
+ await walk(p);
22
+ else if (e.name.endsWith('.as'))
23
+ out.push(p);
24
+ }
25
+ };
26
+ await walk(dir);
27
+ return out.sort();
28
+ }
29
+ /** Compiles `.as` files from `dir` (or explicit file paths) into the SWF. */
30
+ async function importSources(swf, dir, options = {}) {
31
+ const files = options.files ? options.files.map((f) => (f.startsWith('/') ? f : (0, node_path_1.join)(dir, f))) : await listAsFiles(dir);
32
+ const tags = swf.abcTags;
33
+ if (!tags.length)
34
+ throw new Error('SWF has no ActionScript 3 code (no DoABC tag)');
35
+ const interners = new Map();
36
+ const result = { changes: [], unchanged: 0 };
37
+ const existing = [];
38
+ const added = [];
39
+ for (const file of files) {
40
+ const text = await (0, promises_1.readFile)(file, 'utf8');
41
+ const marker = new RegExp(`^${source_export_js_1.CLASS_MARKER.replace(/[/]/g, '\\/')} abc=(\\d+) class=(\\d+)`).exec(text);
42
+ if (marker) {
43
+ const tag = tags[Number(marker[1])];
44
+ if (!tag)
45
+ throw new Error(`${file}: ABC tag #${marker[1]} does not exist`);
46
+ existing.push({ file, text, tag, classIndex: Number(marker[2]) });
47
+ continue;
48
+ }
49
+ const unit = (0, parser_js_1.parseAs3)(text, (0, node_path_1.relative)(process.cwd(), file));
50
+ let target;
51
+ for (const cls of unit.classes) {
52
+ const q = unit.package ? `${unit.package}.${cls.name}` : cls.name;
53
+ target ??= tags.find((t) => t.abc.findClass(q) >= 0);
54
+ }
55
+ if (target)
56
+ existing.push({ file, text, tag: target });
57
+ else
58
+ added.push({ file, text, tag: tags[tags.length - 1] });
59
+ }
60
+ // Interfaces and base classes first among new files.
61
+ added.sort((a, b) => Number(/\binterface\b/.test(b.text)) - Number(/\binterface\b/.test(a.text)));
62
+ for (const p of [...existing, ...added]) {
63
+ let pool = interners.get(p.tag);
64
+ if (!pool)
65
+ interners.set(p.tag, (pool = new interner_js_1.Interner(p.tag.abc)));
66
+ const r = (0, class_compiler_js_1.compileClassSource)(p.tag.abc, p.text, { source: (0, node_path_1.relative)(process.cwd(), p.file), interner: pool, classIndex: p.classIndex });
67
+ if (r.changes.length)
68
+ result.changes.push(...r.changes);
69
+ else
70
+ result.unchanged++;
71
+ }
72
+ return result;
73
+ }
@@ -0,0 +1,63 @@
1
+ import { type PackOptions, type SwfCompression } from '../compression/swf-container.js';
2
+ import { DefineSpriteTag, DoABCTag, Tag } from './tags.js';
3
+ /** Rectangle in twips (1/20 pixel). */
4
+ export interface Rect {
5
+ xMin: number;
6
+ xMax: number;
7
+ yMin: number;
8
+ yMax: number;
9
+ }
10
+ export interface SwfSaveOptions extends PackOptions {
11
+ /** Override the compression used when saving. Defaults to the original compression. */
12
+ compression?: SwfCompression;
13
+ }
14
+ /** An SWF file: header plus a list of tags. */
15
+ export declare class Swf {
16
+ version: number;
17
+ compression: SwfCompression;
18
+ /** Stage bounds in twips. */
19
+ frameSize: Rect;
20
+ /** Frames per second (8.8 fixed point in the file). */
21
+ frameRate: number;
22
+ frameCount: number;
23
+ tags: Tag[];
24
+ /** Bytes after the End tag (normally empty). Preserved on save. */
25
+ trailing: Uint8Array;
26
+ private rectBits;
27
+ constructor(init?: {
28
+ version?: number;
29
+ compression?: SwfCompression;
30
+ frameSize?: Rect;
31
+ frameRate?: number;
32
+ frameCount?: number;
33
+ tags?: Tag[];
34
+ });
35
+ /** Parse an SWF file (FWS, CWS or ZWS). */
36
+ static parse(bytes: Uint8Array): Swf;
37
+ static load(path: string): Promise<Swf>;
38
+ /** Uncompressed SWF body (everything after the 8-byte file header). */
39
+ encodeBody(): Uint8Array;
40
+ /** Serialise to a complete SWF file. */
41
+ toBytes(options?: SwfSaveOptions): Uint8Array;
42
+ /**
43
+ * Write to disk. Uses a temporary file + rename, so saving over the source
44
+ * file is safe.
45
+ */
46
+ save(path: string, options?: SwfSaveOptions): Promise<void>;
47
+ get width(): number;
48
+ get height(): number;
49
+ /** Depth-first iteration over all tags, including tags nested in DefineSprite. */
50
+ walkTags(): Generator<{
51
+ tag: Tag;
52
+ parent: DefineSpriteTag | null;
53
+ index: number;
54
+ }>;
55
+ /** All DoABC / DoABC2 tags in file order. */
56
+ get abcTags(): DoABCTag[];
57
+ /** Character-defining tag with the given id (searches nested sprites too). */
58
+ findCharacter(id: number): Tag | undefined;
59
+ /** Merged SymbolClass mapping: class name → character id. */
60
+ symbolClasses(): Map<string, number>;
61
+ /** Main document class (SymbolClass entry with id 0), if any. */
62
+ get documentClass(): string | undefined;
63
+ }
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Swf = void 0;
4
+ const promises_1 = require("node:fs/promises");
5
+ const node_path_1 = require("node:path");
6
+ const reader_js_1 = require("../io/reader.js");
7
+ const writer_js_1 = require("../io/writer.js");
8
+ const swf_container_js_1 = require("../compression/swf-container.js");
9
+ const tags_js_1 = require("./tags.js");
10
+ const tag_codes_js_1 = require("./tag-codes.js");
11
+ function readRect(r) {
12
+ const bits = r.ub(5);
13
+ const rect = { xMin: r.sb(bits), xMax: r.sb(bits), yMin: r.sb(bits), yMax: r.sb(bits) };
14
+ r.align();
15
+ return { rect, bits };
16
+ }
17
+ function writeRect(w, rect, minBits = 0) {
18
+ const bits = Math.max(minBits, (0, writer_js_1.signedBits)(rect.xMin), (0, writer_js_1.signedBits)(rect.xMax), (0, writer_js_1.signedBits)(rect.yMin), (0, writer_js_1.signedBits)(rect.yMax));
19
+ w.ub(5, bits);
20
+ w.sb(bits, rect.xMin);
21
+ w.sb(bits, rect.xMax);
22
+ w.sb(bits, rect.yMin);
23
+ w.sb(bits, rect.yMax);
24
+ w.align();
25
+ }
26
+ /** An SWF file: header plus a list of tags. */
27
+ class Swf {
28
+ version;
29
+ compression;
30
+ /** Stage bounds in twips. */
31
+ frameSize;
32
+ /** Frames per second (8.8 fixed point in the file). */
33
+ frameRate;
34
+ frameCount;
35
+ tags;
36
+ /** Bytes after the End tag (normally empty). Preserved on save. */
37
+ trailing;
38
+ rectBits;
39
+ constructor(init = {}) {
40
+ this.version = init.version ?? 10;
41
+ this.compression = init.compression ?? 'zlib';
42
+ this.frameSize = init.frameSize ?? { xMin: 0, xMax: 550 * 20, yMin: 0, yMax: 400 * 20 };
43
+ this.frameRate = init.frameRate ?? 24;
44
+ this.frameCount = init.frameCount ?? 1;
45
+ this.tags = init.tags ?? [];
46
+ this.trailing = new Uint8Array(0);
47
+ this.rectBits = 0;
48
+ }
49
+ // -------------------------------------------------------------------------
50
+ // Loading / saving
51
+ // -------------------------------------------------------------------------
52
+ /** Parse an SWF file (FWS, CWS or ZWS). */
53
+ static parse(bytes) {
54
+ const { compression, version, body } = (0, swf_container_js_1.unpackSwf)(bytes);
55
+ const r = new reader_js_1.ByteReader(body);
56
+ const { rect, bits } = readRect(r);
57
+ const frameRate = r.u16() / 256;
58
+ const frameCount = r.u16();
59
+ const { tags, end } = (0, tags_js_1.readTags)(body, r.pos, body.length);
60
+ const swf = new Swf({ version, compression, frameSize: rect, frameRate, frameCount, tags });
61
+ swf.rectBits = bits;
62
+ swf.trailing = body.slice(end);
63
+ return swf;
64
+ }
65
+ static async load(path) {
66
+ return Swf.parse(new Uint8Array(await (0, promises_1.readFile)(path)));
67
+ }
68
+ /** Uncompressed SWF body (everything after the 8-byte file header). */
69
+ encodeBody() {
70
+ const w = new writer_js_1.ByteWriter(1 << 16);
71
+ writeRect(w, this.frameSize, this.rectBits);
72
+ w.u16(Math.round(this.frameRate * 256));
73
+ w.u16(this.frameCount);
74
+ const tags = this.tags;
75
+ (0, tags_js_1.writeTags)(w, tags);
76
+ if (tags.length === 0 || tags[tags.length - 1].code !== tag_codes_js_1.TagCode.End) {
77
+ w.u16(0); // End tag
78
+ }
79
+ w.bytes(this.trailing);
80
+ return w.toBytes();
81
+ }
82
+ /** Serialise to a complete SWF file. */
83
+ toBytes(options = {}) {
84
+ return (0, swf_container_js_1.packSwf)({ compression: options.compression ?? this.compression, version: this.version, body: this.encodeBody() }, options);
85
+ }
86
+ /**
87
+ * Write to disk. Uses a temporary file + rename, so saving over the source
88
+ * file is safe.
89
+ */
90
+ async save(path, options = {}) {
91
+ const bytes = this.toBytes(options);
92
+ const tmp = (0, node_path_1.join)((0, node_path_1.dirname)(path), `.${(0, node_path_1.basename)(path)}.${process.pid}.${Date.now()}.tmp`);
93
+ await (0, promises_1.writeFile)(tmp, bytes);
94
+ await (0, promises_1.rename)(tmp, path);
95
+ }
96
+ // -------------------------------------------------------------------------
97
+ // Convenience accessors
98
+ // -------------------------------------------------------------------------
99
+ get width() {
100
+ return (this.frameSize.xMax - this.frameSize.xMin) / 20;
101
+ }
102
+ get height() {
103
+ return (this.frameSize.yMax - this.frameSize.yMin) / 20;
104
+ }
105
+ /** Depth-first iteration over all tags, including tags nested in DefineSprite. */
106
+ *walkTags() {
107
+ function* walk(list, parent) {
108
+ for (let i = 0; i < list.length; i++) {
109
+ const tag = list[i];
110
+ yield { tag, parent, index: i };
111
+ if (tag instanceof tags_js_1.DefineSpriteTag)
112
+ yield* walk(tag.tags, tag);
113
+ }
114
+ }
115
+ yield* walk(this.tags, null);
116
+ }
117
+ /** All DoABC / DoABC2 tags in file order. */
118
+ get abcTags() {
119
+ return this.tags.filter((t) => t instanceof tags_js_1.DoABCTag);
120
+ }
121
+ /** Character-defining tag with the given id (searches nested sprites too). */
122
+ findCharacter(id) {
123
+ for (const { tag } of this.walkTags())
124
+ if (tag.characterId === id)
125
+ return tag;
126
+ return undefined;
127
+ }
128
+ /** Merged SymbolClass mapping: class name → character id. */
129
+ symbolClasses() {
130
+ const map = new Map();
131
+ for (const t of this.tags) {
132
+ if (t instanceof tags_js_1.SymbolTableTag && t.code === tag_codes_js_1.TagCode.SymbolClass) {
133
+ for (const s of t.symbols)
134
+ map.set(s.name, s.id);
135
+ }
136
+ }
137
+ return map;
138
+ }
139
+ /** Main document class (SymbolClass entry with id 0), if any. */
140
+ get documentClass() {
141
+ for (const [name, id] of this.symbolClasses())
142
+ if (id === 0)
143
+ return name;
144
+ return undefined;
145
+ }
146
+ }
147
+ exports.Swf = Swf;
@@ -0,0 +1,77 @@
1
+ /** SWF tag codes (SWF File Format Specification v19). */
2
+ export declare const TagCode: {
3
+ readonly End: 0;
4
+ readonly ShowFrame: 1;
5
+ readonly DefineShape: 2;
6
+ readonly FreeCharacter: 3;
7
+ readonly PlaceObject: 4;
8
+ readonly RemoveObject: 5;
9
+ readonly DefineBits: 6;
10
+ readonly DefineButton: 7;
11
+ readonly JPEGTables: 8;
12
+ readonly SetBackgroundColor: 9;
13
+ readonly DefineFont: 10;
14
+ readonly DefineText: 11;
15
+ readonly DoAction: 12;
16
+ readonly DefineFontInfo: 13;
17
+ readonly DefineSound: 14;
18
+ readonly StartSound: 15;
19
+ readonly DefineButtonSound: 17;
20
+ readonly SoundStreamHead: 18;
21
+ readonly SoundStreamBlock: 19;
22
+ readonly DefineBitsLossless: 20;
23
+ readonly DefineBitsJPEG2: 21;
24
+ readonly DefineShape2: 22;
25
+ readonly DefineButtonCxform: 23;
26
+ readonly Protect: 24;
27
+ readonly PlaceObject2: 26;
28
+ readonly RemoveObject2: 28;
29
+ readonly DefineShape3: 32;
30
+ readonly DefineText2: 33;
31
+ readonly DefineButton2: 34;
32
+ readonly DefineBitsJPEG3: 35;
33
+ readonly DefineBitsLossless2: 36;
34
+ readonly DefineEditText: 37;
35
+ readonly DefineSprite: 39;
36
+ readonly ProductInfo: 41;
37
+ readonly FrameLabel: 43;
38
+ readonly SoundStreamHead2: 45;
39
+ readonly DefineMorphShape: 46;
40
+ readonly DefineFont2: 48;
41
+ readonly ExportAssets: 56;
42
+ readonly ImportAssets: 57;
43
+ readonly EnableDebugger: 58;
44
+ readonly DoInitAction: 59;
45
+ readonly DefineVideoStream: 60;
46
+ readonly VideoFrame: 61;
47
+ readonly DefineFontInfo2: 62;
48
+ readonly DebugID: 63;
49
+ readonly EnableDebugger2: 64;
50
+ readonly ScriptLimits: 65;
51
+ readonly SetTabIndex: 66;
52
+ readonly FileAttributes: 69;
53
+ readonly PlaceObject3: 70;
54
+ readonly ImportAssets2: 71;
55
+ readonly DoABC: 72;
56
+ readonly DefineFontAlignZones: 73;
57
+ readonly CSMTextSettings: 74;
58
+ readonly DefineFont3: 75;
59
+ readonly SymbolClass: 76;
60
+ readonly Metadata: 77;
61
+ readonly DefineScalingGrid: 78;
62
+ readonly DoABC2: 82;
63
+ readonly DefineShape4: 83;
64
+ readonly DefineMorphShape2: 84;
65
+ readonly DefineSceneAndFrameLabelData: 86;
66
+ readonly DefineBinaryData: 87;
67
+ readonly DefineFontName: 88;
68
+ readonly StartSound2: 89;
69
+ readonly DefineBitsJPEG4: 90;
70
+ readonly DefineFont4: 91;
71
+ readonly EnableTelemetry: 93;
72
+ readonly PlaceObject4: 94;
73
+ };
74
+ export type TagCodeName = keyof typeof TagCode;
75
+ export declare function tagName(code: number): string;
76
+ /** Tags whose body starts with the u16 id of the character they define. */
77
+ export declare const CHARACTER_DEFINING_TAGS: ReadonlySet<number>;