#!/usr/bin/env python3 """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. 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. 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 [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 import argparse import os from pathlib import Path import shutil import sys sys.path.insert(0, str(Path(__file__).resolve().parent)) import match_advance_trace as advance import match_transition_trace as transition CANDIDATE_A_RVA = 0x07AFDCC0 CANDIDATE_B_RVA = 0x07B1BA90 CANDIDATE_C_RVA = 0x07B1C190 CANDIDATE_D_RVA = 0x07E0B200 FREE_ROAM_EVENT_128_CALLSITE_RVA = 0x07A92B0F SCENARIO_SCHEDULER_RVA = 0x07AC3A40 SCENARIO_MANAGER_START_RVA = 0x07B1C2B0 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_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 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 def trace_addresses(fifa_base: int) -> dict[str, int]: return { "candidate_a": fifa_base + CANDIDATE_A_RVA, "candidate_b": fifa_base + CANDIDATE_B_RVA, "candidate_c": fifa_base + CANDIDATE_C_RVA, "candidate_d": fifa_base + CANDIDATE_D_RVA, "free_roam_event_128_callsite": fifa_base + FREE_ROAM_EVENT_128_CALLSITE_RVA, "scenario_scheduler": fifa_base + SCENARIO_SCHEDULER_RVA, "scenario_manager_start": fifa_base + SCENARIO_MANAGER_START_RVA, "mode_zero_publisher": fifa_base + MODE_ZERO_PUBLISHER_RVA, "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_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, "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, } def gdb_prelude(pid: int, output: str) -> str: if any(character in output for character in "\n\r"): raise ValueError("output path cannot contain a newline") return f"""set pagination off set confirm off set print thread-events off set breakpoint always-inserted on set logging file {output} set logging overwrite on set logging redirect off set logging enabled on handle SIGSEGV nostop noprint pass handle SIGILL nostop noprint pass handle SIGFPE nostop noprint pass handle SIGPIPE nostop noprint pass handle SIGALRM nostop noprint pass handle SIGUSR1 nostop noprint pass handle SIGUSR2 nostop noprint pass attach {pid} """ def build_candidate_script(pid: int, fifa_base: int, output: str) -> str: address = trace_addresses(fifa_base) sections = [gdb_prelude(pid, output)] for label in ("a", "b", "c", "d"): sections.append( f"""hbreak *0x{address[f'candidate_{label}']:x} commands silent python import time; print("SCENARIOTRACE epoch_ns=%d mono_ns=%d CANDIDATE_{label.upper()}" % (time.time_ns(), time.monotonic_ns()), end=" ") printf "thread=%d rip=%p caller_return=%p object=%p vtable=%p edx=%#x r8=%p r9=%p field_40=%#x field_50=%#x field_b8=%#x\\n", $_thread, $pc, *(void**)$rsp, $rcx, *(void**)$rcx, $edx, $r8, $r9, *(unsigned char*)($rcx+0x40), *(unsigned int*)($rcx+0x50), *(unsigned int*)($rcx+0xb8) bt 16 continue end """ ) sections.append( "printf \"SCENARIOTRACE ARMED profile=candidates " f"pid={pid} candidate_a=0x{address['candidate_a']:x} " f"candidate_b=0x{address['candidate_b']:x} " f"candidate_c=0x{address['candidate_c']:x} " f"candidate_d=0x{address['candidate_d']:x}\\n\"\ncontinue\n" ) return "".join(sections) def build_scheduler_script(pid: int, fifa_base: int, output: str) -> str: address = trace_addresses(fifa_base) return ( gdb_prelude(pid, output) + f"""hbreak *0x{address['free_roam_event_128_callsite']:x} commands silent set $owner = $rbx python import time; print("SCENARIOTRACE epoch_ns=%d mono_ns=%d FREE_ROAM_EVENT_128" % (time.time_ns(), time.monotonic_ns()), end=" ") printf "thread=%d owner=%p event=%#x payload=%p mode=%d manager=%p caller_return=%p\\n", $_thread, $owner, $esi, $rdi, *(unsigned int*)($owner+0x124), *(void**)($owner+0x168), *(void**)$rsp bt 16 continue end hbreak *0x{address['scenario_scheduler']:x} commands silent set $owner = $rcx python import time; print("SCENARIOTRACE epoch_ns=%d mono_ns=%d SCENARIO_SCHEDULER" % (time.time_ns(), time.monotonic_ns()), end=" ") printf "thread=%d owner=%p event=%#x payload=%p mode=%d manager=%p caller_return=%p\\n", $_thread, $owner, $edx, $r8, *(unsigned int*)($owner+0x124), *(void**)($owner+0x168), *(void**)$rsp bt 16 continue end hbreak *0x{address['scenario_manager_start']:x} 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 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 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 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 printf "SCENARIOTRACE ARMED profile=scheduler pid={pid} event_callsite=0x{address['free_roam_event_128_callsite']:x} scheduler=0x{address['scenario_scheduler']:x} manager_start=0x{address['scenario_manager_start']:x} mode_zero_publisher=0x{address['mode_zero_publisher']:x}\\n" continue """ ) def build_upstream_script(pid: int, fifa_base: int, output: str) -> str: address = trace_addresses(fifa_base) sections = [gdb_prelude(pid, output)] for label, trigger in (("27f9", 0x27F9), ("30", 0x30), ("280e", 0x280E)): sections.append( f"""hbreak *0x{address[f'event_source_{label}']:x} commands silent set $gameplay = $rax set $listeners = 0 set $listener0 = 0 set $listener1 = 0 set $listener2 = 0 if $gameplay != 0 set $listeners = *(void**)$gameplay if $listeners != 0 set $listener0 = *(void**)$listeners set $listener1 = *(void**)($listeners+8) set $listener2 = *(void**)($listeners+16) end end python import time; print("SCENARIOTRACE epoch_ns=%d mono_ns=%d EVENT_128_SOURCE_{label.upper()}" % (time.time_ns(), time.monotonic_ns()), end=" ") printf "thread=%d trigger=%#x gameplay=%p listeners=%p entries=%p,%p,%p caller_return=%p\\n", $_thread, {trigger}, $gameplay, $listeners, $listener0, $listener1, $listener2, *(void**)$rsp bt 20 continue end """ ) sections.append( f"""hbreak *0x{address['free_roam_event_128_callsite']:x} commands silent set $owner = $rbx python import time; print("SCENARIOTRACE epoch_ns=%d mono_ns=%d FREE_ROAM_EVENT_128" % (time.time_ns(), time.monotonic_ns()), end=" ") printf "thread=%d owner=%p event=%#x payload=%p mode=%d manager=%p caller_return=%p\\n", $_thread, $owner, $esi, $rdi, *(unsigned int*)($owner+0x124), *(void**)($owner+0x168), *(void**)$rsp bt 20 continue end printf "SCENARIOTRACE ARMED profile=upstream pid={pid} source_27f9=0x{address['event_source_27f9']:x} source_30=0x{address['event_source_30']:x} source_280e=0x{address['event_source_280e']:x} event_callsite=0x{address['free_roam_event_128_callsite']:x}\\n" 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_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 ( 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 assert address["candidate_b"] == 0x147B1BA90 assert address["candidate_c"] == 0x147B1C190 assert address["candidate_d"] == 0x147E0B200 assert address["free_roam_event_128_callsite"] == 0x147A92B0F assert address["scenario_scheduler"] == 0x147AC3A40 assert address["scenario_manager_start"] == 0x147B1C2B0 assert address["mode_zero_publisher"] == 0x147B1C190 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 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") 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" ) 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 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 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 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") def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("pid", nargs="?", type=int) parser.add_argument( "--profile", choices=( "candidates", "scheduler", "upstream", "instructions", "lifecycle", "screen", "kickoff-control", "pma-transition", "pma-activation", "pma-free-roam", ), default="candidates", ) parser.add_argument("--output") parser.add_argument("--print-script", action="store_true") parser.add_argument("--selftest", action="store_true") args = parser.parse_args() if args.selftest: selftest() return 0 pid = args.pid or transition.find_pid() if not pid: print("FIFA17.exe not found", file=sys.stderr) return 2 try: fifa_base, fifa_path = advance.module_mapping(pid, advance.FIFA_MODULE) advance.validate_file( fifa_path, advance.PINNED_FIFA_SHA256, advance.FIFA_MODULE, ) output = args.output or f"/tmp/fifa17-scenario-{args.profile}-{pid}.log" if args.profile == "candidates": script = build_candidate_script(pid, fifa_base, output) elif args.profile == "scheduler": script = build_scheduler_script(pid, fifa_base, output) 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) 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 if args.print_script: print(script, end="") return 0 if not shutil.which("gdb"): print("gdb not found", file=sys.stderr) return 2 script_path = f"/tmp/fifa17-scenario-{args.profile}-{pid}.gdb" with open(script_path, "w", encoding="utf-8") as handle: handle.write(script) os.execvp("gdb", ["gdb", "-q", "-nx", "-batch", "-x", script_path]) return 127 if __name__ == "__main__": raise SystemExit(main())