From 6adc04ba024d53dc7db9905d76f19fd1d0e3138c Mon Sep 17 00:00:00 2001 From: SDD Date: Sun, 18 Oct 2026 16:22:24 +0300 Subject: [PATCH] dwt/screenshot: --real reads the 1024x2048 interleaved framebuffer and keeps the even rows 1:1; corrects the earlier doubling --- dwt/evidence/real-unit-1to1.png | Bin 0 -> 6998 bytes dwt/notes/REAL_UNIT.md | 25 +++++++++++++++++++ dwt/screenshot.py | 43 +++++++++++++++++++------------- 3 files changed, 51 insertions(+), 17 deletions(-) create mode 100644 dwt/evidence/real-unit-1to1.png diff --git a/dwt/evidence/real-unit-1to1.png b/dwt/evidence/real-unit-1to1.png new file mode 100644 index 0000000000000000000000000000000000000000..db1e2053d496350a5aee318089b5909c14fe604c GIT binary patch literal 6998 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 tuple[int, int]: start, end = int(a, 0), int(b, 0) else: start, end = 0, int(text, 0) - if not (0 <= start < end <= HEIGHT): + if not (0 <= start < end <= MAX_ROWS): raise argparse.ArgumentTypeError( - f"rows must satisfy 0 <= start < end <= {HEIGHT}") + f"rows must satisfy 0 <= start < end <= {MAX_ROWS}") return start, end @@ -87,19 +88,19 @@ def decode(data: bytes, width: int = WIDTH) -> bytearray: return out -def field_double(gray: bytearray, w: int, h: int, parity: str) -> bytearray: - """Select one row parity and duplicate each row. +def field_select(gray: bytearray, w: int, h: int, parity: str) -> bytearray: + """De-interleave: keep one row parity, at 1:1 (no doubling). - The real unit's framebuffer is line-interleaved: the image is on even rows - and the odd rows carry a 0x55 mask; the display doubles the even field. - Reading linearly shows both, which looks 'line doubled' and striped. + The real unit's framebuffer is 1024x2048: the 1024 image lines are on even + rows and the odd rows carry a 0x55 mask. The display scans the even rows. + Reading all 2048 rows and keeping the even ones gives the 1024x1024 image + exactly as the hardware shows it. """ off = 0 if parity == "even" else 1 - out = bytearray(w * h) - for y in range(h): - sy = (y // 2) * 2 + off - if sy >= h: - sy = h - 1 + oh = h // 2 + out = bytearray(w * oh) + for y in range(oh): + sy = y * 2 + off out[y * w:(y + 1) * w] = gray[sy * w:(sy + 1) * w] return out @@ -183,7 +184,8 @@ def one(rd: exploit.RDebug, a: argparse.Namespace, tag: str) -> None: root, dot, ext = out.rpartition(".") out = f"{root}-{tag}.{ext}" if dot else f"{out}-{tag}" if a.field: - gray = field_double(gray, WIDTH, rows, a.field) + gray = field_select(gray, WIDTH, rows, a.field) + rows = rows // 2 if a.stretch: dw, dh = a.stretch scaled = scale_nearest(gray, WIDTH, rows, dw, dh) @@ -225,9 +227,13 @@ def main(argv: list[str]) -> int: help="nearest-neighbour scale the raw canvas to WxH " "(the real unit displays 1024x1024 as 1280x1024)") ap.add_argument("--field", choices=("even", "odd"), default=None, - help="select one row parity and double it; the real unit " - "carries the image on even rows and a 0x55 mask on odd " - "rows, and the display doubles the even field") + help="de-interleave: keep one row parity at 1:1; the real " + "unit's 1024 image lines are on even rows of a 2048-row " + "framebuffer, odd rows are a 0x55 mask") + ap.add_argument("--real", action="store_true", + help="real-unit preset: --rows 0:2048 --field even, 1:1 " + "(add --stretch 1280x1024 only if you want the 5:4 " + "display scaling)") ap.add_argument("--screen-width", type=int, default=SCREEN_W) ap.add_argument("--screen-height", type=int, default=SCREEN_H) ap.add_argument("--offset-x", type=int, default=OFFSET_X) @@ -240,6 +246,9 @@ def main(argv: list[str]) -> int: ap.add_argument("--quiet", action="store_true") ap.add_argument("--verbose", action="store_true") a = ap.parse_args(argv[1:]) + if a.real: + a.rows = (0, MAX_ROWS) + a.field = "even" rd = exploit.RDebug(a.host, a.port, a.timeout, a.verbose) rd.connect()