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

  1. Verify the file's checksum and re-download from the original source.
  2. Re-encode the APNG with ffmpeg/apngasm to regenerate correct sequence numbers.
  3. Use pngcheck or an APNG validator to locate the first bad chunk before parsing.
  4. 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

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


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)