@chicowall/grf-loader 1.0.12 → 1.0.13
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/README.md +248 -37
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +205 -5
- package/dist/index.d.ts +205 -5
- package/dist/index.global.js +6 -1
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/grf-base.ts","../src/des.ts","../src/grf-browser.ts","../src/grf-node.ts","../src/buffer-pool.ts"],"sourcesContent":["export {GrfBrowser} from './grf-browser';\nexport {GrfNode} from './grf-node';\nexport type {TFileEntry} from './grf-base';\nexport {bufferPool} from './buffer-pool';\n","import pako from 'pako';\nimport jDataview from 'jdataview';\nimport {decodeFull, decodeHeader} from './des';\n\nexport interface TFileEntry {\n type: number;\n offset: number;\n realSize: number;\n compressedSize: number;\n lengthAligned: number;\n}\n\nconst FILELIST_TYPE_FILE = 0x01;\nconst FILELIST_TYPE_ENCRYPT_MIXED = 0x02; // encryption mode 0 (header DES + periodic DES/shuffle)\nconst FILELIST_TYPE_ENCRYPT_HEADER = 0x04; // encryption mode 1 (header DES only)\n\nconst HEADER_SIGNATURE = 'Master of Magic';\nconst HEADER_SIZE = 46;\nconst FILE_TABLE_SIZE = Uint32Array.BYTES_PER_ELEMENT * 2;\n\nexport abstract class GrfBase<T> {\n public version = 0x200;\n public fileCount = 0;\n public loaded = false;\n public files = new Map<string, TFileEntry>();\n private fileTableOffset = 0;\n private cache = new Map<string, Uint8Array>();\n private cacheMaxSize = 50; // Max 50 files cached\n private cacheOrder: string[] = []; // LRU tracking\n\n constructor(private fd: T) {}\n\n abstract getStreamBuffer(\n fd: T,\n offset: number,\n length: number\n ): Promise<Uint8Array>;\n\n public async getStreamReader(\n offset: number,\n length: number\n ): Promise<jDataview> {\n const buffer = await this.getStreamBuffer(this.fd, offset, length);\n\n return new jDataview(buffer, void 0, void 0, true);\n }\n\n public async load(): Promise<void> {\n if (!this.loaded) {\n await this.parseHeader();\n await this.parseFileList();\n this.loaded = true;\n }\n }\n\n private async parseHeader(): Promise<void> {\n const reader = await this.getStreamReader(0, HEADER_SIZE);\n\n const signature = reader.getString(15);\n if (signature !== HEADER_SIGNATURE) {\n throw new Error('Not a GRF file (invalid signature)');\n }\n\n reader.skip(15);\n this.fileTableOffset = reader.getUint32() + HEADER_SIZE;\n const reservedFiles = reader.getUint32();\n this.fileCount = reader.getUint32() - reservedFiles - 7;\n this.version = reader.getUint32();\n\n if (this.version !== 0x200) {\n throw new Error(`Unsupported version \"0x${this.version.toString(16)}\"`);\n }\n }\n\n private async parseFileList(): Promise<void> {\n // Read table list, stored information\n const reader = await this.getStreamReader(\n this.fileTableOffset,\n FILE_TABLE_SIZE\n );\n const compressedSize = reader.getUint32();\n const realSize = reader.getUint32();\n\n // Load the chunk and uncompress it\n const compressed = await this.getStreamBuffer(\n this.fd,\n this.fileTableOffset + FILE_TABLE_SIZE,\n compressedSize\n );\n\n const data = pako.inflate(compressed);\n\n // Optimized version using TextDecoder (5-10x faster than String.fromCharCode)\n const decoder = new TextDecoder('utf-8');\n\n for (let i = 0, p = 0; i < this.fileCount; ++i) {\n // Find null terminator\n let endPos = p;\n while (data[endPos] !== 0 && endPos < data.length) {\n endPos++;\n }\n\n // Decode filename using TextDecoder (much faster)\n const filename = decoder.decode(data.subarray(p, endPos));\n\n p = endPos + 1;\n\n // prettier-ignore\n const entry: TFileEntry = {\n compressedSize: data[p++] | (data[p++] << 8) | (data[p++] << 16) | (data[p++] << 24),\n lengthAligned: data[p++] | (data[p++] << 8) | (data[p++] << 16) | (data[p++] << 24),\n realSize: data[p++] | (data[p++] << 8) | (data[p++] << 16) | (data[p++] << 24),\n type: data[p++],\n offset: (data[p++] | (data[p++] << 8) | (data[p++] << 16) | (data[p++] << 24)) >>> 0\n };\n\n // Not a file (folder ?)\n if (entry.type & FILELIST_TYPE_FILE) {\n this.files.set(filename, entry);\n }\n }\n }\n\n private decodeEntry(data: Uint8Array, entry: TFileEntry): Uint8Array {\n // Decode the file\n if (entry.type & FILELIST_TYPE_ENCRYPT_MIXED) {\n decodeFull(data, entry.lengthAligned, entry.compressedSize);\n } else if (entry.type & FILELIST_TYPE_ENCRYPT_HEADER) {\n decodeHeader(data, entry.lengthAligned);\n }\n\n // No compression\n if (entry.realSize === entry.compressedSize) {\n return data;\n }\n\n // Uncompress\n return pako.inflate(data);\n }\n\n private addToCache(filename: string, data: Uint8Array): void {\n // Remove oldest if cache is full\n if (this.cacheOrder.length >= this.cacheMaxSize) {\n const oldest = this.cacheOrder.shift();\n if (oldest) {\n this.cache.delete(oldest);\n }\n }\n\n // Add to cache\n this.cache.set(filename, data);\n this.cacheOrder.push(filename);\n }\n\n private getFromCache(filename: string): Uint8Array | undefined {\n const cached = this.cache.get(filename);\n if (cached) {\n // Move to end (most recently used)\n const index = this.cacheOrder.indexOf(filename);\n if (index > -1) {\n this.cacheOrder.splice(index, 1);\n this.cacheOrder.push(filename);\n }\n }\n return cached;\n }\n\n public clearCache(): void {\n this.cache.clear();\n this.cacheOrder = [];\n }\n\n public async getFile(\n filename: string\n ): Promise<{data: null | Uint8Array; error: null | string}> {\n if (!this.loaded) {\n return Promise.resolve({data: null, error: 'GRF not loaded yet'});\n }\n\n const path = filename;\n\n // Not exists\n if (!this.files.has(path)) {\n return Promise.resolve({data: null, error: `File \"${path}\" not found`});\n }\n\n // Check cache first\n const cached = this.getFromCache(path);\n if (cached) {\n return Promise.resolve({data: cached, error: null});\n }\n\n const entry = this.files.get(path);\n\n if (!entry) {\n return { data: null, error: `File \"${path}\" not found` };\n }\n\n const data = await this.getStreamBuffer(\n this.fd,\n entry.offset + HEADER_SIZE,\n entry.lengthAligned\n );\n\n try {\n const result = this.decodeEntry(data, entry);\n\n // Add to cache\n this.addToCache(path, result);\n\n return Promise.resolve({data: result, error: null});\n } catch (error) {\n const message =\n error instanceof Error ? error.message : String(error);\n return { data: null, error: message };\n }\n }\n}\n","/**\n * Ragnarok Online DES decoder implementation\n * It's a custom one with some alterations\n */\nexport {decodeFull, decodeHeader};\n\nconst mask = new Uint8Array([0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01]);\nconst tmp = new Uint8Array(8);\nconst tmp2 = new Uint8Array(8);\nconst clean = new Uint8Array(8);\n\n// prettier-ignore\nconst initialPermutationTable = new Uint8Array([\n 58, 50, 42, 34, 26, 18, 10, 2,\n 60, 52, 44, 36, 28, 20, 12, 4,\n 62, 54, 46, 38, 30, 22, 14, 6,\n 64, 56, 48, 40, 32, 24, 16, 8,\n 57, 49, 41, 33, 25, 17, 9, 1,\n 59, 51, 43, 35, 27, 19, 11, 3,\n 61, 53, 45, 37, 29, 21, 13, 5,\n 63, 55, 47, 39, 31, 23, 15, 7\n]);\n\n// prettier-ignore\nconst finalPermutationTable = new Uint8Array([\n 40, 8, 48, 16, 56, 24, 64, 32,\n 39, 7, 47, 15, 55, 23, 63, 31,\n 38, 6, 46, 14, 54, 22, 62, 30,\n 37, 5, 45, 13, 53, 21, 61, 29,\n 36, 4, 44, 12, 52, 20, 60, 28,\n 35, 3, 43, 11, 51, 19, 59, 27,\n 34, 2, 42, 10, 50, 18, 58, 26,\n 33, 1, 41, 9, 49, 17, 57, 25\n]);\n\n// prettier-ignore\nconst transpositionTable = new Uint8Array([\n 16, 7, 20, 21,\n 29, 12, 28, 17,\n 1, 15, 23, 26,\n 5, 18, 31, 10,\n 2, 8, 24, 14,\n 32, 27, 3, 9,\n 19, 13, 30, 6,\n 22, 11, 4, 25\n]);\n\n// prettier-ignore\nconst substitutionBoxTable = [\n new Uint8Array([\n 0xef, 0x03, 0x41, 0xfd, 0xd8, 0x74, 0x1e, 0x47, 0x26, 0xef, 0xfb, 0x22, 0xb3, 0xd8, 0x84, 0x1e,\n 0x39, 0xac, 0xa7, 0x60, 0x62, 0xc1, 0xcd, 0xba, 0x5c, 0x96, 0x90, 0x59, 0x05, 0x3b, 0x7a, 0x85,\n 0x40, 0xfd, 0x1e, 0xc8, 0xe7, 0x8a, 0x8b, 0x21, 0xda, 0x43, 0x64, 0x9f, 0x2d, 0x14, 0xb1, 0x72,\n 0xf5, 0x5b, 0xc8, 0xb6, 0x9c, 0x37, 0x76, 0xec, 0x39, 0xa0, 0xa3, 0x05, 0x52, 0x6e, 0x0f, 0xd9 \n ]),\n new Uint8Array([\n 0xa7, 0xdd, 0x0d, 0x78, 0x9e, 0x0b, 0xe3, 0x95, 0x60, 0x36, 0x36, 0x4f, 0xf9, 0x60, 0x5a, 0xa3,\n 0x11, 0x24, 0xd2, 0x87, 0xc8, 0x52, 0x75, 0xec, 0xbb, 0xc1, 0x4c, 0xba, 0x24, 0xfe, 0x8f, 0x19,\n 0xda, 0x13, 0x66, 0xaf, 0x49, 0xd0, 0x90, 0x06, 0x8c, 0x6a, 0xfb, 0x91, 0x37, 0x8d, 0x0d, 0x78,\n 0xbf, 0x49, 0x11, 0xf4, 0x23, 0xe5, 0xce, 0x3b, 0x55, 0xbc, 0xa2, 0x57, 0xe8, 0x22, 0x74, 0xce\n ]),\n new Uint8Array([\n 0x2c, 0xea, 0xc1, 0xbf, 0x4a, 0x24, 0x1f, 0xc2, 0x79, 0x47, 0xa2, 0x7c, 0xb6, 0xd9, 0x68, 0x15,\n 0x80, 0x56, 0x5d, 0x01, 0x33, 0xfd, 0xf4, 0xae, 0xde, 0x30, 0x07, 0x9b, 0xe5, 0x83, 0x9b, 0x68,\n 0x49, 0xb4, 0x2e, 0x83, 0x1f, 0xc2, 0xb5, 0x7c, 0xa2, 0x19, 0xd8, 0xe5, 0x7c, 0x2f, 0x83, 0xda,\n 0xf7, 0x6b, 0x90, 0xfe, 0xc4, 0x01, 0x5a, 0x97, 0x61, 0xa6, 0x3d, 0x40, 0x0b, 0x58, 0xe6, 0x3d\n ]),\n new Uint8Array([\n 0x4d, 0xd1, 0xb2, 0x0f, 0x28, 0xbd, 0xe4, 0x78, 0xf6, 0x4a, 0x0f, 0x93, 0x8b, 0x17, 0xd1, 0xa4,\n 0x3a, 0xec, 0xc9, 0x35, 0x93, 0x56, 0x7e, 0xcb, 0x55, 0x20, 0xa0, 0xfe, 0x6c, 0x89, 0x17, 0x62,\n 0x17, 0x62, 0x4b, 0xb1, 0xb4, 0xde, 0xd1, 0x87, 0xc9, 0x14, 0x3c, 0x4a, 0x7e, 0xa8, 0xe2, 0x7d,\n 0xa0, 0x9f, 0xf6, 0x5c, 0x6a, 0x09, 0x8d, 0xf0, 0x0f, 0xe3, 0x53, 0x25, 0x95, 0x36, 0x28, 0xcb\n ])\n];\n\n/**\n * Initial permutation (IP).\n */\nfunction initialPermutation(src: Uint8Array, index: number): void {\n for (let i = 0; i < 64; ++i) {\n const j = initialPermutationTable[i] - 1;\n if (src[index + ((j >> 3) & 7)] & mask[j & 7]) {\n tmp[(i >> 3) & 7] |= mask[i & 7];\n }\n }\n\n src.set(tmp, index);\n tmp.set(clean);\n}\n\n/**\n * Final permutation (IP^-1).\n */\nfunction finalPermutation(src: Uint8Array, index: number): void {\n for (let i = 0; i < 64; ++i) {\n const j = finalPermutationTable[i] - 1;\n if (src[index + ((j >> 3) & 7)] & mask[j & 7]) {\n tmp[(i >> 3) & 7] |= mask[i & 7];\n }\n }\n\n src.set(tmp, index);\n tmp.set(clean);\n}\n\n/**\n * Transposition (P-BOX).\n */\nfunction transposition(src: Uint8Array, index: number): void {\n for (let i = 0; i < 32; ++i) {\n const j = transpositionTable[i] - 1;\n if (src[index + (j >> 3)] & mask[j & 7]) {\n tmp[(i >> 3) + 4] |= mask[i & 7];\n }\n }\n\n src.set(tmp, index);\n tmp.set(clean);\n}\n\n/**\n * Expansion (E).\n * Expands upper four 8-bits (32b) into eight 6-bits (48b).\n */\nfunction expansion(src: Uint8Array, index: number): void {\n tmp[0] = ((src[index + 7] << 5) | (src[index + 4] >> 3)) & 0x3f; // ..0 vutsr\n tmp[1] = ((src[index + 4] << 1) | (src[index + 5] >> 7)) & 0x3f; // ..srqpo n\n tmp[2] = ((src[index + 4] << 5) | (src[index + 5] >> 3)) & 0x3f; // ..o nmlkj\n tmp[3] = ((src[index + 5] << 1) | (src[index + 6] >> 7)) & 0x3f; // ..kjihg f\n tmp[4] = ((src[index + 5] << 5) | (src[index + 6] >> 3)) & 0x3f; // ..g fedcb\n tmp[5] = ((src[index + 6] << 1) | (src[index + 7] >> 7)) & 0x3f; // ..cba98 7\n tmp[6] = ((src[index + 6] << 5) | (src[index + 7] >> 3)) & 0x3f; // ..8 76543\n tmp[7] = ((src[index + 7] << 1) | (src[index + 4] >> 7)) & 0x3f; // ..43210 v\n\n src.set(tmp, index);\n tmp.set(clean);\n}\n\n/**\n * Substitution boxes (S-boxes).\n * NOTE: This implementation was optimized to process two nibbles in one step (twice as fast).\n */\nfunction substitutionBox(src: Uint8Array, index: number): void {\n for (let i = 0; i < 4; ++i) {\n tmp[i] =\n (substitutionBoxTable[i][src[i * 2 + 0 + index]] & 0xf0) |\n (substitutionBoxTable[i][src[i * 2 + 1 + index]] & 0x0f);\n }\n\n src.set(tmp, index);\n tmp.set(clean);\n}\n\n/**\n * DES round function.\n * XORs src[0..3] with TP(SBOX(E(src[4..7]))).\n */\nfunction roundFunction(src: Uint8Array, index: number): void {\n for (let i = 0; i < 8; i++) {\n tmp2[i] = src[index + i];\n }\n\n expansion(tmp2, 0);\n substitutionBox(tmp2, 0);\n transposition(tmp2, 0);\n\n src[index + 0] ^= tmp2[4];\n src[index + 1] ^= tmp2[5];\n src[index + 2] ^= tmp2[6];\n src[index + 3] ^= tmp2[7];\n}\n\n/**\n * DEcrypt a block\n */\nfunction decryptBlock(src: Uint8Array, index: number): void {\n initialPermutation(src, index);\n roundFunction(src, index);\n finalPermutation(src, index);\n}\n\n/**\n * Decode the whole file\n */\nfunction decodeFull(\n src: Uint8Array,\n length: number,\n entryLength: number\n): void {\n // compute number of digits of the entry length\n const digits = entryLength.toString().length;\n\n // choose size of gap between two encrypted blocks\n // digits: 0 1 2 3 4 5 6 7 8 9 ...\n // cycle: 1 1 1 4 5 14 15 22 23 24 ...\n const cycle =\n digits < 3\n ? 1\n : digits < 5\n ? digits + 1\n : digits < 7\n ? digits + 9\n : digits + 15;\n\n const nblocks = length >> 3;\n\n // first 20 blocks are all des-encrypted\n for (let i = 0; i < 20 && i < nblocks; ++i) {\n decryptBlock(src, i * 8);\n }\n\n for (let i = 20, j = -1; i < nblocks; ++i) {\n // decrypt block\n if (i % cycle === 0) {\n decryptBlock(src, i * 8);\n continue;\n }\n\n // de-shuffle block\n if (++j && j % 7 === 0) {\n shuffleDec(src, i * 8);\n }\n }\n}\n\n/**\n * Decode only the header\n */\nfunction decodeHeader(src: Uint8Array, length: number): void {\n const count = length >> 3;\n\n // first 20 blocks are all des-encrypted\n for (let i = 0; i < 20 && i < count; ++i) {\n decryptBlock(src, i * 8);\n }\n\n // the rest is plaintext, done.\n}\n\n/**\n * Shuffle decode\n */\nfunction shuffleDec(src: Uint8Array, index: number) {\n tmp[0] = src[index + 3];\n tmp[1] = src[index + 4];\n tmp[2] = src[index + 6];\n tmp[3] = src[index + 0];\n tmp[4] = src[index + 1];\n tmp[5] = src[index + 2];\n tmp[6] = src[index + 5];\n tmp[7] = shuffleDecTable[src[index + 7]];\n\n src.set(tmp, index);\n tmp.set(clean);\n}\n\n/**\n * GRF substitution table\n */\nconst shuffleDecTable = (() => {\n // prettier-ignore\n const list = new Uint8Array([\n 0x00, 0x2b, 0x6c, 0x80, 0x01, 0x68, 0x48,\n 0x77, 0x60, 0xff, 0xb9, 0xc0, 0xfe, 0xeb\n ]);\n\n const out = new Uint8Array(Array.from({length: 256}, (_, k) => k));\n const count = list.length;\n\n for (let i = 0; i < count; i += 2) {\n out[list[i + 0]] = list[i + 1];\n out[list[i + 1]] = list[i + 0];\n }\n\n return out;\n})();\n","import jDataview from 'jdataview';\nimport {GrfBase} from './grf-base';\n\n/**\n * Using this Browser, we work from a File or Blob object.\n * We are use the FileReader API to read only some part of the file to avoid\n * loading 2 gigas into memory\n */\nexport class GrfBrowser extends GrfBase<File | Blob> {\n public async getStreamBuffer(\n buffer: File | Blob,\n offset: number,\n length: number\n ): Promise<Uint8Array> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onerror = reject;\n reader.onload = () =>\n resolve(new Uint8Array(reader.result as ArrayBuffer));\n reader.readAsArrayBuffer(buffer.slice(offset, offset + length));\n });\n }\n}\n","// src/grf-node.ts\nimport { fstatSync, read as readCallback } from 'fs';\nimport { promisify } from 'util';\nimport { GrfBase } from './grf-base';\nimport { bufferPool } from './buffer-pool';\n\nconst readAsync = promisify(readCallback);\n\nexport class GrfNode extends GrfBase<number> {\n private useBufferPool: boolean;\n\n constructor(fd: number, options?: { useBufferPool?: boolean }) {\n super(fd);\n\n this.useBufferPool = options?.useBufferPool ?? true;\n\n // Na nossa API, apenas FDs para arquivos regulares são válidos.\n // fstatSync lança erro se o descritor não existir ou não for arquivo.\n try {\n const stat = fstatSync(fd);\n if (!stat.isFile()) {\n throw new Error('GRFNode: file descriptor must point to a regular file');\n }\n } catch {\n // Converte em mensagem clara para o usuário\n throw new Error('GRFNode: invalid file descriptor');\n }\n }\n\n public async getStreamBuffer(\n fd: number,\n offset: number,\n length: number\n ): Promise<Uint8Array> {\n // Use buffer pool for better performance\n const buffer = this.useBufferPool\n ? bufferPool.acquire(length)\n : Buffer.allocUnsafe(length);\n\n const { bytesRead } = await readAsync(fd, buffer, 0, length, offset);\n\n if (bytesRead !== length) {\n // Release buffer back to pool if read failed\n if (this.useBufferPool) {\n bufferPool.release(buffer);\n }\n // ERRO TYPE: GRFNode: unexpected EOF\n throw new Error('Not a GRF file (invalid signature)');\n }\n\n return buffer;\n }\n}\n","/**\n * Simple buffer pool for reducing GC pressure\n * Pools buffers of common sizes for reuse\n */\n\ninterface PoolEntry {\n buffer: Buffer;\n inUse: boolean;\n}\n\nclass BufferPool {\n private pools = new Map<number, PoolEntry[]>();\n private maxPoolSize = 10;\n\n // Common buffer sizes to pool (in bytes)\n private readonly poolSizes = [\n 1024, // 1KB\n 4096, // 4KB\n 8192, // 8KB\n 16384, // 16KB\n 32768, // 32KB\n 65536, // 64KB\n 131072, // 128KB\n 262144, // 256KB\n ];\n\n constructor() {\n // Initialize pools for common sizes\n for (const size of this.poolSizes) {\n this.pools.set(size, []);\n }\n }\n\n /**\n * Get appropriate pool size for requested length\n */\n private getPoolSize(length: number): number | null {\n for (const size of this.poolSizes) {\n if (length <= size) {\n return size;\n }\n }\n return null; // Too large, don't pool\n }\n\n /**\n * Acquire a buffer from the pool or create new one\n */\n acquire(length: number): Buffer {\n const poolSize = this.getPoolSize(length);\n\n // Don't pool large buffers\n if (poolSize === null) {\n return Buffer.allocUnsafe(length);\n }\n\n const pool = this.pools.get(poolSize);\n\n if (pool) {\n // Try to find available buffer\n const available = pool.find(entry => !entry.inUse);\n\n if (available) {\n available.inUse = true;\n return available.buffer.subarray(0, length);\n }\n\n // Pool is full or all in use, create new if pool not maxed\n if (pool.length < this.maxPoolSize) {\n const buffer = Buffer.allocUnsafe(poolSize);\n pool.push({ buffer, inUse: true });\n return buffer.subarray(0, length);\n }\n }\n\n // Fallback: create non-pooled buffer\n return Buffer.allocUnsafe(length);\n }\n\n /**\n * Release a buffer back to the pool\n */\n release(buffer: Buffer): void {\n const actualSize = buffer.buffer.byteLength;\n const pool = this.pools.get(actualSize);\n\n if (pool) {\n const entry = pool.find(e => e.buffer === buffer || e.buffer.buffer === buffer.buffer);\n if (entry) {\n entry.inUse = false;\n }\n }\n }\n\n /**\n * Clear all pools\n */\n clear(): void {\n for (const pool of this.pools.values()) {\n pool.length = 0;\n }\n }\n\n /**\n * Get pool statistics\n */\n stats(): { size: number; total: number; inUse: number }[] {\n const stats: { size: number; total: number; inUse: number }[] = [];\n\n for (const [size, pool] of this.pools.entries()) {\n stats.push({\n size,\n total: pool.length,\n inUse: pool.filter(e => e.inUse).length\n });\n }\n\n return stats;\n }\n}\n\n// Export singleton instance\nexport const bufferPool = new BufferPool();\n"],"mappings":"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","names":["index_exports","__export","GrfBrowser","GrfNode","bufferPool","__toCommonJS","import_pako","import_jdataview","mask","tmp","tmp2","clean","initialPermutationTable","finalPermutationTable","transpositionTable","substitutionBoxTable","initialPermutation","src","index","i","j","finalPermutation","transposition","expansion","substitutionBox","roundFunction","decryptBlock","decodeFull","length","entryLength","digits","cycle","nblocks","shuffleDec","decodeHeader","count","shuffleDecTable","list","out","_","k","FILELIST_TYPE_FILE","FILELIST_TYPE_ENCRYPT_MIXED","FILELIST_TYPE_ENCRYPT_HEADER","HEADER_SIGNATURE","HEADER_SIZE","FILE_TABLE_SIZE","GrfBase","fd","offset","length","buffer","jDataview","reader","reservedFiles","compressedSize","realSize","compressed","data","pako","decoder","i","p","endPos","filename","entry","decodeFull","decodeHeader","oldest","cached","index","path","result","error","GrfBrowser","GrfBase","buffer","offset","length","resolve","reject","reader","import_fs","import_util","BufferPool","size","length","poolSize","pool","available","entry","buffer","actualSize","e","stats","bufferPool","readAsync","readCallback","GrfNode","GrfBase","fd","options","offset","length","buffer","bufferPool","bytesRead"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/grf-base.ts","../src/des.ts","../src/decoder.ts","../src/grf-browser.ts","../src/grf-node.ts","../src/buffer-pool.ts"],"sourcesContent":["export {GrfBrowser} from './grf-browser';\nexport {GrfNode, GrfNodeOptions} from './grf-node';\nexport type {\n TFileEntry,\n FilenameEncoding,\n GrfOptions,\n FindOptions,\n ResolveResult,\n GrfStats\n} from './grf-base';\nexport {GrfError, GRF_ERROR_CODES} from './grf-base';\nexport {bufferPool} from './buffer-pool';\n\n// Encoding utilities\nexport {\n isMojibake,\n fixMojibake,\n toMojibake,\n normalizeFilename,\n normalizePath as normalizeEncodingPath,\n countBadChars,\n countC1ControlChars,\n countReplacementChars,\n hasIconvLite\n} from './decoder';\n","import pako from 'pako';\nimport jDataview from 'jdataview';\nimport {decodeFull, decodeHeader} from './des';\nimport {\n decodeBytes,\n detectBestKoreanEncoding,\n countBadChars,\n countReplacementChars,\n countC1ControlChars,\n hasIconvLite\n} from './decoder';\n\n// ============================================================================\n// Types and Interfaces\n// ============================================================================\n\nexport interface TFileEntry {\n type: number;\n offset: number;\n realSize: number;\n compressedSize: number;\n lengthAligned: number;\n /** Raw filename bytes for re-decoding if needed */\n rawNameBytes?: Uint8Array;\n}\n\n/** Supported filename encodings */\nexport type FilenameEncoding = 'utf-8' | 'euc-kr' | 'cp949' | 'latin1' | 'auto';\n\n/** GRF loader options */\nexport interface GrfOptions {\n /** Encoding for filenames (default: 'auto') */\n filenameEncoding?: FilenameEncoding;\n /** Threshold for auto-detection: if % of U+FFFD exceeds this, try Korean encodings (default: 0.01 = 1%) */\n autoDetectThreshold?: number;\n /** Maximum uncompressed size per file in bytes (default: 256MB) */\n maxFileUncompressedBytes?: number;\n /** Maximum total entries allowed (default: 500000) */\n maxEntries?: number;\n}\n\n/** Search/find options */\nexport interface FindOptions {\n /** Filter by file extension (without dot, e.g., 'spr', 'act') */\n ext?: string;\n /** Filter by substring in path */\n contains?: string;\n /** Filter by path ending */\n endsWith?: string;\n /** Filter by regex pattern */\n regex?: RegExp;\n /** Maximum results to return (default: unlimited) */\n limit?: number;\n}\n\n/** Result of path resolution */\nexport interface ResolveResult {\n status: 'found' | 'not_found' | 'ambiguous';\n /** The exact matched path (if found) */\n matchedPath?: string;\n /** All candidate paths (if ambiguous) */\n candidates?: string[];\n}\n\n/** GRF statistics */\nexport interface GrfStats {\n /** Total file count */\n fileCount: number;\n /** Number of filenames with replacement character (U+FFFD) */\n badNameCount: number;\n /** Number of normalized key collisions */\n collisionCount: number;\n /** Extension statistics: ext -> count */\n extensionStats: Map<string, number>;\n /** Detected encoding used */\n detectedEncoding: FilenameEncoding;\n}\n\n// ============================================================================\n// Error Codes\n// ============================================================================\n\nexport const GRF_ERROR_CODES = {\n INVALID_MAGIC: 'GRF_INVALID_MAGIC',\n UNSUPPORTED_VERSION: 'GRF_UNSUPPORTED_VERSION',\n NOT_LOADED: 'GRF_NOT_LOADED',\n FILE_NOT_FOUND: 'GRF_FILE_NOT_FOUND',\n AMBIGUOUS_PATH: 'GRF_AMBIGUOUS_PATH',\n DECOMPRESS_FAIL: 'GRF_DECOMPRESS_FAIL',\n CORRUPT_TABLE: 'GRF_CORRUPT_TABLE',\n LIMIT_EXCEEDED: 'GRF_LIMIT_EXCEEDED',\n INVALID_OFFSET: 'GRF_INVALID_OFFSET',\n DECRYPT_REQUIRED: 'GRF_DECRYPT_REQUIRED',\n} as const;\n\nexport class GrfError extends Error {\n constructor(\n public code: keyof typeof GRF_ERROR_CODES,\n message: string,\n public context?: Record<string, unknown>\n ) {\n super(message);\n this.name = 'GrfError';\n }\n}\n\nconst FILELIST_TYPE_FILE = 0x01;\nconst FILELIST_TYPE_ENCRYPT_MIXED = 0x02; // encryption mode 0 (header DES + periodic DES/shuffle)\nconst FILELIST_TYPE_ENCRYPT_HEADER = 0x04; // encryption mode 1 (header DES only)\n\nconst HEADER_SIGNATURE = 'Master of Magic';\nconst HEADER_SIZE = 46;\nconst FILE_TABLE_SIZE = Uint32Array.BYTES_PER_ELEMENT * 2;\n\n// Default limits\nconst DEFAULT_MAX_FILE_UNCOMPRESSED_BYTES = 256 * 1024 * 1024; // 256MB\nconst DEFAULT_MAX_ENTRIES = 500000;\nconst DEFAULT_AUTO_DETECT_THRESHOLD = 0.01; // 1%\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Normalize a path for case-insensitive, slash-agnostic lookup\n */\nfunction normalizePath(path: string): string {\n return path.toLowerCase().replace(/\\\\/g, '/');\n}\n\n/**\n * Get file extension from path (lowercase, without dot)\n */\nfunction getExtension(path: string): string {\n const lastDot = path.lastIndexOf('.');\n if (lastDot === -1 || lastDot === path.length - 1) return '';\n return path.substring(lastDot + 1).toLowerCase();\n}\n\n// Note: countReplacementChars, countBadChars, countC1ControlChars, decodeBytes,\n// and detectBestKoreanEncoding are imported from './decoder'\n\n/**\n * Decode filename bytes with specified encoding.\n * Uses iconv-lite for Korean encodings in Node.js for proper CP949 support.\n */\nfunction decodeFilenameBytes(bytes: Uint8Array, encoding: FilenameEncoding): string {\n // For Korean encodings, always use 'cp949' as it's a superset of euc-kr\n // This ensures proper handling of extended Korean characters\n const actualEncoding = (encoding === 'euc-kr' || encoding === 'cp949') ? 'cp949' : encoding;\n return decodeBytes(bytes, actualEncoding);\n}\n\n// ============================================================================\n// GrfBase Class\n// ============================================================================\n\nexport abstract class GrfBase<T> {\n public version = 0x200;\n public fileCount = 0;\n public loaded = false;\n\n /** Map of exact filename -> entry */\n public files = new Map<string, TFileEntry>();\n\n /** Map of normalized path -> array of exact filenames (supports collisions) */\n private normalizedIndex = new Map<string, string[]>();\n\n /** Map of extension -> array of exact filenames (for fast extension lookup) */\n private extensionIndex = new Map<string, string[]>();\n\n private fileTableOffset = 0;\n private cache = new Map<string, Uint8Array>();\n private cacheMaxSize = 50;\n private cacheOrder: string[] = [];\n\n // Options\n protected options: Required<GrfOptions>;\n\n // Statistics\n private _stats: GrfStats = {\n fileCount: 0,\n badNameCount: 0,\n collisionCount: 0,\n extensionStats: new Map(),\n detectedEncoding: 'utf-8'\n };\n\n constructor(private fd: T, options?: GrfOptions) {\n this.options = {\n filenameEncoding: options?.filenameEncoding ?? 'auto',\n autoDetectThreshold: options?.autoDetectThreshold ?? DEFAULT_AUTO_DETECT_THRESHOLD,\n maxFileUncompressedBytes: options?.maxFileUncompressedBytes ?? DEFAULT_MAX_FILE_UNCOMPRESSED_BYTES,\n maxEntries: options?.maxEntries ?? DEFAULT_MAX_ENTRIES\n };\n }\n\n abstract getStreamBuffer(\n fd: T,\n offset: number,\n length: number\n ): Promise<Uint8Array>;\n\n public async getStreamReader(\n offset: number,\n length: number\n ): Promise<jDataview> {\n const buffer = await this.getStreamBuffer(this.fd, offset, length);\n\n return new jDataview(buffer, void 0, void 0, true);\n }\n\n public async load(): Promise<void> {\n if (!this.loaded) {\n await this.parseHeader();\n await this.parseFileList();\n this.loaded = true;\n }\n }\n\n private async parseHeader(): Promise<void> {\n const reader = await this.getStreamReader(0, HEADER_SIZE);\n\n const signature = reader.getString(15);\n if (signature !== HEADER_SIGNATURE) {\n throw new GrfError('INVALID_MAGIC', 'Not a GRF file (invalid signature)', { signature });\n }\n\n reader.skip(15);\n this.fileTableOffset = reader.getUint32() + HEADER_SIZE;\n const reservedFiles = reader.getUint32();\n this.fileCount = reader.getUint32() - reservedFiles - 7;\n this.version = reader.getUint32();\n\n if (this.version !== 0x200) {\n throw new GrfError('UNSUPPORTED_VERSION', `Unsupported version \"0x${this.version.toString(16)}\"`, { version: this.version });\n }\n\n // Validate entry count against limit\n if (this.fileCount > this.options.maxEntries) {\n throw new GrfError('LIMIT_EXCEEDED', `File count ${this.fileCount} exceeds limit ${this.options.maxEntries}`, {\n fileCount: this.fileCount,\n maxEntries: this.options.maxEntries\n });\n }\n }\n\n private async parseFileList(): Promise<void> {\n // Read table list, stored information\n const reader = await this.getStreamReader(\n this.fileTableOffset,\n FILE_TABLE_SIZE\n );\n const compressedSize = reader.getUint32();\n const realSize = reader.getUint32();\n\n // Load the chunk and uncompress it\n const compressed = await this.getStreamBuffer(\n this.fd,\n this.fileTableOffset + FILE_TABLE_SIZE,\n compressedSize\n );\n\n let data: Uint8Array;\n try {\n data = pako.inflate(compressed);\n } catch (error) {\n throw new GrfError('CORRUPT_TABLE', 'Failed to decompress file table', {\n compressedSize,\n realSize,\n error: error instanceof Error ? error.message : String(error)\n });\n }\n\n // Validate decompressed size\n if (data.length !== realSize) {\n throw new GrfError('CORRUPT_TABLE', `File table size mismatch: expected ${realSize}, got ${data.length}`, {\n expected: realSize,\n actual: data.length\n });\n }\n\n // Determine encoding to use\n let detectedEncoding: FilenameEncoding = this.options.filenameEncoding;\n\n // If auto-detect, sample filenames and use improved detection algorithm\n if (this.options.filenameEncoding === 'auto') {\n const sampleBytes: Uint8Array[] = [];\n let samplePos = 0;\n // Sample more files for better detection accuracy\n const sampleCount = Math.min(200, this.fileCount);\n\n for (let i = 0; i < sampleCount && samplePos < data.length; i++) {\n let endPos = samplePos;\n while (data[endPos] !== 0 && endPos < data.length) endPos++;\n\n // Only include samples with non-ASCII bytes for detection\n const bytes = data.subarray(samplePos, endPos);\n sampleBytes.push(bytes);\n\n samplePos = endPos + 1 + 17; // Skip entry data (17 bytes)\n }\n\n // Use improved encoding detection algorithm from decoder module\n // This analyzes:\n // 1. Valid byte sequence patterns for UTF-8 vs EUC-KR/CP949\n // 2. Replacement character ratios after decoding (including C1 control chars)\n // 3. Uses iconv-lite in Node.js for proper CP949 support\n detectedEncoding = detectBestKoreanEncoding(sampleBytes, this.options.autoDetectThreshold);\n }\n\n this._stats.detectedEncoding = detectedEncoding;\n\n // Reset stats\n this._stats.badNameCount = 0;\n this._stats.collisionCount = 0;\n this._stats.extensionStats.clear();\n\n for (let i = 0, p = 0; i < this.fileCount; ++i) {\n // Validate position\n if (p >= data.length) {\n throw new GrfError('CORRUPT_TABLE', `Unexpected end of file table at entry ${i}`, {\n position: p,\n dataLength: data.length,\n entryIndex: i\n });\n }\n\n // Find null terminator\n let endPos = p;\n while (data[endPos] !== 0 && endPos < data.length) {\n endPos++;\n }\n\n // Store raw bytes and decode filename using the detected encoding\n // Uses iconv-lite for Korean encodings in Node.js for proper CP949 support\n const rawBytes = data.slice(p, endPos); // Copy for storage\n const filename = decodeFilenameBytes(rawBytes, detectedEncoding);\n\n // Count bad names (including C1 control chars that indicate wrong decode)\n if (countBadChars(filename) > 0) {\n this._stats.badNameCount++;\n }\n\n p = endPos + 1;\n\n // Validate remaining bytes for entry\n if (p + 17 > data.length) {\n throw new GrfError('CORRUPT_TABLE', `Incomplete entry data at entry ${i}`, {\n position: p,\n dataLength: data.length,\n entryIndex: i\n });\n }\n\n // prettier-ignore\n const entry: TFileEntry = {\n compressedSize: data[p++] | (data[p++] << 8) | (data[p++] << 16) | (data[p++] << 24),\n lengthAligned: data[p++] | (data[p++] << 8) | (data[p++] << 16) | (data[p++] << 24),\n realSize: data[p++] | (data[p++] << 8) | (data[p++] << 16) | (data[p++] << 24),\n type: data[p++],\n offset: (data[p++] | (data[p++] << 8) | (data[p++] << 16) | (data[p++] << 24)) >>> 0,\n rawNameBytes: rawBytes\n };\n\n // Validate sizes against limits\n if (entry.realSize > this.options.maxFileUncompressedBytes) {\n // Skip this entry but don't fail - just warn\n continue;\n }\n\n // Only process files (not folders)\n if (entry.type & FILELIST_TYPE_FILE) {\n // Add to main files map\n this.files.set(filename, entry);\n\n // Add to normalized index (supports collisions)\n const normalizedKey = normalizePath(filename);\n const existingNorm = this.normalizedIndex.get(normalizedKey);\n if (existingNorm) {\n existingNorm.push(filename);\n this._stats.collisionCount++;\n } else {\n this.normalizedIndex.set(normalizedKey, [filename]);\n }\n\n // Add to extension index\n const ext = getExtension(filename);\n if (ext) {\n const existingExt = this.extensionIndex.get(ext);\n if (existingExt) {\n existingExt.push(filename);\n } else {\n this.extensionIndex.set(ext, [filename]);\n }\n\n // Update extension stats\n this._stats.extensionStats.set(ext, (this._stats.extensionStats.get(ext) || 0) + 1);\n }\n }\n }\n\n this._stats.fileCount = this.files.size;\n }\n\n private decodeEntry(data: Uint8Array, entry: TFileEntry): Uint8Array {\n // Decode the file\n if (entry.type & FILELIST_TYPE_ENCRYPT_MIXED) {\n decodeFull(data, entry.lengthAligned, entry.compressedSize);\n } else if (entry.type & FILELIST_TYPE_ENCRYPT_HEADER) {\n decodeHeader(data, entry.lengthAligned);\n }\n\n // No compression\n if (entry.realSize === entry.compressedSize) {\n return data;\n }\n\n // Uncompress\n return pako.inflate(data);\n }\n\n private addToCache(filename: string, data: Uint8Array): void {\n // Remove oldest if cache is full\n if (this.cacheOrder.length >= this.cacheMaxSize) {\n const oldest = this.cacheOrder.shift();\n if (oldest) {\n this.cache.delete(oldest);\n }\n }\n\n // Add to cache\n this.cache.set(filename, data);\n this.cacheOrder.push(filename);\n }\n\n private getFromCache(filename: string): Uint8Array | undefined {\n const cached = this.cache.get(filename);\n if (cached) {\n // Move to end (most recently used)\n const index = this.cacheOrder.indexOf(filename);\n if (index > -1) {\n this.cacheOrder.splice(index, 1);\n this.cacheOrder.push(filename);\n }\n }\n return cached;\n }\n\n public clearCache(): void {\n this.cache.clear();\n this.cacheOrder = [];\n }\n\n public async getFile(\n filename: string\n ): Promise<{data: null | Uint8Array; error: null | string}> {\n if (!this.loaded) {\n return Promise.resolve({data: null, error: 'GRF not loaded yet'});\n }\n\n // Try to resolve the path (exact match first, then normalized)\n const resolved = this.resolvePath(filename);\n\n if (resolved.status === 'not_found') {\n return Promise.resolve({data: null, error: `File \"${filename}\" not found`});\n }\n\n if (resolved.status === 'ambiguous') {\n return Promise.resolve({\n data: null,\n error: `Ambiguous path \"${filename}\": ${resolved.candidates?.length} matches found. Use exact path: ${resolved.candidates?.slice(0, 5).join(', ')}${(resolved.candidates?.length || 0) > 5 ? '...' : ''}`\n });\n }\n\n const path = resolved.matchedPath!;\n\n // Check cache first\n const cached = this.getFromCache(path);\n if (cached) {\n return Promise.resolve({data: cached, error: null});\n }\n\n const entry = this.files.get(path);\n\n if (!entry) {\n return { data: null, error: `File \"${path}\" not found` };\n }\n\n const data = await this.getStreamBuffer(\n this.fd,\n entry.offset + HEADER_SIZE,\n entry.lengthAligned\n );\n\n try {\n const result = this.decodeEntry(data, entry);\n\n // Add to cache\n this.addToCache(path, result);\n\n return Promise.resolve({data: result, error: null});\n } catch (error) {\n const message =\n error instanceof Error ? error.message : String(error);\n return { data: null, error: message };\n }\n }\n\n // ===========================================================================\n // Path Resolution\n // ===========================================================================\n\n /**\n * Resolve a path to its exact filename in the GRF.\n * Tries exact match first, then normalized (case-insensitive, slash-agnostic).\n */\n public resolvePath(query: string): ResolveResult {\n // Try exact match first\n if (this.files.has(query)) {\n return { status: 'found', matchedPath: query };\n }\n\n // Try normalized lookup\n const normalizedQuery = normalizePath(query);\n const candidates = this.normalizedIndex.get(normalizedQuery);\n\n if (!candidates || candidates.length === 0) {\n return { status: 'not_found' };\n }\n\n if (candidates.length === 1) {\n return { status: 'found', matchedPath: candidates[0] };\n }\n\n // Multiple candidates - ambiguous\n return { status: 'ambiguous', candidates };\n }\n\n /**\n * Check if a file exists in the GRF.\n */\n public hasFile(filename: string): boolean {\n const resolved = this.resolvePath(filename);\n return resolved.status === 'found';\n }\n\n /**\n * Get file entry metadata without extracting the file.\n */\n public getEntry(filename: string): TFileEntry | null {\n const resolved = this.resolvePath(filename);\n if (resolved.status !== 'found' || !resolved.matchedPath) {\n return null;\n }\n return this.files.get(resolved.matchedPath) || null;\n }\n\n // ===========================================================================\n // Search API\n // ===========================================================================\n\n /**\n * Find files matching the given criteria.\n */\n public find(options: FindOptions = {}): string[] {\n const { ext, contains, endsWith, regex, limit } = options;\n let results: string[] = [];\n\n // If searching by extension only, use the extension index (fast path)\n if (ext && !contains && !endsWith && !regex) {\n const extLower = ext.toLowerCase().replace(/^\\./, ''); // Remove leading dot if present\n results = this.extensionIndex.get(extLower) || [];\n } else {\n // Full search\n for (const filename of this.files.keys()) {\n // Extension filter\n if (ext) {\n const extLower = ext.toLowerCase().replace(/^\\./, '');\n if (getExtension(filename) !== extLower) continue;\n }\n\n // Contains filter (case-insensitive)\n if (contains) {\n const normalizedFilename = normalizePath(filename);\n const normalizedContains = normalizePath(contains);\n if (!normalizedFilename.includes(normalizedContains)) continue;\n }\n\n // EndsWith filter (case-insensitive)\n if (endsWith) {\n const normalizedFilename = normalizePath(filename);\n const normalizedEndsWith = normalizePath(endsWith);\n if (!normalizedFilename.endsWith(normalizedEndsWith)) continue;\n }\n\n // Regex filter\n if (regex && !regex.test(filename)) continue;\n\n results.push(filename);\n\n // Limit check\n if (limit && results.length >= limit) break;\n }\n }\n\n // Apply limit if not already applied\n if (limit && results.length > limit) {\n results = results.slice(0, limit);\n }\n\n return results;\n }\n\n /**\n * Get all files with a specific extension.\n */\n public getFilesByExtension(ext: string): string[] {\n const extLower = ext.toLowerCase().replace(/^\\./, '');\n return this.extensionIndex.get(extLower) || [];\n }\n\n /**\n * List all unique extensions in the GRF.\n */\n public listExtensions(): string[] {\n return Array.from(this.extensionIndex.keys()).sort();\n }\n\n /**\n * List all files in the GRF.\n */\n public listFiles(): string[] {\n return Array.from(this.files.keys());\n }\n\n // ===========================================================================\n // Statistics\n // ===========================================================================\n\n /**\n * Get GRF statistics.\n */\n public getStats(): GrfStats {\n return { ...this._stats, extensionStats: new Map(this._stats.extensionStats) };\n }\n\n /**\n * Get the detected/configured encoding used for filenames.\n */\n public getDetectedEncoding(): FilenameEncoding {\n return this._stats.detectedEncoding;\n }\n\n // ===========================================================================\n // Re-decoding Support\n // ===========================================================================\n\n /**\n * Re-decode all filenames with a different encoding.\n * Useful if auto-detection chose wrong or you want to try a specific encoding.\n */\n public async reloadWithEncoding(encoding: FilenameEncoding): Promise<void> {\n this.options.filenameEncoding = encoding;\n this.files.clear();\n this.normalizedIndex.clear();\n this.extensionIndex.clear();\n this.clearCache();\n this.loaded = false;\n await this.load();\n }\n}\n","/**\n * Ragnarok Online DES decoder implementation\n * It's a custom one with some alterations\n */\nexport {decodeFull, decodeHeader};\n\nconst mask = new Uint8Array([0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01]);\nconst tmp = new Uint8Array(8);\nconst tmp2 = new Uint8Array(8);\nconst clean = new Uint8Array(8);\n\n// prettier-ignore\nconst initialPermutationTable = new Uint8Array([\n 58, 50, 42, 34, 26, 18, 10, 2,\n 60, 52, 44, 36, 28, 20, 12, 4,\n 62, 54, 46, 38, 30, 22, 14, 6,\n 64, 56, 48, 40, 32, 24, 16, 8,\n 57, 49, 41, 33, 25, 17, 9, 1,\n 59, 51, 43, 35, 27, 19, 11, 3,\n 61, 53, 45, 37, 29, 21, 13, 5,\n 63, 55, 47, 39, 31, 23, 15, 7\n]);\n\n// prettier-ignore\nconst finalPermutationTable = new Uint8Array([\n 40, 8, 48, 16, 56, 24, 64, 32,\n 39, 7, 47, 15, 55, 23, 63, 31,\n 38, 6, 46, 14, 54, 22, 62, 30,\n 37, 5, 45, 13, 53, 21, 61, 29,\n 36, 4, 44, 12, 52, 20, 60, 28,\n 35, 3, 43, 11, 51, 19, 59, 27,\n 34, 2, 42, 10, 50, 18, 58, 26,\n 33, 1, 41, 9, 49, 17, 57, 25\n]);\n\n// prettier-ignore\nconst transpositionTable = new Uint8Array([\n 16, 7, 20, 21,\n 29, 12, 28, 17,\n 1, 15, 23, 26,\n 5, 18, 31, 10,\n 2, 8, 24, 14,\n 32, 27, 3, 9,\n 19, 13, 30, 6,\n 22, 11, 4, 25\n]);\n\n// prettier-ignore\nconst substitutionBoxTable = [\n new Uint8Array([\n 0xef, 0x03, 0x41, 0xfd, 0xd8, 0x74, 0x1e, 0x47, 0x26, 0xef, 0xfb, 0x22, 0xb3, 0xd8, 0x84, 0x1e,\n 0x39, 0xac, 0xa7, 0x60, 0x62, 0xc1, 0xcd, 0xba, 0x5c, 0x96, 0x90, 0x59, 0x05, 0x3b, 0x7a, 0x85,\n 0x40, 0xfd, 0x1e, 0xc8, 0xe7, 0x8a, 0x8b, 0x21, 0xda, 0x43, 0x64, 0x9f, 0x2d, 0x14, 0xb1, 0x72,\n 0xf5, 0x5b, 0xc8, 0xb6, 0x9c, 0x37, 0x76, 0xec, 0x39, 0xa0, 0xa3, 0x05, 0x52, 0x6e, 0x0f, 0xd9 \n ]),\n new Uint8Array([\n 0xa7, 0xdd, 0x0d, 0x78, 0x9e, 0x0b, 0xe3, 0x95, 0x60, 0x36, 0x36, 0x4f, 0xf9, 0x60, 0x5a, 0xa3,\n 0x11, 0x24, 0xd2, 0x87, 0xc8, 0x52, 0x75, 0xec, 0xbb, 0xc1, 0x4c, 0xba, 0x24, 0xfe, 0x8f, 0x19,\n 0xda, 0x13, 0x66, 0xaf, 0x49, 0xd0, 0x90, 0x06, 0x8c, 0x6a, 0xfb, 0x91, 0x37, 0x8d, 0x0d, 0x78,\n 0xbf, 0x49, 0x11, 0xf4, 0x23, 0xe5, 0xce, 0x3b, 0x55, 0xbc, 0xa2, 0x57, 0xe8, 0x22, 0x74, 0xce\n ]),\n new Uint8Array([\n 0x2c, 0xea, 0xc1, 0xbf, 0x4a, 0x24, 0x1f, 0xc2, 0x79, 0x47, 0xa2, 0x7c, 0xb6, 0xd9, 0x68, 0x15,\n 0x80, 0x56, 0x5d, 0x01, 0x33, 0xfd, 0xf4, 0xae, 0xde, 0x30, 0x07, 0x9b, 0xe5, 0x83, 0x9b, 0x68,\n 0x49, 0xb4, 0x2e, 0x83, 0x1f, 0xc2, 0xb5, 0x7c, 0xa2, 0x19, 0xd8, 0xe5, 0x7c, 0x2f, 0x83, 0xda,\n 0xf7, 0x6b, 0x90, 0xfe, 0xc4, 0x01, 0x5a, 0x97, 0x61, 0xa6, 0x3d, 0x40, 0x0b, 0x58, 0xe6, 0x3d\n ]),\n new Uint8Array([\n 0x4d, 0xd1, 0xb2, 0x0f, 0x28, 0xbd, 0xe4, 0x78, 0xf6, 0x4a, 0x0f, 0x93, 0x8b, 0x17, 0xd1, 0xa4,\n 0x3a, 0xec, 0xc9, 0x35, 0x93, 0x56, 0x7e, 0xcb, 0x55, 0x20, 0xa0, 0xfe, 0x6c, 0x89, 0x17, 0x62,\n 0x17, 0x62, 0x4b, 0xb1, 0xb4, 0xde, 0xd1, 0x87, 0xc9, 0x14, 0x3c, 0x4a, 0x7e, 0xa8, 0xe2, 0x7d,\n 0xa0, 0x9f, 0xf6, 0x5c, 0x6a, 0x09, 0x8d, 0xf0, 0x0f, 0xe3, 0x53, 0x25, 0x95, 0x36, 0x28, 0xcb\n ])\n];\n\n/**\n * Initial permutation (IP).\n */\nfunction initialPermutation(src: Uint8Array, index: number): void {\n for (let i = 0; i < 64; ++i) {\n const j = initialPermutationTable[i] - 1;\n if (src[index + ((j >> 3) & 7)] & mask[j & 7]) {\n tmp[(i >> 3) & 7] |= mask[i & 7];\n }\n }\n\n src.set(tmp, index);\n tmp.set(clean);\n}\n\n/**\n * Final permutation (IP^-1).\n */\nfunction finalPermutation(src: Uint8Array, index: number): void {\n for (let i = 0; i < 64; ++i) {\n const j = finalPermutationTable[i] - 1;\n if (src[index + ((j >> 3) & 7)] & mask[j & 7]) {\n tmp[(i >> 3) & 7] |= mask[i & 7];\n }\n }\n\n src.set(tmp, index);\n tmp.set(clean);\n}\n\n/**\n * Transposition (P-BOX).\n */\nfunction transposition(src: Uint8Array, index: number): void {\n for (let i = 0; i < 32; ++i) {\n const j = transpositionTable[i] - 1;\n if (src[index + (j >> 3)] & mask[j & 7]) {\n tmp[(i >> 3) + 4] |= mask[i & 7];\n }\n }\n\n src.set(tmp, index);\n tmp.set(clean);\n}\n\n/**\n * Expansion (E).\n * Expands upper four 8-bits (32b) into eight 6-bits (48b).\n */\nfunction expansion(src: Uint8Array, index: number): void {\n tmp[0] = ((src[index + 7] << 5) | (src[index + 4] >> 3)) & 0x3f; // ..0 vutsr\n tmp[1] = ((src[index + 4] << 1) | (src[index + 5] >> 7)) & 0x3f; // ..srqpo n\n tmp[2] = ((src[index + 4] << 5) | (src[index + 5] >> 3)) & 0x3f; // ..o nmlkj\n tmp[3] = ((src[index + 5] << 1) | (src[index + 6] >> 7)) & 0x3f; // ..kjihg f\n tmp[4] = ((src[index + 5] << 5) | (src[index + 6] >> 3)) & 0x3f; // ..g fedcb\n tmp[5] = ((src[index + 6] << 1) | (src[index + 7] >> 7)) & 0x3f; // ..cba98 7\n tmp[6] = ((src[index + 6] << 5) | (src[index + 7] >> 3)) & 0x3f; // ..8 76543\n tmp[7] = ((src[index + 7] << 1) | (src[index + 4] >> 7)) & 0x3f; // ..43210 v\n\n src.set(tmp, index);\n tmp.set(clean);\n}\n\n/**\n * Substitution boxes (S-boxes).\n * NOTE: This implementation was optimized to process two nibbles in one step (twice as fast).\n */\nfunction substitutionBox(src: Uint8Array, index: number): void {\n for (let i = 0; i < 4; ++i) {\n tmp[i] =\n (substitutionBoxTable[i][src[i * 2 + 0 + index]] & 0xf0) |\n (substitutionBoxTable[i][src[i * 2 + 1 + index]] & 0x0f);\n }\n\n src.set(tmp, index);\n tmp.set(clean);\n}\n\n/**\n * DES round function.\n * XORs src[0..3] with TP(SBOX(E(src[4..7]))).\n */\nfunction roundFunction(src: Uint8Array, index: number): void {\n for (let i = 0; i < 8; i++) {\n tmp2[i] = src[index + i];\n }\n\n expansion(tmp2, 0);\n substitutionBox(tmp2, 0);\n transposition(tmp2, 0);\n\n src[index + 0] ^= tmp2[4];\n src[index + 1] ^= tmp2[5];\n src[index + 2] ^= tmp2[6];\n src[index + 3] ^= tmp2[7];\n}\n\n/**\n * DEcrypt a block\n */\nfunction decryptBlock(src: Uint8Array, index: number): void {\n initialPermutation(src, index);\n roundFunction(src, index);\n finalPermutation(src, index);\n}\n\n/**\n * Decode the whole file\n */\nfunction decodeFull(\n src: Uint8Array,\n length: number,\n entryLength: number\n): void {\n // compute number of digits of the entry length\n const digits = entryLength.toString().length;\n\n // choose size of gap between two encrypted blocks\n // digits: 0 1 2 3 4 5 6 7 8 9 ...\n // cycle: 1 1 1 4 5 14 15 22 23 24 ...\n const cycle =\n digits < 3\n ? 1\n : digits < 5\n ? digits + 1\n : digits < 7\n ? digits + 9\n : digits + 15;\n\n const nblocks = length >> 3;\n\n // first 20 blocks are all des-encrypted\n for (let i = 0; i < 20 && i < nblocks; ++i) {\n decryptBlock(src, i * 8);\n }\n\n for (let i = 20, j = -1; i < nblocks; ++i) {\n // decrypt block\n if (i % cycle === 0) {\n decryptBlock(src, i * 8);\n continue;\n }\n\n // de-shuffle block\n if (++j && j % 7 === 0) {\n shuffleDec(src, i * 8);\n }\n }\n}\n\n/**\n * Decode only the header\n */\nfunction decodeHeader(src: Uint8Array, length: number): void {\n const count = length >> 3;\n\n // first 20 blocks are all des-encrypted\n for (let i = 0; i < 20 && i < count; ++i) {\n decryptBlock(src, i * 8);\n }\n\n // the rest is plaintext, done.\n}\n\n/**\n * Shuffle decode\n */\nfunction shuffleDec(src: Uint8Array, index: number) {\n tmp[0] = src[index + 3];\n tmp[1] = src[index + 4];\n tmp[2] = src[index + 6];\n tmp[3] = src[index + 0];\n tmp[4] = src[index + 1];\n tmp[5] = src[index + 2];\n tmp[6] = src[index + 5];\n tmp[7] = shuffleDecTable[src[index + 7]];\n\n src.set(tmp, index);\n tmp.set(clean);\n}\n\n/**\n * GRF substitution table\n */\nconst shuffleDecTable = (() => {\n // prettier-ignore\n const list = new Uint8Array([\n 0x00, 0x2b, 0x6c, 0x80, 0x01, 0x68, 0x48,\n 0x77, 0x60, 0xff, 0xb9, 0xc0, 0xfe, 0xeb\n ]);\n\n const out = new Uint8Array(Array.from({length: 256}, (_, k) => k));\n const count = list.length;\n\n for (let i = 0; i < count; i += 2) {\n out[list[i + 0]] = list[i + 1];\n out[list[i + 1]] = list[i + 0];\n }\n\n return out;\n})();\n","/**\n * Korean encoding decoder module\n *\n * Uses iconv-lite in Node.js for proper CP949 support.\n * Falls back to TextDecoder in browser (with limitations for CP949 extended chars).\n */\n\n// Try to import iconv-lite (available in Node.js)\nlet iconv: typeof import('iconv-lite') | null = null;\n\n// Dynamic import for Node.js environment\ntry {\n // Use require for synchronous loading in Node.js\n // This will fail in browser environments\n if (typeof process !== 'undefined' && process.versions?.node) {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n iconv = require('iconv-lite');\n }\n} catch {\n // iconv-lite not available (browser environment)\n iconv = null;\n}\n\n/**\n * Check if we're in a Node.js environment with iconv-lite available\n */\nexport function hasIconvLite(): boolean {\n return iconv !== null;\n}\n\n/**\n * Count C1 control characters (U+0080-U+009F) in a string.\n * These usually indicate incorrectly decoded Korean bytes.\n * When EUC-KR decoder encounters CP949-extended bytes (0x80-0x9F range),\n * they get decoded as C1 control characters instead of Korean characters.\n */\nexport function countC1ControlChars(str: string): number {\n let count = 0;\n for (const char of str) {\n const code = char.charCodeAt(0);\n if (code >= 0x80 && code <= 0x9F) {\n count++;\n }\n }\n return count;\n}\n\n/**\n * Count replacement characters (U+FFFD) in a string\n */\nexport function countReplacementChars(str: string): number {\n let count = 0;\n for (const char of str) {\n if (char === '\\uFFFD') count++;\n }\n return count;\n}\n\n/**\n * Count total \"bad\" characters (replacement + C1 control)\n */\nexport function countBadChars(str: string): number {\n return countReplacementChars(str) + countC1ControlChars(str);\n}\n\n/**\n * Decode bytes to string using the specified encoding.\n *\n * For Korean encodings (cp949, euc-kr), uses iconv-lite in Node.js\n * for proper CP949 extended character support.\n *\n * @param bytes - The bytes to decode\n * @param encoding - The encoding to use ('utf-8', 'euc-kr', 'cp949', 'latin1')\n * @returns The decoded string\n */\nexport function decodeBytes(bytes: Uint8Array, encoding: string): string {\n // Normalize encoding name\n const enc = encoding.toLowerCase();\n\n // For Korean encodings, prefer iconv-lite in Node.js\n // iconv-lite properly handles CP949 extended range (0x81-0xFE first byte)\n // which TextDecoder('euc-kr') doesn't fully support\n if ((enc === 'cp949' || enc === 'euc-kr') && iconv) {\n try {\n // Always use 'cp949' with iconv-lite as it's a superset of euc-kr\n // This properly handles the extended range that causes C1 control chars\n const buffer = Buffer.from(bytes);\n return iconv.decode(buffer, 'cp949');\n } catch {\n // Fall through to TextDecoder\n }\n }\n\n // Use TextDecoder for other encodings or as fallback\n try {\n // Map cp949 to euc-kr for TextDecoder (best effort, not perfect)\n const textDecoderEncoding = enc === 'cp949' ? 'euc-kr' : enc;\n const decoder = new TextDecoder(textDecoderEncoding, { fatal: false });\n return decoder.decode(bytes);\n } catch {\n // Ultimate fallback: decode as latin1 (preserves all byte values)\n return Array.from(bytes).map(b => String.fromCharCode(b)).join('');\n }\n}\n\n/**\n * Try to decode bytes and check quality of the result\n */\nexport function tryDecodeWithQuality(\n bytes: Uint8Array,\n encoding: string\n): { text: string; badChars: number; c1Chars: number; replacementChars: number } {\n const text = decodeBytes(bytes, encoding);\n const c1Chars = countC1ControlChars(text);\n const replacementChars = countReplacementChars(text);\n const badChars = c1Chars + replacementChars;\n\n return { text, badChars, c1Chars, replacementChars };\n}\n\n// ============================================================================\n// Mojibake Detection and Fixing\n// ============================================================================\n\n/**\n * Common mojibake patterns that indicate CP949 was misread as Windows-1252.\n * These are high-frequency Korean syllable byte sequences that produce\n * recognizable Latin character patterns when misinterpreted.\n */\nconst MOJIBAKE_PATTERNS = [\n /[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞß][¡-þ]/, // Common Korean lead bytes as Latin\n /À¯/, // 유 (very common)\n /Àú/, // 저\n /ÀÎ/, // 인\n /Ÿ/, // 터/타\n /Æä/, // 페\n /ÀÌ/, // 이\n /½º/, // 스\n /¾Æ/, // 아\n /¸ð/, // 모\n /¸®/, // 리\n /¿¡/, // 에\n /Áö/, // 지\n /µ¥/, // 데\n /ÅØ/, // 텍\n /½ºÆ®/, // 스트\n /¸ÁÅä/, // 망토\n];\n\n/**\n * Check if a string looks like mojibake (CP949 bytes misread as Windows-1252).\n *\n * Mojibake occurs when:\n * 1. Korean text is encoded as CP949 bytes\n * 2. Those bytes are incorrectly decoded as Windows-1252/Latin-1\n *\n * Example: \"유저인터페이스\" → \"À¯ÀúÀÎÅÍÆäÀ̽º\"\n *\n * @param str - The string to check\n * @returns true if the string appears to be mojibake\n */\nexport function isMojibake(str: string): boolean {\n // Quick checks\n if (!str || str.length === 0) return false;\n\n // If string contains Korean characters, it's not mojibake\n if (/[\\uAC00-\\uD7AF]/.test(str)) return false;\n\n // Check for common mojibake patterns\n for (const pattern of MOJIBAKE_PATTERNS) {\n if (pattern.test(str)) return true;\n }\n\n // Check for high concentration of Latin Extended characters (0x80-0xFF)\n // which are common in mojibake but rare in normal text\n let highLatinCount = 0;\n for (const char of str) {\n const code = char.charCodeAt(0);\n if (code >= 0x80 && code <= 0xFF) {\n highLatinCount++;\n }\n }\n\n // If more than 30% of characters are in the Latin Extended range,\n // it's likely mojibake\n const ratio = highLatinCount / str.length;\n return ratio > 0.3;\n}\n\n/**\n * Fix mojibake by re-encoding as Windows-1252 and decoding as CP949.\n *\n * This reverses the common encoding error where CP949 bytes were\n * incorrectly interpreted as Windows-1252.\n *\n * Example: \"À¯ÀúÀÎÅÍÆäÀ̽º\" → \"유저인터페이스\"\n *\n * @param garbled - The mojibake string to fix\n * @returns The corrected Korean string, or the original if unfixable\n */\nexport function fixMojibake(garbled: string): string {\n if (!iconv) {\n // Without iconv-lite, we can't fix mojibake\n return garbled;\n }\n\n try {\n // Encode the garbled string back to Windows-1252 bytes\n const bytes = iconv.encode(garbled, 'windows-1252');\n // Decode those bytes as CP949 to get the original Korean\n const fixed = iconv.decode(bytes, 'cp949');\n\n // Verify the fix worked by checking if:\n // 1. The result contains Korean characters (Hangul Syllables block)\n // 2. The result has fewer or equal bad chars\n const hasKorean = /[\\uAC00-\\uD7AF]/.test(fixed);\n const fixedBadChars = countBadChars(fixed);\n const garbledBadChars = countBadChars(garbled);\n\n if (hasKorean && fixedBadChars <= garbledBadChars) {\n return fixed;\n }\n\n return garbled;\n } catch {\n return garbled;\n }\n}\n\n/**\n * Convert Korean text to mojibake (for testing purposes).\n *\n * This simulates the encoding error where Korean text is encoded as CP949\n * but decoded as Windows-1252.\n *\n * Example: \"유저인터페이스\" → \"À¯ÀúÀÎÅÍÆäÀ̽º\"\n *\n * @param korean - The Korean string to garble\n * @returns The mojibake string\n */\nexport function toMojibake(korean: string): string {\n if (!iconv) {\n return korean;\n }\n\n try {\n const bytes = iconv.encode(korean, 'cp949');\n return iconv.decode(bytes, 'windows-1252');\n } catch {\n return korean;\n }\n}\n\n/**\n * Normalize a filename by detecting and fixing encoding issues.\n *\n * This function:\n * 1. Checks if the filename is mojibake and fixes it\n * 2. Returns the normalized filename\n *\n * @param filename - The filename to normalize\n * @returns The normalized filename\n */\nexport function normalizeFilename(filename: string): string {\n if (isMojibake(filename)) {\n return fixMojibake(filename);\n }\n return filename;\n}\n\n/**\n * Normalize a path by fixing mojibake in each segment.\n *\n * @param filepath - The full path to normalize\n * @returns The normalized path\n */\nexport function normalizePath(filepath: string): string {\n // Split by both forward and back slashes\n const segments = filepath.split(/[\\\\/]/);\n const normalizedSegments = segments.map(seg => normalizeFilename(seg));\n\n // Preserve original separator style\n const separator = filepath.includes('\\\\') ? '\\\\' : '/';\n return normalizedSegments.join(separator);\n}\n\n// ============================================================================\n// Encoding Detection\n// ============================================================================\n\n/**\n * Detect the best encoding for Korean GRF files by analyzing byte patterns\n * and comparing decoded results.\n *\n * This function:\n * 1. Checks if bytes contain non-ASCII characters\n * 2. Tries UTF-8 and CP949 decoding\n * 3. Compares quality (bad chars, C1 control chars)\n * 4. Returns the encoding with best quality\n */\nexport function detectBestKoreanEncoding(\n sampleBytes: Uint8Array[],\n threshold: number = 0.01\n): 'utf-8' | 'cp949' {\n if (sampleBytes.length === 0) return 'utf-8';\n\n let utf8BadTotal = 0;\n let cp949BadTotal = 0;\n let totalBytes = 0;\n let samplesWithHighBytes = 0;\n\n for (const bytes of sampleBytes) {\n // Check if this sample has non-ASCII bytes\n const hasHighBytes = bytes.some(b => b > 0x7F);\n if (!hasHighBytes) continue;\n\n samplesWithHighBytes++;\n totalBytes += bytes.length;\n\n const utf8Result = tryDecodeWithQuality(bytes, 'utf-8');\n const cp949Result = tryDecodeWithQuality(bytes, 'cp949');\n\n utf8BadTotal += utf8Result.badChars;\n cp949BadTotal += cp949Result.badChars;\n }\n\n // If no high bytes found, it's pure ASCII - use UTF-8\n if (samplesWithHighBytes === 0) {\n return 'utf-8';\n }\n\n const utf8BadRatio = totalBytes > 0 ? utf8BadTotal / totalBytes : 0;\n const cp949BadRatio = totalBytes > 0 ? cp949BadTotal / totalBytes : 0;\n\n // If UTF-8 looks perfect, use it\n if (utf8BadRatio < threshold) {\n return 'utf-8';\n }\n\n // If CP949 produces fewer bad chars, use it\n if (cp949BadRatio < utf8BadRatio) {\n return 'cp949';\n }\n\n // Default to UTF-8\n return 'utf-8';\n}\n","import jDataview from 'jdataview';\nimport {GrfBase, GrfOptions} from './grf-base';\n\n/**\n * Using this Browser, we work from a File or Blob object.\n * We are use the FileReader API to read only some part of the file to avoid\n * loading 2 gigas into memory\n */\nexport class GrfBrowser extends GrfBase<File | Blob> {\n constructor(file: File | Blob, options?: GrfOptions) {\n super(file, options);\n }\n public async getStreamBuffer(\n buffer: File | Blob,\n offset: number,\n length: number\n ): Promise<Uint8Array> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onerror = reject;\n reader.onload = () =>\n resolve(new Uint8Array(reader.result as ArrayBuffer));\n reader.readAsArrayBuffer(buffer.slice(offset, offset + length));\n });\n }\n}\n","// src/grf-node.ts\nimport { fstatSync, read as readCallback } from 'fs';\nimport { promisify } from 'util';\nimport { GrfBase, GrfOptions } from './grf-base';\nimport { bufferPool } from './buffer-pool';\n\nconst readAsync = promisify(readCallback);\n\n/** Options for GrfNode */\nexport interface GrfNodeOptions extends GrfOptions {\n /** Use buffer pool for better performance (default: true) */\n useBufferPool?: boolean;\n}\n\nexport class GrfNode extends GrfBase<number> {\n private useBufferPool: boolean;\n\n constructor(fd: number, options?: GrfNodeOptions) {\n super(fd, options);\n\n this.useBufferPool = options?.useBufferPool ?? true;\n\n // Na nossa API, apenas FDs para arquivos regulares são válidos.\n // fstatSync lança erro se o descritor não existir ou não for arquivo.\n try {\n const stat = fstatSync(fd);\n if (!stat.isFile()) {\n throw new Error('GRFNode: file descriptor must point to a regular file');\n }\n } catch {\n // Converte em mensagem clara para o usuário\n throw new Error('GRFNode: invalid file descriptor');\n }\n }\n\n public async getStreamBuffer(\n fd: number,\n offset: number,\n length: number\n ): Promise<Uint8Array> {\n // Use buffer pool for better performance\n const buffer = this.useBufferPool\n ? bufferPool.acquire(length)\n : Buffer.allocUnsafe(length);\n\n const { bytesRead } = await readAsync(fd, buffer, 0, length, offset);\n\n if (bytesRead !== length) {\n // Release buffer back to pool if read failed\n if (this.useBufferPool) {\n bufferPool.release(buffer);\n }\n // ERRO TYPE: GRFNode: unexpected EOF\n throw new Error('Not a GRF file (invalid signature)');\n }\n\n return buffer;\n }\n}\n","/**\n * Simple buffer pool for reducing GC pressure\n * Pools buffers of common sizes for reuse\n */\n\ninterface PoolEntry {\n buffer: Buffer;\n inUse: boolean;\n}\n\nclass BufferPool {\n private pools = new Map<number, PoolEntry[]>();\n private maxPoolSize = 10;\n\n // Common buffer sizes to pool (in bytes)\n private readonly poolSizes = [\n 1024, // 1KB\n 4096, // 4KB\n 8192, // 8KB\n 16384, // 16KB\n 32768, // 32KB\n 65536, // 64KB\n 131072, // 128KB\n 262144, // 256KB\n ];\n\n constructor() {\n // Initialize pools for common sizes\n for (const size of this.poolSizes) {\n this.pools.set(size, []);\n }\n }\n\n /**\n * Get appropriate pool size for requested length\n */\n private getPoolSize(length: number): number | null {\n for (const size of this.poolSizes) {\n if (length <= size) {\n return size;\n }\n }\n return null; // Too large, don't pool\n }\n\n /**\n * Acquire a buffer from the pool or create new one\n */\n acquire(length: number): Buffer {\n const poolSize = this.getPoolSize(length);\n\n // Don't pool large buffers\n if (poolSize === null) {\n return Buffer.allocUnsafe(length);\n }\n\n const pool = this.pools.get(poolSize);\n\n if (pool) {\n // Try to find available buffer\n const available = pool.find(entry => !entry.inUse);\n\n if (available) {\n available.inUse = true;\n return available.buffer.subarray(0, length);\n }\n\n // Pool is full or all in use, create new if pool not maxed\n if (pool.length < this.maxPoolSize) {\n const buffer = Buffer.allocUnsafe(poolSize);\n pool.push({ buffer, inUse: true });\n return buffer.subarray(0, length);\n }\n }\n\n // Fallback: create non-pooled buffer\n return Buffer.allocUnsafe(length);\n }\n\n /**\n * Release a buffer back to the pool\n */\n release(buffer: Buffer): void {\n const actualSize = buffer.buffer.byteLength;\n const pool = this.pools.get(actualSize);\n\n if (pool) {\n const entry = pool.find(e => e.buffer === buffer || e.buffer.buffer === buffer.buffer);\n if (entry) {\n entry.inUse = false;\n }\n }\n }\n\n /**\n * Clear all pools\n */\n clear(): void {\n for (const pool of this.pools.values()) {\n pool.length = 0;\n }\n }\n\n /**\n * Get pool statistics\n */\n stats(): { size: number; total: number; inUse: number }[] {\n const stats: { size: number; total: number; inUse: number }[] = [];\n\n for (const [size, pool] of this.pools.entries()) {\n stats.push({\n size,\n total: pool.length,\n inUse: pool.filter(e => e.inUse).length\n });\n }\n\n return stats;\n }\n}\n\n// Export singleton instance\nexport const bufferPool = new BufferPool();\n"],"mappings":"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","names":["index_exports","__export","GRF_ERROR_CODES","GrfBrowser","GrfError","GrfNode","bufferPool","countBadChars","countC1ControlChars","countReplacementChars","fixMojibake","hasIconvLite","isMojibake","normalizePath","normalizeFilename","toMojibake","__toCommonJS","import_pako","import_jdataview","mask","tmp","tmp2","clean","initialPermutationTable","finalPermutationTable","transpositionTable","substitutionBoxTable","initialPermutation","src","index","i","j","finalPermutation","transposition","expansion","substitutionBox","roundFunction","decryptBlock","decodeFull","length","entryLength","digits","cycle","nblocks","shuffleDec","decodeHeader","count","shuffleDecTable","list","out","_","k","iconv","hasIconvLite","countC1ControlChars","str","count","char","code","countReplacementChars","countBadChars","decodeBytes","bytes","encoding","enc","buffer","textDecoderEncoding","b","tryDecodeWithQuality","text","c1Chars","replacementChars","badChars","MOJIBAKE_PATTERNS","isMojibake","pattern","highLatinCount","fixMojibake","garbled","fixed","hasKorean","fixedBadChars","garbledBadChars","toMojibake","korean","normalizeFilename","filename","normalizePath","filepath","normalizedSegments","seg","separator","detectBestKoreanEncoding","sampleBytes","threshold","utf8BadTotal","cp949BadTotal","totalBytes","samplesWithHighBytes","utf8Result","cp949Result","utf8BadRatio","cp949BadRatio","GRF_ERROR_CODES","GrfError","code","message","context","FILELIST_TYPE_FILE","FILELIST_TYPE_ENCRYPT_MIXED","FILELIST_TYPE_ENCRYPT_HEADER","HEADER_SIGNATURE","HEADER_SIZE","FILE_TABLE_SIZE","DEFAULT_MAX_FILE_UNCOMPRESSED_BYTES","DEFAULT_MAX_ENTRIES","DEFAULT_AUTO_DETECT_THRESHOLD","normalizePath","path","getExtension","lastDot","decodeFilenameBytes","bytes","encoding","decodeBytes","GrfBase","fd","options","offset","length","buffer","jDataview","reader","signature","reservedFiles","compressedSize","realSize","compressed","data","pako","error","detectedEncoding","sampleBytes","samplePos","sampleCount","i","endPos","detectBestKoreanEncoding","p","rawBytes","filename","countBadChars","entry","normalizedKey","existingNorm","ext","existingExt","decodeFull","decodeHeader","oldest","cached","index","resolved","result","query","normalizedQuery","candidates","contains","endsWith","regex","limit","results","extLower","normalizedFilename","normalizedContains","normalizedEndsWith","GrfBrowser","GrfBase","file","options","buffer","offset","length","resolve","reject","reader","import_fs","import_util","BufferPool","size","length","poolSize","pool","available","entry","buffer","actualSize","e","stats","bufferPool","readAsync","readCallback","GrfNode","GrfBase","fd","options","offset","length","buffer","bufferPool","bytesRead"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -6,18 +6,91 @@ interface TFileEntry {
|
|
|
6
6
|
realSize: number;
|
|
7
7
|
compressedSize: number;
|
|
8
8
|
lengthAligned: number;
|
|
9
|
+
/** Raw filename bytes for re-decoding if needed */
|
|
10
|
+
rawNameBytes?: Uint8Array;
|
|
11
|
+
}
|
|
12
|
+
/** Supported filename encodings */
|
|
13
|
+
type FilenameEncoding = 'utf-8' | 'euc-kr' | 'cp949' | 'latin1' | 'auto';
|
|
14
|
+
/** GRF loader options */
|
|
15
|
+
interface GrfOptions {
|
|
16
|
+
/** Encoding for filenames (default: 'auto') */
|
|
17
|
+
filenameEncoding?: FilenameEncoding;
|
|
18
|
+
/** Threshold for auto-detection: if % of U+FFFD exceeds this, try Korean encodings (default: 0.01 = 1%) */
|
|
19
|
+
autoDetectThreshold?: number;
|
|
20
|
+
/** Maximum uncompressed size per file in bytes (default: 256MB) */
|
|
21
|
+
maxFileUncompressedBytes?: number;
|
|
22
|
+
/** Maximum total entries allowed (default: 500000) */
|
|
23
|
+
maxEntries?: number;
|
|
24
|
+
}
|
|
25
|
+
/** Search/find options */
|
|
26
|
+
interface FindOptions {
|
|
27
|
+
/** Filter by file extension (without dot, e.g., 'spr', 'act') */
|
|
28
|
+
ext?: string;
|
|
29
|
+
/** Filter by substring in path */
|
|
30
|
+
contains?: string;
|
|
31
|
+
/** Filter by path ending */
|
|
32
|
+
endsWith?: string;
|
|
33
|
+
/** Filter by regex pattern */
|
|
34
|
+
regex?: RegExp;
|
|
35
|
+
/** Maximum results to return (default: unlimited) */
|
|
36
|
+
limit?: number;
|
|
37
|
+
}
|
|
38
|
+
/** Result of path resolution */
|
|
39
|
+
interface ResolveResult {
|
|
40
|
+
status: 'found' | 'not_found' | 'ambiguous';
|
|
41
|
+
/** The exact matched path (if found) */
|
|
42
|
+
matchedPath?: string;
|
|
43
|
+
/** All candidate paths (if ambiguous) */
|
|
44
|
+
candidates?: string[];
|
|
45
|
+
}
|
|
46
|
+
/** GRF statistics */
|
|
47
|
+
interface GrfStats {
|
|
48
|
+
/** Total file count */
|
|
49
|
+
fileCount: number;
|
|
50
|
+
/** Number of filenames with replacement character (U+FFFD) */
|
|
51
|
+
badNameCount: number;
|
|
52
|
+
/** Number of normalized key collisions */
|
|
53
|
+
collisionCount: number;
|
|
54
|
+
/** Extension statistics: ext -> count */
|
|
55
|
+
extensionStats: Map<string, number>;
|
|
56
|
+
/** Detected encoding used */
|
|
57
|
+
detectedEncoding: FilenameEncoding;
|
|
58
|
+
}
|
|
59
|
+
declare const GRF_ERROR_CODES: {
|
|
60
|
+
readonly INVALID_MAGIC: "GRF_INVALID_MAGIC";
|
|
61
|
+
readonly UNSUPPORTED_VERSION: "GRF_UNSUPPORTED_VERSION";
|
|
62
|
+
readonly NOT_LOADED: "GRF_NOT_LOADED";
|
|
63
|
+
readonly FILE_NOT_FOUND: "GRF_FILE_NOT_FOUND";
|
|
64
|
+
readonly AMBIGUOUS_PATH: "GRF_AMBIGUOUS_PATH";
|
|
65
|
+
readonly DECOMPRESS_FAIL: "GRF_DECOMPRESS_FAIL";
|
|
66
|
+
readonly CORRUPT_TABLE: "GRF_CORRUPT_TABLE";
|
|
67
|
+
readonly LIMIT_EXCEEDED: "GRF_LIMIT_EXCEEDED";
|
|
68
|
+
readonly INVALID_OFFSET: "GRF_INVALID_OFFSET";
|
|
69
|
+
readonly DECRYPT_REQUIRED: "GRF_DECRYPT_REQUIRED";
|
|
70
|
+
};
|
|
71
|
+
declare class GrfError extends Error {
|
|
72
|
+
code: keyof typeof GRF_ERROR_CODES;
|
|
73
|
+
context?: Record<string, unknown> | undefined;
|
|
74
|
+
constructor(code: keyof typeof GRF_ERROR_CODES, message: string, context?: Record<string, unknown> | undefined);
|
|
9
75
|
}
|
|
10
76
|
declare abstract class GrfBase<T> {
|
|
11
77
|
private fd;
|
|
12
78
|
version: number;
|
|
13
79
|
fileCount: number;
|
|
14
80
|
loaded: boolean;
|
|
81
|
+
/** Map of exact filename -> entry */
|
|
15
82
|
files: Map<string, TFileEntry>;
|
|
83
|
+
/** Map of normalized path -> array of exact filenames (supports collisions) */
|
|
84
|
+
private normalizedIndex;
|
|
85
|
+
/** Map of extension -> array of exact filenames (for fast extension lookup) */
|
|
86
|
+
private extensionIndex;
|
|
16
87
|
private fileTableOffset;
|
|
17
88
|
private cache;
|
|
18
89
|
private cacheMaxSize;
|
|
19
90
|
private cacheOrder;
|
|
20
|
-
|
|
91
|
+
protected options: Required<GrfOptions>;
|
|
92
|
+
private _stats;
|
|
93
|
+
constructor(fd: T, options?: GrfOptions);
|
|
21
94
|
abstract getStreamBuffer(fd: T, offset: number, length: number): Promise<Uint8Array>;
|
|
22
95
|
getStreamReader(offset: number, length: number): Promise<jDataview>;
|
|
23
96
|
load(): Promise<void>;
|
|
@@ -31,6 +104,48 @@ declare abstract class GrfBase<T> {
|
|
|
31
104
|
data: null | Uint8Array;
|
|
32
105
|
error: null | string;
|
|
33
106
|
}>;
|
|
107
|
+
/**
|
|
108
|
+
* Resolve a path to its exact filename in the GRF.
|
|
109
|
+
* Tries exact match first, then normalized (case-insensitive, slash-agnostic).
|
|
110
|
+
*/
|
|
111
|
+
resolvePath(query: string): ResolveResult;
|
|
112
|
+
/**
|
|
113
|
+
* Check if a file exists in the GRF.
|
|
114
|
+
*/
|
|
115
|
+
hasFile(filename: string): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Get file entry metadata without extracting the file.
|
|
118
|
+
*/
|
|
119
|
+
getEntry(filename: string): TFileEntry | null;
|
|
120
|
+
/**
|
|
121
|
+
* Find files matching the given criteria.
|
|
122
|
+
*/
|
|
123
|
+
find(options?: FindOptions): string[];
|
|
124
|
+
/**
|
|
125
|
+
* Get all files with a specific extension.
|
|
126
|
+
*/
|
|
127
|
+
getFilesByExtension(ext: string): string[];
|
|
128
|
+
/**
|
|
129
|
+
* List all unique extensions in the GRF.
|
|
130
|
+
*/
|
|
131
|
+
listExtensions(): string[];
|
|
132
|
+
/**
|
|
133
|
+
* List all files in the GRF.
|
|
134
|
+
*/
|
|
135
|
+
listFiles(): string[];
|
|
136
|
+
/**
|
|
137
|
+
* Get GRF statistics.
|
|
138
|
+
*/
|
|
139
|
+
getStats(): GrfStats;
|
|
140
|
+
/**
|
|
141
|
+
* Get the detected/configured encoding used for filenames.
|
|
142
|
+
*/
|
|
143
|
+
getDetectedEncoding(): FilenameEncoding;
|
|
144
|
+
/**
|
|
145
|
+
* Re-decode all filenames with a different encoding.
|
|
146
|
+
* Useful if auto-detection chose wrong or you want to try a specific encoding.
|
|
147
|
+
*/
|
|
148
|
+
reloadWithEncoding(encoding: FilenameEncoding): Promise<void>;
|
|
34
149
|
}
|
|
35
150
|
|
|
36
151
|
/**
|
|
@@ -39,14 +154,18 @@ declare abstract class GrfBase<T> {
|
|
|
39
154
|
* loading 2 gigas into memory
|
|
40
155
|
*/
|
|
41
156
|
declare class GrfBrowser extends GrfBase<File | Blob> {
|
|
157
|
+
constructor(file: File | Blob, options?: GrfOptions);
|
|
42
158
|
getStreamBuffer(buffer: File | Blob, offset: number, length: number): Promise<Uint8Array>;
|
|
43
159
|
}
|
|
44
160
|
|
|
161
|
+
/** Options for GrfNode */
|
|
162
|
+
interface GrfNodeOptions extends GrfOptions {
|
|
163
|
+
/** Use buffer pool for better performance (default: true) */
|
|
164
|
+
useBufferPool?: boolean;
|
|
165
|
+
}
|
|
45
166
|
declare class GrfNode extends GrfBase<number> {
|
|
46
167
|
private useBufferPool;
|
|
47
|
-
constructor(fd: number, options?:
|
|
48
|
-
useBufferPool?: boolean;
|
|
49
|
-
});
|
|
168
|
+
constructor(fd: number, options?: GrfNodeOptions);
|
|
50
169
|
getStreamBuffer(fd: number, offset: number, length: number): Promise<Uint8Array>;
|
|
51
170
|
}
|
|
52
171
|
|
|
@@ -86,4 +205,85 @@ declare class BufferPool {
|
|
|
86
205
|
}
|
|
87
206
|
declare const bufferPool: BufferPool;
|
|
88
207
|
|
|
89
|
-
|
|
208
|
+
/**
|
|
209
|
+
* Korean encoding decoder module
|
|
210
|
+
*
|
|
211
|
+
* Uses iconv-lite in Node.js for proper CP949 support.
|
|
212
|
+
* Falls back to TextDecoder in browser (with limitations for CP949 extended chars).
|
|
213
|
+
*/
|
|
214
|
+
/**
|
|
215
|
+
* Check if we're in a Node.js environment with iconv-lite available
|
|
216
|
+
*/
|
|
217
|
+
declare function hasIconvLite(): boolean;
|
|
218
|
+
/**
|
|
219
|
+
* Count C1 control characters (U+0080-U+009F) in a string.
|
|
220
|
+
* These usually indicate incorrectly decoded Korean bytes.
|
|
221
|
+
* When EUC-KR decoder encounters CP949-extended bytes (0x80-0x9F range),
|
|
222
|
+
* they get decoded as C1 control characters instead of Korean characters.
|
|
223
|
+
*/
|
|
224
|
+
declare function countC1ControlChars(str: string): number;
|
|
225
|
+
/**
|
|
226
|
+
* Count replacement characters (U+FFFD) in a string
|
|
227
|
+
*/
|
|
228
|
+
declare function countReplacementChars(str: string): number;
|
|
229
|
+
/**
|
|
230
|
+
* Count total "bad" characters (replacement + C1 control)
|
|
231
|
+
*/
|
|
232
|
+
declare function countBadChars(str: string): number;
|
|
233
|
+
/**
|
|
234
|
+
* Check if a string looks like mojibake (CP949 bytes misread as Windows-1252).
|
|
235
|
+
*
|
|
236
|
+
* Mojibake occurs when:
|
|
237
|
+
* 1. Korean text is encoded as CP949 bytes
|
|
238
|
+
* 2. Those bytes are incorrectly decoded as Windows-1252/Latin-1
|
|
239
|
+
*
|
|
240
|
+
* Example: "유저인터페이스" → "À¯ÀúÀÎÅÍÆäÀ̽º"
|
|
241
|
+
*
|
|
242
|
+
* @param str - The string to check
|
|
243
|
+
* @returns true if the string appears to be mojibake
|
|
244
|
+
*/
|
|
245
|
+
declare function isMojibake(str: string): boolean;
|
|
246
|
+
/**
|
|
247
|
+
* Fix mojibake by re-encoding as Windows-1252 and decoding as CP949.
|
|
248
|
+
*
|
|
249
|
+
* This reverses the common encoding error where CP949 bytes were
|
|
250
|
+
* incorrectly interpreted as Windows-1252.
|
|
251
|
+
*
|
|
252
|
+
* Example: "À¯ÀúÀÎÅÍÆäÀ̽º" → "유저인터페이스"
|
|
253
|
+
*
|
|
254
|
+
* @param garbled - The mojibake string to fix
|
|
255
|
+
* @returns The corrected Korean string, or the original if unfixable
|
|
256
|
+
*/
|
|
257
|
+
declare function fixMojibake(garbled: string): string;
|
|
258
|
+
/**
|
|
259
|
+
* Convert Korean text to mojibake (for testing purposes).
|
|
260
|
+
*
|
|
261
|
+
* This simulates the encoding error where Korean text is encoded as CP949
|
|
262
|
+
* but decoded as Windows-1252.
|
|
263
|
+
*
|
|
264
|
+
* Example: "유저인터페이스" → "À¯ÀúÀÎÅÍÆäÀ̽º"
|
|
265
|
+
*
|
|
266
|
+
* @param korean - The Korean string to garble
|
|
267
|
+
* @returns The mojibake string
|
|
268
|
+
*/
|
|
269
|
+
declare function toMojibake(korean: string): string;
|
|
270
|
+
/**
|
|
271
|
+
* Normalize a filename by detecting and fixing encoding issues.
|
|
272
|
+
*
|
|
273
|
+
* This function:
|
|
274
|
+
* 1. Checks if the filename is mojibake and fixes it
|
|
275
|
+
* 2. Returns the normalized filename
|
|
276
|
+
*
|
|
277
|
+
* @param filename - The filename to normalize
|
|
278
|
+
* @returns The normalized filename
|
|
279
|
+
*/
|
|
280
|
+
declare function normalizeFilename(filename: string): string;
|
|
281
|
+
/**
|
|
282
|
+
* Normalize a path by fixing mojibake in each segment.
|
|
283
|
+
*
|
|
284
|
+
* @param filepath - The full path to normalize
|
|
285
|
+
* @returns The normalized path
|
|
286
|
+
*/
|
|
287
|
+
declare function normalizePath(filepath: string): string;
|
|
288
|
+
|
|
289
|
+
export { type FilenameEncoding, type FindOptions, GRF_ERROR_CODES, GrfBrowser, GrfError, GrfNode, type GrfNodeOptions, type GrfOptions, type GrfStats, type ResolveResult, type TFileEntry, bufferPool, countBadChars, countC1ControlChars, countReplacementChars, fixMojibake, hasIconvLite, isMojibake, normalizePath as normalizeEncodingPath, normalizeFilename, toMojibake };
|