From 0300af3333eaec3f35bf5f8b9ada1969b59d78ce Mon Sep 17 00:00:00 2001 From: funman300 Date: Wed, 26 Aug 2026 17:34:43 +0000 Subject: [PATCH] Add FIFA17 Kick Off control trace profile --- .../tools/scenario_mode_start_trace.py | 492 +++++++++++++++++- 1 file changed, 482 insertions(+), 10 deletions(-) diff --git a/fifa17-recon/tools/scenario_mode_start_trace.py b/fifa17-recon/tools/scenario_mode_start_trace.py index aa22156..a1823a6 100755 --- a/fifa17-recon/tools/scenario_mode_start_trace.py +++ b/fifa17-recon/tools/scenario_mode_start_trace.py @@ -2,19 +2,25 @@ """Trace FIFA17 ScenarioModeStart sources, scheduling, and publication. The candidate profile samples the four statically recovered publisher clusters. -The scheduler profile follows the exact mode-0 chain: FreeRoam event ``0x128``, -its scheduler, the ScenarioManager start method, and the mode-0 publisher. The -upstream profile traces all three literal ``0x128`` sources and the FreeRoam -case they must reach. +The scheduler profile follows the exact mode-0 chain. The upstream profile +traces the three literal ``0x128`` sources. The instructions, lifecycle, and +screen profiles follow the natural PMA state transition through event ``0x30``. +The kickoff-control profile records a bounded command stream and dynamically +rotates breakpoints into the complete ScenarioModeStart chain after command +``0x128`` while retaining the consumer as an architecture-independent anchor. +Every profile respects the four-breakpoint hardware limit. -Both generated GDB programs use at most four simultaneously enabled hardware -execution breakpoints. They only read registers and client memory, log, and +The generated GDB programs only read registers and client memory, log, and continue. They never call client functions, write client memory, emit events, or drive input. scenario_mode_start_trace.py [pid] --profile candidates scenario_mode_start_trace.py [pid] --profile scheduler scenario_mode_start_trace.py [pid] --profile upstream + scenario_mode_start_trace.py [pid] --profile instructions + 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 --selftest """ from __future__ import annotations @@ -41,6 +47,34 @@ MODE_ZERO_PUBLISHER_RVA = 0x07B1C190 EVENT_SOURCE_27F9_RVA = 0x07DBD16B EVENT_SOURCE_30_RVA = 0x07DCA4E5 EVENT_SOURCE_280E_RVA = 0x07DD065F +PMA_STATE_CHANGE_RVA = 0x07ADB870 +PMA_COMMAND_27_CALLSITE_RVA = 0x07ADBCB2 +PMA_EVENT_30_CALLSITE_RVA = 0x07ADBCF9 +PMA_ADVANCE_RVA = 0x07A9FAA0 +PMA_TRANSITION_DONE_CALLSITE_RVA = 0x07A9D9FD +GAMEPLAY_COMMAND_DISPATCH_RVA = 0x07A8F6C0 +GAMEPLAY_GLOBAL_RVA = 0x04BFB910 +PMA_INSTRUCTIONS_HANDLER_RVA = 0x07AC91E0 +PMA_UI_SET_STATE_RVA = 0x07B0EBB0 +TESTING_GAME_UPDATE_RVA = 0x05A410C8 +SCENARIO_MODE_START_CONSUMER_RVA = 0x05A58EC0 +STATE_INITIALIZER_RVA = 0x05A54CF0 +STATE_INITIALIZER_DONE_RVA = 0x05A54DDB +GAME_MODE_FREE_ROAM_VTABLE_RVA = 0x03AEDF58 +TESTING_GAME_VTABLE_RVA = 0x035C58A8 +TESTING_GAME_STATE_VTABLE_RVA = 0x035C2EE0 + +OWNER_STATE_OFFSET = 0x1958 +STATE_GAME_DATABASE_OFFSET = 0x17450 +STATE_PHASE_OFFSET = 0x27BEC +STATE_SCENARIO_MODE_START_GATE_OFFSET = 0x359E8 +DATABASE_IS_SKILL_GAME_OFFSET = 0x7382 +DATABASE_TEAM_PAIR_OFFSET = 0x73C4 + +EVENT_SOURCE_27F9_RETURN_RVA = 0x07DBD178 +EVENT_SOURCE_30_RETURN_RVA = 0x07DCA4EF +EVENT_SOURCE_280E_RETURN_RVA = 0x07DD0669 +MAX_KICKOFF_DISPATCHES = 512 @@ -57,6 +91,25 @@ def trace_addresses(fifa_base: int) -> dict[str, int]: "event_source_27f9": fifa_base + EVENT_SOURCE_27F9_RVA, "event_source_30": fifa_base + EVENT_SOURCE_30_RVA, "event_source_280e": fifa_base + EVENT_SOURCE_280E_RVA, + "pma_state_change": fifa_base + PMA_STATE_CHANGE_RVA, + "pma_command_27_callsite": fifa_base + PMA_COMMAND_27_CALLSITE_RVA, + "pma_event_30_callsite": fifa_base + PMA_EVENT_30_CALLSITE_RVA, + "pma_advance": fifa_base + PMA_ADVANCE_RVA, + "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_instructions_handler": fifa_base + PMA_INSTRUCTIONS_HANDLER_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, + "state_initializer": fifa_base + STATE_INITIALIZER_RVA, + "state_initializer_done": fifa_base + STATE_INITIALIZER_DONE_RVA, + "game_mode_free_roam_vtable": fifa_base + GAME_MODE_FREE_ROAM_VTABLE_RVA, + "testing_game_vtable": fifa_base + TESTING_GAME_VTABLE_RVA, + "testing_game_state_vtable": fifa_base + TESTING_GAME_STATE_VTABLE_RVA, + "event_source_27f9_return": fifa_base + EVENT_SOURCE_27F9_RETURN_RVA, + "event_source_30_return": fifa_base + EVENT_SOURCE_30_RETURN_RVA, + "event_source_280e_return": fifa_base + EVENT_SOURCE_280E_RETURN_RVA, } @@ -138,7 +191,7 @@ commands silent set $manager = $rcx python import time; print("SCENARIOTRACE epoch_ns=%d mono_ns=%d SCENARIO_MANAGER_START" % (time.time_ns(), time.monotonic_ns()), end=" ") - printf "thread=%d manager=%p vtable=%p requested_countdown=%d scenario_mode=%d child=%p caller_return=%p\\n", $_thread, $manager, *(void**)$manager, $edx, *(unsigned int*)($manager+0x50), *(void**)($manager+0x8), *(void**)$rsp + printf "thread=%d manager=%p vtable=%p skip_countdown=%d scenario_mode=%d child=%p caller_return=%p\\n", $_thread, $manager, *(void**)$manager, $dl, *(unsigned int*)($manager+0x50), *(void**)($manager+0x8), *(void**)$rsp bt 16 continue end @@ -147,7 +200,7 @@ hbreak *0x{address['mode_zero_publisher']:x} commands silent python import time; print("SCENARIOTRACE epoch_ns=%d mono_ns=%d MODE_ZERO_PUBLISHER" % (time.time_ns(), time.monotonic_ns()), end=" ") - printf "thread=%d object=%p vtable=%p requested_countdown=%d r8=%p r9=%p caller_return=%p\\n", $_thread, $rcx, *(void**)$rcx, $edx, $r8, $r9, *(void**)$rsp + printf "thread=%d object=%p vtable=%p context=%p scenario_value=%d skip_countdown=%d caller_return=%p\\n", $_thread, $rcx, *(void**)$rcx, $rdx, $r8d, $r9b, *(void**)$rsp bt 20 continue end @@ -205,6 +258,355 @@ continue return "".join(sections) +def build_instructions_script(pid: int, fifa_base: int, output: str) -> str: + address = trace_addresses(fifa_base) + return ( + gdb_prelude(pid, output) + + f"""hbreak *0x{address['pma_instructions_handler']:x} +commands + silent + set $listener = $rcx + set $parent = *(void**)($listener+0x8) + set $owner = 0 + set $current = 0 + if $parent != 0 + set $owner = *(void**)($parent+0x8) + if $owner != 0 + set $current = *(void**)($owner+0x10) + end + end + python import time; print("SCENARIOTRACE epoch_ns=%d mono_ns=%d PMA_INSTRUCTIONS_EVENT" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d listener=%p event=%d flag18=%d parent=%p owner=%p current=%p current_vtable=%p caller_return=%p\\n", $_thread, $listener, $edx, *(unsigned char*)($listener+0x18), $parent, $owner, $current, $current ? *(void**)$current : 0, *(void**)$rsp + bt 20 + continue +end + +hbreak *0x{address['pma_ui_set_state']:x} +commands + silent + set $parent = $rcx + set $owner = *(void**)($parent+0x8) + set $current = 0 + if $owner != 0 + set $current = *(void**)($owner+0x10) + end + python import time; print("SCENARIOTRACE epoch_ns=%d mono_ns=%d PMA_UI_SET_STATE" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d parent=%p requested_state=%d owner=%p current=%p current_vtable=%p caller_return=%p\\n", $_thread, $parent, $edx, $owner, $current, $current ? *(void**)$current : 0, *(void**)$rsp + bt 20 + continue +end + +hbreak *0x{address['pma_state_change']:x} +commands + silent + set $state = $rcx + python import time; print("SCENARIOTRACE epoch_ns=%d mono_ns=%d PMA_STATE_CHANGE" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d object=%p mode_state=%d old_state=%d new_state=%d reason=%d flag14=%d caller_return=%p\\n", $_thread, $state, *(int*)($state+0x8), *(int*)($state+0xc), $edx, $r8d, *(unsigned char*)($state+0x14), *(void**)$rsp + bt 20 + continue +end + +hbreak *0x{address['pma_command_27_callsite']:x} +commands + silent + python import time; print("SCENARIOTRACE epoch_ns=%d mono_ns=%d PMA_COMMAND_27" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d state_object=%p mode_state=%d transition_state=%d new_state=%d caller_return=%p\\n", $_thread, $r13, *(int*)($r13+0x8), *(int*)($r13+0xc), $r15d, *(void**)$rsp + bt 20 + continue +end + +printf "SCENARIOTRACE ARMED profile=instructions pid={pid} instructions=0x{address['pma_instructions_handler']:x} ui_set_state=0x{address['pma_ui_set_state']:x} state_change=0x{address['pma_state_change']:x} command27=0x{address['pma_command_27_callsite']:x}\\n" +continue +""" + ) + + +def build_lifecycle_script(pid: int, fifa_base: int, output: str) -> str: + address = trace_addresses(fifa_base) + return ( + gdb_prelude(pid, output) + + f"""hbreak *0x{address['pma_state_change']:x} +commands + silent + set $state = $rcx + set $gameplay_global = *(void**)0x{address['gameplay_global']:x} + set $listener_manager = 0 + set $listener_table = 0 + set $listener_index = -1 + set $selected_listener = 0 + if $gameplay_global != 0 + set $listener_manager = *(void**)($gameplay_global+0x58) + if $listener_manager != 0 + set $listener_table = *(void**)$listener_manager + if $listener_table != 0 + set $listener_index = *(int*)($listener_table+0x20) + if $listener_index >= 0 + set $selected_listener = *(void**)($listener_table+$listener_index*8) + end + end + end + end + python import time; print("SCENARIOTRACE epoch_ns=%d mono_ns=%d PMA_STATE_CHANGE" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d object=%p mode_state=%d old_state=%d new_state=%d reason=%d flag14=%d selected_index=%d selected=%p selected_vtable=%p caller_return=%p\\n", $_thread, $state, *(int*)($state+0x8), *(int*)($state+0xc), $edx, $r8d, *(unsigned char*)($state+0x14), $listener_index, $selected_listener, $selected_listener ? *(void**)$selected_listener : 0, *(void**)$rsp + bt 20 + continue +end + +hbreak *0x{address['pma_command_27_callsite']:x} +commands + silent + python import time; print("SCENARIOTRACE epoch_ns=%d mono_ns=%d PMA_COMMAND_27" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d state_object=%p mode_state=%d transition_state=%d new_state=%d selected_state=%d caller_return=%p\\n", $_thread, $r13, *(int*)($r13+0x8), *(int*)($r13+0xc), $r15d, *(int*)($r13+0xc), *(void**)$rsp + bt 20 + continue +end + +hbreak *0x{address['pma_advance']:x} +commands + silent + python import time; print("SCENARIOTRACE epoch_ns=%d mono_ns=%d PMA_ADVANCE_FROM_PMA" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d free_roam=%p old_substate=%d guard124=%d manager=%p caller_return=%p\\n", $_thread, $rcx, *(int*)($rcx+0x30), *(int*)($rcx+0x124), *(void**)($rcx+0x168), *(void**)$rsp + bt 20 + continue +end + +hbreak *0x{address['pma_transition_done_callsite']:x} +commands + silent + python import time; print("SCENARIOTRACE epoch_ns=%d mono_ns=%d PMA_TRANSITION_DONE" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d free_roam=%p substate=%d event=%#x event_target=%p target_vtable=%p caller_return=%p\\n", $_thread, $r14, *(int*)($r14+0x30), $edx, $rbx, *(void**)$rbx, *(void**)$rsp + bt 20 + continue +end + +printf "SCENARIOTRACE ARMED profile=lifecycle pid={pid} state_change=0x{address['pma_state_change']:x} command27=0x{address['pma_command_27_callsite']:x} advance=0x{address['pma_advance']:x} transition_done=0x{address['pma_transition_done_callsite']:x}\\n" +continue +""" + ) + + +def build_screen_script(pid: int, fifa_base: int, output: str) -> str: + address = trace_addresses(fifa_base) + return ( + gdb_prelude(pid, output) + + f"""hbreak *0x{address['pma_state_change']:x} +commands + silent + set $state = $rcx + python import time; print("SCENARIOTRACE epoch_ns=%d mono_ns=%d PMA_STATE_CHANGE" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d object=%p mode_state=%d old_state=%d new_state=%d reason=%d flag14=%d caller_return=%p\\n", $_thread, $state, *(int*)($state+0x8), *(int*)($state+0xc), $edx, $r8d, *(unsigned char*)($state+0x14), *(void**)$rsp + bt 20 + continue +end + +hbreak *0x{address['pma_event_30_callsite']:x} +commands + silent + python import time; print("SCENARIOTRACE epoch_ns=%d mono_ns=%d PMA_EVENT_30" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d state_object=%p mode_state=%d transition_state=%d selected=%p selected_vtable=%p caller_return=%p\\n", $_thread, $r13, *(int*)($r13+0x8), *(int*)($r13+0xc), $rcx, *(void**)$rcx, *(void**)$rsp + bt 20 + continue +end + +hbreak *0x{address['gameplay_command_dispatch']:x} +condition 3 $edx == 0x128 +commands + silent + python import time; print("SCENARIOTRACE epoch_ns=%d mono_ns=%d GAMEPLAY_COMMAND_128" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d dispatcher=%p command=%#x listeners=%p caller_return=%p\\n", $_thread, $rcx, $edx, *(void**)$rcx, *(void**)$rsp + bt 20 + continue +end + +hbreak *0x{address['mode_zero_publisher']:x} +commands + silent + python import time; print("SCENARIOTRACE epoch_ns=%d mono_ns=%d MODE_ZERO_PUBLISHER" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d object=%p vtable=%p requested_countdown=%d caller_return=%p\\n", $_thread, $rcx, *(void**)$rcx, $edx, *(void**)$rsp + bt 20 + continue +end + +printf "SCENARIOTRACE ARMED profile=screen pid={pid} state_change=0x{address['pma_state_change']:x} event30=0x{address['pma_event_30_callsite']:x} command128=0x{address['gameplay_command_dispatch']:x} publisher=0x{address['mode_zero_publisher']:x}\\n" +continue +""" + ) + + +def build_kickoff_control_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 +set $dispatch_count = 0 +set $command_128_seen = 0 + +hbreak *0x{address['gameplay_command_dispatch']:x} +commands + silent + set $dispatch_count = $dispatch_count + 1 + set $caller = *(void**)$rsp + set $listeners = *(void**)$rcx + set $listener0 = 0 + set $listener1 = 0 + set $listener2 = 0 + set $vtable0 = 0 + set $vtable1 = 0 + set $vtable2 = 0 + if $listeners != 0 + set $listener0 = *(void**)$listeners + set $listener1 = *(void**)($listeners+8) + set $listener2 = *(void**)($listeners+16) + end + if $listener0 != 0 + set $vtable0 = *(void**)$listener0 + end + if $listener1 != 0 + set $vtable1 = *(void**)$listener1 + end + if $listener2 != 0 + set $vtable2 = *(void**)$listener2 + end + python import time; print("KICKOFFTRACE epoch_ns=%d mono_ns=%d GAMEPLAY_COMMAND_DISPATCH" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d ordinal=%d command=%#x dispatcher=%p payload=%p r9=%p caller_return=%p listeners=%p entries=%p,%p,%p vtables=%p,%p,%p free_roam_present=%d\\n", $_thread, $dispatch_count, $edx, $rcx, $r8, $r9, $caller, $listeners, $listener0, $listener1, $listener2, $vtable0, $vtable1, $vtable2, $vtable0==0x{address['game_mode_free_roam_vtable']:x} || $vtable1==0x{address['game_mode_free_roam_vtable']:x} || $vtable2==0x{address['game_mode_free_roam_vtable']:x} + if $caller == 0x{address['event_source_27f9_return']:x} + python import time; print("KICKOFFTRACE epoch_ns=%d mono_ns=%d SOURCE_EVENT_27F9" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d event=%#x object=%p vtable=%p payload=%p handler=0x{fifa_base + 0x07DBD070:x} caller_return=%p\\n", $_thread, $ebp, $rsi, *(void**)$rsi, $rdi, $caller + end + if $caller == 0x{address['event_source_30_return']:x} + python import time; print("KICKOFFTRACE epoch_ns=%d mono_ns=%d SOURCE_EVENT_0030" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d event=%#x object=%p vtable=%p payload=%p handler=0x{fifa_base + 0x07DCA400:x} caller_return=%p\\n", $_thread, $ebx, $rsi, *(void**)$rsi, $rdi, $caller + end + if $caller == 0x{address['event_source_280e_return']:x} + python import time; print("KICKOFFTRACE epoch_ns=%d mono_ns=%d SOURCE_EVENT_280E" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d event=0x280e object=%p vtable=%p payload=%p handler=0x{fifa_base + 0x07DD0280:x} caller_return=%p\\n", $_thread, $rbx, *(void**)$rbx, $r10, $caller + end + if $edx == 0x128 + set $command_128_seen = 1 + disable 4 + enable 7 + bt 20 + end + if $dispatch_count >= {MAX_KICKOFF_DISPATCHES} + printf "KICKOFFTRACE DISPATCH_LIMIT_REACHED count=%d\\n", $dispatch_count + disable 1 + end + continue +end + +hbreak *0x{address['mode_zero_publisher']:x} +commands + silent + python import time; print("KICKOFFTRACE epoch_ns=%d mono_ns=%d MODE_ZERO_PUBLISHER" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d object=%p vtable=%p context=%p scenario_value=%d skip_countdown=%d caller_return=%p\\n", $_thread, $rcx, *(void**)$rcx, $rdx, $r8d, $r9b, *(void**)$rsp + bt 20 + disable 2 + continue +end + +hbreak *0x{address['scenario_mode_start_consumer']:x} +commands + silent + set $wrapper = $rcx + set $state = *(void**)($wrapper+0x30) + set $database = 0 + if $state != 0 + set $database = *(void**)($state+0x{STATE_GAME_DATABASE_OFFSET:x}) + end + python import time; print("KICKOFFTRACE epoch_ns=%d mono_ns=%d SCENARIO_MODE_START_CONSUMER" % (time.time_ns(), time.monotonic_ns()), end=" ") + if $database != 0 + printf "thread=%d wrapper=%p wrapper_vtable=%p state=%p state_vtable=%p payload_provider=%p phase_before=%d alternate_gate_before=%d database=%p is_skill_game=%d teams=%d,%d caller_return=%p\\n", $_thread, $wrapper, *(void**)$wrapper, $state, *(void**)$state, $r9, *(unsigned int*)($state+0x{STATE_PHASE_OFFSET:x}), *(unsigned char*)($state+0x{STATE_SCENARIO_MODE_START_GATE_OFFSET:x}), $database, *(unsigned char*)($database+0x{DATABASE_IS_SKILL_GAME_OFFSET:x}), *(unsigned int*)($database+0x{DATABASE_TEAM_PAIR_OFFSET:x}), *(unsigned int*)($database+0x{DATABASE_TEAM_PAIR_OFFSET + 4:x}), *(void**)$rsp + else + printf "thread=%d wrapper=%p state=%p payload_provider=%p database=0 caller_return=%p\\n", $_thread, $wrapper, $state, $r9, *(void**)$rsp + end + bt 20 + disable 1 + disable 2 + disable 7 + enable 5 + continue +end + +hbreak *0x{address['testing_game_update']:x} +commands + silent + set $owner = $rsi + set $state = *(void**)($owner+0x{OWNER_STATE_OFFSET:x}) + python import time; print("KICKOFFTRACE epoch_ns=%d mono_ns=%d GAMEPLAY_UPDATE_POSITIVE" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d owner=%p owner_vtable=%p state=%p state_vtable=%p expected_owner=%d expected_state=%d\\n", $_thread, $owner, *(void**)$owner, $state, *(void**)$state, *(void**)$owner==0x{address['testing_game_vtable']:x}, *(void**)$state==0x{address['testing_game_state_vtable']:x} + disable 4 + continue +end + +hbreak *0x{address['state_initializer']:x} +commands + silent + set $state = $rcx + set $database = *(void**)($state+0x{STATE_GAME_DATABASE_OFFSET:x}) + python import time; print("KICKOFFTRACE epoch_ns=%d mono_ns=%d STATE_INITIALIZER" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d state=%p state_vtable=%p payload_provider=%p phase_before=%d alternate_gate_before=%d database=%p is_skill_game=%d teams=%d,%d caller_return=%p\\n", $_thread, $state, *(void**)$state, $rdx, *(unsigned int*)($state+0x{STATE_PHASE_OFFSET:x}), *(unsigned char*)($state+0x{STATE_SCENARIO_MODE_START_GATE_OFFSET:x}), $database, *(unsigned char*)($database+0x{DATABASE_IS_SKILL_GAME_OFFSET:x}), *(unsigned int*)($database+0x{DATABASE_TEAM_PAIR_OFFSET:x}), *(unsigned int*)($database+0x{DATABASE_TEAM_PAIR_OFFSET + 4:x}), *(void**)$rsp + disable 3 + disable 5 + enable 6 + continue +end +disable 5 + +hbreak *0x{address['state_initializer_done']:x} +commands + silent + set $state = $rbx + set $database = *(void**)($state+0x{STATE_GAME_DATABASE_OFFSET:x}) + python import time; print("KICKOFFTRACE epoch_ns=%d mono_ns=%d STATE_INITIALIZER_DONE" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d state=%p payload_provider=%p phase_after=%d alternate_gate_after=%d database=%p is_skill_game=%d teams=%d,%d\\n", $_thread, $state, $rdi, *(unsigned int*)($state+0x{STATE_PHASE_OFFSET:x}), *(unsigned char*)($state+0x{STATE_SCENARIO_MODE_START_GATE_OFFSET:x}), $database, *(unsigned char*)($database+0x{DATABASE_IS_SKILL_GAME_OFFSET:x}), *(unsigned int*)($database+0x{DATABASE_TEAM_PAIR_OFFSET:x}), *(unsigned int*)($database+0x{DATABASE_TEAM_PAIR_OFFSET + 4:x}) + disable 6 + continue +end +disable 6 + +hbreak *0x{address['free_roam_event_128_callsite']:x} +commands + silent + set $owner = $rbx + python import time; print("KICKOFFTRACE epoch_ns=%d mono_ns=%d FREE_ROAM_EVENT_128" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d owner=%p owner_vtable=%p command=%#x payload=%p ready=%d manager=%p caller_return=%p\\n", $_thread, $owner, *(void**)$owner, $esi, $rdi, *(unsigned char*)($owner+0x124), *(void**)($owner+0x168), *(void**)$rsp + disable 1 + disable 7 + enable 8 + continue +end +disable 7 + +hbreak *0x{address['scenario_scheduler']:x} +commands + silent + set $owner = $rcx + python import time; print("KICKOFFTRACE epoch_ns=%d mono_ns=%d SCENARIO_SCHEDULER" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d owner=%p owner_vtable=%p command=%#x payload=%p ready=%d manager=%p caller_return=%p\\n", $_thread, $owner, *(void**)$owner, $edx, $r8, *(unsigned char*)($owner+0x124), *(void**)($owner+0x168), *(void**)$rsp + disable 8 + enable 9 + continue +end +disable 8 + +hbreak *0x{address['scenario_manager_start']:x} +commands + silent + set $manager = $rcx + set $child = *(void**)($manager+0x8) + python import time; print("KICKOFFTRACE epoch_ns=%d mono_ns=%d SCENARIO_MANAGER_START" % (time.time_ns(), time.monotonic_ns()), end=" ") + printf "thread=%d manager=%p manager_vtable=%p skip_countdown=%d mode=%d child=%p child_vtable=%p caller_return=%p\\n", $_thread, $manager, *(void**)$manager, $dl, *(unsigned int*)($manager+0x50), $child, *(void**)$child, *(void**)$rsp + disable 9 + continue +end +disable 9 + +printf "KICKOFFTRACE ARMED profile=kickoff-control pid={pid} dispatch=0x{address['gameplay_command_dispatch']:x} publisher=0x{address['mode_zero_publisher']:x} consumer=0x{address['scenario_mode_start_consumer']:x} update=0x{address['testing_game_update']:x} initializer=0x{address['state_initializer']:x} free_roam=0x{address['free_roam_event_128_callsite']:x} scheduler=0x{address['scenario_scheduler']:x} manager=0x{address['scenario_manager_start']:x} max_dispatches={MAX_KICKOFF_DISPATCHES}\\n" +continue +""" + ) + + def selftest() -> None: address = trace_addresses(0x140000000) assert address["candidate_a"] == 0x147AFDCC0 @@ -218,9 +620,33 @@ def selftest() -> None: assert address["event_source_27f9"] == 0x147DBD16B assert address["event_source_30"] == 0x147DCA4E5 assert address["event_source_280e"] == 0x147DD065F + assert address["pma_state_change"] == 0x147ADB870 + assert address["pma_command_27_callsite"] == 0x147ADBCB2 + assert address["pma_event_30_callsite"] == 0x147ADBCF9 + assert address["pma_advance"] == 0x147A9FAA0 + assert address["pma_transition_done_callsite"] == 0x147A9D9FD + assert address["gameplay_command_dispatch"] == 0x147A8F6C0 + assert address["pma_instructions_handler"] == 0x147AC91E0 + assert address["pma_ui_set_state"] == 0x147B0EBB0 + assert address["testing_game_update"] == 0x145A410C8 + assert address["scenario_mode_start_consumer"] == 0x145A58EC0 + assert address["state_initializer"] == 0x145A54CF0 + assert address["state_initializer_done"] == 0x145A54DDB + assert address["game_mode_free_roam_vtable"] == 0x143AEDF58 + assert address["event_source_27f9_return"] == 0x147DBD178 + assert address["event_source_30_return"] == 0x147DCA4EF + assert address["event_source_280e_return"] == 0x147DD0669 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") + lifecycle_script = build_lifecycle_script(61470, 0x140000000, "/tmp/lifecycle.log") + screen_script = build_screen_script(61470, 0x140000000, "/tmp/screen.log") + instructions_script = build_instructions_script( + 61470, 0x140000000, "/tmp/instructions.log" + ) + kickoff_script = build_kickoff_control_script( + 61470, 0x140000000, "/tmp/kickoff-control.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 @@ -233,11 +659,41 @@ def selftest() -> None: assert "EVENT_128_SOURCE_30" in upstream_script assert "EVENT_128_SOURCE_280E" in upstream_script assert "FREE_ROAM_EVENT_128" in upstream_script + assert instructions_script.count("hbreak *") == 4 + assert "PMA_INSTRUCTIONS_EVENT" in instructions_script + assert "PMA_UI_SET_STATE" in instructions_script + assert "PMA_STATE_CHANGE" in instructions_script + assert "PMA_COMMAND_27" in instructions_script + assert lifecycle_script.count("hbreak *") == 4 + assert "PMA_STATE_CHANGE" in lifecycle_script + assert "PMA_COMMAND_27" in lifecycle_script + assert "PMA_ADVANCE_FROM_PMA" in lifecycle_script + assert "PMA_TRANSITION_DONE" in lifecycle_script + assert screen_script.count("hbreak *") == 4 + assert "PMA_EVENT_30" in screen_script + assert "GAMEPLAY_COMMAND_128" in screen_script + assert "MODE_ZERO_PUBLISHER" in screen_script + assert kickoff_script.count("hbreak *") == 9 + assert "GAMEPLAY_COMMAND_DISPATCH" in kickoff_script + assert "GAMEPLAY_UPDATE_POSITIVE" in kickoff_script + assert "SOURCE_EVENT_27F9" in kickoff_script + assert "SOURCE_EVENT_0030" in kickoff_script + assert "SOURCE_EVENT_280E" in kickoff_script + assert "FREE_ROAM_EVENT_128" in kickoff_script + assert "SCENARIO_SCHEDULER" in kickoff_script + assert "SCENARIO_MANAGER_START" in kickoff_script + assert "MODE_ZERO_PUBLISHER" in kickoff_script + assert "SCENARIO_MODE_START_CONSUMER" in kickoff_script + assert "STATE_INITIALIZER_DONE" in kickoff_script + assert "set *(" not in kickoff_script assert "0x147ac9900" not in scheduler_script.lower() assert "CANDIDATE_B_GATE" not in scheduler_script assert "set *(" not in candidate_script assert "set *(" not in scheduler_script assert "set *(" not in upstream_script + assert "set *(" not in lifecycle_script + assert "set *(" not in screen_script + assert "set *(" not in instructions_script print("scenario_mode_start_trace selftest: PASS") @@ -246,7 +702,15 @@ def main() -> int: parser.add_argument("pid", nargs="?", type=int) parser.add_argument( "--profile", - choices=("candidates", "scheduler", "upstream"), + choices=( + "candidates", + "scheduler", + "upstream", + "instructions", + "lifecycle", + "screen", + "kickoff-control", + ), default="candidates", ) parser.add_argument("--output") @@ -273,8 +737,16 @@ def main() -> int: script = build_candidate_script(pid, fifa_base, output) elif args.profile == "scheduler": script = build_scheduler_script(pid, fifa_base, output) - else: + elif args.profile == "upstream": script = build_upstream_script(pid, fifa_base, output) + elif args.profile == "instructions": + script = build_instructions_script(pid, fifa_base, output) + elif args.profile == "lifecycle": + script = build_lifecycle_script(pid, fifa_base, output) + elif args.profile == "screen": + script = build_screen_script(pid, fifa_base, output) + else: + script = build_kickoff_control_script(pid, fifa_base, output) except (OSError, RuntimeError, ValueError) as error: print(error, file=sys.stderr) return 2