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,147 @@
1
+ import { decodeCode } from '../code.js';
2
+ import { METHOD_FLAG_NAMES, MethodFlags } from '../constants.js';
3
+ import { opInfo } from '../opcodes.js';
4
+ import { formatDouble, formatMultiname, formatNamespace, formatString, formatTrait, formatValue } from './format.js';
5
+ const labelName = (id) => `L${id}`;
6
+ function hex(data) {
7
+ return Array.from(data, (b) => b.toString(16).padStart(2, '0')).join(' ');
8
+ }
9
+ /** Formats a single instruction (without indentation). */
10
+ export function formatInstruction(abc, ins) {
11
+ const info = opInfo(ins.op);
12
+ if (!info)
13
+ return `.bytes ${ins.op.toString(16).padStart(2, '0')}`;
14
+ const parts = [];
15
+ let a = 0;
16
+ let comment = '';
17
+ for (const t of info.operands) {
18
+ switch (t) {
19
+ case 'branch':
20
+ parts.push(labelName(ins.targets[0]));
21
+ break;
22
+ case 'switch':
23
+ parts.push(labelName(ins.targets[0]), `[${ins.targets.slice(1).map(labelName).join(', ')}]`);
24
+ break;
25
+ case 'int':
26
+ parts.push(String(abc.ints[ins.args[a++]] ?? 0));
27
+ break;
28
+ case 'uint':
29
+ parts.push(String(abc.uints[ins.args[a++]] ?? 0));
30
+ break;
31
+ case 'double':
32
+ parts.push(formatDouble(abc.doubles[ins.args[a++]] ?? NaN));
33
+ break;
34
+ case 'string':
35
+ parts.push(formatString(abc, ins.args[a++]));
36
+ break;
37
+ case 'namespace':
38
+ parts.push(formatNamespace(abc, ins.args[a++]));
39
+ break;
40
+ case 'multiname':
41
+ parts.push(formatMultiname(abc, ins.args[a++]));
42
+ break;
43
+ case 'class': {
44
+ const c = ins.args[a++];
45
+ parts.push(String(c));
46
+ comment = ` ; ${abc.className(c)}`;
47
+ break;
48
+ }
49
+ default:
50
+ parts.push(String(ins.args[a++]));
51
+ }
52
+ }
53
+ return parts.length ? `${info.name} ${parts.join(', ')}${comment}` : info.name + comment;
54
+ }
55
+ /** Formats decoded code items, one per line. */
56
+ export function formatCode(abc, code, indent = ' ') {
57
+ const items = Array.isArray(code) ? code : code.items;
58
+ const lines = [];
59
+ for (const it of items) {
60
+ if (it.type === 'label')
61
+ lines.push(`${labelName(it.id)}:`);
62
+ else if (it.type === 'bytes') {
63
+ for (let i = 0; i < it.data.length; i += 16)
64
+ lines.push(`${indent}.bytes ${hex(it.data.subarray(i, i + 16))}`);
65
+ }
66
+ else
67
+ lines.push(indent + formatInstruction(abc, it));
68
+ }
69
+ return lines;
70
+ }
71
+ function formatFlags(flags) {
72
+ const names = [];
73
+ for (const [bit, name] of METHOD_FLAG_NAMES) {
74
+ if (flags & bit)
75
+ names.push(name);
76
+ }
77
+ const known = [...METHOD_FLAG_NAMES.keys()].reduce((x, y) => x | y, 0);
78
+ if (flags & ~known & 0xff)
79
+ names.push(`0x${(flags & ~known & 0xff).toString(16)}`);
80
+ return names.length ? names.join(' | ') : undefined;
81
+ }
82
+ function bodyLines(abc, body, ind) {
83
+ const lines = ['body'];
84
+ let decoded;
85
+ let error;
86
+ try {
87
+ decoded = decodeCode(body.code, body.exceptions);
88
+ }
89
+ catch (e) {
90
+ error = e.message;
91
+ }
92
+ lines.push(`${ind}maxstack ${body.maxStack}`);
93
+ lines.push(`${ind}localcount ${body.localCount}`);
94
+ lines.push(`${ind}initscopedepth ${body.initScopeDepth}`);
95
+ lines.push(`${ind}maxscopedepth ${body.maxScopeDepth}`);
96
+ for (const t of body.traits)
97
+ lines.push(ind + formatTrait(abc, t));
98
+ if (!decoded) {
99
+ // Not representable with labels: emit raw code and numeric exception offsets.
100
+ lines.push(`${ind}; WARNING: code could not be decoded (${error}); kept as raw bytes`);
101
+ for (const e of body.exceptions) {
102
+ lines.push(`${ind}try from @${e.from} to @${e.to} target @${e.target} type ${formatMultiname(abc, e.excType)} name ${formatMultiname(abc, e.varName)}`);
103
+ }
104
+ lines.push('code');
105
+ for (let i = 0; i < body.code.length; i += 16)
106
+ lines.push(`${ind}.bytes ${hex(body.code.subarray(i, i + 16))}`);
107
+ return lines;
108
+ }
109
+ for (const e of decoded.exceptions) {
110
+ lines.push(`${ind}try from ${labelName(e.from)} to ${labelName(e.to)} target ${labelName(e.target)} type ${formatMultiname(abc, e.excType)} name ${formatMultiname(abc, e.varName)}`);
111
+ }
112
+ lines.push('code');
113
+ lines.push(...formatCode(abc, decoded, ind));
114
+ return lines;
115
+ }
116
+ /**
117
+ * Disassembles a method (signature + body) to P-code text. The output can be
118
+ * edited and fed back to {@link assembleMethods}.
119
+ */
120
+ export function disassembleMethod(abc, methodIndex, options = {}) {
121
+ const ind = options.indent ?? ' ';
122
+ const m = abc.methods[methodIndex];
123
+ if (!m)
124
+ throw new Error(`Method index ${methodIndex} out of range`);
125
+ const lines = [`method ${methodIndex}${options.title ? ` ; ${options.title}` : ''}`];
126
+ if (m.name !== 0)
127
+ lines.push(`${ind}name ${formatString(abc, m.name)}`);
128
+ lines.push(`${ind}returns ${formatMultiname(abc, m.returnType)}`);
129
+ for (const p of m.paramTypes)
130
+ lines.push(`${ind}param ${formatMultiname(abc, p)}`);
131
+ const flags = formatFlags(m.flags & ~(MethodFlags.HAS_OPTIONAL | MethodFlags.HAS_PARAM_NAMES));
132
+ if (flags)
133
+ lines.push(`${ind}flags ${flags}`);
134
+ if (m.flags & MethodFlags.HAS_OPTIONAL && m.optional.length === 0)
135
+ lines.push(`${ind}flags HAS_OPTIONAL`);
136
+ for (const o of m.optional)
137
+ lines.push(`${ind}optional ${formatValue(abc, o.kind, o.value)}`);
138
+ for (const p of m.paramNames)
139
+ lines.push(`${ind}paramname ${formatString(abc, p)}`);
140
+ if (m.flags & MethodFlags.HAS_PARAM_NAMES && m.paramNames.length === 0)
141
+ lines.push(`${ind}flags HAS_PARAM_NAMES`);
142
+ const body = abc.methodBody(methodIndex);
143
+ if (body)
144
+ lines.push(...bodyLines(abc, body, ind));
145
+ lines.push('end');
146
+ return lines.join('\n') + '\n';
147
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Text formatting of constant pool entries for P-code.
3
+ *
4
+ * Syntax (all of it is accepted back by the assembler):
5
+ * strings "text" (JSON escapes), null = string index 0
6
+ * namespaces PackageNamespace("flash.display"), PrivateNamespace("Foo", 12), * = index 0
7
+ * multinames QName(ns, "name"), QNameA(...), RTQName("n"), RTQNameA("n"), RTQNameL(), RTQNameLA(),
8
+ * Multiname("n", [ns, ...]), MultinameA(...), MultinameL([ns, ...]), MultinameLA(...),
9
+ * TypeName(QName(...), [QName(...), ...]), * = index 0
10
+ * values Integer(1), UInteger(1), Double(1.5), Utf8("s"), True, False, Null, Undefined,
11
+ * any namespace syntax
12
+ */
13
+ import type { AbcFile, Trait } from '../abc-file.js';
14
+ export declare function formatString(abc: AbcFile, index: number): string;
15
+ export declare function formatDouble(v: number): string;
16
+ export declare function formatNamespace(abc: AbcFile, index: number): string;
17
+ export declare function formatNsSet(abc: AbcFile, index: number): string;
18
+ export declare function formatMultiname(abc: AbcFile, index: number): string;
19
+ /** Constant value (optional parameter default / slot initial value). */
20
+ export declare function formatValue(abc: AbcFile, kind: number, index: number): string;
21
+ export declare const TRAIT_KIND_BY_TEXT: Map<string, number>;
22
+ /** One-line trait representation: `trait slot QName(...) slotid 1 type ... value ...`. */
23
+ export declare function formatTrait(abc: AbcFile, t: Trait): string;
@@ -0,0 +1,131 @@
1
+ import { ConstantKind, MultinameKind, NAMESPACE_KIND_NAMES, NamespaceKind, TraitAttr, TraitKind } from '../constants.js';
2
+ export function formatString(abc, index) {
3
+ if (index === 0)
4
+ return 'null';
5
+ const s = abc.strings[index];
6
+ return s === undefined ? `null ; invalid string index ${index}` : JSON.stringify(s);
7
+ }
8
+ export function formatDouble(v) {
9
+ if (Number.isNaN(v))
10
+ return 'NaN';
11
+ if (v === Infinity)
12
+ return 'Infinity';
13
+ if (v === -Infinity)
14
+ return '-Infinity';
15
+ if (Object.is(v, -0))
16
+ return '-0';
17
+ return String(v);
18
+ }
19
+ export function formatNamespace(abc, index) {
20
+ if (index === 0)
21
+ return '*';
22
+ const ns = abc.namespaces[index];
23
+ if (!ns)
24
+ return `* ; invalid namespace index ${index}`;
25
+ const kind = NAMESPACE_KIND_NAMES.get(ns.kind) ?? `Namespace_0x${ns.kind.toString(16)}`;
26
+ const name = formatString(abc, ns.name);
27
+ if (ns.kind === NamespaceKind.PrivateNs)
28
+ return `${kind}(${name}, ${index})`;
29
+ return `${kind}(${name})`;
30
+ }
31
+ export function formatNsSet(abc, index) {
32
+ const set = abc.nsSets[index] ?? [];
33
+ return `[${set.map((n) => formatNamespace(abc, n)).join(', ')}]`;
34
+ }
35
+ export function formatMultiname(abc, index) {
36
+ if (index === 0)
37
+ return '*';
38
+ const m = abc.multinames[index];
39
+ if (!m)
40
+ return `* ; invalid multiname index ${index}`;
41
+ const K = MultinameKind;
42
+ switch (m.kind) {
43
+ case K.QName:
44
+ return `QName(${formatNamespace(abc, m.ns)}, ${formatString(abc, m.name)})`;
45
+ case K.QNameA:
46
+ return `QNameA(${formatNamespace(abc, m.ns)}, ${formatString(abc, m.name)})`;
47
+ case K.RTQName:
48
+ return `RTQName(${formatString(abc, m.name)})`;
49
+ case K.RTQNameA:
50
+ return `RTQNameA(${formatString(abc, m.name)})`;
51
+ case K.RTQNameL:
52
+ return 'RTQNameL()';
53
+ case K.RTQNameLA:
54
+ return 'RTQNameLA()';
55
+ case K.Multiname:
56
+ return `Multiname(${formatString(abc, m.name)}, ${formatNsSet(abc, m.nsSet)})`;
57
+ case K.MultinameA:
58
+ return `MultinameA(${formatString(abc, m.name)}, ${formatNsSet(abc, m.nsSet)})`;
59
+ case K.MultinameL:
60
+ return `MultinameL(${formatNsSet(abc, m.nsSet)})`;
61
+ case K.MultinameLA:
62
+ return `MultinameLA(${formatNsSet(abc, m.nsSet)})`;
63
+ case K.TypeName:
64
+ return `TypeName(${formatMultiname(abc, m.base)}, [${m.params.map((p) => formatMultiname(abc, p)).join(', ')}])`;
65
+ default:
66
+ return `* ; unknown multiname kind 0x${m.kind.toString(16)}`;
67
+ }
68
+ }
69
+ /** Constant value (optional parameter default / slot initial value). */
70
+ export function formatValue(abc, kind, index) {
71
+ const C = ConstantKind;
72
+ switch (kind) {
73
+ case C.Int:
74
+ return `Integer(${abc.ints[index] ?? 0})`;
75
+ case C.UInt:
76
+ return `UInteger(${abc.uints[index] ?? 0})`;
77
+ case C.Double:
78
+ return `Double(${formatDouble(abc.doubles[index] ?? NaN)})`;
79
+ case C.Utf8:
80
+ return `Utf8(${formatString(abc, index)})`;
81
+ case C.True:
82
+ case C.False:
83
+ case C.Null:
84
+ case C.Undefined: {
85
+ // Compilers store the kind itself as the (unused) index; keep any other value explicit.
86
+ const name = kind === C.True ? 'True' : kind === C.False ? 'False' : kind === C.Null ? 'Null' : 'Undefined';
87
+ return index === kind ? name : `${name}(${index})`;
88
+ }
89
+ default:
90
+ if (NAMESPACE_KIND_NAMES.has(kind))
91
+ return formatNamespace(abc, index);
92
+ return `RawValue(${kind}, ${index})`;
93
+ }
94
+ }
95
+ const TRAIT_KIND_TEXT = {
96
+ [TraitKind.Slot]: 'slot',
97
+ [TraitKind.Const]: 'const',
98
+ [TraitKind.Method]: 'method',
99
+ [TraitKind.Getter]: 'getter',
100
+ [TraitKind.Setter]: 'setter',
101
+ [TraitKind.Class]: 'class',
102
+ [TraitKind.Function]: 'function',
103
+ };
104
+ export const TRAIT_KIND_BY_TEXT = new Map(Object.entries(TRAIT_KIND_TEXT).map(([k, v]) => [v, Number(k)]));
105
+ /** One-line trait representation: `trait slot QName(...) slotid 1 type ... value ...`. */
106
+ export function formatTrait(abc, t) {
107
+ const parts = [`trait ${TRAIT_KIND_TEXT[t.kind] ?? `kind${t.kind}`} ${formatMultiname(abc, t.name)}`];
108
+ switch (t.kind) {
109
+ case TraitKind.Slot:
110
+ case TraitKind.Const:
111
+ parts.push(`slotid ${t.id}`, `type ${formatMultiname(abc, t.typeName)}`);
112
+ if (t.valueIndex !== 0 || t.valueKind !== 0)
113
+ parts.push(`value ${formatValue(abc, t.valueKind, t.valueIndex)}`);
114
+ break;
115
+ case TraitKind.Class:
116
+ parts.push(`slotid ${t.id}`, `class ${t.classIndex}`);
117
+ break;
118
+ case TraitKind.Function:
119
+ parts.push(`slotid ${t.id}`, `method ${t.method}`);
120
+ break;
121
+ default:
122
+ parts.push(`dispid ${t.id}`, `method ${t.method}`);
123
+ }
124
+ if (t.attrs & TraitAttr.Final)
125
+ parts.push('final');
126
+ if (t.attrs & TraitAttr.Override)
127
+ parts.push('override');
128
+ if (t.attrs & TraitAttr.Metadata)
129
+ parts.push(`metadata [${t.metadata.join(', ')}]`);
130
+ return parts.join(' ');
131
+ }
@@ -0,0 +1,34 @@
1
+ import { AssemblerError } from '../../errors.js';
2
+ export type TokenType = 'id' | 'num' | 'str' | 'punct' | 'offset';
3
+ export interface Token {
4
+ type: TokenType;
5
+ /** Identifier text, punctuation char, or original number text. */
6
+ text: string;
7
+ /** Parsed value for numbers / strings / offsets. */
8
+ value?: number | string;
9
+ col: number;
10
+ }
11
+ /** Tokenises one line of P-code. `;` starts a comment outside of strings. */
12
+ export declare function tokenizeLine(line: string, lineNo: number, source?: string): Token[];
13
+ /** Cursor over the tokens of one line. */
14
+ export declare class LineCursor {
15
+ readonly tokens: Token[];
16
+ readonly line: number;
17
+ readonly source?: string | undefined;
18
+ pos: number;
19
+ constructor(tokens: Token[], line: number, source?: string | undefined);
20
+ get done(): boolean;
21
+ peek(offset?: number): Token | undefined;
22
+ error(message: string, tok?: Token | undefined): AssemblerError;
23
+ next(): Token;
24
+ isPunct(p: string): boolean;
25
+ isId(text?: string): boolean;
26
+ punct(p: string): void;
27
+ tryPunct(p: string): boolean;
28
+ id(expected?: string): string;
29
+ number(): number;
30
+ integer(min?: number, max?: number): number;
31
+ /** String literal or `null`. */
32
+ stringOrNull(): string | null;
33
+ end(): void;
34
+ }
@@ -0,0 +1,183 @@
1
+ import { AssemblerError } from '../../errors.js';
2
+ const PUNCT = new Set(['(', ')', '[', ']', ',', ':', '|', '*']);
3
+ const ID_START = /[A-Za-z_$]/;
4
+ const ID_PART = /[A-Za-z0-9_$.]/;
5
+ const NUM_RE = /^-?(?:0x[0-9a-fA-F]+|(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)/;
6
+ /** Tokenises one line of P-code. `;` starts a comment outside of strings. */
7
+ export function tokenizeLine(line, lineNo, source) {
8
+ const tokens = [];
9
+ const trimmed = line.trimStart();
10
+ if (trimmed.startsWith('.bytes')) {
11
+ // Raw byte directive: `.bytes 0a ff 3c ; comment`
12
+ const indent = line.length - trimmed.length;
13
+ tokens.push({ type: 'id', text: '.bytes', col: indent + 1 });
14
+ const re = /[^\s;]+|;/g;
15
+ let m;
16
+ re.lastIndex = indent + 6;
17
+ while ((m = re.exec(line))) {
18
+ if (m[0] === ';')
19
+ break;
20
+ tokens.push({ type: 'id', text: m[0], col: m.index + 1 });
21
+ }
22
+ return tokens;
23
+ }
24
+ let i = 0;
25
+ const n = line.length;
26
+ while (i < n) {
27
+ const c = line[i];
28
+ if (c === ' ' || c === '\t' || c === '\r') {
29
+ i++;
30
+ continue;
31
+ }
32
+ if (c === ';')
33
+ break;
34
+ const col = i + 1;
35
+ if (c === '"') {
36
+ let j = i + 1;
37
+ while (j < n && line[j] !== '"')
38
+ j += line[j] === '\\' ? 2 : 1;
39
+ if (j >= n)
40
+ throw new AssemblerError('Unterminated string', lineNo, col, source);
41
+ const text = line.slice(i, j + 1);
42
+ let value;
43
+ try {
44
+ value = JSON.parse(text);
45
+ }
46
+ catch {
47
+ throw new AssemblerError(`Invalid string literal ${text}`, lineNo, col, source);
48
+ }
49
+ tokens.push({ type: 'str', text, value, col });
50
+ i = j + 1;
51
+ continue;
52
+ }
53
+ if (c === '@') {
54
+ const m = /^@(\d+)/.exec(line.slice(i));
55
+ if (!m)
56
+ throw new AssemblerError('Expected byte offset after @', lineNo, col, source);
57
+ tokens.push({ type: 'offset', text: m[0], value: Number(m[1]), col });
58
+ i += m[0].length;
59
+ continue;
60
+ }
61
+ const rest = line.slice(i);
62
+ if (/[-0-9.]/.test(c)) {
63
+ if (rest.startsWith('-Infinity')) {
64
+ tokens.push({ type: 'num', text: '-Infinity', value: -Infinity, col });
65
+ i += 9;
66
+ continue;
67
+ }
68
+ const m = NUM_RE.exec(rest);
69
+ if (m) {
70
+ const text = m[0];
71
+ const neg = text.startsWith('-');
72
+ const body = neg ? text.slice(1) : text;
73
+ let value = body.startsWith('0x') ? parseInt(body.slice(2), 16) : Number(body);
74
+ if (neg)
75
+ value = -value;
76
+ tokens.push({ type: 'num', text, value, col });
77
+ i += text.length;
78
+ continue;
79
+ }
80
+ }
81
+ if (ID_START.test(c)) {
82
+ let j = i + 1;
83
+ while (j < n && ID_PART.test(line[j]))
84
+ j++;
85
+ const text = line.slice(i, j);
86
+ if (text === 'NaN' || text === 'Infinity')
87
+ tokens.push({ type: 'num', text, value: text === 'NaN' ? NaN : Infinity, col });
88
+ else
89
+ tokens.push({ type: 'id', text, col });
90
+ i = j;
91
+ continue;
92
+ }
93
+ if (PUNCT.has(c)) {
94
+ tokens.push({ type: 'punct', text: c, col });
95
+ i++;
96
+ continue;
97
+ }
98
+ throw new AssemblerError(`Unexpected character '${c}'`, lineNo, col, source);
99
+ }
100
+ return tokens;
101
+ }
102
+ /** Cursor over the tokens of one line. */
103
+ export class LineCursor {
104
+ tokens;
105
+ line;
106
+ source;
107
+ pos = 0;
108
+ constructor(tokens, line, source) {
109
+ this.tokens = tokens;
110
+ this.line = line;
111
+ this.source = source;
112
+ }
113
+ get done() {
114
+ return this.pos >= this.tokens.length;
115
+ }
116
+ peek(offset = 0) {
117
+ return this.tokens[this.pos + offset];
118
+ }
119
+ error(message, tok = this.peek()) {
120
+ const col = tok?.col ?? (this.tokens.length ? this.tokens[this.tokens.length - 1].col + this.tokens[this.tokens.length - 1].text.length : 1);
121
+ return new AssemblerError(message, this.line, col, this.source);
122
+ }
123
+ next() {
124
+ const t = this.tokens[this.pos];
125
+ if (!t)
126
+ throw this.error('Unexpected end of line');
127
+ this.pos++;
128
+ return t;
129
+ }
130
+ isPunct(p) {
131
+ const t = this.peek();
132
+ return t?.type === 'punct' && t.text === p;
133
+ }
134
+ isId(text) {
135
+ const t = this.peek();
136
+ return t?.type === 'id' && (text === undefined || t.text === text);
137
+ }
138
+ punct(p) {
139
+ const t = this.next();
140
+ if (t.type !== 'punct' || t.text !== p)
141
+ throw this.error(`Expected '${p}'`, t);
142
+ }
143
+ tryPunct(p) {
144
+ if (this.isPunct(p)) {
145
+ this.pos++;
146
+ return true;
147
+ }
148
+ return false;
149
+ }
150
+ id(expected) {
151
+ const t = this.next();
152
+ if (t.type !== 'id' || (expected !== undefined && t.text !== expected)) {
153
+ throw this.error(expected ? `Expected '${expected}'` : 'Expected identifier', t);
154
+ }
155
+ return t.text;
156
+ }
157
+ number() {
158
+ const t = this.next();
159
+ if (t.type !== 'num')
160
+ throw this.error('Expected number', t);
161
+ return t.value;
162
+ }
163
+ integer(min = -Infinity, max = Infinity) {
164
+ const tok = this.peek();
165
+ const v = this.number();
166
+ if (!Number.isInteger(v) || v < min || v > max)
167
+ throw this.error(`Expected integer in range ${min}..${max}`, tok);
168
+ return v;
169
+ }
170
+ /** String literal or `null`. */
171
+ stringOrNull() {
172
+ const t = this.next();
173
+ if (t.type === 'str')
174
+ return t.value;
175
+ if (t.type === 'id' && t.text === 'null')
176
+ return null;
177
+ throw this.error('Expected string or null', t);
178
+ }
179
+ end() {
180
+ if (!this.done)
181
+ throw this.error(`Unexpected '${this.peek().text}'`);
182
+ }
183
+ }
@@ -0,0 +1,26 @@
1
+ import type { Swf } from '../swf/swf.js';
2
+ import { RawTag, type Tag } from '../swf/tags.js';
3
+ export type AssetKind = 'images' | 'sounds' | 'binary' | 'texts';
4
+ export declare const ALL_ASSET_KINDS: AssetKind[];
5
+ export interface ExportAssetsResult {
6
+ files: string[];
7
+ /** Characters that could not be exported, with the reason. */
8
+ skipped: Array<{
9
+ id: number;
10
+ reason: string;
11
+ }>;
12
+ }
13
+ /** Class / export names per character id (SymbolClass + ExportAssets). */
14
+ export declare function symbolNames(swf: Swf): Map<number, string[]>;
15
+ /** Initial text of a DefineEditText tag (undefined when it has none). */
16
+ export declare function editTextOf(tag: RawTag): {
17
+ text: string;
18
+ offset: number;
19
+ end: number;
20
+ } | undefined;
21
+ /** Writes resources into `outDir/<kind>/<id>[_<symbol>].<ext>`. */
22
+ export declare function exportAssets(swf: Swf, outDir: string, kinds?: AssetKind[]): Promise<ExportAssetsResult>;
23
+ /** Replaces a DefineEditText's initial text. */
24
+ export declare function replaceEditText(swf: Swf, id: number, text: string): Tag;
25
+ /** Replaces the resource with character `id` using a file's contents (type chosen by the tag). */
26
+ export declare function replaceAsset(swf: Swf, id: number, file: Uint8Array): Tag;