HMCL-dev/HMCL · error · PngIntegrityException
fdAT chunk expected sequence
Error message
fdAT chunk expected sequence %d but received %d
What it means
fdAT (frame data) chunks, like fcTL, embed a sequence number that must increment exactly in step with the expected counter apngSequenceExpect. readFrameImageDataChunk reads the first 4 bytes as the sequence number and throws PngIntegrityException if it does not match, since frame data would otherwise be attributed to the wrong frame.
Solutions
- Verify the file's checksum and re-download from the original source.
- Re-encode the APNG with ffmpeg/apngasm to regenerate correct sequence numbers.
- Use pngcheck or an APNG validator to locate the first bad chunk before parsing.
- If resuming a partial stream, sync apngSequenceExpect to the last successfully seen sequence + 1.
Example fix
// before: raw stream throws on gap PngReadHelper.read(is, apngReader); // fdAT chunk expected sequence 5 but received 6 // after: pre-validate chunk ordering if (apngSequenceNumbersValid(file)) PngReadHelper.read(is, apngReader); else reencode(file);
Defensive patterns
Strategy: validation
Validate before calling
// verify fdAT sequence continuity alongside fcTL
int expect = 0;
for (Chunk c : chunks(file)) {
if (c.type.equals("fcTL") || c.type.equals("fdAT")) {
int seq = readIntBE(c.data, 0);
if (seq != expect) throw new IllegalArgumentException("seq " + seq + " != " + expect + " at " + c.type);
expect++;
}
} Try / catch
try {
PngReadHelper.read(is, apngReader);
} catch (PngIntegrityException e) {
// corrupt sequencing — recover by re-encoding
renderStaticFallback(file); // show first frame or placeholder
} Prevention
- Keep fcTL and fdAT counters in lockstep when generating APNGs.
- Use established encoders rather than hand-rolled APNG writers.
- Validate with pngcheck before decoding user-supplied files.
- Surface PngIntegrityException to users as file corruption.
When it happens
Trigger: Parsing an APNG where an fdAT chunk's sequence number is out of order, missing, duplicated, or where an IDAT-style payload lost its 4-byte sequence prefix — via readChunk → readFrameImageDataChunk.
Common situations: Corrupt or truncated animated PNG downloads; files edited by tools unaware of APNG sequencing; mixtures of IDAT and fdAT after bad conversion; re-encoded streams with gaps.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- fctl chunk expected sequence
- fcTL chunk length must be
- tRNS chunk for greyscale image must be exactly length=2, not
- Illegal to have tRNS chunk with image type
- Invalid greyscale bit-depth
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/a32f44bff6c72fe4.
Report an issue: GitHub.
Appendix: source
Thrown at HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/DefaultPngChunkReader.java:253
} else {
// fall through
}
processor.processFrameControl(frame);
}
//public abstract void setMainImageOp(PngMainImageOp op);
@Override
public void readFrameImageDataChunk(PngSource source, int dataLength) throws IOException, PngException {
// Note that once the sequence number is confirmed as being correct that there
// is no need to retain it in subsequent data.
int position = source.tell();
int sequence = source.readInt();
dataLength -= 4; // After reading the sequence number the data is just like IDAT.
if (sequence != apngSequenceExpect) {
throw new PngIntegrityException(String.format("fdAT chunk expected sequence %d but received %d", apngSequenceExpect, sequence));
}
apngSequenceExpect++; // for next time
//processFrameData(sequence, source, dataLength);
//processFrameImageData(source, dataLength);
processor.processFrameImageData(source.slice(dataLength), PngChunkCode.fdAT, source.tell(), dataLength);
// PngFrameControl current = container.getCurrentAnimationFrame();
//
// //imageDecoder
// // TODO: send image bytes to digester
// current.appendImageData(new PngChunkMap(PngChunkCode.fdAT, dataLength, position, 0));
//
// // TODO: skip everything except the frame sequence number
// source.skip(dataLength);
}View on GitHub (pinned to 24702dc5a0)