diff --git a/dwt/evidence/real-unit-1280-white.png b/dwt/evidence/real-unit-1280-white.png new file mode 100644 index 0000000..697fac1 Binary files /dev/null and b/dwt/evidence/real-unit-1280-white.png differ diff --git a/dwt/evidence/real-unit-1280.png b/dwt/evidence/real-unit-1280.png new file mode 100644 index 0000000..de9e155 Binary files /dev/null and b/dwt/evidence/real-unit-1280.png differ diff --git a/dwt/notes/REAL_UNIT.md b/dwt/notes/REAL_UNIT.md index 25252ec..a5cacb7 100644 --- a/dwt/notes/REAL_UNIT.md +++ b/dwt/notes/REAL_UNIT.md @@ -45,3 +45,20 @@ The remaining unproven step on this unit is the write path: `poke`/`jump` and then the COFF loader + `create_process`. The loader already refuses a target outside a free-list block (status 8), so a first attempt with `marker.coff` will tell us whether the linked ranges are free before anything runs. + +## Display geometry and background (measured) + +* Framebuffer **1024×1024, 128 bytes/row**, LSB-first. A 160-byte pitch shears; + 128 bytes is coherent. The 160-byte figure is the **total raster** + (1280/8), not the memory pitch (`RENDER_REPORT.md`). +* Visible raster **1280×1024** (monitor OSD: 77.2 kHz / 73 Hz; Samsung + SyncMaster 770 native 1280×1024). The 1024-wide canvas is displayed 5:4, so + `dwt/screenshot.py --stretch 1280x1024` matches the screen. `--screen`'s + emulator 1280×901 geometry pads/crops and looks line-doubled. +* The `101010` background is **real data**, not a decode error. It is a 50% + stipple: rows alternate `55` (`01010101`) and `ff`. The root background has a + setting; setting it to **100% white** changed the root to solid `ff` (verified + by a fresh grab). The Terminal Manager window kept its own stipple, so its + window background is a **separate setting** (or was taken at creation). +* The bottom message box `Ethernet Port Error - 24` is present in VRAM; it is a + real terminal error, not a capture artifact. diff --git a/dwt/screenshot.py b/dwt/screenshot.py index 17b6187..3752214 100644 --- a/dwt/screenshot.py +++ b/dwt/screenshot.py @@ -57,6 +57,11 @@ OFFSET_X = 192 OFFSET_Y = 36 +def parse_size(text: str) -> tuple[int, int]: + w, h = text.lower().split("x", 1) + return int(w, 0), int(h, 0) + + def parse_rows(text: str) -> tuple[int, int]: if ":" in text: a, b = text.split(":", 1) @@ -82,6 +87,19 @@ def decode(data: bytes, width: int = WIDTH) -> bytearray: return out +def scale_nearest(gray: bytearray, sw: int, sh: int, + dw: int, dh: int) -> bytearray: + """Nearest-neighbour scale; used for the real unit's 1024->1280 display.""" + out = bytearray(dw * dh) + for y in range(dh): + sy = y * sh // dh + src = sy * sw + dst = y * dw + for x in range(dw): + out[dst + x] = gray[src + x * sw // dw] + return out + + def compose_screen(gray: bytearray, rows: int, sw: int, sh: int, ox: int, oy: int) -> bytearray: canvas = bytearray(sw * sh) @@ -147,7 +165,12 @@ def one(rd: exploit.RDebug, a: argparse.Namespace, tag: str) -> None: if a.repeat > 1: root, dot, ext = out.rpartition(".") out = f"{root}-{tag}.{ext}" if dot else f"{out}-{tag}" - if a.screen: + if a.stretch: + dw, dh = a.stretch + scaled = scale_nearest(gray, WIDTH, rows, dw, dh) + write_png(out, dw, dh, scaled) + print(f"[+] {out}: {dw}x{dh} (nearest-neighbour from {WIDTH}x{rows})") + elif a.screen: canvas = compose_screen(gray, rows, a.screen_width, a.screen_height, a.offset_x, a.offset_y) write_png(out, a.screen_width, a.screen_height, canvas) @@ -179,6 +202,9 @@ def main(argv: list[str]) -> int: metavar="A:B", help=f"row range to read (default 0:{HEIGHT})") ap.add_argument("--screen", action="store_true", help="compose the visible screen instead of the raw canvas") + ap.add_argument("--stretch", type=parse_size, metavar="WxH", default=None, + help="nearest-neighbour scale the raw canvas to WxH " + "(the real unit displays 1024x1024 as 1280x1024)") 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)