swf-toolkit 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +125 -2
- package/README.tr.md +126 -0
- package/dist/abc/abc-file.d.ts +168 -0
- package/dist/abc/abc-file.js +656 -0
- package/dist/abc/analysis.d.ts +36 -0
- package/dist/abc/analysis.js +213 -0
- package/dist/abc/code.d.ts +59 -0
- package/dist/abc/code.js +379 -0
- package/dist/abc/constants.d.ts +80 -0
- package/dist/abc/constants.js +93 -0
- package/dist/abc/interner.d.ts +46 -0
- package/dist/abc/interner.js +168 -0
- package/dist/abc/model.d.ts +40 -0
- package/dist/abc/model.js +146 -0
- package/dist/abc/opcodes.d.ts +39 -0
- package/dist/abc/opcodes.js +196 -0
- package/dist/abc/pcode/assembler.d.ts +42 -0
- package/dist/abc/pcode/assembler.js +635 -0
- package/dist/abc/pcode/disassembler.d.ts +17 -0
- package/dist/abc/pcode/disassembler.js +147 -0
- package/dist/abc/pcode/format.d.ts +23 -0
- package/dist/abc/pcode/format.js +131 -0
- package/dist/abc/pcode/lexer.d.ts +34 -0
- package/dist/abc/pcode/lexer.js +183 -0
- package/dist/assets/assets.d.ts +26 -0
- package/dist/assets/assets.js +151 -0
- package/dist/assets/images.d.ts +31 -0
- package/dist/assets/images.js +238 -0
- package/dist/assets/png.d.ts +12 -0
- package/dist/assets/png.js +196 -0
- package/dist/assets/sounds.d.ts +37 -0
- package/dist/assets/sounds.js +236 -0
- package/dist/avm1/avm1.d.ts +55 -0
- package/dist/avm1/avm1.js +642 -0
- package/dist/avm1/project.d.ts +16 -0
- package/dist/avm1/project.js +81 -0
- package/dist/cjs/abc/abc-file.d.ts +168 -0
- package/dist/cjs/abc/abc-file.js +663 -0
- package/dist/cjs/abc/analysis.d.ts +36 -0
- package/dist/cjs/abc/analysis.js +221 -0
- package/dist/cjs/abc/code.d.ts +59 -0
- package/dist/cjs/abc/code.js +387 -0
- package/dist/cjs/abc/constants.d.ts +80 -0
- package/dist/cjs/abc/constants.js +97 -0
- package/dist/cjs/abc/interner.d.ts +46 -0
- package/dist/cjs/abc/interner.js +172 -0
- package/dist/cjs/abc/model.d.ts +40 -0
- package/dist/cjs/abc/model.js +155 -0
- package/dist/cjs/abc/opcodes.d.ts +39 -0
- package/dist/cjs/abc/opcodes.js +201 -0
- package/dist/cjs/abc/pcode/assembler.d.ts +42 -0
- package/dist/cjs/abc/pcode/assembler.js +640 -0
- package/dist/cjs/abc/pcode/disassembler.d.ts +17 -0
- package/dist/cjs/abc/pcode/disassembler.js +152 -0
- package/dist/cjs/abc/pcode/format.d.ts +23 -0
- package/dist/cjs/abc/pcode/format.js +141 -0
- package/dist/cjs/abc/pcode/lexer.d.ts +34 -0
- package/dist/cjs/abc/pcode/lexer.js +188 -0
- package/dist/cjs/assets/assets.d.ts +26 -0
- package/dist/cjs/assets/assets.js +159 -0
- package/dist/cjs/assets/images.d.ts +31 -0
- package/dist/cjs/assets/images.js +246 -0
- package/dist/cjs/assets/png.d.ts +12 -0
- package/dist/cjs/assets/png.js +202 -0
- package/dist/cjs/assets/sounds.d.ts +37 -0
- package/dist/cjs/assets/sounds.js +246 -0
- package/dist/cjs/avm1/avm1.d.ts +55 -0
- package/dist/cjs/avm1/avm1.js +649 -0
- package/dist/cjs/avm1/project.d.ts +16 -0
- package/dist/cjs/avm1/project.js +88 -0
- package/dist/cjs/compiler/ast.d.ts +285 -0
- package/dist/cjs/compiler/ast.js +3 -0
- package/dist/cjs/compiler/class-compiler.d.ts +34 -0
- package/dist/cjs/compiler/class-compiler.js +440 -0
- package/dist/cjs/compiler/codegen.d.ts +144 -0
- package/dist/cjs/compiler/codegen.js +1589 -0
- package/dist/cjs/compiler/lexer.d.ts +27 -0
- package/dist/cjs/compiler/lexer.js +254 -0
- package/dist/cjs/compiler/parser.d.ts +58 -0
- package/dist/cjs/compiler/parser.js +989 -0
- package/dist/cjs/compiler/program.d.ts +29 -0
- package/dist/cjs/compiler/program.js +36 -0
- package/dist/cjs/compiler/scope.d.ts +22 -0
- package/dist/cjs/compiler/scope.js +242 -0
- package/dist/cjs/compression/lzma.d.ts +37 -0
- package/dist/cjs/compression/lzma.js +575 -0
- package/dist/cjs/compression/swf-container.d.ts +19 -0
- package/dist/cjs/compression/swf-container.js +92 -0
- package/dist/cjs/decompiler/ast.d.ts +228 -0
- package/dist/cjs/decompiler/ast.js +455 -0
- package/dist/cjs/decompiler/cfg.d.ts +40 -0
- package/dist/cjs/decompiler/cfg.js +236 -0
- package/dist/cjs/decompiler/class-printer.d.ts +9 -0
- package/dist/cjs/decompiler/class-printer.js +173 -0
- package/dist/cjs/decompiler/method.d.ts +41 -0
- package/dist/cjs/decompiler/method.js +436 -0
- package/dist/cjs/decompiler/simulate.d.ts +55 -0
- package/dist/cjs/decompiler/simulate.js +591 -0
- package/dist/cjs/decompiler/structure.d.ts +48 -0
- package/dist/cjs/decompiler/structure.js +764 -0
- package/dist/cjs/errors.d.ts +11 -0
- package/dist/cjs/errors.js +25 -0
- package/dist/cjs/index.d.ts +41 -0
- package/dist/cjs/index.js +82 -0
- package/dist/cjs/io/reader.d.ts +48 -0
- package/dist/cjs/io/reader.js +180 -0
- package/dist/cjs/io/writer.d.ts +37 -0
- package/dist/cjs/io/writer.js +175 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/project/pcode-project.d.ts +32 -0
- package/dist/cjs/project/pcode-project.js +210 -0
- package/dist/cjs/project/source-export.d.ts +12 -0
- package/dist/cjs/project/source-export.js +51 -0
- package/dist/cjs/project/source-import.d.ts +13 -0
- package/dist/cjs/project/source-import.js +73 -0
- package/dist/cjs/swf/swf.d.ts +63 -0
- package/dist/cjs/swf/swf.js +147 -0
- package/dist/cjs/swf/tag-codes.d.ts +77 -0
- package/dist/cjs/swf/tag-codes.js +109 -0
- package/dist/cjs/swf/tags.d.ts +118 -0
- package/dist/cjs/swf/tags.js +330 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +296 -0
- package/dist/compiler/ast.d.ts +285 -0
- package/dist/compiler/ast.js +2 -0
- package/dist/compiler/class-compiler.d.ts +34 -0
- package/dist/compiler/class-compiler.js +437 -0
- package/dist/compiler/codegen.d.ts +144 -0
- package/dist/compiler/codegen.js +1585 -0
- package/dist/compiler/lexer.d.ts +27 -0
- package/dist/compiler/lexer.js +249 -0
- package/dist/compiler/parser.d.ts +58 -0
- package/dist/compiler/parser.js +984 -0
- package/dist/compiler/program.d.ts +29 -0
- package/dist/compiler/program.js +32 -0
- package/dist/compiler/scope.d.ts +22 -0
- package/dist/compiler/scope.js +238 -0
- package/dist/compression/lzma.d.ts +37 -0
- package/dist/compression/lzma.js +569 -0
- package/dist/compression/swf-container.d.ts +19 -0
- package/dist/compression/swf-container.js +87 -0
- package/dist/decompiler/ast.d.ts +228 -0
- package/dist/decompiler/ast.js +441 -0
- package/dist/decompiler/cfg.d.ts +40 -0
- package/dist/decompiler/cfg.js +231 -0
- package/dist/decompiler/class-printer.d.ts +9 -0
- package/dist/decompiler/class-printer.js +170 -0
- package/dist/decompiler/method.d.ts +41 -0
- package/dist/decompiler/method.js +427 -0
- package/dist/decompiler/simulate.d.ts +55 -0
- package/dist/decompiler/simulate.js +586 -0
- package/dist/decompiler/structure.d.ts +48 -0
- package/dist/decompiler/structure.js +760 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +20 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +49 -0
- package/dist/io/reader.d.ts +48 -0
- package/dist/io/reader.js +176 -0
- package/dist/io/writer.d.ts +37 -0
- package/dist/io/writer.js +169 -0
- package/dist/project/pcode-project.d.ts +32 -0
- package/dist/project/pcode-project.js +203 -0
- package/dist/project/source-export.d.ts +12 -0
- package/dist/project/source-export.js +47 -0
- package/dist/project/source-import.d.ts +13 -0
- package/dist/project/source-import.js +70 -0
- package/dist/swf/swf.d.ts +63 -0
- package/dist/swf/swf.js +143 -0
- package/dist/swf/tag-codes.d.ts +77 -0
- package/dist/swf/tag-codes.js +105 -0
- package/dist/swf/tags.d.ts +118 -0
- package/dist/swf/tags.js +316 -0
- package/package.json +71 -4
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure TypeScript LZMA ("LZMA1", as used by ZWS SWF files) decoder and encoder.
|
|
3
|
+
*
|
|
4
|
+
* Written from the public LZMA format description (LZMA SDK `lzma-specification.txt`).
|
|
5
|
+
* The SWF variant stores a 5-byte properties header and raw LZMA data with a known
|
|
6
|
+
* uncompressed size and usually no end marker.
|
|
7
|
+
*/
|
|
8
|
+
import { SwfFormatError } from '../errors.js';
|
|
9
|
+
const NUM_STATES = 12;
|
|
10
|
+
const POS_STATES_MAX = 1 << 4;
|
|
11
|
+
const END_POS_MODEL_INDEX = 14;
|
|
12
|
+
const NUM_FULL_DISTANCES = 128;
|
|
13
|
+
const NUM_LEN_TO_POS_STATES = 4;
|
|
14
|
+
const NUM_ALIGN_BITS = 4;
|
|
15
|
+
const MATCH_MIN_LEN = 2;
|
|
16
|
+
const MATCH_MAX_LEN = 273;
|
|
17
|
+
const PROB_INIT = 1024;
|
|
18
|
+
const TOP = 0x1000000;
|
|
19
|
+
export function parseLzmaProperties(props) {
|
|
20
|
+
if (props.length < 5)
|
|
21
|
+
throw new SwfFormatError('LZMA properties must be 5 bytes');
|
|
22
|
+
let d = props[0];
|
|
23
|
+
if (d >= 9 * 5 * 5)
|
|
24
|
+
throw new SwfFormatError(`Invalid LZMA properties byte ${d}`);
|
|
25
|
+
const lc = d % 9;
|
|
26
|
+
d = Math.floor(d / 9);
|
|
27
|
+
const lp = d % 5;
|
|
28
|
+
const pb = Math.floor(d / 5);
|
|
29
|
+
const dictSize = (props[1] | (props[2] << 8) | (props[3] << 16) | (props[4] << 24)) >>> 0;
|
|
30
|
+
return { lc, lp, pb, dictSize };
|
|
31
|
+
}
|
|
32
|
+
export function encodeLzmaProperties(p) {
|
|
33
|
+
const out = new Uint8Array(5);
|
|
34
|
+
out[0] = (p.pb * 5 + p.lp) * 9 + p.lc;
|
|
35
|
+
out[1] = p.dictSize & 0xff;
|
|
36
|
+
out[2] = (p.dictSize >>> 8) & 0xff;
|
|
37
|
+
out[3] = (p.dictSize >>> 16) & 0xff;
|
|
38
|
+
out[4] = (p.dictSize >>> 24) & 0xff;
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
function probs(n) {
|
|
42
|
+
return new Uint16Array(n).fill(PROB_INIT);
|
|
43
|
+
}
|
|
44
|
+
class LenModel {
|
|
45
|
+
choice = probs(2); // [0] = choice, [1] = choice2
|
|
46
|
+
low = probs(POS_STATES_MAX << 3);
|
|
47
|
+
mid = probs(POS_STATES_MAX << 3);
|
|
48
|
+
high = probs(256);
|
|
49
|
+
}
|
|
50
|
+
class Model {
|
|
51
|
+
isMatch = probs(NUM_STATES << 4);
|
|
52
|
+
isRep = probs(NUM_STATES);
|
|
53
|
+
isRepG0 = probs(NUM_STATES);
|
|
54
|
+
isRepG1 = probs(NUM_STATES);
|
|
55
|
+
isRepG2 = probs(NUM_STATES);
|
|
56
|
+
isRep0Long = probs(NUM_STATES << 4);
|
|
57
|
+
posSlot = probs(NUM_LEN_TO_POS_STATES << 6);
|
|
58
|
+
specPos = probs(1 + NUM_FULL_DISTANCES - END_POS_MODEL_INDEX);
|
|
59
|
+
align = probs(1 << NUM_ALIGN_BITS);
|
|
60
|
+
len = new LenModel();
|
|
61
|
+
repLen = new LenModel();
|
|
62
|
+
literal;
|
|
63
|
+
constructor(lc, lp) {
|
|
64
|
+
this.literal = probs(0x300 << (lc + lp));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function stateAfterLiteral(s) {
|
|
68
|
+
return s < 4 ? 0 : s < 10 ? s - 3 : s - 6;
|
|
69
|
+
}
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Decoder
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
class RangeDecoder {
|
|
74
|
+
data;
|
|
75
|
+
range = 0xffffffff;
|
|
76
|
+
code = 0;
|
|
77
|
+
pos;
|
|
78
|
+
constructor(data, start = 0) {
|
|
79
|
+
this.data = data;
|
|
80
|
+
this.pos = start;
|
|
81
|
+
if (this.next() !== 0)
|
|
82
|
+
throw new SwfFormatError('Corrupted LZMA stream (first byte must be 0)');
|
|
83
|
+
for (let i = 0; i < 4; i++)
|
|
84
|
+
this.code = this.code * 256 + this.next();
|
|
85
|
+
if (this.code === this.range)
|
|
86
|
+
throw new SwfFormatError('Corrupted LZMA stream');
|
|
87
|
+
}
|
|
88
|
+
next() {
|
|
89
|
+
// Reading past the end yields zeros; well-formed streams never depend on it.
|
|
90
|
+
return this.pos < this.data.length ? this.data[this.pos++] : (this.pos++, 0);
|
|
91
|
+
}
|
|
92
|
+
get overrun() {
|
|
93
|
+
return this.pos > this.data.length + 4;
|
|
94
|
+
}
|
|
95
|
+
bit(p, i) {
|
|
96
|
+
const prob = p[i];
|
|
97
|
+
const bound = (this.range >>> 11) * prob;
|
|
98
|
+
let b;
|
|
99
|
+
if (this.code < bound) {
|
|
100
|
+
this.range = bound;
|
|
101
|
+
p[i] = prob + ((2048 - prob) >>> 5);
|
|
102
|
+
b = 0;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
this.range -= bound;
|
|
106
|
+
this.code -= bound;
|
|
107
|
+
p[i] = prob - (prob >>> 5);
|
|
108
|
+
b = 1;
|
|
109
|
+
}
|
|
110
|
+
if (this.range < TOP) {
|
|
111
|
+
this.range = this.range * 256;
|
|
112
|
+
this.code = this.code * 256 + this.next();
|
|
113
|
+
}
|
|
114
|
+
return b;
|
|
115
|
+
}
|
|
116
|
+
direct(numBits) {
|
|
117
|
+
let res = 0;
|
|
118
|
+
for (let i = 0; i < numBits; i++) {
|
|
119
|
+
this.range = this.range >>> 1;
|
|
120
|
+
let b = 0;
|
|
121
|
+
if (this.code >= this.range) {
|
|
122
|
+
this.code -= this.range;
|
|
123
|
+
b = 1;
|
|
124
|
+
}
|
|
125
|
+
res = res * 2 + b;
|
|
126
|
+
if (this.range < TOP) {
|
|
127
|
+
this.range = this.range * 256;
|
|
128
|
+
this.code = this.code * 256 + this.next();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return res;
|
|
132
|
+
}
|
|
133
|
+
tree(p, base, numBits) {
|
|
134
|
+
let m = 1;
|
|
135
|
+
for (let i = 0; i < numBits; i++)
|
|
136
|
+
m = (m << 1) + this.bit(p, base + m);
|
|
137
|
+
return m - (1 << numBits);
|
|
138
|
+
}
|
|
139
|
+
reverseTree(p, base, numBits) {
|
|
140
|
+
let m = 1;
|
|
141
|
+
let sym = 0;
|
|
142
|
+
for (let i = 0; i < numBits; i++) {
|
|
143
|
+
const b = this.bit(p, base + m);
|
|
144
|
+
m = (m << 1) + b;
|
|
145
|
+
sym |= b << i;
|
|
146
|
+
}
|
|
147
|
+
return sym;
|
|
148
|
+
}
|
|
149
|
+
len(l, posState) {
|
|
150
|
+
if (this.bit(l.choice, 0) === 0)
|
|
151
|
+
return this.tree(l.low, posState << 3, 3);
|
|
152
|
+
if (this.bit(l.choice, 1) === 0)
|
|
153
|
+
return 8 + this.tree(l.mid, posState << 3, 3);
|
|
154
|
+
return 16 + this.tree(l.high, 0, 8);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Decompress raw LZMA data.
|
|
159
|
+
* @param props 5-byte LZMA properties
|
|
160
|
+
* @param data compressed stream (without properties)
|
|
161
|
+
* @param outSize exact uncompressed size
|
|
162
|
+
*/
|
|
163
|
+
export function lzmaDecompress(props, data, outSize) {
|
|
164
|
+
const { lc, lp, pb } = parseLzmaProperties(props);
|
|
165
|
+
const m = new Model(lc, lp);
|
|
166
|
+
const rc = new RangeDecoder(data);
|
|
167
|
+
const out = new Uint8Array(outSize);
|
|
168
|
+
const pbMask = (1 << pb) - 1;
|
|
169
|
+
const lpMask = (1 << lp) - 1;
|
|
170
|
+
let state = 0;
|
|
171
|
+
let rep0 = 0;
|
|
172
|
+
let rep1 = 0;
|
|
173
|
+
let rep2 = 0;
|
|
174
|
+
let rep3 = 0;
|
|
175
|
+
let pos = 0;
|
|
176
|
+
while (pos < outSize) {
|
|
177
|
+
const posState = pos & pbMask;
|
|
178
|
+
if (rc.bit(m.isMatch, (state << 4) + posState) === 0) {
|
|
179
|
+
const prev = pos > 0 ? out[pos - 1] : 0;
|
|
180
|
+
const base = 0x300 * (((pos & lpMask) << lc) + (prev >>> (8 - lc)));
|
|
181
|
+
let sym = 1;
|
|
182
|
+
if (state >= 7) {
|
|
183
|
+
let matchByte = out[pos - rep0 - 1];
|
|
184
|
+
do {
|
|
185
|
+
const matchBit = (matchByte >>> 7) & 1;
|
|
186
|
+
matchByte <<= 1;
|
|
187
|
+
const b = rc.bit(m.literal, base + ((1 + matchBit) << 8) + sym);
|
|
188
|
+
sym = (sym << 1) | b;
|
|
189
|
+
if (matchBit !== b)
|
|
190
|
+
break;
|
|
191
|
+
} while (sym < 0x100);
|
|
192
|
+
}
|
|
193
|
+
while (sym < 0x100)
|
|
194
|
+
sym = (sym << 1) | rc.bit(m.literal, base + sym);
|
|
195
|
+
out[pos++] = sym & 0xff;
|
|
196
|
+
state = stateAfterLiteral(state);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
let len;
|
|
200
|
+
if (rc.bit(m.isRep, state) !== 0) {
|
|
201
|
+
if (pos === 0)
|
|
202
|
+
throw new SwfFormatError('Corrupted LZMA stream (rep match at start)');
|
|
203
|
+
if (rc.bit(m.isRepG0, state) === 0) {
|
|
204
|
+
if (rc.bit(m.isRep0Long, (state << 4) + posState) === 0) {
|
|
205
|
+
state = state < 7 ? 9 : 11;
|
|
206
|
+
out[pos] = out[pos - rep0 - 1];
|
|
207
|
+
pos++;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
let dist;
|
|
213
|
+
if (rc.bit(m.isRepG1, state) === 0) {
|
|
214
|
+
dist = rep1;
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
if (rc.bit(m.isRepG2, state) === 0) {
|
|
218
|
+
dist = rep2;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
dist = rep3;
|
|
222
|
+
rep3 = rep2;
|
|
223
|
+
}
|
|
224
|
+
rep2 = rep1;
|
|
225
|
+
}
|
|
226
|
+
rep1 = rep0;
|
|
227
|
+
rep0 = dist;
|
|
228
|
+
}
|
|
229
|
+
len = rc.len(m.repLen, posState);
|
|
230
|
+
state = state < 7 ? 8 : 11;
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
rep3 = rep2;
|
|
234
|
+
rep2 = rep1;
|
|
235
|
+
rep1 = rep0;
|
|
236
|
+
len = rc.len(m.len, posState);
|
|
237
|
+
state = state < 7 ? 7 : 10;
|
|
238
|
+
const lenState = len < NUM_LEN_TO_POS_STATES - 1 ? len : NUM_LEN_TO_POS_STATES - 1;
|
|
239
|
+
const posSlot = rc.tree(m.posSlot, lenState << 6, 6);
|
|
240
|
+
if (posSlot < 4) {
|
|
241
|
+
rep0 = posSlot;
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
const numDirectBits = (posSlot >>> 1) - 1;
|
|
245
|
+
let dist = (2 | (posSlot & 1)) * 2 ** numDirectBits;
|
|
246
|
+
if (posSlot < END_POS_MODEL_INDEX) {
|
|
247
|
+
dist += rc.reverseTree(m.specPos, dist - posSlot, numDirectBits);
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
dist += rc.direct(numDirectBits - NUM_ALIGN_BITS) * 16;
|
|
251
|
+
dist += rc.reverseTree(m.align, 0, NUM_ALIGN_BITS);
|
|
252
|
+
}
|
|
253
|
+
rep0 = dist;
|
|
254
|
+
}
|
|
255
|
+
if (rep0 === 0xffffffff)
|
|
256
|
+
break; // end marker
|
|
257
|
+
if (rep0 >= pos)
|
|
258
|
+
throw new SwfFormatError('Corrupted LZMA stream (distance out of range)');
|
|
259
|
+
}
|
|
260
|
+
len += MATCH_MIN_LEN;
|
|
261
|
+
let src = pos - rep0 - 1;
|
|
262
|
+
const end = Math.min(outSize, pos + len);
|
|
263
|
+
while (pos < end)
|
|
264
|
+
out[pos++] = out[src++];
|
|
265
|
+
}
|
|
266
|
+
if (rc.overrun)
|
|
267
|
+
throw new SwfFormatError('Corrupted LZMA stream (input truncated)');
|
|
268
|
+
return out;
|
|
269
|
+
}
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
// Encoder
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
class RangeEncoder {
|
|
274
|
+
low = 0;
|
|
275
|
+
range = 0xffffffff;
|
|
276
|
+
cache = 0;
|
|
277
|
+
cacheSize = 1;
|
|
278
|
+
buf;
|
|
279
|
+
length = 0;
|
|
280
|
+
constructor(capacity) {
|
|
281
|
+
this.buf = new Uint8Array(Math.max(1024, capacity));
|
|
282
|
+
}
|
|
283
|
+
write(b) {
|
|
284
|
+
if (this.length === this.buf.length) {
|
|
285
|
+
const next = new Uint8Array(this.buf.length * 2);
|
|
286
|
+
next.set(this.buf);
|
|
287
|
+
this.buf = next;
|
|
288
|
+
}
|
|
289
|
+
this.buf[this.length++] = b;
|
|
290
|
+
}
|
|
291
|
+
shiftLow() {
|
|
292
|
+
const lo32 = this.low >>> 0;
|
|
293
|
+
const carry = this.low > 0xffffffff ? 1 : 0;
|
|
294
|
+
if (lo32 < 0xff000000 || carry !== 0) {
|
|
295
|
+
let temp = this.cache;
|
|
296
|
+
do {
|
|
297
|
+
this.write((temp + carry) & 0xff);
|
|
298
|
+
temp = 0xff;
|
|
299
|
+
} while (--this.cacheSize !== 0);
|
|
300
|
+
this.cache = lo32 >>> 24;
|
|
301
|
+
}
|
|
302
|
+
this.cacheSize++;
|
|
303
|
+
this.low = (lo32 & 0x00ffffff) * 256;
|
|
304
|
+
}
|
|
305
|
+
bit(p, i, b) {
|
|
306
|
+
const prob = p[i];
|
|
307
|
+
const bound = (this.range >>> 11) * prob;
|
|
308
|
+
if (b === 0) {
|
|
309
|
+
this.range = bound;
|
|
310
|
+
p[i] = prob + ((2048 - prob) >>> 5);
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
this.low += bound;
|
|
314
|
+
this.range -= bound;
|
|
315
|
+
p[i] = prob - (prob >>> 5);
|
|
316
|
+
}
|
|
317
|
+
while (this.range < TOP) {
|
|
318
|
+
this.range = this.range * 256;
|
|
319
|
+
this.shiftLow();
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
direct(value, numBits) {
|
|
323
|
+
for (let i = numBits - 1; i >= 0; i--) {
|
|
324
|
+
this.range = this.range >>> 1;
|
|
325
|
+
if (Math.floor(value / 2 ** i) & 1)
|
|
326
|
+
this.low += this.range;
|
|
327
|
+
while (this.range < TOP) {
|
|
328
|
+
this.range = this.range * 256;
|
|
329
|
+
this.shiftLow();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
tree(p, base, numBits, sym) {
|
|
334
|
+
let m = 1;
|
|
335
|
+
for (let i = numBits - 1; i >= 0; i--) {
|
|
336
|
+
const b = (sym >>> i) & 1;
|
|
337
|
+
this.bit(p, base + m, b);
|
|
338
|
+
m = (m << 1) | b;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
reverseTree(p, base, numBits, sym) {
|
|
342
|
+
let m = 1;
|
|
343
|
+
for (let i = 0; i < numBits; i++) {
|
|
344
|
+
const b = sym & 1;
|
|
345
|
+
sym >>>= 1;
|
|
346
|
+
this.bit(p, base + m, b);
|
|
347
|
+
m = (m << 1) | b;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
len(l, posState, len) {
|
|
351
|
+
if (len < 8) {
|
|
352
|
+
this.bit(l.choice, 0, 0);
|
|
353
|
+
this.tree(l.low, posState << 3, 3, len);
|
|
354
|
+
}
|
|
355
|
+
else if (len < 16) {
|
|
356
|
+
this.bit(l.choice, 0, 1);
|
|
357
|
+
this.bit(l.choice, 1, 0);
|
|
358
|
+
this.tree(l.mid, posState << 3, 3, len - 8);
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
this.bit(l.choice, 0, 1);
|
|
362
|
+
this.bit(l.choice, 1, 1);
|
|
363
|
+
this.tree(l.high, 0, 8, len - 16);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
finish() {
|
|
367
|
+
for (let i = 0; i < 5; i++)
|
|
368
|
+
this.shiftLow();
|
|
369
|
+
return this.buf.slice(0, this.length);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function posSlotOf(dist) {
|
|
373
|
+
if (dist < 4)
|
|
374
|
+
return dist;
|
|
375
|
+
const n = 31 - Math.clz32(dist);
|
|
376
|
+
return (n << 1) | ((dist >>> (n - 1)) & 1);
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Compress data with LZMA. Returns the 5-byte properties and the raw stream
|
|
380
|
+
* (no end marker, no size header), as stored in ZWS SWF files.
|
|
381
|
+
*/
|
|
382
|
+
export function lzmaCompress(input, options = {}) {
|
|
383
|
+
const lc = options.lc ?? 3;
|
|
384
|
+
const lp = options.lp ?? 0;
|
|
385
|
+
const pb = options.pb ?? 2;
|
|
386
|
+
let dictSize = options.dictSize ?? 0;
|
|
387
|
+
if (!dictSize) {
|
|
388
|
+
dictSize = 1 << 16;
|
|
389
|
+
while (dictSize < input.length && dictSize < 1 << 26)
|
|
390
|
+
dictSize *= 2;
|
|
391
|
+
}
|
|
392
|
+
const depth = options.depth ?? 48;
|
|
393
|
+
const niceLength = Math.min(options.niceLength ?? 128, MATCH_MAX_LEN);
|
|
394
|
+
const m = new Model(lc, lp);
|
|
395
|
+
const rc = new RangeEncoder((input.length >>> 1) + 1024);
|
|
396
|
+
const pbMask = (1 << pb) - 1;
|
|
397
|
+
const lpMask = (1 << lp) - 1;
|
|
398
|
+
const n = input.length;
|
|
399
|
+
const HASH_BITS = 18;
|
|
400
|
+
const head = new Int32Array(1 << HASH_BITS).fill(-1);
|
|
401
|
+
const prev = new Int32Array(n);
|
|
402
|
+
const hash3 = (i) => Math.imul(input[i] | (input[i + 1] << 8) | (input[i + 2] << 16), 0x9e3779b1) >>> (32 - HASH_BITS);
|
|
403
|
+
const insert = (i) => {
|
|
404
|
+
if (i + 2 >= n)
|
|
405
|
+
return;
|
|
406
|
+
const h = hash3(i);
|
|
407
|
+
prev[i] = head[h];
|
|
408
|
+
head[h] = i;
|
|
409
|
+
};
|
|
410
|
+
const reps = [0, 0, 0, 0];
|
|
411
|
+
let state = 0;
|
|
412
|
+
let pos = 0;
|
|
413
|
+
const matchLen = (a, b, max) => {
|
|
414
|
+
let l = 0;
|
|
415
|
+
while (l < max && input[a + l] === input[b + l])
|
|
416
|
+
l++;
|
|
417
|
+
return l;
|
|
418
|
+
};
|
|
419
|
+
const encodeLiteral = () => {
|
|
420
|
+
const posState = pos & pbMask;
|
|
421
|
+
rc.bit(m.isMatch, (state << 4) + posState, 0);
|
|
422
|
+
const prevByte = pos > 0 ? input[pos - 1] : 0;
|
|
423
|
+
const base = 0x300 * (((pos & lpMask) << lc) + (prevByte >>> (8 - lc)));
|
|
424
|
+
const byte = input[pos];
|
|
425
|
+
let sym = 1;
|
|
426
|
+
if (state >= 7) {
|
|
427
|
+
const matchByte = input[pos - reps[0] - 1];
|
|
428
|
+
let matched = true;
|
|
429
|
+
for (let i = 7; i >= 0; i--) {
|
|
430
|
+
const b = (byte >>> i) & 1;
|
|
431
|
+
if (matched) {
|
|
432
|
+
const mb = (matchByte >>> i) & 1;
|
|
433
|
+
rc.bit(m.literal, base + ((1 + mb) << 8) + sym, b);
|
|
434
|
+
matched = mb === b;
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
rc.bit(m.literal, base + sym, b);
|
|
438
|
+
}
|
|
439
|
+
sym = (sym << 1) | b;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
for (let i = 7; i >= 0; i--) {
|
|
444
|
+
const b = (byte >>> i) & 1;
|
|
445
|
+
rc.bit(m.literal, base + sym, b);
|
|
446
|
+
sym = (sym << 1) | b;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
state = stateAfterLiteral(state);
|
|
450
|
+
};
|
|
451
|
+
const encodeShortRep = () => {
|
|
452
|
+
const posState = pos & pbMask;
|
|
453
|
+
rc.bit(m.isMatch, (state << 4) + posState, 1);
|
|
454
|
+
rc.bit(m.isRep, state, 1);
|
|
455
|
+
rc.bit(m.isRepG0, state, 0);
|
|
456
|
+
rc.bit(m.isRep0Long, (state << 4) + posState, 0);
|
|
457
|
+
state = state < 7 ? 9 : 11;
|
|
458
|
+
};
|
|
459
|
+
const encodeRep = (repIndex, len) => {
|
|
460
|
+
const posState = pos & pbMask;
|
|
461
|
+
rc.bit(m.isMatch, (state << 4) + posState, 1);
|
|
462
|
+
rc.bit(m.isRep, state, 1);
|
|
463
|
+
if (repIndex === 0) {
|
|
464
|
+
rc.bit(m.isRepG0, state, 0);
|
|
465
|
+
rc.bit(m.isRep0Long, (state << 4) + posState, 1);
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
rc.bit(m.isRepG0, state, 1);
|
|
469
|
+
if (repIndex === 1) {
|
|
470
|
+
rc.bit(m.isRepG1, state, 0);
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
rc.bit(m.isRepG1, state, 1);
|
|
474
|
+
rc.bit(m.isRepG2, state, repIndex - 2);
|
|
475
|
+
}
|
|
476
|
+
const dist = reps[repIndex];
|
|
477
|
+
for (let i = repIndex; i > 0; i--)
|
|
478
|
+
reps[i] = reps[i - 1];
|
|
479
|
+
reps[0] = dist;
|
|
480
|
+
}
|
|
481
|
+
rc.len(m.repLen, posState, len - MATCH_MIN_LEN);
|
|
482
|
+
state = state < 7 ? 8 : 11;
|
|
483
|
+
};
|
|
484
|
+
const encodeMatch = (dist, len) => {
|
|
485
|
+
const posState = pos & pbMask;
|
|
486
|
+
rc.bit(m.isMatch, (state << 4) + posState, 1);
|
|
487
|
+
rc.bit(m.isRep, state, 0);
|
|
488
|
+
rc.len(m.len, posState, len - MATCH_MIN_LEN);
|
|
489
|
+
const lenState = Math.min(len - MATCH_MIN_LEN, NUM_LEN_TO_POS_STATES - 1);
|
|
490
|
+
const posSlot = posSlotOf(dist);
|
|
491
|
+
rc.tree(m.posSlot, lenState << 6, 6, posSlot);
|
|
492
|
+
if (posSlot >= 4) {
|
|
493
|
+
const footerBits = (posSlot >>> 1) - 1;
|
|
494
|
+
const base = (2 | (posSlot & 1)) << footerBits;
|
|
495
|
+
const reduced = dist - base;
|
|
496
|
+
if (posSlot < END_POS_MODEL_INDEX) {
|
|
497
|
+
rc.reverseTree(m.specPos, base - posSlot, footerBits, reduced);
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
rc.direct(reduced >>> NUM_ALIGN_BITS, footerBits - NUM_ALIGN_BITS);
|
|
501
|
+
rc.reverseTree(m.align, 0, NUM_ALIGN_BITS, reduced & 0xf);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
reps[3] = reps[2];
|
|
505
|
+
reps[2] = reps[1];
|
|
506
|
+
reps[1] = reps[0];
|
|
507
|
+
reps[0] = dist;
|
|
508
|
+
state = state < 7 ? 7 : 10;
|
|
509
|
+
};
|
|
510
|
+
while (pos < n) {
|
|
511
|
+
const maxLen = Math.min(MATCH_MAX_LEN, n - pos);
|
|
512
|
+
// Repeat matches (cheap to encode).
|
|
513
|
+
let bestRepLen = 0;
|
|
514
|
+
let bestRepIndex = 0;
|
|
515
|
+
if (pos > 0 && maxLen >= 2) {
|
|
516
|
+
for (let r = 0; r < 4; r++) {
|
|
517
|
+
const src = pos - reps[r] - 1;
|
|
518
|
+
if (src < 0)
|
|
519
|
+
continue;
|
|
520
|
+
const l = matchLen(src, pos, maxLen);
|
|
521
|
+
if (l > bestRepLen) {
|
|
522
|
+
bestRepLen = l;
|
|
523
|
+
bestRepIndex = r;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
// Main match via hash chains.
|
|
528
|
+
let bestLen = 0;
|
|
529
|
+
let bestDist = 0;
|
|
530
|
+
if (maxLen >= 3 && pos + 2 < n) {
|
|
531
|
+
let cand = head[hash3(pos)];
|
|
532
|
+
let chain = depth;
|
|
533
|
+
const minPos = pos - dictSize;
|
|
534
|
+
while (cand >= 0 && cand >= minPos && chain-- > 0) {
|
|
535
|
+
if (input[cand + bestLen] === input[pos + bestLen]) {
|
|
536
|
+
const l = matchLen(cand, pos, maxLen);
|
|
537
|
+
if (l > bestLen) {
|
|
538
|
+
bestLen = l;
|
|
539
|
+
bestDist = pos - cand - 1;
|
|
540
|
+
if (l >= niceLength)
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
cand = prev[cand];
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
let advance;
|
|
548
|
+
if (bestRepLen >= 2 && bestRepLen + 1 >= bestLen) {
|
|
549
|
+
encodeRep(bestRepIndex, bestRepLen);
|
|
550
|
+
advance = bestRepLen;
|
|
551
|
+
}
|
|
552
|
+
else if (bestLen >= 3 && !(bestLen === 3 && bestDist >= 1 << 14)) {
|
|
553
|
+
encodeMatch(bestDist, bestLen);
|
|
554
|
+
advance = bestLen;
|
|
555
|
+
}
|
|
556
|
+
else if (pos > reps[0] && input[pos] === input[pos - reps[0] - 1] && state >= 7) {
|
|
557
|
+
encodeShortRep();
|
|
558
|
+
advance = 1;
|
|
559
|
+
}
|
|
560
|
+
else {
|
|
561
|
+
encodeLiteral();
|
|
562
|
+
advance = 1;
|
|
563
|
+
}
|
|
564
|
+
for (let i = 0; i < advance; i++)
|
|
565
|
+
insert(pos + i);
|
|
566
|
+
pos += advance;
|
|
567
|
+
}
|
|
568
|
+
return { props: encodeLzmaProperties({ lc, lp, pb, dictSize }), data: rc.finish() };
|
|
569
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type LzmaCompressOptions } from './lzma.js';
|
|
2
|
+
export type SwfCompression = 'none' | 'zlib' | 'lzma';
|
|
3
|
+
export interface SwfContainer {
|
|
4
|
+
compression: SwfCompression;
|
|
5
|
+
version: number;
|
|
6
|
+
/** Uncompressed SWF body: everything after the 8-byte file header. */
|
|
7
|
+
body: Uint8Array;
|
|
8
|
+
}
|
|
9
|
+
/** Detects whether a buffer starts with an SWF signature. */
|
|
10
|
+
export declare function isSwf(bytes: Uint8Array): boolean;
|
|
11
|
+
/** Unwraps an FWS/CWS/ZWS file into its uncompressed body. */
|
|
12
|
+
export declare function unpackSwf(bytes: Uint8Array): SwfContainer;
|
|
13
|
+
export interface PackOptions {
|
|
14
|
+
/** zlib level 0-9 (default 9). */
|
|
15
|
+
zlibLevel?: number;
|
|
16
|
+
lzma?: LzmaCompressOptions;
|
|
17
|
+
}
|
|
18
|
+
/** Wraps an uncompressed SWF body in an FWS/CWS/ZWS file. */
|
|
19
|
+
export declare function packSwf(container: SwfContainer, options?: PackOptions): Uint8Array;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { deflateSync, inflateSync } from 'node:zlib';
|
|
2
|
+
import { SwfFormatError } from '../errors.js';
|
|
3
|
+
import { lzmaCompress, lzmaDecompress } from './lzma.js';
|
|
4
|
+
const SIG = {
|
|
5
|
+
FWS: [0x46, 0x57, 0x53],
|
|
6
|
+
CWS: [0x43, 0x57, 0x53],
|
|
7
|
+
ZWS: [0x5a, 0x57, 0x53],
|
|
8
|
+
};
|
|
9
|
+
function sigIs(b, sig) {
|
|
10
|
+
return b[0] === sig[0] && b[1] === sig[1] && b[2] === sig[2];
|
|
11
|
+
}
|
|
12
|
+
function u32(b, o) {
|
|
13
|
+
return (b[o] | (b[o + 1] << 8) | (b[o + 2] << 16) | (b[o + 3] << 24)) >>> 0;
|
|
14
|
+
}
|
|
15
|
+
/** Detects whether a buffer starts with an SWF signature. */
|
|
16
|
+
export function isSwf(bytes) {
|
|
17
|
+
return bytes.length >= 8 && (sigIs(bytes, SIG.FWS) || sigIs(bytes, SIG.CWS) || sigIs(bytes, SIG.ZWS));
|
|
18
|
+
}
|
|
19
|
+
/** Unwraps an FWS/CWS/ZWS file into its uncompressed body. */
|
|
20
|
+
export function unpackSwf(bytes) {
|
|
21
|
+
if (bytes.length < 8)
|
|
22
|
+
throw new SwfFormatError('File too small to be an SWF');
|
|
23
|
+
const version = bytes[3];
|
|
24
|
+
const fileLength = u32(bytes, 4);
|
|
25
|
+
const bodyLength = fileLength - 8;
|
|
26
|
+
if (bodyLength < 0)
|
|
27
|
+
throw new SwfFormatError(`Invalid SWF file length ${fileLength}`);
|
|
28
|
+
if (sigIs(bytes, SIG.FWS)) {
|
|
29
|
+
// Some tools write a wrong length; trust the actual data when it is shorter.
|
|
30
|
+
const body = bytes.subarray(8, Math.min(bytes.length, fileLength)).slice();
|
|
31
|
+
return { compression: 'none', version, body };
|
|
32
|
+
}
|
|
33
|
+
if (sigIs(bytes, SIG.CWS)) {
|
|
34
|
+
let body;
|
|
35
|
+
try {
|
|
36
|
+
body = new Uint8Array(inflateSync(bytes.subarray(8), { finishFlush: 2 /* Z_SYNC_FLUSH: tolerate truncated streams */ }));
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
throw new SwfFormatError(`zlib decompression failed: ${e.message}`);
|
|
40
|
+
}
|
|
41
|
+
if (body.length > bodyLength)
|
|
42
|
+
body = body.subarray(0, bodyLength);
|
|
43
|
+
return { compression: 'zlib', version, body };
|
|
44
|
+
}
|
|
45
|
+
if (sigIs(bytes, SIG.ZWS)) {
|
|
46
|
+
if (bytes.length < 17)
|
|
47
|
+
throw new SwfFormatError('Truncated ZWS header');
|
|
48
|
+
const compressedLength = u32(bytes, 8);
|
|
49
|
+
const props = bytes.subarray(12, 17);
|
|
50
|
+
const data = bytes.subarray(17, Math.min(bytes.length, 17 + compressedLength));
|
|
51
|
+
const body = lzmaDecompress(props, data, bodyLength);
|
|
52
|
+
return { compression: 'lzma', version, body };
|
|
53
|
+
}
|
|
54
|
+
throw new SwfFormatError('Not an SWF file (missing FWS/CWS/ZWS signature)');
|
|
55
|
+
}
|
|
56
|
+
/** Wraps an uncompressed SWF body in an FWS/CWS/ZWS file. */
|
|
57
|
+
export function packSwf(container, options = {}) {
|
|
58
|
+
const { body, version, compression } = container;
|
|
59
|
+
const fileLength = body.length + 8;
|
|
60
|
+
const header = (sig) => {
|
|
61
|
+
const h = new Uint8Array(8);
|
|
62
|
+
h.set(sig, 0);
|
|
63
|
+
h[3] = version;
|
|
64
|
+
new DataView(h.buffer).setUint32(4, fileLength, true);
|
|
65
|
+
return h;
|
|
66
|
+
};
|
|
67
|
+
if (compression === 'none') {
|
|
68
|
+
const out = new Uint8Array(fileLength);
|
|
69
|
+
out.set(header(SIG.FWS), 0);
|
|
70
|
+
out.set(body, 8);
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
if (compression === 'zlib') {
|
|
74
|
+
const z = deflateSync(body, { level: options.zlibLevel ?? 9 });
|
|
75
|
+
const out = new Uint8Array(8 + z.length);
|
|
76
|
+
out.set(header(SIG.CWS), 0);
|
|
77
|
+
out.set(z, 8);
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
const { props, data } = lzmaCompress(body, options.lzma);
|
|
81
|
+
const out = new Uint8Array(17 + data.length);
|
|
82
|
+
out.set(header(SIG.ZWS), 0);
|
|
83
|
+
new DataView(out.buffer).setUint32(8, data.length, true);
|
|
84
|
+
out.set(props, 12);
|
|
85
|
+
out.set(data, 17);
|
|
86
|
+
return out;
|
|
87
|
+
}
|