1
0
Files
h8-536-decoder/tests/test_ccu_seed_hints.py
2026-05-26 11:07:36 +10:00

119 lines
4.2 KiB
Python

import io
import json
import tempfile
import unittest
from pathlib import Path
from h8536.ccu_seed_hints import (
analyze_ccu_seed_hints,
checksum,
encode_host_frame,
frame_hex,
main,
selector_bytes,
write_ccu_seed_hints,
)
def reference(address: int) -> dict:
return {"address": address}
def instruction(address: int, mnemonic: str, operands: str = "", refs: list[int] | None = None) -> dict:
return {
"address": address,
"mnemonic": mnemonic,
"operands": operands,
"text": f"{mnemonic} {operands}".strip(),
"references": [reference(item) for item in (refs or [])],
"targets": [],
}
def payload() -> dict:
return {
"instructions": [
instruction(0xC000, "MOV:G.W", "#H'0006, R3"),
instruction(0xC004, "MOV:G.W", "@(-H'2000,R3), R0"),
instruction(0xC008, "MOV:G.W", "R1, @H'E1EC", [0xE1EC]),
],
"call_graph": {
"nodes": [
{"start": 0xC000, "end": 0xC0FF, "label": "loc_C000"},
],
},
"indirect_flow": {
"sites": [
{
"table": {
"base": 0x28A6,
"entries": [
{"index": 0, "entry_address": 0x28A6, "target": 0x2CB9, "target_label": "loc_2CB9"},
{"index": 1, "entry_address": 0x28A8, "target": 0x1234, "target_label": "loc_1234"},
{"index": 2, "entry_address": 0x28AA, "target": 0x1234, "target_label": "loc_1234"},
],
},
}
],
},
}
class CcuSeedHintsTest(unittest.TestCase):
def test_selector_encoding_matches_loc_622b_ranges(self):
self.assertEqual(selector_bytes(0x000), (0x00, 0x00))
self.assertEqual(selector_bytes(0x07F), (0x00, 0x7F))
self.assertEqual(selector_bytes(0x080), (0x01, 0x00))
self.assertEqual(selector_bytes(0x17F), (0x01, 0xFF))
self.assertEqual(selector_bytes(0x180), (0x02, 0x00))
self.assertEqual(selector_bytes(0x1FF), (0x02, 0x7F))
def test_frame_encoding_uses_xor_seed(self):
frame = encode_host_frame(0x00, 0x000, 0x8080)
self.assertEqual(frame, [0x00, 0x00, 0x00, 0x80, 0x80, 0x5A])
self.assertEqual(checksum(frame[:5]), frame[5])
self.assertEqual(frame_hex(frame), "00 00 00 80 80 5A")
def test_analysis_emits_seed_plan_and_selector_reasons(self):
analysis = analyze_ccu_seed_hints(payload(), rom_path=None)
by_selector = {
int(item["selector"]): item
for item in analysis["selector_candidates"]
}
self.assertEqual(analysis["kind"], "ccu_seed_hints")
self.assertIn(0x000, by_selector)
self.assertIn(0x0F6, by_selector)
self.assertIn("00 00 00 80 80 5A", [step["frame"] for step in analysis["seed_plan"]["steps"]])
self.assertIn("01 01 76 00 00 2C", [step["readback_frame"] for step in analysis["seed_plan"]["steps"]])
def test_write_json_output(self):
with tempfile.TemporaryDirectory() as tmp:
input_path = Path(tmp) / "rom.json"
output_path = Path(tmp) / "hints.json"
input_path.write_text(json.dumps(payload()), encoding="utf-8")
write_ccu_seed_hints(input_path, output_path, rom_path=None, as_json=True)
written = json.loads(output_path.read_text(encoding="utf-8"))
self.assertEqual(written["kind"], "ccu_seed_hints")
self.assertGreaterEqual(written["summary"]["candidate_count"], 1)
def test_cli_writes_text_report(self):
with tempfile.TemporaryDirectory() as tmp:
input_path = Path(tmp) / "rom.json"
output_path = Path(tmp) / "hints.txt"
input_path.write_text(json.dumps(payload()), encoding="utf-8")
stdout = io.StringIO()
rc = main([str(input_path), "--rom", str(Path(tmp) / "missing.bin"), "--out", str(output_path)], stdout=stdout)
self.assertEqual(rc, 0)
self.assertIn("wrote", stdout.getvalue())
self.assertIn("Candidate Fake-CCU Seed Plan", output_path.read_text(encoding="utf-8"))
if __name__ == "__main__":
unittest.main()