This commit is contained in:
1 parent
e85e4d74ea
commit
2e4ed76121
378 files changed
+22404
-20686
No files matched your search
@@ -2,151 +2,151 @@
|
||||
* Low-level SWF file parsing: decompression, tag reading, symbol class.
|
||||
*/
|
||||
|
||||
import { inflateSync } from 'node:zlib'
|
||||
import LZMA from 'lzma'
|
||||
import { inflateSync } from "node:zlib";
|
||||
import LZMA from "lzma";
|
||||
|
||||
// ── SWF Tag Types ──────────────────────────────────────────────────
|
||||
export const TAG_END = 0
|
||||
export const TAG_DEFINE_BITS_LOSSLESS = 20
|
||||
export const TAG_DEFINE_BITS_JPEG2 = 21
|
||||
export const TAG_DEFINE_BITS_JPEG3 = 35
|
||||
export const TAG_DEFINE_BITS_LOSSLESS2 = 36
|
||||
export const TAG_SYMBOL_CLASS = 76
|
||||
export const TAG_DEFINE_BINARY_DATA = 87
|
||||
export const TAG_END = 0;
|
||||
export const TAG_DEFINE_BITS_LOSSLESS = 20;
|
||||
export const TAG_DEFINE_BITS_JPEG2 = 21;
|
||||
export const TAG_DEFINE_BITS_JPEG3 = 35;
|
||||
export const TAG_DEFINE_BITS_LOSSLESS2 = 36;
|
||||
export const TAG_SYMBOL_CLASS = 76;
|
||||
export const TAG_DEFINE_BINARY_DATA = 87;
|
||||
|
||||
// ── Interfaces ─────────────────────────────────────────────────────
|
||||
|
||||
export interface SwfTag {
|
||||
type: number
|
||||
data: Buffer
|
||||
type: number;
|
||||
data: Buffer;
|
||||
}
|
||||
|
||||
export interface SymbolClassResult {
|
||||
/** charID → first symbol name (canonical name) */
|
||||
classNames: Map<number, string>
|
||||
classNames: Map<number, string>;
|
||||
/** symbol name → charID (all symbols including aliases) */
|
||||
symbols: Map<string, number>
|
||||
symbols: Map<string, number>;
|
||||
}
|
||||
|
||||
// ── SWF Header Parsing ─────────────────────────────────────────────
|
||||
|
||||
export function decompressSwf(buffer: Buffer): Buffer {
|
||||
const sig = buffer.toString('ascii', 0, 3)
|
||||
const sig = buffer.toString("ascii", 0, 3);
|
||||
|
||||
if (sig === 'FWS') {
|
||||
return buffer
|
||||
if (sig === "FWS") {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
if (sig === 'CWS') {
|
||||
const header = Buffer.alloc(8)
|
||||
buffer.copy(header, 0, 0, 8)
|
||||
header.write('FWS', 0, 3, 'ascii')
|
||||
const compressed = buffer.subarray(8)
|
||||
const decompressed = inflateSync(compressed)
|
||||
return Buffer.concat([header, decompressed])
|
||||
if (sig === "CWS") {
|
||||
const header = Buffer.alloc(8);
|
||||
buffer.copy(header, 0, 0, 8);
|
||||
header.write("FWS", 0, 3, "ascii");
|
||||
const compressed = buffer.subarray(8);
|
||||
const decompressed = inflateSync(compressed);
|
||||
return Buffer.concat([header, decompressed]);
|
||||
}
|
||||
|
||||
if (sig === 'ZWS') {
|
||||
const fileLength = buffer.readUInt32LE(4)
|
||||
const compressedSize = buffer.readUInt32LE(8)
|
||||
const lzmaData = buffer.subarray(12, 12 + compressedSize)
|
||||
if (sig === "ZWS") {
|
||||
const fileLength = buffer.readUInt32LE(4);
|
||||
const compressedSize = buffer.readUInt32LE(8);
|
||||
const lzmaData = buffer.subarray(12, 12 + compressedSize);
|
||||
|
||||
const props = lzmaData.subarray(0, 5)
|
||||
const compressedPayload = lzmaData.subarray(5)
|
||||
const props = lzmaData.subarray(0, 5);
|
||||
const compressedPayload = lzmaData.subarray(5);
|
||||
|
||||
const uncompressedSize = fileLength - 8
|
||||
const lzmaHeader = Buffer.alloc(13)
|
||||
props.copy(lzmaHeader, 0)
|
||||
lzmaHeader.writeUInt32LE(uncompressedSize, 5)
|
||||
lzmaHeader.writeUInt32LE(0, 9)
|
||||
const uncompressedSize = fileLength - 8;
|
||||
const lzmaHeader = Buffer.alloc(13);
|
||||
props.copy(lzmaHeader, 0);
|
||||
lzmaHeader.writeUInt32LE(uncompressedSize, 5);
|
||||
lzmaHeader.writeUInt32LE(0, 9);
|
||||
|
||||
const lzmaStream = Buffer.concat([lzmaHeader, compressedPayload])
|
||||
const decompressed = Buffer.from(LZMA.decompress(lzmaStream) as number[])
|
||||
const lzmaStream = Buffer.concat([lzmaHeader, compressedPayload]);
|
||||
const decompressed = Buffer.from(LZMA.decompress(lzmaStream) as number[]);
|
||||
|
||||
const header = Buffer.alloc(8)
|
||||
buffer.copy(header, 0, 0, 8)
|
||||
header.write('FWS', 0, 3, 'ascii')
|
||||
return Buffer.concat([header, decompressed])
|
||||
const header = Buffer.alloc(8);
|
||||
buffer.copy(header, 0, 0, 8);
|
||||
header.write("FWS", 0, 3, "ascii");
|
||||
return Buffer.concat([header, decompressed]);
|
||||
}
|
||||
|
||||
throw new Error(`Unknown SWF signature: ${sig}`)
|
||||
throw new Error(`Unknown SWF signature: ${sig}`);
|
||||
}
|
||||
|
||||
function readRect(buf: Buffer, offset: number): number {
|
||||
const nbits = (buf[offset] >> 3) & 0x1f
|
||||
const totalBits = 5 + nbits * 4
|
||||
const totalBytes = Math.ceil(totalBits / 8)
|
||||
return offset + totalBytes
|
||||
const nbits = (buf[offset] >> 3) & 0x1f;
|
||||
const totalBits = 5 + nbits * 4;
|
||||
const totalBytes = Math.ceil(totalBits / 8);
|
||||
return offset + totalBytes;
|
||||
}
|
||||
|
||||
// ── SWF Tag Reader ─────────────────────────────────────────────────
|
||||
|
||||
export function parseSwfTags(swfBuffer: Buffer): SwfTag[] {
|
||||
const tags: SwfTag[] = []
|
||||
const tags: SwfTag[] = [];
|
||||
|
||||
let offset = 8
|
||||
offset = readRect(swfBuffer, offset)
|
||||
offset += 4
|
||||
let offset = 8;
|
||||
offset = readRect(swfBuffer, offset);
|
||||
offset += 4;
|
||||
|
||||
while (offset < swfBuffer.length) {
|
||||
if (offset + 2 > swfBuffer.length) break
|
||||
if (offset + 2 > swfBuffer.length) break;
|
||||
|
||||
const tagCodeAndLength = swfBuffer.readUInt16LE(offset)
|
||||
offset += 2
|
||||
const tagCodeAndLength = swfBuffer.readUInt16LE(offset);
|
||||
offset += 2;
|
||||
|
||||
const tagType = (tagCodeAndLength >> 6) & 0x3ff
|
||||
let tagLength = tagCodeAndLength & 0x3f
|
||||
const tagType = (tagCodeAndLength >> 6) & 0x3ff;
|
||||
let tagLength = tagCodeAndLength & 0x3f;
|
||||
|
||||
if (tagLength === 0x3f) {
|
||||
if (offset + 4 > swfBuffer.length) break
|
||||
tagLength = swfBuffer.readUInt32LE(offset)
|
||||
offset += 4
|
||||
if (offset + 4 > swfBuffer.length) break;
|
||||
tagLength = swfBuffer.readUInt32LE(offset);
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
if (tagType === TAG_END) break
|
||||
if (offset + tagLength > swfBuffer.length) break
|
||||
if (tagType === TAG_END) break;
|
||||
if (offset + tagLength > swfBuffer.length) break;
|
||||
|
||||
const tagData = swfBuffer.subarray(offset, offset + tagLength)
|
||||
tags.push({ type: tagType, data: Buffer.from(tagData) })
|
||||
offset += tagLength
|
||||
const tagData = swfBuffer.subarray(offset, offset + tagLength);
|
||||
tags.push({ type: tagType, data: Buffer.from(tagData) });
|
||||
offset += tagLength;
|
||||
}
|
||||
|
||||
return tags
|
||||
return tags;
|
||||
}
|
||||
|
||||
// ── SymbolClass Parser ─────────────────────────────────────────────
|
||||
|
||||
export function parseSymbolClass(data: Buffer): SymbolClassResult {
|
||||
const classNames = new Map<number, string>()
|
||||
const symbols = new Map<string, number>()
|
||||
let offset = 0
|
||||
const classNames = new Map<number, string>();
|
||||
const symbols = new Map<string, number>();
|
||||
let offset = 0;
|
||||
|
||||
const numSymbols = data.readUInt16LE(offset)
|
||||
offset += 2
|
||||
const numSymbols = data.readUInt16LE(offset);
|
||||
offset += 2;
|
||||
|
||||
for (let i = 0; i < numSymbols; i++) {
|
||||
if (offset + 2 > data.length) break
|
||||
const charId = data.readUInt16LE(offset)
|
||||
offset += 2
|
||||
if (offset + 2 > data.length) break;
|
||||
const charId = data.readUInt16LE(offset);
|
||||
offset += 2;
|
||||
|
||||
const strStart = offset
|
||||
while (offset < data.length && data[offset] !== 0) offset++
|
||||
const name = data.toString('utf-8', strStart, offset)
|
||||
offset++
|
||||
const strStart = offset;
|
||||
while (offset < data.length && data[offset] !== 0) offset++;
|
||||
const name = data.toString("utf-8", strStart, offset);
|
||||
offset++;
|
||||
|
||||
symbols.set(name, charId)
|
||||
symbols.set(name, charId);
|
||||
if (!classNames.has(charId)) {
|
||||
classNames.set(charId, name)
|
||||
classNames.set(charId, name);
|
||||
}
|
||||
}
|
||||
|
||||
return { classNames, symbols }
|
||||
return { classNames, symbols };
|
||||
}
|
||||
|
||||
// ── DefineBinaryData Parser ────────────────────────────────────────
|
||||
|
||||
export function parseBinaryData(data: Buffer): { id: number; content: Buffer } {
|
||||
const id = data.readUInt16LE(0)
|
||||
const content = data.subarray(6)
|
||||
return { id, content }
|
||||
const id = data.readUInt16LE(0);
|
||||
const content = data.subarray(6);
|
||||
return { id, content };
|
||||
}
|
||||
Reference in new issue
Block a user