{"record":{"id":"b286d3e0484a6d7f","repo":"HMCL-dev/HMCL","slug":"fctl-chunk-length-must-be-d-not-d","errorCode":null,"errorMessage":"fcTL chunk length must be %d, not %d","messagePattern":"fcTL chunk length must be (.+?), not (.+?)","errorType":"exception","errorClass":"PngIntegrityException","httpStatus":null,"severity":"error","filePath":"HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/DefaultPngChunkReader.java","lineNumber":202,"sourceCode":"            default:\n                processor.processDefaultImageData(source.slice(dataLength), PngChunkCode.IDAT, source.tell(), dataLength);\n                break;\n        }\n//        source.skip(dataLength);\n    }\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","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/DefaultPngChunkReader.java#L184-L220","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-obtain the PNG file from a trusted source and verify integrity (checksum/signature).","Re-encode the APNG with a conformant encoder (e.g. ffmpeg, apngasm).","Validate the file with a PNG/APNG checker (pngcheck) before feeding it to this reader.","If the length really is 26 but constants disagree, check that your PngConstants matches the APNG spec."],"exampleFix":"// before: reading a possibly corrupt file directly\nreader.read(inputStream, apngReader);\n// after: validate/repair the source first\nboolean ok = PngReadHelper.readSignature(is) && pngCheckPasses(file);\nif (ok) reader.read(is, apngReader); else reDownloadOrReencode(file);","handlingStrategy":"validation","validationCode":"// verify chunk lengths before parsing\nlong off = 8; // after signature\nwhile (off + 8 <= file.length()) {\n  int len = readIntBE(data, off);\n  String type = new String(data, (int) off + 4, 4, StandardCharsets.US_ASCII);\n  if (type.equals(\"fcTL\") && len != 26) throw new IllegalArgumentException(\"bad fcTL length \" + len);\n  off += 12 + len; // length+type+CRC\n}","typeGuard":null,"tryCatchPattern":"try {\n  PngReadHelper.read(is, apngReader);\n} catch (PngException e) {\n  if (e instanceof PngIntegrityException) showCorruptFileDialog();\n  else throw e;\n}","preventionTips":["Run pngcheck -v on APNG assets before shipping/decoding them.","Re-encode untrusted APNGs with a known-good encoder.","Treat PngIntegrityException as corrupt-file signal, not a code bug.","Keep files checksummed at download time and verify before parsing."],"tags":["png","apng","corrupt-file","binary-parsing"],"backgroundTag":"invalid-argument-value","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"}