{"record":{"id":"5edf30d88492c528","repo":"HMCL-dev/HMCL","slug":"bkgd-chunk-received-before-ihdr-chunk","errorCode":null,"errorMessage":"bKGD chunk received before IHDR chunk","messagePattern":"bKGD chunk received before IHDR chunk","errorType":"exception","errorClass":"PngIntegrityException","httpStatus":null,"severity":"error","filePath":"HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/DefaultPngChunkReader.java","lineNumber":139,"sourceCode":"//        if (dataLength % 3 != 0) {\n//            throw new PngIntegrityException(String.format(\"png spec: palette chunk length must be divisible by 3: %d\", dataLength));\n//        }\n\n        if (source.supportsByteAccess()) {\n            processor.processTransparency(source.getBytes(), source.tell(), dataLength);\n            source.skip(dataLength);\n        } else {\n            byte[] paletteBytes = new byte[dataLength];\n            //ByteStreams.readFully(source.getBis(), paletteBytes);\n            source.getDis().readFully(paletteBytes);\n            processor.processTransparency(paletteBytes, 0, dataLength);\n        }\n    }\n\n    @Override\n    public void readBackgroundChunk(PngSource source, int dataLength) throws IOException, PngException {\n        if (!seenHeader) {\n            throw new PngIntegrityException(\"bKGD chunk received before IHDR chunk\");\n        }\n        // TODO\n        //processor.processBackground(PngBackground.from(source, dataLength);\n        source.skip(dataLength);\n    }\n\n    @Override\n    public void readPaletteChunk(PngSource source, int dataLength) throws IOException, PngException {\n\n        if (dataLength % 3 != 0) {\n            throw new PngIntegrityException(String.format(\"png spec: palette chunk length must be divisible by 3: %d\", dataLength));\n        }\n        // TODO: can check if colour type matches palette type, or if any palette received before (overkill?)\n\n        if (source.supportsByteAccess()) {\n            processor.processPalette(source.getBytes(), source.tell(), dataLength);\n            source.skip(dataLength);\n        } else {","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/DefaultPngChunkReader.java#L121-L157","documentation":"This ordering guard in readBackgroundChunk fires when a bKGD chunk appears in the stream before the IHDR header chunk has been processed. The PNG spec requires IHDR to be the first chunk; a background chunk without a header means the stream is out of order or malformed, and the reader cannot interpret the background against an unknown colour type.","triggerScenarios":"A stream where bKGD appears before IHDR, e.g. a malformed file or a reader resumed mid-stream at the wrong offset.","commonSituations":"Corrupted or hand-assembled PNGs, chunk streams spliced by tools, resuming a partial decode without skipping to IHDR.","solutions":["Ensure decoding starts at the file beginning so IHDR is processed first.","Validate chunk ordering before feeding the stream to the reader (IHDR must be first chunk).","Re-export the PNG with a compliant tool to fix chunk order.","Catch PngIntegrityException and reject the malformed file."],"exampleFix":"// before: feeding arbitrary chunk stream\nreader.readChunk(source, code, len);\n// after: require header first\nif (!headerSeen) {\n    throw new PngIntegrityException(\"bKGD before IHDR\");\n}\nreader.readBackgroundChunk(source, len);","handlingStrategy":"try-catch","validationCode":"if (!headerSeen && chunkCodeIsBackground(code)) throw new IllegalStateException(\"bKGD before IHDR\");","typeGuard":null,"tryCatchPattern":"try { readChunks(stream); } catch (PngIntegrityException e) { if (e.getMessage().contains(\"before IHDR\")) rejectMalformedOrdering(file); }","preventionTips":["Always start decoding from the file's first chunk (IHDR).","Validate chunk ordering before parsing (IHDR first, PLTE before IDAT/tRNS/bKGD).","Re-encode malformed files.","Never resume decoding mid-stream without resynchronizing at a chunk boundary."],"tags":["png","image-decoding","chunk-order","validation"],"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"}