Files

731 lines
21 KiB
NASM

INCLUDE include\master.inc
; UWorld field offsets
UWORLD_NetDriver EQU 038h
UWORLD_AuthorityGameMode EQU 140h
UWORLD_GameState EQU 148h
UWORLD_OwningGameInstance EQU 190h
UGAMEINST_LocalPlayers EQU 038h ; TArray<ULocalPlayer*> Data ptr
UPLAYER_PlayerController EQU 030h ; UPlayer::PlayerController
; AGameMode / AFortGameModeAthena offsets
AGAMEMODE_MatchState EQU 03B8h
AGAMEMODE_MinRespawnDelay EQU 03D0h
AFGM_bEnableReplicationGraph EQU 0491h
AFGMA_bDisableGCOnServer EQU 08E8h
AFGMA_bAllowSpectateAfterDeath EQU 0B68h
; AFortGameStateAthena offsets
AFGSA_WarmupCntdwnEnd EQU 14F4h
AFGSA_AircraftStartTime EQU 14F8h
AFGSA_GamePhase EQU 1B98h
AFGSA_bSkipAircraft EQU 1BA8h
; AGameStateBase
AGSB_bReplHasBegunPlay EQU 0340h
; EDeathCause values
EDC_Unspecified EQU 0
EDC_Shotgun EQU 1
EDC_Rifle EQU 2
EDC_Sniper EQU 3
EDC_Pistol EQU 4
EDC_Grenade EQU 5
EDC_SMG EQU 6
EDC_Melee EQU 7
EDC_FallDamage EQU 8
EDC_RocketLauncher EQU 9
EDC_GrenadeLauncher EQU 10
EDC_Trap EQU 11
EDC_OutsideSafeZone EQU 12
EDC_Minigun EQU 13
EDC_Bow EQU 14
; UFunction field offsets
UFUNCTION_FLAGS_OFFSET EQU 088h ; UFunction::FunctionFlags (int32) - after UStruct(0x88)
FUNC_NATIVE EQU 0400h ; FUNC_Native flag
.const
; Console command for ExecuteConsoleCommand: "open Athena_Terrain?game=..."
szOpenAthena DW 'o','p','e','n',' '
DW 'A','t','h','e','n','a','_','T','e','r','r','a','i','n','?'
DW 'g','a','m','e','=','/','G','a','m','e','/','A','t','h','e'
DW 'n','a','/','A','t','h','e','n','a','_','G','a','m','e','M'
DW 'o','d','e','.','A','t','h','e','n','a','_','G','a','m','e'
szInProgress DW 'I','n','P','r','o','g','r','e','s','s', 0
; UFunction paths
szFn_ExecCmd DB "KismetSystemLibrary.ExecuteConsoleCommand", 0
szFn_ConvToName DB "KismetStringLibrary.Conv_StringToName", 0
szFn_K2SetMatchState DB "GameMode.K2_OnSetMatchState", 0
szFn_StartPlay DB "GameMode.StartPlay", 0
szFn_StartMatch DB "GameMode.StartMatch", 0
szFn_OnRepHasBegun DB "GameStateBase.OnRep_ReplicatedHasBegunPlay", 0
szFn_OnRepGamePhase DB "FortGameStateAthena.OnRep_GamePhase", 0
szClass_KismetStr DB "Class Engine.KismetStringLibrary", 0
szClass_KismetSys DB "Class Engine.KismetSystemLibrary", 0
; Death cause tags
szTag_Shotgun DB "weapon.ranged.shotgun", 0
szTag_Rifle DB "weapon.ranged.assault", 0
szTag_Fall DB "Gameplay.Damage.Environment.Falling", 0
szTag_Sniper DB "weapon.ranged.sniper", 0
szTag_SMG DB "Weapon.Ranged.SMG", 0
szTag_Rocket DB "weapon.ranged.heavy.rocket_launcher", 0
szTag_GrenadeL DB "weapon.ranged.heavy.grenade_launcher", 0
szTag_Grenade DB "Weapon.ranged.heavy.grenade", 0
szTag_Minigun DB "Weapon.Ranged.Heavy.Minigun", 0
szTag_Bow DB "Weapon.Ranged.Crossbow", 0
szTag_Trap DB "trap.floor", 0
szTag_Pistol DB "weapon.ranged.pistol", 0
szTag_SafeZone DB "Gameplay.Damage.OutsideSafeZone", 0
szTag_Melee DB "Weapon.Melee.Impact.Pickaxe", 0
szLogMatch DB "[GAME] Initializing match!", 0
szLogMode DB "[GAME] Solos game mode active.", 0
szLogStart DB "[GAME] Game::Start - traveling to Athena_Terrain.", 0
IFDEF DEBUG
szGDbg_AfterGP DB "[GAME] OnRep_GamePhase done", 0
szGDbg_AfterConv DB "[GAME] Conv_StringToName done", 0
szGDbg_AfterK2 DB "[GAME] K2_OnSetMatchState done", 0
szGDbg_BeforePlay DB "[GAME] Calling StartPlay", 0
szGDbg_AfterPlay DB "[GAME] StartPlay returned", 0
szGDbg_AfterRep DB "[GAME] OnRep_ReplicatedHasBegunPlay done", 0
szGDbg_AfterMatch DB "[GAME] StartMatch returned", 0
ENDIF
szDbgPCFound DB "[GAME] PlayerController found.", 0
szDbgPCNull DB "[GAME] PlayerController is NULL - aborting travel!", 0
szDbgECFound DB "[GAME] ExecuteConsoleCommand UFunction found.", 0
szDbgECNull DB "[GAME] ExecuteConsoleCommand NOT found in GObjects!", 0
szDbgCallingEC DB "[GAME] Calling ExecuteConsoleCommand 'open Athena_Terrain'...", 0
szDbgPEReturned DB "[GAME] ProcessEvent returned from ExecuteConsoleCommand.", 0
szDbgFlagsOk DB "[GAME] FunctionFlags restored.", 0
szDbgFStringFd DB "[GAME] FString freed.", 0
szDbgTraveled DB "[GAME] bTraveled set to 1.", 0
.data?
pFn_ExecCmd QWORD ?
pFn_ConvToName QWORD ?
pFn_K2SetMatchState QWORD ?
pFn_StartPlay QWORD ?
pFn_StartMatch QWORD ?
pFn_OnRepHasBegun QWORD ?
pFn_OnRepGamePhase QWORD ?
pClass_KismetStr QWORD ?
pClass_KismetSys QWORD ?
Game_Mode QWORD ? ; current game mode ptr (Solos = AuthorityGameMode)
.code
; Internal helper: load cached UFunction*.
; Inputs: RSI = &cache_qword, RDI = &szFunctionPath
; Output: RAX = UFunction* (0 if not found)
; Trashes: RCX
Game__LoadFn PROC
mov rax, QWORD PTR [rsi]
test rax, rax
jnz @@done
mov rcx, rdi
call SDK_FindObject
mov QWORD PTR [rsi], rax
@@done:
ret
Game__LoadFn ENDP
; QWORD Game__GetWorld_AuthMode() -> RAX
Game__GetWorld_AuthMode PROC
call SDK_GetWorld
test rax, rax
jz @@null
mov rax, QWORD PTR [rax + UWORLD_AuthorityGameMode]
ret
@@null:
xor eax, eax
ret
Game__GetWorld_AuthMode ENDP
; QWORD Game__GetWorld_GameState() -> RAX
Game__GetWorld_GameState PROC
call SDK_GetWorld
test rax, rax
jz @@null
mov rax, QWORD PTR [rax + UWORLD_GameState]
ret
@@null:
xor eax, eax
ret
Game__GetWorld_GameState ENDP
; QWORD Game_GetPlayerController() -> RAX
; Exported so ufunctionhooks can use it.
Game_GetPlayerController PROC
call SDK_GetWorld
test rax, rax
jz @@null
mov rax, QWORD PTR [rax + UWORLD_OwningGameInstance]
test rax, rax
jz @@null
mov rax, QWORD PTR [rax + UGAMEINST_LocalPlayers] ; LocalPlayers.Data
test rax, rax
jz @@null
mov rax, QWORD PTR [rax] ; [0] = ULocalPlayer*
test rax, rax
jz @@null
mov rax, QWORD PTR [rax + UPLAYER_PlayerController]
ret
@@null:
xor eax, eax
ret
Game_GetPlayerController ENDP
; Internal: call ProcessEvent with no meaningful params.
; RCX = UObject*, RDX = UFunction*, R8 = scratch buf on caller stack.
Game__CallPE_NoParams PROC
call QWORD PTR [ProcessEvent]
ret
Game__CallPE_NoParams ENDP
; void Game_Start()
; Frame: 4 pushes (rbp,rbx,rsi,rdi) + sub 58h
; After 4 pushes RSP=8; sub 58h(88=8) -> RSP=0
;
; ExecuteConsoleCommand params struct at [rsp+32] (32 bytes total):
; [rsp+32..39] = WorldContextObject: UObject* = PC
; [rsp+40..55] = Command: FString (Data*8, Count4, Max4) = "open Athena_Terrain?..."
; [rsp+56..63] = SpecificPlayer: APlayerController* = null
; [rsp+64..67] = saved UFunction::FunctionFlags
Game_Start PROC
push rbp
push rbx
push rsi
push rdi
sub rsp, 58h
lea rcx, szLogStart
call Logger_LogInfo
; Get player controller
call Game_GetPlayerController
test rax, rax
jz @@pc_null
mov rbx, rax ; rbx = PlayerController* (callee-saved)
lea rcx, szDbgPCFound
call Logger_LogInfo
jmp @@pc_ok
@@pc_null:
lea rcx, szDbgPCNull
call Logger_LogInfo
jmp @@done
@@pc_ok:
; Wait for both the KismetSystemLibrary UClass and the ExecuteConsoleCommand
; UFunction to appear in GObjects. Both may be absent immediately at startup
; (UFunctions for Blueprint-callable statics are registered lazily).
; Sleep 1 s between attempts; give up after 30 s.
; rbx (PC) is callee-saved and survives every call inside the loop.
mov DWORD PTR [rsp+68], 0 ; retry counter (within 88-byte frame)
@@find_retry:
cmp QWORD PTR [pClass_KismetSys], 0
jne @@class_cached
lea rcx, szClass_KismetSys
call SDK_FindObject
mov QWORD PTR [pClass_KismetSys], rax
@@class_cached:
cmp QWORD PTR [pFn_ExecCmd], 0
jne @@fn_cached
lea rcx, szFn_ExecCmd
call SDK_FindObject
mov QWORD PTR [pFn_ExecCmd], rax
@@fn_cached:
; Both found?
cmp QWORD PTR [pClass_KismetSys], 0
je @@do_retry
cmp QWORD PTR [pFn_ExecCmd], 0
jne @@lookup_done
@@do_retry:
cmp DWORD PTR [rsp+68], 30
jge @@ec_null ; give up — log and fall through
inc DWORD PTR [rsp+68]
mov ecx, 1000
call Sleep ; 1 s pause
jmp @@find_retry
@@lookup_done:
mov rsi, QWORD PTR [pFn_ExecCmd] ; rsi = ExecuteConsoleCommand UFunction*
lea rcx, szDbgECFound
call Logger_LogInfo
; Build ExecuteConsoleCommand params at [rsp+32]:
; [+0 ] WorldContextObject = PC
; [+8 ] Command (FString)
; [+24] SpecificPlayer = null
mov QWORD PTR [rsp+32], rbx ; WorldContextObject = PC
lea rcx, [rsp+40] ; &Command FString
lea rdx, szOpenAthena
call FString_FromWideChar ; fills [rsp+40..55]
mov QWORD PTR [rsp+56], 0 ; SpecificPlayer = null
; Set FUNC_Native, save original flags
mov eax, DWORD PTR [rsi + UFUNCTION_FLAGS_OFFSET]
mov DWORD PTR [rsp+64], eax
or DWORD PTR [rsi + UFUNCTION_FLAGS_OFFSET], FUNC_NATIVE
lea rcx, szDbgCallingEC
call Logger_LogInfo
; ProcessEvent(KismetSystemLibrary_class, fn, &params)
mov rcx, QWORD PTR [pClass_KismetSys]
mov rdx, rsi
lea r8, [rsp+32]
call QWORD PTR [ProcessEvent]
lea rcx, szDbgPEReturned
call Logger_LogInfo
; Restore flags
mov eax, DWORD PTR [rsp+64]
mov DWORD PTR [rsi + UFUNCTION_FLAGS_OFFSET], eax
lea rcx, szDbgFlagsOk
call Logger_LogInfo
; Free Command FString
lea rcx, [rsp+40]
call FString_Free
lea rcx, szDbgFStringFd
call Logger_LogInfo
jmp @@set_traveled
@@ec_null:
lea rcx, szDbgECNull
call Logger_LogInfo
@@set_traveled:
mov BYTE PTR [bTraveled], 1
lea rcx, szDbgTraveled
call Logger_LogInfo
@@done:
add rsp, 58h
pop rdi
pop rsi
pop rbx
pop rbp
ret
Game_Start ENDP
; void Game_OnReadyToStartMatch()
; Frame: 8 pushes (rbp,rbx,rsi,rdi,r12,r13,r14,r15) + sub 88h
; 8 pushes from base 8 -> RSP=8; sub 88h(136)=8 -> 0
; [rsp+32..+47] = FString scratch / Conv_StringToName input FString
; [rsp+48..+55] = FName output from Conv_StringToName
; [rsp+56..+63] = OnRep_GamePhase params (BYTE OldPhase + 7 pad)
; [rsp+64..+71] = K2_OnSetMatchState params (FName, 8 bytes)
; [rsp+72..+79] = saved UFunction::FunctionFlags (Conv_StringToName)
Game_OnReadyToStartMatch PROC
push rbp
push rbx
push rsi
push rdi
push r12
push r13
push r14
push r15
sub rsp, 88h
lea rcx, szLogMatch
call Logger_LogInfo
; r12 = GameState, r13 = AuthorityGameMode
call Game__GetWorld_GameState
test rax, rax
jz @@done
mov r12, rax
call Game__GetWorld_AuthMode
test rax, rax
jz @@done
mov r13, rax
; GameState fields
mov BYTE PTR [r12 + AFGSA_bSkipAircraft], 1
mov DWORD PTR [r12 + AFGSA_AircraftStartTime], 461C7EB8h ; 9999.9f
mov DWORD PTR [r12 + AFGSA_WarmupCntdwnEnd], 47C34F80h ; 99999.9f
mov BYTE PTR [r12 + AFGSA_GamePhase], EAthenaGamePhase_Warmup
; OnRep_GamePhase(EAthenaGamePhase::None)
lea rsi, pFn_OnRepGamePhase
lea rdi, szFn_OnRepGamePhase
call Game__LoadFn
test rax, rax
jz @@skip_gp
mov BYTE PTR [rsp+56], EAthenaGamePhase_None
mov rcx, r12
mov rdx, rax
lea r8, [rsp+56]
call QWORD PTR [ProcessEvent]
@@skip_gp:
LOG_DBG szGDbg_AfterGP
; GameMode fields
mov BYTE PTR [r13 + AFGMA_bDisableGCOnServer], 1
mov BYTE PTR [r13 + AFGMA_bAllowSpectateAfterDeath], 1
mov BYTE PTR [r13 + AFGM_bEnableReplicationGraph],1
; Get FName("InProgress") via Conv_StringToName
; Build FString "InProgress" at [rsp+32]
lea rcx, [rsp+32]
lea rdx, szInProgress
call FString_FromWideChar
; Get Conv_StringToName fn (static function - use fn as its own receiver)
lea rsi, pFn_ConvToName
lea rdi, szFn_ConvToName
call Game__LoadFn
mov r15, rax ; r15 = fn (may be 0)
; Zero FName output slot
xor eax, eax
mov QWORD PTR [rsp+48], rax
; Call Conv_StringToName: static function, use fn as receiver.
; Set FUNC_Native so ProcessEvent routes to native implementation.
; Params: [rsp+32]=FString input, [rsp+48]=FName output
test r15, r15
jz @@skip_conv
mov eax, DWORD PTR [r15 + UFUNCTION_FLAGS_OFFSET]
mov DWORD PTR [rsp+72], eax ; save FunctionFlags
or DWORD PTR [r15 + UFUNCTION_FLAGS_OFFSET], FUNC_NATIVE
; Use KismetStringLibrary UClass as receiver
; GetKismetString() = UKismetStringLibrary::StaticClass()
; r15 (fn) is callee-saved and preserved across SDK_FindObject.
mov rax, QWORD PTR [pClass_KismetStr]
test rax, rax
jnz @@have_ksllib
lea rcx, szClass_KismetStr
call SDK_FindObject
mov QWORD PTR [pClass_KismetStr], rax
@@have_ksllib:
test rax, rax
jnz @@do_conv_pe
mov rax, r15 ; fallback: fn as receiver
@@do_conv_pe:
mov rcx, rax ; receiver = KismetStringLibrary UClass
mov rdx, r15
lea r8, [rsp+32]
call QWORD PTR [ProcessEvent]
mov eax, DWORD PTR [rsp+72]
mov DWORD PTR [r15 + UFUNCTION_FLAGS_OFFSET], eax
@@skip_conv:
LOG_DBG szGDbg_AfterConv
; Free FString temp
lea rcx, [rsp+32]
call FString_Free
; MatchState = InProgress (FName at [rsp+48])
mov rax, QWORD PTR [rsp+48]
mov QWORD PTR [r13 + AGAMEMODE_MatchState], rax
; K2_OnSetMatchState(InProgress) - params: {FName NewState}
lea rsi, pFn_K2SetMatchState
lea rdi, szFn_K2SetMatchState
call Game__LoadFn
test rax, rax
jz @@skip_k2
mov rbx, QWORD PTR [rsp+48]
mov QWORD PTR [rsp+64], rbx ; params.NewState
mov rcx, r13
mov rdx, rax
lea r8, [rsp+64]
call QWORD PTR [ProcessEvent]
@@skip_k2:
LOG_DBG szGDbg_AfterK2
; Store current game mode, can be changed
mov QWORD PTR [Game_Mode], r13
lea rcx, szLogMode
call Logger_LogInfo
; MinRespawnDelay = 5.0f
mov DWORD PTR [r13 + AGAMEMODE_MinRespawnDelay], 40A00000h ; 5.0f
; StartPlay()
lea rsi, pFn_StartPlay
lea rdi, szFn_StartPlay
call Game__LoadFn
test rax, rax
jz @@skip_sp
LOG_DBG szGDbg_BeforePlay
xor r8d, r8d ; params = NULL (StartPlay takes no params)
mov rcx, r13
mov rdx, rax
lea r8, [rsp+32]
call QWORD PTR [ProcessEvent]
LOG_DBG szGDbg_AfterPlay
@@skip_sp:
; GameState->bReplicatedHasBegunPlay = true
mov BYTE PTR [r12 + AGSB_bReplHasBegunPlay], 1
; OnRep_ReplicatedHasBegunPlay()
lea rsi, pFn_OnRepHasBegun
lea rdi, szFn_OnRepHasBegun
call Game__LoadFn
test rax, rax
jz @@skip_rb
xor r8d, r8d ; no params
mov rcx, r12
mov rdx, rax
lea r8, [rsp+32]
call QWORD PTR [ProcessEvent]
LOG_DBG szGDbg_AfterRep
@@skip_rb:
; StartMatch()
lea rsi, pFn_StartMatch
lea rdi, szFn_StartMatch
call Game__LoadFn
test rax, rax
jz @@done
mov rcx, r13
mov rdx, rax
lea r8, [rsp+32]
call QWORD PTR [ProcessEvent]
LOG_DBG szGDbg_AfterMatch
@@done:
add rsp, 88h
pop r15
pop r14
pop r13
pop r12
pop rdi
pop rsi
pop rbx
pop rbp
ret
Game_OnReadyToStartMatch ENDP
; DWORD Game_GetDeathCause(FFortPlayerDeathReport* pReport)
; RCX = pReport. Returns EDeathCause in EAX.
;
; FFortPlayerDeathReport::Tags (FGameplayTagContainer) at +0x30.
; FGameplayTagContainer::GameplayTags (TArray<FGameplayTag>) at +0x00.
; FGameplayTag::TagName (FName, 8 bytes).
; We FNameToString each tag name, narrow-convert, strcmp vs table.
;
; Frame: 5 pushes (rbp,rbx,rsi,rdi,r12) + sub 48h
; 5 pushes from base 8 -> RSP=8+5*8=48=0 mod16... wait:
; entry 8 mod16, push->0, push->8, push->0, push->8, push->0 after 5 pushes = 0 mod16
; sub 48h(72): 72 mod16=8 -> 0-8=-8=8 mod16.
AFPDR_Tags_Data EQU 030h
AFPDR_Tags_Num EQU 038h
Game_GetDeathCause PROC
push rbp
push rbx
push rsi
push rdi
push r12
sub rsp, 50h
; Default = Unspecified
xor r12d, r12d
test rcx, rcx
jz @@done
mov rbp, rcx ; rbp = pReport
; Load GameplayTags array
mov rsi, QWORD PTR [rbp + AFPDR_Tags_Data] ; Tags.GameplayTags.Data
mov edi, DWORD PTR [rbp + AFPDR_Tags_Num] ; Tags.GameplayTags.Num
test rsi, rsi
jz @@done
test edi, edi
jz @@done
xor ebx, ebx ; i = 0
@@loop:
cmp ebx, edi
jge @@done
; FName at rsi + i*8
lea rcx, [rsi + rbx*8] ; FName* in
; Zero FString at [rsp+32]
xor eax, eax
mov QWORD PTR [rsp+32], rax
mov QWORD PTR [rsp+40], rax
lea rdx, [rsp+32] ; FString& out
call QWORD PTR [FNameToString]
; Load FString.Data (wchar_t*)
mov rdi, QWORD PTR [rsp+32]
test rdi, rdi
jz @@next
; Narrow-convert wchar_t -> char in-place
xor ecx, ecx
@@narrow:
movzx eax, WORD PTR [rdi + rcx*2]
test eax, eax
jz @@narrow_done
mov BYTE PTR [rdi + rcx], al
inc ecx
jmp @@narrow
@@narrow_done:
mov BYTE PTR [rdi + rcx], 0
; Compare against tag table; first arg = rdi (our string), second = table entry
; We use a helper macro sequence:
; strcmp(s1=rdi, s2=tag) in x64: RCX=s1, RDX=s2
mov rcx, rdi
lea rdx, szTag_Shotgun
call strcmp
test eax, eax
jnz @@c2
mov r12d, EDC_Shotgun
jmp @@found
@@c2:
mov rcx, rdi
lea rdx, szTag_Rifle
call strcmp
test eax, eax
jnz @@c3
mov r12d, EDC_Rifle
jmp @@found
@@c3:
mov rcx, rdi
lea rdx, szTag_Fall
call strcmp
test eax, eax
jnz @@c4
mov r12d, EDC_FallDamage
jmp @@found
@@c4:
mov rcx, rdi
lea rdx, szTag_Sniper
call strcmp
test eax, eax
jnz @@c5
mov r12d, EDC_Sniper
jmp @@found
@@c5:
mov rcx, rdi
lea rdx, szTag_SMG
call strcmp
test eax, eax
jnz @@c6
mov r12d, EDC_SMG
jmp @@found
@@c6:
mov rcx, rdi
lea rdx, szTag_Rocket
call strcmp
test eax, eax
jnz @@c7
mov r12d, EDC_RocketLauncher
jmp @@found
@@c7:
mov rcx, rdi
lea rdx, szTag_GrenadeL
call strcmp
test eax, eax
jnz @@c8
mov r12d, EDC_GrenadeLauncher
jmp @@found
@@c8:
mov rcx, rdi
lea rdx, szTag_Grenade
call strcmp
test eax, eax
jnz @@c9
mov r12d, EDC_Grenade
jmp @@found
@@c9:
mov rcx, rdi
lea rdx, szTag_Minigun
call strcmp
test eax, eax
jnz @@c10
mov r12d, EDC_Minigun
jmp @@found
@@c10:
mov rcx, rdi
lea rdx, szTag_Bow
call strcmp
test eax, eax
jnz @@c11
mov r12d, EDC_Bow
jmp @@found
@@c11:
mov rcx, rdi
lea rdx, szTag_Trap
call strcmp
test eax, eax
jnz @@c12
mov r12d, EDC_Trap
jmp @@found
@@c12:
mov rcx, rdi
lea rdx, szTag_Pistol
call strcmp
test eax, eax
jnz @@c13
mov r12d, EDC_Pistol
jmp @@found
@@c13:
mov rcx, rdi
lea rdx, szTag_SafeZone
call strcmp
test eax, eax
jnz @@c14
mov r12d, EDC_OutsideSafeZone
jmp @@found
@@c14:
mov rcx, rdi
lea rdx, szTag_Melee
call strcmp
test eax, eax
jnz @@next
mov r12d, EDC_Melee
jmp @@found
@@found:
lea rcx, [rsp+32]
call FString_Free
jmp @@done
@@next:
lea rcx, [rsp+32]
call FString_Free
inc ebx
jmp @@loop
@@done:
mov eax, r12d
add rsp, 50h
pop r12
pop rdi
pop rsi
pop rbx
pop rbp
ret
Game_GetDeathCause ENDP
END