HMCL-dev/HMCL · error · PngIntegrityException

acTL chunk length must be

Error message

acTL chunk length must be %d, not %d

What it means

This guard in readAnimationControlChunk fires when an acTL chunk's data length does not equal the exact size the APNG specification requires (8 bytes: num_frames plus num_plays). Any other length means the animation control chunk is malformed, so the animation metadata cannot be trusted.

Solutions

  1. Re-export the APNG with a compliant tool (e.g. ffmpeg -apng or apngasm).
  2. Verify the file with an APNG-aware validator; confirm the acTL chunk length is 8.
  3. If the file is not meant to be animated, check whether a wrong chunk is being read as acTL.
  4. Catch PngIntegrityException and reject the malformed animation metadata.

Example fix

// before
reader.readAnimationControlChunk(source, dataLength);
// after
if (dataLength != 8) {
    throw new PngIntegrityException("acTL must be 8 bytes, got " + dataLength);
}
reader.readAnimationControlChunk(source, dataLength);
Defensive patterns

Strategy: try-catch

Validate before calling

if (code == acTL && dataLength != 8) throw new IllegalStateException("acTL must be exactly 8 bytes");

Try / catch

try { reader.readAnimationControlChunk(source, dataLength); } catch (PngIntegrityException e) { failDecode("malformed acTL: " + e.getMessage()); }

Prevention

When it happens

Trigger: An APNG whose acTL chunk dataLength differs from 8, due to corruption, truncation, or a non-conformant APNG writer.

Common situations: Corrupted animated PNG downloads, files produced by experimental APNG encoders, misidentified chunk boundaries.

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/957be05cd6dbe6f6. Report an issue: GitHub.

Appendix: source

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

                // do nothing
                source.skip(dataLength);
                break;
            case ANIMATED_KEEP_DEFAULT_IMAGE:
                processor.processFrameImageData(source.slice(dataLength), PngChunkCode.IDAT, source.tell(), dataLength);
                break;

            case NOT_ANIMATED:
            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,

View on GitHub (pinned to 24702dc5a0)