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,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ALL_ASSET_KINDS = void 0;
|
|
4
|
+
exports.symbolNames = symbolNames;
|
|
5
|
+
exports.editTextOf = editTextOf;
|
|
6
|
+
exports.exportAssets = exportAssets;
|
|
7
|
+
exports.replaceEditText = replaceEditText;
|
|
8
|
+
exports.replaceAsset = replaceAsset;
|
|
9
|
+
/**
|
|
10
|
+
* Export / replace SWF resources: bitmaps, sounds, binary data and edit-text strings.
|
|
11
|
+
*/
|
|
12
|
+
const promises_1 = require("node:fs/promises");
|
|
13
|
+
const node_path_1 = require("node:path");
|
|
14
|
+
const reader_js_1 = require("../io/reader.js");
|
|
15
|
+
const writer_js_1 = require("../io/writer.js");
|
|
16
|
+
const tag_codes_js_1 = require("../swf/tag-codes.js");
|
|
17
|
+
const tags_js_1 = require("../swf/tags.js");
|
|
18
|
+
const images_js_1 = require("./images.js");
|
|
19
|
+
const sounds_js_1 = require("./sounds.js");
|
|
20
|
+
exports.ALL_ASSET_KINDS = ['images', 'sounds', 'binary', 'texts'];
|
|
21
|
+
/** Class / export names per character id (SymbolClass + ExportAssets). */
|
|
22
|
+
function symbolNames(swf) {
|
|
23
|
+
const map = new Map();
|
|
24
|
+
for (const { tag } of swf.walkTags()) {
|
|
25
|
+
if (!(tag instanceof tags_js_1.SymbolTableTag))
|
|
26
|
+
continue;
|
|
27
|
+
for (const s of tag.symbols)
|
|
28
|
+
map.set(s.id, [...(map.get(s.id) ?? []), s.name]);
|
|
29
|
+
}
|
|
30
|
+
return map;
|
|
31
|
+
}
|
|
32
|
+
/** Initial text of a DefineEditText tag (undefined when it has none). */
|
|
33
|
+
function editTextOf(tag) {
|
|
34
|
+
const r = new reader_js_1.ByteReader(tag.data);
|
|
35
|
+
r.u16();
|
|
36
|
+
const bits = r.ub(5);
|
|
37
|
+
r.ub(bits * 4);
|
|
38
|
+
r.align();
|
|
39
|
+
const f1 = r.u8();
|
|
40
|
+
const f2 = r.u8();
|
|
41
|
+
if (f1 & 0x01)
|
|
42
|
+
r.u16();
|
|
43
|
+
if (f2 & 0x80)
|
|
44
|
+
r.cstring();
|
|
45
|
+
if (f1 & 0x01)
|
|
46
|
+
r.u16();
|
|
47
|
+
if (f1 & 0x04)
|
|
48
|
+
r.u32();
|
|
49
|
+
if (f1 & 0x02)
|
|
50
|
+
r.u16();
|
|
51
|
+
if (f2 & 0x20)
|
|
52
|
+
r.bytesView(9);
|
|
53
|
+
r.cstring(); // variable name
|
|
54
|
+
if (!(f1 & 0x80))
|
|
55
|
+
return undefined;
|
|
56
|
+
const offset = r.pos;
|
|
57
|
+
const text = r.cstring();
|
|
58
|
+
return { text, offset, end: r.pos };
|
|
59
|
+
}
|
|
60
|
+
/** Writes resources into `outDir/<kind>/<id>[_<symbol>].<ext>`. */
|
|
61
|
+
async function exportAssets(swf, outDir, kinds = exports.ALL_ASSET_KINDS) {
|
|
62
|
+
const result = { files: [], skipped: [] };
|
|
63
|
+
const names = symbolNames(swf);
|
|
64
|
+
let jpegTables;
|
|
65
|
+
for (const { tag } of swf.walkTags()) {
|
|
66
|
+
if (tag.code === tag_codes_js_1.TagCode.JPEGTables && tag instanceof tags_js_1.RawTag)
|
|
67
|
+
jpegTables = tag.data;
|
|
68
|
+
}
|
|
69
|
+
const fileName = (id, ext) => {
|
|
70
|
+
const sym = names.get(id)?.[0];
|
|
71
|
+
const safe = sym ? '_' + sym.replace(/[^A-Za-z0-9_.$-]/g, '_') : '';
|
|
72
|
+
return `${id}${safe}.${ext}`;
|
|
73
|
+
};
|
|
74
|
+
const write = async (kind, name, data) => {
|
|
75
|
+
const dir = (0, node_path_1.join)(outDir, kind);
|
|
76
|
+
await (0, promises_1.mkdir)(dir, { recursive: true });
|
|
77
|
+
const p = (0, node_path_1.join)(dir, name);
|
|
78
|
+
await (0, promises_1.writeFile)(p, data);
|
|
79
|
+
result.files.push(p);
|
|
80
|
+
};
|
|
81
|
+
for (const { tag } of swf.walkTags()) {
|
|
82
|
+
const id = tag.characterId;
|
|
83
|
+
if (id === undefined)
|
|
84
|
+
continue;
|
|
85
|
+
try {
|
|
86
|
+
if (kinds.includes('images') && images_js_1.IMAGE_TAGS.has(tag.code)) {
|
|
87
|
+
const img = (0, images_js_1.extractImage)(tag, jpegTables);
|
|
88
|
+
if (img) {
|
|
89
|
+
await write('images', fileName(id, img.ext), img.data);
|
|
90
|
+
if (img.alphaMask)
|
|
91
|
+
await write('images', fileName(id, 'alpha.png'), img.alphaMask);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else if (kinds.includes('sounds') && tag.code === tag_codes_js_1.TagCode.DefineSound) {
|
|
95
|
+
const s = (0, sounds_js_1.extractSound)(tag);
|
|
96
|
+
if (s)
|
|
97
|
+
await write('sounds', fileName(id, s.ext), s.data);
|
|
98
|
+
}
|
|
99
|
+
else if (kinds.includes('binary') && tag instanceof tags_js_1.DefineBinaryDataTag) {
|
|
100
|
+
await write('binary', fileName(id, 'bin'), tag.data);
|
|
101
|
+
}
|
|
102
|
+
else if (kinds.includes('texts') && tag.code === tag_codes_js_1.TagCode.DefineEditText && tag instanceof tags_js_1.RawTag) {
|
|
103
|
+
const t = editTextOf(tag);
|
|
104
|
+
if (t)
|
|
105
|
+
await write('texts', fileName(id, 'txt'), t.text);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
result.skipped.push({ id, reason: e.message });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
/** Replaces a DefineEditText's initial text. */
|
|
115
|
+
function replaceEditText(swf, id, text) {
|
|
116
|
+
for (const { tag } of swf.walkTags()) {
|
|
117
|
+
if (tag.characterId !== id || tag.code !== tag_codes_js_1.TagCode.DefineEditText || !(tag instanceof tags_js_1.RawTag))
|
|
118
|
+
continue;
|
|
119
|
+
const t = editTextOf(tag);
|
|
120
|
+
const w = new writer_js_1.ByteWriter(tag.data.length + text.length * 3);
|
|
121
|
+
if (t) {
|
|
122
|
+
w.bytes(tag.data.subarray(0, t.offset));
|
|
123
|
+
w.cstring(text);
|
|
124
|
+
w.bytes(tag.data.subarray(t.end));
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// Set HasText and append the text.
|
|
128
|
+
const data = tag.data.slice();
|
|
129
|
+
const r = new reader_js_1.ByteReader(data);
|
|
130
|
+
r.u16();
|
|
131
|
+
const bits = r.ub(5);
|
|
132
|
+
r.ub(bits * 4);
|
|
133
|
+
r.align();
|
|
134
|
+
data[r.pos] |= 0x80;
|
|
135
|
+
w.bytes(data);
|
|
136
|
+
w.cstring(text);
|
|
137
|
+
}
|
|
138
|
+
tag.data = w.toBytes();
|
|
139
|
+
return tag;
|
|
140
|
+
}
|
|
141
|
+
throw new Error(`No DefineEditText with id ${id}`);
|
|
142
|
+
}
|
|
143
|
+
/** Replaces the resource with character `id` using a file's contents (type chosen by the tag). */
|
|
144
|
+
function replaceAsset(swf, id, file) {
|
|
145
|
+
const tag = swf.findCharacter(id);
|
|
146
|
+
if (!tag)
|
|
147
|
+
throw new Error(`No character with id ${id}`);
|
|
148
|
+
if (images_js_1.IMAGE_TAGS.has(tag.code))
|
|
149
|
+
return (0, images_js_1.replaceImage)(swf, id, file);
|
|
150
|
+
if (tag.code === tag_codes_js_1.TagCode.DefineSound)
|
|
151
|
+
return (0, sounds_js_1.replaceSound)(swf, id, file);
|
|
152
|
+
if (tag instanceof tags_js_1.DefineBinaryDataTag) {
|
|
153
|
+
tag.data = file.slice();
|
|
154
|
+
return tag;
|
|
155
|
+
}
|
|
156
|
+
if (tag.code === tag_codes_js_1.TagCode.DefineEditText)
|
|
157
|
+
return replaceEditText(swf, id, new TextDecoder().decode(file));
|
|
158
|
+
throw new Error(`Replacing ${tag.name} (id ${id}) is not supported`);
|
|
159
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Swf } from '../swf/swf.js';
|
|
2
|
+
import { RawTag, type Tag } from '../swf/tags.js';
|
|
3
|
+
import { type RgbaImage } from './png.js';
|
|
4
|
+
export declare const IMAGE_TAGS: ReadonlySet<number>;
|
|
5
|
+
export interface ExtractedImage {
|
|
6
|
+
id: number;
|
|
7
|
+
/** File extension: png, jpg or gif. */
|
|
8
|
+
ext: 'png' | 'jpg' | 'gif';
|
|
9
|
+
data: Uint8Array;
|
|
10
|
+
/** Alpha mask (grayscale PNG) for JPEG3/4 images. */
|
|
11
|
+
alphaMask?: Uint8Array;
|
|
12
|
+
width?: number;
|
|
13
|
+
height?: number;
|
|
14
|
+
}
|
|
15
|
+
/** Decodes DefineBitsLossless(2) pixel data to straight RGBA. */
|
|
16
|
+
export declare function decodeLossless(tag: RawTag): RgbaImage;
|
|
17
|
+
/** Extracts the image stored in a bitmap tag. */
|
|
18
|
+
export declare function extractImage(tag: Tag, jpegTables?: Uint8Array): ExtractedImage | undefined;
|
|
19
|
+
/** Reads width/height from a JPEG's SOF marker. */
|
|
20
|
+
export declare function jpegSize(b: Uint8Array): {
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
} | undefined;
|
|
24
|
+
/** Builds a DefineBitsLossless2 tag body from an RGBA image. */
|
|
25
|
+
export declare function encodeLossless2(id: number, img: RgbaImage): Uint8Array;
|
|
26
|
+
/**
|
|
27
|
+
* Replaces the bitmap with character `id`. PNG files become
|
|
28
|
+
* DefineBitsLossless2 (exact pixels, alpha); JPEG / GIF files are stored
|
|
29
|
+
* as DefineBitsJPEG2.
|
|
30
|
+
*/
|
|
31
|
+
export declare function replaceImage(swf: Swf, id: number, file: Uint8Array): Tag;
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IMAGE_TAGS = void 0;
|
|
4
|
+
exports.decodeLossless = decodeLossless;
|
|
5
|
+
exports.extractImage = extractImage;
|
|
6
|
+
exports.jpegSize = jpegSize;
|
|
7
|
+
exports.encodeLossless2 = encodeLossless2;
|
|
8
|
+
exports.replaceImage = replaceImage;
|
|
9
|
+
/**
|
|
10
|
+
* Bitmap tags ↔ image files.
|
|
11
|
+
*
|
|
12
|
+
* - DefineBitsLossless / DefineBitsLossless2 ↔ PNG (with alpha)
|
|
13
|
+
* - DefineBits (+ JPEGTables), DefineBitsJPEG2/3/4 → original JPEG / PNG / GIF data.
|
|
14
|
+
* JPEG3/4 alpha channels are exported as a separate grayscale PNG mask.
|
|
15
|
+
*/
|
|
16
|
+
const node_zlib_1 = require("node:zlib");
|
|
17
|
+
const reader_js_1 = require("../io/reader.js");
|
|
18
|
+
const writer_js_1 = require("../io/writer.js");
|
|
19
|
+
const tag_codes_js_1 = require("../swf/tag-codes.js");
|
|
20
|
+
const tags_js_1 = require("../swf/tags.js");
|
|
21
|
+
const png_js_1 = require("./png.js");
|
|
22
|
+
exports.IMAGE_TAGS = new Set([
|
|
23
|
+
tag_codes_js_1.TagCode.DefineBits,
|
|
24
|
+
tag_codes_js_1.TagCode.DefineBitsJPEG2,
|
|
25
|
+
tag_codes_js_1.TagCode.DefineBitsJPEG3,
|
|
26
|
+
tag_codes_js_1.TagCode.DefineBitsJPEG4,
|
|
27
|
+
tag_codes_js_1.TagCode.DefineBitsLossless,
|
|
28
|
+
tag_codes_js_1.TagCode.DefineBitsLossless2,
|
|
29
|
+
]);
|
|
30
|
+
function imageType(b) {
|
|
31
|
+
if ((0, png_js_1.isPng)(b))
|
|
32
|
+
return 'png';
|
|
33
|
+
if (b[0] === 0x47 && b[1] === 0x49 && b[2] === 0x46)
|
|
34
|
+
return 'gif';
|
|
35
|
+
return 'jpg';
|
|
36
|
+
}
|
|
37
|
+
/** Removes the erroneous `FF D9 FF D8` prefix some encoders emit and merges JPEGTables. */
|
|
38
|
+
function cleanJpeg(data, tables) {
|
|
39
|
+
let d = data;
|
|
40
|
+
if (d[0] === 0xff && d[1] === 0xd9 && d[2] === 0xff && d[3] === 0xd8)
|
|
41
|
+
d = d.subarray(4);
|
|
42
|
+
// Some files contain an EOI/SOI pair in the middle (tables + image); remove it.
|
|
43
|
+
const parts = [];
|
|
44
|
+
if (tables && tables.length > 4) {
|
|
45
|
+
let t = tables;
|
|
46
|
+
if (t[0] === 0xff && t[1] === 0xd9 && t[2] === 0xff && t[3] === 0xd8)
|
|
47
|
+
t = t.subarray(4);
|
|
48
|
+
// tables: SOI ... EOI → drop trailing EOI; image: SOI ... → drop leading SOI
|
|
49
|
+
parts.push(t[t.length - 2] === 0xff && t[t.length - 1] === 0xd9 ? t.subarray(0, -2) : t);
|
|
50
|
+
parts.push(d[0] === 0xff && d[1] === 0xd8 ? d.subarray(2) : d);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
for (let i = 2; i + 3 < d.length; i++) {
|
|
54
|
+
if (d[i] === 0xff && d[i + 1] === 0xd9 && d[i + 2] === 0xff && d[i + 3] === 0xd8) {
|
|
55
|
+
parts.push(d.subarray(0, i), d.subarray(i + 4));
|
|
56
|
+
d = new Uint8Array(0);
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (d.length)
|
|
61
|
+
parts.push(d);
|
|
62
|
+
}
|
|
63
|
+
const out = new Uint8Array(parts.reduce((a, p) => a + p.length, 0));
|
|
64
|
+
let o = 0;
|
|
65
|
+
for (const p of parts) {
|
|
66
|
+
out.set(p, o);
|
|
67
|
+
o += p.length;
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
/** Decodes DefineBitsLossless(2) pixel data to straight RGBA. */
|
|
72
|
+
function decodeLossless(tag) {
|
|
73
|
+
const r = new reader_js_1.ByteReader(tag.data);
|
|
74
|
+
r.u16(); // id
|
|
75
|
+
const format = r.u8();
|
|
76
|
+
const width = r.u16();
|
|
77
|
+
const height = r.u16();
|
|
78
|
+
const alpha = tag.code === tag_codes_js_1.TagCode.DefineBitsLossless2;
|
|
79
|
+
const colorTableSize = format === 3 ? r.u8() + 1 : 0;
|
|
80
|
+
const pixels = new Uint8Array((0, node_zlib_1.inflateSync)(r.rest()));
|
|
81
|
+
const out = new Uint8Array(width * height * 4);
|
|
82
|
+
const unpremul = (d) => {
|
|
83
|
+
const a = out[d + 3];
|
|
84
|
+
if (a > 0 && a < 255) {
|
|
85
|
+
out[d] = Math.min(255, Math.round((out[d] * 255) / a));
|
|
86
|
+
out[d + 1] = Math.min(255, Math.round((out[d + 1] * 255) / a));
|
|
87
|
+
out[d + 2] = Math.min(255, Math.round((out[d + 2] * 255) / a));
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
if (format === 3) {
|
|
91
|
+
const entry = alpha ? 4 : 3;
|
|
92
|
+
const table = pixels.subarray(0, colorTableSize * entry);
|
|
93
|
+
const stride = (width + 3) & ~3;
|
|
94
|
+
const base = colorTableSize * entry;
|
|
95
|
+
for (let y = 0; y < height; y++) {
|
|
96
|
+
for (let x = 0; x < width; x++) {
|
|
97
|
+
const idx = pixels[base + y * stride + x];
|
|
98
|
+
const d = (y * width + x) * 4;
|
|
99
|
+
out[d] = table[idx * entry] ?? 0;
|
|
100
|
+
out[d + 1] = table[idx * entry + 1] ?? 0;
|
|
101
|
+
out[d + 2] = table[idx * entry + 2] ?? 0;
|
|
102
|
+
out[d + 3] = alpha ? (table[idx * entry + 3] ?? 255) : 255;
|
|
103
|
+
if (alpha)
|
|
104
|
+
unpremul(d);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else if (format === 4) {
|
|
109
|
+
const stride = (width * 2 + 3) & ~3;
|
|
110
|
+
for (let y = 0; y < height; y++) {
|
|
111
|
+
for (let x = 0; x < width; x++) {
|
|
112
|
+
const v = (pixels[y * stride + x * 2] << 8) | pixels[y * stride + x * 2 + 1];
|
|
113
|
+
const d = (y * width + x) * 4;
|
|
114
|
+
out[d] = ((v >> 10) & 0x1f) * 255 / 31;
|
|
115
|
+
out[d + 1] = ((v >> 5) & 0x1f) * 255 / 31;
|
|
116
|
+
out[d + 2] = (v & 0x1f) * 255 / 31;
|
|
117
|
+
out[d + 3] = 255;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else if (format === 5) {
|
|
122
|
+
for (let i = 0; i < width * height; i++) {
|
|
123
|
+
const s = i * 4;
|
|
124
|
+
out[s] = pixels[s + 1];
|
|
125
|
+
out[s + 1] = pixels[s + 2];
|
|
126
|
+
out[s + 2] = pixels[s + 3];
|
|
127
|
+
out[s + 3] = alpha ? pixels[s] : 255;
|
|
128
|
+
if (alpha)
|
|
129
|
+
unpremul(s);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else
|
|
133
|
+
throw new Error(`Unsupported lossless bitmap format ${format}`);
|
|
134
|
+
return { width, height, data: out };
|
|
135
|
+
}
|
|
136
|
+
/** Extracts the image stored in a bitmap tag. */
|
|
137
|
+
function extractImage(tag, jpegTables) {
|
|
138
|
+
if (!(tag instanceof tags_js_1.RawTag) || !exports.IMAGE_TAGS.has(tag.code))
|
|
139
|
+
return undefined;
|
|
140
|
+
const id = tag.characterId;
|
|
141
|
+
const r = new reader_js_1.ByteReader(tag.data);
|
|
142
|
+
r.u16();
|
|
143
|
+
switch (tag.code) {
|
|
144
|
+
case tag_codes_js_1.TagCode.DefineBitsLossless:
|
|
145
|
+
case tag_codes_js_1.TagCode.DefineBitsLossless2: {
|
|
146
|
+
const img = decodeLossless(tag);
|
|
147
|
+
return { id, ext: 'png', data: (0, png_js_1.encodePng)(img), width: img.width, height: img.height };
|
|
148
|
+
}
|
|
149
|
+
case tag_codes_js_1.TagCode.DefineBits:
|
|
150
|
+
return { id, ext: 'jpg', data: cleanJpeg(r.rest(), jpegTables) };
|
|
151
|
+
case tag_codes_js_1.TagCode.DefineBitsJPEG2: {
|
|
152
|
+
const d = r.rest();
|
|
153
|
+
const ext = imageType(d);
|
|
154
|
+
return { id, ext, data: ext === 'jpg' ? cleanJpeg(d) : d.slice() };
|
|
155
|
+
}
|
|
156
|
+
case tag_codes_js_1.TagCode.DefineBitsJPEG3:
|
|
157
|
+
case tag_codes_js_1.TagCode.DefineBitsJPEG4: {
|
|
158
|
+
const alphaOffset = r.u32();
|
|
159
|
+
if (tag.code === tag_codes_js_1.TagCode.DefineBitsJPEG4)
|
|
160
|
+
r.u16(); // deblock
|
|
161
|
+
const d = r.bytesView(alphaOffset);
|
|
162
|
+
const ext = imageType(d);
|
|
163
|
+
const result = { id, ext, data: ext === 'jpg' ? cleanJpeg(d) : d.slice() };
|
|
164
|
+
const alphaZ = r.rest();
|
|
165
|
+
if (ext === 'jpg' && alphaZ.length) {
|
|
166
|
+
const dims = jpegSize(result.data);
|
|
167
|
+
if (dims) {
|
|
168
|
+
const a = new Uint8Array((0, node_zlib_1.inflateSync)(alphaZ));
|
|
169
|
+
const rgba = new Uint8Array(dims.width * dims.height * 4);
|
|
170
|
+
for (let i = 0; i < dims.width * dims.height; i++) {
|
|
171
|
+
rgba[i * 4] = rgba[i * 4 + 1] = rgba[i * 4 + 2] = a[i] ?? 255;
|
|
172
|
+
rgba[i * 4 + 3] = 255;
|
|
173
|
+
}
|
|
174
|
+
result.alphaMask = (0, png_js_1.encodePng)({ width: dims.width, height: dims.height, data: rgba });
|
|
175
|
+
result.width = dims.width;
|
|
176
|
+
result.height = dims.height;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
/** Reads width/height from a JPEG's SOF marker. */
|
|
185
|
+
function jpegSize(b) {
|
|
186
|
+
let i = 2;
|
|
187
|
+
while (i + 9 < b.length) {
|
|
188
|
+
if (b[i] !== 0xff) {
|
|
189
|
+
i++;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const marker = b[i + 1];
|
|
193
|
+
if (marker === 0xd8 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) {
|
|
194
|
+
i += 2;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const len = (b[i + 2] << 8) | b[i + 3];
|
|
198
|
+
if (marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {
|
|
199
|
+
return { height: (b[i + 5] << 8) | b[i + 6], width: (b[i + 7] << 8) | b[i + 8] };
|
|
200
|
+
}
|
|
201
|
+
i += 2 + len;
|
|
202
|
+
}
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
/** Builds a DefineBitsLossless2 tag body from an RGBA image. */
|
|
206
|
+
function encodeLossless2(id, img) {
|
|
207
|
+
const { width, height, data } = img;
|
|
208
|
+
const argb = new Uint8Array(width * height * 4);
|
|
209
|
+
for (let i = 0; i < width * height; i++) {
|
|
210
|
+
const a = data[i * 4 + 3];
|
|
211
|
+
const s = i * 4;
|
|
212
|
+
argb[s] = a;
|
|
213
|
+
argb[s + 1] = Math.round((data[s] * a) / 255);
|
|
214
|
+
argb[s + 2] = Math.round((data[s + 1] * a) / 255);
|
|
215
|
+
argb[s + 3] = Math.round((data[s + 2] * a) / 255);
|
|
216
|
+
}
|
|
217
|
+
const w = new writer_js_1.ByteWriter(argb.length / 2 + 16);
|
|
218
|
+
w.u16(id).u8(5).u16(width).u16(height);
|
|
219
|
+
w.bytes(new Uint8Array((0, node_zlib_1.deflateSync)(argb, { level: 9 })));
|
|
220
|
+
return w.toBytes();
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Replaces the bitmap with character `id`. PNG files become
|
|
224
|
+
* DefineBitsLossless2 (exact pixels, alpha); JPEG / GIF files are stored
|
|
225
|
+
* as DefineBitsJPEG2.
|
|
226
|
+
*/
|
|
227
|
+
function replaceImage(swf, id, file) {
|
|
228
|
+
for (const { tag, parent, index } of swf.walkTags()) {
|
|
229
|
+
if (tag.characterId !== id)
|
|
230
|
+
continue;
|
|
231
|
+
if (!exports.IMAGE_TAGS.has(tag.code))
|
|
232
|
+
throw new Error(`Character ${id} is a ${tag.name}, not a bitmap`);
|
|
233
|
+
let next;
|
|
234
|
+
if ((0, png_js_1.isPng)(file))
|
|
235
|
+
next = new tags_js_1.RawTag(tag_codes_js_1.TagCode.DefineBitsLossless2, encodeLossless2(id, (0, png_js_1.decodePng)(file)));
|
|
236
|
+
else {
|
|
237
|
+
const w = new writer_js_1.ByteWriter(file.length + 2);
|
|
238
|
+
w.u16(id).bytes(file);
|
|
239
|
+
next = new tags_js_1.RawTag(tag_codes_js_1.TagCode.DefineBitsJPEG2, w.toBytes());
|
|
240
|
+
}
|
|
241
|
+
next.longHeader = true;
|
|
242
|
+
(parent ? parent.tags : swf.tags)[index] = next;
|
|
243
|
+
return next;
|
|
244
|
+
}
|
|
245
|
+
throw new Error(`No character with id ${id}`);
|
|
246
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface RgbaImage {
|
|
2
|
+
width: number;
|
|
3
|
+
height: number;
|
|
4
|
+
/** Straight (non-premultiplied) RGBA, row-major. */
|
|
5
|
+
data: Uint8Array;
|
|
6
|
+
}
|
|
7
|
+
export declare function crc32(data: Uint8Array, crc?: number): number;
|
|
8
|
+
export declare function isPng(b: Uint8Array): boolean;
|
|
9
|
+
export declare function encodePng(img: RgbaImage, options?: {
|
|
10
|
+
level?: number;
|
|
11
|
+
}): Uint8Array;
|
|
12
|
+
export declare function decodePng(bytes: Uint8Array): RgbaImage;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.crc32 = crc32;
|
|
4
|
+
exports.isPng = isPng;
|
|
5
|
+
exports.encodePng = encodePng;
|
|
6
|
+
exports.decodePng = decodePng;
|
|
7
|
+
/**
|
|
8
|
+
* Minimal PNG encoder / decoder (8-bit RGBA output, all standard colour types
|
|
9
|
+
* with bit depth 8 on input; 1/2/4-bit palette and grayscale also accepted).
|
|
10
|
+
*/
|
|
11
|
+
const node_zlib_1 = require("node:zlib");
|
|
12
|
+
const CRC_TABLE = (() => {
|
|
13
|
+
const t = new Uint32Array(256);
|
|
14
|
+
for (let n = 0; n < 256; n++) {
|
|
15
|
+
let c = n;
|
|
16
|
+
for (let k = 0; k < 8; k++)
|
|
17
|
+
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
18
|
+
t[n] = c >>> 0;
|
|
19
|
+
}
|
|
20
|
+
return t;
|
|
21
|
+
})();
|
|
22
|
+
function crc32(data, crc = 0xffffffff) {
|
|
23
|
+
for (let i = 0; i < data.length; i++)
|
|
24
|
+
crc = CRC_TABLE[(crc ^ data[i]) & 0xff] ^ (crc >>> 8);
|
|
25
|
+
return crc >>> 0;
|
|
26
|
+
}
|
|
27
|
+
const SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
|
|
28
|
+
function isPng(b) {
|
|
29
|
+
return SIGNATURE.every((v, i) => b[i] === v);
|
|
30
|
+
}
|
|
31
|
+
function encodePng(img, options = {}) {
|
|
32
|
+
const { width, height, data } = img;
|
|
33
|
+
const stride = width * 4;
|
|
34
|
+
const raw = new Uint8Array((stride + 1) * height);
|
|
35
|
+
for (let y = 0; y < height; y++) {
|
|
36
|
+
raw[y * (stride + 1)] = 0; // filter: none
|
|
37
|
+
raw.set(data.subarray(y * stride, (y + 1) * stride), y * (stride + 1) + 1);
|
|
38
|
+
}
|
|
39
|
+
const idat = (0, node_zlib_1.deflateSync)(raw, { level: options.level ?? 9 });
|
|
40
|
+
const chunks = [];
|
|
41
|
+
const chunk = (type, body) => {
|
|
42
|
+
const out = new Uint8Array(12 + body.length);
|
|
43
|
+
const dv = new DataView(out.buffer);
|
|
44
|
+
dv.setUint32(0, body.length);
|
|
45
|
+
for (let i = 0; i < 4; i++)
|
|
46
|
+
out[4 + i] = type.charCodeAt(i);
|
|
47
|
+
out.set(body, 8);
|
|
48
|
+
dv.setUint32(8 + body.length, crc32(out.subarray(4, 8 + body.length)) ^ 0xffffffff);
|
|
49
|
+
chunks.push(out);
|
|
50
|
+
};
|
|
51
|
+
const ihdr = new Uint8Array(13);
|
|
52
|
+
const dv = new DataView(ihdr.buffer);
|
|
53
|
+
dv.setUint32(0, width);
|
|
54
|
+
dv.setUint32(4, height);
|
|
55
|
+
ihdr[8] = 8; // bit depth
|
|
56
|
+
ihdr[9] = 6; // RGBA
|
|
57
|
+
chunk('IHDR', ihdr);
|
|
58
|
+
chunk('IDAT', new Uint8Array(idat));
|
|
59
|
+
chunk('IEND', new Uint8Array(0));
|
|
60
|
+
const total = 8 + chunks.reduce((a, c) => a + c.length, 0);
|
|
61
|
+
const out = new Uint8Array(total);
|
|
62
|
+
out.set(SIGNATURE, 0);
|
|
63
|
+
let o = 8;
|
|
64
|
+
for (const c of chunks) {
|
|
65
|
+
out.set(c, o);
|
|
66
|
+
o += c.length;
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
function paeth(a, b, c) {
|
|
71
|
+
const p = a + b - c;
|
|
72
|
+
const pa = Math.abs(p - a);
|
|
73
|
+
const pb = Math.abs(p - b);
|
|
74
|
+
const pc = Math.abs(p - c);
|
|
75
|
+
return pa <= pb && pa <= pc ? a : pb <= pc ? b : c;
|
|
76
|
+
}
|
|
77
|
+
function decodePng(bytes) {
|
|
78
|
+
if (!isPng(bytes))
|
|
79
|
+
throw new Error('Not a PNG file');
|
|
80
|
+
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
81
|
+
let pos = 8;
|
|
82
|
+
let width = 0;
|
|
83
|
+
let height = 0;
|
|
84
|
+
let depth = 8;
|
|
85
|
+
let colorType = 6;
|
|
86
|
+
let interlace = 0;
|
|
87
|
+
let palette;
|
|
88
|
+
let trns;
|
|
89
|
+
const idat = [];
|
|
90
|
+
while (pos + 8 <= bytes.length) {
|
|
91
|
+
const len = dv.getUint32(pos);
|
|
92
|
+
const type = String.fromCharCode(...bytes.subarray(pos + 4, pos + 8));
|
|
93
|
+
const body = bytes.subarray(pos + 8, pos + 8 + len);
|
|
94
|
+
if (type === 'IHDR') {
|
|
95
|
+
width = dv.getUint32(pos + 8);
|
|
96
|
+
height = dv.getUint32(pos + 12);
|
|
97
|
+
depth = body[8];
|
|
98
|
+
colorType = body[9];
|
|
99
|
+
interlace = body[12];
|
|
100
|
+
}
|
|
101
|
+
else if (type === 'PLTE')
|
|
102
|
+
palette = body;
|
|
103
|
+
else if (type === 'tRNS')
|
|
104
|
+
trns = body;
|
|
105
|
+
else if (type === 'IDAT')
|
|
106
|
+
idat.push(body);
|
|
107
|
+
else if (type === 'IEND')
|
|
108
|
+
break;
|
|
109
|
+
pos += 12 + len;
|
|
110
|
+
}
|
|
111
|
+
if (interlace)
|
|
112
|
+
throw new Error('Interlaced PNG files are not supported');
|
|
113
|
+
if (depth === 16)
|
|
114
|
+
throw new Error('16-bit PNG files are not supported');
|
|
115
|
+
const channels = { 0: 1, 2: 3, 3: 1, 4: 2, 6: 4 }[colorType];
|
|
116
|
+
if (!channels)
|
|
117
|
+
throw new Error(`Unsupported PNG colour type ${colorType}`);
|
|
118
|
+
const total = idat.reduce((a, c) => a + c.length, 0);
|
|
119
|
+
const z = new Uint8Array(total);
|
|
120
|
+
let o = 0;
|
|
121
|
+
for (const c of idat) {
|
|
122
|
+
z.set(c, o);
|
|
123
|
+
o += c.length;
|
|
124
|
+
}
|
|
125
|
+
const raw = new Uint8Array((0, node_zlib_1.inflateSync)(z));
|
|
126
|
+
const bpp = Math.max(1, (channels * depth) >> 3);
|
|
127
|
+
const stride = Math.ceil((width * channels * depth) / 8);
|
|
128
|
+
const pixels = new Uint8Array(stride * height);
|
|
129
|
+
let prev = new Uint8Array(stride);
|
|
130
|
+
for (let y = 0; y < height; y++) {
|
|
131
|
+
const filter = raw[y * (stride + 1)];
|
|
132
|
+
const line = raw.subarray(y * (stride + 1) + 1, (y + 1) * (stride + 1));
|
|
133
|
+
const cur = pixels.subarray(y * stride, (y + 1) * stride);
|
|
134
|
+
for (let x = 0; x < stride; x++) {
|
|
135
|
+
const a = x >= bpp ? cur[x - bpp] : 0;
|
|
136
|
+
const b = prev[x];
|
|
137
|
+
const c = x >= bpp ? prev[x - bpp] : 0;
|
|
138
|
+
let v = line[x];
|
|
139
|
+
switch (filter) {
|
|
140
|
+
case 1:
|
|
141
|
+
v += a;
|
|
142
|
+
break;
|
|
143
|
+
case 2:
|
|
144
|
+
v += b;
|
|
145
|
+
break;
|
|
146
|
+
case 3:
|
|
147
|
+
v += (a + b) >> 1;
|
|
148
|
+
break;
|
|
149
|
+
case 4:
|
|
150
|
+
v += paeth(a, b, c);
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
cur[x] = v & 0xff;
|
|
154
|
+
}
|
|
155
|
+
prev = cur;
|
|
156
|
+
}
|
|
157
|
+
const out = new Uint8Array(width * height * 4);
|
|
158
|
+
const sample = (row, i) => {
|
|
159
|
+
if (depth === 8)
|
|
160
|
+
return row[i];
|
|
161
|
+
const bit = i * depth;
|
|
162
|
+
return (row[bit >> 3] >> (8 - depth - (bit & 7))) & ((1 << depth) - 1);
|
|
163
|
+
};
|
|
164
|
+
const scale = depth === 8 ? 1 : 255 / ((1 << depth) - 1);
|
|
165
|
+
for (let y = 0; y < height; y++) {
|
|
166
|
+
const row = pixels.subarray(y * stride, (y + 1) * stride);
|
|
167
|
+
for (let x = 0; x < width; x++) {
|
|
168
|
+
const d = (y * width + x) * 4;
|
|
169
|
+
switch (colorType) {
|
|
170
|
+
case 0: {
|
|
171
|
+
const g = sample(row, x);
|
|
172
|
+
const v = Math.round(g * scale);
|
|
173
|
+
out[d] = out[d + 1] = out[d + 2] = v;
|
|
174
|
+
out[d + 3] = trns && trns.length >= 2 && ((trns[0] << 8) | trns[1]) === g ? 0 : 255;
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
case 2:
|
|
178
|
+
out[d] = row[x * 3];
|
|
179
|
+
out[d + 1] = row[x * 3 + 1];
|
|
180
|
+
out[d + 2] = row[x * 3 + 2];
|
|
181
|
+
out[d + 3] = 255;
|
|
182
|
+
break;
|
|
183
|
+
case 3: {
|
|
184
|
+
const idx = sample(row, x);
|
|
185
|
+
out[d] = palette?.[idx * 3] ?? 0;
|
|
186
|
+
out[d + 1] = palette?.[idx * 3 + 1] ?? 0;
|
|
187
|
+
out[d + 2] = palette?.[idx * 3 + 2] ?? 0;
|
|
188
|
+
out[d + 3] = trns && idx < trns.length ? trns[idx] : 255;
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case 4:
|
|
192
|
+
out[d] = out[d + 1] = out[d + 2] = row[x * 2];
|
|
193
|
+
out[d + 3] = row[x * 2 + 1];
|
|
194
|
+
break;
|
|
195
|
+
case 6:
|
|
196
|
+
out.set(row.subarray(x * 4, x * 4 + 4), d);
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return { width, height, data: out };
|
|
202
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Swf } from '../swf/swf.js';
|
|
2
|
+
import { RawTag, type Tag } from '../swf/tags.js';
|
|
3
|
+
export declare const SOUND_RATES: number[];
|
|
4
|
+
export interface SoundInfo {
|
|
5
|
+
id: number;
|
|
6
|
+
/** 0 native PCM, 1 ADPCM, 2 MP3, 3 PCM LE, 4-6 Nellymoser, 11 Speex. */
|
|
7
|
+
format: number;
|
|
8
|
+
rate: number;
|
|
9
|
+
bits: 8 | 16;
|
|
10
|
+
channels: 1 | 2;
|
|
11
|
+
sampleCount: number;
|
|
12
|
+
data: Uint8Array;
|
|
13
|
+
}
|
|
14
|
+
export declare function parseDefineSound(tag: RawTag): SoundInfo;
|
|
15
|
+
export declare function encodeWav(pcm: Uint8Array, rate: number, channels: number, bits: number): Uint8Array;
|
|
16
|
+
/** Decodes SWF ADPCM to 16-bit little-endian PCM. */
|
|
17
|
+
export declare function decodeAdpcm(data: Uint8Array, channels: number, sampleCount: number): Uint8Array;
|
|
18
|
+
export interface ExtractedSound {
|
|
19
|
+
id: number;
|
|
20
|
+
ext: 'mp3' | 'wav' | 'nelly' | 'speex';
|
|
21
|
+
data: Uint8Array;
|
|
22
|
+
info: Omit<SoundInfo, 'data'>;
|
|
23
|
+
}
|
|
24
|
+
export declare function extractSound(tag: Tag): ExtractedSound | undefined;
|
|
25
|
+
export interface Mp3Info {
|
|
26
|
+
rate: number;
|
|
27
|
+
channels: 1 | 2;
|
|
28
|
+
sampleCount: number;
|
|
29
|
+
/** Offset of the first frame (after ID3v2). */
|
|
30
|
+
start: number;
|
|
31
|
+
end: number;
|
|
32
|
+
}
|
|
33
|
+
export declare function parseMp3(b: Uint8Array): Mp3Info;
|
|
34
|
+
/** Builds a DefineSound body from an MP3 or WAV file. */
|
|
35
|
+
export declare function encodeDefineSound(id: number, file: Uint8Array): Uint8Array;
|
|
36
|
+
/** Replaces DefineSound `id` with an MP3 or WAV file. */
|
|
37
|
+
export declare function replaceSound(swf: Swf, id: number, file: Uint8Array): Tag;
|