{"record":{"id":"04b3c23d51edd39e","repo":"HMCL-dev/HMCL","slug":"invalid-palette-data-length-d-not-a-multiple-of","errorCode":null,"errorMessage":"Invalid palette data length: %d (not a multiple of 3)","messagePattern":"Invalid palette data length: (.+?) \\(not a multiple of 3\\)","errorType":"exception","errorClass":"PngIntegrityException","httpStatus":null,"severity":"error","filePath":"HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/chunks/PngPalette.java","lineNumber":40,"sourceCode":" */\npublic class PngPalette {\n    // TODO: should include alpha here? Can then store as int32s?\n    public final byte[] rgb888;\n    public final int[] rgba8888; // Including this duplicate for now. Not sure if will keep it.\n    public final int numColours;\n\n    public static final int LENGTH_RGB_BYTES = 3;\n    public static final int BYTE_INITIAL_ALPHA = 0xff;\n\n    public PngPalette(byte[] rgb888, int[] rgba8888) {\n        this.rgb888 = rgb888;\n        this.rgba8888 = rgba8888;\n        this.numColours = rgb888.length / 3;\n    }\n\n    public static PngPalette from(byte[] source, int first, int length) throws PngException {\n        if (length % LENGTH_RGB_BYTES != 0) {\n            throw new PngIntegrityException(String.format(\"Invalid palette data length: %d (not a multiple of 3)\", length));\n        }\n\n        return new PngPalette(\n                Arrays.copyOfRange(source, first, first + length),\n                rgba8888From(source, first, length)\n        );\n    }\n\n    private static int[] rgba8888From(byte[] source, int first, int length) {\n        int last = first + length;\n        int numColours = length / 3;\n        int[] rgba8888 = new int[numColours];\n        int j = 0;\n        for (int i = first; i < last; i += LENGTH_RGB_BYTES) {\n            rgba8888[j] = source[i] << 24 | source[i + 1] << 16 | source[i + 2] << 8 | BYTE_INITIAL_ALPHA;\n            j++;\n        }\n        return rgba8888;","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/chunks/PngPalette.java#L22-L58","documentation":"This validation in PngPalette.from fires when a PLTE chunk's data length is not divisible by 3. Each palette entry is exactly 3 bytes (R, G, B), so a length that is not a multiple of 3 means the chunk is truncated or corrupt and the palette cannot be parsed into whole RGB triplets.","triggerScenarios":"A PLTE chunk whose dataLength is not a multiple of 3, from truncation, corruption, or a broken encoder.","commonSituations":"Corrupted downloads, files edited with hex editors, fuzzed test inputs, faulty custom PNG writers.","solutions":["Re-encode or re-save the PNG with a compliant tool to regenerate a valid PLTE.","Verify the chunk length against the file's chunk table; check for truncation in transfer.","Catch PngIntegrityException and reject the image as corrupt."],"exampleFix":"// before\nPngPalette.from(source, first, length);\n// after\nif (length % 3 != 0) {\n    throw new PngIntegrityException(\"PLTE length \" + length + \" not multiple of 3\");\n}\nPngPalette.from(source, first, length);","handlingStrategy":"try-catch","validationCode":"if (plteLength % 3 != 0) throw new IllegalArgumentException(\"PLTE length not multiple of 3\");","typeGuard":null,"tryCatchPattern":"try { PngPalette.from(source, first, length); } catch (PngIntegrityException e) { handleCorruptPalette(e); }","preventionTips":["Check PLTE length % 3 == 0 during chunk pre-scan.","Re-encode corrupted files.","Use pngcheck to pre-screen.","Avoid manual chunk editing."],"tags":["png","image-decoding","validation","palette"],"backgroundTag":"invalid-argument-format","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"}