#!/usr/bin/env python3
"""Compare selected GDR-8050L RAM/MMIO state bytes in raw memory dumps.

Use for real-drive snapshots from the cross-flashed GDR-8163B/8050L firmware path.
The default address set mirrors the emulator auth-watch bytes plus packet buffers.
"""
from __future__ import annotations

import argparse, json, sys
from pathlib import Path

VERSION = "v101-auth-watch"
DEFAULT_ADDRS = [
    0x0130,0x013B,0x0143,0x0144,0x014A,
    0x037C,0x037D,0x037E,0x037F,
    0x05B1,0x05B4,0x05B6,0x05B7,0x05C6,0x05CD,0x05CE,
    0x05F0,0x071E,0x0722,0x0724,0x076D,0x076E,0x077C,0x077D,0x07F8,0x07F9,0x06E1,0x06EC,0x06ED,0x06F1,0x06F2,0x06F4,0x06FC,
    0xDA00,0xDA0C,0xDA18,0xDA24,0xDA28,0xDA3C,
    0xD904,0xD905,0xD950,0xD954,0xD955,
]
RANGES = [(0x07E8,8,"packet_frame_07E8"),(0xDF30,8,"host_cdb_df30"),(0xDF70,32,"data_fifo_df70"),(0xDF90,32,"data_out_df90")]
LABELS = {
    0x0130:"firmware service flag",0x0143:"drive state mirror",0x0144:"scheduler/event flags",0x014A:"drive state mirror",
    0x05B1:"packet/auth status flags",0x05B4:"media/mech flags",0x05B6:"secondary auth/media flags",0x05B7:"packet result flags",
    0x05CD:"auth transition candidate: 0x40 clears to 0 at partition unlock",
    0x05CE:"neighbor cleared with 0x05CD",
    0x071E:"auth/session state: 1 -> 2 at partition unlock",
    0x0722:"auth/session slot: 0 -> 1 at partition unlock",
    0x0724:"challenge1 bookkeeping",
    0x076D:"session state byte",0x076E:"session state byte",
    0x077C:"session aggregate low byte",0x077D:"session aggregate byte",
    0x07F8:"status word low byte; captured 0x8444 marker",0x07F9:"status word high byte",
    0x06E1:"packet/media state",0x06EC:"packet response byte",0x06ED:"packet response byte",0x06F1:"packet phase/result",
    0xDA0C:"packet/media state",0xD904:"host status",0xD905:"host/DMA status",0xD950:"ATAPI control/status",0xD954:"transfer status",0xD955:"transfer status",
}

def parse_int(s: str) -> int:
    return int(str(s), 0)

def read_byte(buf: bytes, base: int, addr: int) -> int | None:
    off = addr - base
    if off < 0 or off >= len(buf):
        return None
    return buf[off]

def read_range(buf: bytes, base: int, addr: int, n: int) -> list[int] | None:
    off = addr - base
    if off < 0 or off+n > len(buf):
        return None
    return list(buf[off:off+n])

def fmt(vals):
    if vals is None: return None
    if isinstance(vals, int): return f"{vals:02X}"
    return " ".join(f"{v:02X}" for v in vals)

def main(argv: list[str]) -> int:
    ap = argparse.ArgumentParser(description="Compare selected GDR RAM/MMIO state bytes in memory dumps")
    ap.add_argument("--before", type=Path, help="pre-auth/locked memory dump")
    ap.add_argument("--after", type=Path, help="post-auth/unlocked memory dump")
    ap.add_argument("--dump", type=Path, help="single memory dump to inspect")
    ap.add_argument("--base", default="0x0", help="address represented by byte offset 0 in dump")
    ap.add_argument("--addr", action="append", default=[], help="extra byte address to inspect, e.g. 0x05B1")
    ap.add_argument("--out-json", type=Path)
    ns = ap.parse_args(argv[1:])
    base = parse_int(ns.base)
    addrs = sorted(set(DEFAULT_ADDRS + [parse_int(x) for x in ns.addr]))
    if not ns.dump and not (ns.before and ns.after):
        ap.error("pass --dump or both --before and --after")
    result = {"tool":"compare_gdr_memdump_state.py","version":VERSION,"base":hex(base),"bytes":[],"ranges":[]}
    if ns.dump:
        buf = ns.dump.read_bytes()
        print(f"[MEMDUMP] version={VERSION} dump={ns.dump} size={len(buf)} base=0x{base:X}")
        for a in addrs:
            v = read_byte(buf, base, a)
            result["bytes"].append({"addr":f"0x{a:04X}","label":LABELS.get(a,""),"value":fmt(v)})
            if v is not None: print(f"0x{a:04X} {fmt(v)} {LABELS.get(a,'')}")
        for a,n,name in RANGES:
            vals = read_range(buf, base, a, n)
            result["ranges"].append({"addr":f"0x{a:04X}","name":name,"value":fmt(vals)})
            if vals is not None: print(f"0x{a:04X}+{n} {name}: {fmt(vals)}")
    else:
        b0, b1 = ns.before.read_bytes(), ns.after.read_bytes()
        print(f"[MEMDUMP-COMPARE] version={VERSION} before={ns.before} after={ns.after} base=0x{base:X}")
        changes=0
        for a in addrs:
            v0, v1 = read_byte(b0, base, a), read_byte(b1, base, a)
            ch = v0 != v1
            changes += int(ch)
            result["bytes"].append({"addr":f"0x{a:04X}","label":LABELS.get(a,""),"before":fmt(v0),"after":fmt(v1),"changed":ch})
            mark="*" if ch else " "
            print(f"{mark} 0x{a:04X} {fmt(v0)} -> {fmt(v1)} {LABELS.get(a,'')}")
        for a,n,name in RANGES:
            v0, v1 = read_range(b0, base, a, n), read_range(b1, base, a, n)
            ch = v0 != v1
            result["ranges"].append({"addr":f"0x{a:04X}","name":name,"before":fmt(v0),"after":fmt(v1),"changed":ch})
            mark="*" if ch else " "
            print(f"{mark} 0x{a:04X}+{n} {name}: {fmt(v0)} -> {fmt(v1)}")
        result["changed_byte_count"] = changes
    if ns.out_json:
        ns.out_json.write_text(json.dumps(result, indent=2), encoding="utf-8")
        print(f"[OK] wrote {ns.out_json}")
    return 0

if __name__ == "__main__":
    raise SystemExit(main(sys.argv))
