stormlib-js 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.browser.ts","../src/stream/file-stream.browser.ts","../src/file/sector-reader.browser.ts","../src/file/mpq-file.browser.ts","../src/archive/mpq-archive.browser.ts"],"sourcesContent":["import { Buffer as BufferPolyfill } from 'buffer';\n\nif (typeof globalThis !== 'undefined' && typeof (globalThis as { Buffer?: typeof BufferPolyfill }).Buffer === 'undefined') {\n (globalThis as { Buffer?: typeof BufferPolyfill }).Buffer = BufferPolyfill;\n}\n\n// Main API (browser-safe)\nexport { MpqArchive } from './archive/mpq-archive.browser';\nexport { MpqFile } from './file/mpq-file.browser';\n\n// Types\nexport type {\n MpqHeader,\n MpqUserData,\n MpqHashEntry,\n MpqBlockEntry,\n FileEntry,\n FileFindData,\n OpenArchiveOptions,\n HetTableHeader,\n BetTableHeader,\n} from './types';\n\n// Errors\nexport {\n MpqError,\n MpqNotFoundError,\n MpqCorruptError,\n MpqUnsupportedError,\n MpqEncryptionError,\n MpqCompressionError,\n} from './errors';\n\n// Constants (for advanced usage)\nexport {\n ID_MPQ,\n ID_MPQ_USERDATA,\n MPQ_FORMAT_VERSION_1,\n MPQ_FORMAT_VERSION_2,\n MPQ_FORMAT_VERSION_3,\n MPQ_FORMAT_VERSION_4,\n MPQ_FILE_IMPLODE,\n MPQ_FILE_COMPRESS,\n MPQ_FILE_ENCRYPTED,\n MPQ_FILE_KEY_V2,\n MPQ_FILE_SINGLE_UNIT,\n MPQ_FILE_EXISTS,\n MPQ_COMPRESSION_HUFFMANN,\n MPQ_COMPRESSION_ZLIB,\n MPQ_COMPRESSION_PKWARE,\n MPQ_COMPRESSION_BZIP2,\n MPQ_COMPRESSION_LZMA,\n MPQ_COMPRESSION_SPARSE,\n MPQ_COMPRESSION_ADPCM_MONO,\n MPQ_COMPRESSION_ADPCM_STEREO,\n LISTFILE_NAME,\n SIGNATURE_NAME,\n ATTRIBUTES_NAME,\n} from './constants';\n\n// Low-level crypto (for advanced usage / testing)\nexport { hashString, hashTableIndex, hashNameA, hashNameB, hashFileKey, jenkinsHash } from './crypto/hash';\nexport { decryptBlock, encryptBlock, decryptFileKey } from './crypto/cipher';\nexport { getStormBuffer } from './crypto/storm-buffer';\n","/**\n * Browser-safe stream abstraction backed by in-memory binary data.\n */\n\ntype BinaryInput = Buffer | Uint8Array | ArrayBuffer;\n\nexport class FileStream {\n private fileSize: bigint;\n private filePath: string;\n private memoryData: Buffer | null;\n\n private constructor(fileSize: bigint, filePath: string, memoryData: Buffer) {\n this.fileSize = fileSize;\n this.filePath = filePath;\n this.memoryData = memoryData;\n }\n\n /**\n * Path-based open is not available in browsers.\n */\n static open(_path: string): never {\n throw new Error('Path-based archive open is not available in browser runtime. Use MpqArchive.openFromBuffer().');\n }\n\n /**\n * Create an in-memory stream from binary data.\n */\n static fromBuffer(data: BinaryInput, filePath = '[buffer]'): FileStream {\n const buffer = FileStream.normalizeBuffer(data);\n return new FileStream(BigInt(buffer.length), filePath, buffer);\n }\n\n private static normalizeBuffer(data: BinaryInput): Buffer {\n if (Buffer.isBuffer(data)) {\n return data;\n }\n\n if (data instanceof Uint8Array) {\n return Buffer.from(data.buffer, data.byteOffset, data.byteLength);\n }\n\n return Buffer.from(data);\n }\n\n /**\n * Read bytes at a given offset.\n */\n read(offset: bigint, length: number): Buffer {\n if (!this.memoryData) {\n return Buffer.alloc(0);\n }\n\n const start = Number(offset);\n if (start >= this.memoryData.length || length <= 0) {\n return Buffer.alloc(0);\n }\n\n const end = Math.min(start + length, this.memoryData.length);\n return this.memoryData.subarray(start, end);\n }\n\n /**\n * Read bytes into an existing buffer.\n */\n readInto(offset: bigint, buffer: Buffer, bufOffset: number, length: number): number {\n if (!this.memoryData) {\n return 0;\n }\n\n const start = Number(offset);\n if (start >= this.memoryData.length || length <= 0) {\n return 0;\n }\n\n const end = Math.min(start + length, this.memoryData.length);\n return this.memoryData.copy(buffer, bufOffset, start, end);\n }\n\n /**\n * Get the total file size.\n */\n getSize(): bigint {\n return this.fileSize;\n }\n\n /**\n * Get the file path.\n */\n getPath(): string {\n return this.filePath;\n }\n\n /**\n * Close the stream.\n */\n close(): void {\n this.memoryData = null;\n }\n}\n","/**\n * Sector-based reading pipeline for MPQ file data.\n *\n * MPQ files are divided into sectors of a fixed size (usually 4096 bytes).\n * Each sector can be independently compressed and/or encrypted.\n *\n * Reading pipeline per sector:\n * 1. Read raw bytes from file stream\n * 2. Decrypt if encrypted (key = fileKey + sectorIndex)\n * 3. Decompress if compressed (first byte = compression bitmask)\n */\n\nimport {\n MPQ_FILE_COMPRESS,\n MPQ_FILE_IMPLODE,\n MPQ_FILE_ENCRYPTED,\n MPQ_FILE_SINGLE_UNIT,\n MPQ_FILE_SECTOR_CRC,\n} from '../constants';\nimport type { FileEntry } from '../types';\nimport { decryptBlock } from '../crypto/cipher';\nimport { decompress } from '../compression';\nimport { decompressPkware } from '../compression/pkware';\nimport { MpqCorruptError, MpqCompressionError } from '../errors';\nimport { FileStream } from '../stream/file-stream.browser';\n\n/**\n * Read and decrypt the sector offset table for a file.\n *\n * The sector offset table is stored at the beginning of the file's data\n * and contains (sectorCount + 1) DWORDs indicating the offset of each\n * sector's data relative to the file's start position.\n *\n * @param stream - File stream\n * @param fileOffset - Absolute offset of the file data in the stream\n * @param fileEntry - File entry metadata\n * @param sectorSize - Sector size\n * @param fileKey - File encryption key (0 if not encrypted)\n * @returns Array of sector offsets\n */\nexport function readSectorOffsets(\n stream: FileStream,\n fileOffset: bigint,\n fileEntry: FileEntry,\n sectorSize: number,\n fileKey: number,\n): Uint32Array {\n const sectorCount = Math.ceil(fileEntry.fileSize / sectorSize);\n\n // The sector offset table has (sectorCount + 1) entries\n // Plus 1 more if the file has sector CRC checksums\n let offsetCount = sectorCount + 1;\n if (fileEntry.flags & MPQ_FILE_SECTOR_CRC) {\n offsetCount++;\n }\n\n const tableSize = offsetCount * 4;\n const tableData = stream.read(fileOffset, tableSize);\n\n if (tableData.length < tableSize) {\n throw new MpqCorruptError('Sector offset table is truncated');\n }\n\n // Decrypt the sector offset table (key = fileKey - 1)\n if (fileEntry.flags & MPQ_FILE_ENCRYPTED) {\n decryptBlock(tableData, (fileKey - 1) >>> 0);\n }\n\n // Parse into array\n const offsets = new Uint32Array(offsetCount);\n for (let i = 0; i < offsetCount; i++) {\n offsets[i] = tableData.readUInt32LE(i * 4);\n }\n\n return offsets;\n}\n\n/**\n * Read a single sector of a file.\n *\n * @param stream - File stream\n * @param fileOffset - Absolute offset of the file data in the stream\n * @param sectorOffsets - Sector offset table\n * @param sectorIndex - Index of the sector to read\n * @param fileEntry - File entry metadata\n * @param sectorSize - Sector size\n * @param fileKey - File encryption key (0 if not encrypted)\n * @returns Decompressed sector data\n */\nexport function readSector(\n stream: FileStream,\n fileOffset: bigint,\n sectorOffsets: Uint32Array,\n sectorIndex: number,\n fileEntry: FileEntry,\n sectorSize: number,\n fileKey: number,\n): Buffer {\n const rawOffset = sectorOffsets[sectorIndex];\n const rawEnd = sectorOffsets[sectorIndex + 1];\n const rawSize = rawEnd - rawOffset;\n\n // Calculate the expected uncompressed size of this sector\n const sectorStart = sectorIndex * sectorSize;\n const expectedSize = Math.min(sectorSize, fileEntry.fileSize - sectorStart);\n\n // Read raw sector data\n let sectorData = stream.read(fileOffset + BigInt(rawOffset), rawSize);\n\n if (sectorData.length < rawSize) {\n throw new MpqCorruptError(`Sector ${sectorIndex} data is truncated`);\n }\n\n // Step 1: Decrypt if encrypted\n if (fileEntry.flags & MPQ_FILE_ENCRYPTED) {\n // Make a mutable copy for decryption\n sectorData = Buffer.from(sectorData);\n decryptBlock(sectorData, (fileKey + sectorIndex) >>> 0);\n }\n\n // Step 2: Decompress if needed\n if (rawSize < expectedSize) {\n if (fileEntry.flags & MPQ_FILE_COMPRESS) {\n // Multi-algorithm compression (first byte = bitmask)\n sectorData = decompress(sectorData, expectedSize);\n } else if (fileEntry.flags & MPQ_FILE_IMPLODE) {\n // PKWARE DCL implode\n sectorData = decompressPkware(sectorData, expectedSize);\n }\n }\n\n return sectorData;\n}\n\n/**\n * Read an entire file using sector-based reading.\n *\n * @param stream - File stream\n * @param archiveOffset - Offset of the MPQ archive within the file\n * @param fileEntry - File entry metadata\n * @param sectorSize - Sector size\n * @param fileKey - File encryption key (0 if not encrypted)\n * @returns Complete file data\n */\nexport function readSectorFile(\n stream: FileStream,\n archiveOffset: bigint,\n fileEntry: FileEntry,\n sectorSize: number,\n fileKey: number,\n): Buffer {\n const fileOffset = archiveOffset + fileEntry.byteOffset;\n\n // Read sector offset table\n const sectorOffsets = readSectorOffsets(stream, fileOffset, fileEntry, sectorSize, fileKey);\n\n // Read each sector and assemble the output\n const sectorCount = Math.ceil(fileEntry.fileSize / sectorSize);\n const output = Buffer.alloc(fileEntry.fileSize);\n let outPos = 0;\n\n for (let i = 0; i < sectorCount; i++) {\n const sectorData = readSector(\n stream, fileOffset, sectorOffsets, i,\n fileEntry, sectorSize, fileKey,\n );\n\n const copyLen = Math.min(sectorData.length, fileEntry.fileSize - outPos);\n sectorData.copy(output, outPos, 0, copyLen);\n outPos += copyLen;\n }\n\n return output;\n}\n\n/**\n * Read a single-unit file (no sector division).\n *\n * Files with MPQ_FILE_SINGLE_UNIT flag are stored as a single block\n * without sector offset tables.\n *\n * @param stream - File stream\n * @param archiveOffset - Offset of the MPQ archive within the file\n * @param fileEntry - File entry metadata\n * @param fileKey - File encryption key (0 if not encrypted)\n * @returns Complete file data\n */\nexport function readSingleUnitFile(\n stream: FileStream,\n archiveOffset: bigint,\n fileEntry: FileEntry,\n fileKey: number,\n): Buffer {\n const fileOffset = archiveOffset + fileEntry.byteOffset;\n\n // Read the entire compressed/encrypted data\n let data = stream.read(fileOffset, fileEntry.cmpSize);\n\n if (data.length < fileEntry.cmpSize) {\n throw new MpqCorruptError('Single-unit file data is truncated');\n }\n\n // Decrypt if encrypted\n if (fileEntry.flags & MPQ_FILE_ENCRYPTED) {\n data = Buffer.from(data);\n decryptBlock(data, fileKey);\n }\n\n // Decompress if needed\n if (fileEntry.cmpSize < fileEntry.fileSize) {\n if (fileEntry.flags & MPQ_FILE_COMPRESS) {\n data = decompress(data, fileEntry.fileSize);\n } else if (fileEntry.flags & MPQ_FILE_IMPLODE) {\n data = decompressPkware(data, fileEntry.fileSize);\n }\n }\n\n return data;\n}\n","/**\n * MpqFile represents an open file handle within an MPQ archive.\n * It provides methods to read file data.\n */\n\nimport {\n MPQ_FILE_ENCRYPTED,\n MPQ_FILE_SINGLE_UNIT,\n MPQ_FILE_EXISTS,\n} from '../constants';\nimport type { FileEntry } from '../types';\nimport { decryptFileKey } from '../crypto/cipher';\nimport { readSectorFile, readSingleUnitFile } from './sector-reader.browser';\nimport { FileStream } from '../stream/file-stream.browser';\n\nexport class MpqFile {\n private stream: FileStream;\n private archiveOffset: bigint;\n private entry: FileEntry;\n private sectorSize: number;\n private fileKey: number;\n private closed = false;\n\n constructor(\n stream: FileStream,\n archiveOffset: bigint,\n entry: FileEntry,\n sectorSize: number,\n ) {\n this.stream = stream;\n this.archiveOffset = archiveOffset;\n this.entry = entry;\n this.sectorSize = sectorSize;\n\n // Calculate the file key if the file is encrypted\n if (entry.flags & MPQ_FILE_ENCRYPTED) {\n this.fileKey = decryptFileKey(\n entry.fileName,\n entry.byteOffset,\n entry.fileSize,\n entry.flags,\n );\n } else {\n this.fileKey = 0;\n }\n }\n\n /** Uncompressed file size */\n get size(): number {\n return this.entry.fileSize;\n }\n\n /** Compressed file size */\n get compressedSize(): number {\n return this.entry.cmpSize;\n }\n\n /** File flags */\n get flags(): number {\n return this.entry.flags;\n }\n\n /** File name */\n get name(): string {\n return this.entry.fileName;\n }\n\n /** The underlying file entry */\n get fileEntry(): FileEntry {\n return this.entry;\n }\n\n /**\n * Read the entire file contents.\n *\n * @returns Buffer containing the decompressed file data\n */\n read(): Buffer {\n if (this.closed) {\n throw new Error('File handle is closed');\n }\n\n if (this.entry.fileSize === 0) {\n return Buffer.alloc(0);\n }\n\n if (this.entry.flags & MPQ_FILE_SINGLE_UNIT) {\n return readSingleUnitFile(\n this.stream,\n this.archiveOffset,\n this.entry,\n this.fileKey,\n );\n }\n\n return readSectorFile(\n this.stream,\n this.archiveOffset,\n this.entry,\n this.sectorSize,\n this.fileKey,\n );\n }\n\n /**\n * Close the file handle.\n */\n close(): void {\n this.closed = true;\n }\n}\n","/**\n * MpqArchive - Main class for reading MPQ archives.\n *\n * This is the primary public API for opening and reading MPQ files.\n */\n\nimport {\n LISTFILE_NAME,\n ATTRIBUTES_NAME,\n MPQ_FILE_EXISTS,\n BLOCK_INDEX_MASK,\n MPQ_OPEN_NO_LISTFILE,\n MPQ_OPEN_NO_ATTRIBUTES,\n MPQ_OPEN_NO_HEADER_SEARCH,\n MPQ_OPEN_FORCE_MPQ_V1,\n MPQ_FORMAT_VERSION_1,\n MPQ_FORMAT_VERSION_3,\n} from '../constants';\nimport type { MpqHeader, MpqHashEntry, MpqBlockEntry, FileEntry, FileFindData, OpenArchiveOptions } from '../types';\nimport type { FileStream as NodeFileStream } from '../stream/file-stream';\nimport { MpqNotFoundError, MpqError, MpqEncryptionError } from '../errors';\nimport { FileStream } from '../stream/file-stream.browser';\nimport {\n findHeader,\n getSectorSize,\n getHashTableOffset,\n getBlockTableOffset,\n getHiBlockTableOffset,\n getHetTableOffset,\n getBetTableOffset,\n type HeaderSearchResult,\n} from './header';\nimport { loadHashTable, findHashEntry } from '../tables/hash-table';\nimport { loadBlockTable, loadHiBlockTable } from '../tables/block-table';\nimport { loadHetTable, findInHetTable, type HetTable } from '../tables/het-table';\nimport { loadBetTable, getBetEntry, type BetTable } from '../tables/bet-table';\nimport { buildFileTable, buildFileTableFromHetBet, applyFileNames } from '../tables/file-table';\nimport { parseListfile, parseAttributes, type FileAttributes } from './listfile';\nimport { MpqFile } from '../file/mpq-file.browser';\n\ntype BinaryInput = Buffer | Uint8Array | ArrayBuffer;\nconst asNodeStream = (stream: FileStream): NodeFileStream => stream as unknown as NodeFileStream;\n\nexport class MpqArchive {\n private stream: FileStream;\n private header: MpqHeader;\n private archiveOffset: bigint;\n private sectorSize: number;\n\n private hashTable: MpqHashEntry[] = [];\n private blockTable: MpqBlockEntry[] = [];\n private hiBlockTable: Uint16Array | null = null;\n private hetTable: HetTable | null = null;\n private betTable: BetTable | null = null;\n private fileEntries: FileEntry[] = [];\n private attributes: FileAttributes | null = null;\n private closed = false;\n\n private constructor(\n stream: FileStream,\n header: MpqHeader,\n archiveOffset: bigint,\n ) {\n this.stream = stream;\n this.header = header;\n this.archiveOffset = archiveOffset;\n this.sectorSize = getSectorSize(header);\n }\n\n /**\n * Open an MPQ archive from a file path.\n *\n * @param path - Path to the MPQ file\n * @param options - Open options\n * @returns MpqArchive instance\n */\n static open(path: string, options?: OpenArchiveOptions): MpqArchive {\n const stream = FileStream.open(path);\n return MpqArchive.openFromStream(stream, options);\n }\n\n /**\n * Open an MPQ archive from in-memory binary data.\n *\n * This API works in both Node.js and browser runtimes.\n *\n * @param data - MPQ binary contents\n * @param options - Open options\n * @returns MpqArchive instance\n */\n static openFromBuffer(data: BinaryInput, options?: OpenArchiveOptions): MpqArchive {\n const stream = FileStream.fromBuffer(data);\n return MpqArchive.openFromStream(stream, options);\n }\n\n private static openFromStream(stream: FileStream, options?: OpenArchiveOptions): MpqArchive {\n try {\n const noHeaderSearch = options?.noHeaderSearch ?? false;\n const { header, headerOffset } = findHeader(asNodeStream(stream), noHeaderSearch);\n\n // Force V1 if requested\n if (options?.forceMpqV1) {\n header.wFormatVersion = MPQ_FORMAT_VERSION_1;\n }\n\n const archive = new MpqArchive(stream, header, headerOffset);\n archive.loadTables();\n\n // Load (listfile) unless disabled\n if (!options?.noListfile) {\n archive.loadListfile();\n }\n\n // Load (attributes) unless disabled\n if (!options?.noAttributes) {\n archive.loadAttributes();\n }\n\n return archive;\n } catch (err) {\n stream.close();\n throw err;\n }\n }\n\n /**\n * Load all archive tables (hash, block, HET, BET).\n */\n private loadTables(): void {\n const h = this.header;\n const stream = asNodeStream(this.stream);\n\n // Load classic hash and block tables\n if (h.dwHashTableSize > 0) {\n const hashOffset = getHashTableOffset(h, this.archiveOffset);\n this.hashTable = loadHashTable(stream, hashOffset, h.dwHashTableSize);\n }\n\n if (h.dwBlockTableSize > 0) {\n const blockOffset = getBlockTableOffset(h, this.archiveOffset);\n this.blockTable = loadBlockTable(stream, blockOffset, h.dwBlockTableSize);\n }\n\n // Load hi-block table (V2+)\n const hiBlockOffset = getHiBlockTableOffset(h, this.archiveOffset);\n if (hiBlockOffset !== 0n && this.blockTable.length > 0) {\n this.hiBlockTable = loadHiBlockTable(stream, hiBlockOffset, this.blockTable.length);\n }\n\n // Load HET/BET tables (V3+)\n if (h.wFormatVersion >= MPQ_FORMAT_VERSION_3) {\n const hetOffset = getHetTableOffset(h, this.archiveOffset);\n if (hetOffset !== 0n) {\n this.hetTable = loadHetTable(stream, hetOffset, h.hetTableSize64);\n }\n\n const betOffset = getBetTableOffset(h, this.archiveOffset);\n if (betOffset !== 0n) {\n this.betTable = loadBetTable(stream, betOffset, h.betTableSize64);\n }\n }\n\n // Build the unified file table\n if (this.hetTable && this.betTable) {\n this.fileEntries = buildFileTableFromHetBet(this.hetTable, this.betTable);\n } else {\n this.fileEntries = buildFileTable(this.hashTable, this.blockTable, this.hiBlockTable);\n }\n }\n\n /**\n * Load and parse the (listfile) to populate file names.\n */\n private loadListfile(): void {\n try {\n const data = this.extractFileByName(LISTFILE_NAME);\n if (data) {\n const names = parseListfile(data);\n applyFileNames(this.fileEntries, this.hashTable, names);\n }\n } catch {\n // (listfile) may not exist - that's okay\n }\n }\n\n /**\n * Load and parse the (attributes) file.\n */\n private loadAttributes(): void {\n try {\n const data = this.extractFileByName(ATTRIBUTES_NAME);\n if (data) {\n this.attributes = parseAttributes(data, this.fileEntries.length);\n\n // Apply attributes to file entries\n if (this.attributes) {\n for (let i = 0; i < this.fileEntries.length; i++) {\n if (i < this.attributes.crc32s.length) {\n this.fileEntries[i].crc32 = this.attributes.crc32s[i];\n }\n if (i < this.attributes.fileTimes.length) {\n this.fileEntries[i].fileTime = this.attributes.fileTimes[i];\n }\n if (i < this.attributes.md5s.length) {\n this.fileEntries[i].md5 = this.attributes.md5s[i];\n }\n }\n }\n }\n } catch {\n // (attributes) may not exist - that's okay\n }\n }\n\n /**\n * Internal: extract a file by name without relying on the listfile.\n * Used for bootstrapping (listfile) and (attributes) loading.\n */\n private extractFileByName(fileName: string): Buffer | null {\n // Try hash table lookup first\n const hashEntry = findHashEntry(\n this.hashTable,\n fileName,\n 0,\n this.fileEntries.length,\n );\n\n if (!hashEntry) return null;\n\n const blockIndex = hashEntry.dwBlockIndex & BLOCK_INDEX_MASK;\n if (blockIndex >= this.fileEntries.length) return null;\n\n const entry = this.fileEntries[blockIndex];\n if (!(entry.flags & MPQ_FILE_EXISTS)) return null;\n\n // Temporarily set the file name for key derivation\n const savedName = entry.fileName;\n entry.fileName = fileName;\n\n try {\n const file = new MpqFile(this.stream, this.archiveOffset, entry, this.sectorSize);\n const data = file.read();\n file.close();\n return data;\n } finally {\n entry.fileName = savedName;\n }\n }\n\n /**\n * Check if a file exists in the archive.\n */\n hasFile(name: string): boolean {\n this.ensureOpen();\n\n // Try HET table first (V3+)\n if (this.hetTable && this.betTable) {\n const betIndex = findInHetTable(this.hetTable, name);\n if (betIndex >= 0 && betIndex < this.fileEntries.length) {\n return !!(this.fileEntries[betIndex].flags & MPQ_FILE_EXISTS);\n }\n }\n\n // Fall back to hash table\n const hashEntry = findHashEntry(this.hashTable, name, 0, this.fileEntries.length);\n if (!hashEntry) return false;\n\n const blockIndex = hashEntry.dwBlockIndex & BLOCK_INDEX_MASK;\n return blockIndex < this.fileEntries.length &&\n !!(this.fileEntries[blockIndex].flags & MPQ_FILE_EXISTS);\n }\n\n /**\n * Open a file within the archive for reading.\n *\n * @param name - File path within the archive\n * @returns MpqFile handle\n */\n openFile(name: string): MpqFile {\n this.ensureOpen();\n\n const entry = this.resolveFile(name);\n if (!entry) {\n throw new MpqNotFoundError(`File not found: ${name}`);\n }\n\n return new MpqFile(this.stream, this.archiveOffset, entry, this.sectorSize);\n }\n\n /**\n * Extract a file's contents directly.\n *\n * @param name - File path within the archive\n * @returns Buffer with the file contents\n */\n extractFile(name: string): Buffer {\n const file = this.openFile(name);\n try {\n return file.read();\n } finally {\n file.close();\n }\n }\n\n /**\n * Get the list of all known file names (from listfile).\n */\n getFileList(): string[] {\n this.ensureOpen();\n return this.fileEntries\n .filter(e => e.fileName && (e.flags & MPQ_FILE_EXISTS))\n .map(e => e.fileName);\n }\n\n /**\n * Find files matching a wildcard pattern.\n *\n * @param mask - Wildcard pattern (supports '*' and '?')\n * @returns Array of matching file entries\n */\n findFiles(mask = '*'): FileFindData[] {\n this.ensureOpen();\n const regex = wildcardToRegex(mask);\n const results: FileFindData[] = [];\n\n for (let i = 0; i < this.fileEntries.length; i++) {\n const entry = this.fileEntries[i];\n if (!(entry.flags & MPQ_FILE_EXISTS)) continue;\n if (!entry.fileName) continue;\n\n if (regex.test(entry.fileName)) {\n const plainName = entry.fileName.replace(/^.*[\\\\/]/, '');\n results.push({\n fileName: entry.fileName,\n plainName,\n hashIndex: 0,\n blockIndex: i,\n fileSize: entry.fileSize,\n compSize: entry.cmpSize,\n fileFlags: entry.flags,\n locale: 0,\n });\n }\n }\n\n return results;\n }\n\n /**\n * Apply an external list of file names to resolve unnamed entries.\n * Equivalent to providing an external listfile to SFileFindFirstFile.\n *\n * @param names - Array of file names to try\n * @returns Number of newly resolved names\n */\n addListfile(names: string[]): number {\n this.ensureOpen();\n const before = this.fileEntries.filter(e => e.fileName).length;\n applyFileNames(this.fileEntries, this.hashTable, names);\n return this.fileEntries.filter(e => e.fileName).length - before;\n }\n\n /**\n * Enumerate all existing file entries, including those without names.\n * Unnamed entries get a synthetic name like \"File00001234.xxx\".\n */\n enumerateFiles(): FileFindData[] {\n this.ensureOpen();\n const results: FileFindData[] = [];\n\n for (let i = 0; i < this.fileEntries.length; i++) {\n const entry = this.fileEntries[i];\n if (!(entry.flags & MPQ_FILE_EXISTS)) continue;\n\n const fileName = entry.fileName || `File${String(i).padStart(8, '0')}.xxx`;\n const plainName = fileName.replace(/^.*[\\\\/]/, '');\n results.push({\n fileName,\n plainName,\n hashIndex: 0,\n blockIndex: i,\n fileSize: entry.fileSize,\n compSize: entry.cmpSize,\n fileFlags: entry.flags,\n locale: 0,\n });\n }\n\n return results;\n }\n\n /** Get the archive header */\n getHeader(): Readonly<MpqHeader> {\n return this.header;\n }\n\n /** Get the number of file entries */\n get fileCount(): number {\n return this.fileEntries.length;\n }\n\n /** Get the sector size */\n getSectorSize(): number {\n return this.sectorSize;\n }\n\n /**\n * Close the archive and release resources.\n */\n close(): void {\n if (!this.closed) {\n this.stream.close();\n this.closed = true;\n }\n }\n\n /**\n * Resolve a file name to its FileEntry.\n */\n private resolveFile(name: string): FileEntry | null {\n // Try HET table first (V3+)\n if (this.hetTable && this.betTable) {\n const betIndex = findInHetTable(this.hetTable, name);\n if (betIndex >= 0 && betIndex < this.fileEntries.length) {\n const entry = this.fileEntries[betIndex];\n if (entry.flags & MPQ_FILE_EXISTS) {\n if (!entry.fileName) entry.fileName = name;\n return entry;\n }\n }\n }\n\n // Fall back to hash table\n const hashEntry = findHashEntry(this.hashTable, name, 0, this.fileEntries.length);\n if (!hashEntry) return null;\n\n const blockIndex = hashEntry.dwBlockIndex & BLOCK_INDEX_MASK;\n if (blockIndex >= this.fileEntries.length) return null;\n\n const entry = this.fileEntries[blockIndex];\n if (!(entry.flags & MPQ_FILE_EXISTS)) return null;\n\n // Ensure file name is set for encryption key derivation\n if (!entry.fileName) {\n entry.fileName = name;\n }\n\n return entry;\n }\n\n private ensureOpen(): void {\n if (this.closed) {\n throw new MpqError('Archive is closed');\n }\n }\n}\n\n/**\n * Convert a simple wildcard pattern to a RegExp.\n * Supports '*' (any characters) and '?' (any single character).\n */\nfunction wildcardToRegex(pattern: string): RegExp {\n const escaped = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*/g, '.*')\n .replace(/\\?/g, '.');\n return new RegExp(`^${escaped}$`, 'i');\n}\n"],"mappings":"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","names":["entry"]}
package/dist/index.d.mts CHANGED
@@ -1,163 +1,29 @@
1
- /** Raw MPQ header as parsed from disk (normalized to V4 superset) */
2
- interface MpqHeader {
3
- /** 'MPQ\x1A' signature */
4
- dwID: number;
5
- /** Size of the header */
6
- dwHeaderSize: number;
7
- /** 32-bit archive size (V1) */
8
- dwArchiveSize: number;
9
- /** Format version: 0=V1, 1=V2, 2=V3, 3=V4 */
10
- wFormatVersion: number;
11
- /** Sector size shift (sector size = 512 << wSectorSize) */
12
- wSectorSize: number;
13
- /** Hash table offset (relative to archive start) */
14
- dwHashTablePos: number;
15
- /** Block table offset (relative to archive start) */
16
- dwBlockTablePos: number;
17
- /** Number of hash table entries */
18
- dwHashTableSize: number;
19
- /** Number of block table entries */
20
- dwBlockTableSize: number;
21
- /** 64-bit hi-block table offset */
22
- hiBlockTablePos64: bigint;
23
- /** High 16 bits of hash table offset */
24
- wHashTablePosHi: number;
25
- /** High 16 bits of block table offset */
26
- wBlockTablePosHi: number;
27
- /** 64-bit archive size */
28
- archiveSize64: bigint;
29
- /** 64-bit BET table position */
30
- betTablePos64: bigint;
31
- /** 64-bit HET table position */
32
- hetTablePos64: bigint;
33
- /** Compressed hash table size */
34
- hashTableSize64: bigint;
35
- /** Compressed block table size */
36
- blockTableSize64: bigint;
37
- /** Compressed hi-block table size */
38
- hiBlockTableSize64: bigint;
39
- /** Compressed HET table size */
40
- hetTableSize64: bigint;
41
- /** Compressed BET table size */
42
- betTableSize64: bigint;
43
- /** Raw chunk size for MD5 */
44
- dwRawChunkSize: number;
45
- /** MD5 checksums (V4) */
46
- md5BlockTable: Uint8Array;
47
- md5HashTable: Uint8Array;
48
- md5HiBlockTable: Uint8Array;
49
- md5BetTable: Uint8Array;
50
- md5HetTable: Uint8Array;
51
- md5MpqHeader: Uint8Array;
52
- }
53
- /** User data header that may precede the MPQ header */
54
- interface MpqUserData {
55
- dwID: number;
56
- cbUserDataSize: number;
57
- dwHeaderOffs: number;
58
- cbUserDataHeader: number;
59
- }
60
- /** Hash table entry (16 bytes on disk) */
61
- interface MpqHashEntry {
62
- /** Hash of file path (method A) */
63
- dwName1: number;
64
- /** Hash of file path (method B) */
65
- dwName2: number;
66
- /** Windows LANGID locale */
67
- lcLocale: number;
68
- /** Platform (always 0) */
69
- platform: number;
70
- /** Index into block table (0xFFFFFFFF=free, 0xFFFFFFFE=deleted) */
71
- dwBlockIndex: number;
72
- }
73
- /** Block table entry (16 bytes on disk) */
74
- interface MpqBlockEntry {
75
- /** File data offset relative to archive start */
76
- dwFilePos: number;
77
- /** Compressed file size */
78
- dwCSize: number;
79
- /** Uncompressed file size */
80
- dwFSize: number;
81
- /** File flags */
82
- dwFlags: number;
83
- }
84
- /** Unified file entry (in-memory representation) */
85
- interface FileEntry {
86
- /** Jenkins hash (for BET table) */
87
- fileNameHash: bigint;
88
- /** File data offset relative to MPQ header */
89
- byteOffset: bigint;
90
- /** File time (FILETIME) from attributes */
91
- fileTime: bigint;
92
- /** Decompressed size */
93
- fileSize: number;
94
- /** Compressed size */
95
- cmpSize: number;
96
- /** File flags */
97
- flags: number;
98
- /** CRC32 from attributes */
99
- crc32: number;
100
- /** MD5 from attributes */
101
- md5: Uint8Array;
102
- /** File name (empty if unknown) */
103
- fileName: string;
104
- }
105
- /** HET table header */
106
- interface HetTableHeader {
107
- signature: number;
108
- version: number;
109
- dataSize: number;
110
- tableSize: number;
111
- maxFileCount: number;
112
- hashTableSize: number;
113
- hashEntrySize: number;
114
- totalIndexSize: number;
115
- indexSizeExtra: number;
116
- indexSize: number;
117
- blockTableSize: number;
118
- }
119
- /** BET table header */
120
- interface BetTableHeader {
121
- signature: number;
122
- version: number;
123
- dataSize: number;
124
- tableSize: number;
125
- maxFileCount: number;
126
- flagCount: number;
127
- }
128
- /** File search result */
129
- interface FileFindData {
130
- fileName: string;
131
- plainName: string;
132
- hashIndex: number;
133
- blockIndex: number;
134
- fileSize: number;
135
- compSize: number;
136
- fileFlags: number;
137
- locale: number;
138
- }
139
- /** Open archive flags */
140
- interface OpenArchiveOptions {
141
- noListfile?: boolean;
142
- noAttributes?: boolean;
143
- noHeaderSearch?: boolean;
144
- forceMpqV1?: boolean;
145
- checkSectorCrc?: boolean;
146
- }
1
+ import { F as FileEntry, O as OpenArchiveOptions, a as FileFindData, M as MpqHeader } from './storm-buffer-nHXHoPVG.mjs';
2
+ export { A as ATTRIBUTES_NAME, B as BetTableHeader, H as HetTableHeader, I as ID_MPQ, b as ID_MPQ_USERDATA, L as LISTFILE_NAME, c as MPQ_COMPRESSION_ADPCM_MONO, d as MPQ_COMPRESSION_ADPCM_STEREO, e as MPQ_COMPRESSION_BZIP2, f as MPQ_COMPRESSION_HUFFMANN, g as MPQ_COMPRESSION_LZMA, h as MPQ_COMPRESSION_PKWARE, i as MPQ_COMPRESSION_SPARSE, j as MPQ_COMPRESSION_ZLIB, k as MPQ_FILE_COMPRESS, l as MPQ_FILE_ENCRYPTED, m as MPQ_FILE_EXISTS, n as MPQ_FILE_IMPLODE, o as MPQ_FILE_KEY_V2, p as MPQ_FILE_SINGLE_UNIT, q as MPQ_FORMAT_VERSION_1, r as MPQ_FORMAT_VERSION_2, s as MPQ_FORMAT_VERSION_3, t as MPQ_FORMAT_VERSION_4, u as MpqBlockEntry, v as MpqCompressionError, w as MpqCorruptError, x as MpqEncryptionError, y as MpqError, z as MpqHashEntry, C as MpqNotFoundError, D as MpqUnsupportedError, E as MpqUserData, S as SIGNATURE_NAME, G as decryptBlock, J as decryptFileKey, K as encryptBlock, N as getStormBuffer, P as hashFileKey, Q as hashNameA, R as hashNameB, T as hashString, U as hashTableIndex, V as jenkinsHash } from './storm-buffer-nHXHoPVG.mjs';
147
3
 
148
4
  /**
149
5
  * File stream abstraction for reading MPQ archives from disk.
150
6
  * Uses Node.js fs module for synchronous positioned reads.
151
7
  */
8
+ type BinaryInput$1 = Buffer | Uint8Array | ArrayBuffer;
152
9
  declare class FileStream {
153
10
  private fd;
154
11
  private fileSize;
155
12
  private filePath;
13
+ private memoryData;
14
+ private static fsModule;
156
15
  private constructor();
157
16
  /**
158
17
  * Open a file for reading.
159
18
  */
160
19
  static open(path: string): FileStream;
20
+ /**
21
+ * Create an in-memory stream from binary data.
22
+ */
23
+ static fromBuffer(data: BinaryInput$1, filePath?: string): FileStream;
24
+ private static normalizeBuffer;
25
+ private static getRequire;
26
+ private static getFs;
161
27
  /**
162
28
  * Read bytes at a given offset.
163
29
  *
@@ -225,6 +91,7 @@ declare class MpqFile {
225
91
  * This is the primary public API for opening and reading MPQ files.
226
92
  */
227
93
 
94
+ type BinaryInput = Buffer | Uint8Array | ArrayBuffer;
228
95
  declare class MpqArchive {
229
96
  private stream;
230
97
  private header;
@@ -247,6 +114,17 @@ declare class MpqArchive {
247
114
  * @returns MpqArchive instance
248
115
  */
249
116
  static open(path: string, options?: OpenArchiveOptions): MpqArchive;
117
+ /**
118
+ * Open an MPQ archive from in-memory binary data.
119
+ *
120
+ * This API works in both Node.js and browser runtimes.
121
+ *
122
+ * @param data - MPQ binary contents
123
+ * @param options - Open options
124
+ * @returns MpqArchive instance
125
+ */
126
+ static openFromBuffer(data: BinaryInput, options?: OpenArchiveOptions): MpqArchive;
127
+ private static openFromStream;
250
128
  /**
251
129
  * Load all archive tables (hash, block, HET, BET).
252
130
  */
@@ -323,98 +201,4 @@ declare class MpqArchive {
323
201
  private ensureOpen;
324
202
  }
325
203
 
326
- declare class MpqError extends Error {
327
- constructor(message: string);
328
- }
329
- declare class MpqNotFoundError extends MpqError {
330
- constructor(message?: string);
331
- }
332
- declare class MpqCorruptError extends MpqError {
333
- constructor(message?: string);
334
- }
335
- declare class MpqUnsupportedError extends MpqError {
336
- constructor(message?: string);
337
- }
338
- declare class MpqEncryptionError extends MpqError {
339
- constructor(message?: string);
340
- }
341
- declare class MpqCompressionError extends MpqError {
342
- constructor(message?: string);
343
- }
344
-
345
- declare const ID_MPQ = 441536589;
346
- declare const ID_MPQ_USERDATA = 458313805;
347
- declare const MPQ_FORMAT_VERSION_1 = 0;
348
- declare const MPQ_FORMAT_VERSION_2 = 1;
349
- declare const MPQ_FORMAT_VERSION_3 = 2;
350
- declare const MPQ_FORMAT_VERSION_4 = 3;
351
- declare const MPQ_FILE_IMPLODE = 256;
352
- declare const MPQ_FILE_COMPRESS = 512;
353
- declare const MPQ_FILE_ENCRYPTED = 65536;
354
- declare const MPQ_FILE_KEY_V2 = 131072;
355
- declare const MPQ_FILE_SINGLE_UNIT = 16777216;
356
- declare const MPQ_FILE_EXISTS = 2147483648;
357
- declare const MPQ_COMPRESSION_HUFFMANN = 1;
358
- declare const MPQ_COMPRESSION_ZLIB = 2;
359
- declare const MPQ_COMPRESSION_PKWARE = 8;
360
- declare const MPQ_COMPRESSION_BZIP2 = 16;
361
- declare const MPQ_COMPRESSION_LZMA = 18;
362
- declare const MPQ_COMPRESSION_SPARSE = 32;
363
- declare const MPQ_COMPRESSION_ADPCM_MONO = 64;
364
- declare const MPQ_COMPRESSION_ADPCM_STEREO = 128;
365
- declare const LISTFILE_NAME = "(listfile)";
366
- declare const SIGNATURE_NAME = "(signature)";
367
- declare const ATTRIBUTES_NAME = "(attributes)";
368
-
369
- /**
370
- * Compute MPQ hash of a string. The hashType selects which section of the
371
- * StormBuffer to use:
372
- * - MPQ_HASH_TABLE_INDEX (0x000): hash table slot
373
- * - MPQ_HASH_NAME_A (0x100): file name verification hash 1
374
- * - MPQ_HASH_NAME_B (0x200): file name verification hash 2
375
- * - MPQ_HASH_FILE_KEY (0x300): encryption key derivation
376
- */
377
- declare function hashString(str: string, hashType: number): number;
378
- /** Convenience: compute hash table index */
379
- declare function hashTableIndex(fileName: string): number;
380
- /** Convenience: compute name hash A */
381
- declare function hashNameA(fileName: string): number;
382
- /** Convenience: compute name hash B */
383
- declare function hashNameB(fileName: string): number;
384
- /** Convenience: compute file encryption key hash */
385
- declare function hashFileKey(fileName: string): number;
386
- /**
387
- * Jenkins hashlittle2 - used for HET/BET tables (64-bit hash).
388
- * Produces a 64-bit hash as a BigInt.
389
- */
390
- declare function jenkinsHash(str: string): bigint;
391
-
392
- /**
393
- * Decrypt an MPQ data block in-place.
394
- * The buffer must be DWORD-aligned (length must be a multiple of 4).
395
- *
396
- * @param data - Buffer to decrypt (modified in place)
397
- * @param key - Primary encryption key (DWORD)
398
- */
399
- declare function decryptBlock(data: Buffer, key: number): void;
400
- /**
401
- * Encrypt an MPQ data block in-place.
402
- *
403
- * @param data - Buffer to encrypt (modified in place)
404
- * @param key - Primary encryption key (DWORD)
405
- */
406
- declare function encryptBlock(data: Buffer, key: number): void;
407
- /**
408
- * Calculate the decryption key for a file within an MPQ archive.
409
- *
410
- * @param fileName - Full path of the file within the archive
411
- * @param byteOffset - File's offset within the archive (lower 32 bits)
412
- * @param fileSize - Uncompressed file size
413
- * @param flags - File flags from block table
414
- * @returns The file decryption key
415
- */
416
- declare function decryptFileKey(fileName: string, byteOffset: bigint, fileSize: number, flags: number): number;
417
-
418
- declare function getStormBuffer(): Uint32Array;
419
-
420
- export { ATTRIBUTES_NAME, type BetTableHeader, type FileEntry, type FileFindData, type HetTableHeader, ID_MPQ, ID_MPQ_USERDATA, LISTFILE_NAME, MPQ_COMPRESSION_ADPCM_MONO, MPQ_COMPRESSION_ADPCM_STEREO, MPQ_COMPRESSION_BZIP2, MPQ_COMPRESSION_HUFFMANN, MPQ_COMPRESSION_LZMA, MPQ_COMPRESSION_PKWARE, MPQ_COMPRESSION_SPARSE, MPQ_COMPRESSION_ZLIB, MPQ_FILE_COMPRESS, MPQ_FILE_ENCRYPTED, MPQ_FILE_EXISTS, MPQ_FILE_IMPLODE, MPQ_FILE_KEY_V2, MPQ_FILE_SINGLE_UNIT, MPQ_FORMAT_VERSION_1, MPQ_FORMAT_VERSION_2, MPQ_FORMAT_VERSION_3, MPQ_FORMAT_VERSION_4, MpqArchive, type MpqBlockEntry, MpqCompressionError, MpqCorruptError, MpqEncryptionError, MpqError, MpqFile, type MpqHashEntry, type MpqHeader, MpqNotFoundError, MpqUnsupportedError, type MpqUserData, type OpenArchiveOptions, SIGNATURE_NAME, decryptBlock, decryptFileKey, encryptBlock, getStormBuffer, hashFileKey, hashNameA, hashNameB, hashString, hashTableIndex, jenkinsHash };
204
+ export { FileEntry, FileFindData, MpqArchive, MpqFile, MpqHeader, OpenArchiveOptions };