UNPKG

igir

Version:

🕹 A zero-setup ROM collection manager that sorts, filters, extracts or archives, patches, and reports on collections of any size on any OS.

130 lines (129 loc) • 4.21 kB
import CP437Decoder from './cp437Decoder.js'; import TimestampUtil from './timestampUtil.js'; export const CompressionMethod = { STORE: 0, SHRUNK: 1, REDUCED_FACTOR_1: 2, REDUCED_FACTOR_2: 3, REDUCED_FACTOR_3: 4, REDUCED_FACTOR_4: 5, IMPLODE: 6, DEFLATE: 8, DEFLATE64: 9, PKWARE_IMPLODE: 10, BZIP2: 12, LZMA: 14, IBM_CMPSC: 16, IBM_TERSE: 18, IBM_LZ77: 19, ZSTD_DEPRECATED: 20, ZSTD: 93, MP3: 94, XZ: 95, JPEG_VARIANT: 96, WAVPACK: 97, PPMD: 98, AE_X: 99, }; export const CompressionMethodInverted = Object.fromEntries(Object.entries(CompressionMethod).map(([key, value]) => [value, key])); /** * A zip file record, with properties and functionality common to local file headers and central * directory file headers. */ export default class FileRecord { raw; versionNeeded; generalPurposeBitFlag; compressionMethod; fileModificationTime; fileModificationDate; uncompressedCrc32; compressedSize; uncompressedSize; fileNameLength; extraFieldLength; fileName; extraFields; zip64ExtendedInformation; constructor(props) { this.raw = props.raw; this.versionNeeded = props.versionNeeded; this.generalPurposeBitFlag = props.generalPurposeBitFlag; this.compressionMethod = props.compressionMethod; this.fileModificationTime = props.fileModificationTime; this.fileModificationDate = props.fileModificationDate; this.uncompressedCrc32 = props.uncompressedCrc32; this.compressedSize = props.compressedSize; this.uncompressedSize = props.uncompressedSize; this.fileNameLength = props.fileNameLength; this.extraFieldLength = props.extraFieldLength; this.fileName = props.fileName; this.extraFields = props.extraFields; this.zip64ExtendedInformation = props.zip64ExtendedInformation; } /** * Return the file modification time, taking extra fields into account. */ fileModificationResolved() { // TODO(cemmer): 0x000d UNIX timestamp // TODO(cemmer): 0x7855 unix extra field new? return (TimestampUtil.parseExtendedTimestamp(this.extraFields.get(0x54_55))?.modified ?? TimestampUtil.parseUnixExtraTimestamp(this.extraFields.get(0x58_55))?.modified ?? TimestampUtil.parseNtfsExtraTimestamp(this.extraFields.get(0x00_0a))?.modified ?? TimestampUtil.parseDOSTimestamp(this.fileModificationTime, this.fileModificationDate)); } /** * Return the numerical CRC32. */ uncompressedCrc32Number() { return this.uncompressedCrc32.readUInt32LE(); } /** * Return the lowercased and padded CRC32 string */ uncompressedCrc32String() { return this.uncompressedCrc32Number().toString(16).toLowerCase().padStart(8, '0'); } /** * Return the compressed size, taking zip64 information into account. */ compressedSizeResolved() { return (this.zip64ExtendedInformation?.compressedSize ?? this.compressedSize - (this.isEncrypted() ? 12 : 0)); } /** * Return the uncompressed size, taking zip64 information into account. */ uncompressedSizeResolved() { return this.zip64ExtendedInformation?.uncompressedSize ?? this.uncompressedSize; } /** * Return the file name, taking extra fields into account. */ fileNameResolved() { return ( // Info-ZIP Unicode Path Extra Field this.extraFields.get(0x70_75)?.subarray(5).toString('utf8') ?? (this.generalPurposeBitFlag & 0x8_00 ? this.fileName.toString('utf8') : CP437Decoder.decode(this.fileName))); } /** * Return if this file is encrypted. */ isEncrypted() { return (this.generalPurposeBitFlag & 0x01) !== 0; } /** * Return if this file has a data descriptor. */ hasDataDescriptor() { return (this.generalPurposeBitFlag & 0x08) !== 0; } /** * Return if this file is a directory. */ isDirectory() { return this.fileNameResolved().endsWith('/'); } }