{"record":{"id":"4dc01ff2290feff1","repo":"HMCL-dev/HMCL","slug":"fctl-chunk-expected-sequence-d-but-received-d","errorCode":null,"errorMessage":"fctl chunk expected sequence %d but received %d","messagePattern":"fctl chunk expected sequence (.+?) but received (.+?)","errorType":"exception","errorClass":"PngIntegrityException","httpStatus":null,"severity":"error","filePath":"HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/DefaultPngChunkReader.java","lineNumber":207,"sourceCode":"    }\n\n    @Override\n    public void readAnimationControlChunk(PngSource source, int dataLength) throws IOException, PngException {\n        if (dataLength != PngConstants.LENGTH_acTL_CHUNK) {\n            throw new PngIntegrityException(String.format(\"acTL chunk length must be %d, not %d\", PngConstants.LENGTH_acTL_CHUNK, dataLength));\n        }\n        processor.processAnimationControl(new PngAnimationControl(source.readInt(), source.readInt()));\n    }\n\n    @Override\n    public void readFrameControlChunk(PngSource source, int dataLength) throws IOException, PngException {\n        if (dataLength != PngConstants.LENGTH_fcTL_CHUNK) {\n            throw new PngIntegrityException(String.format(\"fcTL chunk length must be %d, not %d\", PngConstants.LENGTH_fcTL_CHUNK, dataLength));\n        }\n        int sequence = source.readInt(); // TODO: check sequence # is correct or PngIntegrityException\n\n        if (sequence != apngSequenceExpect) {\n            throw new PngIntegrityException(String.format(\"fctl chunk expected sequence %d but received %d\", apngSequenceExpect, sequence));\n        }\n        apngSequenceExpect++; // ready for next time\n\n        PngFrameControl frame = new PngFrameControl(\n                sequence,\n                source.readInt(), // width\n                source.readInt(), // height\n                source.readInt(), // x offset\n                source.readInt(), // y offset\n                source.readUnsignedShort(), // delay numerator\n                source.readUnsignedShort(), // delay denominator\n                source.readByte(), // dispose op\n                source.readByte() // blend op\n        );\n\n        if (sequence == 0) { // We're at the first frame...\n            if (idatCount == 0) { // Not seen any IDAT chunks yet\n                // APNG Spec says that when the first fcTL chunk is received *before* the first IDAT chunk","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/DefaultPngChunkReader.java#L189-L225","documentation":"APNG chunks carry a monotonically increasing sequence number in fcTL (and fdAT) chunks so frames can be reassembled in order. readFrameControlChunk compares the chunk's sequence number against apngSequenceExpect and throws PngIntegrityException when they differ, because out-of-order or duplicated sequence numbers make frame reassembly ambiguous.","triggerScenarios":"Reading an APNG whose fcTL sequence number is out of order, skipped, duplicated, or starts at a value other than the expected 0 — typically in a corrupt or non-conformant file processed by readChunk → readFrameControlChunk.","commonSituations":"Files reordered/spliced by editing tools; partially overwritten downloads; APNGs assembled by scripts that renumber frames incorrectly; multiple fcTL chunks repeated after truncation/replay.","solutions":["Re-acquire the APNG from the original source and verify its checksum.","Re-encode the animation with apngasm/ffmpeg so sequence numbers are regenerated correctly.","Renumber fcTL/fdAT sequence numbers with a repair tool before parsing.","If intentional resumption is needed, reset the reader's expected sequence state (apngSequenceExpect) to match the stream start."],"exampleFix":"// before: strict parse fails on renumbered file\nPngReadHelper.read(is, reader); // throws fctl chunk expected sequence 3 but received 7\n// after: renumber sequence numbers first\nrenumberApngSequences(file); // rewrite fcTL/fdAT seq from 0\nPngReadHelper.read(is, reader);","handlingStrategy":"validation","validationCode":"// walk chunks and assert fcTL sequence numbers start at 0 and increment\nint expect = 0;\nfor (Chunk c : chunks(file)) {\n  if (c.type.equals(\"fcTL\")) {\n    int seq = readIntBE(c.data, 0);\n    if (seq != expect) throw new IllegalArgumentException(\"fcTL seq \" + seq + \" != \" + expect);\n    expect++;\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  PngReadHelper.read(is, apngReader);\n} catch (PngIntegrityException e) {\n  LOG.warning(\"APNG sequence error, re-encoding: \" + e.getMessage());\n  reencodeWithFfmpeg(file);\n}","preventionTips":["Never manually reorder or splice fcTL chunks in APNG files.","Validate sequence continuity when concatenating or editing APNGs programmatically.","Use apngasm to disassemble/reassemble frames safely.","Checksum-verify downloads before decoding."],"tags":["png","apng","sequence-number","corrupt-file"],"backgroundTag":"invalid-state-transition","analyzedSha":"24702dc5a0214034f4c27166d5fd30cad08cec19","analyzedAt":"2026-09-10T12:36:46.680Z","contentChangedAt":"2026-09-10T12:36:46.680Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}