Tencent/tinker · error · ZipException

Spanned archives not supported

Error message

Spanned archives not supported

What it means

While parsing the End Of Central Directory record (non-zip64 path), TinkerZipFile checks that the entry count on this disk equals the total entry count and that both disk-number fields are zero. Any mismatch means the archive is split across multiple disks/volumes, which this reader does not support, so it throws ZipException('Spanned archives not supported'). This is the same restriction as java.util.zip.ZipFile.

Source

Thrown at third-party/tinker-ziputils/src/main/java/com/tencent/tinker/ziputils/ziputil/TinkerZipFile.java:224

        if (isZip64) {
            numEntries = -1;
            centralDirOffset = -1;
            // If we have a zip64 end of central directory record, we skip through the regular
            // end of central directory record and use the information from the zip64 eocd record.
            // We're still forced to read the comment length (below) since it isn't present in the
            // zip64 eocd record.
            it.skip(16);
        } else {
            // If we don't have a zip64 eocd record, we read values from the "regular"
            // eocd record.
            int diskNumber = it.readShort() & 0xffff;
            int diskWithCentralDir = it.readShort() & 0xffff;
            numEntries = it.readShort() & 0xffff;
            int totalNumEntries = it.readShort() & 0xffff;
            it.skip(4); // Ignore centralDirSize.
            centralDirOffset = ((long) it.readInt()) & 0xffffffffL;
            if (numEntries != totalNumEntries || diskNumber != 0 || diskWithCentralDir != 0) {
                throw new ZipException("Spanned archives not supported");
            }
        }
        final int commentLength = it.readShort() & 0xffff;
        return new EocdRecord(numEntries, centralDirOffset, commentLength);
    }

    static void throwZipException(String filename, long fileSize, String entryName, long localHeaderRelOffset, String msg, int magic) throws ZipException {
        final String hexString = Integer.toHexString(magic);
        throw new ZipException("file name:" + filename
                               + ", file size" + fileSize
                               + ", entry name:" + entryName
                               + ", entry localHeaderRelOffset:" + localHeaderRelOffset
                               + ", "
                               + msg + " signature not found; was " + hexString);
    }

    /**
     * Closes this zip file. This method is idempotent. This method may cause I/O if the

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. Rejoin all parts into a single file before opening (e.g. 'zip -s 0 archive.zip --out full.zip' or 'zip -F').
  2. If the file should be single-part, treat this as corruption: re-download or regenerate the archive.
  3. Verify with 'unzip -l' or 'zipinfo' that the archive is a self-contained single-volume zip before handing it to TinkerZipFile.

Example fix

// before
TinkerZipFile zf = new TinkerZipFile(new File("patch-part1.zip")); // spanned archive

// after
// merge parts first: zip -s 0 patch.zip --out patch-full.zip
TinkerZipFile zf = new TinkerZipFile(new File("patch-full.zip"));
Defensive patterns

Strategy: validation

Validate before calling

// cheap pre-check: EOCD disk fields must be zero for a single-volume zip
static boolean isSingleVolume(File f) throws IOException {
    try (RandomAccessFile r = new RandomAccessFile(f, "r")) {
        long len = r.length();
        if (len < 22) return false;
        for (long off = len - 22; off >= Math.max(0, len - 22 - 65536); off--) {
            r.seek(off);
            if (r.readInt() == 0x06054b50) { // ENDSIG little-endian after reverseBytes
                r.seek(off + 4);
                return (r.readShort() | r.readShort()) == 0; // diskNumber & diskWithCentralDir
            }
        }
    }
    return false;
}

Try / catch

try {
    zf = new TinkerZipFile(file);
} catch (ZipException e) {
    if (e.getMessage().contains("Spanned archives")) {
        // actionable: merge parts, then reopen
        throw new IllegalArgumentException("Multi-part archive not supported: merge parts first", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Opening a multi-part zip (created with 'zip -s', WinRAR split, or hjsplit) with new TinkerZipFile(...): the EOCD then reports diskNumber != 0, diskWithCentralDir != 0, or per-disk numEntries != totalNumEntries.

Common situations: Android patch/OTA files distributed as split archives; a download tool that stored parts without rejoining; a truncated single-disk archive whose EOCD fields were corrupted so they resemble a spanned set.

Related errors


AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14). Data as JSON: /api/errors/5a8ffaf4d34eb8c4. Report an issue: GitHub.