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.
189 lines (188 loc) • 9 kB
JavaScript
import CP437Decoder from "./cp437Decoder.js";
class EndOfCentralDirectory {
static END_OF_CENTRAL_DIRECTORY_RECORD_SIGNATURE = Buffer.from(
"06054b50",
"hex"
).reverse();
static ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIGNATURE = Buffer.from(
"07064b50",
"hex"
).reverse();
static ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIGNATURE = Buffer.from(
"06064b50",
"hex"
).reverse();
// Size with the signature, and without variable length fields at the end
static END_OF_CENTRAL_DIRECTORY_RECORD_SIZE = 22;
static ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIZE = 20;
static ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE = 56;
// Maximum size of the non-zip64 EOCD (~64KiB)
static END_OF_CENTRAL_DIRECTORY_MAX_SIZE = 22 + 65535;
diskNumber;
centralDirectoryDiskStart;
centralDirectoryDiskRecordsCount;
centralDirectoryTotalRecordsCount;
centralDirectorySizeBytes;
centralDirectoryOffset;
comment;
zip64Locator;
zip64Record;
constructor(props) {
this.diskNumber = props.diskNumber;
this.centralDirectoryDiskStart = props.centralDirectoryDiskStart;
this.centralDirectoryDiskRecordsCount = props.centralDirectoryDiskRecordsCount;
this.centralDirectoryTotalRecordsCount = props.centralDirectoryTotalRecordsCount;
this.centralDirectorySizeBytes = props.centralDirectorySizeBytes;
this.centralDirectoryOffset = props.centralDirectoryOffset;
this.comment = props.comment;
this.zip64Locator = props.zip64Locator;
this.zip64Record = props.zip64Record;
}
/**
* Parse the end of central directory in a file.
*/
static async fromFileHandle(fileHandle) {
const fileSize = (await fileHandle.stat()).size;
const filePosition = Math.max(fileSize - 1 - this.END_OF_CENTRAL_DIRECTORY_MAX_SIZE, 0);
const buffer = Buffer.allocUnsafe(Math.min(this.END_OF_CENTRAL_DIRECTORY_MAX_SIZE, fileSize));
const readResult = await fileHandle.read({ buffer, position: filePosition });
const eocdPosition = buffer.subarray(0, readResult.bytesRead).lastIndexOf(this.END_OF_CENTRAL_DIRECTORY_RECORD_SIGNATURE);
if (eocdPosition === -1) {
throw new Error("could not find end of central directory record");
}
return await this.readEndOfCentralDirectoryRecordZip(fileHandle, filePosition + eocdPosition);
}
static async readEndOfCentralDirectoryRecordZip(fileHandle, eocdPosition) {
const buffer = Buffer.allocUnsafe(this.END_OF_CENTRAL_DIRECTORY_RECORD_SIZE);
await fileHandle.read({ buffer, position: eocdPosition });
if (!buffer.subarray(0, 4).equals(this.END_OF_CENTRAL_DIRECTORY_RECORD_SIGNATURE)) {
throw new Error("bad end of central directory record position");
}
const diskNumber = buffer.readUInt16LE(4);
const centralDirectoryDiskStart = buffer.readUInt16LE(6);
const centralDirectoryDiskRecordsCount = buffer.readUInt16LE(8);
const centralDirectoryTotalRecordsCount = buffer.readUInt16LE(10);
const centralDirectorySizeBytes = buffer.readUInt32LE(12);
const centralDirectoryOffset = buffer.readUInt32LE(16);
const commentLength = buffer.readUInt16LE(20);
let commentBuffer;
if (commentLength === 0) {
commentBuffer = Buffer.allocUnsafe(0);
} else if (commentLength <= buffer.length) {
const readResult = await fileHandle.read({
buffer,
position: eocdPosition + this.END_OF_CENTRAL_DIRECTORY_RECORD_SIZE,
length: commentLength
});
commentBuffer = readResult.buffer.subarray(0, readResult.bytesRead);
} else {
const readResult = await fileHandle.read({
buffer: Buffer.allocUnsafe(commentLength),
position: eocdPosition + this.END_OF_CENTRAL_DIRECTORY_RECORD_SIZE
});
commentBuffer = readResult.buffer;
}
const comment = CP437Decoder.decode(commentBuffer);
let zip64EndOfCentralDirectoryLocator;
let zip64EndOfCentralDirectoryRecord;
if (centralDirectoryDiskStart === 65535 || centralDirectoryDiskRecordsCount === 65535 || centralDirectoryTotalRecordsCount === 65535 || centralDirectorySizeBytes === 4294967295 || centralDirectoryOffset === 4294967295) {
zip64EndOfCentralDirectoryLocator = await this.readZip64EndOfCentralDirectoryLocator(
fileHandle,
eocdPosition - this.ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIZE
);
if (zip64EndOfCentralDirectoryLocator.centralDirectoryDiskStart !== 0 || zip64EndOfCentralDirectoryLocator.diskCount !== 1) {
throw new Error("multi-disk zip64 zips aren't supported");
}
zip64EndOfCentralDirectoryRecord = await this.readZip64EndOfCentralDirectoryRecord(
fileHandle,
zip64EndOfCentralDirectoryLocator.centralDirectoryOffset
);
}
return new EndOfCentralDirectory({
diskNumber,
centralDirectoryDiskStart,
centralDirectoryDiskRecordsCount,
centralDirectoryTotalRecordsCount,
centralDirectorySizeBytes,
centralDirectoryOffset,
comment,
zip64Locator: zip64EndOfCentralDirectoryLocator,
zip64Record: zip64EndOfCentralDirectoryRecord
});
}
static async readZip64EndOfCentralDirectoryLocator(fileHandle, locatorPosition) {
const buffer = Buffer.allocUnsafe(this.ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIZE);
await fileHandle.read({ buffer, position: locatorPosition });
if (!buffer.subarray(0, 4).equals(this.ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIGNATURE)) {
throw new Error("bad zip64 end of central directory locator position");
}
return {
centralDirectoryDiskStart: buffer.readUInt32LE(4),
centralDirectoryOffset: Number(buffer.readBigUInt64LE(8)),
diskCount: buffer.readUInt32LE(16)
};
}
static async readZip64EndOfCentralDirectoryRecord(fileHandle, eocdPosition) {
const fixedLengthBuffer = Buffer.allocUnsafe(this.ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE);
await fileHandle.read({ buffer: fixedLengthBuffer, position: eocdPosition });
if (!fixedLengthBuffer.subarray(0, 4).equals(this.ZIP64_END_OF_CENTRAL_DIRECTORY_RECORD_SIGNATURE)) {
throw new Error(`bad zip64 end of central directory record position`);
}
const eocdSize = fixedLengthBuffer.readBigUInt64LE(4) + 12n;
const commentBuffer = Buffer.allocUnsafe(Number(eocdSize - 56n));
if (commentBuffer.length > 0) {
await fileHandle.read({ buffer: commentBuffer, position: eocdPosition + 56 });
}
return {
versionMadeBy: fixedLengthBuffer.readUInt16LE(12),
versionNeeded: fixedLengthBuffer.readUInt16LE(14),
diskNumber: fixedLengthBuffer.readUInt32LE(16),
centralDirectoryDiskStart: fixedLengthBuffer.readUInt32LE(20),
centralDirectoryDiskRecordsCount: Number(fixedLengthBuffer.readBigUInt64LE(24)),
centralDirectoryTotalRecordsCount: Number(fixedLengthBuffer.readBigUInt64LE(32)),
centralDirectorySizeBytes: Number(fixedLengthBuffer.readBigUInt64LE(40)),
centralDirectoryOffset: Number(fixedLengthBuffer.readBigUInt64LE(48)),
comment: CP437Decoder.decode(commentBuffer)
};
}
/**
* Return this EOCD's disk number, taking zip64 information into account.
*/
diskNumberResolved() {
return this.diskNumber === 65535 && this.zip64Record !== void 0 ? this.zip64Record.diskNumber : this.diskNumber;
}
/**
* Return the disk that contains the SOCD, taking zip64 information into account.
*/
centralDirectoryDiskStartResolved() {
return this.centralDirectoryDiskStart === 65535 && this.zip64Record !== void 0 ? this.zip64Record.centralDirectoryDiskStart : this.centralDirectoryDiskStart;
}
/**
* Return the number of central directory records on this disk, taking zip64 information into account.
*/
centralDirectoryDiskRecordsCountResolved() {
return this.centralDirectoryDiskRecordsCount === 65535 && this.zip64Record !== void 0 ? this.zip64Record.centralDirectoryDiskRecordsCount : this.centralDirectoryDiskRecordsCount;
}
/**
* Return the total number of central directory records, taking zip64 information into account.
*/
centralDirectoryTotalRecordsCountResolved() {
return this.centralDirectoryTotalRecordsCount === 65535 && this.zip64Record !== void 0 ? this.zip64Record.centralDirectoryTotalRecordsCount : this.centralDirectoryTotalRecordsCount;
}
/**
* Return the total size of the central directory, taking zip64 information into account.
*/
centralDirectorySizeBytesResolved() {
return this.centralDirectorySizeBytes === 4294967295 && this.zip64Record !== void 0 ? this.zip64Record.centralDirectorySizeBytes : this.centralDirectorySizeBytes;
}
/**
* Return the relative offset to the SOCD, taking zip64 information into account.
*/
centralDirectoryOffsetResolved() {
return this.centralDirectoryOffset === 4294967295 && this.zip64Record !== void 0 ? this.zip64Record.centralDirectoryOffset : this.centralDirectoryOffset;
}
}
export {
EndOfCentralDirectory as default
};
//# sourceMappingURL=endOfCentralDirectory.js.map