diff --git a/fifa17-recon/tools/scenario_mode_start_trace.py b/fifa17-recon/tools/scenario_mode_start_trace.py index a1823a6..d110190 100755 --- a/fifa17-recon/tools/scenario_mode_start_trace.py +++ b/fifa17-recon/tools/scenario_mode_start_trace.py @@ -21,6 +21,9 @@ or drive input. scenario_mode_start_trace.py [pid] --profile lifecycle scenario_mode_start_trace.py [pid] --profile screen scenario_mode_start_trace.py [pid] --profile kickoff-control + scenario_mode_start_trace.py [pid] --profile pma-transition + scenario_mode_start_trace.py [pid] --profile pma-activation + scenario_mode_start_trace.py [pid] --profile pma-free-roam scenario_mode_start_trace.py --selftest """ from __future__ import annotations @@ -51,10 +54,16 @@ PMA_STATE_CHANGE_RVA = 0x07ADB870 PMA_COMMAND_27_CALLSITE_RVA = 0x07ADBCB2 PMA_EVENT_30_CALLSITE_RVA = 0x07ADBCF9 PMA_ADVANCE_RVA = 0x07A9FAA0 +PMA_COMMAND_71_CALLSITE_RVA = 0x07A92A1F +PMA_ACTIVATE_RVA = 0x07AC2FF0 +PMA_CHILD_EVENT_RVA = 0x07AC9470 +PMA_ENTER_DRILL_RVA = 0x07A97EA0 +PMA_ENTER_ACTIVE_RVA = 0x07AA1050 PMA_TRANSITION_DONE_CALLSITE_RVA = 0x07A9D9FD GAMEPLAY_COMMAND_DISPATCH_RVA = 0x07A8F6C0 GAMEPLAY_GLOBAL_RVA = 0x04BFB910 PMA_INSTRUCTIONS_HANDLER_RVA = 0x07AC91E0 +PMA_SCREEN_HANDLER_RVA = 0x07DCA400 PMA_UI_SET_STATE_RVA = 0x07B0EBB0 TESTING_GAME_UPDATE_RVA = 0x05A410C8 SCENARIO_MODE_START_CONSUMER_RVA = 0x05A58EC0 @@ -98,7 +107,13 @@ def trace_addresses(fifa_base: int) -> dict[str, int]: "pma_transition_done_callsite": fifa_base + PMA_TRANSITION_DONE_CALLSITE_RVA, "gameplay_command_dispatch": fifa_base + GAMEPLAY_COMMAND_DISPATCH_RVA, "gameplay_global": fifa_base + GAMEPLAY_GLOBAL_RVA, + "pma_command_71_callsite": fifa_base + PMA_COMMAND_71_CALLSITE_RVA, + "pma_activate": fifa_base + PMA_ACTIVATE_RVA, + "pma_child_event": fifa_base + PMA_CHILD_EVENT_RVA, "pma_instructions_handler": fifa_base + PMA_INSTRUCTIONS_HANDLER_RVA, + "pma_screen_handler": fifa_base + PMA_SCREEN_HANDLER_RVA, + "pma_enter_drill": fifa_base + PMA_ENTER_DRILL_RVA, + "pma_enter_active": fifa_base + PMA_ENTER_ACTIVE_RVA, "pma_ui_set_state": fifa_base + PMA_UI_SET_STATE_RVA, "testing_game_update": fifa_base + TESTING_GAME_UPDATE_RVA, "scenario_mode_start_consumer": fifa_base + SCENARIO_MODE_START_CONSUMER_RVA, @@ -433,6 +448,281 @@ continue ) +def build_pma_free_roam_script(pid: int, fifa_base: int, output: str) -> str: + address = trace_addresses(fifa_base) + return ( + gdb_prelude(pid, output) + + f"""hbreak *0x{address['pma_enter_drill']:x} +commands + silent + set $owner = $rcx + python import time; print("PMAFREEROAM epoch_ns=%d mono_ns=%d ENTER_DRILL" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d function=%p owner=%p owner_vtable=%p substate=%d previous_substate=%d field38=%d flag111=%d initialized112=%d ready124=%d params=%d,%d caller_return=%p\\n", $_thread, $pc, $owner, *(void**)$owner, *(int*)($owner+0x30), *(int*)($owner+0x34), *(int*)($owner+0x38), *(unsigned char*)($owner+0x111), *(unsigned char*)($owner+0x112), *(int*)($owner+0x124), $r8 ? *(int*)$r8 : -1, $r8 ? *(int*)($r8+4) : -1, *(void**)$rsp + bt 20 + continue +end + +hbreak *0x{address['pma_enter_active']:x} +commands + silent + set $owner = $rcx + python import time; print("PMAFREEROAM epoch_ns=%d mono_ns=%d ENTER_ACTIVE" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d function=%p owner=%p owner_vtable=%p substate=%d previous_substate=%d field38=%d flag111=%d initialized112=%d ready124=%d caller_return=%p\\n", $_thread, $pc, $owner, *(void**)$owner, *(int*)($owner+0x30), *(int*)($owner+0x34), *(int*)($owner+0x38), *(unsigned char*)($owner+0x111), *(unsigned char*)($owner+0x112), *(int*)($owner+0x124), *(void**)$rsp + bt 20 + continue +end + +hbreak *0x{address['pma_command_71_callsite']:x} +commands + silent + set $owner = $rbx + python import time; print("PMAFREEROAM epoch_ns=%d mono_ns=%d COMMAND_71_CASE" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d callsite=%p owner=%p owner_vtable=%p command=%#x payload=%p substate=%d previous_substate=%d field38=%d initialized112=%d ready124=%d caller_return=%p\\n", $_thread, $pc, $owner, *(void**)$owner, $esi, $rdi, *(int*)($owner+0x30), *(int*)($owner+0x34), *(int*)($owner+0x38), *(unsigned char*)($owner+0x112), *(int*)($owner+0x124), *(void**)$rsp + bt 20 + continue +end + +hbreak *0x{address['pma_activate']:x} +commands + silent + set $owner = $rcx + python import time; print("PMAFREEROAM epoch_ns=%d mono_ns=%d ACTIVATE_PMA" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d function=%p owner=%p owner_vtable=%p substate=%d previous_substate=%d field38=%d initialized112=%d ready124=%d manager=%p caller_return=%p\\n", $_thread, $pc, $owner, *(void**)$owner, *(int*)($owner+0x30), *(int*)($owner+0x34), *(int*)($owner+0x38), *(unsigned char*)($owner+0x112), *(int*)($owner+0x124), *(void**)($owner+0x168), *(void**)$rsp + bt 20 + continue +end + +printf "PMAFREEROAM ARMED profile=pma-free-roam pid={pid} enter_drill=0x{address['pma_enter_drill']:x} enter_active=0x{address['pma_enter_active']:x} command71=0x{address['pma_command_71_callsite']:x} activate=0x{address['pma_activate']:x}\\n" +continue +""" + ) + + +def build_pma_activation_script(pid: int, fifa_base: int, output: str) -> str: + address = trace_addresses(fifa_base) + return ( + gdb_prelude(pid, output) + + f"""hbreak *0x{address['pma_command_71_callsite']:x} +commands + silent + set $owner = $rbx + set $manager = *(void**)($owner+0x168) + set $child = 0 + if $manager != 0 + set $child = *(void**)($manager+0x8) + end + python import time; print("PMAACTIVATION epoch_ns=%d mono_ns=%d COMMAND_71_CASE" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d callsite=%p owner=%p owner_vtable=%p command=%#x payload=%p substate=%d previous_substate=%d ready124=%d manager=%p manager_mode=%d child=%p child_vtable=%p\\n", $_thread, $pc, $owner, *(void**)$owner, $esi, $rdi, *(int*)($owner+0x30), *(int*)($owner+0x34), *(int*)($owner+0x124), $manager, $manager ? *(int*)($manager+0x50) : -1, $child, $child ? *(void**)$child : 0 + bt 16 + continue +end + +hbreak *0x{address['pma_activate']:x} +commands + silent + set $owner = $rcx + set $manager = *(void**)($owner+0x168) + set $child = 0 + if $manager != 0 + set $child = *(void**)($manager+0x8) + end + python import time; print("PMAACTIVATION epoch_ns=%d mono_ns=%d ACTIVATE_PMA" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d function=%p owner=%p owner_vtable=%p substate=%d previous_substate=%d ready124=%d initialized112=%d manager=%p manager_mode=%d child=%p child_vtable=%p caller_return=%p\\n", $_thread, $pc, $owner, *(void**)$owner, *(int*)($owner+0x30), *(int*)($owner+0x34), *(int*)($owner+0x124), *(unsigned char*)($owner+0x112), $manager, $manager ? *(int*)($manager+0x50) : -1, $child, $child ? *(void**)$child : 0, *(void**)$rsp + bt 20 + continue +end + +hbreak *0x{address['pma_child_event']:x} +commands + silent + set $child = $rcx + set $callback = $child+0x80 + set $callback_vtable = *(void**)$callback + set $callback_target = 0 + if $callback_vtable != 0 + set $callback_target = *(void**)$callback_vtable + end + python import time; print("PMAACTIVATION epoch_ns=%d mono_ns=%d CHILD_EVENT_FORWARD" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d function=%p child=%p child_vtable=%p event=%d callback=%p callback_vtable=%p callback_target=%p caller_return=%p\\n", $_thread, $pc, $child, *(void**)$child, $edx, $callback, $callback_vtable, $callback_target, *(void**)$rsp + bt 20 + continue +end + +hbreak *0x{address['pma_instructions_handler']:x} +commands + silent + set $listener = $rcx + set $parent = *(void**)($listener+0x8) + set $machine = 0 + set $current = 0 + if $parent != 0 + set $machine = *(void**)($parent+0x8) + end + if $machine != 0 + set $current = *(void**)($machine+0x10) + end + python import time; print("PMAACTIVATION epoch_ns=%d mono_ns=%d INSTRUCTIONS_EVENT" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d handler=%p listener=%p listener_vtable=%p event=%d flag18=%d parent=%p machine=%p current=%p current_vtable=%p dispatcher=%p dispatcher_caller_return=%p caller_return=%p\\n", $_thread, $pc, $listener, *(void**)$listener, $edx, *(unsigned char*)($listener+0x18), $parent, $machine, $current, $current ? *(void**)$current : 0, $rdi-8, *(void**)($rsp+0x30), *(void**)$rsp + bt 20 + continue +end + +printf "PMAACTIVATION ARMED profile=pma-activation pid={pid} command71=0x{address['pma_command_71_callsite']:x} activate=0x{address['pma_activate']:x} child_event=0x{address['pma_child_event']:x} instructions=0x{address['pma_instructions_handler']:x}\\n" +continue +""" + ) + + +def build_pma_transition_script(pid: int, fifa_base: int, output: str) -> str: + address = trace_addresses(fifa_base) + return ( + gdb_prelude(pid, output) + + f"""set breakpoint always-inserted off + +hbreak *0x{address['pma_instructions_handler']:x} +commands + silent + set $listener = $rcx + set $parent = *(void**)($listener+0x8) + set $machine = 0 + set $states = 0 + set $current = 0 + set $current_index = -1 + if $parent != 0 + set $machine = *(void**)($parent+0x8) + end + if $machine != 0 + set $states = *(void**)($machine+0x8) + set $current = *(void**)($machine+0x10) + end + if $states != 0 + if $current == *(void**)($states+0x0) + set $current_index = 0 + end + if $current == *(void**)($states+0x8) + set $current_index = 1 + end + if $current == *(void**)($states+0x10) + set $current_index = 2 + end + if $current == *(void**)($states+0x18) + set $current_index = 3 + end + if $current == *(void**)($states+0x20) + set $current_index = 4 + end + if $current == *(void**)($states+0x28) + set $current_index = 5 + end + end + python import time; print("PMATRANSITION epoch_ns=%d mono_ns=%d INSTRUCTIONS_EVENT" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d handler=%p listener=%p listener_vtable=%p event=%d payload=%p extra=%p flag18=%d parent=%p machine=%p current_index=%d current=%p current_vtable=%p dispatcher=%p dispatcher_caller_return=%p caller_return=%p\\n", $_thread, $pc, $listener, *(void**)$listener, $edx, $r8, $r9, *(unsigned char*)($listener+0x18), $parent, $machine, $current_index, $current, $current ? *(void**)$current : 0, $rdi-8, *(void**)($rsp+0x30), *(void**)$rsp + continue +end + +hbreak *0x{address['pma_ui_set_state']:x} +commands + silent + set $parent = $rcx + set $machine = *(void**)($parent+0x8) + set $states = 0 + set $current = 0 + set $target = 0 + set $current_index = -1 + if $machine != 0 + set $states = *(void**)($machine+0x8) + set $current = *(void**)($machine+0x10) + end + if $states != 0 + set $target = *(void**)($states+$edx*8) + if $current == *(void**)($states+0x0) + set $current_index = 0 + end + if $current == *(void**)($states+0x8) + set $current_index = 1 + end + if $current == *(void**)($states+0x10) + set $current_index = 2 + end + if $current == *(void**)($states+0x18) + set $current_index = 3 + end + if $current == *(void**)($states+0x20) + set $current_index = 4 + end + if $current == *(void**)($states+0x28) + set $current_index = 5 + end + end + python import time; print("PMATRANSITION epoch_ns=%d mono_ns=%d UI_SET_STATE" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d handler=%p parent=%p machine=%p current_index=%d current=%p current_vtable=%p requested_state=%d target=%p target_vtable=%p caller_return=%p\\n", $_thread, $pc, $parent, $machine, $current_index, $current, $current ? *(void**)$current : 0, $edx, $target, $target ? *(void**)$target : 0, *(void**)$rsp + bt 20 + continue +end + +hbreak *0x{address['pma_state_change']:x} +commands + silent + set $state = $rcx + set $gameplay_global = *(void**)0x{address['gameplay_global']:x} + set $global_guard = -1 + set $listener_manager = 0 + set $listener_table = 0 + set $listener_index = -1 + set $listener0 = 0 + set $listener1 = 0 + set $listener2 = 0 + set $selected = 0 + if $gameplay_global != 0 + set $global_guard = *(unsigned char*)($gameplay_global+0x114) + set $listener_manager = *(void**)($gameplay_global+0x58) + end + if $listener_manager != 0 + set $listener_table = *(void**)$listener_manager + end + if $listener_table != 0 + set $listener0 = *(void**)($listener_table+0x0) + set $listener1 = *(void**)($listener_table+0x8) + set $listener2 = *(void**)($listener_table+0x10) + set $listener_index = *(int*)($listener_table+0x20) + if $listener_index >= 0 && $listener_index < 3 + set $selected = *(void**)($listener_table+$listener_index*8) + end + end + python import time; print("PMATRANSITION epoch_ns=%d mono_ns=%d PRESENTATION_STATE_CHANGE" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d handler=%p object=%p object_vtable=%p mode_state=%d old_state=%d new_state=%d reason=%d flag14=%d global_guard=%d listener_index=%d listeners=%p,%p,%p listener_vtables=%p,%p,%p selected=%p selected_vtable=%p caller_return=%p\\n", $_thread, $pc, $state, *(void**)$state, *(int*)($state+0x8), *(int*)($state+0xc), $edx, $r8d, *(unsigned char*)($state+0x14), $global_guard, $listener_index, $listener0, $listener1, $listener2, $listener0 ? *(void**)$listener0 : 0, $listener1 ? *(void**)$listener1 : 0, $listener2 ? *(void**)$listener2 : 0, $selected, $selected ? *(void**)$selected : 0, *(void**)$rsp + bt 20 + continue +end + +hbreak *0x{address['pma_event_30_callsite']:x} +commands + silent + python import time; print("PMATRANSITION epoch_ns=%d mono_ns=%d EVENT_30_PRODUCER" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d callsite=%p state_object=%p object_vtable=%p mode_state=%d transition_state=%d listener=%p listener_vtable=%p event=%#x payload=%p caller_return=%p\\n", $_thread, $pc, $r13, *(void**)$r13, *(int*)($r13+0x8), *(int*)($r13+0xc), $rcx, *(void**)$rcx, 0x30, $r8, *(void**)$rsp + disable 4 + enable 5 + bt 20 + continue +end + +hbreak *0x{address['pma_screen_handler']:x} +commands + silent + python import time; print("PMATRANSITION epoch_ns=%d mono_ns=%d SCREEN_EVENT" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d handler=%p object=%p object_vtable=%p event=%#x payload=%p allow_advance=%d field140=%p caller_return=%p\\n", $_thread, $pc, $rcx, *(void**)$rcx, $edx, $r8, *(unsigned int*)($rcx+0x138), *(void**)($rcx+0x140), *(void**)$rsp + bt 20 + continue +end +disable 5 + + +printf "PMATRANSITION ARMED profile=pma-transition pid={pid} instructions=0x{address['pma_instructions_handler']:x} ui_set_state=0x{address['pma_ui_set_state']:x} presentation=0x{address['pma_state_change']:x} event30=0x{address['pma_event_30_callsite']:x} screen=0x{address['pma_screen_handler']:x}\\n" +continue +""" + ) + + def build_kickoff_control_script(pid: int, fifa_base: int, output: str) -> str: address = trace_addresses(fifa_base) return ( @@ -636,6 +926,11 @@ def selftest() -> None: assert address["event_source_27f9_return"] == 0x147DBD178 assert address["event_source_30_return"] == 0x147DCA4EF assert address["event_source_280e_return"] == 0x147DD0669 + assert address["pma_command_71_callsite"] == 0x147A92A1F + assert address["pma_activate"] == 0x147AC2FF0 + assert address["pma_child_event"] == 0x147AC9470 + assert address["pma_enter_drill"] == 0x147A97EA0 + assert address["pma_enter_active"] == 0x147AA1050 candidate_script = build_candidate_script(61470, 0x140000000, "/tmp/candidates.log") scheduler_script = build_scheduler_script(61470, 0x140000000, "/tmp/scheduler.log") upstream_script = build_upstream_script(61470, 0x140000000, "/tmp/upstream.log") @@ -647,12 +942,22 @@ def selftest() -> None: kickoff_script = build_kickoff_control_script( 61470, 0x140000000, "/tmp/kickoff-control.log" ) + pma_transition_script = build_pma_transition_script( + 61470, 0x140000000, "/tmp/pma-transition.log" + ) + pma_activation_script = build_pma_activation_script( + 61470, 0x140000000, "/tmp/pma-activation.log" + ) + pma_free_roam_script = build_pma_free_roam_script( + 61470, 0x140000000, "/tmp/pma-free-roam.log" + ) assert candidate_script.count("hbreak *") == 4 assert all(f"CANDIDATE_{label}" in candidate_script for label in "ABCD") assert scheduler_script.count("hbreak *") == 4 assert "FREE_ROAM_EVENT_128" in scheduler_script assert "SCENARIO_SCHEDULER" in scheduler_script assert "SCENARIO_MANAGER_START" in scheduler_script + assert address["pma_screen_handler"] == 0x147DCA400 assert "MODE_ZERO_PUBLISHER" in scheduler_script assert upstream_script.count("hbreak *") == 4 assert "EVENT_128_SOURCE_27F9" in upstream_script @@ -673,6 +978,25 @@ def selftest() -> None: assert "PMA_EVENT_30" in screen_script assert "GAMEPLAY_COMMAND_128" in screen_script assert "MODE_ZERO_PUBLISHER" in screen_script + assert pma_transition_script.count("hbreak *") == 5 + assert "INSTRUCTIONS_EVENT" in pma_transition_script + assert "UI_SET_STATE" in pma_transition_script + assert "PRESENTATION_STATE_CHANGE" in pma_transition_script + assert "EVENT_30_PRODUCER" in pma_transition_script + assert "SCREEN_EVENT" in pma_transition_script + assert "set *(" not in pma_transition_script + assert pma_activation_script.count("hbreak *") == 4 + assert "COMMAND_71_CASE" in pma_activation_script + assert "ACTIVATE_PMA" in pma_activation_script + assert "CHILD_EVENT_FORWARD" in pma_activation_script + assert "INSTRUCTIONS_EVENT" in pma_activation_script + assert "set *(" not in pma_activation_script + assert pma_free_roam_script.count("hbreak *") == 4 + assert "ENTER_DRILL" in pma_free_roam_script + assert "ENTER_ACTIVE" in pma_free_roam_script + assert "COMMAND_71_CASE" in pma_free_roam_script + assert "ACTIVATE_PMA" in pma_free_roam_script + assert "set *(" not in pma_free_roam_script assert kickoff_script.count("hbreak *") == 9 assert "GAMEPLAY_COMMAND_DISPATCH" in kickoff_script assert "GAMEPLAY_UPDATE_POSITIVE" in kickoff_script @@ -710,6 +1034,9 @@ def main() -> int: "lifecycle", "screen", "kickoff-control", + "pma-transition", + "pma-activation", + "pma-free-roam", ), default="candidates", ) @@ -745,8 +1072,14 @@ def main() -> int: script = build_lifecycle_script(pid, fifa_base, output) elif args.profile == "screen": script = build_screen_script(pid, fifa_base, output) - else: + elif args.profile == "kickoff-control": script = build_kickoff_control_script(pid, fifa_base, output) + elif args.profile == "pma-transition": + script = build_pma_transition_script(pid, fifa_base, output) + elif args.profile == "pma-activation": + script = build_pma_activation_script(pid, fifa_base, output) + else: + script = build_pma_free_roam_script(pid, fifa_base, output) except (OSError, RuntimeError, ValueError) as error: print(error, file=sys.stderr) return 2