@swf2es/native 0.0.0-stage → 0.1.1

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 (105) hide show
  1. package/LICENSE +202 -0
  2. package/LICENSE.third-party +313 -0
  3. package/README.md +489 -2
  4. package/dist/atf/binary.d.ts +2 -0
  5. package/dist/atf/binary.d.ts.map +1 -0
  6. package/dist/atf/binary.js +4 -0
  7. package/dist/atf/binary.js.map +1 -0
  8. package/dist/atf/index.d.ts +27 -0
  9. package/dist/atf/index.d.ts.map +1 -0
  10. package/dist/atf/index.js +158 -0
  11. package/dist/atf/index.js.map +1 -0
  12. package/dist/atf/jxr.d.ts +22 -0
  13. package/dist/atf/jxr.d.ts.map +1 -0
  14. package/dist/atf/jxr.js +60 -0
  15. package/dist/atf/jxr.js.map +1 -0
  16. package/dist/atf/parse.d.ts +65 -0
  17. package/dist/atf/parse.d.ts.map +1 -0
  18. package/dist/atf/parse.js +204 -0
  19. package/dist/atf/parse.js.map +1 -0
  20. package/dist/audio/binary.d.ts +2 -0
  21. package/dist/audio/binary.d.ts.map +1 -0
  22. package/dist/audio/binary.js +4 -0
  23. package/dist/audio/binary.js.map +1 -0
  24. package/dist/audio/index.d.ts +21 -0
  25. package/dist/audio/index.d.ts.map +1 -0
  26. package/dist/audio/index.js +73 -0
  27. package/dist/audio/index.js.map +1 -0
  28. package/dist/base64.d.ts +3 -0
  29. package/dist/base64.d.ts.map +1 -0
  30. package/dist/base64.js +10 -0
  31. package/dist/base64.js.map +1 -0
  32. package/dist/compress/binary.d.ts +2 -0
  33. package/dist/compress/binary.d.ts.map +1 -0
  34. package/dist/compress/binary.js +4 -0
  35. package/dist/compress/binary.js.map +1 -0
  36. package/dist/compress/index.d.ts +59 -0
  37. package/dist/compress/index.d.ts.map +1 -0
  38. package/dist/compress/index.js +146 -0
  39. package/dist/compress/index.js.map +1 -0
  40. package/dist/instance.d.ts +25 -0
  41. package/dist/instance.d.ts.map +1 -0
  42. package/dist/instance.js +50 -0
  43. package/dist/instance.js.map +1 -0
  44. package/dist/mp3/binary.d.ts +2 -0
  45. package/dist/mp3/binary.d.ts.map +1 -0
  46. package/dist/mp3/binary.js +4 -0
  47. package/dist/mp3/binary.js.map +1 -0
  48. package/dist/mp3/index.d.ts +20 -0
  49. package/dist/mp3/index.d.ts.map +1 -0
  50. package/dist/mp3/index.js +49 -0
  51. package/dist/mp3/index.js.map +1 -0
  52. package/dist/render/binary.d.ts +2 -0
  53. package/dist/render/binary.d.ts.map +1 -0
  54. package/dist/render/binary.js +4 -0
  55. package/dist/render/binary.js.map +1 -0
  56. package/dist/render/bitmap-kernel.d.ts +118 -0
  57. package/dist/render/bitmap-kernel.d.ts.map +1 -0
  58. package/dist/render/bitmap-kernel.js +592 -0
  59. package/dist/render/bitmap-kernel.js.map +1 -0
  60. package/dist/render/draw-kernel.d.ts +79 -0
  61. package/dist/render/draw-kernel.d.ts.map +1 -0
  62. package/dist/render/draw-kernel.js +205 -0
  63. package/dist/render/draw-kernel.js.map +1 -0
  64. package/dist/render/dxt-encoder.d.ts +19 -0
  65. package/dist/render/dxt-encoder.d.ts.map +1 -0
  66. package/dist/render/dxt-encoder.js +95 -0
  67. package/dist/render/dxt-encoder.js.map +1 -0
  68. package/dist/render/index.d.ts +6 -0
  69. package/dist/render/index.d.ts.map +1 -0
  70. package/dist/render/index.js +11 -0
  71. package/dist/render/index.js.map +1 -0
  72. package/dist/render/lines.d.ts +55 -0
  73. package/dist/render/lines.d.ts.map +1 -0
  74. package/dist/render/lines.js +123 -0
  75. package/dist/render/lines.js.map +1 -0
  76. package/dist/render/module.d.ts +11 -0
  77. package/dist/render/module.d.ts.map +1 -0
  78. package/dist/render/module.js +27 -0
  79. package/dist/render/module.js.map +1 -0
  80. package/dist/render/pixel-ops.d.ts +77 -0
  81. package/dist/render/pixel-ops.d.ts.map +1 -0
  82. package/dist/render/pixel-ops.js +236 -0
  83. package/dist/render/pixel-ops.js.map +1 -0
  84. package/dist/render/tessellator.d.ts +90 -0
  85. package/dist/render/tessellator.d.ts.map +1 -0
  86. package/dist/render/tessellator.js +251 -0
  87. package/dist/render/tessellator.js.map +1 -0
  88. package/package.json +53 -3
  89. package/src/atf/index.ts +212 -0
  90. package/src/atf/jxr.ts +83 -0
  91. package/src/atf/parse.ts +263 -0
  92. package/src/audio/index.ts +93 -0
  93. package/src/base64.ts +10 -0
  94. package/src/compress/index.ts +225 -0
  95. package/src/instance.ts +53 -0
  96. package/src/mp3/index.ts +71 -0
  97. package/src/render/bitmap-kernel.ts +856 -0
  98. package/src/render/draw-kernel.ts +268 -0
  99. package/src/render/dxt-encoder.ts +121 -0
  100. package/src/render/index.ts +37 -0
  101. package/src/render/lines.ts +161 -0
  102. package/src/render/module.ts +30 -0
  103. package/src/render/pixel-ops.ts +356 -0
  104. package/src/render/tessellator.ts +367 -0
  105. package/src/webassembly.d.ts +22 -0
@@ -0,0 +1,263 @@
1
+ // ATF, Adobe's texture container: its header and the blocks of each face
2
+ // and level, read as AIR reads them. The layout is Adobe's "ATF file
3
+ // format" (version 3, 2015), and the old header, which has no version,
4
+ // as Adobe's dds2atf writes it:
5
+ //
6
+ // old: "ATF", length U24, cube UB[1] format UB[7], log2 width U8,
7
+ // log2 height U8, count U8; each block's length U24
8
+ // new: "ATF", reserved U8[3], 0xff, version U8, length U32, then as
9
+ // the old; each block's length U32 (U24 at version 0)
10
+ //
11
+ // The length counts what follows its own field. Each face, then each level
12
+ // of it, holds the blocks of every GPU's encoding in a fixed order; from
13
+ // version 3 those include ETC2's.
14
+
15
+ /**
16
+ * Why AIR refuses an ATF: `size` is Error #3669 (Bad input size), `decode`
17
+ * #3677 (Texture decoding failed) and `format` #3675 (Texture format
18
+ * mismatch).
19
+ */
20
+ export type AtfFailure = "size" | "decode" | "format";
21
+
22
+ export class AtfError extends Error {
23
+ readonly reason: AtfFailure;
24
+
25
+ constructor(reason: AtfFailure, message: string) {
26
+ super(message);
27
+ this.reason = reason;
28
+ }
29
+ }
30
+
31
+ /** The Context3DTextureFormat whose textures take an ATF format. */
32
+ export type AtfKind = "bgra" | "compressed" | "compressedAlpha";
33
+
34
+ export const ATF_RGB888 = 0;
35
+ export const ATF_RGBA8888 = 1;
36
+ export const ATF_COMPRESSED = 2;
37
+ export const ATF_RAW_COMPRESSED = 3;
38
+ export const ATF_COMPRESSED_ALPHA = 4;
39
+ export const ATF_RAW_COMPRESSED_ALPHA = 5;
40
+ export const ATF_COMPRESSED_LOSSY = 0x0c;
41
+ export const ATF_COMPRESSED_LOSSY_ALPHA = 0x0d;
42
+
43
+ /** The largest texture ATF describes, 4096 pixels a side. */
44
+ const MAX_LOG2 = 12;
45
+
46
+ export interface AtfHeader {
47
+ /** Whether it is the old header, which AIR reads alone for content before SWF 17. */
48
+ old: boolean;
49
+ /** 0 for the old header. */
50
+ version: number;
51
+ format: number;
52
+ cube: boolean;
53
+ log2Width: number;
54
+ log2Height: number;
55
+ width: number;
56
+ height: number;
57
+ /** Levels each face holds, the first the full size. */
58
+ count: number;
59
+ /** Where the first face's first block starts, in the data. */
60
+ start: number;
61
+ /** Where the length field says the texture ends: AIR refuses less data (#3669), and reads on past it. */
62
+ end: number;
63
+ }
64
+
65
+ /** A level's blocks, in the order its format lists them: views of the data. */
66
+ export type AtfBlocks = Uint8Array[];
67
+
68
+ /**
69
+ * The texture format that takes ATF's `format`: null for the formats 6 to
70
+ * 11, which AIR calls a mismatch whatever the texture (#3675), and
71
+ * undefined for any later one, which it calls corrupt (#3677).
72
+ */
73
+ export function atfKind(format: number): AtfKind | null | undefined {
74
+ switch (format) {
75
+ case ATF_RGB888:
76
+ case ATF_RGBA8888:
77
+ return "bgra";
78
+ case ATF_COMPRESSED:
79
+ case ATF_RAW_COMPRESSED:
80
+ case ATF_COMPRESSED_LOSSY:
81
+ return "compressed";
82
+ case ATF_COMPRESSED_ALPHA:
83
+ case ATF_RAW_COMPRESSED_ALPHA:
84
+ case ATF_COMPRESSED_LOSSY_ALPHA:
85
+ return "compressedAlpha";
86
+ default:
87
+ return format <= 11 ? null : undefined;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * The ATF at `offset` of `data`, checked as AIR checks it before it looks
93
+ * further, even for an asynchronous upload: its signature (#3677), and that
94
+ * the data holds the header and what its length says (#3669).
95
+ */
96
+ export function readAtfHeader(data: Uint8Array, offset: number): AtfHeader {
97
+ // An offset past the data, or not one at all, is as short as none.
98
+ const available =
99
+ Number.isInteger(offset) && offset >= 0 && offset <= data.length ? data.length - offset : -1;
100
+ if (available < 3) {
101
+ throw new AtfError("size", "no ATF signature");
102
+ }
103
+
104
+ if (data[offset] !== 0x41 || data[offset + 1] !== 0x54 || data[offset + 2] !== 0x46) {
105
+ throw new AtfError("decode", "not an ATF");
106
+ }
107
+
108
+ // The new header marks itself where the old one has its format.
109
+ const old = available < 7 || data[offset + 6] !== 0xff;
110
+ if (available < (old ? 10 : 16)) {
111
+ throw new AtfError("size", "ATF header truncated");
112
+ }
113
+
114
+ const at = offset + (old ? 6 : 12);
115
+ const length = old
116
+ ? (data[offset + 3] << 16) | (data[offset + 4] << 8) | data[offset + 5]
117
+ : ((data[offset + 8] << 24) |
118
+ (data[offset + 9] << 16) |
119
+ (data[offset + 10] << 8) |
120
+ data[offset + 11]) >>>
121
+ 0;
122
+ // Past this, AIR reads as far as the data goes, the length aside.
123
+ const end = at + length;
124
+ if (end > data.length) {
125
+ throw new AtfError("size", "ATF longer than its data");
126
+ }
127
+
128
+ const log2Width = data[at + 1];
129
+ const log2Height = data[at + 2];
130
+
131
+ return {
132
+ old,
133
+ version: old ? 0 : data[offset + 7],
134
+ format: data[at] & 0x7f,
135
+ cube: (data[at] & 0x80) !== 0,
136
+ log2Width,
137
+ log2Height,
138
+ width: 2 ** Math.min(log2Width, 31),
139
+ height: 2 ** Math.min(log2Height, 31),
140
+ count: data[at + 3],
141
+ start: at + 4,
142
+ end,
143
+ };
144
+ }
145
+
146
+ /** The lossy formats, which came with version 3 and AIR reads in no other. */
147
+ function lossy(format: number): boolean {
148
+ return format === ATF_COMPRESSED_LOSSY || format === ATF_COMPRESSED_LOSSY_ALPHA;
149
+ }
150
+
151
+ /** Blocks a level holds, every GPU's encoding: ETC2's from version 3. */
152
+ function blocksPerLevel(format: number, version: number): number {
153
+ const etc2 = version >= 3;
154
+ switch (format) {
155
+ case ATF_RGB888:
156
+ case ATF_RGBA8888:
157
+ return 1;
158
+ case ATF_COMPRESSED:
159
+ // DXT1 2, PVRTC 3, ETC1 3, ETC2 3.
160
+ return etc2 ? 11 : 8;
161
+ case ATF_RAW_COMPRESSED:
162
+ case ATF_RAW_COMPRESSED_ALPHA:
163
+ // DXT, PVRTC, ETC1 and ETC2 one each.
164
+ return etc2 ? 4 : 3;
165
+ case ATF_COMPRESSED_ALPHA:
166
+ // DXT5 4, PVRTC 3, ETC1 3, ETC2 6.
167
+ return etc2 ? 16 : 10;
168
+ case ATF_COMPRESSED_LOSSY:
169
+ // As compressed, with a block of ETC2's modes; version 3 alone.
170
+ return 12;
171
+ default:
172
+ // As compressed with alpha, with a block of ETC2's modes; version 3 alone.
173
+ return 17;
174
+ }
175
+ }
176
+
177
+ /** How many of a level's blocks the desktop reads: its BGRA image, or its DXT's. */
178
+ function desktopBlocks(format: number): number {
179
+ switch (format) {
180
+ case ATF_COMPRESSED:
181
+ case ATF_COMPRESSED_LOSSY:
182
+ return 2;
183
+ case ATF_COMPRESSED_ALPHA:
184
+ case ATF_COMPRESSED_LOSSY_ALPHA:
185
+ return 4;
186
+ default:
187
+ return 1;
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Each face's levels, each its blocks, as views of `data`; a format AIR
193
+ * knows, a size up to 4096 and a level count from 1 to the size's own, or
194
+ * #3677, and a format no texture takes, #3675. AIR reads the blocks as far
195
+ * as the data goes, whatever the length says, and streams what it finds: a
196
+ * level that starts where too little is left for a block's length is
197
+ * absent, as are those after it; one whose blocks for the desktop are whole
198
+ * stands though a later block of its is cut short, and the levels after it
199
+ * are absent; but a block the desktop reads that is cut short is corrupt
200
+ * (#3677).
201
+ */
202
+ export function readAtfBlocks(data: Uint8Array, header: AtfHeader): (AtfBlocks | null)[][] {
203
+ const kind = atfKind(header.format);
204
+ if (kind === null) {
205
+ throw new AtfError("format", `ATF format ${header.format} is no texture's`);
206
+ }
207
+
208
+ const levels = 1 + Math.max(header.log2Width, header.log2Height);
209
+ if (
210
+ kind === undefined ||
211
+ header.log2Width > MAX_LOG2 ||
212
+ header.log2Height > MAX_LOG2 ||
213
+ header.count === 0 ||
214
+ header.count > levels ||
215
+ (lossy(header.format) && header.version < 3)
216
+ ) {
217
+ throw new AtfError("decode", "ATF format, size or level count out of range");
218
+ }
219
+
220
+ const blocks = blocksPerLevel(header.format, header.version);
221
+ const needed = desktopBlocks(header.format);
222
+ const field = header.version === 0 ? 3 : 4;
223
+ const faces: (AtfBlocks | null)[][] = [];
224
+ let at = header.start;
225
+ let streamed = false;
226
+
227
+ for (let face = 0; face < (header.cube ? 6 : 1); face++) {
228
+ const found: (AtfBlocks | null)[] = [];
229
+ for (let level = 0; level < header.count; level++) {
230
+ const read: AtfBlocks = [];
231
+ for (let block = 0; block < blocks && !streamed; block++) {
232
+ if (field > data.length - at) {
233
+ streamed = true;
234
+ break;
235
+ }
236
+
237
+ let length = (data[at] << 16) | (data[at + 1] << 8) | data[at + 2];
238
+ if (field === 4) {
239
+ length = length * 256 + data[at + 3];
240
+ }
241
+
242
+ at += field;
243
+ if (length > data.length - at) {
244
+ if (block < needed) {
245
+ throw new AtfError("decode", "ATF block past the data");
246
+ }
247
+
248
+ streamed = true;
249
+ break;
250
+ }
251
+
252
+ read.push(data.subarray(at, at + length));
253
+ at += length;
254
+ }
255
+
256
+ found.push(read.length >= needed ? read : null);
257
+ }
258
+
259
+ faces.push(found);
260
+ }
261
+
262
+ return faces;
263
+ }
@@ -0,0 +1,93 @@
1
+ // SWF's ADPCM and Nellymoser sound, decoded synchronously in one module
2
+ // that imports nothing (src/audio/root.zig), kept as compress's is: each
3
+ // call copies its input into the module's memory, decodes there in an
4
+ // arena reset first, and copies the samples out. A Nellymoser stream's
5
+ // state stays here between its calls, copied in and out with each.
6
+ import { KeptInstance } from "../instance.js";
7
+ import { binary } from "./binary.js";
8
+
9
+ interface Exports {
10
+ memory: WebAssembly.Memory;
11
+ reset(): void;
12
+ alloc(n: number): number;
13
+ resultPointer(): number;
14
+ decodeAdpcm(input: number, length: number, channels: number): number;
15
+ nellymoserStateSize(): number;
16
+ nellymoserStart(state: number): void;
17
+ decodeNellymoser(input: number, length: number, state: number): number;
18
+ }
19
+
20
+ const kept = new KeptInstance<Exports>(binary);
21
+
22
+ /** `data` in the module's memory, after a reset; 0 if it does not fit. */
23
+ function input(x: Exports, data: Uint8Array): number {
24
+ x.reset();
25
+ const at = x.alloc(data.length);
26
+ if (at !== 0) {
27
+ new Uint8Array(x.memory.buffer, at, data.length).set(data);
28
+ }
29
+
30
+ return at;
31
+ }
32
+
33
+ /**
34
+ * SWF's ADPCM as 16-bit samples, channels interleaved: a code size, then
35
+ * packets of a header per channel (a 16-bit sample and a 6-bit step index)
36
+ * and 4095 codes, the header's sample the packet's first, as adl gives it
37
+ * (Ruffle's decoder leaves it out). It ends where the bits do; none where
38
+ * the module cannot have the memory.
39
+ */
40
+ export function decodeAdpcm(data: Uint8Array, channels: 1 | 2): Int16Array {
41
+ return kept.call((x) => {
42
+ const at = input(x, data);
43
+ const n = at === 0 ? -1 : x.decodeAdpcm(at, data.length, channels);
44
+ return n <= 0
45
+ ? new Int16Array(0)
46
+ : new Int16Array(x.memory.buffer, x.resultPointer(), n).slice();
47
+ });
48
+ }
49
+
50
+ /** The samples a Nellymoser block decodes to. */
51
+ export const NELLYMOSER_SAMPLES = 256;
52
+
53
+ /** Nellymoser's blocks into samples, the overlap carried from one call to the next, as a stream's blocks need. */
54
+ export class NellymoserDecoder {
55
+ /** The stream's state as the module keeps it, made at the first call. */
56
+ #state: Uint8Array | null = null;
57
+
58
+ /**
59
+ * `data`'s whole 64-byte blocks as samples from -1 to 1, 256 a block;
60
+ * none where the module cannot have the memory, the stream's state then
61
+ * as it was.
62
+ */
63
+ decode(data: Uint8Array): Float32Array {
64
+ return kept.call((x) => {
65
+ x.reset();
66
+ const size = x.nellymoserStateSize();
67
+ const state = x.alloc(size);
68
+ if (state === 0) {
69
+ return new Float32Array(0);
70
+ }
71
+
72
+ if (this.#state) {
73
+ new Uint8Array(x.memory.buffer, state, size).set(this.#state);
74
+ } else {
75
+ x.nellymoserStart(state);
76
+ }
77
+
78
+ const at = x.alloc(data.length);
79
+ if (at === 0) {
80
+ return new Float32Array(0);
81
+ }
82
+
83
+ new Uint8Array(x.memory.buffer, at, data.length).set(data);
84
+ const n = x.decodeNellymoser(at, data.length, state);
85
+ if (n < 0) {
86
+ return new Float32Array(0);
87
+ }
88
+
89
+ this.#state = new Uint8Array(x.memory.buffer, state, size).slice();
90
+ return new Float32Array(x.memory.buffer, x.resultPointer(), n).slice();
91
+ });
92
+ }
93
+ }
package/src/base64.ts ADDED
@@ -0,0 +1,10 @@
1
+ /** Base64 to bytes with a plain loop: Uint8Array.from with a callback takes some 8 times as long. */
2
+ export function decodeBase64(text: string): Uint8Array {
3
+ const raw = atob(text);
4
+ const bytes = new Uint8Array(raw.length);
5
+ for (let i = 0; i < raw.length; i++) {
6
+ bytes[i] = raw.charCodeAt(i);
7
+ }
8
+
9
+ return bytes;
10
+ }
@@ -0,0 +1,225 @@
1
+ // zlib and LZMA, synchronously, in one module that imports nothing. Each call
2
+ // copies its input into the module's memory, works there in an arena reset
3
+ // first, and copies the output out. Memory never shrinks, so an instance
4
+ // whose memory has grown past MEMORY_KEPT is dropped after its call, as is
5
+ // one whose call threw, and the next call starts a new one from the
6
+ // compiled module.
7
+ import { KeptInstance } from "../instance.js";
8
+ import { binary } from "./binary.js";
9
+
10
+ interface Exports {
11
+ memory: WebAssembly.Memory;
12
+ reset(): void;
13
+ alloc(n: number): number;
14
+ resultPointer(): number;
15
+ resultSize(): number;
16
+ inputUsed(): number;
17
+ errorMessage(): number;
18
+ inflateData(input: number, length: number, windowBits: number, limit: number): number;
19
+ deflateData(input: number, length: number, level: number, windowBits: number): number;
20
+ lzmaDecode(properties: number, input: number, length: number, size: number): number;
21
+ lzmaEncode(input: number, length: number): number;
22
+ }
23
+
24
+ /** The stream ended. */
25
+ export const DONE = 0;
26
+ /** The output reached its limit before the stream ended. */
27
+ export const FULL = 1;
28
+ /** The data is not what it says. */
29
+ export const CORRUPT = -1;
30
+ /** The input ended before the stream did. */
31
+ export const TRUNCATED = -2;
32
+ /** The module, or the page, could not have the memory the output needed. */
33
+ export const NO_MEMORY = -3;
34
+ /** A zlib stream asked for a preset dictionary. */
35
+ export const DICTIONARY = -4;
36
+
37
+ /** What a decoder made of its input. */
38
+ export interface Decoded {
39
+ /** DONE, FULL or one of the failures. */
40
+ status: number;
41
+ /**
42
+ * The output, after `offset` bytes left for the caller: on DONE and FULL,
43
+ * and on a failure only when `partial` asked for what decoded before it;
44
+ * otherwise only the `offset` bytes, so that what a bomb inflated is
45
+ * never copied out of the module only to be dropped.
46
+ */
47
+ bytes: Uint8Array;
48
+ /** How many bytes of the input it read. */
49
+ used: number;
50
+ /** zlib's account of corrupt data, if it gave one. */
51
+ message: string | null;
52
+ }
53
+
54
+ /** How a decoder hands its output over. */
55
+ export interface DecodeOptions {
56
+ /** Bytes to leave before the output, for the caller to fill. */
57
+ offset?: number;
58
+ /** Whether a failure still gives what decoded before it. */
59
+ partial?: boolean;
60
+ }
61
+
62
+ export { MEMORY_KEPT } from "../instance.js";
63
+
64
+ const kept = new KeptInstance<Exports>(binary);
65
+
66
+ /** Run `f` on the module's kept instance. */
67
+ function call<T>(f: (x: Exports) => T): T {
68
+ return kept.call(f);
69
+ }
70
+
71
+ /** `data` in the module's memory, after a reset; 0 if it does not fit. */
72
+ function input(x: Exports, data: Uint8Array): number {
73
+ x.reset();
74
+ const at = x.alloc(data.length);
75
+ if (at !== 0) {
76
+ new Uint8Array(x.memory.buffer, at, data.length).set(data);
77
+ }
78
+
79
+ return at;
80
+ }
81
+
82
+ /** The result, copied out after `offset` bytes; null if the page cannot have that much. */
83
+ function output(x: Exports, offset: number): Uint8Array | null {
84
+ const n = x.resultSize();
85
+ let bytes: Uint8Array;
86
+ try {
87
+ bytes = new Uint8Array(offset + n);
88
+ } catch (e) {
89
+ if (e instanceof RangeError) {
90
+ return null;
91
+ }
92
+
93
+ throw e;
94
+ }
95
+
96
+ if (n) {
97
+ bytes.set(new Uint8Array(x.memory.buffer, x.resultPointer(), n), offset);
98
+ }
99
+
100
+ return bytes;
101
+ }
102
+
103
+ /** A decoder's result: its output copied out only when it is wanted. */
104
+ function decoded(x: Exports, status: number, options: DecodeOptions): Decoded {
105
+ const offset = options.offset ?? 0;
106
+ const used = x.inputUsed();
107
+ const message = status === CORRUPT ? cString(x, x.errorMessage()) : null;
108
+ const wanted = status === DONE || status === FULL || options.partial === true;
109
+ const bytes = wanted ? output(x, offset) : new Uint8Array(offset);
110
+ if (bytes === null) {
111
+ return { status: NO_MEMORY, bytes: new Uint8Array(offset), used, message: null };
112
+ }
113
+
114
+ return { status, bytes, used, message };
115
+ }
116
+
117
+ function cString(x: Exports, at: number): string | null {
118
+ if (at === 0) {
119
+ return null;
120
+ }
121
+
122
+ const memory = new Uint8Array(x.memory.buffer);
123
+ let end = at;
124
+ while (memory[end] !== 0) {
125
+ end++;
126
+ }
127
+
128
+ return String.fromCharCode(...memory.subarray(at, end));
129
+ }
130
+
131
+ /**
132
+ * Inflate a zlib stream, or with `raw` a raw deflate stream, to at most
133
+ * `limit` bytes. What follows the stream's end is not read.
134
+ */
135
+ export function inflate(
136
+ data: Uint8Array,
137
+ raw: boolean,
138
+ limit: number,
139
+ options: DecodeOptions = {},
140
+ ): Decoded {
141
+ return call((x) => {
142
+ const at = input(x, data);
143
+ const status =
144
+ at === 0
145
+ ? NO_MEMORY
146
+ : x.inflateData(at, data.length, raw ? -15 : 15, Math.min(limit, 2 ** 32 - 1));
147
+ return decoded(x, status, options);
148
+ });
149
+ }
150
+
151
+ /** `data` deflated at `level` (0 to 9) with zlib's default memory and window, as a zlib stream or, `raw`, a raw one. */
152
+ export function deflate(data: Uint8Array, level: number, raw: boolean): Uint8Array {
153
+ return call((x) => {
154
+ const at = input(x, data);
155
+ const bytes =
156
+ at === 0 || x.deflateData(at, data.length, level, raw ? -15 : 15) !== DONE
157
+ ? null
158
+ : output(x, 0);
159
+ if (bytes === null) {
160
+ throw new RangeError(`no memory to deflate ${data.length} bytes`);
161
+ }
162
+
163
+ return bytes;
164
+ });
165
+ }
166
+
167
+ /**
168
+ * Decode the LZMA stream at `start` of `data`, whose 5 property bytes are at
169
+ * `properties`, to `size` bytes, or fewer if its end marker comes first.
170
+ * Properties past 224 are corrupt, a dictionary is at least 4 KB, and any
171
+ * read past the data's end fails, as a whole stream never makes one.
172
+ */
173
+ export function lzmaDecode(
174
+ data: Uint8Array,
175
+ properties: number,
176
+ start: number,
177
+ size: number,
178
+ options: DecodeOptions = {},
179
+ ): Decoded {
180
+ if (properties < 0 || properties + 5 > data.length || start < 0 || start > data.length) {
181
+ return {
182
+ status: TRUNCATED,
183
+ bytes: new Uint8Array(options.offset ?? 0),
184
+ used: 0,
185
+ message: null,
186
+ };
187
+ }
188
+
189
+ return call((x) => {
190
+ const from = Math.min(properties, start);
191
+ const at = input(x, data.subarray(from));
192
+ const status =
193
+ at === 0
194
+ ? NO_MEMORY
195
+ : x.lzmaDecode(
196
+ at + properties - from,
197
+ at + start - from,
198
+ data.length - start,
199
+ Math.min(size, 2 ** 32 - 1),
200
+ );
201
+ return decoded(x, status, options);
202
+ });
203
+ }
204
+
205
+ /**
206
+ * `data` as LZMA, as avmplus' ByteArray writes it: 5 property bytes, the
207
+ * length in 8 bytes, little-endian, and the stream, at level 9 with a 1 MB
208
+ * dictionary, or a smaller one for less data, and no end marker.
209
+ */
210
+ export function lzmaEncode(data: Uint8Array): Uint8Array {
211
+ return call((x) => {
212
+ const at = input(x, data);
213
+ const bytes = at === 0 || x.lzmaEncode(at, data.length) !== DONE ? null : output(x, 0);
214
+ if (bytes === null) {
215
+ throw new RangeError(`no memory to compress ${data.length} bytes`);
216
+ }
217
+
218
+ return bytes;
219
+ });
220
+ }
221
+
222
+ /** The module's memory now, for measuring: 0 when no instance is kept. */
223
+ export function memoryInUse(): number {
224
+ return kept.memory;
225
+ }
@@ -0,0 +1,53 @@
1
+ // One kept instance of a module that imports nothing, for compress and atf:
2
+ // the module compiled from its base64 the first time it is called, an
3
+ // instance made as needed, and let go after a call that threw, or whose
4
+ // memory grew past MEMORY_KEPT, since memory never shrinks.
5
+ import { decodeBase64 } from "./base64.js";
6
+
7
+ /**
8
+ * Memory an instance may keep between calls: what a bitmap or a SWF of a
9
+ * few MB needs, less than an LZMA encoder's for a dictionary of 1 MB.
10
+ * Starting again costs an instantiation, a fraction of a millisecond.
11
+ */
12
+ export const MEMORY_KEPT = 8 << 20;
13
+
14
+ export class KeptInstance<Exports extends { memory: WebAssembly.Memory }> {
15
+ private readonly binary: string;
16
+ private compiled: WebAssembly.Module | null = null;
17
+ private instance: Exports | null = null;
18
+
19
+ constructor(binary: string) {
20
+ this.binary = binary;
21
+ }
22
+
23
+ /**
24
+ * Run `f` on the instance, made first if there is none (the module
25
+ * compiled synchronously the first time: some 100 KB, which browsers
26
+ * allow on the main thread).
27
+ */
28
+ call<T>(f: (x: Exports) => T): T {
29
+ this.compiled ??= new WebAssembly.Module(decodeBase64(this.binary));
30
+ this.instance ??= new WebAssembly.Instance(this.compiled).exports as unknown as Exports;
31
+ const x = this.instance;
32
+ try {
33
+ return f(x);
34
+ } catch (e) {
35
+ this.instance = null;
36
+ throw e;
37
+ } finally {
38
+ if (x.memory.buffer.byteLength > MEMORY_KEPT) {
39
+ this.instance = null;
40
+ }
41
+ }
42
+ }
43
+
44
+ /** Let the instance go, so that the next call starts a new one. */
45
+ drop(): void {
46
+ this.instance = null;
47
+ }
48
+
49
+ /** The instance's memory now, for measuring: 0 when none is kept. */
50
+ get memory(): number {
51
+ return this.instance ? this.instance.memory.buffer.byteLength : 0;
52
+ }
53
+ }