can1357/oh-my-pi · error · ArchiveError
Invalid ZIP archive: missing ZIP64 end of central directory
Error message
Invalid ZIP archive: missing ZIP64 end of central directory
What it means
readZip64Info is only called when the ZIP needs ZIP64 decoding (the locator was found), yet no candidate location yields a record with the ZIP64 EOCD signature and a consistent extensible-data size ending exactly at the locator. The reader throws this ArchiveError because ZIP64 metadata it was told exists cannot actually be located.
Source
Thrown at packages/utils/src/ar/zip.ts:163
const record = await source.read(candidate, candidate + ZIP64_EOCD_LENGTH);
if (record.byteLength !== ZIP64_EOCD_LENGTH || readUInt32LE(record, 0) !== ZIP64_EOCD_SIGNATURE) continue;
const extensibleSize = readUInt64LE(record, 4);
if (extensibleSize < 44 || candidate + 12 + extensibleSize !== locatorOffset) continue;
if (readUInt32LE(record, 16) !== 0 || readUInt32LE(record, 20) !== 0) {
throw new ArchiveError("Multi-volume ZIP archives are not supported");
}
const entriesOnDisk = readUInt64LE(record, 24);
const entries = readUInt64LE(record, 32);
if (entriesOnDisk !== entries) throw new ArchiveError("Multi-volume ZIP archives are not supported");
return {
entries,
size: readUInt64LE(record, 40),
offset: readUInt64LE(record, 48),
physicalEnd: candidate,
archiveOffset: 0,
};
}
throw new ArchiveError("Invalid ZIP archive: missing ZIP64 end of central directory");
}
async function locateCentralDirectory(source: ByteSource, info: CentralDirectoryInfo): Promise<number> {
if (info.entries === 0) return info.offset;
const candidates = [info.offset];
const adjacent = info.physicalEnd - info.size;
if (adjacent !== info.offset) candidates.push(adjacent);
for (const offset of candidates) {
if (offset < 0 || offset + 4 > source.size || offset + info.size > source.size) continue;
const signature = await source.read(offset, offset + 4);
if (signature.byteLength === 4 && readUInt32LE(signature, 0) === CENTRAL_HEADER_SIGNATURE) return offset;
}
throw new ArchiveError("Invalid ZIP archive: central directory is out of bounds or malformed");
}
async function readCentralDirectoryInfo(source: ByteSource, limits: ArchiveLimits): Promise<CentralDirectoryInfo> {
if (source.size < EOCD_LENGTH) throw new ArchiveError("Invalid ZIP archive: missing end of central directory");
const tailLength = Math.min(source.size, EOCD_LENGTH + MAX_COMMENT_LENGTH);View on GitHub (pinned to 9690622007)
Solutions
- Re-create the archive with a current zip implementation (Info-ZIP, 7-Zip) that writes ZIP64 correctly
- Check for truncation — ZIP64 records are near the end, so a cut file loses them first
- If the file has a prepended stub, use a reader path that accounts for the prefix offset, or strip the stub
- Validate with `unzip -t`; mismatch there confirms structural damage
Example fix
// before
const zip = await readZip(sfxBytes); // ZIP64 EOCD offset shifted by stub
// after
const raw = await Bun.file("app.sfx").bytes();
const stripped = stripSfxStub(raw); // remove prepended bytes so offsets line up
const zip = await readZip(stripped); Defensive patterns
Strategy: validation
Validate before calling
// require ZIP64 structures before parsing huge zips
function needsZip64AndHasLocator(bytes: Uint8Array): boolean {
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
// find EOCD
for (let i = bytes.length - 22; i >= Math.max(0, bytes.length - 22 - 65535); i--) {
if (dv.getUint32(i, true) !== 0x06054b50) continue;
const entries = dv.getUint16(i + 10, true);
const cdSize = dv.getUint32(i + 12, true);
const needs64 = entries === 0xffff || cdSize === 0xffffffff;
if (!needs64) return false;
// locator must exist just before EOCD
const loc = i - 20;
return loc >= 0 && dv.getUint32(loc, true) === 0x07064b50;
}
return false;
} Try / catch
try {
const zip = await readZip(bytes);
} catch (err) {
if (err instanceof ArchiveError && err.message.includes("missing ZIP64 end of central directory")) {
return rejectUpload("zip64 metadata missing/corrupt — archive may be truncated or stub-prefixed");
}
throw err;
} Prevention
- For >4GB or >65535-entry archives, verify completeness before parsing — ZIP64 records sit at the tail and are the first casualty of truncation
- Strip or account for SFX/prefix stubs that shift ZIP64 offsets
- Re-create archives with modern zip tools if third-party software produced malformed ZIP64 structures
When it happens
Trigger: readZip on an archive with a valid ZIP64 locator but a missing, moved, or corrupted ZIP64 EOCD record; prepended data (self-extractor stubs, concatenated files) shifting offsets so the declared ZIP64 EOCD offset no longer matches.
Common situations: Very large archives (>4GB or >65535 entries) that require ZIP64, whose final records were truncated; SFX stubs prepended after creation; tools that rewrite the EOCD but not the ZIP64 structures.
Related errors
- Invalid ZIP archive: missing end of central directory
- Invalid ZIP archive: ${what} has an invalid range
- Multi-volume ZIP archives are not supported
- Invalid ZIP archive: missing ZIP64 central-directory metadat
- Invalid ZIP archive: missing ZIP64 extra field
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/b6f2b8dd2312dc56.
Report an issue: GitHub.