from __future__ import annotations import argparse import json import re from collections.abc import Iterable, Mapping from pathlib import Path from typing import Any from .formatting import h16, label_for from .serial_semantics import DIRECT_TABLE_TO_LOGICAL_OFFSET, LOGICAL_TABLES JsonObject = dict[str, Any] TABLES: tuple[JsonObject, ...] = ( { "name": "primary_value_table_candidate", "logical_base_address": 0xE000, "logical_range_end": 0xE3FF, "negative_offset": 0x2000, "element_candidate": "word_value", "direct_addresses": [0xF900], "direct_range_end": 0xF91F, }, { "name": "secondary_value_table_candidate", "logical_base_address": 0xE400, "logical_range_end": 0xE7FF, "negative_offset": 0x1C00, "element_candidate": "word_value", "direct_addresses": [0xF940], "direct_range_end": 0xF95F, }, { "name": "current_value_table_candidate", "logical_base_address": 0xE800, "logical_range_end": 0xEBFF, "negative_offset": 0x1800, "element_candidate": "word_value", "direct_addresses": [0xF920], "direct_range_end": 0xF93F, }, { "name": "flag_table_candidate", "logical_base_address": 0xEC00, "logical_range_end": 0xEFFF, "negative_offset": 0x1400, "element_candidate": "bit_flags", "direct_addresses": [0xF980], "direct_range_end": 0xF99F, }, ) _TABLE_BY_NEGATIVE_OFFSET = {int(item["negative_offset"]): item for item in TABLES} _TABLE_BY_DIRECT_ADDRESS = { address: item for item in TABLES for address in item["direct_addresses"] } LCD_CORRELATION_TERMS = ( "CONNECT", "CONNECT: OK", "CONNECT: NOT ACT", "NOT ACT", "COMM LINK", "COMPLETED", ) def load_table_xref_input(path: Path) -> JsonObject: with path.open("r", encoding="utf-8") as handle: payload = json.load(handle) if not isinstance(payload, dict) or "instructions" not in payload: raise ValueError(f"{path} does not look like h8536_decompiler JSON output") return payload def analyze_table_xrefs(payload: Mapping[str, Any]) -> JsonObject: instructions = _instruction_sequence(payload.get("instructions")) functions = _function_ranges(payload) semantic_accesses = _semantic_access_locations(payload) accesses_by_table = {str(table["name"]): [] for table in TABLES} for index, ins in enumerate(instructions): for access in _logical_operand_accesses(instructions, index, functions, semantic_accesses): accesses_by_table.setdefault(str(access["table"]), []).append(access) for access in _direct_address_accesses(ins, functions, semantic_accesses): accesses_by_table.setdefault(str(access["table"]), []).append(access) tables: list[JsonObject] = [] for table in TABLES: name = str(table["name"]) accesses = sorted(accesses_by_table.get(name, []), key=lambda item: int(item["instruction_address"])) reads = sum(1 for access in accesses if access["access"] == "read") writes = sum(1 for access in accesses if access["access"] == "write") read_write = sum(1 for access in accesses if access["access"] == "read_write_candidate") dynamic = sum(1 for access in accesses if access.get("index") == "dynamic") static_offsets = sorted( { int(access["offset"]) for access in accesses if isinstance(access.get("offset"), int) } ) tables.append( { "name": name, "logical_base_address": table["logical_base_address"], "logical_base_address_hex": h16(int(table["logical_base_address"])), "logical_range_end": table["logical_range_end"], "logical_range_end_hex": h16(int(table["logical_range_end"])), "negative_offset": table["negative_offset"], "negative_offset_hex": h16(int(table["negative_offset"])), "element_candidate": table["element_candidate"], "direct_addresses": table["direct_addresses"], "direct_addresses_hex": [h16(int(address)) for address in table["direct_addresses"]], "direct_range_end": table["direct_range_end"], "direct_range_end_hex": h16(int(table["direct_range_end"])), "access_count": len(accesses), "read_count": reads, "write_count": writes, "read_write_candidate_count": read_write, "dynamic_index_count": dynamic, "static_offsets": static_offsets, "static_offsets_hex": [h16(offset) for offset in static_offsets], "functions": _summarize_functions(accesses), "accesses": accesses, } ) return { "kind": "table_xrefs", "tables": tables, "summary": { "table_count": len(tables), "access_count": sum(int(table["access_count"]) for table in tables), "dynamic_index_count": sum(int(table["dynamic_index_count"]) for table in tables), "source_instruction_count": len(instructions), }, "lcd_correlation": _lcd_correlation_hints(payload), "caveat": ( "Static offsets are emitted only when an index register value can be derived from " "nearby immediate loads in the current JSON. Other indexed accesses are dynamic." ), } def generate_table_xref_report(payload: Mapping[str, Any], *, source_name: str = "") -> str: analysis = analyze_table_xrefs(payload) lines: list[str] = [] suffix = f" for {source_name}" if source_name else "" lines.append(f"Table/Index Cross-Reference Report{suffix}") lines.append("=" * len(lines[0])) lines.append("") lines.append(str(analysis["caveat"])) lines.append("") lines.extend(_format_lcd_correlation_lines(analysis.get("lcd_correlation"))) if lines[-1] != "": lines.append("") for table in analysis["tables"]: name = str(table["name"]) direct = ", ".join(str(item) for item in table["direct_addresses_hex"]) lines.append( f"{name} {table['logical_base_address_hex']}-{table['logical_range_end_hex']} " f"(negative {table['negative_offset_hex']}; direct {direct}-{table['direct_range_end_hex']})" ) lines.append( f" accesses={table['access_count']} reads={table['read_count']} " f"writes={table['write_count']} dynamic={table['dynamic_index_count']}" ) offsets = table.get("static_offsets_hex") or [] if offsets: lines.append(f" static offsets: {', '.join(str(item) for item in offsets[:16])}") function_summaries = table.get("functions") or [] if function_summaries: joined = ", ".join( f"{item['label']}:{item['access_count']}" for item in function_summaries[:12] ) lines.append(f" functions: {joined}") accesses = table.get("accesses") if isinstance(accesses, list) and accesses: for access in accesses[:80]: lines.append(f" - {_format_access_line(access)}") if len(accesses) > 80: lines.append(f" - ... {len(accesses) - 80} more accesses omitted") else: lines.append(" no references found in current JSON") lines.append("") return "\n".join(lines).rstrip() + "\n" def _format_lcd_correlation_lines(value: Any) -> list[str]: if not isinstance(value, Mapping): return [] lines = ["LCD correlation hints"] for hit in value.get("term_hits", []): if not isinstance(hit, Mapping): continue term = hit.get("term") count = int(hit.get("hit_count", 0)) if count: samples = ", ".join( f"{item['address_hex']} {item['trimmed']!r}" for item in hit.get("hits", [])[:4] if isinstance(item, Mapping) ) lines.append(f" term {term!r}: {count} candidate hit(s): {samples}") else: lines.append(f" term {term!r}: no LCD/text candidate hits in current decompile") builders = value.get("display_builder_targets", []) if isinstance(builders, list) and builders: parts = [ f"{item['target_hex']}:{item['xref_count']}" for item in builders[:8] if isinstance(item, Mapping) ] lines.append(f" display builder xrefs: {', '.join(parts)}") routines = value.get("lcd_driver_routines", []) if isinstance(routines, list) and routines: parts = [ f"{item['start_hex']} {item['role_hint']}" for item in routines[:4] if isinstance(item, Mapping) ] lines.append(f" LCD driver routines: {', '.join(parts)}") lines.append( " caveat: LCD strings can be builder/script output; absence of a literal term does not disprove runtime composition." ) return lines def write_table_xrefs(input_path: Path, output_path: Path, *, as_json: bool = False) -> None: payload = load_table_xref_input(input_path) output_path.parent.mkdir(parents=True, exist_ok=True) if as_json: analysis = analyze_table_xrefs(payload) analysis["source"] = str(input_path) output_path.write_text(json.dumps(analysis, indent=2), encoding="utf-8") else: output_path.write_text(generate_table_xref_report(payload, source_name=str(input_path)), encoding="utf-8") def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser( description="Generate table/index cross-references for candidate serial protocol data tables.", ) parser.add_argument( "input", nargs="?", type=Path, default=Path("build/rom_decompiled.json"), help="structured JSON emitted by h8536_decompiler.py", ) parser.add_argument( "--out", type=Path, default=Path("build/rom_table_xrefs.txt"), help="table cross-reference report output path", ) parser.add_argument("--json", action="store_true", help="write structured JSON instead of text") args = parser.parse_args(argv) write_table_xrefs(args.input, args.out, as_json=args.json) print(f"wrote {args.out}") return 0 def _logical_operand_accesses( instructions: list[JsonObject], index: int, functions: list[JsonObject], semantic_accesses: Mapping[int, list[JsonObject]], ) -> list[JsonObject]: ins = instructions[index] accesses: list[JsonObject] = [] operands = str(ins.get("operands", "")) for operand in _negative_indexed_operands(operands): table = _TABLE_BY_NEGATIVE_OFFSET.get(int(operand["negative_offset"])) if table is None: continue register = str(operand["index_register"]) known = _nearby_register_value(instructions, index, register) offset: int | str = known if known is not None else "dynamic" logical_address: int | None = None if isinstance(offset, int): logical_address = (int(table["logical_base_address"]) + offset) & 0xFFFF selector = _selector_for_table_offset(table, offset) access = _base_access(ins, functions, semantic_accesses) access.update( { "table": table["name"], "table_base_address": table["logical_base_address"], "table_base_address_hex": h16(int(table["logical_base_address"])), "kind": "logical_negative_indexed_access", "operand": operand["operand"], "negative_offset": operand["negative_offset"], "negative_offset_hex": h16(int(operand["negative_offset"])), "index_register": register, "index": offset, "offset": offset, "access": _operand_access_kind(ins, str(operand["operand"])), } ) if logical_address is not None: access["logical_address"] = logical_address access["logical_address_hex"] = h16(logical_address) if selector is not None: access["selector"] = selector access["selector_hex"] = f"0x{selector:03X}" accesses.append(access) return accesses def _direct_address_accesses( ins: Mapping[str, Any], functions: list[JsonObject], semantic_accesses: Mapping[int, list[JsonObject]], ) -> list[JsonObject]: accesses: list[JsonObject] = [] refs = _references(ins) for address in refs: logical_table = _table_for_logical_address(address) if logical_table is not None: accesses.append( _direct_logical_address_access(ins, logical_table, address, functions, semantic_accesses), ) continue direct_table = _table_for_direct_candidate_address(address) if direct_table is not None: accesses.append( _direct_candidate_address_access(ins, direct_table, address, functions, semantic_accesses), ) return accesses def _direct_logical_address_access( ins: Mapping[str, Any], table: Mapping[str, Any], address: int, functions: list[JsonObject], semantic_accesses: Mapping[int, list[JsonObject]], ) -> JsonObject: base = int(table["logical_base_address"]) offset = address - base selector = _selector_for_table_offset(table, offset) access = _base_access(ins, functions, semantic_accesses) access.update( { "table": table["name"], "table_base_address": base, "table_base_address_hex": h16(base), "kind": "direct_logical_address_access", "direct_address": address, "direct_address_hex": h16(address), "logical_address": address, "logical_address_hex": h16(address), "index": offset, "offset": offset, "offset_hex": h16(offset), "access": _access_direction(ins, address) or "read_write_candidate", } ) if selector is not None: access["selector"] = selector access["selector_hex"] = f"0x{selector:03X}" return access def _direct_candidate_address_access( ins: Mapping[str, Any], table: Mapping[str, Any], address: int, functions: list[JsonObject], semantic_accesses: Mapping[int, list[JsonObject]], ) -> JsonObject: base = min(int(item) for item in table["direct_addresses"]) offset = address - base access = _base_access(ins, functions, semantic_accesses) logical_offset = DIRECT_TABLE_TO_LOGICAL_OFFSET.get(base) selector = _selector_for_table_offset(table, offset) access.update( { "table": table["name"], "table_base_address": table["logical_base_address"], "table_base_address_hex": h16(int(table["logical_base_address"])), "kind": "direct_candidate_address_access", "direct_address": address, "direct_address_hex": h16(address), "direct_base_address": base, "direct_base_address_hex": h16(base), "index": offset, "offset": offset, "offset_hex": h16(offset), "access": _access_direction(ins, address) or "read_write_candidate", } ) if logical_offset is not None: access["semantic_negative_offset"] = logical_offset access["semantic_negative_offset_hex"] = h16(logical_offset) if selector is not None: access["selector"] = selector access["selector_hex"] = f"0x{selector:03X}" return access def _lcd_correlation_hints(payload: Mapping[str, Any]) -> JsonObject: lcd_text = payload.get("lcd_text") strings = [] if isinstance(lcd_text, Mapping) and isinstance(lcd_text.get("strings"), list): strings = [item for item in lcd_text["strings"] if isinstance(item, Mapping)] term_hits = [] for term in LCD_CORRELATION_TERMS: hits = [] upper_term = term.upper() for item in strings: text = f"{item.get('text', '')} {item.get('trimmed', '')}".upper() if upper_term not in text: continue hits.append(_lcd_string_summary(item)) term_hits.append( { "term": term, "hit_count": len(hits), "hits": hits[:24], "status": "candidate_hits" if hits else "not_found", } ) builder_targets: dict[int, JsonObject] = {} for item in strings: for xref in item.get("xrefs", []): if not isinstance(xref, Mapping): continue following = xref.get("following_bsr") if not isinstance(following, Mapping) or not isinstance(following.get("target"), int): continue target = int(following["target"]) record = builder_targets.setdefault( target, { "target": target, "target_hex": h16(target), "xref_count": 0, "examples": [], }, ) record["xref_count"] = int(record["xref_count"]) + 1 examples = record["examples"] if isinstance(examples, list) and len(examples) < 8: examples.append( { "text_address": item.get("address"), "text_address_hex": h16(int(item["address"])) if isinstance(item.get("address"), int) else None, "trimmed": item.get("trimmed"), "xref_address": xref.get("address"), "xref_address_hex": h16(int(xref["address"])) if isinstance(xref.get("address"), int) else None, } ) lcd_driver = payload.get("lcd_driver") routines = [] if isinstance(lcd_driver, Mapping) and isinstance(lcd_driver.get("routines"), list): for routine in lcd_driver["routines"]: if not isinstance(routine, Mapping) or not isinstance(routine.get("start"), int): continue routines.append( { "start": routine["start"], "start_hex": h16(int(routine["start"])), "end": routine.get("end"), "end_hex": h16(int(routine["end"])) if isinstance(routine.get("end"), int) else None, "role_hint": routine.get("role_hint"), "roles": routine.get("roles", []), } ) return { "terms": list(LCD_CORRELATION_TERMS), "term_hits": term_hits, "display_builder_targets": sorted( builder_targets.values(), key=lambda item: (-int(item["xref_count"]), int(item["target"])), ), "lcd_driver_routines": routines, "caveat": ( "This is a static correlation helper. It reports text/script candidates and LCD driver " "routines in the same decompile; it does not prove a protocol field directly causes a string." ), } def _lcd_string_summary(item: Mapping[str, Any]) -> JsonObject: address = item.get("address") return { "address": address, "address_hex": h16(int(address)) if isinstance(address, int) else None, "text": item.get("text"), "trimmed": item.get("trimmed"), "confidence": item.get("confidence"), "xref_count": item.get("xref_count", 0), } def _base_access( ins: Mapping[str, Any], functions: list[JsonObject], semantic_accesses: Mapping[int, list[JsonObject]], ) -> JsonObject: address = int(ins["address"]) function = _function_for_address(functions, address) access: JsonObject = { "instruction_address": address, "instruction_address_hex": h16(address), "mnemonic": str(ins.get("mnemonic", "")), "operands": str(ins.get("operands", "")), "instruction": str(ins.get("text") or _instruction_text(ins)), "references": _references(ins), "references_hex": [h16(ref) for ref in _references(ins)], "targets": _targets(ins), "targets_hex": [h16(target) for target in _targets(ins)], "label": _label_for_instruction(ins), "semantic_candidates": semantic_accesses.get(address, []), } if function: access["function_start"] = function["start"] access["function_start_hex"] = h16(int(function["start"])) access["function_label"] = function["label"] return access def _semantic_access_locations(payload: Mapping[str, Any]) -> dict[int, list[JsonObject]]: locations: dict[int, list[JsonObject]] = {} semantics = payload.get("serial_semantics") if not isinstance(semantics, Mapping): return locations sources: list[Any] = [] protocols = semantics.get("protocol_semantics") if isinstance(protocols, list): sources.extend(protocols) sources.append(semantics) for source in sources: if not isinstance(source, Mapping): continue for item in _table_candidate_items(source.get("table_map_candidates")): for access in _table_candidate_items(item.get("accesses")): address = access.get("instruction_address") if isinstance(address, int): locations.setdefault(address, []).append( { "name_candidate": item.get("name_candidate"), "kind": item.get("kind"), "confidence": item.get("confidence"), } ) return locations def _table_candidate_items(value: Any) -> list[Mapping[str, Any]]: if isinstance(value, Mapping): return [item for item in value.values() if isinstance(item, Mapping)] if isinstance(value, list): return [item for item in value if isinstance(item, Mapping)] return [] def _format_access_line(access: Mapping[str, Any]) -> str: function = access.get("function_label") or "" operand = access.get("operand") or access.get("direct_address_hex") index = access.get("index") if index == "dynamic": index_text = f"index dynamic via {access.get('index_register')} operand {operand}" else: index_text = f"offset {h16(int(index or 0))}" if access.get("selector_hex"): index_text += f" selector {access['selector_hex']}" if access.get("logical_address_hex"): index_text += f" -> {access['logical_address_hex']}" elif access.get("direct_address_hex"): index_text += f" at {access['direct_address_hex']}" return ( f"{access['instruction_address_hex']} {access['access']} {index_text}; " f"{function}; {access['instruction']}" ) def _summarize_functions(accesses: Iterable[Mapping[str, Any]]) -> list[JsonObject]: summaries: dict[int, JsonObject] = {} for access in accesses: start = access.get("function_start") if not isinstance(start, int): start = -1 summary = summaries.setdefault( start, { "start": start if start >= 0 else None, "start_hex": h16(start) if start >= 0 else None, "label": access.get("function_label") or "", "access_count": 0, "reads": 0, "writes": 0, }, ) summary["access_count"] = int(summary["access_count"]) + 1 if access.get("access") == "read": summary["reads"] = int(summary["reads"]) + 1 elif access.get("access") == "write": summary["writes"] = int(summary["writes"]) + 1 return sorted(summaries.values(), key=lambda item: (-int(item["access_count"]), str(item["label"]))) def _selector_for_table_offset(table: Mapping[str, Any], offset: int | str) -> int | None: if not isinstance(offset, int): return None element = str(table.get("element_candidate") or "") if element == "word_value": if offset % 2: return None return (offset // 2) & 0x01FF if element == "bit_flags": return offset & 0x01FF return None def _function_ranges(payload: Mapping[str, Any]) -> list[JsonObject]: call_graph = payload.get("call_graph") if not isinstance(call_graph, Mapping): return [] nodes = call_graph.get("nodes") if not isinstance(nodes, list): return [] ranges: list[JsonObject] = [] for node in nodes: if not isinstance(node, Mapping): continue start = node.get("start") end = node.get("end") if isinstance(start, int) and isinstance(end, int): ranges.append({"start": start, "end": end, "label": str(node.get("label") or label_for(start))}) return sorted(ranges, key=lambda item: int(item["start"])) def _function_for_address(functions: list[JsonObject], address: int) -> JsonObject | None: for function in functions: if int(function["start"]) <= address <= int(function["end"]): return function return None def _nearby_register_value(instructions: list[JsonObject], index: int, register: str) -> int | None: register = register.upper() for prior_index in range(index - 1, max(-1, index - 10), -1): prior = instructions[prior_index] source, destination = _source_destination_operands(str(prior.get("operands", ""))) if destination.upper() != register: continue value = _parse_immediate(source) if value is not None: return value if _writes_register(prior, register): return None return None def _writes_register(ins: Mapping[str, Any], register: str) -> bool: _source, destination = _source_destination_operands(str(ins.get("operands", ""))) return destination.upper() == register def _instruction_sequence(value: object) -> list[JsonObject]: if isinstance(value, Mapping): values: Iterable[Any] = value.values() elif isinstance(value, list): values = value else: values = [] return sorted( [item for item in values if isinstance(item, dict) and isinstance(item.get("address"), int)], key=lambda item: int(item["address"]), ) def _label_for_instruction(ins: Mapping[str, Any]) -> str | None: address = int(ins["address"]) for key in ("label", "target_label"): value = ins.get(key) if isinstance(value, str) and value: return value if _targets(ins): return label_for(address) return None def _instruction_text(ins: Mapping[str, Any]) -> str: operands = str(ins.get("operands", "")) return f"{ins.get('mnemonic', '')} {operands}".strip() def _references(ins: Mapping[str, Any]) -> list[int]: refs = ins.get("references", []) if not isinstance(refs, list): return [] output: list[int] = [] for ref in refs: if isinstance(ref, Mapping) and isinstance(ref.get("address"), int): output.append(int(ref["address"])) elif isinstance(ref, int): output.append(ref) return output def _targets(ins: Mapping[str, Any]) -> list[int]: targets = ins.get("targets", []) if not isinstance(targets, list): return [] return [int(target) for target in targets if isinstance(target, int)] def _negative_indexed_operands(operands: str) -> list[JsonObject]: matches: list[JsonObject] = [] for match in re.finditer(r"@\(-H'([0-9A-Fa-f]+),\s*(R[0-7])\)", operands): offset = int(match.group(1), 16) & 0xFFFF if offset not in LOGICAL_TABLES: continue matches.append( { "operand": match.group(0), "negative_offset": offset, "index_register": match.group(2).upper(), } ) return matches def _table_for_logical_address(address: int) -> Mapping[str, Any] | None: for table in TABLES: if int(table["logical_base_address"]) <= address <= int(table["logical_range_end"]): return table return None def _table_for_direct_candidate_address(address: int) -> Mapping[str, Any] | None: for table in TABLES: direct_addresses = [int(item) for item in table["direct_addresses"]] if min(direct_addresses) <= address <= int(table["direct_range_end"]): return table return None def _operand_access_kind(ins: Mapping[str, Any], operand: str) -> str: root = _mnemonic_root(str(ins.get("mnemonic", ""))) source, destination = _source_destination_operands(str(ins.get("operands", ""))) if root in {"BTST", "CMP", "CMP:E", "CMP:G", "CMP:I", "TST"}: return "read" if root in {"BCLR", "BNOT", "BSET", "CLR", "INC", "INC:G", "NEG", "NOT"}: return "write" if operand in destination and operand not in source: return "write" if operand in source and operand not in destination: return "read" if root in {"ADD:Q", "ADD:G", "ADDS", "ADDX", "AND", "OR", "SUB", "SUBS", "SUBX", "XOR"}: return "write" return "read_write_candidate" def _access_direction(ins: Mapping[str, Any], address: int) -> str | None: root = _mnemonic_root(str(ins.get("mnemonic", ""))) if root in {"BTST", "CMP", "CMP:E", "CMP:G", "CMP:I", "MOVFPE", "TST"}: return "read" if root in {"BCLR", "BNOT", "BSET", "CLR", "INC", "INC:G", "NEG", "NOT"}: return "write" if root in {"ADD:Q", "ADD:G", "ADDS", "ADDX", "AND", "OR", "SUB", "SUBS", "SUBX", "XOR"}: return "write" if root in {"MOV:G", "MOV:S", "MOVTPE"}: source, destination = _source_destination_operands(str(ins.get("operands", ""))) if _operand_mentions_address(destination, address): return "write" if _operand_mentions_address(source, address): return "read" if address in _references(ins): if destination.startswith("@") and not _operand_mentions_any_reference(source, _references(ins)): return "write" if source.startswith("@") and not _operand_mentions_any_reference(destination, _references(ins)): return "read" if root in {"MOV:L", "MOV:F"}: return "read" if root == "STC": return "write" if root == "LDC": return "read" return None def _source_destination_operands(operands: str) -> tuple[str, str]: depth = 0 split_at: int | None = None for index, char in enumerate(operands): if char in "({": depth += 1 elif char in ")}" and depth: depth -= 1 elif char == "," and depth == 0: split_at = index if split_at is None: operand = operands.strip() return "", operand return operands[:split_at].strip(), operands[split_at + 1 :].strip() def _parse_immediate(operand: str) -> int | None: text = operand.strip() if text.startswith("#"): text = text[1:].strip() try: if text.upper().startswith("H'"): return int(text[2:], 16) & 0xFFFF if text.upper().startswith("0X"): return int(text, 16) & 0xFFFF if text.upper().startswith("$"): return int(text[1:], 16) & 0xFFFF return int(text, 10) & 0xFFFF except ValueError: return None def _operand_mentions_any_reference(operand: str, references: list[int]) -> bool: return any(_operand_mentions_address(operand, address) for address in references) def _operand_mentions_address(operand: str, address: int) -> bool: operand_upper = operand.upper().replace(" ", "") negative = (0x10000 - address) & 0xFFFF return ( f"H'{address:04X}" in operand_upper or f"0X{address:04X}" in operand_upper or f"${address:04X}" in operand_upper or f"-H'{negative:04X}" in operand_upper or f"-0X{negative:04X}" in operand_upper or f"-${negative:04X}" in operand_upper ) def _mnemonic_root(mnemonic: str) -> str: return mnemonic.rsplit(".", 1)[0].upper() __all__ = [ "analyze_table_xrefs", "generate_table_xref_report", "load_table_xref_input", "main", "write_table_xrefs", ]