Add FIFA17 Kick Off control trace profile

This commit is contained in:
funman300
2026-08-26 17:34:43 +00:00
parent 2c572e918f
commit 0300af3333
+482 -10
View File
@@ -2,19 +2,25 @@
"""Trace FIFA17 ScenarioModeStart sources, scheduling, and publication. """Trace FIFA17 ScenarioModeStart sources, scheduling, and publication.
The candidate profile samples the four statically recovered publisher clusters. The candidate profile samples the four statically recovered publisher clusters.
The scheduler profile follows the exact mode-0 chain: FreeRoam event ``0x128``, The scheduler profile follows the exact mode-0 chain. The upstream profile
its scheduler, the ScenarioManager start method, and the mode-0 publisher. The traces the three literal ``0x128`` sources. The instructions, lifecycle, and
upstream profile traces all three literal ``0x128`` sources and the FreeRoam screen profiles follow the natural PMA state transition through event ``0x30``.
case they must reach. 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 The generated GDB programs only read registers and client memory, log, and
execution breakpoints. They only read registers and client memory, log, and
continue. They never call client functions, write client memory, emit events, continue. They never call client functions, write client memory, emit events,
or drive input. or drive input.
scenario_mode_start_trace.py [pid] --profile candidates scenario_mode_start_trace.py [pid] --profile candidates
scenario_mode_start_trace.py [pid] --profile scheduler scenario_mode_start_trace.py [pid] --profile scheduler
scenario_mode_start_trace.py [pid] --profile upstream 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 scenario_mode_start_trace.py --selftest
""" """
from __future__ import annotations from __future__ import annotations
@@ -41,6 +47,34 @@ MODE_ZERO_PUBLISHER_RVA = 0x07B1C190
EVENT_SOURCE_27F9_RVA = 0x07DBD16B EVENT_SOURCE_27F9_RVA = 0x07DBD16B
EVENT_SOURCE_30_RVA = 0x07DCA4E5 EVENT_SOURCE_30_RVA = 0x07DCA4E5
EVENT_SOURCE_280E_RVA = 0x07DD065F 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_27f9": fifa_base + EVENT_SOURCE_27F9_RVA,
"event_source_30": fifa_base + EVENT_SOURCE_30_RVA, "event_source_30": fifa_base + EVENT_SOURCE_30_RVA,
"event_source_280e": fifa_base + EVENT_SOURCE_280E_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 silent
set $manager = $rcx set $manager = $rcx
python import time; print("SCENARIOTRACE epoch_ns=%d mono_ns=%d SCENARIO_MANAGER_START" % (time.time_ns(), time.monotonic_ns()), end=" ") 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 bt 16
continue continue
end end
@@ -147,7 +200,7 @@ hbreak *0x{address['mode_zero_publisher']:x}
commands commands
silent silent
python import time; print("SCENARIOTRACE epoch_ns=%d mono_ns=%d MODE_ZERO_PUBLISHER" % (time.time_ns(), time.monotonic_ns()), end=" ") 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 bt 20
continue continue
end end
@@ -205,6 +258,355 @@ continue
return "".join(sections) 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: def selftest() -> None:
address = trace_addresses(0x140000000) address = trace_addresses(0x140000000)
assert address["candidate_a"] == 0x147AFDCC0 assert address["candidate_a"] == 0x147AFDCC0
@@ -218,9 +620,33 @@ def selftest() -> None:
assert address["event_source_27f9"] == 0x147DBD16B assert address["event_source_27f9"] == 0x147DBD16B
assert address["event_source_30"] == 0x147DCA4E5 assert address["event_source_30"] == 0x147DCA4E5
assert address["event_source_280e"] == 0x147DD065F 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") candidate_script = build_candidate_script(61470, 0x140000000, "/tmp/candidates.log")
scheduler_script = build_scheduler_script(61470, 0x140000000, "/tmp/scheduler.log") scheduler_script = build_scheduler_script(61470, 0x140000000, "/tmp/scheduler.log")
upstream_script = build_upstream_script(61470, 0x140000000, "/tmp/upstream.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 candidate_script.count("hbreak *") == 4
assert all(f"CANDIDATE_{label}" in candidate_script for label in "ABCD") assert all(f"CANDIDATE_{label}" in candidate_script for label in "ABCD")
assert scheduler_script.count("hbreak *") == 4 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_30" in upstream_script
assert "EVENT_128_SOURCE_280E" in upstream_script assert "EVENT_128_SOURCE_280E" in upstream_script
assert "FREE_ROAM_EVENT_128" 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 "0x147ac9900" not in scheduler_script.lower()
assert "CANDIDATE_B_GATE" not in scheduler_script assert "CANDIDATE_B_GATE" not in scheduler_script
assert "set *(" not in candidate_script assert "set *(" not in candidate_script
assert "set *(" not in scheduler_script assert "set *(" not in scheduler_script
assert "set *(" not in upstream_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") print("scenario_mode_start_trace selftest: PASS")
@@ -246,7 +702,15 @@ def main() -> int:
parser.add_argument("pid", nargs="?", type=int) parser.add_argument("pid", nargs="?", type=int)
parser.add_argument( parser.add_argument(
"--profile", "--profile",
choices=("candidates", "scheduler", "upstream"), choices=(
"candidates",
"scheduler",
"upstream",
"instructions",
"lifecycle",
"screen",
"kickoff-control",
),
default="candidates", default="candidates",
) )
parser.add_argument("--output") parser.add_argument("--output")
@@ -273,8 +737,16 @@ def main() -> int:
script = build_candidate_script(pid, fifa_base, output) script = build_candidate_script(pid, fifa_base, output)
elif args.profile == "scheduler": elif args.profile == "scheduler":
script = build_scheduler_script(pid, fifa_base, output) script = build_scheduler_script(pid, fifa_base, output)
else: elif args.profile == "upstream":
script = build_upstream_script(pid, fifa_base, output) 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: except (OSError, RuntimeError, ValueError) as error:
print(error, file=sys.stderr) print(error, file=sys.stderr)
return 2 return 2