EIGHT_BIT_GRAYSCALE decode of a 3-component progressive JPEG calls JPEGDecodeMCU_P with MCU_SKIP for Cb and Cr after every Y MCU. The existing safe-pMCU patch redirects the wild pointer to &sMCUs[0] but leaves the DC store unguarded, so each chroma skip overwrites the just-decoded Y DC with the chroma DC predictor. Output reads sMCUs[0], gets the trailing Cr DC (~0), and renders an all-black image. Add `if (iMCU >= 0)` guards to the two pMCU[0] writes (main DC store and successive-approximation update). The pointer redirect stays as the AC wild-pointer defence; the new guards stop the silent corruption at sMCUs[0]. The two fixes are independent and both required.
126 lines
3.9 KiB
Python
126 lines
3.9 KiB
Python
"""
|
|
PlatformIO pre-build script: patch JPEGDEC for safe MCU_SKIP handling.
|
|
|
|
When iMCU is MCU_SKIP (-8), JPEGDecodeMCU_P computes pMCU as
|
|
&sMCUs[iMCU & 0xffffff] = &sMCUs[0xFFFFF8], a wild pointer ~33 MB past
|
|
the array. EIGHT_BIT_GRAYSCALE decoding of a 3-component progressive
|
|
JPEG calls JPEGDecodeMCU_P with MCU_SKIP twice per Y MCU (Cb then Cr),
|
|
so every MCU exercises the wild pointer.
|
|
|
|
Two patches, both required:
|
|
|
|
1. Redirect pMCU to &sMCUs[0] when iMCU < 0. Without this, the AC
|
|
decode loop (`pMCU[iIndex] = ...`) store-faults on any progressive
|
|
JPEG whose first scan carries AC coefficients (iScanEnd > 0).
|
|
|
|
2. Guard the two pMCU[0] DC writes with `if (iMCU >= 0)`. Without
|
|
this, patch 1 just relocates the corruption: chroma-skip DC writes
|
|
land at sMCUs[0] and clobber the freshly-decoded Y DC, producing
|
|
all-black output for progressive JPEGs at JPEG_SCALE_EIGHTH grayscale.
|
|
|
|
The AC loop body (`pMCU[iIndex] = ...` writes and matching reads) is
|
|
not separately guarded. It is unreachable on the JPEG_SCALE_EIGHTH
|
|
DC-only first-scan path, and guarding the writes without also skipping
|
|
bit consumption would desync the bitstream for the next MCU.
|
|
|
|
Both patches are idempotent.
|
|
"""
|
|
|
|
Import("env")
|
|
import os
|
|
|
|
|
|
def patch_jpegdec(env):
|
|
libdeps_dir = os.path.join(env["PROJECT_DIR"], ".pio", "libdeps")
|
|
if not os.path.isdir(libdeps_dir):
|
|
return
|
|
for env_dir in os.listdir(libdeps_dir):
|
|
jpeg_inl = os.path.join(libdeps_dir, env_dir, "JPEGDEC", "src", "jpeg.inl")
|
|
if os.path.isfile(jpeg_inl):
|
|
_apply_mcu_skip_pointer_fix(jpeg_inl)
|
|
_apply_dc_write_guards(jpeg_inl)
|
|
|
|
|
|
def _apply_mcu_skip_pointer_fix(filepath):
|
|
MARKER = "// CrossPoint patch: safe pMCU for MCU_SKIP"
|
|
with open(filepath, "r") as f:
|
|
content = f.read()
|
|
|
|
if MARKER in content:
|
|
return
|
|
|
|
OLD = " signed short *pMCU = &pJPEG->sMCUs[iMCU & 0xffffff];"
|
|
|
|
NEW = (
|
|
" " + MARKER + "\n"
|
|
" signed short *pMCU = (iMCU < 0) ? pJPEG->sMCUs\n"
|
|
" : &pJPEG->sMCUs[iMCU & 0xffffff];"
|
|
)
|
|
|
|
if OLD not in content:
|
|
print(
|
|
"WARNING: JPEGDEC MCU_SKIP pointer patch target not found in %s "
|
|
"-- library may have been updated" % filepath
|
|
)
|
|
return
|
|
|
|
content = content.replace(OLD, NEW, 1)
|
|
with open(filepath, "w") as f:
|
|
f.write(content)
|
|
print("Patched JPEGDEC: safe pMCU for MCU_SKIP in JPEGDecodeMCU_P: %s" % filepath)
|
|
|
|
|
|
def _apply_dc_write_guards(filepath):
|
|
MARKER = "// CrossPoint patch: guard pMCU DC writes for MCU_SKIP"
|
|
with open(filepath, "r") as f:
|
|
content = f.read()
|
|
|
|
if MARKER in content:
|
|
return
|
|
|
|
OLD_DC = """\
|
|
pMCU[0] = (short)*iDCPredictor; // store in MCU[0]
|
|
}
|
|
// Now get the other 63 AC coefficients"""
|
|
|
|
NEW_DC = """\
|
|
""" + MARKER + """
|
|
if (iMCU >= 0)
|
|
pMCU[0] = (short)*iDCPredictor; // store in MCU[0]
|
|
}
|
|
// Now get the other 63 AC coefficients"""
|
|
|
|
OLD_SA = """\
|
|
pMCU[0] |= iPositive;
|
|
}
|
|
goto mcu_done; // that's it"""
|
|
|
|
NEW_SA = """\
|
|
if (iMCU >= 0)
|
|
pMCU[0] |= iPositive;
|
|
}
|
|
goto mcu_done; // that's it"""
|
|
|
|
if OLD_DC not in content:
|
|
print(
|
|
"WARNING: JPEGDEC DC write guard target not found in %s "
|
|
"-- library may have been updated" % filepath
|
|
)
|
|
return
|
|
|
|
content = content.replace(OLD_DC, NEW_DC, 1)
|
|
if OLD_SA in content:
|
|
content = content.replace(OLD_SA, NEW_SA, 1)
|
|
else:
|
|
print(
|
|
"WARNING: JPEGDEC successive-approximation DC guard target not found in %s "
|
|
"-- continuing without that half of the patch" % filepath
|
|
)
|
|
|
|
with open(filepath, "w") as f:
|
|
f.write(content)
|
|
print("Patched JPEGDEC: guard pMCU[0] DC writes for MCU_SKIP in JPEGDecodeMCU_P: %s" % filepath)
|
|
|
|
|
|
patch_jpegdec(env)
|