Files
Perfectium/ufunctionhooks.asm

1813 lines
57 KiB
NASM
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
INCLUDE include\master.inc
ACONTROLLER_Pawn EQU 0260h ; AController::Pawn (APawn*)
ACONTROLLER_PlayerState EQU 0248h ; AController::PlayerState (APlayerState*)
APAWN_Controller EQU 0228h ; APawn::Controller (AController*)
APAWN_PlayerState EQU 0258h ; APawn::PlayerState
AFPCA_bIsDisconnecting EQU 1674h ; AFortPlayerControllerAthena::bIsDisconnecting (bool)
AOBH_ListenPort EQU 0208h ; AOnlineBeaconHost::ListenPort (int32)
AOBH_NetDriver EQU 0220h ; AOnlineBeaconHost::Driver (UNetDriver*)
ABSMA_EditingPlayer EQU 0480h ; ABuildingSMActor::EditingPlayer
UWORLD_NetDriver EQU 038h ; UWorld::NetDriver (UNetDriver*)
UWORLD_AuthorityGameMode EQU 0140h ; UWorld::AuthorityGameMode (AGameMode*)
FURL_Port EQU 020h ; FURL::Port (DWORD, per structs.inc)
VTABLE_ServerReplicateActors EQU (083h * 8) ; vtable byte offset for slot 0x53
UWORLD_GameState EQU 0148h ; UWorld::GameState (AGameStateBase*)
ABLDGCONT_bAlreadySearched EQU 0EA5h ; ABuildingContainer::bAlreadySearched (bit 0)
; Offsets for Server_Initialize network wiring
UWORLD_LevelCollections EQU 170h ; UWorld::LevelCollections (TArray<FLevelCollection>)
FLEVELLCOLL_NetDriver EQU 010h ; FLevelCollection::NetDriver
FLEVELLCOLL_SIZEOF EQU 080h ; sizeof(FLevelCollection)
AGAMEMODE_GameSession EQU 370h ; AGameMode::GameSession (AGameSession*)
AGAMESESSION_MaxPlayers EQU 31Ch ; AGameSession::MaxPlayers (int32)
MAX_PEHOOKS EQU 32 ; capacity of the hook arrays
.data?
; Public pointers consumed by Hooks_ProcessEventHook
UFunctionHooks_ToHook_Data QWORD ? ; -> _ToHook_Storage (set in Initialize)
UFunctionHooks_ToHook_Num DWORD ?
DWORD ? ; pad to QWORD
UFunctionHooks_ToCall_Data QWORD ? ; -> _ToCall_Storage (set in Initialize)
; Internal flat arrays (MAX_PEHOOKS elements each)
_ToHook_Storage QWORD MAX_PEHOOKS DUP (?)
_ToCall_Storage QWORD MAX_PEHOOKS DUP (?)
; Lazily-resolved UFunction* cache (used by handlers)
pFn_K2_DestroyActor QWORD ?
pFn_ClientOnPawnRevived QWORD ?
pFn_ForceNetUpdate QWORD ?
pFn_OnRep_ReplicatedAnimMontage QWORD ?
pFn_InitKismetBuildingActor QWORD ?
pFn_SilentDie QWORD ?
pFn_OnRep_EditingPlayer QWORD ?
pFn_OnRep_EditActor QWORD ?
pFn_K2_GetActorLocation QWORD ?
pFn_RepairBuilding QWORD ?
pFn_OnRep_bAlreadySearched QWORD ?
pFn_OnRep_PlayersLeft QWORD ?
pFn_OnRep_DeathInfo QWORD ?
; Cached class pointers
pClass_FortOnlineBeaconHost QWORD ?
pClass_FortGameModeAthena QWORD ?
.const
szFn_ServerTryActivateAbility DB "Function GameplayAbilities.AbilitySystemComponent.ServerTryActivateAbility", 0
szFn_ServerTryActivateWithEventData DB "Function GameplayAbilities.AbilitySystemComponent.ServerTryActivateAbilityWithEventData", 0
szFn_ServerAbilityRPCBatch DB "Function FortniteGame.FortAbilitySystemComponent.ServerAbilityRPCBatch", 0
szFn_ServerHandlePickup DB "Function FortniteGame.FortPlayerPawn.ServerHandlePickup", 0
szFn_CheatScript DB "Function FortniteGame.FortGameModeAthena.CheatScript", 0
szFn_OnDeathServer DB "Function FortniteGame.BuildingSMActor.OnDeathServer", 0
szFn_ServerCreateBuildingActor DB "Function FortniteGame.BuildingSMActor.ServerCreateBuildingActor", 0
szFn_ServerBeginEditingBuilding DB "Function FortniteGame.BuildingSMActor.ServerBeginEditingBuildingActor", 0
szFn_ServerSpawnDeco DB "Function FortniteGame.FortDecoTool.ServerSpawnDeco", 0
szFn_ServerEditBuildingActor DB "Function FortniteGame.BuildingSMActor.ServerEditBuildingActor", 0
szFn_ClientOnPawnDied DB "Function FortniteGame.FortPlayerController.ClientOnPawnDied", 0
szFn_ServerEndEditingBuilding DB "Function FortniteGame.BuildingSMActor.ServerEndEditingBuildingActor", 0
szFn_ServerRepairBuildingActor DB "Function FortniteGame.BuildingSMActor.ServerRepairBuildingActor", 0
szFn_ServerAttemptAircraftJump DB "Function FortniteGame.FortPlayerPawn.ServerAttemptAircraftJump", 0
szFn_ServerReviveFromDBNO DB "Function FortniteGame.FortPlayerPawn.ServerReviveFromDBNO", 0
szFn_ServerAttemptInteract DB "Function FortniteGame.FortPlayerPawn.ServerAttemptInteract", 0
szFn_ServerPlayEmoteItem DB "Function FortniteGame.FortPlayerPawn.ServerPlayEmoteItem", 0
szFn_ServerAttemptInventoryDrop DB "Function FortniteGame.FortPlayerPawn.ServerAttemptInventoryDrop", 0
szFn_OnSpawnOutAnimEnded DB "Function FortniteGame.AthenaVictoryDrone.OnSpawnOutAnimEnded", 0
szFn_ServerExecuteInventoryItem DB "Function FortniteGame.FortPlayerController.ServerExecuteInventoryItem", 0
szFn_ServerReturnToMainMenu DB "Function FortniteGame.FortPlayerController.ServerReturnToMainMenu", 0
szFn_ServerLoadingScreenDropped DB "Function FortniteGame.FortPlayerController.ServerLoadingScreenDropped", 0
szFn_ServerChoosePart DB "Function FortniteGame.FortPlayerControllerCommon.ServerChoosePart", 0
szFn_OnAircraftExitedDropZone DB "Function FortniteGame.FortAthenaAircraft.OnAircraftExitedDropZone", 0
szFn_ServerCheatAll DB "Function FortniteGame.FortGameModeAthena.ServerCheatAll", 0
szFn_Logout DB "Function FortniteGame.FortGameModeAthena.Logout", 0
szFn_ReadyToStartMatch DB "Function Engine.GameMode.ReadyToStartMatch", 0
szFn_K2_DestroyActor DB "Function Engine.Actor.K2_DestroyActor", 0
szFn_ClientOnPawnRevived DB "Function FortniteGame.FortPlayerPawn.ClientOnPawnRevived", 0
szFn_ForceNetUpdate DB "Function Engine.Actor.ForceNetUpdate", 0
szFn_OnRepRepAnimMontage DB "Function GameplayAbilities.AbilitySystemComponent.OnRep_ReplicatedAnimMontageForMesh", 0
szFn_InitKismetBuildingActor DB "Function FortniteGame.BuildingSMActor.InitializeKismetSpawnedBuildingActor", 0
szFn_SilentDie DB "Function FortniteGame.BuildingActor.SilentDie", 0
szFn_OnRep_EditingPlayer DB "Function FortniteGame.BuildingSMActor.OnRep_EditingPlayer", 0
szFn_OnRep_EditActor DB "Function FortniteGame.FortWeap_EditingTool.OnRep_EditActor", 0
szFn_K2_GetActorLocation DB "Function Engine.Actor.K2_GetActorLocation", 0
szClass_FortOnlineBeaconHost DB "Class FortniteGame.FortOnlineBeaconHost", 0
szClass_FortGameModeAthena DB "Class FortniteGame.FortGameModeAthena", 0
szFn_RepairBuilding DB "Function FortniteGame.BuildingSMActor.RepairBuilding", 0
szFn_OnRep_bAlreadySearched DB "Function FortniteGame.BuildingContainer.OnRep_bAlreadySearched", 0
szFn_OnRep_PlayersLeft DB "Function FortniteGame.FortGameStateAthena.OnRep_PlayersLeft", 0
szFn_OnRep_DeathInfo DB "Function FortniteGame.FortPlayerStateAthena.OnRep_DeathInfo", 0
szHookCount DB "[UFHOOKS] Registered %d UFunction hooks", 0Ah, 0
; Server_Initialize
szSI_AthenaOk DB "[SI] Athena context confirmed - starting listen server setup", 0
szSI_BeaconSpawned DB "[SI] OnlineBeaconHost spawned", 0
szSI_BeaconFail DB "[SI] WARN: OnlineBeaconHost spawn failed - skipping beacon", 0
szSI_NetDriverOk DB "[SI] NetDriver obtained from beacon", 0
szSI_NoNetDriver DB "[SI] WARN: NetDriver null after InitHost - skipping InitListen", 0
szSI_InitListen DB "[SI] Calling InitListen(port 7777)", 0
szSI_WorldWired DB "[SI] World->NetDriver wired, LevelCollections patched", 0
szSI_Listening DB "[SI] Server is now listening - initialization complete", 0
IFDEF DEBUG
szUFDbg_Start DB "[UFHOOKS] Starting UFunction hook registration", 0
szSI_Dbg_Entry DB "[SI] Server_Initialize tick (bListening=0)", 0
szSI_Dbg_NoWorld DB "[SI] World null - retrying next tick", 0
szSI_Dbg_NoGameMode DB "[SI] AuthorityGameMode null - retrying next tick", 0
szSI_Dbg_NoClass DB "[SI] FortGameModeAthena class not found - retrying next tick", 0
szSI_Dbg_NotAthena DB "[SI] AuthorityGameMode not AFortGameModeAthena - retrying next tick", 0
ENDIF
.code
PEHOOK_CheatScript PROC
xor al, al ; return 0
ret
PEHOOK_CheatScript ENDP
PEHOOK_ServerCheatAll PROC
mov al, 1 ; return 1
ret
PEHOOK_ServerCheatAll ENDP
PEHOOK_ServerChoosePart PROC
mov al, 1 ; return 1
ret
PEHOOK_ServerChoosePart ENDP
; PEHOOK_Logout - set bIsDisconnecting on the leaving controller
; RCX = AFortGameModeAthena* (GameMode)
; RDX = Params: { AController* Exiting; } ([RDX+0])
PEHOOK_Logout PROC
mov rax, QWORD PTR [rdx] ; Exiting controller
test rax, rax
jz @@done
mov BYTE PTR [rax + AFPCA_bIsDisconnecting], 1
@@done:
xor al, al
ret
PEHOOK_Logout ENDP
; PEHOOK_OnSpawnOutAnimEnded - destroy the VictoryDrone actor
; RCX = AthenaVictoryDrone* (the drone that finished its exit anim)
; RDX = void* Params (empty)
;
; Calls K2_DestroyActor on the drone via ProcessEvent (lazy lookup).
;
; Stack: push rbp + push rbx = 2 pushes; sub 40 -> 0
PEHOOK_OnSpawnOutAnimEnded PROC
push rbp
push rbx
sub rsp, 40 ; 2 pushes: RSP=8; sub40 -> 0
mov rbx, rcx ; save drone actor*
; Lazy-resolve K2_DestroyActor UFunction
mov rax, QWORD PTR [pFn_K2_DestroyActor]
test rax, rax
jnz @@have_fn
lea rcx, szFn_K2_DestroyActor
call SDK_FindObject
mov QWORD PTR [pFn_K2_DestroyActor], rax
@@have_fn:
test rax, rax
jz @@done
; ProcessEvent(Drone, K2_DestroyActor, NULL)
mov rcx, rbx
mov rdx, rax
xor r8d, r8d
call QWORD PTR [ProcessEvent]
@@done:
add rsp, 40
pop rbx
pop rbp
xor al, al
ret
PEHOOK_OnSpawnOutAnimEnded ENDP
; PEHOOK_ServerLoadingScreenDropped - apply abilities to pawn
; RCX = AFortPlayerController*
; Gets Pawn from PC, calls Abilities_ApplyAbilities(Pawn).
;
; Stack: push rbp + push rbx = 2 pushes; sub 40 -> 0
PEHOOK_ServerLoadingScreenDropped PROC
push rbp
push rbx
sub rsp, 40
mov rbx, rcx ; PC
; Pawn = PC->Pawn (AController::Pawn at +ACONTROLLER_Pawn)
mov rax, QWORD PTR [rbx + ACONTROLLER_Pawn]
test rax, rax
jz @@done
mov rcx, rax
call Abilities_ApplyAbilities
@@done:
add rsp, 40
pop rbx
pop rbp
xor al, al
ret
PEHOOK_ServerLoadingScreenDropped ENDP
; PEHOOK_ServerAttemptInventoryDrop
; RCX = AFortPlayerPawn* (the pawn dropping)
; RDX = Params: { FGuid ItemGuid; int Count; }
; Gets PC from Pawn, calls Inventory_OnDrop(PC, Params).
;
; Stack: push rbp + push rbx + push rsi = 3 pushes (RSP=0); sub 32 -> 0
PEHOOK_ServerAttemptInventoryDrop PROC
push rbp
push rbx
push rsi
sub rsp, 32 ; 3 pushes: RSP=0; sub32 -> 0
mov rbx, rcx ; Pawn
mov rsi, rdx ; Params
; Controller = Pawn->Controller
mov rax, QWORD PTR [rbx + APAWN_Controller]
test rax, rax
jz @@done
mov rcx, rax
mov rdx, rsi
call Inventory_OnDrop
@@done:
add rsp, 32
pop rsi
pop rbx
pop rbp
xor al, al
ret
PEHOOK_ServerAttemptInventoryDrop ENDP
; PEHOOK_ServerHandlePickup
; RCX = AFortPlayerPawn* (the picking-up pawn)
; RDX = Params: { AFortPickup* Pickup; float FlyTime; FVector StartDir; bool bPlaySound; }
;
; Stack: push rbp + push rbx + push rsi = 3 pushes; sub 32 -> 0
PEHOOK_ServerHandlePickup PROC
push rbp
push rbx
push rsi
sub rsp, 32
mov rbx, rcx ; Pawn
mov rsi, rdx ; Params
mov rax, QWORD PTR [rbx + APAWN_Controller]
test rax, rax
jz @@done
mov rcx, rax ; PC
mov rdx, rsi ; Params
call Inventory_OnPickup
@@done:
add rsp, 32
pop rsi
pop rbx
pop rbp
xor al, al
ret
PEHOOK_ServerHandlePickup ENDP
; PEHOOK_ServerExecuteInventoryItem
; RCX = AFortPlayerController*
; RDX = Params: { FGuid ItemGuid; } ([RDX+0])
;
; Stack: push rbp + push rbx = 2 pushes; sub 40 -> 0
PEHOOK_ServerExecuteInventoryItem PROC
push rbp
push rbx
sub rsp, 40
mov rbx, rcx ; PC
; Guid at [RDX+0] - pass rdx as-is (Inventory_EquipInventoryItem takes (PC, Guid*))
mov rcx, rbx
; rdx = Params (Guid is at offset 0, so Params == &Guid)
call Inventory_EquipInventoryItem
add rsp, 40
pop rbx
pop rbp
xor al, al
ret
PEHOOK_ServerExecuteInventoryItem ENDP
; PEHOOK_ServerReturnToMainMenu
; RCX = AFortPlayerController* - set bIsDisconnecting + return true
PEHOOK_ServerReturnToMainMenu PROC
test rcx, rcx
jz @@done
mov BYTE PTR [rcx + AFPCA_bIsDisconnecting], 1
@@done:
mov al, 1
ret
PEHOOK_ServerReturnToMainMenu ENDP
; PEHOOK_OnDeathServer - remove dying building from ExistingBuildings
; RCX = ABuildingSMActor* (dying actor)
; Scans ExistingBuildings array; on match, swaps with last and decrements Num.
;
; Stack: push rbp + push rbx + push rsi + push rdi = 4 pushes (RSP=8); sub 40 -> 0
PEHOOK_OnDeathServer PROC
push rbp
push rbx
push rsi
push rdi
sub rsp, 40 ; 4 pushes: RSP=8; sub40 -> 0
mov rbx, rcx ; dying actor
; Load ExistingBuildings array
mov rsi, QWORD PTR [ExistingBuildings] ; Data ptr (QWORD**)
mov edi, DWORD PTR [ExistingBuildingsNum]; Num
test rsi, rsi
jz @@done
test edi, edi
jle @@done
xor ebp, ebp ; i = 0
@@scan_loop:
cmp ebp, edi
jge @@done
mov rax, QWORD PTR [rsi + rbp * 8] ; ExistingBuildings[i]
cmp rax, rbx
jne @@next
; Found at index i - swap with last element (unordered remove)
dec edi ; new Num = Num-1
cmp ebp, edi ; if i == new Num, no swap needed
je @@erase
mov rax, QWORD PTR [rsi + rdi * 8] ; last element
mov QWORD PTR [rsi + rbp * 8], rax ; [i] = last
@@erase:
mov DWORD PTR [ExistingBuildingsNum], edi
jmp @@done
@@next:
inc ebp
jmp @@scan_loop
@@done:
add rsp, 40
pop rdi
pop rsi
pop rbx
pop rbp
xor al, al
ret
PEHOOK_OnDeathServer ENDP
; PEHOOK_ServerEndEditingBuildingActor
; RCX = ABuildingSMActor* (building being released)
; Clears EditingPlayer field on the building.
PEHOOK_ServerEndEditingBuildingActor PROC
test rcx, rcx
jz @@done
mov QWORD PTR [rcx + ABSMA_EditingPlayer], 0
@@done:
xor al, al
ret
PEHOOK_ServerEndEditingBuildingActor ENDP
; PEHOOK_ServerRepairBuildingActor
; RCX = AFortPlayerPawn* (pawn issuing repair)
; RDX = Params: { ABuildingSMActor* BuildingActorToRepair; }
;
; Calls RepairBuilding (amount 50) via ProcessEvent on the target building.
; Stack: push rbp + push rbx + push rsi = 3 pushes; sub 32 -> 0
PEHOOK_ServerRepairBuildingActor PROC
push rbp
push rbx
push rsi
sub rsp, 48h ; 3 pushes=0 mod16; sub 48h=0 mod16
; [0..1F]=shadow, [20..27]=RepairBuilding params.RepairingController
; [28..2B]=params.ResourcesSpent, [30..37]=scratch
mov rbx, rcx ; Controller (PC) - Object per DEFINE_PEHOOK
mov rsi, QWORD PTR [rdx] ; Params->BuildingActorToRepair ([RDX+0])
test rsi, rsi
jz @@done
; Lazy-resolve RepairBuilding UFunction
mov rax, QWORD PTR [pFn_RepairBuilding]
test rax, rax
jnz @@have_fn
lea rcx, [szFn_RepairBuilding]
call SDK_FindObject
mov QWORD PTR [pFn_RepairBuilding], rax
@@have_fn:
test rax, rax
jz @@done
; Build RepairBuilding params: {AFortPlayerController* RepairingController; int ResourcesSpent;}
mov QWORD PTR [rsp+20h], rbx ; RepairingController = Controller
mov DWORD PTR [rsp+28h], 50 ; ResourcesSpent = 50
; ProcessEvent(Building, RepairBuilding, &params)
mov rcx, rsi
mov rdx, rax
lea r8, [rsp+20h]
call QWORD PTR [ProcessEvent]
@@done:
add rsp, 48h
pop rsi
pop rbx
pop rbp
xor al, al
ret
PEHOOK_ServerRepairBuildingActor ENDP
; PEHOOK_ServerReviveFromDBNO
; RCX = AFortPlayerPawn* (the DBNO pawn being revived)
; RDX = Params: { AController* EventInstigator; }
;
; Stack: push rbp + push rbx = 2 pushes; sub 40 -> 0
PEHOOK_ServerReviveFromDBNO PROC
push rbp
push rbx
sub rsp, 40
mov rbx, rcx ; Pawn
; Lazy-resolve ClientOnPawnRevived UFunction
mov rax, QWORD PTR [pFn_ClientOnPawnRevived]
test rax, rax
jnz @@have_revive
lea rcx, szFn_ClientOnPawnRevived
call SDK_FindObject
mov QWORD PTR [pFn_ClientOnPawnRevived], rax
@@have_revive:
test rax, rax
jz @@done
; ProcessEvent(Pawn, ClientOnPawnRevived, NULL)
mov rcx, rbx
mov rdx, rax
xor r8d, r8d
call QWORD PTR [ProcessEvent]
@@done:
add rsp, 40
pop rbx
pop rbp
xor al, al
ret
PEHOOK_ServerReviveFromDBNO ENDP
; PEHOOK_ServerAttemptInteract
; RCX = AFortPlayerPawn* (interacting pawn)
; RDX = Params: { AActor* ReceivingActor; }
;
; If ReceivingActor is a pawn: call PEHOOK_ServerReviveFromDBNO logic.
;
; Stack: push rbp + push rbx + push rsi = 3 pushes; sub 32 -> 0
PEHOOK_ServerAttemptInteract PROC
push rbp
push rbx
push rsi
sub rsp, 32
mov rbx, rcx ; interacting Pawn
mov rsi, QWORD PTR [rdx] ; ReceivingActor ([Params+0])
test rsi, rsi
jz @@done
; Check if ReceivingActor is a Pawn (non-null Controller means it's a player pawn)
mov rax, QWORD PTR [rsi + APAWN_Controller]
test rax, rax
jz @@container_case
; Pawn case - revive from DBNO
mov rcx, rsi ; the pawn to revive
xor edx, edx
call PEHOOK_ServerReviveFromDBNO
jmp @@done
@@container_case:
; rsi = Container (ABuildingContainer*)
; Set bAlreadySearched (bit 0 of byte at +0x0EA5)
or BYTE PTR [rsi + ABLDGCONT_bAlreadySearched], 1
; Lazy-resolve OnRep_bAlreadySearched UFunction (rsi is callee-saved, survives FindObject)
mov rax, QWORD PTR [pFn_OnRep_bAlreadySearched]
test rax, rax
jnz @@have_searched_rep
lea rcx, [szFn_OnRep_bAlreadySearched]
call SDK_FindObject
mov QWORD PTR [pFn_OnRep_bAlreadySearched], rax
@@have_searched_rep:
test rax, rax
jz @@done
mov rcx, rsi
mov rdx, rax
xor r8d, r8d
call QWORD PTR [ProcessEvent]
@@done:
add rsp, 32
pop rsi
pop rbx
pop rbp
xor al, al
ret
PEHOOK_ServerAttemptInteract ENDP
; PEHOOK_ServerPlayEmoteItem
; RCX = APlayerPawn_Athena_C* (the pawn emoting)
; RDX = Params: { UFortMontageItemDefinitionBase* EmoteAsset; }
;
; Sets RepAnimMontageInfo on the AbilitySystemComponent so the emote
; is replicated to all clients.
;
; Stack: push rbp + push rbx + push rsi = 3 pushes; sub 32 -> 0
PEHOOK_ServerPlayEmoteItem PROC
push rbp
push rbx
push rsi
sub rsp, 32
mov rbx, rcx ; Pawn
mov rsi, QWORD PTR [rdx] ; EmoteAsset ([Params+0])
test rsi, rsi
jz @@done
; Get AbilitySystemComponent from pawn
mov rcx, rbx
call FortHelper_GetAbilitySystemComponent
test rax, rax
jz @@done
; Write to RepAnimMontageInfo (FGameplayAbilityRepAnimMontage at ASC+0xAF0)
; Layout: AnimMontage(+0), PlayRate(+8), Position(+C), BlendTime(+10),
; NextSectionID(+14), bitfield(+15)
mov QWORD PTR [rax + UASC_RepAnimMontageInfo + 000h], rsi ; AnimMontage = EmoteAsset
mov DWORD PTR [rax + UASC_RepAnimMontageInfo + 008h], 3F800000h ; PlayRate = 1.0f
mov DWORD PTR [rax + UASC_RepAnimMontageInfo + 00Ch], 0 ; Position = 0.0f
mov DWORD PTR [rax + UASC_RepAnimMontageInfo + 010h], 0 ; BlendTime = 0.0f
mov BYTE PTR [rax + UASC_RepAnimMontageInfo + 014h], 0 ; NextSectionID = 0
mov BYTE PTR [rax + UASC_RepAnimMontageInfo + 015h], 2 ; ForcePlayBit set (bit1)
; ForceNetUpdate on the pawn to push replication
mov rcx, rbx
mov rdx, QWORD PTR [pFn_ForceNetUpdate]
test rdx, rdx
jz @@done
xor r8d, r8d
call QWORD PTR [ProcessEvent]
@@done:
add rsp, 32
pop rsi
pop rbx
pop rbp
xor al, al
ret
PEHOOK_ServerPlayEmoteItem ENDP
; PEHOOK_ServerSpawnDeco
; RCX = AFortDecoTool* (the deco tool actor)
; RDX = Params: { FVector WorldPosition; FRotator Rotation; ABuildingSMActor* AttachedTo; }
; WorldPos at [RDX+0], Rotation at [RDX+C], AttachedTo at [RDX+18] (after FRotator)
;
; Stack: push rbp + push rbx + push rsi = 3 pushes; sub 32 -> 0
PEHOOK_ServerSpawnDeco PROC
push rbp
push rbx
push rsi
sub rsp, 32
mov rbx, rcx ; DecoTool
mov rsi, rdx ; Params
mov rcx, rbx
mov rdx, rsi
call Spawners_SpawnDeco
add rsp, 32
pop rsi
pop rbx
pop rbp
xor al, al
ret
PEHOOK_ServerSpawnDeco ENDP
; PEHOOK_ServerTryActivateAbility
; RCX = UAbilitySystemComponent* (ASC)
; RDX = Params: { FGameplayAbilitySpecHandle Handle(+0,4B); bool InputPressed(+8,1B);
; FPredictionKey PredKey(+10,0x18B); }
;
; Stack: push rbp + push rbx + push rsi = 3 pushes; sub 32 -> 0
PEHOOK_ServerTryActivateAbility PROC
push rbp
push rbx
push rsi
sub rsp, 32
mov rbx, rcx ; ASC
mov rsi, rdx ; Params
; Abilities_TryActivateAbility(ASC, Handle*, InputPressed, PredKey*, null)
mov rcx, rbx
mov rdx, rsi ; &Params->Handle (at offset 0, same address as Params)
movzx r8d, BYTE PTR [rsi + 8] ; InputPressed (bool at +8)
lea r9, [rsi + 10h] ; &Params->PredictionKey (at +0x10)
call Abilities_TryActivateAbility
add rsp, 32
pop rsi
pop rbx
pop rbp
xor al, al
ret
PEHOOK_ServerTryActivateAbility ENDP
; PEHOOK_ServerTryActivateAbilityWithEventData - includes FGameplayEventData
; ------------------------------------------------------------
; Same as above but Params also has FGameplayEventData after PredKey (at +0x28).
; We forward the EventData pointer as the 5th arg.
;
; Stack: push rbp + push rbx + push rsi = 3 pushes; sub 32 -> 0
PEHOOK_ServerTryActivateAbilityWithEventData PROC
push rbp
push rbx
push rsi
sub rsp, 32
mov rbx, rcx
mov rsi, rdx
mov rcx, rbx
mov rdx, rsi ; same address as Params base
movzx r8d, BYTE PTR [rsi + 8]
lea r9, [rsi + 10h]
; 5th arg: &EventData at [rsi + 28h]
lea rax, [rsi + 28h]
mov QWORD PTR [rsp + 32], rax
call Abilities_TryActivateAbility
add rsp, 32
pop rsi
pop rbx
pop rbp
xor al, al
ret
PEHOOK_ServerTryActivateAbilityWithEventData ENDP
; PEHOOK_ServerAbilityRPCBatch - parse batch and call TryActivate per entry
; Stack: push rbp + push rbx + push rsi = 3 pushes; sub 32 -> 0
PEHOOK_ServerAbilityRPCBatch PROC
push rbp
push rbx
push rsi
sub rsp, 32
mov rbx, rcx ; ASC
mov rsi, rdx ; Params (FServerAbilityRPCBatch)
; BatchInfo[0].AbilitySpecHandle at [Params+0], PredKey at [Params+8]
; InputPressed flag at batch entry - assume true (batch implies activation
mov rcx, rbx
lea rdx, [rsi]
mov r8d, 1 ; InputPressed = true
lea r9, [rsi + 8]
call Abilities_TryActivateAbility
add rsp, 32
pop rsi
pop rbx
pop rbp
xor al, al
ret
PEHOOK_ServerAbilityRPCBatch ENDP
; PEHOOK_ClientOnPawnDied - handle death: update state, check win condition
; RCX = AFortPlayerController* (dead player's PC)
; RDX = Params: { FFortPlayerDeathReport DeathReport; } ([RDX+0..+0x4F])
; Stack: push rbp,rbx,rsi,rdi,r12,r13 = 6 pushes (RSP=8); sub 40 -> 0
PEHOOK_ClientOnPawnDied PROC
push rbp
push rbx
push rsi
push rdi
push r12
push r13
sub rsp, 40 ; 6 pushes: RSP=8; sub40 -> 0
mov rbx, rcx ; PC (dead player's controller)
mov r12, rdx ; Params = &FFortPlayerDeathReport
; PlayerState = PC->PlayerState
mov rsi, QWORD PTR [rbx + ACONTROLLER_PlayerState]
test rsi, rsi
jz @@done
; Copy FDeathInfo from DeathReport into PlayerState->DeathInfo
; FDeathInfo at AFPSA_DeathInfo offset on PlayerState (0xF40)
; DeathReport.KillerPlayerState at [Params+0x10] -> DeathInfo.FinisherOrDowner
; DeathReport.bDBNO flags etc.
mov rax, QWORD PTR [r12 + 010h] ; KillerPlayerState
mov QWORD PTR [rsi + AFPSA_DeathInfo + 000h], rax ; FinisherOrDowner
; DeathCause: call Game_GetDeathCause(DeathReport) to compute
mov rcx, r12
call Game_GetDeathCause ; returns DWORD in EAX
mov BYTE PTR [rsi + AFPSA_DeathInfo + 009h], al ; DeathCause byte
; OnRep_DeathInfo - notify clients of death info update
mov rax, QWORD PTR [pFn_OnRep_DeathInfo]
test rax, rax
jnz @@have_deathinf_rep
lea rcx, [szFn_OnRep_DeathInfo]
call SDK_FindObject
mov QWORD PTR [pFn_OnRep_DeathInfo], rax
@@have_deathinf_rep:
test rax, rax
jz @@skip_deathinf_rep
mov rcx, rsi ; PlayerState
mov rdx, rax
xor r8d, r8d
call QWORD PTR [ProcessEvent]
@@skip_deathinf_rep:
; Get GameState: World->GameState
call SDK_GetWorld
test rax, rax
jz @@done
mov rdi, QWORD PTR [rax + UWORLD_GameState] ; rdi = GameState*
test rdi, rdi
jz @@done
; GameState->PlayersLeft--
dec DWORD PTR [rdi + AFGSA_PlayersLeft]
; OnRep_PlayersLeft on GameState
mov rax, QWORD PTR [pFn_OnRep_PlayersLeft]
test rax, rax
jnz @@have_pleft_rep
lea rcx, [szFn_OnRep_PlayersLeft]
call SDK_FindObject
mov QWORD PTR [pFn_OnRep_PlayersLeft], rax
@@have_pleft_rep:
test rax, rax
jz @@call_on_death
mov rcx, rdi
mov rdx, rax
xor r8d, r8d
call QWORD PTR [ProcessEvent]
@@call_on_death:
; GameModeBase_OnPlayerDeath(PC) - handles respawn logic per game mode
mov rcx, rbx
call GameModeBase_OnPlayerDeath
@@done:
add rsp, 40
pop r13
pop r12
pop rdi
pop rsi
pop rbx
pop rbp
xor al, al
ret
PEHOOK_ClientOnPawnDied ENDP
; PEHOOK_ServerAttemptAircraftJump - jump from battle bus
; RCX = APlayerPawn_Athena_C* (pawn on aircraft)
; RDX = Params: { FRotator ClientRotation; } ([RDX+0])
;
; Calls GameModeBase_InitPawn to initialise the pawn for landing.
; Stack: push rbp + push rbx + push rsi = 3 pushes; sub 32 -> 0
PEHOOK_ServerAttemptAircraftJump PROC
push rbp
push rbx
push rsi
sub rsp, 32
mov rbx, rcx ; Pawn
mov rsi, rdx ; Params
; Get controller from pawn
mov rax, QWORD PTR [rbx + APAWN_Controller]
test rax, rax
jz @@done
; GameModeBase_InitPawn(PC) - sets up pawn for first touch-down
mov rcx, rax
call GameModeBase_InitPawn
@@done:
add rsp, 32
pop rsi
pop rbx
pop rbp
xor al, al
ret
PEHOOK_ServerAttemptAircraftJump ENDP
; PEHOOK_OnAircraftExitedDropZone - auto-jump remaining passengers
; RCX = AFortAthenaAircraft* (the aircraft, unused)
; Iterates HostBeacon->NetDriver->ClientConnections and calls
; GameModeBase_InitPawn for each connected PlayerController.
;
; Stack: 3 pushes (24) + sub 20h (32) = 56; 8-56=0
PEHOOK_OnAircraftExitedDropZone PROC
push rbx
push rbp
push rsi
sub rsp, 20h
; HostBeacon->NetDriver (AOBH_NetDriver = 0x220)
mov rbx, QWORD PTR [HostBeacon]
test rbx, rbx
jz @OAEDZ_done
mov rbx, QWORD PTR [rbx + AOBH_NetDriver]
test rbx, rbx
jz @OAEDZ_done
; ClientConnections TArray at UNetDriver+0x80
mov rsi, QWORD PTR [rbx + 080h] ; Connections.Data
test rsi, rsi
jz @OAEDZ_done
mov ebp, DWORD PTR [rbx + 088h] ; Connections.Num
test ebp, ebp
jz @OAEDZ_done
xor ebx, ebx ; i = 0
@OAEDZ_loop:
cmp ebx, ebp
jge @OAEDZ_done
mov rcx, QWORD PTR [rsi + rbx*8] ; Connections[i] (UNetConnection*)
test rcx, rcx
jz @OAEDZ_next
mov rcx, QWORD PTR [rcx + 030h] ; PlayerController
test rcx, rcx
jz @OAEDZ_next
call GameModeBase_InitPawn ; (PC in RCX)
@OAEDZ_next:
inc ebx
jmp @OAEDZ_loop
@OAEDZ_done:
add rsp, 20h
pop rsi
pop rbp
pop rbx
xor al, al
ret
PEHOOK_OnAircraftExitedDropZone ENDP
; PEHOOK_ServerCreateBuildingActor
; RCX = AFortPlayerControllerAthena* (PC)
; RDX = AFortPlayerController_ServerCreateBuildingActor_Params*
; +0x00: FBuildingClassData { BuildingClass(8), PreviousBuildingLevel(4), UpgradeLevel(4) }
; +0x10: FVector BuildLoc (12 bytes)
; +0x1C: FRotator BuildRot (12 bytes)
; +0x28: bMirrored (BYTE)
;
; Spawns building, sets team index, fires InitializeKismetSpawnedBuildingActor.
;
; Stack: 4 pushes (32) + sub 48h (72) = 104; 8-104=0
; Frame: [+00..+1F] shadow; [+20..+2F] InitKismet params; [+30..+47] scratch
PEHOOK_ServerCreateBuildingActor PROC
push rbx
push rbp
push rsi
push rdi
sub rsp, 48h
mov rdi, rcx ; RDI = PC
mov rsi, rdx ; RSI = Params
test rdi, rdi
jz @SCBA_done
test rsi, rsi
jz @SCBA_done
; BuildingClass = Params->BuildingClassData.BuildingClass
mov rbx, QWORD PTR [rsi]
test rbx, rbx
jz @SCBA_done
; SpawnActor(BuildingClass, &BuildLoc, PC)
mov rcx, rbx
lea rdx, [rsi + 010h] ; &BuildLoc
mov r8, rdi
call Spawners_SpawnActor
mov rbp, rax ; RBP = BuildingActor
test rbp, rbp
jz @SCBA_done
; DynamicBuildingPlacementType = 2 (DestroyAnythingThatCollides)
mov BYTE PTR [rbp + 04B0h], 2
; BuildingActor->Team = PC->PlayerState->TeamIndex
mov rax, QWORD PTR [rdi + 0248h] ; PlayerState
test rax, rax
jz @SCBA_init
movzx ecx, BYTE PTR [rax + 0F60h] ; TeamIndex
mov BYTE PTR [rbp + 04C5h], cl
@SCBA_init:
; InitializeKismetSpawnedBuildingActor(BuildingActor, BuildingOwner=self, PC)
; Params: BuildingOwner(8) + SpawningController(8) = 0x10 at [rsp+20h]
mov rax, QWORD PTR [pFn_InitKismetBuildingActor]
test rax, rax
jnz @SCBA_have_init
lea rcx, [szFn_InitKismetBuildingActor]
call SDK_FindObject
mov QWORD PTR [pFn_InitKismetBuildingActor], rax
@SCBA_have_init:
test rax, rax
jz @SCBA_done
mov QWORD PTR [rsp + 20h], rbp ; BuildingOwner = BuildingActor
mov QWORD PTR [rsp + 28h], rdi ; SpawningController = PC
mov rcx, rbp
mov rdx, rax
lea r8, [rsp + 20h]
call QWORD PTR [ProcessEvent]
@SCBA_done:
add rsp, 48h
pop rdi
pop rsi
pop rbp
pop rbx
xor al, al
ret
PEHOOK_ServerCreateBuildingActor ENDP
; PEHOOK_ServerBeginEditingBuildingActor
; RCX = AFortPlayerControllerAthena* (PC)
; RDX = Params: { ABuildingSMActor* BuildingActorToEdit }
;
; Equips the edit tool (primary slot 0), then wires EditActor and EditingPlayer.
;
; Stack: 5 pushes (40) + sub 60h (96) = 136; 8-136=0
; Frame: [+00..+1F] shadow; [+20..+2F] ProcessEvent params scratch
; [+30..+37] BuildingActorToEdit; [+38..+3F] spare; [+40..+57] FGuid copy
PEHOOK_ServerBeginEditingBuildingActor PROC
push rbx
push rbp
push rsi
push rdi
push r12
sub rsp, 60h
mov r12, rcx ; R12 = PC
; BuildingActorToEdit = Params[+0]
mov rax, QWORD PTR [rdx]
mov QWORD PTR [rsp + 30h], rax ; save BuildingActorToEdit
test r12, r12
jz @SBEBA_done
test rax, rax
jz @SBEBA_done
; Pawn = PC->Pawn
mov rbx, QWORD PTR [r12 + ACONTROLLER_Pawn]
test rbx, rbx
jz @SBEBA_done
; GetEntryInSlot(PC, Slot=0, Item=0, Bars=Primary=0)
mov rcx, r12
xor edx, edx
xor r8d, r8d
xor r9d, r9d
call Inventory_GetEntryInSlot
test rax, rax
jz @SBEBA_done
mov rbp, rax ; RBP = &FFortItemEntry for edit tool
; ItemDef = entry[+0x18]
mov rdi, QWORD PTR [rbp + 018h]
test rdi, rdi
jz @SBEBA_done
; Copy FGuid from entry[+0x50]
mov rax, QWORD PTR [rbp + 050h]
mov QWORD PTR [rsp + 40h], rax
mov rax, QWORD PTR [rbp + 058h]
mov QWORD PTR [rsp + 48h], rax
; EquipWeaponDefinition(Pawn, ItemDef, &Guid)
mov rcx, rbx
mov rdx, rdi
lea r8, [rsp + 40h]
call Inventory_EquipWeaponDefinition
; EditTool = Pawn->CurrentWeapon
mov rsi, QWORD PTR [rbx + 07D0h] ; AFortPawn::CurrentWeapon
test rsi, rsi
jz @SBEBA_done
; EditTool->EditActor = BuildingActorToEdit
mov rax, QWORD PTR [rsp + 30h]
mov QWORD PTR [rsi + 0AB0h], rax ; AFortWeap_EditingTool::EditActor
; OnRep_EditActor(EditTool)
mov rax, QWORD PTR [pFn_OnRep_EditActor]
test rax, rax
jnz @SBEBA_have_ea
lea rcx, [szFn_OnRep_EditActor]
call SDK_FindObject
mov QWORD PTR [pFn_OnRep_EditActor], rax
@SBEBA_have_ea:
test rax, rax
jz @SBEBA_editing_player
xor r8d, r8d
mov QWORD PTR [rsp + 20h], r8
mov rcx, rsi
mov rdx, rax
lea r8, [rsp + 20h]
call QWORD PTR [ProcessEvent]
@SBEBA_editing_player:
; BuildingActorToEdit->EditingPlayer = Pawn->PlayerState
mov rcx, QWORD PTR [rsp + 30h] ; BuildingActorToEdit
test rcx, rcx
jz @SBEBA_done
mov rax, QWORD PTR [rbx + APAWN_PlayerState]
mov QWORD PTR [rcx + 0C70h], rax ; ABuildingSMActor::EditingPlayer
; OnRep_EditingPlayer(BuildingActorToEdit)
mov rax, QWORD PTR [pFn_OnRep_EditingPlayer]
test rax, rax
jnz @SBEBA_have_ep
lea rcx, [szFn_OnRep_EditingPlayer]
call SDK_FindObject
mov QWORD PTR [pFn_OnRep_EditingPlayer], rax
@SBEBA_have_ep:
test rax, rax
jz @SBEBA_done
mov rcx, QWORD PTR [rsp + 30h]
xor r8d, r8d
mov QWORD PTR [rsp + 20h], r8
mov rdx, rax
lea r8, [rsp + 20h]
call QWORD PTR [ProcessEvent]
@SBEBA_done:
add rsp, 60h
pop r12
pop rdi
pop rsi
pop rbp
pop rbx
xor al, al
ret
PEHOOK_ServerBeginEditingBuildingActor ENDP
; PEHOOK_ServerEditBuildingActor
; RCX = AFortPlayerControllerAthena* (PC)
; RDX = Params:
; +0x00: ABuildingSMActor* BuildingActorToEdit
; +0x08: UClass* NewBuildingClass
; +0x10: int RotationIterations
; +0x14: bool bMirrored
;
; Gets old building location, calls SilentDie, spawns new building.
; (Rotation offset correction is omitted - walls use simple in-place respawn.)
;
; Stack: 5 pushes (40) + sub 70h (112) = 152; 8-152=0
; Frame: [+00..+1F] shadow; [+20..+2F] K2_GetActorLocation RetValue (FVector=12B)
; [+30..+3F] scratch; [+40..+47] saved OldBuilding; [+48..+4F] saved NewClass
; [+50..+57] saved PC; [+58..+5F] spare
PEHOOK_ServerEditBuildingActor PROC
push rbx
push rbp
push rsi
push rdi
push r12
sub rsp, 70h
mov r12, rcx ; R12 = PC
mov rbx, QWORD PTR [rdx] ; RBX = BuildingActorToEdit
mov rdi, QWORD PTR [rdx + 008h] ; RDI = NewBuildingClass
test r12, r12
jz @SEBA_done
test rbx, rbx
jz @SEBA_done
test rdi, rdi
jz @SEBA_done
; K2_GetActorLocation(BuildingActor) -> RetValue FVector at [rsp+20h]
mov rax, QWORD PTR [pFn_K2_GetActorLocation]
test rax, rax
jnz @SEBA_have_loc
lea rcx, [szFn_K2_GetActorLocation]
call SDK_FindObject
mov QWORD PTR [pFn_K2_GetActorLocation], rax
@SEBA_have_loc:
xor esi, esi
test rax, rax
jz @SEBA_no_loc
mov DWORD PTR [rsp + 20h], esi ; X = 0
mov DWORD PTR [rsp + 24h], esi ; Y = 0
mov DWORD PTR [rsp + 28h], esi ; Z = 0
mov rcx, rbx
mov rdx, rax
lea r8, [rsp + 20h]
call QWORD PTR [ProcessEvent]
@SEBA_no_loc:
; SilentDie(BuildingActor)
mov rax, QWORD PTR [pFn_SilentDie]
test rax, rax
jnz @SEBA_have_sd
lea rcx, [szFn_SilentDie]
call SDK_FindObject
mov QWORD PTR [pFn_SilentDie], rax
@SEBA_have_sd:
test rax, rax
jz @SEBA_spawn
xor esi, esi
mov QWORD PTR [rsp + 30h], rsi
mov rcx, rbx
mov rdx, rax
lea r8, [rsp + 30h]
call QWORD PTR [ProcessEvent]
@SEBA_spawn:
; SpawnActor(NewBuildingClass, &Location, PC)
mov rcx, rdi ; NewBuildingClass
lea rdx, [rsp + 20h] ; &FVector (location)
mov r8, r12 ; PC
call Spawners_SpawnActor
mov rbp, rax ; RBP = NewBuildingActor
test rbp, rbp
jz @SEBA_done
; DynamicBuildingPlacementType = 2
mov BYTE PTR [rbp + 04B0h], 2
; NewBuilding->Team = PC->PlayerState->TeamIndex
mov rax, QWORD PTR [r12 + 0248h]
test rax, rax
jz @SEBA_init
movzx ecx, BYTE PTR [rax + 0F60h]
mov BYTE PTR [rbp + 04C5h], cl
@SEBA_init:
; InitializeKismetSpawnedBuildingActor
mov rax, QWORD PTR [pFn_InitKismetBuildingActor]
test rax, rax
jnz @SEBA_have_init
lea rcx, [szFn_InitKismetBuildingActor]
call SDK_FindObject
mov QWORD PTR [pFn_InitKismetBuildingActor], rax
@SEBA_have_init:
test rax, rax
jz @SEBA_done
mov QWORD PTR [rsp + 38h], rbp ; BuildingOwner = NewBuilding
mov QWORD PTR [rsp + 40h], r12 ; SpawningController = PC
mov rcx, rbp
mov rdx, rax
lea r8, [rsp + 38h]
call QWORD PTR [ProcessEvent]
@SEBA_done:
add rsp, 70h
pop r12
pop rdi
pop rsi
pop rbp
pop rbx
xor al, al
ret
PEHOOK_ServerEditBuildingActor ENDP
; PEHOOK_ReadyToStartMatch - trigger Server_Initialize once
; RCX = AFortGameModeAthena* (Object), RDX = void* Params (empty)
; Server_Initialize is internally guarded by bListening so it only
; runs once even if ReadyToStartMatch fires more than once.
;
; Stack: entry RSP=8; sub40(=8) -> 0; [0..31]=shadow
PEHOOK_ReadyToStartMatch PROC
sub rsp, 40
call Server_Initialize
add rsp, 40
xor al, al
ret
PEHOOK_ReadyToStartMatch ENDP
; Server_Initialize - set up full listen-server infrastructure
; Called from Hooks_TickFlush each tick until bListening is set,
; and from PEHOOK_ReadyToStartMatch when ReadyToStartMatch fires.
; No arguments; no return value.
; Returns immediately (bListening=0, no state change) if the world is
; not yet in Athena context - Hooks_TickFlush will retry next tick.
;
; Stack: 7 pushes (entry RSP=8; 7×8=56=8; 8-8=0) + sub 0B0h(176,=0) -> 0
; [rsp+0..31] = shadow
; [rsp+32..39] = arg5 slot (FString* Error for InitListen)
; [rsp+40..55] = Error FString (16 bytes: Data*, Count, Max)
; [rsp+56..167] = FURL local struct (112 bytes)
;
; Registers:
; rbx = HostBeacon->NetDriver (0 if beacon spawn/init failed)
; r13 = HostBeacon
; r14 = ReplicationDriver (@@resolve_repdriver block)
Server_Initialize PROC
push rbp
push rbx
push rsi
push rdi
push r12
push r13
push r14
sub rsp, 0B0h ; 176 bytes; RSP=0
; Guard: already listening?
movzx eax, BYTE PTR [bListening]
test al, al
jnz @@done
LOG_DBG szSI_Dbg_Entry ; debug-only: fires every tick until listening
; Type check: AuthorityGameMode must be (a subclass of) AFortGameModeAthena.
; If not (lobby context), return WITHOUT setting bListening=1 so
; Hooks_TickFlush retries on the next tick.
call SDK_GetWorld
test rax, rax
jz @@wait_no_world
mov r12, QWORD PTR [rax + UWORLD_AuthorityGameMode]
test r12, r12
jz @@wait_no_gamemode
; Lazily cache AFortGameModeAthena class pointer
mov rax, QWORD PTR [pClass_FortGameModeAthena]
test rax, rax
jnz @@have_athena_class
lea rcx, szClass_FortGameModeAthena
call SDK_FindClass
mov QWORD PTR [pClass_FortGameModeAthena], rax
@@have_athena_class:
test rax, rax
jz @@wait_no_class
; UObject_IsA(AuthorityGameMode, AFortGameModeAthena)
mov rcx, r12 ; obj = AuthorityGameMode
mov rdx, rax ; cls = pClass_FortGameModeAthena
call UObject_IsA
test al, al
jz @@wait_not_athena
; Verified Athena context - proceed with full server setup
lea rcx, szSI_AthenaOk
call Logger_LogInfo
; Notify game logic
call Game_OnReadyToStartMatch
mov BYTE PTR [bListening], 1
; rbx = NetDriver pointer (0 until beacon spawn succeeds)
xor ebx, ebx
; Spawn AFortOnlineBeaconHost
mov rax, QWORD PTR [pClass_FortOnlineBeaconHost]
test rax, rax
jnz @@have_beacon_class
lea rcx, szClass_FortOnlineBeaconHost
call SDK_FindClass
mov QWORD PTR [pClass_FortOnlineBeaconHost], rax
@@have_beacon_class:
test rax, rax
jz @@beacon_spawn_skip
call SDK_GetWorld
test rax, rax
jz @@beacon_spawn_skip
mov rcx, QWORD PTR [pClass_FortOnlineBeaconHost]
xor edx, edx
xor r8d, r8d
call Spawners_SpawnActor
test rax, rax
jz @@beacon_spawn_failed
mov r13, rax
mov QWORD PTR [HostBeacon], r13
lea rcx, szSI_BeaconSpawned
call Logger_LogInfo
; Set ListenPort = 7776
mov DWORD PTR [r13 + AOBH_ListenPort], LISTEN_BEACON_PORT
; InitHost(HostBeacon)
mov rcx, r13
call QWORD PTR [Native_OnlineBeaconHost_InitHost]
; Get HostBeacon->NetDriver (AOBH_NetDriver = 0x220)
mov rbx, QWORD PTR [r13 + AOBH_NetDriver]
test rbx, rbx
jz @@beacon_no_netdriver
lea rcx, szSI_NetDriverOk
call Logger_LogInfo
; NetDriver->World = GetWorld() (UNETDRIVER_World = 0x0B8h)
call SDK_GetWorld
test rax, rax
jz @@net_setup
mov QWORD PTR [rbx + UNETDRIVER_World], rax
jmp @@net_setup
@@beacon_no_netdriver:
lea rcx, szSI_NoNetDriver
call Logger_LogWarn
jmp @@net_setup
@@beacon_spawn_failed:
lea rcx, szSI_BeaconFail
call Logger_LogWarn
@@beacon_spawn_skip:
@@net_setup:
; Zero-init FURL at [rsp+56], set Port=7777
lea rcx, [rsp + 56]
mov edx, 112 ; Length = sizeof(FURL) = 0x70 bytes
call RtlZeroMemory
mov DWORD PTR [rsp + 56 + FURL_Port], LISTEN_GAME_PORT
; Call InitListen(NetDriver, World, &FURL, true, &Error)
; Signature: bool(*)(UObject* Driver, void* InNotify, FURL& LocalURL,
; bool bReuseAddressAndPort, FString& Error)
test rbx, rbx
jz @@post_init_listen
lea rcx, szSI_InitListen
call Logger_LogInfo
; Zero Error FString at [rsp+40]
xor eax, eax
mov QWORD PTR [rsp + 40], rax ; Error.Data = null
mov QWORD PTR [rsp + 48], rax ; Error.Count/Max = 0
; arg5 (FString& Error) at [rsp+32] = pointer to Error FString
lea rax, [rsp + 40]
mov QWORD PTR [rsp + 32], rax
mov rcx, rbx ; arg1: NetDriver
call SDK_GetWorld
mov rdx, rax ; arg2: InNotify = World
lea r8, [rsp + 56] ; arg3: FURL&
mov r9d, 1 ; arg4: bReuseAddressAndPort = true
call QWORD PTR [Native_NetDriver_InitListen]
@@post_init_listen:
; Wire World->NetDriver and LevelCollections[0..1].NetDriver
test rbx, rbx
jz @@resolve_repdriver
call SDK_GetWorld
test rax, rax
jz @@resolve_repdriver
mov rsi, rax ; rsi = World
; World->NetDriver = NetDriver
mov QWORD PTR [rsi + UWORLD_NetDriver], rbx
; LevelCollections.Data at [World+0x170], Num at [World+0x178]
; FLevelCollection::NetDriver at +0x10, sizeof = 0x80
mov rdi, QWORD PTR [rsi + UWORLD_LevelCollections] ; Data ptr
test rdi, rdi
jz @@skip_level_collections
cmp DWORD PTR [rsi + UWORLD_LevelCollections + 8], 0 ; Num > 0?
jle @@skip_level_collections
mov QWORD PTR [rdi + FLEVELLCOLL_NetDriver], rbx ; [0].NetDriver
cmp DWORD PTR [rsi + UWORLD_LevelCollections + 8], 1 ; Num > 1?
jle @@skip_level_collections
mov QWORD PTR [rdi + FLEVELLCOLL_SIZEOF + FLEVELLCOLL_NetDriver], rbx ; [1].NetDriver
@@skip_level_collections:
lea rcx, szSI_WorldWired
call Logger_LogInfo
; World->AuthorityGameMode->GameSession->MaxPlayers = 100
mov rax, QWORD PTR [rsi + UWORLD_AuthorityGameMode]
test rax, rax
jz @@resolve_repdriver
mov rax, QWORD PTR [rax + AGAMEMODE_GameSession]
test rax, rax
jz @@resolve_repdriver
mov DWORD PTR [rax + AGAMESESSION_MaxPlayers], MAXPLAYERS
@@resolve_repdriver:
; Resolve ServerReplicateActors from HostBeacon->NetDriver->ReplicationDriver
; vtable slot 0x53 = VTABLE_ServerReplicateActors
; World->NetDriver now points to HostBeacon->NetDriver (set above).
call SDK_GetWorld
test rax, rax
jz @@pause_beacon
mov rax, QWORD PTR [rax + UWORLD_NetDriver]
test rax, rax
jz @@pause_beacon
mov rax, QWORD PTR [rax + UNETDRIVER_ReplDriver]
test rax, rax
jz @@pause_beacon
mov r14, rax
mov rax, QWORD PTR [r14]
mov rax, QWORD PTR [rax + VTABLE_ServerReplicateActors]
mov QWORD PTR [Native_ReplicationDriver_ServerReplicateActors], rax
@@pause_beacon:
; PauseBeaconRequests(false), mark listening
mov rax, QWORD PTR [HostBeacon]
test rax, rax
jz @@set_listening
mov rcx, rax
xor edx, edx
call QWORD PTR [Native_OnlineBeacon_PauseBeaconRequests]
@@set_listening:
lea rcx, szSI_Listening
call Logger_LogInfo
mov BYTE PTR [bListening], 1
jmp @@done
; Early-exit paths
@@wait_no_world:
LOG_DBG szSI_Dbg_NoWorld
jmp @@done
@@wait_no_gamemode:
LOG_DBG szSI_Dbg_NoGameMode
jmp @@done
@@wait_no_class:
LOG_DBG szSI_Dbg_NoClass
jmp @@done
@@wait_not_athena:
LOG_DBG szSI_Dbg_NotAthena
@@done:
add rsp, 0B0h
pop r14
pop r13
pop r12
pop rdi
pop rsi
pop rbx
pop rbp
ret
Server_Initialize ENDP
; UFunctionHooks_Initialize
; Resolves all 27 UFunction* pointers and fills the ToHook + ToCall arrays.
; Called once from UFunctionHooks_Initialize (triggered before Detours in Main).
;
; Uses rbp as the insertion index (callee-saved, pushed/popped correctly).
; Uses rsi = &_ToHook_Storage[0]
; rdi = &_ToCall_Storage[0]
;
; Stack: push rbp,rbx,rsi,rdi,r12 = 5 pushes (RSP=8); sub 40(=8) -> 0
; Helper macro-equivalent - inline for each registration:
; REGISTER rcx=szFnName, handler_label
; lea rcx, szFn_X
; call SDK_FindObject
; test rax,rax / jz skip
; mov [rsi + rbp*8], rax
; lea rax, HANDLER
; mov [rdi + rbp*8], rax
; inc ebp
; skip:
UFunctionHooks_Initialize PROC
push rbp
push rbx
push rsi
push rdi
push r12
sub rsp, 48 ; 5 pushes: entry RSP=8 -> after pushes RSP=0; sub48(=0) -> 0
LOG_DBG szUFDbg_Start
; Set public pointers to internal storage
lea rax, _ToHook_Storage
mov QWORD PTR [UFunctionHooks_ToHook_Data], rax
lea rax, _ToCall_Storage
mov QWORD PTR [UFunctionHooks_ToCall_Data], rax
lea rsi, _ToHook_Storage ; hook fn ptr array base
lea rdi, _ToCall_Storage ; handler fn ptr array base
xor ebp, ebp ; index = 0
; ServerTryActivateAbility
lea rcx, szFn_ServerTryActivateAbility
call SDK_FindObject
test rax, rax
jz @@r1
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ServerTryActivateAbility
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r1:
; ServerTryActivateAbilityWithEventData
lea rcx, szFn_ServerTryActivateWithEventData
call SDK_FindObject
test rax, rax
jz @@r2
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ServerTryActivateAbilityWithEventData
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r2:
; ServerAbilityRPCBatch
lea rcx, szFn_ServerAbilityRPCBatch
call SDK_FindObject
test rax, rax
jz @@r3
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ServerAbilityRPCBatch
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r3:
; ServerHandlePickup
lea rcx, szFn_ServerHandlePickup
call SDK_FindObject
test rax, rax
jz @@r4
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ServerHandlePickup
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r4:
; CheatScript
lea rcx, szFn_CheatScript
call SDK_FindObject
test rax, rax
jz @@r5
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_CheatScript
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r5:
; OnDeathServer
lea rcx, szFn_OnDeathServer
call SDK_FindObject
test rax, rax
jz @@r6
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_OnDeathServer
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r6:
; ServerCreateBuildingActor
lea rcx, szFn_ServerCreateBuildingActor
call SDK_FindObject
test rax, rax
jz @@r7
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ServerCreateBuildingActor
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r7:
; ServerBeginEditingBuildingActor
lea rcx, szFn_ServerBeginEditingBuilding
call SDK_FindObject
test rax, rax
jz @@r8
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ServerBeginEditingBuildingActor
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r8:
; ServerSpawnDeco
lea rcx, szFn_ServerSpawnDeco
call SDK_FindObject
test rax, rax
jz @@r9
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ServerSpawnDeco
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r9:
; ServerEditBuildingActor
lea rcx, szFn_ServerEditBuildingActor
call SDK_FindObject
test rax, rax
jz @@r10
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ServerEditBuildingActor
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r10:
; ClientOnPawnDied
lea rcx, szFn_ClientOnPawnDied
call SDK_FindObject
test rax, rax
jz @@r11
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ClientOnPawnDied
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r11:
; ServerEndEditingBuildingActor
lea rcx, szFn_ServerEndEditingBuilding
call SDK_FindObject
test rax, rax
jz @@r12
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ServerEndEditingBuildingActor
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r12:
; ServerRepairBuildingActor
lea rcx, szFn_ServerRepairBuildingActor
call SDK_FindObject
test rax, rax
jz @@r13
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ServerRepairBuildingActor
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r13:
; ServerAttemptAircraftJump
lea rcx, szFn_ServerAttemptAircraftJump
call SDK_FindObject
test rax, rax
jz @@r14
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ServerAttemptAircraftJump
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r14:
; ServerReviveFromDBNO
lea rcx, szFn_ServerReviveFromDBNO
call SDK_FindObject
test rax, rax
jz @@r15
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ServerReviveFromDBNO
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r15:
; ServerAttemptInteract
lea rcx, szFn_ServerAttemptInteract
call SDK_FindObject
test rax, rax
jz @@r16
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ServerAttemptInteract
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r16:
; ServerPlayEmoteItem
lea rcx, szFn_ServerPlayEmoteItem
call SDK_FindObject
test rax, rax
jz @@r17
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ServerPlayEmoteItem
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r17:
; ServerAttemptInventoryDrop
lea rcx, szFn_ServerAttemptInventoryDrop
call SDK_FindObject
test rax, rax
jz @@r18
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ServerAttemptInventoryDrop
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r18:
; OnSpawnOutAnimEnded
lea rcx, szFn_OnSpawnOutAnimEnded
call SDK_FindObject
test rax, rax
jz @@r19
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_OnSpawnOutAnimEnded
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r19:
; ServerExecuteInventoryItem
lea rcx, szFn_ServerExecuteInventoryItem
call SDK_FindObject
test rax, rax
jz @@r20
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ServerExecuteInventoryItem
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r20:
; ServerReturnToMainMenu
lea rcx, szFn_ServerReturnToMainMenu
call SDK_FindObject
test rax, rax
jz @@r21
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ServerReturnToMainMenu
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r21:
; ServerLoadingScreenDropped
lea rcx, szFn_ServerLoadingScreenDropped
call SDK_FindObject
test rax, rax
jz @@r22
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ServerLoadingScreenDropped
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r22:
; ServerChoosePart
lea rcx, szFn_ServerChoosePart
call SDK_FindObject
test rax, rax
jz @@r23
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ServerChoosePart
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r23:
; ---- 24 (renumbered). OnAircraftExitedDropZone ----
; NOTE: ReadyToStartMatch removed - server init now called directly
; via Server_Initialize from raider.asm Main after Detours commit.
lea rcx, szFn_OnAircraftExitedDropZone
call SDK_FindObject
test rax, rax
jz @@r25
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_OnAircraftExitedDropZone
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r25:
; ServerCheatAll
lea rcx, szFn_ServerCheatAll
call SDK_FindObject
test rax, rax
jz @@r26
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ServerCheatAll
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r26:
; Logout
lea rcx, szFn_Logout
call SDK_FindObject
test rax, rax
jz @@r27
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_Logout
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r27:
; ReadyToStartMatch -> Server_Initialize
lea rcx, szFn_ReadyToStartMatch
call SDK_FindObject
test rax, rax
jz @@r28
mov QWORD PTR [rsi + rbp * 8], rax
lea rax, PEHOOK_ReadyToStartMatch
mov QWORD PTR [rdi + rbp * 8], rax
inc ebp
@@r28:
; Store final count
mov DWORD PTR [UFunctionHooks_ToHook_Num], ebp
; Also lazily prime the ForceNetUpdate UFunction pointer
lea rcx, szFn_ForceNetUpdate
call SDK_FindObject
mov QWORD PTR [pFn_ForceNetUpdate], rax
; Log registration count
lea rcx, szHookCount
mov edx, ebp
call Logger_LogInfoFmt
add rsp, 48
pop r12
pop rdi
pop rsi
pop rbx
pop rbp
ret
UFunctionHooks_Initialize ENDP
END