1
0

More decompiling work

This commit is contained in:
Aiden
2026-05-25 17:32:00 +10:00
parent 56829b6e0b
commit 07f48c76e0
22 changed files with 9837 additions and 5 deletions

View File

@@ -21,6 +21,14 @@ TX_CHECKSUM_ADDRESS = TX_FRAME_END
SEND_BUILDER_ADDRESS = 0xBA26
SEND_BUILDER_LABEL = "loc_BA26"
AUTONOMOUS_TX_REPORT_CALL = 0xBB43
AUTONOMOUS_TX_REPORT_LABEL = "loc_BB43"
MAIN_REPORT_GATE_ENTRY = 0x3FD3
MAIN_REPORT_GATE_CALL = 0x3FEB
SESSION_GATE_ENTRY = 0x3FEF
QUEUE_REPORT_ENTRY = 0xBAF2
RESEND_GATE_ENTRY = 0xBE9E
PERIODIC_RESEND_ENTRY = 0xBED5
INDEX_DECODER_ADDRESS = 0x622B
INDEX_DECODER_LABEL = "loc_622B"
CHECKSUM_SEED = 0x5A
@@ -74,8 +82,29 @@ STATE_VARIABLES = {
0xF9B5: "event_queue_write_or_pending_cursor_candidate",
0xF9B9: "event_queue_base_or_current_slot_candidate",
0xF9C0: "serial_tx_busy_timer_candidate",
0xF9C6: "autonomous_report_period_timer_candidate",
0xF9C8: "autonomous_report_resend_countdown_candidate",
}
OBSERVED_TX_REPORT_OVERLAY = [
{
"logical_index": 0x0000,
"name_candidate": "heartbeat_or_idle_report_candidate",
"observed_frames_hex": ["00 00 00 00 80 DA"],
"observed_period_ms_candidate": 700,
},
{
"logical_index": 0x0015,
"name_candidate": "call_button_report_candidate",
"observed_frames_hex": ["00 00 15 80 00 CF", "00 00 15 00 00 4F"],
},
{
"logical_index": 0x0007,
"name_candidate": "camera_power_report_candidate",
"observed_frames_hex": ["00 00 07 80 00 DD"],
},
]
def analyze_serial_semantics(payload: Mapping[str, Any]) -> JsonObject:
"""Infer conservative SCI1 frame/command semantics from decompiler JSON."""
@@ -100,6 +129,9 @@ def analyze_serial_semantics(payload: Mapping[str, Any]) -> JsonObject:
"table_map_candidates": [],
"state_variable_candidates": [],
"retry_error_model": None,
"gate_queue_model": None,
"tx_report_model": None,
"periodic_resend_model": None,
"confidence": "low",
"confidence_score": 0.0,
"caveat": "No protocol semantics are emitted without both RX and TX serial reconstruction candidates.",
@@ -113,6 +145,9 @@ def analyze_serial_semantics(payload: Mapping[str, Any]) -> JsonObject:
logical_tables = _logical_table_map_candidates(ordered)
state_variables = _state_variable_candidates(ordered)
retry_error_model = _retry_error_model(ordered, responses)
gate_queue_model = _gate_queue_model(ordered, commands)
tx_report_model = _tx_report_model(ordered, responses)
periodic_resend_model = _periodic_resend_model(ordered, responses)
evidence = _top_level_evidence(ordered, dispatch, responses, rx_candidate, tx_candidate)
confidence_score = _confidence_score(frame_supported, dispatch, responses, commands)
@@ -164,6 +199,9 @@ def analyze_serial_semantics(payload: Mapping[str, Any]) -> JsonObject:
"rx_fields": _rx_field_candidates(ordered, dispatch),
"response_builders": _response_builder_aliases(responses),
"retry_error_model": retry_error_model,
"gate_queue_model": gate_queue_model,
"tx_report_model": tx_report_model,
"periodic_resend_model": periodic_resend_model,
"evidence": evidence,
}
return {
@@ -181,6 +219,9 @@ def analyze_serial_semantics(payload: Mapping[str, Any]) -> JsonObject:
"table_map_candidates": protocol["table_map_candidates"],
"state_variable_candidates": protocol["state_variable_candidates"],
"retry_error_model": protocol["retry_error_model"],
"gate_queue_model": protocol["gate_queue_model"],
"tx_report_model": protocol["tx_report_model"],
"periodic_resend_model": protocol["periodic_resend_model"],
"confidence": protocol["confidence"],
"confidence_score": protocol["confidence_score"],
"caveat": protocol["caveat"],
@@ -1576,6 +1617,324 @@ def _retry_error_model(ordered: list[JsonObject], responses: list[JsonObject]) -
}
def _gate_queue_model(ordered: list[JsonObject], commands: list[JsonObject]) -> JsonObject | None:
evidence = _dedupe_ints(
_addresses_in_ranges(ordered, [(MAIN_REPORT_GATE_ENTRY, MAIN_REPORT_GATE_CALL)], MAIN_REPORT_GATE_ENTRY, MAIN_REPORT_GATE_CALL)
+ _addresses_in_ranges(ordered, [(SESSION_GATE_ENTRY, 0x4007)], SESSION_GATE_ENTRY, 0x4007)
+ _addresses_in_ranges(ordered, [(QUEUE_REPORT_ENTRY, AUTONOMOUS_TX_REPORT_CALL)], QUEUE_REPORT_ENTRY, AUTONOMOUS_TX_REPORT_CALL)
+ _addresses_in_ranges(ordered, [(RESEND_GATE_ENTRY, PERIODIC_RESEND_ENTRY)], RESEND_GATE_ENTRY, PERIODIC_RESEND_ENTRY)
)
command_ack_values = [
int(command["command_value"])
for command in commands
if command.get("command_value") in {0x05, 0x06}
]
if not evidence and not command_ack_values:
return None
return {
"kind": "serial_gate_queue_state_machine_candidate",
"summary": (
"Conservative model for autonomous report gating, queue cursor comparison, "
"periodic resend, and RX/session side effects."
),
"predicates": [
{
"name": "main_loop_may_enter_report_builder",
"entry_label": "loc_3FD3",
"target_label": "loc_BAF2",
"condition_candidate": (
"FAA2 == 0 && F9C0 == 0 && ((FAA5.bit7 == 0) || (F9C3 == 0))"
),
"summary": "Main-loop report gate; session must be idle, TX busy timer clear, and RX gate open.",
"state_addresses_hex": [_h16(0xFAA2), _h16(0xFAA5), _h16(0xF9C3), _h16(0xF9C0)],
"evidence_addresses": _addresses_in_ranges(
ordered,
[(MAIN_REPORT_GATE_ENTRY, MAIN_REPORT_GATE_CALL)],
MAIN_REPORT_GATE_ENTRY,
MAIN_REPORT_GATE_CALL,
),
},
{
"name": "queue_has_pending_report",
"entry_label": "loc_BAF2",
"condition_candidate": "F9B5 != F9B0",
"summary": "Queue/pending cursor gate; non-empty state stages through BB43 before loc_BA26.",
"state_addresses_hex": [_h16(0xF9B5), _h16(0xF9B0)],
"staging_path": ["loc_BAF2", "loc_BB43", "loc_BA26"],
"evidence_addresses": _addresses_in_ranges(
ordered,
[(QUEUE_REPORT_ENTRY, AUTONOMOUS_TX_REPORT_CALL)],
QUEUE_REPORT_ENTRY,
AUTONOMOUS_TX_REPORT_CALL,
),
},
{
"name": "periodic_resend_may_fire",
"entry_label": "loc_BE9E",
"target_label": "loc_BED5",
"condition_candidate": (
"(FAA5 & FAA3 & 0x80) != 0 && F9C6 == 0 && F9C8 != 0 after countdown"
),
"summary": "Resend gate masks pending state with FAA5, checks F9C6/F9C8, then calls BA26 at BED5.",
"state_addresses_hex": [_h16(0xFAA5), _h16(0xFAA3), _h16(0xF9C6), _h16(0xF9C8)],
"evidence_addresses": _addresses_in_ranges(
ordered,
[(RESEND_GATE_ENTRY, PERIODIC_RESEND_ENTRY)],
RESEND_GATE_ENTRY,
PERIODIC_RESEND_ENTRY,
),
},
],
"session_effects": [
{
"name": "rx_completion_sets_session_timer",
"summary": "RX completion sets F9C5 (observed reload H'14) after the sixth byte is captured.",
"state_addresses_hex": [_h16(0xF9C5)],
"evidence_addresses": _state_immediate_evidence(ordered, 0xF9C5, 0x14),
},
{
"name": "session_timeout_clears_gate_and_queue",
"entry_label": "loc_3FEF",
"summary": "When F9C5 is clear, loc_3FEF clears F9B5/F9B0 and clears FAA5.bit7; when nonzero, it sets FAA5.bit7.",
"state_addresses_hex": [_h16(0xF9C5), _h16(0xF9B5), _h16(0xF9B0), _h16(0xFAA5)],
"evidence_addresses": _addresses_in_ranges(
ordered,
[(SESSION_GATE_ENTRY, 0x4007)],
SESSION_GATE_ENTRY,
0x4007,
),
},
{
"name": "host_ack_can_advance_queue",
"summary": "Commands 0x05/0x06 are modeled as acknowledgement paths that can clear pending state or advance F9B5.",
"command_values_hex": [_h16(value, width=2) for value in command_ack_values],
"state_addresses_hex": [_h16(0xF9B5)],
"evidence_addresses": _dedupe_ints(
addr
for command in commands
if command.get("command_value") in {0x05, 0x06}
for addr in command.get("evidence_addresses", [])
if isinstance(addr, int)
),
},
],
"caveat": (
"Many panel controls may require host/session traffic before reporting. Observed "
"autonomous call/camera-power indexes are runtime/capture overlays, not ROM constants."
),
"confidence": "candidate-medium",
"evidence_addresses": evidence,
"evidence_addresses_hex": _hlist(evidence),
}
def _tx_report_model(ordered: list[JsonObject], responses: list[JsonObject]) -> JsonObject | None:
report_responses = [
response for response in responses
if response.get("call_address") == AUTONOMOUS_TX_REPORT_CALL
]
if not report_responses:
report_responses = [
response for response in responses
if _response_reads_current_value_table(response)
and not _response_reads_rx_frame(response)
]
if not report_responses:
return None
response_ids = [
str(response["id"])
for response in report_responses
if isinstance(response.get("id"), str)
]
evidence = _dedupe_ints(
addr
for response in report_responses
for addr in response.get("evidence_addresses", [])
if isinstance(addr, int)
)
byte_roles = [
{
"offset": 0,
"field_candidate": "encoded_logical_index_or_report_id_byte0",
"source_candidate": "computed from candidate logical index/report id",
},
{
"offset": 1,
"field_candidate": "encoded_logical_index_or_report_id_byte1",
"source_candidate": "computed from candidate logical index/report id",
},
{
"offset": 2,
"field_candidate": "encoded_logical_index_or_report_id_byte2",
"source_candidate": "computed from candidate logical index/report id",
},
{
"offset": 3,
"field_candidate": "current_value_hi",
"source_candidate": "current_value_table_candidate high byte",
"table_candidate": "current_value_table_candidate",
},
{
"offset": 4,
"field_candidate": "current_value_lo",
"source_candidate": "current_value_table_candidate low byte",
"table_candidate": "current_value_table_candidate",
},
{
"offset": 5,
"field_candidate": "checksum",
"source_candidate": "0x5A XOR TX[0..4]",
},
]
return {
"kind": "bb43_to_ba26_tx_report_model_candidate",
"direction": "device_to_host_autonomous_report_candidate",
"entry_label": AUTONOMOUS_TX_REPORT_LABEL,
"entry_address": AUTONOMOUS_TX_REPORT_CALL,
"entry_address_hex": _h16(AUTONOMOUS_TX_REPORT_CALL),
"send_builder": SEND_BUILDER_LABEL,
"send_builder_address": SEND_BUILDER_ADDRESS,
"send_builder_address_hex": _h16(SEND_BUILDER_ADDRESS),
"response_candidates": _dedupe_strings(response_ids),
"summary": (
"TX report bytes 0..2 are computed encoded logical index/report id bytes, "
"bytes 3..4 come from current_value_table_candidate, and byte5 is the "
"0x5A XOR checksum."
),
"byte_roles": byte_roles,
"value_source_candidate": "current_value_table_candidate",
"checksum_formula": "checksum = 0x5A ^ byte0 ^ byte1 ^ byte2 ^ byte3 ^ byte4",
"observed_capture_overlay_candidates": OBSERVED_TX_REPORT_OVERLAY,
"observed_autonomous_output_caveat": (
"Real captures supplied so far show only heartbeat/idle, call, and camera-power "
"autonomous TX frames. Other panel controls may require a host/device request or "
"state transition before the firmware reports them."
),
"confidence": "candidate-medium",
"caveat": (
"This is a TX/report model for the BB43 -> BA26 path, separate from RX command "
"dispatch. Observed report names are a capture overlay candidate only, not hard-coded "
"source truth."
),
"evidence_addresses": evidence,
"evidence_addresses_hex": _hlist(evidence),
}
def _periodic_resend_model(ordered: list[JsonObject], responses: list[JsonObject]) -> JsonObject | None:
del responses
period_evidence = _state_immediate_evidence(ordered, 0xF9C6, 0x01F4)
countdown_evidence = _state_immediate_evidence(ordered, 0xF9C8, 0x14)
pending_evidence = _state_immediate_evidence(ordered, 0xFAA3, 0x80)
pending_evidence = _dedupe_ints(pending_evidence + _state_bit_evidence(ordered, 0xFAA3, 7))
resend_evidence = [
int(ins["address"])
for ins in ordered
if PERIODIC_RESEND_ENTRY <= int(ins.get("address", -1)) <= SERIAL_HANDLER_END
]
resend_send_evidence = [
int(ins["address"])
for ins in ordered
if PERIODIC_RESEND_ENTRY <= int(ins.get("address", -1)) <= SERIAL_HANDLER_END
and (_is_send_builder_call(ins) or _has_ref_in_range(ins, TX_STAGING_START, TX_FRAME_END))
]
evidence = _dedupe_ints(period_evidence + countdown_evidence + pending_evidence + resend_send_evidence)
if not evidence and not resend_evidence:
return None
return {
"kind": "autonomous_periodic_resend_model_candidate",
"period_timer": {
"address": 0xF9C6,
"address_hex": _h16(0xF9C6),
"reload_value_candidate": 0x01F4,
"reload_value_hex": _h16(0x01F4),
"summary": "Candidate periodic report/heartbeat timer reload.",
"evidence_addresses": period_evidence,
"evidence_addresses_hex": _hlist(period_evidence),
},
"resend_countdown": {
"address": 0xF9C8,
"address_hex": _h16(0xF9C8),
"reload_value_candidate": 0x14,
"reload_value_hex": _h16(0x14, width=2),
"summary": "Candidate periodic resend countdown/retry spacing value.",
"evidence_addresses": countdown_evidence,
"evidence_addresses_hex": _hlist(countdown_evidence),
},
"pending_mask": {
"address": 0xFAA3,
"address_hex": _h16(0xFAA3),
"mask_candidate": 0x80,
"mask_hex": _h16(0x80, width=2),
"summary": "Candidate bit/mask that marks an autonomous report pending.",
"evidence_addresses": pending_evidence,
"evidence_addresses_hex": _hlist(pending_evidence),
},
"resend_path": {
"entry_label": "loc_BED5",
"entry_address": PERIODIC_RESEND_ENTRY,
"entry_address_hex": _h16(PERIODIC_RESEND_ENTRY),
"summary": "Candidate periodic resend path feeding the TX staging/send-builder flow.",
"evidence_addresses": _dedupe_ints(resend_send_evidence or resend_evidence),
"evidence_addresses_hex": _hlist(resend_send_evidence or resend_evidence),
},
"evidence_addresses": evidence,
"evidence_addresses_hex": _hlist(evidence),
"confidence": "candidate-medium" if evidence else "candidate-low",
"caveat": (
"Timer and resend roles are inferred from constants/state references around F9C6, "
"F9C8, FAA3, and loc_BED5; exact scheduling units remain candidate phrasing."
),
}
def _response_reads_current_value_table(response: Mapping[str, Any]) -> bool:
schema = response.get("schema")
if not isinstance(schema, Mapping):
return False
return any(
isinstance(item, Mapping)
and isinstance(item.get("source"), Mapping)
and item["source"].get("kind") == "table"
and item["source"].get("name_candidate") == "current_value_table_candidate"
for item in schema.get("bytes", [])
)
def _response_reads_rx_frame(response: Mapping[str, Any]) -> bool:
schema = response.get("schema")
if not isinstance(schema, Mapping):
return False
return any(
isinstance(item, Mapping)
and isinstance(item.get("source"), Mapping)
and item["source"].get("kind") == "rx_frame_byte"
for item in schema.get("bytes", [])
)
def _state_immediate_evidence(ordered: list[JsonObject], state_address: int, value: int) -> list[int]:
evidence = []
for ins in ordered:
if not _has_ref_in_range(ins, state_address, state_address):
continue
source, _destination = _source_destination_operands(str(ins.get("operands", "")))
if _parse_immediate(source) == value:
evidence.append(int(ins["address"]))
return _dedupe_ints(evidence)
def _state_bit_evidence(ordered: list[JsonObject], state_address: int, bit: int) -> list[int]:
return _dedupe_ints(
int(ins["address"])
for ins in ordered
if _has_ref_in_range(ins, state_address, state_address)
and _bit_number_from_instruction(ins) == bit
)
def _send_builder_candidate(
ordered: list[JsonObject],
responses: list[JsonObject],
@@ -1687,6 +2046,28 @@ def _top_level_evidence(
"response_count": len(responses),
}
)
tx_report_responses = [
response for response in responses
if response.get("call_address") == AUTONOMOUS_TX_REPORT_CALL
]
if tx_report_responses:
addresses = _dedupe_ints(
addr
for response in tx_report_responses
for addr in response.get("evidence_addresses", [])
if isinstance(addr, int)
)
evidence.append(
{
"kind": "bb43_autonomous_tx_report_path",
"summary": (
"BB43 stages a candidate device-to-host report before loc_BA26; this is "
"separate from RX command dispatch."
),
"addresses": addresses,
"addresses_hex": _hlist(addresses),
}
)
rx_payload_reads = [
int(ins["address"])
for ins in ordered