1
0

LCD decompile

This commit is contained in:
Aiden
2026-05-25 15:10:32 +10:00
parent 1d7f00e59c
commit cdfb811c28
15 changed files with 8836 additions and 19 deletions

View File

@@ -8,6 +8,8 @@ from .dataflow import state_for_instruction
from .dtc import DtcEndpointInfo, DtcRegisterInfo
from .formatting import h16, label_for
from .indirect import indirect_comment_for_instruction, indirect_metadata_for_instruction
from .lcd_driver import lcd_comment_for_instruction, lcd_metadata_for_instruction
from .lcd_text import lcd_text_comment_for_instruction
from .memory import MEMORY_REGIONS, region_for
from .model import Instruction
from .peripheral_access import (
@@ -118,6 +120,103 @@ def _dataflow_comment(analysis: dict[str, object] | None, address: int) -> str:
return "dataflow " + ", ".join(parts[:4]) + suffix
def _lcd_text_lines(lcd_text: dict[str, object] | None) -> list[str]:
if not lcd_text:
return []
strings = lcd_text.get("strings", [])
regions = lcd_text.get("regions", [])
searches = lcd_text.get("searches", [])
if not strings and not regions and not searches:
return []
lines = ["; LCD/Text Scan"]
for search in (searches if isinstance(searches, list) else []):
if not isinstance(search, dict):
continue
hits = len(search.get("literal_hits", [])) + len(search.get("candidate_hits", []))
status = "found" if hits else "not literal"
lines.append(f"; search {search.get('term')!r}: {status}, hits={hits}")
near = search.get("near_matches", [])
if isinstance(near, list) and near:
sample = ", ".join(f"{h16(int(item['address']))} {item['trimmed']!r}" for item in near[:4])
lines.append(f"; near: {sample}")
if isinstance(regions, list) and regions:
lines.append("; LCD text regions")
for region in regions[:12]:
if not isinstance(region, dict):
continue
samples = ", ".join(repr(sample) for sample in region.get("samples", [])[:4])
lines.append(
f"; region {h16(int(region['start']))}-{h16(int(region['end']))} "
f"count={region['count']:<3} {samples}",
)
if len(regions) > 12:
lines.append(f"; ... {len(regions) - 12} more LCD text regions")
if isinstance(strings, list) and strings:
lines.append("; LCD text candidates")
shown = 0
for item in strings:
if not isinstance(item, dict):
continue
if item.get("confidence") == "low" and not item.get("xref_count"):
continue
xrefs = f" xrefs={item['xref_count']}" if item.get("xref_count") else ""
lines.append(
f"; text {h16(int(item['address'])):<8} len={item['length']:<3} "
f"{item['confidence']:<6} {str(item['trimmed'])!r}{xrefs}",
)
shown += 1
if shown >= 48:
break
if len(strings) > shown:
lines.append(f"; ... {len(strings) - shown} more LCD text candidates")
lines.append("")
return lines
def _lcd_driver_lines(lcd_driver: dict[str, object] | None) -> list[str]:
if not lcd_driver:
return []
accesses = lcd_driver.get("accesses", [])
loops = lcd_driver.get("polling_loops", [])
routines = lcd_driver.get("routines", [])
if not accesses and not loops and not routines:
return []
lines = ["; LCD Driver Candidates"]
for address_info in lcd_driver.get("addresses", []):
if not isinstance(address_info, dict):
continue
lines.append(
f"; {h16(int(address_info['address']))} {address_info['name']:<18} {address_info['role']}",
)
if isinstance(routines, list) and routines:
lines.append("; LCD routines")
for routine in routines[:16]:
if not isinstance(routine, dict):
continue
roles = ", ".join(str(role) for role in routine.get("roles", []))
lines.append(
f"; routine {h16(int(routine['start']))}-{h16(int(routine['end']))} "
f"{routine['role_hint']:<24} {roles}",
)
if len(routines) > 16:
lines.append(f"; ... {len(routines) - 16} more LCD routines")
if isinstance(loops, list) and loops:
lines.append("; LCD busy loops")
for loop in loops[:16]:
if not isinstance(loop, dict):
continue
lines.append(
f"; loop {h16(int(loop['read_address']))}->{h16(int(loop['branch_address']))} "
f"{loop['summary']}",
)
lines.append("")
return lines
def format_listing(
rom_path: Path,
rom: Rom,
@@ -135,6 +234,8 @@ def format_listing(
indirect_flow: dict[str, object] | None = None,
dataflow: dict[str, object] | None = None,
symbols: dict[str, object] | None = None,
lcd_text: dict[str, object] | None = None,
lcd_driver: dict[str, object] | None = None,
) -> str:
lines: list[str] = []
lines.append("; H8/536 ROM disassembly")
@@ -149,6 +250,7 @@ def format_listing(
lines.append("; - The register field is H'FE80-H'FFFF; names below come from appendix B.")
lines.append("; - @aa:8 short absolute operands use BR as the upper address byte.")
lines.append("; - SCI baud inference uses section 14.2.8 BRR formulas when SMR/BRR are known.")
lines.append("; - LCD inference treats E-clock H'F200/H'F201 accesses as status/control and data candidates.")
if sci_analysis and sci_analysis.get("clock_hz") is None:
lines.append("; - Pass --clock-hz to convert SCI BRR settings into numeric baud rates.")
if show_cycles:
@@ -192,6 +294,8 @@ def format_listing(
lines.append("")
lines.extend(_symbol_lines(symbols))
lines.extend(_lcd_text_lines(lcd_text))
lines.extend(_lcd_driver_lines(lcd_driver))
if timing_summary:
lines.extend(format_timing_summary(timing_summary))
@@ -210,6 +314,8 @@ def format_listing(
sci_comment_for_instruction(sci_analysis, address),
peripheral_comment_for_instruction(peripheral_access, address),
indirect_comment_for_instruction(indirect_flow, address),
lcd_text_comment_for_instruction(lcd_text, address),
lcd_comment_for_instruction(lcd_driver, address),
_dataflow_comment(dataflow, address),
_reference_comment(ins, symbols) if not ins.comment else "",
cycle_comment(ins.cycles) if show_cycles else "",
@@ -236,6 +342,8 @@ def write_json(
indirect_flow: dict[str, object] | None = None,
dataflow: dict[str, object] | None = None,
symbols: dict[str, object] | None = None,
lcd_text: dict[str, object] | None = None,
lcd_driver: dict[str, object] | None = None,
) -> None:
payload = {
"vectors": [
@@ -261,8 +369,10 @@ def write_json(
"indirect_flow": indirect_flow or {"sites": []},
"dataflow": _dataflow_json_payload(dataflow),
"symbols": symbols or {"symbols": [], "by_address": {}},
"lcd_text": lcd_text or {"strings": [], "regions": [], "searches": []},
"lcd_driver": lcd_driver or {"accesses": [], "polling_loops": [], "routines": []},
"instructions": [
_instruction_payload(ins, sci_analysis, peripheral_access, indirect_flow, dataflow, symbols)
_instruction_payload(ins, sci_analysis, peripheral_access, indirect_flow, dataflow, symbols, lcd_text, lcd_driver)
for ins in (instructions[addr] for addr in sorted(instructions))
],
}
@@ -319,6 +429,8 @@ def _instruction_payload(
indirect_flow: dict[str, object] | None = None,
dataflow: dict[str, object] | None = None,
symbols: dict[str, object] | None = None,
lcd_text: dict[str, object] | None = None,
lcd_driver: dict[str, object] | None = None,
) -> dict[str, object]:
payload: dict[str, object] = {
"address": ins.address,
@@ -355,6 +467,12 @@ def _instruction_payload(
dataflow_metadata = _dataflow_instruction_payload(dataflow, ins.address)
if dataflow_metadata:
payload["dataflow"] = dataflow_metadata
lcd_text_comment = lcd_text_comment_for_instruction(lcd_text, ins.address)
if lcd_text_comment:
payload["lcd_text"] = {"comment": lcd_text_comment}
lcd_driver_metadata = lcd_metadata_for_instruction(lcd_driver, ins.address)
if lcd_driver_metadata:
payload["lcd_driver"] = lcd_driver_metadata
return payload