from __future__ import annotations import argparse from pathlib import Path from .analysis import build_call_graph, collect_labels, linear_sweep, trace from .cycles import annotate_cycles from .data_analysis import analyze_unreached_data from .dataflow import analyze_dataflow from .decoder import H8536Decoder from .formatting import parse_int from .indirect import analyze_indirect_flow from .lcd_driver import analyze_lcd_driver from .lcd_text import analyze_lcd_text from .peripheral_access import analyze_peripheral_access from .render import format_callgraph_dot, format_listing, write_json from .rom import Rom from .sci import analyze_sci from .symbols import discover_symbols from .timing import summarize_timing from .vectors import read_dtc_vectors_max, read_dtc_vectors_min, read_vectors_max, read_vectors_min def default_end(data: bytes) -> int: last = 0 for idx, value in enumerate(data): if value != 0xFF: last = idx return min(len(data), ((last + 0x100) // 0x100) * 0x100) def main() -> int: parser = argparse.ArgumentParser(description="Disassemble/decompile a Hitachi H8/536 ROM image.") parser.add_argument("rom", nargs="?", default="ROM/M27C512@DIP28_1.BIN", type=Path, help="ROM binary to decode") parser.add_argument("--out", type=Path, default=Path("build/rom_decompiled.asm"), help="assembly listing output") parser.add_argument("--json", type=Path, default=None, help="optional structured JSON output") parser.add_argument("--mode", choices=("min", "max"), default="min", help="CPU/vector mode; this ROM appears to be min") parser.add_argument("--start", type=parse_int, default=0x0000, help="decode lower address limit") parser.add_argument("--end", type=parse_int, default=None, help="decode upper address limit, exclusive") parser.add_argument("--entry", type=parse_int, action="append", default=[], help="extra entry point to trace") parser.add_argument("--br", type=parse_int, default=None, help="optional BR value for @aa:8 short absolute operands") parser.add_argument("--clock-hz", type=parse_int, default=None, help="oscillator clock in Hz for SCI baud inference") parser.add_argument("--linear", action="store_true", help="linear-sweep the selected range instead of tracing from vectors") parser.add_argument("--cycles", action="store_true", help="append Appendix A cycle estimates to assembly comments") parser.add_argument("--timing", action="store_true", help="include straight-line block and loop cycle summaries") parser.add_argument("--callgraph-dot", type=Path, default=None, help="optional Graphviz DOT call graph output") args = parser.parse_args() if args.clock_hz is not None and args.clock_hz <= 0: parser.error("--clock-hz must be positive") data = args.rom.read_bytes() rom = Rom(data) end = args.end if args.end is not None else default_end(data) vectors = read_vectors_min(rom) if args.mode == "min" else read_vectors_max(rom) dtc_vectors = read_dtc_vectors_min(rom) if args.mode == "min" else read_dtc_vectors_max(rom) starts = [target for _name, target in vectors.values()] + args.entry labels: dict[int, str] = {} decoder = H8536Decoder(rom, br=args.br, labels=labels) decoder.br = args.br if args.linear: instructions = linear_sweep(decoder, args.start, end) else: instructions = trace(decoder, starts, args.start, end) labels.update(collect_labels(instructions.values(), vectors)) decoder.labels = labels # Re-decode now that labels are known, so operands can use stable symbols. decoder.br = args.br if args.linear: instructions = linear_sweep(decoder, args.start, end) else: instructions = trace(decoder, starts, args.start, end) labels.update(collect_labels(instructions.values(), vectors)) annotate_cycles(instructions, args.mode) data_candidates = analyze_unreached_data(rom, instructions, args.start, end) call_graph = build_call_graph(instructions, vectors, labels) dataflow = analyze_dataflow(instructions, labels, call_graph) symbols = discover_symbols(instructions, data_candidates=data_candidates) timing_summary = summarize_timing(instructions, labels, call_graph) if args.timing else None sci_analysis = analyze_sci(instructions, clock_hz=args.clock_hz) peripheral_access = analyze_peripheral_access(instructions) indirect_flow = analyze_indirect_flow(rom, instructions, labels) lcd_text = analyze_lcd_text(rom, instructions, start=args.start, end=end) lcd_driver = analyze_lcd_driver(instructions) args.out.parent.mkdir(parents=True, exist_ok=True) args.out.write_text( format_listing( args.rom, rom, instructions, vectors, labels, args.mode, traced=not args.linear, dtc_vectors=dtc_vectors, data_candidates=data_candidates, timing_summary=timing_summary, show_cycles=args.cycles, sci_analysis=sci_analysis, peripheral_access=peripheral_access, indirect_flow=indirect_flow, dataflow=dataflow, symbols=symbols, lcd_text=lcd_text, lcd_driver=lcd_driver, ), encoding="utf-8", ) if args.json: args.json.parent.mkdir(parents=True, exist_ok=True) write_json( args.json, instructions, vectors, labels, dtc_vectors=dtc_vectors, data_candidates=data_candidates, call_graph=call_graph, timing_summary=timing_summary, sci_analysis=sci_analysis, peripheral_access=peripheral_access, indirect_flow=indirect_flow, dataflow=dataflow, symbols=symbols, lcd_text=lcd_text, lcd_driver=lcd_driver, ) if args.callgraph_dot: args.callgraph_dot.parent.mkdir(parents=True, exist_ok=True) args.callgraph_dot.write_text(format_callgraph_dot(call_graph), encoding="utf-8") invalid = sum(1 for ins in instructions.values() if not ins.valid) print(f"wrote {args.out} ({len(instructions)} items, {invalid} invalid/data bytes)") if args.json: print(f"wrote {args.json}") if args.callgraph_dot: print(f"wrote {args.callgraph_dot}") return 0