{"record":{"id":"c951b37a2bea585b","repo":"HMCL-dev/HMCL","slug":"invalid-frame-control","errorCode":null,"errorMessage":"Invalid frame control: ","messagePattern":"Invalid frame control: ","errorType":"validation","errorClass":"PngIntegrityException","httpStatus":null,"severity":"error","filePath":"HMCL/src/main/java/org/jackhuang/hmcl/ui/image/ImageUtils.java","lineNumber":603,"sourceCode":"\n        int[] buffer = new int[Math.multiplyExact(width, height)];\n        for (int frameIndex = 0; frameIndex < frames.size(); frameIndex++) {\n            var frame = frames.get(frameIndex);\n            PngFrameControl control = frame.control();\n\n            if (frameIndex == 0 && (\n                    control.xOffset != 0 || control.yOffset != 0\n                            || control.width != width || control.height != height)) {\n                throw new PngIntegrityException(\"Invalid first frame: \" + control);\n            }\n\n            if (control.xOffset < 0 || control.yOffset < 0\n                    || width < 0 || height < 0\n                    || control.xOffset + control.width > width\n                    || control.yOffset + control.height > height\n                    || control.delayNumerator < 0 || control.delayDenominator < 0\n            ) {\n                throw new PngIntegrityException(\"Invalid frame control: \" + control);\n            }\n\n            int[] currentFrameBuffer = buffer.clone();\n            if (control.blendOp == 0) {\n                for (int row = 0; row < control.height; row++) {\n                    System.arraycopy(frame.bitmap().array(),\n                            row * control.width,\n                            currentFrameBuffer,\n                            (control.yOffset + row) * width + control.xOffset,\n                            control.width);\n                }\n            } else if (control.blendOp == 1) {\n                // APNG_BLEND_OP_OVER - Alpha blending\n                for (int row = 0; row < control.height; row++) {\n                    for (int col = 0; col < control.width; col++) {\n                        int srcIndex = row * control.width + col;\n                        int dstIndex = (control.yOffset + row) * width + control.xOffset + col;\n","sourceCodeStart":585,"sourceCodeEnd":621,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/ImageUtils.java#L585-L621","documentation":"Thrown by ImageUtils.toImage as PngIntegrityException when an APNG frame's fcTL control values are out of bounds: negative offsets/delays, a frame extending past the canvas, or invalid canvas dimensions. It guards the framebuffer from out-of-range writes.","triggerScenarios":"Decoding an APNG where any frame's xOffset/width exceeds the canvas (xOffset + width > width, same for y/height), or delayNumerator/delayDenominator are negative — a corrupt or non-conformant fcTL chunk.","commonSituations":"Corrupt downloads/truncated APNG files; broken third-party APNG encoders writing bad chunk fields; hand-crafted or fuzzed PNG inputs.","solutions":["Re-encode the APNG with ffmpeg/apngasm to regenerate correct fcTL chunks","Re-download the file — the image may be corrupted in transit","Catch PngIntegrityException and fall back to the PNG's static default image","Pre-validate frame geometry against the IHDR canvas before decoding"],"exampleFix":"// before\nframes.forEach(f -> decode(f)); // throws on bad control\n// after\nif (frames.stream().allMatch(f -> validControl(f.control(), width, height))) {\n    decodeAll(frames);\n} else {\n    useStaticImage();\n}","handlingStrategy":"validation","validationCode":"static boolean frameInBounds(PngFrameControl c, int w, int h) {\n    return c.xOffset >= 0 && c.yOffset >= 0\n        && c.xOffset + c.width <= w && c.yOffset + c.height <= h\n        && c.delayNumerator >= 0 && c.delayDenominator >= 0;\n}","typeGuard":null,"tryCatchPattern":"try {\n    animation = ImageUtils.toImage(sequence, ...);\n} catch (PngIntegrityException e) {\n    animation = staticImage;\n    LOG.warning(\"Corrupt APNG frame control: \" + e.getMessage());\n}","preventionTips":["Verify downloaded files with checksums before decoding","Run apngcheck on third-party APNG assets","Cache a static first-frame fallback for every animated resource","Reject files whose fcTL geometry exceeds the IHDR canvas at parse time"],"tags":["apng","png-validation","bounds-check"],"backgroundTag":"value-out-of-range","analyzedSha":"24702dc5a0214034f4c27166d5fd30cad08cec19","analyzedAt":"2026-09-10T12:36:46.680Z","contentChangedAt":"2026-09-10T12:36:46.680Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}