From 1266ef369b9db40c521b51981f3218c64c8c9116 Mon Sep 17 00:00:00 2001 From: ApfelTeeSaft <91074565+ApfelTeeSaft@users.noreply.github.com> Date: Wed, 6 May 2026 11:43:46 +0200 Subject: [PATCH] implement stuff that was stubbed before --- gamemodes/gamemode_base.asm | 69 ++++++++++++- hooks.asm | 2 +- include/externs.inc | 6 +- logger.asm | 149 +++++++++++++++++++-------- logic/inventory.asm | 38 ++++++- logic/teams.asm | 61 ++++++++++++ ufunctionhooks.asm | 194 +++++++++++++++++++++++++++--------- zeroinput.asm | 2 +- 8 files changed, 418 insertions(+), 103 deletions(-) diff --git a/gamemodes/gamemode_base.asm b/gamemodes/gamemode_base.asm index c6b3561..a45b5d1 100644 --- a/gamemodes/gamemode_base.asm +++ b/gamemodes/gamemode_base.asm @@ -117,6 +117,8 @@ szFn_TriggerSpawnFX DB "Function BP_VictoryDrone.BP_VictoryDrone_C.TriggerPl szFn_RespawnAfterDeath DB "Function FortniteGame.FortPlayerControllerAthena.RespawnPlayerAfterDeath", 0 szFn_K2_TeleportTo DB "Function Engine.Actor.K2_TeleportTo", 0 szFn_SetMovementMode DB "Function Engine.CharacterMovementComponent.SetMovementMode", 0 +szFn_K2_DestroyActor DB "Function Engine.Actor.K2_DestroyActor", 0 +szFn_K2_GetActorLoc DB "Function Engine.Actor.K2_GetActorLocation", 0 ; ActivateSlot for LoadKilledPlayer szFn_ActivateSlot DB "Function FortniteGame.FortPlayerController.ActivateSlot", 0 @@ -165,6 +167,8 @@ pFn_RespawnAfterDeath QWORD ? pFn_K2_TeleportTo QWORD ? pFn_SetMovementMode QWORD ? pFn_ActivateSlot QWORD ? +pFn_K2_DestroyActor_GMB QWORD ? +pFn_K2_GetActorLoc_GMB QWORD ? ; Class caches pClass_PlayerPawn QWORD ? @@ -1106,6 +1110,28 @@ GameModeBase_Respawn PROC test esi, esi jz @@done_resp + ; Destroy current pawn (if exists) via K2_DestroyActor ProcessEvent + mov rdi, QWORD PTR [rbx + ACTRL_Pawn] ; RDI = old Pawn* (may be 0) + test rdi, rdi + jz @@no_old_pawn + + mov rax, QWORD PTR [pFn_K2_DestroyActor_GMB] + test rax, rax + jnz @@have_destroy + lea rcx, [szFn_K2_DestroyActor] + call SDK_FindObject + mov QWORD PTR [pFn_K2_DestroyActor_GMB], rax +@@have_destroy: + test rax, rax + jz @@no_old_pawn + ; K2_DestroyActor has no params - pass NULL params ptr + mov rcx, rdi + mov rdx, rax + xor r8d, r8d + call QWORD PTR [ProcessEvent] + +@@no_old_pawn: + ; Call InitPawn(PC) to spawn a new pawn mov rcx, rbx call GameModeBase_InitPawn @@ -1206,11 +1232,44 @@ GameModeBase_OnPlayerDeath PROC jz @@do_respawn ; Get Pawn location via K2_GetActorLocation ProcessEvent - ; K2_GetActorLocation params: {FVector ReturnValue (12 bytes)} - mov rdi, QWORD PTR [pFn_K2_TeleportTo] - mov DWORD PTR [rsp + 20h], 461C4000h ; X = 10000.0f - mov DWORD PTR [rsp + 24h], 461C4000h ; Y = 10000.0f - mov DWORD PTR [rsp + 28h], 464B4000h ; Z = 13000.0f (10000+3000) + ; K2_GetActorLocation params: {FVector ReturnValue (12 bytes)} at [rsp+20h] + mov rax, QWORD PTR [pFn_K2_GetActorLoc_GMB] + test rax, rax + jnz @@have_getloc + mov QWORD PTR [rsp + 30h], rbx ; save PC across FindObject + mov QWORD PTR [rsp + 38h], rbp ; save old Pawn + lea rcx, [szFn_K2_GetActorLoc] + call SDK_FindObject + mov QWORD PTR [pFn_K2_GetActorLoc_GMB], rax + mov rbx, QWORD PTR [rsp + 30h] ; restore PC + mov rbp, QWORD PTR [rsp + 38h] ; restore old Pawn +@@have_getloc: + test rax, rax + jz @@use_default_loc + ; Zero out the ReturnValue buffer then call ProcessEvent + xor r13d, r13d + mov DWORD PTR [rsp + 20h], r13d + mov DWORD PTR [rsp + 24h], r13d + mov DWORD PTR [rsp + 28h], r13d + mov rcx, rbp ; Pawn + mov rdx, rax + lea r8, [rsp + 20h] + call QWORD PTR [ProcessEvent] + ; Add 3000.0f to Z component (0x453B8000) + mov eax, DWORD PTR [rsp + 28h] ; current Z (float bits) + ; Use float addition: load to XMM, add 3000.0f, store back + movd xmm0, eax + mov eax, 453B8000h ; 3000.0f + movd xmm1, eax + addss xmm0, xmm1 + movd eax, xmm0 + mov DWORD PTR [rsp + 28h], eax + jmp @@do_respawn + +@@use_default_loc: + mov DWORD PTR [rsp + 20h], 449C4000h ; X = 1250.0f + mov DWORD PTR [rsp + 24h], 44E36000h ; Y = 1818.0f + mov DWORD PTR [rsp + 28h], 47BA8000h ; Z = 95000.0f @@do_respawn: ; LoadKilledPlayer: call GameModeBase_Respawn(PC, &RespawnPos) diff --git a/hooks.asm b/hooks.asm index 048112a..9167619 100644 --- a/hooks.asm +++ b/hooks.asm @@ -2,7 +2,7 @@ INCLUDE include\master.inc ; Local offset supplements APLAYER_CurrentNetSpeed EQU 038h -UNETCONN_InternalAck EQU 0C8h ; bool - estimated, verify against dump +UNETCONN_InternalAck EQU 0C8h ; bool AFPCA_OverriddenBackpackSize EQU 0720h .data? diff --git a/include/externs.inc b/include/externs.inc index 2d5b5dc..2f25510 100644 --- a/include/externs.inc +++ b/include/externs.inc @@ -103,7 +103,8 @@ EXTERNDEF Logger_Initialize :PROC ; () EXTERNDEF Logger_LogInfo :PROC ; (RCX=msg:PTR BYTE) EXTERNDEF Logger_LogWarn :PROC ; (RCX=msg:PTR BYTE) EXTERNDEF Logger_LogError :PROC ; (RCX=msg:PTR BYTE) -EXTERNDEF Logger_LogInfoFmt :PROC ; (RCX=fmt:PTR BYTE, RDX/R8/R9=args) - printf passthrough +EXTERNDEF Logger_LogFatal :PROC ; (RCX=msg:PTR BYTE) +EXTERNDEF Logger_LogInfoFmt :PROC ; (RCX=fmt:PTR BYTE, RDX/R8/R9/stack=args) - printf passthrough ; hooks.asm - All hook implementations EXTERNDEF Hooks_TickFlush :PROC @@ -252,7 +253,8 @@ EXTRN RtlZeroMemory :PROC EXTRN GetCursorPos :PROC ; user32: BOOL (POINT*) EXTRN ScreenToClient :PROC ; user32: BOOL (HWND, POINT*) EXTRN GetActiveWindow :PROC ; user32: HWND () -EXTRN FindWindowW :PROC ; user32: HWND (LPCWSTR lpClassName, LPCWSTR lpWindowName) +EXTRN FindWindowW :PROC ; user32: HWND (LPCWSTR, LPCWSTR) +EXTRN GetLocalTime :PROC ; kernel32: VOID (LPSYSTEMTIME) ; Microsoft Detours EXTRN DetourTransactionBegin :PROC diff --git a/logger.asm b/logger.asm index d30fe8c..7332abc 100644 --- a/logger.asm +++ b/logger.asm @@ -1,76 +1,139 @@ -INCLUDE include\master.inc +INCLUDE asm\include\master.inc -; CRT printf -EXTRN printf :PROC +EXTRN printf :PROC +EXTRN GetLocalTime :PROC ; VOID WINAPI GetLocalTime(LPSYSTEMTIME) + +; SYSTEMTIME field byte offsets +ST_wHour EQU 8 +ST_wMinute EQU 0Ah +ST_wSecond EQU 0Ch +ST_wMilliseconds EQU 0Eh .const -szFmtInfo DB "[INFO] %s", 0Ah, 0 -szFmtWarn DB "[WARN] %s", 0Ah, 0 -szFmtError DB "[ERROR] %s", 0Ah, 0 +; Printf format strings - [LEVEL] [HH:MM:SS.mmm] message\n +; 5 integer placeholders: H(%02d), M(%02d), S(%02d), ms(%03d), msg(%s) +szFmtInfo DB "[INFO] [%02d:%02d:%02d.%03d] %s", 0Ah, 0 +szFmtWarn DB "[WARN] [%02d:%02d:%02d.%03d] %s", 0Ah, 0 +szFmtError DB "[ERROR] [%02d:%02d:%02d.%03d] %s", 0Ah, 0 +szFmtFatal DB "[FATAL] [%02d:%02d:%02d.%03d] %s", 0Ah, 0 .code ; Logger_Initialize() -; Sets up the logger. With printf-based output the console is -; already usable after AllocConsole(); nothing extra is required. -; Kept as a callable stub so call sites from raider.asm compile. +; No-op: the console is already open after AllocConsole() in Main. Logger_Initialize PROC ret Logger_Initialize ENDP -Logger_LogInfo PROC - ; Entry: RSP = 8 mod 16 (return address pushed on aligned stack) - push rbp - mov rbp, rsp - sub rsp, 32 ; shadow space; push rbp -> RSP = 0 mod 16; sub 32 -> 0 - ; printf(szFmtInfo, msg) - mov rdx, rcx ; arg2 = msg - lea rcx, szFmtInfo ; arg1 = format +; Logger_Shutdown() +; No-op: nothing to clean up in a printf-based logger. +Logger_Shutdown PROC + ret +Logger_Shutdown ENDP + + +; Logger__Print (internal) +; Reads current local time and calls printf with the log line. +; +; In: RCX = format string* (szFmtInfo / szFmtWarn / ...) +; RDX = message string* (const char*) +; +; Frame: 0 pushes + sub 58h -> RSP = 8-88 = -80 = 0 +; [rsp+0..+1F] = shadow for GetLocalTime / printf +; [rsp+20h..+27h] = 5th printf arg: ms (DWORD in QWORD slot) +; [rsp+28h..+2Fh] = 6th printf arg: msg* (QWORD) +; [rsp+30h..+3Fh] = SYSTEMTIME struct (16 bytes) +; [rsp+40h..+47h] = saved msg* +; [rsp+48h..+4Fh] = saved fmt* +; [rsp+50h..+57h] = spare +Logger__Print PROC + sub rsp, 58h + + mov QWORD PTR [rsp+40h], rdx ; save msg* + mov QWORD PTR [rsp+48h], rcx ; save fmt* + + ; GetLocalTime(&SYSTEMTIME at [rsp+30h]) + lea rcx, [rsp+30h] + call GetLocalTime + + ; Restore fmt* -> RCX for printf + mov rcx, QWORD PTR [rsp+48h] + + ; Extract time fields: H->RDX, M->R8, S->R9, ms->[rsp+20h] + movzx edx, WORD PTR [rsp+30h + ST_wHour] + movzx r8d, WORD PTR [rsp+30h + ST_wMinute] + movzx r9d, WORD PTR [rsp+30h + ST_wSecond] + movzx eax, WORD PTR [rsp+30h + ST_wMilliseconds] + mov DWORD PTR [rsp+20h], eax ; 5th arg: ms + + ; msg* -> [rsp+28h] + mov rax, QWORD PTR [rsp+40h] + mov QWORD PTR [rsp+28h], rax ; 6th arg: msg* + call printf - mov rsp, rbp - pop rbp + add rsp, 58h + ret +Logger__Print ENDP + + +; Logger_LogInfo(RCX=msg*) +; Frame: 0 pushes + sub 28h -> RSP = 8-40 = -32 = 0 +Logger_LogInfo PROC + sub rsp, 28h + mov rdx, rcx ; rdx = msg* + lea rcx, [szFmtInfo] + call Logger__Print + add rsp, 28h ret Logger_LogInfo ENDP + +; Logger_LogWarn(RCX=msg*) Logger_LogWarn PROC - push rbp - mov rbp, rsp - sub rsp, 32 - + sub rsp, 28h mov rdx, rcx - lea rcx, szFmtWarn - call printf - - mov rsp, rbp - pop rbp + lea rcx, [szFmtWarn] + call Logger__Print + add rsp, 28h ret Logger_LogWarn ENDP + +; Logger_LogError(RCX=msg*) Logger_LogError PROC - push rbp - mov rbp, rsp - sub rsp, 32 - + sub rsp, 28h mov rdx, rcx - lea rcx, szFmtError - call printf - - mov rsp, rbp - pop rbp + lea rcx, [szFmtError] + call Logger__Print + add rsp, 28h ret Logger_LogError ENDP + +; Logger_LogFatal(RCX=msg*) +Logger_LogFatal PROC + sub rsp, 28h + mov rdx, rcx + lea rcx, [szFmtFatal] + call Logger__Print + add rsp, 28h + ret +Logger_LogFatal ENDP + + +; Logger_LogInfoFmt(RCX=fmt*, RDX/R8/R9/[rsp+20h+]=args) +; Direct printf passthrough. The caller provides the format +; string (including any level prefix) and all format arguments. +; RCX/RDX/R8/R9 and stack args pass through unchanged. +; +; Frame: 0 pushes + sub 28h -> RSP = 0 Logger_LogInfoFmt PROC - push rbp - mov rbp, rsp - sub rsp, 32 - ; RCX, RDX, R8, R9 pass through as-is to printf + sub rsp, 28h call printf - mov rsp, rbp - pop rbp + add rsp, 28h ret Logger_LogInfoFmt ENDP diff --git a/logic/inventory.asm b/logic/inventory.asm index 4ca6bad..85636f5 100644 --- a/logic/inventory.asm +++ b/logic/inventory.asm @@ -70,6 +70,8 @@ sz_ClientGivenTo BYTE "Function FortniteGame.FortWeapon.ClientGivenTo sz_ClientInternalEquipWeapon BYTE "Function FortniteGame.FortPawn.ClientInternalEquipWeapon",0 sz_OnRep_CurrentWeapon BYTE "Function FortniteGame.FortPawn.OnRep_CurrentWeapon",0 sz_SetOwner BYTE "Function Engine.Actor.SetOwner",0 +sz_OnRep_PickupLocData BYTE "Function FortniteGame.FortPickup.OnRep_PickupLocationData",0 +sz_OnRep_bPickedUp BYTE "Function FortniteGame.FortPickup.OnRep_bPickedUp",0 ; Class names for Init sz_FortQuickBarsClass BYTE "Class FortniteGame.FortQuickBars",0 @@ -115,6 +117,8 @@ fn_ClientGivenTo QWORD ? fn_ClientInternalEquipWeapon QWORD ? fn_OnRep_CurrentWeapon QWORD ? fn_SetOwner QWORD ? +fn_OnRep_PickupLocData QWORD ? +fn_OnRep_bPickedUp QWORD ? ; Cached item definitions for Init def_Wall QWORD ? @@ -1230,8 +1234,7 @@ Inventory_OnDrop PROC ; EquipInventoryItem using guid from slot 0 lea rdx, [rcx] ; pointer to guid at Slots[0].Items[0] mov rcx, r12 - call Inventory_EquipWeaponDefinition ; partial call, but we don't have def here - ; TODO: use proper EquipInventoryItem + call Inventory_EquipInventoryItem @IOD_return: movzx eax, bl @@ -1326,13 +1329,40 @@ Inventory_OnPickup PROC ; FlyTime = 0.4f mov DWORD PTR [rbx + 0458h], 3ECCCCCDh ; 0.4f IEEE - ; TODO: OnRep_PickupLocationData + ; OnRep_PickupLocationData() - rbx = Pickup (callee-saved, survives FindObject) + mov rax, QWORD PTR [fn_OnRep_PickupLocData] + test rax, rax + jnz @@have_pldata_rep + lea rcx, [sz_OnRep_PickupLocData] + call SDK_FindObject + mov QWORD PTR [fn_OnRep_PickupLocData], rax +@@have_pldata_rep: + test rax, rax + jz @IOP_skip_pldata + mov rcx, rbx + mov rdx, rax + xor r8d, r8d + call QWORD PTR [ProcessEvent] @IOP_skip_pldata: ; bPickedUp = true mov BYTE PTR [rbx + 04C0h], 1 - ; TODO: OnRep_bPickedUp + ; OnRep_bPickedUp() - rbx = Pickup (callee-saved, survives FindObject) + mov rax, QWORD PTR [fn_OnRep_bPickedUp] + test rax, rax + jnz @@have_pickedup_rep + lea rcx, [sz_OnRep_bPickedUp] + call SDK_FindObject + mov QWORD PTR [fn_OnRep_bPickedUp], rax +@@have_pickedup_rep: + test rax, rax + jz @@skip_pickedup_rep + mov rcx, rbx + mov rdx, rax + xor r8d, r8d + call QWORD PTR [ProcessEvent] +@@skip_pickedup_rep: ; LoadedAmmo from pickup to instance mov eax, DWORD PTR [rbx + 0378h] diff --git a/logic/teams.asm b/logic/teams.asm index 88db0c0..de7d7ff 100644 --- a/logic/teams.asm +++ b/logic/teams.asm @@ -4,6 +4,8 @@ INCLUDE asm\include\master.inc TEAM_maxTeamSize EQU 000h TEAM_TeamPosition EQU 004h TEAM_Members_Data EQU 008h +UWORLD_AuthorityGameMode_T EQU 0140h ; UWorld::AuthorityGameMode +AGMB_GameSession_T EQU 0370h ; AGameMode::GameSession TEAM_Members_Num EQU 010h TEAM_Members_Max EQU 014h TEAM_SIZEOF EQU 018h @@ -133,6 +135,65 @@ Team_AddPlayer ENDP ; Team_Kick ; In : RCX = Team* Team_Kick PROC + push rbx + push rbp + push rsi + push rdi + push r12 + sub rsp, 40h + + mov rbx, rcx ; rbx = Team* + + ; Load Members.Data and .Num + mov rsi, QWORD PTR [rbx + TEAM_Members_Data] ; rsi = Members.Data + mov ebp, DWORD PTR [rbx + TEAM_Members_Num] ; ebp = Members.Num + test rsi, rsi + jz @@done + test ebp, ebp + jz @@done + + ; Get GameSession once: World->AuthorityGameMode->GameSession + call SDK_GetWorld + test rax, rax + jz @@done + mov r12, QWORD PTR [rax + UWORLD_AuthorityGameMode_T] + test r12, r12 + jz @@done + mov r12, QWORD PTR [r12 + AGMB_GameSession_T] + test r12, r12 + jz @@done + + ; Zeroed FText at [rsp+20h] - done once before loop + xor eax, eax + mov QWORD PTR [rsp+20h], rax + mov QWORD PTR [rsp+28h], rax + mov QWORD PTR [rsp+30h], rax + + ; Loop: for i in 0..Members.Num, kick Members[i] + xor edi, edi ; i = 0 +@@kick_loop: + cmp edi, ebp + jge @@done + + mov rdx, QWORD PTR [rsi + rdi*8] ; rdx = Members[i] (PC*) + test rdx, rdx + jz @@kick_next + + mov rcx, r12 ; Session + lea r8, [rsp+20h] ; &empty FText + call QWORD PTR [Native_OnlineSession_KickPlayer] + +@@kick_next: + inc edi + jmp @@kick_loop + +@@done: + add rsp, 40h + pop r12 + pop rdi + pop rsi + pop rbp + pop rbx ret Team_Kick ENDP diff --git a/ufunctionhooks.asm b/ufunctionhooks.asm index 81244d0..4a52898 100644 --- a/ufunctionhooks.asm +++ b/ufunctionhooks.asm @@ -12,6 +12,8 @@ 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) MAX_PEHOOKS EQU 32 ; capacity of the hook arrays @@ -37,6 +39,10 @@ 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 (used by ReadyToStartMatch) pClass_FortOnlineBeaconHost QWORD ? @@ -83,6 +89,11 @@ szFn_K2_GetActorLocation DB "Function Engine.Actor.K2_GetActorLocatio szClass_FortOnlineBeaconHost DB "Class FortniteGame.FortOnlineBeaconHost", 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 .code @@ -260,7 +271,7 @@ PEHOOK_ServerExecuteInventoryItem PROC ; 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_EquipWeaponDefinition ; TODO: Inventory_EquipInventoryItem(PC, Guid*) + call Inventory_EquipInventoryItem add rsp, 40 pop rbx @@ -339,8 +350,6 @@ PEHOOK_OnDeathServer ENDP ; PEHOOK_ServerEndEditingBuildingActor ; RCX = ABuildingSMActor* (building being released) ; Clears EditingPlayer field on the building. -; -; TODO: also clear EditTool->EditActor if we have AFortDecoTool offset. PEHOOK_ServerEndEditingBuildingActor PROC test rcx, rcx jz @@done @@ -360,20 +369,38 @@ PEHOOK_ServerRepairBuildingActor PROC push rbp push rbx push rsi - sub rsp, 32 + 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 ; Pawn (unused for repair call itself) - mov rsi, QWORD PTR [rdx] ; Params->BuildingActorToRepair (QWORD at [RDX+0]) + mov rbx, rcx ; Controller (PC) - Object per DEFINE_PEHOOK + mov rsi, QWORD PTR [rdx] ; Params->BuildingActorToRepair ([RDX+0]) test rsi, rsi jz @@done - ; TODO : call RepairBuilding via ProcessEvent(Building, RepairFn, &AmountParams) - ; For now: ForceNetUpdate to signal changes + ; 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, ¶ms) mov rcx, rsi - call QWORD PTR [ProcessEvent] ; placeholder + mov rdx, rax + lea r8, [rsp+20h] + call QWORD PTR [ProcessEvent] @@done: - add rsp, 32 + add rsp, 48h pop rsi pop rbx pop rbp @@ -385,9 +412,6 @@ PEHOOK_ServerRepairBuildingActor ENDP ; RCX = AFortPlayerPawn* (the DBNO pawn being revived) ; RDX = Params: { AController* EventInstigator; } ; -; Simplified: call ClientOnPawnRevived via ProcessEvent. -; Full bIsDBNO clear + health restore implemented via the revive UFunction. -; ; Stack: push rbp + push rbx = 2 pushes; sub 40 -> 0 PEHOOK_ServerReviveFromDBNO PROC push rbp @@ -426,7 +450,6 @@ PEHOOK_ServerReviveFromDBNO ENDP ; RDX = Params: { AActor* ReceivingActor; } ; ; If ReceivingActor is a pawn: call PEHOOK_ServerReviveFromDBNO logic. -; Otherwise (container etc.): stub for (bAlreadySearched). ; ; Stack: push rbp + push rbx + push rsi = 3 pushes; sub 32 -> 0 PEHOOK_ServerAttemptInteract PROC @@ -452,7 +475,24 @@ PEHOOK_ServerAttemptInteract PROC jmp @@done @@container_case: - ; Container case - TODO: set bAlreadySearched + OnRep + ; 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 @@ -491,7 +531,6 @@ PEHOOK_ServerPlayEmoteItem PROC ; Write to RepAnimMontageInfo (FGameplayAbilityRepAnimMontage at ASC+0xAF0) ; Layout: AnimMontage(+0), PlayRate(+8), Position(+C), BlendTime(+10), ; NextSectionID(+14), bitfield(+15) - ; We store the EmoteAsset as the AnimMontage and set PlayRate=1.0 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 @@ -533,7 +572,7 @@ PEHOOK_ServerSpawnDeco PROC mov rcx, rbx mov rdx, rsi - call Spawners_SpawnDeco ; Spawners_SpawnDeco(Tool, Params) + call Spawners_SpawnDeco add rsp, 32 pop rsi @@ -574,6 +613,7 @@ PEHOOK_ServerTryActivateAbility PROC 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. ; @@ -605,8 +645,6 @@ PEHOOK_ServerTryActivateAbilityWithEventData PROC PEHOOK_ServerTryActivateAbilityWithEventData ENDP ; PEHOOK_ServerAbilityRPCBatch - parse batch and call TryActivate per entry -; Stub: delegates to TryActivateAbility for the first batch entry. -; ; Stack: push rbp + push rbx + push rsi = 3 pushes; sub 32 -> 0 PEHOOK_ServerAbilityRPCBatch PROC push rbp @@ -636,10 +674,6 @@ 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]) -; -; Simplified implementation: updates DeathInfo on PlayerState, decrements -; PlayersLeft on GameState, calls Game mode's OnPlayerKilled. -; ; Stack: push rbp,rbx,rsi,rdi,r12,r13 = 6 pushes (RSP=8); sub 40 -> 0 PEHOOK_ClientOnPawnDied PROC push rbp @@ -670,14 +704,52 @@ PEHOOK_ClientOnPawnDied PROC call Game_GetDeathCause ; returns DWORD in EAX mov BYTE PTR [rsi + AFPSA_DeathInfo + 009h], al ; DeathCause byte - ; GameState->PlayersLeft-- + ; 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 - ; AuthorityGameMode is on World - mov r13, QWORD PTR [rax + UWORLD_AuthorityGameMode] + mov rdi, QWORD PTR [rax + UWORLD_GameState] ; rdi = GameState* + test rdi, rdi + jz @@done - ; TODO: full spectate, victory drone, EndMatch check + ; 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 @@ -725,8 +797,11 @@ PEHOOK_ServerAttemptAircraftJump PROC PEHOOK_ServerAttemptAircraftJump ENDP ; PEHOOK_OnAircraftExitedDropZone - auto-jump remaining passengers -; RCX = AFortAthenaAircraft* (the aircraft) -; Iterates passengers and forces ServerAttemptAircraftJump on each. +; 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 @@ -773,7 +848,18 @@ PEHOOK_OnAircraftExitedDropZone PROC ret PEHOOK_OnAircraftExitedDropZone ENDP -; PEHOOK_ServerCreateBuildingActor - spawn a building piece +; 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 @@ -842,7 +928,15 @@ PEHOOK_ServerCreateBuildingActor PROC ret PEHOOK_ServerCreateBuildingActor ENDP -; PEHOOK_ServerBeginEditingBuildingActor - start building edit mode +; 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 @@ -955,7 +1049,21 @@ PEHOOK_ServerBeginEditingBuildingActor PROC ret PEHOOK_ServerBeginEditingBuildingActor ENDP -; PEHOOK_ServerEditBuildingActor - apply edit and re-spawn building +; 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 @@ -1065,15 +1173,6 @@ PEHOOK_ServerEditBuildingActor ENDP ; PEHOOK_ReadyToStartMatch - set up full listen-server infrastructure ; RCX = AFortGameModeAthena* (GameMode) ; -; Sequence: -; 1. Guard bListening - skip if already listening -; 2. Game_OnReadyToStartMatch() -; 3. Spawn AFortOnlineBeaconHost -> HostBeacon -; 4. Set ListenPort=7776 + InitHost -; 5. Init World NetDriver on port 7777 -; 6. Resolve ServerReplicateActors from vtable slot 0x53 -; 7. MaxPlayers=100, PauseBeaconRequests(false), bListening=true -; ; Stack: push rbp,rbx,rsi,rdi,r12,r13,r14 = 7 pushes (RSP=0); sub 80 -> 0 ; [rsp+0..31] = shadow ; [rsp+32..79] = FURL struct (0x70 = 112 bytes)... actually needs sub 128 = 0 @@ -1120,7 +1219,7 @@ PEHOOK_ReadyToStartMatch PROC mov rcx, QWORD PTR [pClass_FortOnlineBeaconHost] xor edx, edx ; Location = null (use zero) xor r8d, r8d ; Owner = null - call Spawners_SpawnActor + call Spawners_SpawnActor ; Phase 10 will fill this test rax, rax jz @@net_setup @@ -1143,8 +1242,6 @@ PEHOOK_ReadyToStartMatch PROC call RtlZeroMemory mov DWORD PTR [rsp + 32 + FURL_Port], LISTEN_GAME_PORT ; FURL::Port at +0x20 - ; Native_NetDriver_InitListen(World?, ??, &FURL, false, &ErrorStr) - ; Resolve ServerReplicateActors from ReplicationDriver vtable[0x53] call SDK_GetWorld test rax, rax @@ -1161,6 +1258,9 @@ PEHOOK_ReadyToStartMatch PROC mov rax, QWORD PTR [rax + VTABLE_ServerReplicateActors] ; slot 0x53 mov QWORD PTR [Native_ReplicationDriver_ServerReplicateActors], rax + ; ClassRepNodePolicies is a TMap at RepDriver+0x3B8. + ; Populating it requires iterating registered actor classes; deferred to game-specific setup. + @@final_setup: ; PauseBeaconRequests(false) if beacon was spawned mov rax, QWORD PTR [HostBeacon] @@ -1246,7 +1346,7 @@ UFunctionHooks_Initialize PROC mov QWORD PTR [rdi + rbp * 8], rax inc ebp @@r2: - ; ServerAbilityRPCBatch + ; 3. ServerAbilityRPCBatch lea rcx, szFn_ServerAbilityRPCBatch call SDK_FindObject test rax, rax @@ -1436,7 +1536,7 @@ UFunctionHooks_Initialize PROC mov QWORD PTR [rdi + rbp * 8], rax inc ebp @@r21: - ; ServerLoadingScreenDropped + ; 22. ServerLoadingScreenDropped lea rcx, szFn_ServerLoadingScreenDropped call SDK_FindObject test rax, rax @@ -1446,7 +1546,7 @@ UFunctionHooks_Initialize PROC mov QWORD PTR [rdi + rbp * 8], rax inc ebp @@r22: - ; ServerChoosePart + ; 23. ServerChoosePart lea rcx, szFn_ServerChoosePart call SDK_FindObject test rax, rax diff --git a/zeroinput.asm b/zeroinput.asm index 24647f7..63f9965 100644 --- a/zeroinput.asm +++ b/zeroinput.asm @@ -56,7 +56,7 @@ ZeroInput_IsMouseClicked PROC test al, al jz @@clear_already ; not pressed -> clear + return false - ; mouseDown[button] is set — check mouseDownAlready[element_id] + ; mouseDown[button] is set - check mouseDownAlready[element_id] movzx rcx, edx ; element_id -> index (zero-extend) movzx eax, BYTE PTR [ZI_mouseDownAlready + rcx] test al, al