HMCL-dev/HMCL · error · PngIntegrityException

fcTL chunk length must be

Error message

fcTL chunk length must be %d, not %d

What it means

APNG's fcTL (frame control) chunk is defined by the spec to have an exact length of 26 bytes (PngConstants.LENGTH_fcTL_CHUNK). DefaultPngChunkReader.readFrameControlChunk validates the declared chunk data length before parsing the sequence number and frame parameters; a mismatch means the file is structurally corrupt per the APNG specification, so it throws PngIntegrityException rather than reading garbage.

Solutions

  1. Re-obtain the PNG file from a trusted source and verify integrity (checksum/signature).
  2. Re-encode the APNG with a conformant encoder (e.g. ffmpeg, apngasm).
  3. Validate the file with a PNG/APNG checker (pngcheck) before feeding it to this reader.
  4. If the length really is 26 but constants disagree, check that your PngConstants matches the APNG spec.

Example fix

// before: reading a possibly corrupt file directly
reader.read(inputStream, apngReader);
// after: validate/repair the source first
boolean ok = PngReadHelper.readSignature(is) && pngCheckPasses(file);
if (ok) reader.read(is, apngReader); else reDownloadOrReencode(file);
Defensive patterns

Strategy: validation

Validate before calling

// verify chunk lengths before parsing
long off = 8; // after signature
while (off + 8 <= file.length()) {
  int len = readIntBE(data, off);
  String type = new String(data, (int) off + 4, 4, StandardCharsets.US_ASCII);
  if (type.equals("fcTL") && len != 26) throw new IllegalArgumentException("bad fcTL length " + len);
  off += 12 + len; // length+type+CRC
}

Try / catch

try {
  PngReadHelper.read(is, apngReader);
} catch (PngException e) {
  if (e instanceof PngIntegrityException) showCorruptFileDialog();
  else throw e;
}

Prevention

When it happens

Trigger: Parsing a PNG whose fcTL chunk declares a data length other than 26 — e.g. a truncated, hand-edited, or non-conformant APNG file passed to PngReadHelper.read / DefaultPngChunkReader.readChunk.

Common situations: Corrupted downloads of animated PNGs; files produced by broken APNG encoders or post-processing tools that rewrote chunks; concatenated/truncated streams; manually modified chunk payloads.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10). Data as JSON: /api/errors/b286d3e0484a6d7f. Report an issue: GitHub.

Appendix: source

Thrown at HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/DefaultPngChunkReader.java:202

            default:
                processor.processDefaultImageData(source.slice(dataLength), PngChunkCode.IDAT, source.tell(), dataLength);
                break;
        }
//        source.skip(dataLength);
    }

    @Override
    public void readAnimationControlChunk(PngSource source, int dataLength) throws IOException, PngException {
        if (dataLength != PngConstants.LENGTH_acTL_CHUNK) {
            throw new PngIntegrityException(String.format("acTL chunk length must be %d, not %d", PngConstants.LENGTH_acTL_CHUNK, dataLength));
        }
        processor.processAnimationControl(new PngAnimationControl(source.readInt(), source.readInt()));
    }

    @Override
    public void readFrameControlChunk(PngSource source, int dataLength) throws IOException, PngException {
        if (dataLength != PngConstants.LENGTH_fcTL_CHUNK) {
            throw new PngIntegrityException(String.format("fcTL chunk length must be %d, not %d", PngConstants.LENGTH_fcTL_CHUNK, dataLength));
        }
        int sequence = source.readInt(); // TODO: check sequence # is correct or PngIntegrityException

        if (sequence != apngSequenceExpect) {
            throw new PngIntegrityException(String.format("fctl chunk expected sequence %d but received %d", apngSequenceExpect, sequence));
        }
        apngSequenceExpect++; // ready for next time

        PngFrameControl frame = new PngFrameControl(
                sequence,
                source.readInt(), // width
                source.readInt(), // height
                source.readInt(), // x offset
                source.readInt(), // y offset
                source.readUnsignedShort(), // delay numerator
                source.readUnsignedShort(), // delay denominator
                source.readByte(), // dispose op
                source.readByte() // blend op

View on GitHub (pinned to 24702dc5a0)