Trace FIFA17 PMA completion divergence

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