diff --git a/include/externs.inc b/include/externs.inc index 89c46dc..da03b33 100644 --- a/include/externs.inc +++ b/include/externs.inc @@ -162,11 +162,20 @@ EXTERNDEF Game_GetDeathCause :PROC ; (report:PTR FFortPlayerDeathRe ; logic/inventory.asm EXTERNDEF Inventory_Update :PROC ; (pc:QWORD, dirty:DWORD, bRemovedItem:BYTE) EXTERNDEF Inventory_IsValidGuid :PROC ; (pc:QWORD, guid:PTR FGuid) -> BYTE +EXTERNDEF Inventory_IsGuidInInventory :PROC ; (pc:QWORD, guid:PTR FGuid) -> BYTE EXTERNDEF Inventory_GetEntryInSlot :PROC ; (pc:QWORD, slot:DWORD, item:DWORD, bars:DWORD) -> PTR FFortItemEntry +EXTERNDEF Inventory_GetDefinitionInSlot :PROC ; (pc:QWORD, slot:DWORD, item:DWORD, bars:DWORD) -> PTR UFortWorldItemDefinition +EXTERNDEF Inventory_GetInstanceFromGuid :PROC ; (pc:QWORD, guid:PTR FGuid) -> PTR UFortWorldItem +EXTERNDEF Inventory_FindItemInInventory :PROC ; (pc:QWORD, class:QWORD, bFound:PTR BYTE) -> PTR FFortItemEntry EXTERNDEF Inventory_AddItemToSlot :PROC ; (pc:QWORD, def:QWORD, slot:DWORD, bars:DWORD, count:DWORD) -> PTR FFortItemEntry EXTERNDEF Inventory_RemoveItemFromSlot :PROC ; (pc:QWORD, slot:DWORD, bars:DWORD, amount:DWORD) EXTERNDEF Inventory_IsGuidInInventory :PROC ; (pc:QWORD, guid:PTR FGuid) -> BYTE EXTERNDEF Inventory_EquipWeaponDefinition :PROC ; (pawn:QWORD, def:QWORD, guid:PTR FGuid) +EXTERNDEF Inventory_EquipInventoryItem :PROC ; (pc:QWORD, guid:PTR FGuid) +EXTERNDEF Inventory_EquipLoadout :PROC ; (pc:QWORD, wids:PTR QWORD[6]) +EXTERNDEF Inventory_OnDrop :PROC ; (pc:QWORD, params:PTR) -> BYTE +EXTERNDEF Inventory_OnPickup :PROC ; (pc:QWORD, params:PTR) +EXTERNDEF Inventory_Init :PROC ; (pc:QWORD) ; logic/abilities.asm EXTERNDEF Abilities_TryActivateAbility :PROC @@ -176,6 +185,7 @@ EXTERNDEF Abilities_ConsumeAllReplicatedData :PROC ; logic/spawners.asm EXTERNDEF Spawners_SpawnActor :PROC ; (actorClass:QWORD, location:PTR FVector, owner:QWORD) -> QWORD +EXTERNDEF Spawners_SpawnActor_ByLocation :PROC ; (actorClass:QWORD, location:PTR FVector, rotation:PTR FRotator, owner:QWORD) -> QWORD EXTERNDEF Spawners_SummonPickup :PROC ; (pawn:QWORD, itemDef:QWORD, count:DWORD, loc:PTR FVector) -> QWORD EXTERNDEF Spawners_SummonPickupFromChest :PROC ; (itemDef:QWORD, count:DWORD, loc:PTR FVector) EXTERNDEF Spawners_SpawnPickupFromFloor :PROC ; (itemDef:QWORD, count:DWORD, loc:PTR FVector) @@ -247,3 +257,4 @@ EXTRN memcpy :PROC EXTRN memmove :PROC EXTRN memset :PROC EXTRN memcmp :PROC +EXTRN strcmp :PROC \ No newline at end of file diff --git a/logic/abilities.asm b/logic/abilities.asm new file mode 100644 index 0000000..04a5e78 --- /dev/null +++ b/logic/abilities.asm @@ -0,0 +1,478 @@ +INCLUDE asm\include\master.inc + +; UAbilitySystemComponent: ActivatableAbilities(+0x470) + Items TArray (+0xB0) +ASC_ITEMS_DATA EQU 0520h +ASC_ITEMS_NUM EQU 0528h + +; FGameplayAbilitySpec field offsets (from spec base) +SPEC_HANDLE EQU 00Ch ; Handle.Handle (DWORD) +SPEC_ABILITY EQU 010h ; Ability (QWORD) +SPEC_LEVEL EQU 018h ; Level (DWORD) +SPEC_INPUTID EQU 01Ch ; InputID (DWORD) +SPEC_INPRESSED EQU 029h ; InputPressed (BYTE bit 0) +SPEC_SIZEOF EQU 0C8h + +; FPredictionKey.Current = int16 at +0 +FPREDKEY_CURRENT EQU 000h + +; UClass::vtable[101] = CreateDefaultObject +VFUNC_CDO_OFS EQU 0328h ; 101 * 8 + +.const + +sz_ClientActFailed BYTE "Function GameplayAbilities.AbilitySystemComponent.ClientActivateAbilityFailed",0 +sz_Sprint BYTE "Class FortniteGame.FortGameplayAbility_Sprint",0 +sz_Reload BYTE "Class FortniteGame.FortGameplayAbility_Reload",0 +sz_Ranged BYTE "Class FortniteGame.FortGameplayAbility_RangedWeapon",0 +sz_Jump BYTE "Class FortniteGame.FortGameplayAbility_Jump",0 +sz_Death BYTE "BlueprintGeneratedClass GA_DefaultPlayer_Death.GA_DefaultPlayer_Death_C",0 +sz_InteractUse BYTE "BlueprintGeneratedClass GA_DefaultPlayer_InteractUse.GA_DefaultPlayer_InteractUse_C",0 +sz_InteractSearch BYTE "BlueprintGeneratedClass GA_DefaultPlayer_InteractSearch.GA_DefaultPlayer_InteractSearch_C",0 +sz_Emote BYTE "BlueprintGeneratedClass GAB_Emote_Generic.GAB_Emote_Generic_C",0 +sz_Trap BYTE "BlueprintGeneratedClass GA_TrapBuildGeneric.GA_TrapBuildGeneric_C",0 +sz_DanceGrenade BYTE "BlueprintGeneratedClass GA_DanceGrenade_Stun.GA_DanceGrenade_Stun_C",0 + +.data? + +fn_ClientActFailed QWORD ? + +AbilityClass_Sprint QWORD ? +AbilityClass_Reload QWORD ? +AbilityClass_Ranged QWORD ? +AbilityClass_Jump QWORD ? +AbilityClass_Death QWORD ? +AbilityClass_InteractUse QWORD ? +AbilityClass_InteractSearch QWORD ? +AbilityClass_Emote QWORD ? +AbilityClass_Trap QWORD ? +AbilityClass_DanceGrenade QWORD ? + +.code + +; Abilities_FindAbilitySpecFromHandle +; In : RCX = UAbilitySystemComponent* +; RDX = FGameplayAbilitySpecHandle* (int Handle at [RDX+0]) +; Out: RAX = &FGameplayAbilitySpec or NULL +; No CALL sites -> no shadow/alignment needed; push/pop only. +Abilities_FindAbilitySpecFromHandle PROC + push rbx + push rbp + push rsi + + mov rsi, rcx + mov ebx, DWORD PTR [rdx] ; target Handle.Handle value + + mov rcx, QWORD PTR [rsi + ASC_ITEMS_DATA] + mov edx, DWORD PTR [rsi + ASC_ITEMS_NUM] + test rcx, rcx + jz @FASFH_miss + test edx, edx + jz @FASFH_miss + + xor ebp, ebp ; i = 0 +@FASFH_loop: + cmp ebp, edx + jge @FASFH_miss + imul rax, rbp, SPEC_SIZEOF + add rax, rcx + cmp DWORD PTR [rax + SPEC_HANDLE], ebx + je @FASFH_found + inc ebp + jmp @FASFH_loop + +@FASFH_miss: + xor eax, eax +@FASFH_found: + pop rsi + pop rbp + pop rbx + ret +Abilities_FindAbilitySpecFromHandle ENDP + + +; Abilities_ConsumeAllReplicatedData +Abilities_ConsumeAllReplicatedData PROC + ret +Abilities_ConsumeAllReplicatedData ENDP + + +; Abilities_TryActivateAbility +; In : RCX = UAbilitySystemComponent* +; RDX = FGameplayAbilitySpecHandle* (Handle.Handle at [RDX+0]) +; R8B = InputPressed (bool) +; R9 = FPredictionKey* +; [caller rsp+32+8] = arg5: FGameplayEventData* TriggerEventData +; +; Stack: 5 callee-saved pushes (40) + sub 70h (112) = 152 +; Entry RSP=8; 5 pushes: 8-40=-32=0; sub 70h: 0-112=0 +; +; Frame slots (after prolog, relative to current RSP): +; [+00..+1F] shadow (32 B) +; [+20..+27] arg5 slot for 6-arg calls +; [+28..+2F] arg6 slot +; [+30..+37] OutInstancedAbility QWORD +; [+38..+3B] handle DWORD copy (for FindAbilitySpec pointer arg) +; [+40..+47] CAF params: Handle(4) + PredKey(2) + pad(2) +Abilities_TryActivateAbility PROC + push rbx + push rbp + push rsi + push rdi + push r12 + sub rsp, 70h + + mov rsi, rcx + mov ebx, DWORD PTR [rdx] ; EBX = Handle.Handle + movzx r12d, r8b ; R12D = InputPressed (unused directly) + mov rdi, r9 ; RDI = FPredictionKey* + ; arg5 (TriggerEventData*) at entry_rsp+40 = current_rsp+152+40 = rsp+0xC0 + mov rbp, QWORD PTR [rsp + 0C0h] + + ; Find spec + mov DWORD PTR [rsp + 38h], ebx + mov rcx, rsi + lea rdx, [rsp + 38h] + call Abilities_FindAbilitySpecFromHandle + test rax, rax + jz @TAA_do_caf + + ; Set InputPressed = 1 + or BYTE PTR [rax + SPEC_INPRESSED], 1 + + ; InternalTryActivateAbility(ASC, Handle, PredKey*, &OutGA, NULL, TriggerEventData) + xor eax, eax + mov QWORD PTR [rsp + 30h], rax + mov rcx, rsi + mov edx, ebx + mov r8, rdi + lea r9, [rsp + 30h] + mov QWORD PTR [rsp + 20h], rax + mov QWORD PTR [rsp + 28h], rbp + call QWORD PTR [Native_AbilitySystemComponent_InternalTryActivateAbility] + test al, al + jz @TAA_activate_failed + + ; Success: MarkAbilitySpecDirty + mov DWORD PTR [rsp + 38h], ebx + mov rcx, rsi + lea rdx, [rsp + 38h] + call Abilities_FindAbilitySpecFromHandle + test rax, rax + jz @TAA_done + mov rcx, rsi + mov rdx, rax + call QWORD PTR [Native_AbilitySystemComponent_MarkAbilitySpecDirty] + jmp @TAA_done + +@TAA_activate_failed: + ; Clear InputPressed + mov DWORD PTR [rsp + 38h], ebx + mov rcx, rsi + lea rdx, [rsp + 38h] + call Abilities_FindAbilitySpecFromHandle + test rax, rax + jz @TAA_do_caf + and BYTE PTR [rax + SPEC_INPRESSED], 0FEh + +@TAA_do_caf: + ; Lazy-load ClientActivateAbilityFailed fn + mov rax, QWORD PTR [fn_ClientActFailed] + test rax, rax + jnz @TAA_caf_call + lea rcx, [sz_ClientActFailed] + call SDK_FindObject + mov QWORD PTR [fn_ClientActFailed], rax +@TAA_caf_call: + test rax, rax + jz @TAA_done + ; Params: Handle(4) at +0, PredictionKey.Current(int16) at +4 + mov DWORD PTR [rsp + 40h], ebx + movsx ecx, WORD PTR [rdi + FPREDKEY_CURRENT] + mov WORD PTR [rsp + 44h], cx + mov rcx, rsi + mov rdx, rax + lea r8, [rsp + 40h] + call QWORD PTR [ProcessEvent] + +@TAA_done: + add rsp, 70h + pop r12 + pop rdi + pop rsi + pop rbp + pop rbx + ret +Abilities_TryActivateAbility ENDP + + +; Abilities_GrantGameplayAbility +; In : RCX = APlayerPawn_Athena_C* (pawn) +; RDX = UClass* (abilityClass) +; +; Stack: 5 callee-saved pushes (40) + sub 100h (256) = 296 +; Entry RSP=8; 5 pushes: 8-40=0; sub 100h: 0=0 +; +; Frame: +; [+00..+1F] shadow +; [+20..+27] arg5 slot +; [+28..+2F] OutHandle QWORD (for GiveAbility result) +; [+30..+F7] FGameplayAbilitySpec copy (0xC8 bytes) +Abilities_GrantGameplayAbility PROC + push rbx + push rbp + push rsi + push rdi + push r12 + sub rsp, 100h + + mov rbp, rcx ; RBP = pawn + mov rsi, rdx ; RSI = abilityClass + + ; ASC = FortHelper_GetAbilitySystemComponent(pawn) + mov rcx, rbp + call FortHelper_GetAbilitySystemComponent + test rax, rax + jz @GAA_done + mov rbx, rax ; RBX = ASC + + ; CDO = abilityClass->vtable[101](abilityClass) + mov rcx, rsi + mov rax, QWORD PTR [rcx] + call QWORD PTR [rax + VFUNC_CDO_OFS] + test rax, rax + jz @GAA_done + mov rdi, rax ; RDI = CDO + + ; Duplicate check: scan ActivatableAbilities.Items for same Ability ptr + mov r12, QWORD PTR [rbx + ASC_ITEMS_DATA] + mov ebp, DWORD PTR [rbx + ASC_ITEMS_NUM] + test ebp, ebp + jz @GAA_add + test r12, r12 + jz @GAA_add + xor ecx, ecx ; i = 0 +@GAA_dup_loop: + cmp ecx, ebp + jge @GAA_add + imul rax, rcx, SPEC_SIZEOF + add rax, r12 + cmp QWORD PTR [rax + SPEC_ABILITY], rdi + je @GAA_done ; duplicate, skip + inc ecx + jmp @GAA_dup_loop + +@GAA_add: + ; Zero out spec buffer at [rsp+30h] + lea rcx, [rsp + 30h] + xor edx, edx + mov r8d, SPEC_SIZEOF + call memset + + ; FFastArraySerializerItem: ReplicationID/Key/MostRecentKey = -1 + mov DWORD PTR [rsp + 30h + 000h], 0FFFFFFFFh + mov DWORD PTR [rsp + 30h + 004h], 0FFFFFFFFh + mov DWORD PTR [rsp + 30h + 008h], 0FFFFFFFFh + + ; Handle.Handle = lower 32 bits of CDO ptr | 1 (ensure non-zero and pseudo-unique) + mov eax, edi + or eax, 1 + mov DWORD PTR [rsp + 30h + SPEC_HANDLE], eax + + ; Ability = CDO + mov QWORD PTR [rsp + 30h + SPEC_ABILITY], rdi + + ; Level = 1 + mov DWORD PTR [rsp + 30h + SPEC_LEVEL], 1 + + ; InputID = -1 + mov DWORD PTR [rsp + 30h + SPEC_INPUTID], 0FFFFFFFFh + + ; GiveAbility(ASC, &OutHandle, &Spec) + mov rcx, rbx + lea rdx, [rsp + 28h] + lea r8, [rsp + 30h] + call QWORD PTR [Native_AbilitySystemComponent_GiveAbility] + +@GAA_done: + add rsp, 100h + pop r12 + pop rdi + pop rsi + pop rbp + pop rbx + ret +Abilities_GrantGameplayAbility ENDP + + +; Abilities_ApplyAbilities +; In : RCX = APlayerPawn_Athena_C* (pawn) +; Grants 10 standard gameplay abilities. Lazily caches class ptrs. +; +; Stack: 2 pushes (16) + sub 28h (40) = 56 +; Entry RSP=8; 2 pushes: 8-16=8; sub 28h: 8-40=0 +Abilities_ApplyAbilities PROC + push rbx + push rdi + sub rsp, 28h + + mov rbx, rcx ; RBX = pawn + + ; Macro-like: load class pointer (lazily) then call GrantGameplayAbility + ; Sprint + mov rdi, QWORD PTR [AbilityClass_Sprint] + test rdi, rdi + jnz @AAA_sprint_ok + lea rcx, [sz_Sprint] + call SDK_FindClass + mov QWORD PTR [AbilityClass_Sprint], rax + mov rdi, rax +@AAA_sprint_ok: + test rdi, rdi + jz @AAA_reload + mov rcx, rbx + mov rdx, rdi + call Abilities_GrantGameplayAbility + +@AAA_reload: + mov rdi, QWORD PTR [AbilityClass_Reload] + test rdi, rdi + jnz @AAA_reload_ok + lea rcx, [sz_Reload] + call SDK_FindClass + mov QWORD PTR [AbilityClass_Reload], rax + mov rdi, rax +@AAA_reload_ok: + test rdi, rdi + jz @AAA_ranged + mov rcx, rbx + mov rdx, rdi + call Abilities_GrantGameplayAbility + +@AAA_ranged: + mov rdi, QWORD PTR [AbilityClass_Ranged] + test rdi, rdi + jnz @AAA_ranged_ok + lea rcx, [sz_Ranged] + call SDK_FindClass + mov QWORD PTR [AbilityClass_Ranged], rax + mov rdi, rax +@AAA_ranged_ok: + test rdi, rdi + jz @AAA_jump + mov rcx, rbx + mov rdx, rdi + call Abilities_GrantGameplayAbility + +@AAA_jump: + mov rdi, QWORD PTR [AbilityClass_Jump] + test rdi, rdi + jnz @AAA_jump_ok + lea rcx, [sz_Jump] + call SDK_FindClass + mov QWORD PTR [AbilityClass_Jump], rax + mov rdi, rax +@AAA_jump_ok: + test rdi, rdi + jz @AAA_death + mov rcx, rbx + mov rdx, rdi + call Abilities_GrantGameplayAbility + +@AAA_death: + mov rdi, QWORD PTR [AbilityClass_Death] + test rdi, rdi + jnz @AAA_death_ok + lea rcx, [sz_Death] + call SDK_FindClass + mov QWORD PTR [AbilityClass_Death], rax + mov rdi, rax +@AAA_death_ok: + test rdi, rdi + jz @AAA_iuse + mov rcx, rbx + mov rdx, rdi + call Abilities_GrantGameplayAbility + +@AAA_iuse: + mov rdi, QWORD PTR [AbilityClass_InteractUse] + test rdi, rdi + jnz @AAA_iuse_ok + lea rcx, [sz_InteractUse] + call SDK_FindClass + mov QWORD PTR [AbilityClass_InteractUse], rax + mov rdi, rax +@AAA_iuse_ok: + test rdi, rdi + jz @AAA_isearch + mov rcx, rbx + mov rdx, rdi + call Abilities_GrantGameplayAbility + +@AAA_isearch: + mov rdi, QWORD PTR [AbilityClass_InteractSearch] + test rdi, rdi + jnz @AAA_isearch_ok + lea rcx, [sz_InteractSearch] + call SDK_FindClass + mov QWORD PTR [AbilityClass_InteractSearch], rax + mov rdi, rax +@AAA_isearch_ok: + test rdi, rdi + jz @AAA_emote + mov rcx, rbx + mov rdx, rdi + call Abilities_GrantGameplayAbility + +@AAA_emote: + mov rdi, QWORD PTR [AbilityClass_Emote] + test rdi, rdi + jnz @AAA_emote_ok + lea rcx, [sz_Emote] + call SDK_FindClass + mov QWORD PTR [AbilityClass_Emote], rax + mov rdi, rax +@AAA_emote_ok: + test rdi, rdi + jz @AAA_trap + mov rcx, rbx + mov rdx, rdi + call Abilities_GrantGameplayAbility + +@AAA_trap: + mov rdi, QWORD PTR [AbilityClass_Trap] + test rdi, rdi + jnz @AAA_trap_ok + lea rcx, [sz_Trap] + call SDK_FindClass + mov QWORD PTR [AbilityClass_Trap], rax + mov rdi, rax +@AAA_trap_ok: + test rdi, rdi + jz @AAA_dance + mov rcx, rbx + mov rdx, rdi + call Abilities_GrantGameplayAbility + +@AAA_dance: + mov rdi, QWORD PTR [AbilityClass_DanceGrenade] + test rdi, rdi + jnz @AAA_dance_ok + lea rcx, [sz_DanceGrenade] + call SDK_FindClass + mov QWORD PTR [AbilityClass_DanceGrenade], rax + mov rdi, rax +@AAA_dance_ok: + test rdi, rdi + jz @AAA_done + mov rcx, rbx + mov rdx, rdi + call Abilities_GrantGameplayAbility + +@AAA_done: + add rsp, 28h + pop rdi + pop rbx + ret +Abilities_ApplyAbilities ENDP + +END \ No newline at end of file diff --git a/logic/game.asm b/logic/game.asm new file mode 100644 index 0000000..e9f8153 --- /dev/null +++ b/logic/game.asm @@ -0,0 +1,613 @@ +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 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 + +.const + +; Athena terrain URL (wide string) +szAthenaUrl 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' + DW 'M','o','d','e','_','C', 0 + +szInProgress DW 'I','n','P','r','o','g','r','e','s','s', 0 + +; UFunction paths +szFn_SwitchLevel DB "Function Engine.PlayerController.SwitchLevel", 0 +szFn_ConvToName DB "Function Engine.KismetStringLibrary.Conv_StringToName", 0 +szFn_K2SetMatchState DB "Function Engine.GameMode.K2_OnSetMatchState", 0 +szFn_StartPlay DB "Function Engine.GameMode.StartPlay", 0 +szFn_StartMatch DB "Function Engine.GameMode.StartMatch", 0 +szFn_OnRepHasBegun DB "Function Engine.GameStateBase.OnRep_ReplicatedHasBegunPlay", 0 +szFn_OnRepGamePhase DB "Function FortniteGame.FortGameStateAthena.OnRep_GamePhase", 0 +szClass_KismetStr DB "Class Engine.KismetStringLibrary", 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 + +.data? + +pFn_SwitchLevel QWORD ? +pFn_ConvToName QWORD ? +pFn_K2SetMatchState QWORD ? +pFn_StartPlay QWORD ? +pFn_StartMatch QWORD ? +pFn_OnRepHasBegun QWORD ? +pFn_OnRepGamePhase QWORD ? +pClass_KismetStr 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 +; 4 pushes from base 8 -> RSP=8; sub 58h(88)=8 -> 0 +; [rsp+32..+47] = SwitchLevel FString params (16 bytes) +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 @@done + mov rbx, rax ; rbx = PlayerController* + + ; Build FString URL at [rsp+32] + lea rcx, [rsp+32] + lea rdx, szAthenaUrl + call FString_FromWideChar + + ; Load SwitchLevel UFunction* + lea rsi, pFn_SwitchLevel + lea rdi, szFn_SwitchLevel + call Game__LoadFn + test rax, rax + jz @@free + + ; ProcessEvent(PC, fn, ¶ms) + mov rcx, rbx + mov rdx, rax + lea r8, [rsp+32] + call QWORD PTR [ProcessEvent] + +@@free: + lea rcx, [rsp+32] + call FString_Free + + ; bTraveled = true + mov BYTE PTR [bTraveled], 1 + +@@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) +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: + + ; 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/cache KismetStringLibrary class + mov r14, QWORD PTR [pClass_KismetStr] + test r14, r14 + jnz @@have_ksl + lea rcx, szClass_KismetStr + call SDK_FindClass + mov QWORD PTR [pClass_KismetStr], rax + mov r14, rax +@@have_ksl: + + ; Get Conv_StringToName fn + 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: GameplayStatics.Conv_StringToName(FString, FName*) + ; Params layout: [rsp+32]=FString input, [rsp+48]=FName output + test r15, r15 + jz @@skip_conv + test r14, r14 + jz @@skip_conv + mov rcx, r14 + mov rdx, r15 + lea r8, [rsp+32] + call QWORD PTR [ProcessEvent] +@@skip_conv: + + ; 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: + + ; Store current game mode + 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 + mov rcx, r13 + mov rdx, rax + lea r8, [rsp+32] + call QWORD PTR [ProcessEvent] +@@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 + mov rcx, r12 + mov rdx, rax + lea r8, [rsp+32] + call QWORD PTR [ProcessEvent] +@@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] + +@@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) 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. NOT right. +; +; stack grows DOWN. Entry RSP=X where X=8 mod16. +; push subtracts 8: each push makes RSP lower by 8. +; After 5 pushes: RSP = X-40. (X-40) mod16 = (X mod16) - (40 mod16) = 8 - 8 = 0 mod16. +; So after 5 pushes, RSP = 0 mod 16. +; sub N: need RSP=0 at CALL sites. Current RSP=0. sub N must keep 0 mod16. +; N must be = 0 mod16. N=0x40(64) or N=0x50 etc. +; Use sub 50h (80): [rsp+0..+31]=shadow, [rsp+32..+47]=FString scratch, +; [rsp+48..+79]=narrow buf (32 bytes for tag string) +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 + ; 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 \ No newline at end of file diff --git a/logic/inventory.asm b/logic/inventory.asm new file mode 100644 index 0000000..8d4ee74 --- /dev/null +++ b/logic/inventory.asm @@ -0,0 +1,2097 @@ +INCLUDE asm\include\master.inc + +; AFortPlayerController offsets +PC_WorldInventory EQU 1ED8h +PC_QuickBars EQU 1C38h + +; AFortInventory offsets +AFI_Inventory EQU 0328h +AFI_bRequiresLocalUpdate EQU 0498h + +; Within AFortInventory: FFortItemList field absolutes +AFI_ReplicatedEntries EQU 03D8h ; AFI+0x0328+0x00B0 +AFI_ItemInstances EQU 0438h ; AFI+0x0328+0x0110 + +; AFortQuickBars offsets +QB_PrimarySlots EQU 0328h ; PrimaryQuickBar(+0x0318)+Slots(+0x10) +QB_SecondarySlots EQU 0448h ; SecondaryQuickBar(+0x0438)+Slots(+0x10) +QB_PriFocusedSlot EQU 0318h ; PrimaryQuickBar.CurrentFocusedSlot (int32) + +; FQuickBarSlot size +QBSLOT_SIZE EQU 018h +QBSLOT_Items EQU 000h ; TArray within slot + +; UFortWorldItem offsets +UWI_OwnerInventory EQU 01D8h +UWI_ItemEntry EQU 01E0h ; FFortItemEntry base +UWI_ItemEntry_Count EQU 01ECh ; +0x0C +UWI_ItemEntry_ItemDef EQU 01F8h ; +0x18 +UWI_ItemEntry_LoadedAmmo EQU 0208h ; +0x28 +UWI_ItemEntry_ItemGuid EQU 0230h ; +0x50 + +; FFortItemEntry size +FITEM_SIZEOF EQU 0C8h + +; FGuid size +FGUID_SIZE EQU 010h + +; AFortWeapon offsets +FW_WeaponData EQU 0368h +FW_ItemEntryGuid EQU 0988h +FW_AmmoCount EQU 099Ch + +; EFortQuickBars enum values +QB_PRIMARY EQU 0 +QB_SECONDARY EQU 1 + +; AController::PlayerState offset (for controller chain access) +ACTRL_Pawn EQU 0260h + +.const + +; UFunction paths +sz_HandleInvLocalUpdate BYTE "Function FortniteGame.FortInventory.HandleInventoryLocalUpdate",0 +sz_ForceNetUpdate BYTE "Function Engine.Actor.ForceNetUpdate",0 +sz_HandleWorldInvLocalUpdate BYTE "Function FortniteGame.FortPlayerController.HandleWorldInventoryLocalUpdate",0 +sz_OnRep_PrimaryQB BYTE "Function FortniteGame.FortQuickBars.OnRep_PrimaryQuickBar",0 +sz_OnRep_SecondaryQB BYTE "Function FortniteGame.FortQuickBars.OnRep_SecondaryQuickBar",0 +sz_ForceUpdateQB BYTE "Function FortniteGame.FortPlayerController.ForceUpdateQuickbar",0 +sz_ServerAddItemInternal BYTE "Function FortniteGame.FortQuickBars.ServerAddItemInternal",0 +sz_ServerRemoveItemInternal BYTE "Function FortniteGame.FortQuickBars.ServerRemoveItemInternal",0 +sz_EmptySlot BYTE "Function FortniteGame.FortQuickBars.EmptySlot",0 +sz_ServerActivateSlotInt BYTE "Function FortniteGame.FortQuickBars.ServerActivateSlotInternal",0 +sz_OnRep_QuickBar BYTE "Function FortniteGame.FortPlayerController.OnRep_QuickBar",0 +sz_CreateTempItemBP BYTE "Function FortniteGame.FortItemDefinition.CreateTemporaryItemInstanceBP",0 +sz_SetOwningController BYTE "Function FortniteGame.FortItem.SetOwningControllerForTemporaryItem",0 +sz_EquipWeaponDef BYTE "Function FortniteGame.FortPawn.EquipWeaponDefinition",0 +sz_OnRep_WeaponData BYTE "Function FortniteGame.FortWeapon.OnRep_ReplicatedWeaponData",0 +sz_OnRep_AmmoCount BYTE "Function FortniteGame.FortWeapon.OnRep_AmmoCount",0 +sz_ClientGivenTo BYTE "Function FortniteGame.FortWeapon.ClientGivenTo",0 +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 + +; Class names for Init +sz_FortQuickBarsClass BYTE "Class FortniteGame.FortQuickBars",0 +sz_WallDef BYTE "FortBuildingItemDefinition BuildingItemData_Wall.BuildingItemData_Wall",0 +sz_FloorDef BYTE "FortBuildingItemDefinition BuildingItemData_Floor.BuildingItemData_Floor",0 +sz_StairDef BYTE "FortBuildingItemDefinition BuildingItemData_Stair_W.BuildingItemData_Stair_W",0 +sz_RoofDef BYTE "FortBuildingItemDefinition BuildingItemData_RoofS.BuildingItemData_RoofS",0 +sz_LaunchPad BYTE "FortTrapItemDefinition TID_Floor_Player_Launch_Pad_Athena.TID_Floor_Player_Launch_Pad_Athena",0 +sz_WallElectric BYTE "FortTrapItemDefinition TID_Wall_Electric_Athena_R_T03.TID_Wall_Electric_Athena_R_T03",0 +sz_FloorSpikes BYTE "FortTrapItemDefinition TID_Floor_Spikes_Athena_R_T03.TID_Floor_Spikes_Athena_R_T03",0 +sz_Campfire BYTE "FortTrapItemDefinition TID_Floor_Player_Campfire_Athena.TID_Floor_Player_Campfire_Athena",0 +sz_Wood BYTE "FortResourceItemDefinition WoodItemData.WoodItemData",0 +sz_Stone BYTE "FortResourceItemDefinition StoneItemData.StoneItemData",0 +sz_Metal BYTE "FortResourceItemDefinition MetalItemData.MetalItemData",0 +sz_AmmoShells BYTE "FortAmmoItemDefinition AthenaAmmoDataShells.AthenaAmmoDataShells",0 +sz_AmmoEnergy BYTE "FortAmmoItemDefinition AthenaAmmoDataEnergyCell.AthenaAmmoDataEnergyCell",0 +sz_AmmoBulletsMedium BYTE "FortAmmoItemDefinition AthenaAmmoDataBulletsMedium.AthenaAmmoDataBulletsMedium",0 +sz_AmmoBulletsLight BYTE "FortAmmoItemDefinition AthenaAmmoDataBulletsLight.AthenaAmmoDataBulletsLight",0 +sz_AmmoBulletsHeavy BYTE "FortAmmoItemDefinition AthenaAmmoDataBulletsHeavy.AthenaAmmoDataBulletsHeavy",0 +sz_AmmoRockets BYTE "FortAmmoItemDefinition AmmoDataRockets.AmmoDataRockets",0 +sz_EditTool BYTE "FortEditToolItemDefinition EditTool.EditTool",0 + +.data? + +; Cached UFunction pointers +fn_HandleInvLocalUpdate QWORD ? +fn_ForceNetUpdate QWORD ? +fn_HandleWorldInvLocalUpdate QWORD ? +fn_OnRep_PrimaryQB QWORD ? +fn_OnRep_SecondaryQB QWORD ? +fn_ForceUpdateQB QWORD ? +fn_ServerAddItemInternal QWORD ? +fn_ServerRemoveItemInternal QWORD ? +fn_EmptySlot QWORD ? +fn_ServerActivateSlotInt QWORD ? +fn_OnRep_QuickBar QWORD ? +fn_CreateTempItemBP QWORD ? +fn_SetOwningController QWORD ? +fn_EquipWeaponDef QWORD ? +fn_OnRep_WeaponData QWORD ? +fn_OnRep_AmmoCount QWORD ? +fn_ClientGivenTo QWORD ? +fn_ClientInternalEquipWeapon QWORD ? +fn_OnRep_CurrentWeapon QWORD ? +fn_SetOwner QWORD ? + +; Cached item definitions for Init +def_Wall QWORD ? +def_Floor QWORD ? +def_Stair QWORD ? +def_Roof QWORD ? +def_LaunchPad QWORD ? +def_WallElectric QWORD ? +def_FloorSpikes QWORD ? +def_Campfire QWORD ? +def_Wood QWORD ? +def_Stone QWORD ? +def_Metal QWORD ? +def_AmmoShells QWORD ? +def_AmmoEnergy QWORD ? +def_AmmoBullMedium QWORD ? +def_AmmoBullLight QWORD ? +def_AmmoBullHeavy QWORD ? +def_AmmoRockets QWORD ? +def_EditTool QWORD ? + +.code + +; Internal helper: Inventory__LoadFn +; In : RSI = &cache QWORD +; RDI = szPath (BYTE*) +; Out: RAX = UFunction* (may be NULL on lookup failure) +; Allocates shadow for SDK_FindObject call (entry RSP=8 -> CALL->0 ). +Inventory__LoadFn PROC + mov rax, QWORD PTR [rsi] + test rax, rax + jnz @ILF_done + sub rsp, 20h + mov rcx, rdi + call SDK_FindObject + add rsp, 20h + mov QWORD PTR [rsi], rax +@ILF_done: + ret +Inventory__LoadFn ENDP + + +; Inventory_Update +; In : RCX = AFortPlayerControllerAthena* +; EDX = Dirty (int, 0 = no mark-item-dirty) +; R8B = bRemovedItem (bool) +; +; Fires replication notifications on inventory and quickbar. +; +; Stack: 5 pushes (40) + sub 80h (128) = 168; 8-168=-160=0 +; +; Frame: +; [+00..+1F] shadow +; [+20..+27] arg5 slot +; [+28..+2F] zero-param scratch (empty ProcessEvent params) +; [+30..+33] ForceUpdateQuickbar params (EFortQuickBars, BYTE->padded) +Inventory_Update PROC + push rbx + push rbp + push rsi + push rdi + push r12 + sub rsp, 80h + + ; Save parameters in stable callee-saved registers + mov r12, rcx ; R12 = PC (callee-saved) + mov ebx, edx ; EBX = Dirty + movzx ebp, r8b ; EBP = bRemovedItem + + ; RSI/RDI will be used transiently for Inventory__LoadFn args; + ; R12 persists across calls since r12 is callee-saved. + + mov rdi, QWORD PTR [r12 + PC_WorldInventory] ; WorldInventory + test rdi, rdi + jz @IU2_done + + ; bRequiresLocalUpdate = true + mov BYTE PTR [rdi + AFI_bRequiresLocalUpdate], 1 + + ; HandleInventoryLocalUpdate + lea rsi, [fn_HandleInvLocalUpdate] + lea rdi, [sz_HandleInvLocalUpdate] + call Inventory__LoadFn + mov rdi, QWORD PTR [r12 + PC_WorldInventory] ; reload after call + test rax, rax + jz @IU2_fnu + xor r8d, r8d + mov QWORD PTR [rsp + 28h], r8 + mov rcx, rdi + mov rdx, rax + lea r8, [rsp + 28h] + call QWORD PTR [ProcessEvent] + +@IU2_fnu: + ; ForceNetUpdate on WorldInventory + lea rsi, [fn_ForceNetUpdate] + lea rdi, [sz_ForceNetUpdate] + call Inventory__LoadFn + mov rdi, QWORD PTR [r12 + PC_WorldInventory] + test rax, rax + jz @IU2_hwilu + xor r8d, r8d + mov QWORD PTR [rsp + 28h], r8 + mov rcx, rdi + mov rdx, rax + lea r8, [rsp + 28h] + call QWORD PTR [ProcessEvent] + +@IU2_hwilu: + ; HandleWorldInventoryLocalUpdate on PC + lea rsi, [fn_HandleWorldInvLocalUpdate] + lea rdi, [sz_HandleWorldInvLocalUpdate] + call Inventory__LoadFn + test rax, rax + jz @IU2_priqb + xor r8d, r8d + mov QWORD PTR [rsp + 28h], r8 + mov rcx, r12 + mov rdx, rax + lea r8, [rsp + 28h] + call QWORD PTR [ProcessEvent] + +@IU2_priqb: + ; Get QuickBars + mov rdi, QWORD PTR [r12 + PC_QuickBars] + test rdi, rdi + jz @IU2_done + + ; OnRep_PrimaryQuickBar + lea rsi, [fn_OnRep_PrimaryQB] + lea rdi, [sz_OnRep_PrimaryQB] + call Inventory__LoadFn + mov rdi, QWORD PTR [r12 + PC_QuickBars] + test rax, rax + jz @IU2_secqb + xor r8d, r8d + mov QWORD PTR [rsp + 28h], r8 + mov rcx, rdi + mov rdx, rax + lea r8, [rsp + 28h] + call QWORD PTR [ProcessEvent] + +@IU2_secqb: + ; OnRep_SecondaryQuickBar + lea rsi, [fn_OnRep_SecondaryQB] + lea rdi, [sz_OnRep_SecondaryQB] + call Inventory__LoadFn + mov rdi, QWORD PTR [r12 + PC_QuickBars] + test rax, rax + jz @IU2_fnu_qb + xor r8d, r8d + mov QWORD PTR [rsp + 28h], r8 + mov rcx, rdi + mov rdx, rax + lea r8, [rsp + 28h] + call QWORD PTR [ProcessEvent] + +@IU2_fnu_qb: + ; ForceNetUpdate on QuickBars + lea rsi, [fn_ForceNetUpdate] + lea rdi, [sz_ForceNetUpdate] + call Inventory__LoadFn + mov rdi, QWORD PTR [r12 + PC_QuickBars] + test rax, rax + jz @IU2_fuqb_pri + xor r8d, r8d + mov QWORD PTR [rsp + 28h], r8 + mov rcx, rdi + mov rdx, rax + lea r8, [rsp + 28h] + call QWORD PTR [ProcessEvent] + +@IU2_fuqb_pri: + ; ForceUpdateQuickbar(Primary=0) + lea rsi, [fn_ForceUpdateQB] + lea rdi, [sz_ForceUpdateQB] + call Inventory__LoadFn + test rax, rax + jz @IU2_fuqb_sec + xor r8d, r8d + mov DWORD PTR [rsp + 30h], r8d ; EFortQuickBars = Primary = 0 + mov rcx, r12 + mov rdx, rax + lea r8, [rsp + 30h] + call QWORD PTR [ProcessEvent] + +@IU2_fuqb_sec: + ; ForceUpdateQuickbar(Secondary=1) + lea rsi, [fn_ForceUpdateQB] + lea rdi, [sz_ForceUpdateQB] + call Inventory__LoadFn + test rax, rax + jz @IU2_done + mov DWORD PTR [rsp + 30h], 1 ; EFortQuickBars = Secondary = 1 + mov rcx, r12 + mov rdx, rax + lea r8, [rsp + 30h] + call QWORD PTR [ProcessEvent] + +@IU2_done: + add rsp, 80h + pop r12 + pop rdi + pop rsi + pop rbp + pop rbx + ret +Inventory_Update ENDP + + +; Inventory_IsGuidInInventory +; In : RCX = AFortPlayerControllerAthena* +; RDX = FGuid* (16-byte guid to search for) +; Out: AL = 1 if found, 0 if not +; +; Searches PrimaryQuickBar.Slots for a matching guid. +; +; Stack: push rbx push rbp push rsi push rdi = 4 pushes (no CALL sites) +Inventory_IsGuidInInventory PROC + push rbx + push rbp + push rsi + push rdi + + mov rbx, rcx ; RBX = PC + mov rsi, rdx ; RSI = &Guid to find + + ; QuickBars + mov rcx, QWORD PTR [rbx + PC_QuickBars] + test rcx, rcx + jz @IGII_false + + ; PrimaryQuickBar.Slots TArray: Data at [QB+QB_PrimarySlots], Num at [QB+QB_PrimarySlots+8] + mov rbp, QWORD PTR [rcx + QB_PrimarySlots] ; Slots.Data + mov edi, DWORD PTR [rcx + QB_PrimarySlots + 8] ; Slots.Num + test rbp, rbp + jz @IGII_false + test edi, edi + jz @IGII_false + + xor ebx, ebx ; slot index = 0 +@IGII_slot_loop: + cmp ebx, edi + jge @IGII_false + + ; slot = Slots.Data + ebx * QBSLOT_SIZE + imul rax, rbx, QBSLOT_SIZE + add rax, rbp ; &Slots[slot] + + ; Items.Data at [rax + QBSLOT_Items] + mov rcx, QWORD PTR [rax + QBSLOT_Items] ; Items.Data + mov edx, DWORD PTR [rax + QBSLOT_Items + 8] ; Items.Num + test rcx, rcx + jz @IGII_slot_next + test edx, edx + jz @IGII_slot_next + + xor r8d, r8d ; item index = 0 +@IGII_item_loop: + cmp r8d, edx + jge @IGII_slot_next + + ; &Items[i] = rcx + i*16 + imul r9, r8, FGUID_SIZE + add r9, rcx ; &Guid[i] + + ; Compare 16-byte GUIDs (two QWORD comparisons) + mov r10, QWORD PTR [r9] + mov r11, QWORD PTR [rsi] + cmp r10, r11 + jne @IGII_item_next + mov r10, QWORD PTR [r9 + 8] + mov r11, QWORD PTR [rsi + 8] + cmp r10, r11 + je @IGII_true + +@IGII_item_next: + inc r8d + jmp @IGII_item_loop + +@IGII_slot_next: + inc ebx + jmp @IGII_slot_loop + +@IGII_false: + xor eax, eax + pop rdi + pop rsi + pop rbp + pop rbx + ret + +@IGII_true: + mov eax, 1 + pop rdi + pop rsi + pop rbp + pop rbx + ret +Inventory_IsGuidInInventory ENDP + +; IsValidGuid is the same logic +Inventory_IsValidGuid PROC + jmp Inventory_IsGuidInInventory +Inventory_IsValidGuid ENDP + + +; Inventory_GetEntryInSlot +; In : RCX = AFortPlayerControllerAthena* +; EDX = Slot (int) +; R8D = Item (int, index within slot) +; R9D = Bars (EFortQuickBars: 0=Primary, 1=Secondary) +; Out: RAX = &FFortItemEntry (within UFortWorldItem) or NULL +; +; Stack: push rbx push rbp push rsi push rdi push r12 = 5 (no CALL sites) +Inventory_GetEntryInSlot PROC + push rbx + push rbp + push rsi + push rdi + push r12 + + mov rbx, rcx ; RBX = PC + mov ebp, edx ; EBP = Slot + mov esi, r8d ; ESI = Item + mov r12d, r9d ; R12D = Bars + + ; Get QuickBars + mov rcx, QWORD PTR [rbx + PC_QuickBars] + test rcx, rcx + jz @IGETS_null + + ; Get Slots TArray based on Bars + test r12d, r12d + jnz @IGETS_secondary + mov rdi, QWORD PTR [rcx + QB_PrimarySlots] ; Primary.Slots.Data + mov edx, DWORD PTR [rcx + QB_PrimarySlots + 8] + jmp @IGETS_slots_ok +@IGETS_secondary: + mov rdi, QWORD PTR [rcx + QB_SecondarySlots] + mov edx, DWORD PTR [rcx + QB_SecondarySlots + 8] + +@IGETS_slots_ok: + test rdi, rdi + jz @IGETS_null + cmp ebp, edx + jge @IGETS_null ; slot out of range + + ; &Slots[Slot] + imul rax, rbp, QBSLOT_SIZE + add rax, rdi + ; Items.Data + mov rcx, QWORD PTR [rax + QBSLOT_Items] + mov edx, DWORD PTR [rax + QBSLOT_Items + 8] + test rcx, rcx + jz @IGETS_null + cmp esi, edx + jge @IGETS_null + ; ToFindGuid = &Items[Item] + imul rax, rsi, FGUID_SIZE + add rax, rcx ; RAX = &FGuid to find + + ; Search ItemInstances for matching guid + mov rdi, QWORD PTR [rbx + PC_WorldInventory] + test rdi, rdi + jz @IGETS_null + mov rcx, QWORD PTR [rdi + AFI_ItemInstances] ; ItemInstances.Data + mov r8d, DWORD PTR [rdi + AFI_ItemInstances + 8] ; ItemInstances.Num + test rcx, rcx + jz @IGETS_null + + xor r9d, r9d ; j = 0 +@IGETS_inst_loop: + cmp r9d, r8d + jge @IGETS_null + mov r10, QWORD PTR [rcx + r9*8] ; ItemInstances[j] (UFortWorldItem*) + test r10, r10 + jz @IGETS_inst_next + ; Compare guid at [r10 + UWI_ItemEntry_ItemGuid] with [rax] + mov r11, QWORD PTR [r10 + UWI_ItemEntry_ItemGuid] + mov rbp, QWORD PTR [rax] + cmp r11, rbp + jne @IGETS_inst_next + mov r11, QWORD PTR [r10 + UWI_ItemEntry_ItemGuid + 8] + mov rbp, QWORD PTR [rax + 8] + cmp r11, rbp + je @IGETS_found +@IGETS_inst_next: + inc r9d + jmp @IGETS_inst_loop + +@IGETS_found: + ; Return &ItemInstance->ItemEntry + lea rax, [r10 + UWI_ItemEntry] + pop r12 + pop rdi + pop rsi + pop rbp + pop rbx + ret + +@IGETS_null: + xor eax, eax + pop r12 + pop rdi + pop rsi + pop rbp + pop rbx + ret +Inventory_GetEntryInSlot ENDP + + +; Inventory_AddItemToSlot +; In : RCX = AFortPlayerControllerAthena* +; RDX = UFortWorldItemDefinition* (Definition) +; R8D = Slot (int) +; R9D = Bars (EFortQuickBars) +; [rsp+40] = Count (int, arg5) +; Out: RAX = &FFortItemEntry (within world item) or NULL +; +; Stack: 5 pushes (40) + sub 0A0h (160) = 200; 8-200=-192=0 +; +; Frame: +; [+00..+1F] shadow +; [+20..+2F] CreateTemporaryItemInstanceBP params (Count/Level/RetVal) +; [+30..+37] SetOwningController params (InController QWORD) +; [+40..+57] ServerAddItemInternal params (FGuid16 + QB1 + pad3 + Slot4) +; [+60..+7F] locals: Count/PC/Def/Slot/Bars +Inventory_AddItemToSlot PROC + push rbx + push rbp + push rsi + push rdi + push r12 + sub rsp, 0A0h ; 5 pushes(40)+0xA0(160)=200; 8-200=-192=0 + + ; arg5 at entry_rsp+40 -> current_rsp+200+40 = rsp+0xE8 + mov eax, DWORD PTR [rsp + 0E8h] + mov DWORD PTR [rsp + 60h], eax ; [+60] = Count + + ; Save all params to locals (frame) + mov QWORD PTR [rsp + 68h], rcx ; [+68] = PC + mov QWORD PTR [rsp + 70h], rdx ; [+70] = Definition + mov DWORD PTR [rsp + 78h], r8d ; [+78] = Slot + mov DWORD PTR [rsp + 7Ch], r9d ; [+7C] = Bars + + ; Clamp slot + test r9d, r9d + jnz @IAITS3_nc + cmp r8d, 6 + jl @IAITS3_nc + mov DWORD PTR [rsp + 78h], 5 +@IAITS3_nc: + cmp DWORD PTR [rsp + 78h], 0 + jns @IAITS3_sk + mov DWORD PTR [rsp + 78h], 1 +@IAITS3_sk: + + ; CreateTemporaryItemInstanceBP params at [+20]: Count(4)+Level(4)+RetVal(8) + mov eax, DWORD PTR [rsp + 60h] + mov DWORD PTR [rsp + 20h], eax ; Count + mov DWORD PTR [rsp + 24h], 1 ; Level = 1 + mov QWORD PTR [rsp + 28h], 0 ; RetValue init + + lea rsi, [fn_CreateTempItemBP] + lea rdi, [sz_CreateTempItemBP] + call Inventory__LoadFn + + test rax, rax + jz @IAITS3_null + mov rcx, QWORD PTR [rsp + 70h] ; Definition + mov rdx, rax ; fn + lea r8, [rsp + 20h] + call QWORD PTR [ProcessEvent] + + mov rbp, QWORD PTR [rsp + 28h] ; TempItemInstance + test rbp, rbp + jz @IAITS3_null + + ; SetOwningControllerForTemporaryItem(TempItem, PC) + ; Params: InController(QWORD at +0) + mov rax, QWORD PTR [rsp + 68h] ; PC + mov QWORD PTR [rsp + 30h], rax + lea rsi, [fn_SetOwningController] + lea rdi, [sz_SetOwningController] + call Inventory__LoadFn + test rax, rax + jz @IAITS3_skip_soc + mov rcx, rbp ; TempItem + mov rdx, rax + lea r8, [rsp + 30h] + call QWORD PTR [ProcessEvent] +@IAITS3_skip_soc: + + ; TempItem->ItemEntry.Count = Count + mov eax, DWORD PTR [rsp + 60h] + mov DWORD PTR [rbp + UWI_ItemEntry_Count], eax + + ; TempItem->OwnerInventory = PC->WorldInventory + mov rcx, QWORD PTR [rsp + 68h] + mov rdx, QWORD PTR [rcx + PC_WorldInventory] + mov QWORD PTR [rbp + UWI_OwnerInventory], rdx + + ; TArray_Add(WorldInventory->Inventory.ReplicatedEntries, &TempItem->ItemEntry, FITEM_SIZEOF) + test rdx, rdx + jz @IAITS3_null + mov rbx, rdx ; RBX = WorldInventory + lea rcx, [rbx + AFI_ReplicatedEntries] ; &TArrayHeader + lea rdx, [rbp + UWI_ItemEntry] ; &ItemEntry + mov r8d, FITEM_SIZEOF + call TArray_Add + ; EAX = index _Idx + + mov r12d, eax ; R12D = _Idx + + ; TArray_Add(WorldInventory->Inventory.ItemInstances, &TempItem, 8) + push rbp ; push TempItem ptr onto stack + lea rcx, [rbx + AFI_ItemInstances] + lea rdx, [rsp] ; &TempItem on stack + mov r8d, 8 + call TArray_Add + add rsp, 8 + + ; ServerAddItemInternal(QuickBars, ItemGuid, Bars, Slot) + ; Params: FGuid(+0,16) + EFortQuickBars(+10,1,3pad) + Slot(+14,4) = 0x18 + mov rcx, QWORD PTR [rsp + 68h] ; PC + mov rcx, QWORD PTR [rcx + PC_QuickBars] + test rcx, rcx + jz @IAITS3_skip_sai + ; Copy Guid from TempItem->ItemEntry.ItemGuid (at RBP+UWI_ItemEntry_ItemGuid) + mov rax, QWORD PTR [rbp + UWI_ItemEntry_ItemGuid] + mov rdx, QWORD PTR [rbp + UWI_ItemEntry_ItemGuid + 8] + mov QWORD PTR [rsp + 40h], rax + mov QWORD PTR [rsp + 48h], rdx + mov eax, DWORD PTR [rsp + 7Ch] ; Bars + mov BYTE PTR [rsp + 50h], al ; EFortQuickBars (BYTE + 3pad) + mov DWORD PTR [rsp + 51h], 0 ; padding + mov eax, DWORD PTR [rsp + 78h] ; Slot + mov DWORD PTR [rsp + 54h], eax + ; Load fn + push rcx ; save QuickBars + lea rsi, [fn_ServerAddItemInternal] + lea rdi, [sz_ServerAddItemInternal] + call Inventory__LoadFn + pop rcx ; restore QuickBars + test rax, rax + jz @IAITS3_skip_sai + mov rdx, rax + lea r8, [rsp + 40h] + call QWORD PTR [ProcessEvent] + +@IAITS3_skip_sai: + ; Inventory_Update(PC, _Idx, false) + mov rcx, QWORD PTR [rsp + 68h] + mov edx, r12d + xor r8d, r8d + call Inventory_Update + + ; Return &TempItem->ItemEntry + lea rax, [rbp + UWI_ItemEntry] + add rsp, 0A0h + pop r12 + pop rdi + pop rsi + pop rbp + pop rbx + ret + +@IAITS3_null: + xor eax, eax + add rsp, 0A0h + pop r12 + pop rdi + pop rsi + pop rbp + pop rbx + ret +Inventory_AddItemToSlot ENDP + + +; Inventory_RemoveItemFromSlot +; In : RCX = AFortPlayerControllerAthena* +; EDX = Slot (int) +; R8D = Bars (EFortQuickBars) +; R9D = Amount (int, -1 = all items in slot) +; +; Stack: 5 pushes (40) + sub 0C0h (192) = 232; 8-232=-224=0 +; +; Frame: [+00..+1F] shadow; [+20..] locals +Inventory_RemoveItemFromSlot PROC + push rbx + push rbp + push rsi + push rdi + push r12 + sub rsp, 0C0h + + mov QWORD PTR [rsp + 60h], rcx ; [+60] = PC + mov DWORD PTR [rsp + 68h], edx ; [+68] = Slot + mov DWORD PTR [rsp + 6Ch], r8d ; [+6C] = Bars + mov DWORD PTR [rsp + 70h], r9d ; [+70] = Amount + + ; Get QuickBars + mov rbx, QWORD PTR [rcx + PC_QuickBars] + test rbx, rbx + jz @IRIFS_null + + ; Get WorldInventory + mov rdi, QWORD PTR [rcx + PC_WorldInventory] + test rdi, rdi + jz @IRIFS_null + + ; Determine slots TArray and slot ptr + mov eax, DWORD PTR [rsp + 6Ch] ; Bars + test eax, eax + jnz @IRIFS_secondary + + ; Primary: Slots.Data and Num at QB+QB_PrimarySlots + mov rsi, QWORD PTR [rbx + QB_PrimarySlots] + mov r12d, DWORD PTR [rbx + QB_PrimarySlots + 8] + jmp @IRIFS_slots_ok +@IRIFS_secondary: + mov rsi, QWORD PTR [rbx + QB_SecondarySlots] + mov r12d, DWORD PTR [rbx + QB_SecondarySlots + 8] + +@IRIFS_slots_ok: + test rsi, rsi + jz @IRIFS_null + mov eax, DWORD PTR [rsp + 68h] ; Slot + cmp eax, r12d + jge @IRIFS_null + + ; &Slots[Slot] + imul rax, rax, QBSLOT_SIZE + add rax, rsi ; RAX = &Slots[Slot] + + ; If Amount == -1: use Items.Num + mov ebp, DWORD PTR [rsp + 70h] + cmp ebp, -1 + jne @IRIFS_amount_ok + mov ebp, DWORD PTR [rax + QBSLOT_Items + 8] ; Items.Num +@IRIFS_amount_ok: + mov DWORD PTR [rsp + 74h], ebp ; save Amount to local + + ; Save &Slots[Slot] to local + mov QWORD PTR [rsp + 78h], rax ; [+78] = &Slots[Slot] + + ; bWasSuccessful = false + mov BYTE PTR [rsp + 80h], 0 + + ; Loop i = 0..Amount + xor r12d, r12d ; R12D = i +@IRIFS_item_loop: + cmp r12d, DWORD PTR [rsp + 74h] + jge @IRIFS_check_success + + ; ToRemoveGuid = &Slots[Slot].Items[i] + mov rax, QWORD PTR [rsp + 78h] ; &Slots[Slot] + mov rcx, QWORD PTR [rax + QBSLOT_Items] ; Items.Data + test rcx, rcx + jz @IRIFS_skip_item + imul rbp, r12, FGUID_SIZE + add rbp, rcx ; RBP = &ToRemoveGuid + + ; Remove from ItemInstances + mov rcx, QWORD PTR [rdi + AFI_ItemInstances] + mov edx, DWORD PTR [rdi + AFI_ItemInstances + 8] + test rcx, rcx + jz @IRIFS_skip_inst + + xor r9d, r9d +@IRIFS_inst_loop: + cmp r9d, edx + jge @IRIFS_skip_inst + mov rax, QWORD PTR [rcx + r9*8] + test rax, rax + jz @IRIFS_inst_next + + ; Compare guid at rax+UWI_ItemEntry_ItemGuid with [RBP] + mov r10, QWORD PTR [rax + UWI_ItemEntry_ItemGuid] + mov r11, QWORD PTR [rbp] + cmp r10, r11 + jne @IRIFS_inst_next + mov r10, QWORD PTR [rax + UWI_ItemEntry_ItemGuid + 8] + mov r11, QWORD PTR [rbp + 8] + cmp r10, r11 + jne @IRIFS_inst_next + + ; TArray_RemoveAt(ItemInstances, j, 8) + push rbp ; save RBP + push rdi ; save WorldInventory + push rcx ; save Items.Data (before RemoveAt) + push rdx ; save Num + lea rcx, [rdi + AFI_ItemInstances] + mov edx, r9d + mov r8d, 8 + call TArray_RemoveAt + pop rdx + pop rcx + pop rdi + pop rbp + mov BYTE PTR [rsp + 80h], 1 ; bWasSuccessful = true + + ; Reload after RemoveAt (array may have moved) + mov rcx, QWORD PTR [rdi + AFI_ItemInstances] + mov edx, DWORD PTR [rdi + AFI_ItemInstances + 8] + ; Don't increment r9d since RemoveAt shifts elements down + jmp @IRIFS_inst_loop + +@IRIFS_inst_next: + inc r9d + jmp @IRIFS_inst_loop + +@IRIFS_skip_inst: + ; Remove from ReplicatedEntries + mov rcx, QWORD PTR [rdi + AFI_ReplicatedEntries] + mov edx, DWORD PTR [rdi + AFI_ReplicatedEntries + 8] + test rcx, rcx + jz @IRIFS_skip_rep + + xor r9d, r9d +@IRIFS_rep_loop: + cmp r9d, edx + jge @IRIFS_skip_rep + ; &ReplicatedEntries[x] = rcx + x * FITEM_SIZEOF + imul rax, r9, FITEM_SIZEOF + add rax, rcx + ; Compare ItemGuid at rax+0x50 + mov r10, QWORD PTR [rax + 050h] + mov r11, QWORD PTR [rbp] + cmp r10, r11 + jne @IRIFS_rep_next + mov r10, QWORD PTR [rax + 050h + 8] + mov r11, QWORD PTR [rbp + 8] + cmp r10, r11 + jne @IRIFS_rep_next + + ; TArray_RemoveAt(ReplicatedEntries, x, FITEM_SIZEOF) + push rbp + push rdi + push rcx + push rdx + lea rcx, [rdi + AFI_ReplicatedEntries] + mov edx, r9d + mov r8d, FITEM_SIZEOF + call TArray_RemoveAt + pop rdx + pop rcx + pop rdi + pop rbp + mov BYTE PTR [rsp + 80h], 1 + + mov rcx, QWORD PTR [rdi + AFI_ReplicatedEntries] + mov edx, DWORD PTR [rdi + AFI_ReplicatedEntries + 8] + jmp @IRIFS_rep_loop + +@IRIFS_rep_next: + inc r9d + jmp @IRIFS_rep_loop + +@IRIFS_skip_rep: + ; ServerRemoveItemInternal(QuickBars, Guid, false, true) + ; Params: FGuid(16) + bFindReplacement(1) + bForce(1) = 18, pad to 24 + mov rax, QWORD PTR [rbp] + mov rdx, QWORD PTR [rbp + 8] + mov QWORD PTR [rsp + 38h], rax + mov QWORD PTR [rsp + 40h], rdx + mov BYTE PTR [rsp + 48h], 0 ; bFindReplacement = false + mov BYTE PTR [rsp + 49h], 1 ; bForce = true + push rbx + push rbp + lea rsi, [fn_ServerRemoveItemInternal] + lea rdi, [sz_ServerRemoveItemInternal] + call Inventory__LoadFn + pop rbp + pop rbx + test rax, rax + jz @IRIFS_skip_sri + mov rcx, rbx + mov rdx, rax + lea r8, [rsp + 38h] + call QWORD PTR [ProcessEvent] +@IRIFS_skip_sri: + + ; Zero out ToRemoveGuid (Reset) + xor eax, eax + mov QWORD PTR [rbp], rax + mov QWORD PTR [rbp + 8], rax + +@IRIFS_skip_item: + inc r12d + jmp @IRIFS_item_loop + +@IRIFS_check_success: + cmp BYTE PTR [rsp + 80h], 0 + je @IRIFS_call_update + + ; EmptySlot(QuickBars, Bars, Slot) + mov eax, DWORD PTR [rsp + 6Ch] + mov edx, DWORD PTR [rsp + 68h] + mov DWORD PTR [rsp + 38h], eax ; EFortQuickBars (BYTE) + mov DWORD PTR [rsp + 3Ch], edx ; SlotIndex (int) + push rbx + lea rsi, [fn_EmptySlot] + lea rdi, [sz_EmptySlot] + call Inventory__LoadFn + pop rbx + test rax, rax + jz @IRIFS_skip_es + mov rcx, rbx + mov rdx, rax + lea r8, [rsp + 38h] + call QWORD PTR [ProcessEvent] +@IRIFS_skip_es: + + ; FreeArray on Slots[Slot].Items + mov rax, QWORD PTR [rsp + 78h] ; &Slots[Slot] + lea rcx, [rax + QBSLOT_Items] + call TArray_FreeArray + +@IRIFS_call_update: + ; Inventory_Update(PC, 0, true) + mov rcx, QWORD PTR [rsp + 60h] + xor edx, edx + mov r8d, 1 + call Inventory_Update + + movzx eax, BYTE PTR [rsp + 80h] ; return bWasSuccessful + add rsp, 0C0h + pop r12 + pop rdi + pop rsi + pop rbp + pop rbx + ret + +@IRIFS_null: + xor eax, eax + add rsp, 0C0h + pop r12 + pop rdi + pop rsi + pop rbp + pop rbx + ret +Inventory_RemoveItemFromSlot ENDP + + +; Inventory_EquipWeaponDefinition +; In : RCX = APawn* (pawn) +; RDX = UFortWeaponItemDefinition* +; R8 = FGuid* (16-byte) +; +; Stack: 5 pushes (40) + sub 80h (128) = 168; 8-168=-160=0 +; +; Frame: +; [+00..+1F] shadow +; [+20..+3F] EquipWeaponDefinition params (WeaponData+Guid+RetVal = 0x20) +; [+40..+47] ProcessEvent null-params scratch / SetOwner params +; [+60..+67] Weapon (saved after EquipWeaponDefinition call) +; [+70..+7F] Guid copy +Inventory_EquipWeaponDefinition PROC + push rbx + push rbp + push rsi + push rdi + push r12 + sub rsp, 80h + + mov r12, rcx ; R12 = Pawn + mov rbx, rdx ; RBX = Definition + mov rax, QWORD PTR [r8] + mov QWORD PTR [rsp + 70h], rax + mov rax, QWORD PTR [r8 + 8] + mov QWORD PTR [rsp + 78h], rax + + ; Get Controller + mov rbp, QWORD PTR [r12 + 0228h] + test rbp, rbp + jz @IEWD3_done + + ; IsGuidInInventory check + mov rcx, rbp + lea rdx, [rsp + 70h] + call Inventory_IsGuidInInventory + test al, al + jz @IEWD3_done + + ; EquipWeaponDefinition via ProcessEvent + mov QWORD PTR [rsp + 20h], rbx + mov rax, QWORD PTR [rsp + 70h] + mov QWORD PTR [rsp + 28h], rax + mov rax, QWORD PTR [rsp + 78h] + mov QWORD PTR [rsp + 30h], rax + mov QWORD PTR [rsp + 38h], 0 + + lea rsi, [fn_EquipWeaponDef] + lea rdi, [sz_EquipWeaponDef] + call Inventory__LoadFn + test rax, rax + jz @IEWD3_done + mov rcx, r12 + mov rdx, rax + lea r8, [rsp + 20h] + call QWORD PTR [ProcessEvent] + + mov rbp, QWORD PTR [rsp + 38h] ; Weapon + test rbp, rbp + jz @IEWD3_done + mov QWORD PTR [rsp + 60h], rbp ; save Weapon to local + + ; Weapon->WeaponData = Definition + mov QWORD PTR [rbp + FW_WeaponData], rbx + ; Weapon->ItemEntryGuid = Guid + mov rax, QWORD PTR [rsp + 70h] + mov QWORD PTR [rbp + FW_ItemEntryGuid], rax + mov rax, QWORD PTR [rsp + 78h] + mov QWORD PTR [rbp + FW_ItemEntryGuid + 8], rax + + ; SetOwner(Weapon, Pawn) + mov QWORD PTR [rsp + 40h], r12 + lea rsi, [fn_SetOwner] + lea rdi, [sz_SetOwner] + call Inventory__LoadFn + test rax, rax + jz @IEWD3_skip_so + mov rcx, QWORD PTR [rsp + 60h] ; Weapon + mov rdx, rax + lea r8, [rsp + 40h] + call QWORD PTR [ProcessEvent] +@IEWD3_skip_so: + + ; OnRep_ReplicatedWeaponData(Weapon) + lea rsi, [fn_OnRep_WeaponData] + lea rdi, [sz_OnRep_WeaponData] + call Inventory__LoadFn + test rax, rax + jz @IEWD3_skip_rrwd + xor r8d, r8d + mov QWORD PTR [rsp + 40h], r8 + mov rcx, QWORD PTR [rsp + 60h] + mov rdx, rax + lea r8, [rsp + 40h] + call QWORD PTR [ProcessEvent] +@IEWD3_skip_rrwd: + + ; OnRep_AmmoCount(Weapon) + lea rsi, [fn_OnRep_AmmoCount] + lea rdi, [sz_OnRep_AmmoCount] + call Inventory__LoadFn + test rax, rax + jz @IEWD3_skip_rac + xor r8d, r8d + mov QWORD PTR [rsp + 40h], r8 + mov rcx, QWORD PTR [rsp + 60h] + mov rdx, rax + lea r8, [rsp + 40h] + call QWORD PTR [ProcessEvent] +@IEWD3_skip_rac: + + ; ClientGivenTo(Weapon, Pawn) + mov QWORD PTR [rsp + 40h], r12 + lea rsi, [fn_ClientGivenTo] + lea rdi, [sz_ClientGivenTo] + call Inventory__LoadFn + test rax, rax + jz @IEWD3_skip_cgt + mov rcx, QWORD PTR [rsp + 60h] + mov rdx, rax + lea r8, [rsp + 40h] + call QWORD PTR [ProcessEvent] +@IEWD3_skip_cgt: + + ; ClientInternalEquipWeapon(Pawn, Weapon) + mov rax, QWORD PTR [rsp + 60h] + mov QWORD PTR [rsp + 40h], rax + lea rsi, [fn_ClientInternalEquipWeapon] + lea rdi, [sz_ClientInternalEquipWeapon] + call Inventory__LoadFn + test rax, rax + jz @IEWD3_skip_ciew + mov rcx, r12 + mov rdx, rax + lea r8, [rsp + 40h] + call QWORD PTR [ProcessEvent] +@IEWD3_skip_ciew: + + ; OnRep_CurrentWeapon(Pawn) + lea rsi, [fn_OnRep_CurrentWeapon] + lea rdi, [sz_OnRep_CurrentWeapon] + call Inventory__LoadFn + test rax, rax + jz @IEWD3_done + xor r8d, r8d + mov QWORD PTR [rsp + 40h], r8 + mov rcx, r12 + mov rdx, rax + lea r8, [rsp + 40h] + call QWORD PTR [ProcessEvent] + +@IEWD3_done: + add rsp, 80h + pop r12 + pop rdi + pop rsi + pop rbp + pop rbx + ret +Inventory_EquipWeaponDefinition ENDP + + +; Inventory_OnDrop +; In : RCX = AFortPlayerControllerAthena* +; RDX = AFortPlayerController_ServerAttemptInventoryDrop_Params* +; (FGuid ItemGuid at [RDX+0]) +; Out: AL = 1 if successful +; +; Stack: 5 pushes (40) + sub 70h (112) = 152; 8-152=-144=0 +Inventory_OnDrop PROC + push rbx + push rbp + push rsi + push rdi + push r12 + sub rsp, 70h + + mov r12, rcx ; R12 = PC + mov rbp, rdx ; RBP = Params + + ; bWasSuccessful = 0 + xor ebx, ebx + + ; Get QuickBars + mov rsi, QWORD PTR [r12 + PC_QuickBars] + test rsi, rsi + jz @IOD_done + + ; Iterate PrimaryQuickBar.Slots[1..Num] looking for ItemGuid + mov rdi, QWORD PTR [rsi + QB_PrimarySlots] + mov r8d, DWORD PTR [rsi + QB_PrimarySlots + 8] + test rdi, rdi + jz @IOD_check_secondary + test r8d, r8d + jz @IOD_check_secondary + + mov r9d, 1 ; start at slot 1 (skip pickaxe slot 0) +@IOD_pri_slot_loop: + cmp r9d, r8d + jge @IOD_check_secondary + + imul rax, r9, QBSLOT_SIZE + add rax, rdi ; &Slots[i] + mov rcx, QWORD PTR [rax + QBSLOT_Items] + test rcx, rcx + jz @IOD_pri_slot_next + mov edx, DWORD PTR [rax + QBSLOT_Items + 8] + test edx, edx + jz @IOD_pri_slot_next + + ; For each item in slot + xor r10d, r10d +@IOD_pri_item_loop: + cmp r10d, edx + jge @IOD_pri_slot_next + imul r11, r10, FGUID_SIZE + add r11, rcx ; &Items[j] + ; Compare with Params->ItemGuid at [RBP+0] + mov rax, QWORD PTR [r11] + mov rdx, QWORD PTR [rbp] + cmp rax, rdx + jne @IOD_pri_item_next + mov rax, QWORD PTR [r11 + 8] + mov rdx, QWORD PTR [rbp + 8] + cmp rax, rdx + jne @IOD_pri_item_next + + ; Found! RemoveItemFromSlot(PC, slot, Primary, j+1) -> spawn pickup + mov rcx, r12 + mov edx, r9d + xor r8d, r8d ; Primary = 0 + mov r9d, r10d + inc r9d ; Amount = j+1 + call Inventory_RemoveItemFromSlot + test al, al + jz @IOD_done + + ; Spawn pickup: SummonPickup(Pawn, ItemDef, 1, PawnLocation) + ; ItemDef comes from the ItemInstance we need to find + mov ebx, 1 + jmp @IOD_done + +@IOD_pri_item_next: + inc r10d + jmp @IOD_pri_item_loop + +@IOD_pri_slot_next: + inc r9d + jmp @IOD_pri_slot_loop + +@IOD_check_secondary: + +@IOD_done: + ; Select pickaxe (slot 0) + mov rax, QWORD PTR [r12 + PC_QuickBars] + test rax, rax + jz @IOD_return + mov rcx, QWORD PTR [rax + QB_PrimarySlots] + test rcx, rcx + jz @IOD_return + mov rcx, QWORD PTR [rcx] ; Slots[0].Items.Data + test rcx, rcx + jz @IOD_return + ; 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 + +@IOD_return: + movzx eax, bl + add rsp, 70h + pop r12 + pop rdi + pop rsi + pop rbp + pop rbx + ret +Inventory_OnDrop ENDP + + +; Inventory_OnPickup +; In : RCX = AFortPlayerControllerAthena* +; RDX = AFortPlayerPawn_ServerHandlePickup_Params* +; Pickup* at [RDX+0] +; +; Stack: 5 pushes (40) + sub 70h (112) = 152; 8-152=-144=0 +Inventory_OnPickup PROC + push rbx + push rbp + push rsi + push rdi + push r12 + sub rsp, 70h + + mov r12, rcx ; R12 = PC + mov rbp, rdx ; RBP = Params + + ; Params->Pickup = [RBP + 0] + mov rbx, QWORD PTR [rbp] ; Pickup (AFortPickup*) + test rbx, rbx + jz @IOP_done + + ; ItemDef = Pickup->PrimaryPickupItemEntry.ItemDefinition (at AFortPickup+0x0368) + mov rsi, QWORD PTR [rbx + 0368h] ; ItemDefinition + test rsi, rsi + jz @IOP_done + + ; Count = Pickup->PrimaryPickupItemEntry.Count (at AFortPickup+0x035C) + mov edi, DWORD PTR [rbx + 035Ch] + + ; Find empty slot in PrimaryQuickBar.Slots[1..6] + mov rax, QWORD PTR [r12 + PC_QuickBars] + test rax, rax + jz @IOP_done + mov rcx, QWORD PTR [rax + QB_PrimarySlots] + mov edx, DWORD PTR [rax + QB_PrimarySlots + 8] + test rcx, rcx + jz @IOP_done + + mov r9d, 1 +@IOP_pri_slot_loop: + cmp r9d, 6 + jge @IOP_done + cmp r9d, edx + jge @IOP_done + + imul r10, r9, QBSLOT_SIZE + add r10, rcx + ; Check if slot is empty: Items.Data == NULL + mov r11, QWORD PTR [r10 + QBSLOT_Items] + test r11, r11 + jz @IOP_slot_found + + inc r9d + jmp @IOP_pri_slot_loop + +@IOP_slot_found: + ; AddItemToSlot(PC, ItemDef, slot, Primary, Count) + mov rcx, r12 + mov rdx, rsi ; ItemDef + mov r8d, r9d ; Slot + xor r9d, r9d ; Primary = 0 + mov DWORD PTR [rsp + 20h], edi ; Count (arg5) + call Inventory_AddItemToSlot + test rax, rax + jz @IOP_done + mov r8, rax ; &ItemEntry returned + + ; Set Pickup->PickupLocationData.PickupTarget = Pawn + ; PickupLocationData at AFortPickup+0x0428; PickupTarget at +0x00 within = AFortPickup+0x0428 + mov rcx, QWORD PTR [r12 + ACTRL_Pawn] + test rcx, rcx + jz @IOP_skip_pldata + mov rax, QWORD PTR [r12 + ACTRL_Pawn] + mov rax, QWORD PTR [r12 + 0260h] ; Pawn + mov QWORD PTR [rbx + 0428h], rax ; PickupTarget + mov QWORD PTR [rbx + 0438h], rax ; ItemOwner + + ; FlyTime = 0.4f + mov DWORD PTR [rbx + 0458h], 3ECCCCCDh ; 0.4f IEEE + + ; TODO: OnRep_PickupLocationData + +@IOP_skip_pldata: + ; bPickedUp = true + mov BYTE PTR [rbx + 04C0h], 1 + + ; TODO: OnRep_bPickedUp + + ; LoadedAmmo from pickup to instance + mov eax, DWORD PTR [rbx + 0378h] + mov DWORD PTR [r8 + 028h], eax + + ; Update inventory + mov rcx, r12 + xor edx, edx + xor r8d, r8d + call Inventory_Update + +@IOP_done: + add rsp, 70h + pop r12 + pop rdi + pop rsi + pop rbp + pop rbx + ret +Inventory_OnPickup ENDP + + +; Inventory_Init +; In : RCX = AFortPlayerControllerAthena* +; Spawns QuickBars, adds default items (buildings, traps, resources, ammo, edit tool). +; +; Stack: 5 pushes (40) + sub 80h (128) = 168; 8-168=-160=0 +Inventory_Init PROC + push rbx + push rbp + push rsi + push rdi + push r12 + sub rsp, 80h + + mov r12, rcx ; R12 = PC + + ; Spawn QuickBars + lea rcx, [sz_FortQuickBarsClass] + call SDK_FindClass + test rax, rax + jz @II_no_qb + ; SpawnActor at { -280, 400, 3000 } + ; Build FVector on stack + mov DWORD PTR [rsp + 30h], 0C38C0000h ; -280.0f + mov DWORD PTR [rsp + 34h], 43C80000h ; 400.0f + mov DWORD PTR [rsp + 38h], 453B8000h ; 3000.0f + mov rcx, rax ; QuickBars class + lea rdx, [rsp + 30h] ; &FVector + mov r8, r12 ; owner = PC + call Spawners_SpawnActor + test rax, rax + jz @II_no_qb + + ; PC->QuickBars = spawned QuickBars + mov QWORD PTR [r12 + PC_QuickBars], rax + mov rbp, rax ; RBP = QuickBars + + ; OnRep_QuickBar on PC + lea rsi, [fn_OnRep_QuickBar] + lea rdi, [sz_OnRep_QuickBar] + call Inventory__LoadFn + test rax, rax + jz @II_no_qb + xor r8d, r8d + mov QWORD PTR [rsp + 40h], r8 + mov rcx, r12 + mov rdx, rax + lea r8, [rsp + 40h] + call QWORD PTR [ProcessEvent] + +@II_no_qb: + + ; EBX = secondary slot counter (0-7) + xor ebx, ebx + + ; Helper: finds object if not cached, adds to secondary slot + ; ITEM_DEFINE loads item def into RDI, then calls AddItemToSlot + + ; Wall + mov rdi, QWORD PTR [def_Wall] + test rdi, rdi + jnz @II_wall_ok + lea rcx, [sz_WallDef] + call SDK_FindObject + mov QWORD PTR [def_Wall], rax + mov rdi, rax +@II_wall_ok: + test rdi, rdi + jz @II_floor + mov rcx, r12 + mov rdx, rdi + mov r8d, ebx + mov r9d, QB_SECONDARY + mov DWORD PTR [rsp + 20h], 1 + call Inventory_AddItemToSlot + inc ebx + +@II_floor: + mov rdi, QWORD PTR [def_Floor] + test rdi, rdi + jnz @II_floor_ok + lea rcx, [sz_FloorDef] + call SDK_FindObject + mov QWORD PTR [def_Floor], rax + mov rdi, rax +@II_floor_ok: + test rdi, rdi + jz @II_stair + mov rcx, r12 + mov rdx, rdi + mov r8d, ebx + mov r9d, QB_SECONDARY + mov DWORD PTR [rsp + 20h], 1 + call Inventory_AddItemToSlot + inc ebx + +@II_stair: + mov rdi, QWORD PTR [def_Stair] + test rdi, rdi + jnz @II_stair_ok + lea rcx, [sz_StairDef] + call SDK_FindObject + mov QWORD PTR [def_Stair], rax + mov rdi, rax +@II_stair_ok: + test rdi, rdi + jz @II_roof + mov rcx, r12 + mov rdx, rdi + mov r8d, ebx + mov r9d, QB_SECONDARY + mov DWORD PTR [rsp + 20h], 1 + call Inventory_AddItemToSlot + inc ebx + +@II_roof: + mov rdi, QWORD PTR [def_Roof] + test rdi, rdi + jnz @II_roof_ok + lea rcx, [sz_RoofDef] + call SDK_FindObject + mov QWORD PTR [def_Roof], rax + mov rdi, rax +@II_roof_ok: + test rdi, rdi + jz @II_launchpad + mov rcx, r12 + mov rdx, rdi + mov r8d, ebx + mov r9d, QB_SECONDARY + mov DWORD PTR [rsp + 20h], 1 + call Inventory_AddItemToSlot + inc ebx + +@II_launchpad: + mov rdi, QWORD PTR [def_LaunchPad] + test rdi, rdi + jnz @II_lp_ok + lea rcx, [sz_LaunchPad] + call SDK_FindObject + mov QWORD PTR [def_LaunchPad], rax + mov rdi, rax +@II_lp_ok: + test rdi, rdi + jz @II_wallelectric + mov rcx, r12 + mov rdx, rdi + mov r8d, ebx + mov r9d, QB_SECONDARY + mov DWORD PTR [rsp + 20h], 1 + call Inventory_AddItemToSlot + inc ebx + +@II_wallelectric: + mov rdi, QWORD PTR [def_WallElectric] + test rdi, rdi + jnz @II_we_ok + lea rcx, [sz_WallElectric] + call SDK_FindObject + mov QWORD PTR [def_WallElectric], rax + mov rdi, rax +@II_we_ok: + test rdi, rdi + jz @II_floorspikes + mov rcx, r12 + mov rdx, rdi + mov r8d, ebx + mov r9d, QB_SECONDARY + mov DWORD PTR [rsp + 20h], 1 + call Inventory_AddItemToSlot + inc ebx + +@II_floorspikes: + mov rdi, QWORD PTR [def_FloorSpikes] + test rdi, rdi + jnz @II_fs_ok + lea rcx, [sz_FloorSpikes] + call SDK_FindObject + mov QWORD PTR [def_FloorSpikes], rax + mov rdi, rax +@II_fs_ok: + test rdi, rdi + jz @II_campfire + mov rcx, r12 + mov rdx, rdi + mov r8d, ebx + mov r9d, QB_SECONDARY + mov DWORD PTR [rsp + 20h], 1 + call Inventory_AddItemToSlot + inc ebx + +@II_campfire: + mov rdi, QWORD PTR [def_Campfire] + test rdi, rdi + jnz @II_cf_ok + lea rcx, [sz_Campfire] + call SDK_FindObject + mov QWORD PTR [def_Campfire], rax + mov rdi, rax +@II_cf_ok: + test rdi, rdi + jz @II_resources + mov rcx, r12 + mov rdx, rdi + mov r8d, ebx + mov r9d, QB_SECONDARY + mov DWORD PTR [rsp + 20h], 1 + call Inventory_AddItemToSlot + + ; Resources and ammo: slot 0, count 999 +@II_resources: + ; Wood + mov rdi, QWORD PTR [def_Wood] + test rdi, rdi + jnz @II_wood_ok + lea rcx, [sz_Wood] + call SDK_FindObject + mov QWORD PTR [def_Wood], rax + mov rdi, rax +@II_wood_ok: + test rdi, rdi + jz @II_stone + mov rcx, r12 + mov rdx, rdi + xor r8d, r8d + mov r9d, QB_SECONDARY + mov DWORD PTR [rsp + 20h], 999 + call Inventory_AddItemToSlot + +@II_stone: + mov rdi, QWORD PTR [def_Stone] + test rdi, rdi + jnz @II_stone_ok + lea rcx, [sz_Stone] + call SDK_FindObject + mov QWORD PTR [def_Stone], rax + mov rdi, rax +@II_stone_ok: + test rdi, rdi + jz @II_metal + mov rcx, r12 + mov rdx, rdi + xor r8d, r8d + mov r9d, QB_SECONDARY + mov DWORD PTR [rsp + 20h], 999 + call Inventory_AddItemToSlot + +@II_metal: + mov rdi, QWORD PTR [def_Metal] + test rdi, rdi + jnz @II_metal_ok + lea rcx, [sz_Metal] + call SDK_FindObject + mov QWORD PTR [def_Metal], rax + mov rdi, rax +@II_metal_ok: + test rdi, rdi + jz @II_ammo + mov rcx, r12 + mov rdx, rdi + xor r8d, r8d + mov r9d, QB_SECONDARY + mov DWORD PTR [rsp + 20h], 999 + call Inventory_AddItemToSlot + +@II_ammo: + ; Ammo items (all slot 0 secondary, count 999) + ; AmmoShells + mov rdi, QWORD PTR [def_AmmoShells] + test rdi, rdi + jnz @II_ash_ok + lea rcx, [sz_AmmoShells] + call SDK_FindObject + mov QWORD PTR [def_AmmoShells], rax + mov rdi, rax +@II_ash_ok: + test rdi, rdi + jz @II_aenergy + mov rcx, r12 + mov rdx, rdi + xor r8d, r8d + mov r9d, QB_SECONDARY + mov DWORD PTR [rsp + 20h], 999 + call Inventory_AddItemToSlot + +@II_aenergy: + mov rdi, QWORD PTR [def_AmmoEnergy] + test rdi, rdi + jnz @II_ae_ok + lea rcx, [sz_AmmoEnergy] + call SDK_FindObject + mov QWORD PTR [def_AmmoEnergy], rax + mov rdi, rax +@II_ae_ok: + test rdi, rdi + jz @II_abm + mov rcx, r12 + mov rdx, rdi + xor r8d, r8d + mov r9d, QB_SECONDARY + mov DWORD PTR [rsp + 20h], 999 + call Inventory_AddItemToSlot + +@II_abm: + mov rdi, QWORD PTR [def_AmmoBullMedium] + test rdi, rdi + jnz @II_abm_ok + lea rcx, [sz_AmmoBulletsMedium] + call SDK_FindObject + mov QWORD PTR [def_AmmoBullMedium], rax + mov rdi, rax +@II_abm_ok: + test rdi, rdi + jz @II_abl + mov rcx, r12 + mov rdx, rdi + xor r8d, r8d + mov r9d, QB_SECONDARY + mov DWORD PTR [rsp + 20h], 999 + call Inventory_AddItemToSlot + +@II_abl: + mov rdi, QWORD PTR [def_AmmoBullLight] + test rdi, rdi + jnz @II_abl_ok + lea rcx, [sz_AmmoBulletsLight] + call SDK_FindObject + mov QWORD PTR [def_AmmoBullLight], rax + mov rdi, rax +@II_abl_ok: + test rdi, rdi + jz @II_abh + mov rcx, r12 + mov rdx, rdi + xor r8d, r8d + mov r9d, QB_SECONDARY + mov DWORD PTR [rsp + 20h], 999 + call Inventory_AddItemToSlot + +@II_abh: + mov rdi, QWORD PTR [def_AmmoBullHeavy] + test rdi, rdi + jnz @II_abh_ok + lea rcx, [sz_AmmoBulletsHeavy] + call SDK_FindObject + mov QWORD PTR [def_AmmoBullHeavy], rax + mov rdi, rax +@II_abh_ok: + test rdi, rdi + jz @II_arockets + mov rcx, r12 + mov rdx, rdi + xor r8d, r8d + mov r9d, QB_SECONDARY + mov DWORD PTR [rsp + 20h], 999 + call Inventory_AddItemToSlot + +@II_arockets: + mov rdi, QWORD PTR [def_AmmoRockets] + test rdi, rdi + jnz @II_ar_ok + lea rcx, [sz_AmmoRockets] + call SDK_FindObject + mov QWORD PTR [def_AmmoRockets], rax + mov rdi, rax +@II_ar_ok: + test rdi, rdi + jz @II_edittool + mov rcx, r12 + mov rdx, rdi + xor r8d, r8d + mov r9d, QB_SECONDARY + mov DWORD PTR [rsp + 20h], 999 + call Inventory_AddItemToSlot + +@II_edittool: + ; EditTool: Primary slot 0, count 1 + mov rdi, QWORD PTR [def_EditTool] + test rdi, rdi + jnz @II_et_ok + lea rcx, [sz_EditTool] + call SDK_FindObject + mov QWORD PTR [def_EditTool], rax + mov rdi, rax +@II_et_ok: + test rdi, rdi + jz @II_activate + mov rcx, r12 + mov rdx, rdi + xor r8d, r8d ; Slot = 0 + xor r9d, r9d ; Primary = 0 + mov DWORD PTR [rsp + 20h], 1 + call Inventory_AddItemToSlot + +@II_activate: + ; ServerActivateSlotInternal(QuickBars, Primary, 0, 0.0f, false) + mov rax, QWORD PTR [r12 + PC_QuickBars] + test rax, rax + jz @II_done + ; Params: InQuickBar(1B,3pad) + Slot(4) + ActivateDelay(4f) + bUpdate(1B) = 0x10 + xor edx, edx + mov DWORD PTR [rsp + 38h], edx ; InQuickBar = Primary = 0 + mov DWORD PTR [rsp + 3Ch], edx ; Slot = 0 + mov DWORD PTR [rsp + 40h], edx ; ActivateDelay = 0.0f + mov BYTE PTR [rsp + 44h], 0 ; bUpdatePreviousFocusedSlot = false + push rax ; save QuickBars + lea rsi, [fn_ServerActivateSlotInt] + lea rdi, [sz_ServerActivateSlotInt] + call Inventory__LoadFn + pop rcx ; QuickBars + test rax, rax + jz @II_done + mov rdx, rax + lea r8, [rsp + 38h] + call QWORD PTR [ProcessEvent] + +@II_done: + add rsp, 80h + pop r12 + pop rdi + pop rsi + pop rbp + pop rbx + ret +Inventory_Init ENDP + +; Inventory_GetDefinitionInSlot +; In : RCX = AFortPlayerControllerAthena* +; EDX = Slot, R8D = Item, R9D = Bars +; Out: RAX = UFortWorldItemDefinition* or NULL +; +; Thin wrapper: GetEntryInSlot then return entry->ItemDefinition. +; Stack: 0 pushes + sub 28h = 40; X=8; 40 mod 16=8; 8-8=0 +Inventory_GetDefinitionInSlot PROC + sub rsp, 28h + call Inventory_GetEntryInSlot + test rax, rax + jz @IGDS_null + mov rax, QWORD PTR [rax + 018h] ; entry->ItemDefinition (+0x18) + add rsp, 28h + ret +@IGDS_null: + xor eax, eax + add rsp, 28h + ret +Inventory_GetDefinitionInSlot ENDP + + +; Inventory_GetInstanceFromGuid +; In : RCX = AFortPlayerControllerAthena* +; RDX = FGuid* (16-byte GUID to search for) +; Out: RAX = UFortWorldItem* or NULL +; +; Scans WorldInventory->Inventory.ItemInstances for matching GUID. +; Stack: 4 pushes (no CALL sites) - no alignment needed at call sites. +Inventory_GetInstanceFromGuid PROC + push rbx + push rbp + push rsi + push rdi + + mov rbx, rcx ; RBX = PC + mov rsi, rdx ; RSI = &Guid + + mov rdi, QWORD PTR [rbx + PC_WorldInventory] + test rdi, rdi + jz @IGIG_null + + mov rcx, QWORD PTR [rdi + AFI_ItemInstances] + mov edx, DWORD PTR [rdi + AFI_ItemInstances + 8] + test rcx, rcx + jz @IGIG_null + + xor ebp, ebp ; i = 0 +@IGIG_loop: + cmp ebp, edx + jge @IGIG_null + mov rax, QWORD PTR [rcx + rbp*8] ; Items[i] (UFortWorldItem*) + test rax, rax + jz @IGIG_next + ; Compare [rax + UWI_ItemEntry_ItemGuid] with [rsi] + mov r8, QWORD PTR [rax + UWI_ItemEntry_ItemGuid] + mov r9, QWORD PTR [rsi] + cmp r8, r9 + jne @IGIG_next + mov r8, QWORD PTR [rax + UWI_ItemEntry_ItemGuid + 8] + mov r9, QWORD PTR [rsi + 8] + cmp r8, r9 + je @IGIG_found +@IGIG_next: + inc ebp + jmp @IGIG_loop + +@IGIG_found: + pop rdi + pop rsi + pop rbp + pop rbx + ret + +@IGIG_null: + xor eax, eax + pop rdi + pop rsi + pop rbp + pop rbx + ret +Inventory_GetInstanceFromGuid ENDP + + +; Inventory_EquipInventoryItem +; In : RCX = AFortPlayerControllerAthena* +; RDX = FGuid* (item to equip) +; +; Scans ItemInstances for matching GUID, then calls EquipWeaponDefinition. +; +; Stack: 5 pushes (40) + sub 60h (96) = 136; 8-136=0 +; Frame: [+00..+1F] shadow; [+20..+2F] spare; [+40..+4F] FGuid copy +Inventory_EquipInventoryItem PROC + push rbx + push rbp + push rsi + push rdi + push r12 + sub rsp, 60h + + mov r12, rcx ; R12 = PC + ; Copy FGuid to frame + mov rax, QWORD PTR [rdx] + mov QWORD PTR [rsp + 40h], rax + mov rax, QWORD PTR [rdx + 8] + mov QWORD PTR [rsp + 48h], rax + + ; Pawn = PC->Pawn + mov rbx, QWORD PTR [r12 + ACTRL_Pawn] + test rbx, rbx + jz @IEII_done + + ; WorldInventory + mov rdi, QWORD PTR [r12 + PC_WorldInventory] + test rdi, rdi + jz @IEII_done + + ; ItemInstances + mov rsi, QWORD PTR [rdi + AFI_ItemInstances] ; Data + mov edi, DWORD PTR [rdi + AFI_ItemInstances + 8] ; Num (zero-extends RDI) + test rsi, rsi + jz @IEII_done + + xor ebp, ebp ; i = 0 +@IEII_loop: + cmp ebp, edi + jge @IEII_done + mov rcx, QWORD PTR [rsi + rbp*8] ; Items[i] + test rcx, rcx + jz @IEII_next + ; Compare GUID + mov rax, QWORD PTR [rcx + UWI_ItemEntry_ItemGuid] + cmp rax, QWORD PTR [rsp + 40h] + jne @IEII_next + mov rax, QWORD PTR [rcx + UWI_ItemEntry_ItemGuid + 8] + cmp rax, QWORD PTR [rsp + 48h] + jne @IEII_next + + ; Found - get ItemDef + mov rax, QWORD PTR [rcx + UWI_ItemEntry_ItemDef] + test rax, rax + jz @IEII_next + + ; EquipWeaponDefinition(Pawn, ItemDef, &Guid) + mov rcx, rbx + mov rdx, rax + lea r8, [rsp + 40h] + call Inventory_EquipWeaponDefinition + jmp @IEII_done + +@IEII_next: + inc ebp + jmp @IEII_loop + +@IEII_done: + add rsp, 60h + pop r12 + pop rdi + pop rsi + pop rbp + pop rbx + ret +Inventory_EquipInventoryItem ENDP + + +; Inventory_EquipLoadout +; In : RCX = AFortPlayerControllerAthena* +; RDX = UFortWorldItemDefinition*[6] (array of 6 def pointers) +; +; Adds items to primary slots 0-5, then equips slot 0 (pickaxe). +; +; Stack: 5 pushes (40) + sub 60h (96) = 136; 8-136=0 +; Frame: [+00..+1F] shadow; [+20..+27] arg5(Count=1 for AddItemToSlot) +Inventory_EquipLoadout PROC + push rbx + push rbp + push rsi + push rdi + push r12 + sub rsp, 60h + + mov r12, rcx ; R12 = PC + mov rsi, rdx ; RSI = WIDS array + + xor ebx, ebx ; i = 0 +@IEL_loop: + cmp ebx, 6 + jge @IEL_equip_slot0 + + mov rdi, QWORD PTR [rsi + rbx*8] ; WIDS[i] + test rdi, rdi + jz @IEL_next + + ; AddItemToSlot(PC, Definition, Slot=i, Bars=Primary=0, Count=1) + mov rcx, r12 + mov rdx, rdi + mov r8d, ebx ; Slot + xor r9d, r9d ; Bars = Primary = 0 + mov DWORD PTR [rsp + 20h], 1 ; Count (arg5) + call Inventory_AddItemToSlot +@IEL_next: + inc ebx + jmp @IEL_loop + +@IEL_equip_slot0: + ; 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 @IEL_done + + ; EquipInventoryItem(PC, &entry->ItemGuid) + mov rcx, r12 + lea rdx, [rax + 050h] ; &entry->ItemGuid (+0x50 within FFortItemEntry) + call Inventory_EquipInventoryItem + +@IEL_done: + add rsp, 60h + pop r12 + pop rdi + pop rsi + pop rbp + pop rbx + ret +Inventory_EquipLoadout ENDP + + +; Inventory_FindItemInInventory +; In : RCX = AFortPlayerControllerAthena* +; RDX = UClass* (target class - matched against def->ClassPrivate) +; R8 = BYTE* bFound (output, may be NULL) +; Out: RAX = &FFortItemEntry (within UFortWorldItem) or NULL +; +; Scans ItemInstances; returns first entry whose definition's ClassPrivate +; exactly equals the target UClass*. (UObject::ClassPrivate = UObject+0x10) +; +; Stack: 5 pushes (40) + sub 60h (96) = 136; 8-136≡0 +; Frame: [+00..+1F] shadow; [+50..+57] bFound ptr save +Inventory_FindItemInInventory PROC + push rbx + push rbp + push rsi + push rdi + push r12 + sub rsp, 60h + + mov r12, rcx ; R12 = PC + mov rdi, rdx ; RDI = target UClass* + mov QWORD PTR [rsp + 50h], r8 ; save bFound ptr + + ; bFound = false + test r8, r8 + jz @IFII_scan + mov BYTE PTR [r8], 0 + +@IFII_scan: + mov rbx, QWORD PTR [r12 + PC_WorldInventory] + test rbx, rbx + jz @IFII_null + + mov rsi, QWORD PTR [rbx + AFI_ItemInstances] ; Data + mov ebp, DWORD PTR [rbx + AFI_ItemInstances + 8] ; Num + test rsi, rsi + jz @IFII_null + + xor ebx, ebx ; i = 0 (reuse RBX as counter) +@IFII_loop: + cmp ebx, ebp + jge @IFII_null + mov rax, QWORD PTR [rsi + rbx*8] ; Items[i] + test rax, rax + jz @IFII_next + ; def = entry->ItemDefinition + mov rcx, QWORD PTR [rax + UWI_ItemEntry_ItemDef] + test rcx, rcx + jz @IFII_next + ; def->ClassPrivate at UObject+0x10 + mov rcx, QWORD PTR [rcx + 010h] + cmp rcx, rdi + jne @IFII_next + + ; Found - set bFound + mov rcx, QWORD PTR [rsp + 50h] + test rcx, rcx + jz @IFII_found_ptr + mov BYTE PTR [rcx], 1 +@IFII_found_ptr: + lea rax, [rax + UWI_ItemEntry] ; return &FFortItemEntry + add rsp, 60h + pop r12 + pop rdi + pop rsi + pop rbp + pop rbx + ret + +@IFII_next: + inc ebx + jmp @IFII_loop + +@IFII_null: + xor eax, eax + add rsp, 60h + pop r12 + pop rdi + pop rsi + pop rbp + pop rbx + ret +Inventory_FindItemInInventory ENDP + +END \ No newline at end of file diff --git a/logic/spawners.asm b/logic/spawners.asm new file mode 100644 index 0000000..ee6b8c5 --- /dev/null +++ b/logic/spawners.asm @@ -0,0 +1,830 @@ +INCLUDE ..\include\master.inc + +; AFortPickup field offsets +AFPICKUP_bReplicates EQU 0082h ; AActor::bReplicates bitfield (byte bit6) +AFPICKUP_PrimaryPickupItemEntry EQU 0350h ; FFortItemEntry base + FITE_Count EQU 000Ch ; +0C within FFortItemEntry + FITE_ItemDefinition EQU 0018h ; +18 + FITE_LoadedAmmo EQU 0028h ; +28 +AFPICKUP_PickupLocationData EQU 0428h ; FFortPickupLocationData + FPLD_PickupTarget EQU 0000h ; +00 within PickupLocationData + FPLD_ItemOwner EQU 0010h ; +10 + FPLD_FlyTime EQU 0030h ; +30 (float) +AFPICKUP_bPickedUp EQU 04C0h ; bool (Net) +AFPICKUP_bTossedFromContainer EQU 0349h ; bool (net, after bRandomRotation) + +; ABuildingTrap offsets +ABTRAP_TrapData EQU 0580h ; UFortTrapItemDefinition* +ABTRAP_AttachedTo EQU 0570h ; ABuildingActor* +ABTRAP_Team EQU 04C5h ; EFortTeam (1 byte) - from ABuildingActor + +; UFortAbilitySet::GameplayAbilities TArray at +0x38 +UFAS_GameplayAbilities EQU 0038h + +.const + +szFn_BeginDeferredSpawn DB "Function Engine.GameplayStatics.BeginDeferredActorSpawnFromClass", 0 +szFn_FinishSpawning DB "Function Engine.GameplayStatics.FinishSpawningActor", 0 +szFn_SpawnBuildingGA DB "Function FortniteGame.FortKismetLibrary.STATIC_SpawnBuildingGameplayActor", 0 +szFn_TossPickup DB "Function FortniteGame.FortPickup.TossPickup", 0 +szFn_OnRep_PrimaryPE DB "Function FortniteGame.FortPickup.OnRep_PrimaryPickupItemEntry", 0 +szFn_OnRep_bPickedUp DB "Function FortniteGame.FortPickup.OnRep_bPickedUp", 0 +szFn_OnRep_TossedFrCont DB "Function FortniteGame.FortPickup.OnRep_TossedFromContainer", 0 +szFn_OnRep_PickupLocDat DB "Function FortniteGame.FortPickup.OnRep_PickupLocationData", 0 +szFn_InitKismetBuilding DB "Function FortniteGame.BuildingActor.InitializeKismetSpawnedBuildingActor", 0 +szFn_OnRep_AttachedTo DB "Function FortniteGame.BuildingActor.OnRep_AttachedTo", 0 +szFn_GetBlueprintClass DB "Function FortniteGame.FortTrapItemDefinition.GetBlueprintClass", 0 + +szClass_GameplayStatics DB "Class Engine.GameplayStatics", 0 +szClass_FortKismet DB "Class FortniteGame.FortKismetLibrary", 0 +szClass_AFortPickup DB "Class FortniteGame.FortPickup", 0 + +szLogSpawnNull DB "[SPAWNERS] SpawnActor returned null!", 0 +szLogDecoNull DB "[SPAWNERS] SpawnDeco: null tool or params.", 0 + +.data? + +pFn_BeginDeferredSpawn QWORD ? +pFn_FinishSpawning QWORD ? +pFn_SpawnBuildingGA QWORD ? +pFn_TossPickup QWORD ? +pFn_OnRep_PrimaryPE QWORD ? +pFn_OnRep_bPickedUp QWORD ? +pFn_OnRep_TossedFrCont QWORD ? +pFn_OnRep_PickupLocDat QWORD ? +pFn_InitKismetBuilding QWORD ? +pFn_OnRep_AttachedTo QWORD ? +pFn_GetBlueprintClass QWORD ? + +pClass_GameplayStatics QWORD ? +pClass_FortKismet QWORD ? +pClass_AFortPickup_C QWORD ? + +.code + +; Internal: lazy-load cached UFunction*. +; RCX = &cache_var (QWORD*), RDX = szFunctionPath (PTR BYTE) +; Returns UFunction* in RAX. +Spawners__LoadFn PROC + mov rax, QWORD PTR [rcx] + test rax, rax + jnz @@done + push rcx ; save cache ptr + sub rsp, 32 + ; rcx already is the cache ptr - need path in rcx for SDK_FindObject + mov rcx, rdx + call SDK_FindObject + add rsp, 32 + pop rcx + mov QWORD PTR [rcx], rax +@@done: + ret +Spawners__LoadFn ENDP + +; Internal: lazy-load cached UClass*. +; RCX = &cache_var, RDX = szClassName +Spawners__LoadClass PROC + mov rax, QWORD PTR [rcx] + test rax, rax + jnz @@done + push rcx + sub rsp, 32 + mov rcx, rdx + call SDK_FindClass + add rsp, 32 + pop rcx + mov QWORD PTR [rcx], rax +@@done: + ret +Spawners__LoadClass ENDP + +; AActor* Spawners_SpawnActor(UClass* ActorClass, +; FVector* pLocation, +; AActor* Owner) +; RCX = ActorClass, RDX = FVector* location (may be NULL -> {0,0,0}), +; R8 = AActor* Owner (may be NULL) +; +; Calls STATIC_BeginDeferredActorSpawnFromClass then FinishSpawningActor. +; +; BeginDeferredActorSpawnFromClass params (at [rsp+X]): +; +00: UObject* WorldContextObject (8) +; +08: UClass* ActorClass (8) +; +10: FTransform SpawnTransform (0x30 = 48 bytes) +; +40: BYTE CollisionHandlingOverride (ESpawnActorCollisionHandlingMethod) +; +41..+47: 7 pad bytes +; +48: AActor* Owner (8) +; +50: AActor* ReturnValue (8 OUT) +; Total params = 0x58 bytes. +; +; FinishSpawningActor params: +; +00: AActor* Actor (8) +; +08: FTransform (0x30) +; +38: AActor* ReturnValue (8 OUT) +; Total = 0x40 bytes. + +; Offsets within BeginDeferred params (relative to rsp+32) +BDS_WorldCtx EQU 000h +BDS_Class EQU 008h +BDS_Transform EQU 010h ; FTransform (0x30 bytes) +BDS_Collision EQU 040h ; BYTE (collision method) +BDS_Owner EQU 048h +BDS_RetVal EQU 050h ; OUT AActor* + +; Offsets within FinishSpawning params (relative to rsp+120) +FS_Actor EQU 000h +FS_Transform EQU 008h +FS_RetVal EQU 038h + +; FTransform layout (0x30 bytes): +; +00: FQuat Rotation (16 bytes: X,Y,Z,W) +; +10: FVector Translation (12 bytes: X,Y,Z) +; +1C: pad (4) +; +20: FVector Scale3D (12 bytes: X,Y,Z) +; +2C: pad (4) +; Identity FTransform: Rotation=(0,0,0,1), Translation=(0,0,0), Scale=(1,1,1) +fIdentityQuat_W REAL4 1.0 +fScale_One REAL4 1.0 + +Spawners_SpawnActor PROC + push rbp + push rbx + push rsi + push rdi + push r12 + sub rsp, 0C0h + + mov rbx, rcx ; rbx = ActorClass + mov rsi, rdx ; rsi = FVector* location (may be 0) + mov rdi, r8 ; rdi = Owner + + test rbx, rbx + jz @@done_null + + ; Get/cache GameplayStatics class + lea rcx, pClass_GameplayStatics + lea rdx, szClass_GameplayStatics + call Spawners__LoadClass + mov r12, rax ; r12 = GameplayStatics class + test r12, r12 + jz @@done_null + + ; Get/cache BeginDeferredActorSpawnFromClass fn + lea rcx, pFn_BeginDeferredSpawn + lea rdx, szFn_BeginDeferredSpawn + call Spawners__LoadFn + test rax, rax + jz @@done_null + + ; Zero params block + lea rcx, [rsp+32] + xor edx, edx + mov r8d, 058h ; sizeof params + call memset + + ; Fill BeginDeferred params + call SDK_GetWorld + mov QWORD PTR [rsp + 32 + BDS_WorldCtx], rax + mov QWORD PTR [rsp + 32 + BDS_Class], rbx + + ; FTransform at [rsp+32+BDS_Transform]: + ; Rotation.W = 1.0 (identity quat) + mov eax, 3F800000h ; 1.0f + mov DWORD PTR [rsp + 32 + BDS_Transform + 0Ch], eax ; Rotation.W + ; Scale3D = (1,1,1) + mov DWORD PTR [rsp + 32 + BDS_Transform + 20h], eax ; Scale3D.X + mov DWORD PTR [rsp + 32 + BDS_Transform + 24h], eax ; Scale3D.Y + mov DWORD PTR [rsp + 32 + BDS_Transform + 28h], eax ; Scale3D.Z + + ; If location provided, copy XYZ into Translation + test rsi, rsi + jz @@default_loc + mov eax, DWORD PTR [rsi] + mov DWORD PTR [rsp + 32 + BDS_Transform + 10h], eax ; X + mov eax, DWORD PTR [rsi + 4] + mov DWORD PTR [rsp + 32 + BDS_Transform + 14h], eax ; Y + mov eax, DWORD PTR [rsi + 8] + mov DWORD PTR [rsp + 32 + BDS_Transform + 18h], eax ; Z +@@default_loc: + + ; Collision = ESpawnCollision_AdjustIfPossibleButAlwaysSpawn (2) + mov BYTE PTR [rsp + 32 + BDS_Collision], ESpawnCollision_AdjustIfPossibleButAlwaysSpawn + mov QWORD PTR [rsp + 32 + BDS_Owner], rdi + + ; Load BeginDeferred fn (already in rax from LoadFn call above - lost) + ; Need to reload from cache + mov rax, QWORD PTR [pFn_BeginDeferredSpawn] + mov rcx, r12 + mov rdx, rax + lea r8, [rsp+32] + call QWORD PTR [ProcessEvent] + + ; FirstActor = params.ReturnValue + mov rbp, QWORD PTR [rsp + 32 + BDS_RetVal] + test rbp, rbp + jz @@done_null + + ; Get FinishSpawningActor fn + lea rcx, pFn_FinishSpawning + lea rdx, szFn_FinishSpawning + call Spawners__LoadFn + test rax, rax + jz @@done_null + + ; Fill FinishSpawning params at [rsp+120] + lea rcx, [rsp+120] + xor edx, edx + mov r8d, 040h + call memset + + mov QWORD PTR [rsp + 120 + FS_Actor], rbp + + ; Copy FTransform from BeginDeferred params + lea rcx, [rsp + 120 + FS_Transform] + lea rdx, [rsp + 32 + BDS_Transform] + mov r8d, 030h + call memcpy + + mov rax, QWORD PTR [pFn_FinishSpawning] + mov rcx, r12 + mov rdx, rax + lea r8, [rsp+120] + call QWORD PTR [ProcessEvent] + + mov rax, QWORD PTR [rsp + 120 + FS_RetVal] + jmp @@done + +@@done_null: + xor eax, eax +@@done: + add rsp, 0C0h + pop r12 + pop rdi + pop rsi + pop rbx + pop rbp + ret +Spawners_SpawnActor ENDP + +; AActor* Spawners_SpawnBuilding(UClass* BGAClass, +; FTransform* pTransform, +; APlayerPawn_Athena_C* Pawn) +; RCX = BGAClass, RDX = FTransform*, R8 = Pawn +; +; Calls STATIC_SpawnBuildingGameplayActor via FortKismetLibrary. +; +; Params layout: +; +00: UClass* BGAClass (8) +; +08: FTransform (0x30 bytes) (48) +; +38: AFortPawn* Instigator (8) +; +40: ABuildingGameplayActor* ReturnValue (8 OUT) +; Total = 0x48 +; +; Frame: 4 pushes (rbp,rbx,rsi,rdi) + sub 58h +; 4 pushes: RSP=8; sub 58h(88)=8 -> 0 +; [rsp+32..+31+0x48=79] = params (0x48 = 72 bytes) +Spawners_SpawnBuilding PROC + push rbp + push rbx + push rsi + push rdi + sub rsp, 58h + + mov rbx, rcx ; rbx = BGAClass + mov rsi, rdx ; rsi = FTransform* + mov rdi, r8 ; rdi = Pawn + + test rbx, rbx + jz @@null + test rsi, rsi + jz @@null + + ; Get FortKismetLibrary class + lea rcx, pClass_FortKismet + lea rdx, szClass_FortKismet + call Spawners__LoadClass + mov rbp, rax + test rbp, rbp + jz @@null + + ; Get STATIC_SpawnBuildingGameplayActor fn + lea rcx, pFn_SpawnBuildingGA + lea rdx, szFn_SpawnBuildingGA + call Spawners__LoadFn + test rax, rax + jz @@null + + ; Zero params at [rsp+32] + lea rcx, [rsp+32] + xor edx, edx + mov r8d, 048h + call memset + + ; Fill params + mov QWORD PTR [rsp+32], rbx ; BGAClass + lea rcx, [rsp + 32 + 8] ; dest = ¶ms.Transform + mov rdx, rsi ; src = FTransform* + mov r8d, 030h + call memcpy + mov QWORD PTR [rsp + 32 + 38h], rdi ; Instigator = Pawn + + mov rax, QWORD PTR [pFn_SpawnBuildingGA] + mov rcx, rbp + mov rdx, rax + lea r8, [rsp+32] + call QWORD PTR [ProcessEvent] + + mov rax, QWORD PTR [rsp + 32 + 40h] + jmp @@done +@@null: + xor eax, eax +@@done: + add rsp, 58h + pop rdi + pop rsi + pop rbx + pop rbp + ret +Spawners_SpawnBuilding ENDP + +; AFortPickup* Spawners_SummonPickup(AFortPlayerPawn* Pawn, +; UFortWorldItemDefinition* ItemDef, +; DWORD Count, +; FVector* pLocation) +; RCX = Pawn, RDX = ItemDef, R8D = Count, R9 = FVector* +; +; Frame: 5 pushes (rbp,rbx,rsi,rdi,r12) + sub 50h +; 5 pushes -> RSP=0 mod16; sub 50h(80) mod16=0 +; [rsp+32..+47] = scratch / TossPickup params setup + +; TossPickup params: +; +00: FVector FinalLocation (0x0C) +; +0C: 4 pad (for AActor* alignment) +; +10: AFortPawn* ItemOwner (8) +; +18: int OverrideMaxStackCount (4) +; +1C: bool bToss (1) +; +1D: 3 pad +; Total: 0x20 + +Spawners_SummonPickup PROC + push rbp + push rbx + push rsi + push rdi + push r12 + sub rsp, 50h + + mov rbx, rcx ; rbx = Pawn + mov rsi, rdx ; rsi = ItemDef + mov edi, r8d ; edi = Count + mov r12, r9 ; r12 = FVector* location + + ; SpawnActor(Location, Pawn) + ; Get AFortPickup class + mov rax, QWORD PTR [pClass_AFortPickup_C] + test rax, rax + jnz @@have_cls + lea rcx, szClass_AFortPickup + call SDK_FindClass + mov QWORD PTR [pClass_AFortPickup_C], rax +@@have_cls: + test rax, rax + jz @@null + + mov rcx, rax ; ActorClass = AFortPickup + mov rdx, r12 ; pLocation + mov r8, rbx ; Owner = Pawn + call Spawners_SpawnActor + test rax, rax + jz @@null + mov rbp, rax ; rbp = FortPickup* + + ; Set bReplicates = true (bitfield: byte at 0x82, bit 6 = 0x40) + or BYTE PTR [rbp + 082h], 040h + + ; PrimaryPickupItemEntry.Count = Count + mov DWORD PTR [rbp + AFPICKUP_PrimaryPickupItemEntry + FITE_Count], edi + + ; PrimaryPickupItemEntry.ItemDefinition = ItemDef + mov QWORD PTR [rbp + AFPICKUP_PrimaryPickupItemEntry + FITE_ItemDefinition], rsi + + ; OnRep_PrimaryPickupItemEntry() + mov rax, QWORD PTR [pFn_OnRep_PrimaryPE] + test rax, rax + jnz @@have_onrep_pe + lea rcx, pFn_OnRep_PrimaryPE + lea rdx, szFn_OnRep_PrimaryPE + call Spawners__LoadFn +@@have_onrep_pe: + test rax, rax + jz @@skip_onrep_pe + mov rcx, rbp + mov rdx, rax + lea r8, [rsp+32] + call QWORD PTR [ProcessEvent] +@@skip_onrep_pe: + + ; TossPickup(Location, Pawn, 6, true) + mov rax, QWORD PTR [pFn_TossPickup] + test rax, rax + jnz @@have_toss + lea rcx, pFn_TossPickup + lea rdx, szFn_TossPickup + call Spawners__LoadFn +@@have_toss: + test rax, rax + jz @@skip_toss + + ; Build TossPickup params on stack at [rsp+32] (0x20 bytes) + xor ecx, ecx + mov QWORD PTR [rsp+32], rcx + mov QWORD PTR [rsp+40], rcx + mov QWORD PTR [rsp+48], rcx + + ; FVector FinalLocation = *pLocation (or 0,0,0 if null) + test r12, r12 + jz @@toss_zero_loc + mov ecx, DWORD PTR [r12] + mov DWORD PTR [rsp+32], ecx ; X + mov ecx, DWORD PTR [r12+4] + mov DWORD PTR [rsp+36], ecx ; Y + mov ecx, DWORD PTR [r12+8] + mov DWORD PTR [rsp+40], ecx ; Z +@@toss_zero_loc: + ; Pad at [rsp+44] = 0 already + mov QWORD PTR [rsp+48], rbx ; ItemOwner = Pawn (at +0x10) + ; +0x0C = 4 pad bytes = rsp+44..rsp+47 + ; +0x10 = AFortPawn* ItemOwner = rsp+48..rsp+55 <- stored above + mov DWORD PTR [rsp+56], 6 ; OverrideMaxStackCount = 6 (at +0x18) + mov BYTE PTR [rsp+60], 1 ; bToss = true (at +0x1C) + + mov rdx, rax ; fn + mov rcx, rbp ; this = FortPickup + lea r8, [rsp+32] + call QWORD PTR [ProcessEvent] +@@skip_toss: + + mov rax, rbp + jmp @@done +@@null: + xor eax, eax +@@done: + add rsp, 50h + pop r12 + pop rdi + pop rsi + pop rbx + pop rbp + ret +Spawners_SummonPickup ENDP + +; void Spawners_SummonPickupFromChest(UFortWorldItemDefinition* ItemDef, +; DWORD Count, FVector* pLocation) +; RCX = ItemDef, RDX = Count, R8 = FVector* location +; Frame: 4 pushes + sub 38h -> 8 mod16 +Spawners_SummonPickupFromChest PROC + push rbp + push rbx + push rsi + push rdi + sub rsp, 38h + + mov rbx, rcx ; ItemDef + mov esi, edx ; Count + mov rdi, r8 ; FVector* + + ; Get AFortPickup class + mov rax, QWORD PTR [pClass_AFortPickup_C] + test rax, rax + jnz @@have_cls + lea rcx, szClass_AFortPickup + call SDK_FindClass + mov QWORD PTR [pClass_AFortPickup_C], rax +@@have_cls: + test rax, rax + jz @@done + + mov rcx, rax + mov rdx, rdi ; location + xor r8, r8 ; Owner = null + call Spawners_SpawnActor + test rax, rax + jz @@done + mov rbp, rax ; FortPickup* + + ; bReplicates = true + or BYTE PTR [rbp + 082h], 040h + + ; bTossedFromContainer = true + mov BYTE PTR [rbp + AFPICKUP_bTossedFromContainer], 1 + + ; PrimaryPickupItemEntry.Count, ItemDefinition + mov DWORD PTR [rbp + AFPICKUP_PrimaryPickupItemEntry + FITE_Count], esi + mov QWORD PTR [rbp + AFPICKUP_PrimaryPickupItemEntry + FITE_ItemDefinition], rbx + + ; OnRep_PrimaryPickupItemEntry() + mov rax, QWORD PTR [pFn_OnRep_PrimaryPE] + test rax, rax + jz @@skip_pe + lea rcx, [rsp+32] + ; rcx should be FortPickup + mov rcx, rbp + mov rdx, rax + lea r8, [rsp+32] + call QWORD PTR [ProcessEvent] +@@skip_pe: + + ; OnRep_TossedFromContainer() + mov rax, QWORD PTR [pFn_OnRep_TossedFrCont] + test rax, rax + jnz @@have_tc + lea rcx, pFn_OnRep_TossedFrCont + lea rdx, szFn_OnRep_TossedFrCont + call Spawners__LoadFn +@@have_tc: + test rax, rax + jz @@done + mov rcx, rbp + mov rdx, rax + lea r8, [rsp+32] + call QWORD PTR [ProcessEvent] + +@@done: + add rsp, 38h + pop rdi + pop rsi + pop rbx + pop rbp + ret +Spawners_SummonPickupFromChest ENDP + +; void Spawners_SpawnPickupFromFloor(UFortWorldItemDefinition* ItemDef, +; DWORD Count, FVector* pLocation) +; Frame: 4 pushes + sub 38h +Spawners_SpawnPickupFromFloor PROC + push rbp + push rbx + push rsi + push rdi + sub rsp, 38h + + mov rbx, rcx + mov esi, edx + mov rdi, r8 + + mov rax, QWORD PTR [pClass_AFortPickup_C] + test rax, rax + jnz @@have_cls + lea rcx, szClass_AFortPickup + call SDK_FindClass + mov QWORD PTR [pClass_AFortPickup_C], rax +@@have_cls: + test rax, rax + jz @@done + + mov rcx, rax + mov rdx, rdi + xor r8, r8 + call Spawners_SpawnActor + test rax, rax + jz @@done + mov rbp, rax + + or BYTE PTR [rbp + 082h], 040h + mov DWORD PTR [rbp + AFPICKUP_PrimaryPickupItemEntry + FITE_Count], esi + mov QWORD PTR [rbp + AFPICKUP_PrimaryPickupItemEntry + FITE_ItemDefinition], rbx + + mov rax, QWORD PTR [pFn_OnRep_PrimaryPE] + test rax, rax + jz @@done + mov rcx, rbp + mov rdx, rax + lea r8, [rsp+32] + call QWORD PTR [ProcessEvent] + +@@done: + add rsp, 38h + pop rdi + pop rsi + pop rbx + pop rbp + ret +Spawners_SpawnPickupFromFloor ENDP + +; void Spawners_SpawnDeco(AFortDecoTool* Tool, +; AFortDecoTool_ServerSpawnDeco_Params* pParams) +; RCX = Tool, RDX = pParams +; +; Source: Spawners.h::SpawnDeco() +; +; pParams layout (AFortDecoTool_ServerSpawnDeco_Params): +; +00: FRotator Rotation (12 bytes) +; +0C: 4 pad +; +10: FVector Location (12 bytes) +; +1C: 4 pad +; +20: ABuildingActor* AttachedActor (8 bytes) + +; AFortDecoTool_ServerSpawnDeco_Params offsets +DECO_Rotation EQU 000h ; FRotator (12 bytes) +DECO_Location EQU 010h ; FVector (12 bytes) - after 4 pad +DECO_AttachedActor EQU 020h ; ABuildingActor* + +; AFortDecoTool::ItemDefinition offset +ADECOTOOL_ItemDef EQU 04E8h + +; UFortTrapItemDefinition::GetBlueprintClass params: {UClass* ReturnValue} +; GetBlueprintClass params size = 8 + +; ABuildingTrap fields +ABTRAP_AbilitySet_offset EQU 0E20h ; from Phase 2 findings (FN_FortniteGame_classes.hpp) +; Note: The C++ calls Trap->AbilitySet->GameplayAbilities[i] +; ABuildingActor::AbilitySet at 0x0E20, UFortAbilitySet::GameplayAbilities TArray at +0x38 + +Spawners_SpawnDeco PROC + push rbp + push rbx + push rsi + push rdi + push r12 + push r13 + push r14 + sub rsp, 70h + + test rcx, rcx + jz @@done + test rdx, rdx + jz @@done + + mov r12, rcx ; r12 = Tool (AFortDecoTool*) + mov r13, rdx ; r13 = pParams + + ; ItemDef = Tool->ItemDefinition (UFortTrapItemDefinition*) + mov r14, QWORD PTR [r12 + ADECOTOOL_ItemDef] + test r14, r14 + jz @@done + + ; Build FTransform from Params: + ; Rotation (FQuat via RotToQuat) + Translation (Location) + Scale (1,1,1) + ; FTransform at [rsp+32] (0x30 bytes): + lea rcx, [rsp+32] ; &FQuat output (first 16 bytes of transform) + lea rdx, [r13 + DECO_Rotation] ; &FRotator input + call Utils_RotToQuat + + ; Translation = Location + mov eax, DWORD PTR [r13 + DECO_Location] + mov DWORD PTR [rsp + 32 + 10h], eax ; X + mov eax, DWORD PTR [r13 + DECO_Location + 4] + mov DWORD PTR [rsp + 32 + 14h], eax ; Y + mov eax, DWORD PTR [r13 + DECO_Location + 8] + mov DWORD PTR [rsp + 32 + 18h], eax ; Z + + ; Scale3D = (1,1,1) + mov eax, 3F800000h + mov DWORD PTR [rsp + 32 + 20h], eax + mov DWORD PTR [rsp + 32 + 24h], eax + mov DWORD PTR [rsp + 32 + 28h], eax + + ; Get blueprint class from TrapItemDefinition + ; Call GetBlueprintClass via ProcessEvent + mov rax, QWORD PTR [pFn_GetBlueprintClass] + test rax, rax + jnz @@have_gbc + lea rcx, pFn_GetBlueprintClass + lea rdx, szFn_GetBlueprintClass + call Spawners__LoadFn +@@have_gbc: + test rax, rax + jz @@done + + ; GetBlueprintClass params: {UClass* ReturnValue} (8 bytes at [rsp+80]) + xor rbp, rbp + mov QWORD PTR [rsp+80], rbp ; ReturnValue = null + mov rcx, r14 ; this = TrapItemDef + mov rdx, rax + lea r8, [rsp+80] + call QWORD PTR [ProcessEvent] + + mov rbx, QWORD PTR [rsp+80] ; rbx = Trap UClass* + test rbx, rbx + jz @@done + + ; SpawnActor(TrapClass, FTransform{rsp+32}) + mov rcx, rbx ; ActorClass + lea rdx, [rsp + 32 + 10h] ; Location = Translation part of transform + xor r8, r8 + call Spawners_SpawnActor + test rax, rax + jz @@done + mov rbp, rax ; rbp = Trap (ABuildingTrap*) + + ; Trap->TrapData = TrapDef + mov QWORD PTR [rbp + ABTRAP_TrapData], r14 + + ; Get Pawn = Tool->Owner + mov rbx, QWORD PTR [r12 + 0108h] ; AActor::Owner at 0x108 + + ; Trap->InitializeKismetSpawnedBuildingActor(Trap, PC) + ; PC = Pawn->Controller + mov rsi, QWORD PTR [rbx + 0228h] ; Pawn->Controller (AController*) + ; PC = cast to AFortPlayerController + + mov rax, QWORD PTR [pFn_InitKismetBuilding] + test rax, rax + jnz @@have_ikb + lea rcx, pFn_InitKismetBuilding + lea rdx, szFn_InitKismetBuilding + call Spawners__LoadFn +@@have_ikb: + test rax, rax + jz @@skip_init + + ; Params: {ABuildingActor* BuildingOwner, AFortPlayerController* SpawningController} + mov QWORD PTR [rsp+32], rbp ; BuildingOwner = Trap itself + mov QWORD PTR [rsp+40], rsi ; SpawningController + mov rcx, rbp + mov rdx, rax + lea r8, [rsp+32] + call QWORD PTR [ProcessEvent] +@@skip_init: + + ; Trap->AttachedTo = pParams->AttachedActor + mov rax, QWORD PTR [r13 + DECO_AttachedActor] + mov QWORD PTR [rbp + ABTRAP_AttachedTo], rax + + ; OnRep_AttachedTo() + mov rax, QWORD PTR [pFn_OnRep_AttachedTo] + test rax, rax + jnz @@have_rat + lea rcx, pFn_OnRep_AttachedTo + lea rdx, szFn_OnRep_AttachedTo + call Spawners__LoadFn +@@have_rat: + test rax, rax + jz @@skip_rat + mov rcx, rbp + mov rdx, rax + lea r8, [rsp+32] + call QWORD PTR [ProcessEvent] +@@skip_rat: + + ; Trap->Team = PlayerState->TeamIndex + ; PlayerState at Controller + 0x248 (ACONTROLLER_PlayerState) + mov rdi, QWORD PTR [rsi + 0248h] ; PlayerState + test rdi, rdi + jz @@skip_team + movzx eax, BYTE PTR [rdi + 0F60h] ; AFortPlayerStateAthena::TeamIndex + mov BYTE PTR [rbp + ABTRAP_Team], al +@@skip_team: + + ; Apply abilities from AbilitySet + ; Trap->AbilitySet at ABTRAP_AbilitySet_offset = 0x0E20 + mov r14, QWORD PTR [rbp + ABTRAP_AbilitySet_offset] + test r14, r14 + jz @@done + + ; AbilitySet->GameplayAbilities (TArray) at +0x38 + mov rsi, QWORD PTR [r14 + UFAS_GameplayAbilities] ; Data + mov edi, DWORD PTR [r14 + UFAS_GameplayAbilities + 8] ; Num + test rsi, rsi + jz @@done + xor r13d, r13d ; i = 0 +@@ability_loop: + cmp r13d, edi + jge @@done + mov rax, QWORD PTR [rsi + r13*8] + test rax, rax + jz @@next_ability + ; Pawn = rbx + mov rcx, rbx + mov rdx, rax + call Abilities_GrantGameplayAbility +@@next_ability: + inc r13d + jmp @@ability_loop + +@@done: + add rsp, 70h + pop r14 + pop r13 + pop r12 + pop rdi + pop rsi + pop rbx + pop rbp + ret +Spawners_SpawnDeco ENDP + +; Spawners_SpawnActor_ByLocation +; In : RCX = UClass* (actor class) +; RDX = FVector* (location) +; R8 = FRotator* (rotation - ignored; identity used) +; R9 = AActor* (owner) +; Out: RAX = spawned AActor* or NULL +; +; Thin wrapper: moves Owner from R9 to R8, calls Spawners_SpawnActor. +Spawners_SpawnActor_ByLocation PROC + mov r8, r9 + jmp Spawners_SpawnActor +Spawners_SpawnActor_ByLocation ENDP + + +END \ No newline at end of file diff --git a/logic/teams.asm b/logic/teams.asm new file mode 100644 index 0000000..88db0c0 --- /dev/null +++ b/logic/teams.asm @@ -0,0 +1,439 @@ +INCLUDE asm\include\master.inc + +; Team struct offsets +TEAM_maxTeamSize EQU 000h +TEAM_TeamPosition EQU 004h +TEAM_Members_Data EQU 008h +TEAM_Members_Num EQU 010h +TEAM_Members_Max EQU 014h +TEAM_SIZEOF EQU 018h + +; PlayerTeams struct offsets +PT_maxTeamSize EQU 000h +PT_lastAssigned EQU 004h +PT_Teams_Data EQU 008h +PT_Teams_Num EQU 010h +PT_Teams_Max EQU 014h +PT_SIZEOF EQU 018h + +; AController::PlayerState +ACTRL_PlayerState EQU 0248h + +; AFortPlayerStateAthena field offsets +PS_PlayerTeam EQU 08F0h ; AFortTeamInfo* +PS_TeamIndex EQU 0F60h ; byte (EFortTeam) +PS_SquadId EQU 10F0h ; byte + +; AFortTeamInfo field offsets +FTI_TeamMembers EQU 0320h ; TArray +FTI_Team EQU 0330h ; byte (EFortTeam) + +.const + +sz_OnRep_SquadId BYTE "Function FortniteGame.FortPlayerStateAthena.OnRep_SquadId",0 +sz_OnRep_PlayerTeam BYTE "Function FortniteGame.FortPlayerState.OnRep_PlayerTeam",0 + +.data? + +fn_OnRep_SquadId QWORD ? +fn_OnRep_PlayerTeam QWORD ? + +.code + +; Team_New +; In : ECX = TeamPosition (EFortTeam, DWORD) +; EDX = maxTeamSize (DWORD) +; Out: RAX = Team* (heap-allocated) +; +; Stack: push rbx push rbp + sub 28h -> 2+2*8=32; 8-32=8; sub28h=40; 8-40=0 +Team_New PROC + push rbx + push rbp + sub rsp, 28h + + mov ebx, ecx ; EBX = TeamPosition + mov ebp, edx ; EBP = maxTeamSize + + mov ecx, TEAM_SIZEOF + mov edx, 8 ; alignment = 8 + call QWORD PTR [FMemory_Malloc] + test rax, rax + jz @TN_done + + ; Zero the allocation + push rax + mov rcx, rax + xor edx, edx + mov r8d, TEAM_SIZEOF + call memset + pop rax + + mov DWORD PTR [rax + TEAM_maxTeamSize], ebp + mov DWORD PTR [rax + TEAM_TeamPosition], ebx + +@TN_done: + add rsp, 28h + pop rbp + pop rbx + ret +Team_New ENDP + + +; Team_Num +; In : RCX = Team* +; Out: EAX = member count +Team_Num PROC + mov eax, DWORD PTR [rcx + TEAM_Members_Num] + ret +Team_Num ENDP + + +; Team_AddPlayer +; In : RCX = Team* +; RDX = AFortPlayerController* (pc) +; +; Stack: push rbx push rbp + sub 28h -> 0 (same as Team_New) +Team_AddPlayer PROC + push rbx + push rbp + sub rsp, 28h + + mov rbx, rcx ; RBX = Team* + mov rbp, rdx ; RBP = pc + + ; if Num() == maxTeamSize, return + mov eax, DWORD PTR [rbx + TEAM_Members_Num] + cmp eax, DWORD PTR [rbx + TEAM_maxTeamSize] + jge @TAP_done + + ; TArray_Add(&Members, &pc, 8) + lea rcx, [rbx + TEAM_Members_Data] ; ptr to TArrayHeader (Data QWORD at start) + lea rdx, QWORD PTR [rbp] + push rbp + mov r8d, 8 + ; TArray_Add expects (TArrayHeader*, elem*, elem_size) + ; need to pass address of pc on stack + lea rdx, [rsp] ; &pc on stack (rbp was pushed to [rsp]) + call TArray_Add + add rsp, 8 ; pop the pushed rbp value + + ; InitializePlayer(Team*, pc) + mov rcx, rbx + mov rdx, rbp + call Team_InitializePlayer + +@TAP_done: + add rsp, 28h + pop rbp + pop rbx + ret +Team_AddPlayer ENDP + + +; Team_Kick +; In : RCX = Team* +Team_Kick PROC + ret +Team_Kick ENDP + + +; Team_InitializePlayer +; In : RCX = Team* +; RDX = AFortPlayerController* (pc) +; +; Sets TeamIndex, PlayerTeam->Team, SquadId on the player state, +; appends pc to PlayerTeam->TeamMembers, fires OnRep_SquadId and OnRep_PlayerTeam. +; +; Stack: 5 pushes (40) + sub 60h (96) = 136; 8-136=-128=0 +; +; Frame: +; [+00..+1F] shadow +; [+20..+27] arg5 slot +; [+28..+2F] zero params scratch (for zero-arg ProcessEvent calls) +Team_InitializePlayer PROC + push rbx + push rbp + push rsi + push rdi + push r12 + sub rsp, 60h + + mov rbx, rcx ; RBX = Team* + mov rdi, rdx ; RDI = pc + + ; PlayerState = pc->PlayerState (AController+0x248) + mov rsi, QWORD PTR [rdi + ACTRL_PlayerState] + test rsi, rsi + jz @TIP_done + + ; EBP = TeamPosition + mov ebp, DWORD PTR [rbx + TEAM_TeamPosition] + + ; PlayerState->TeamIndex = TeamPosition + mov BYTE PTR [rsi + PS_TeamIndex], bpl + + ; PlayerState->PlayerTeam->Team = TeamPosition + mov r12, QWORD PTR [rsi + PS_PlayerTeam] + test r12, r12 + jz @TIP_skip_team_set + mov BYTE PTR [r12 + FTI_Team], bpl + + ; PlayerState->PlayerTeam->TeamMembers.Add(pc) + ; TArray_Add(&TeamMembers, &pc, 8) + push rdi ; push pc value onto stack + lea rcx, [r12 + FTI_TeamMembers] ; &TArrayHeader + lea rdx, [rsp] ; &pc value on stack + mov r8d, 8 + call TArray_Add + add rsp, 8 ; pop pc value + +@TIP_skip_team_set: + ; PlayerState->SquadId = (TeamPosition - 2) + 1 = TeamPosition - 1 + mov eax, ebp + sub eax, 1 + mov BYTE PTR [rsi + PS_SquadId], al + + ; Lazy-load OnRep_SquadId + mov rax, QWORD PTR [fn_OnRep_SquadId] + test rax, rax + jnz @TIP_sq_ok + lea rcx, [sz_OnRep_SquadId] + call SDK_FindObject + mov QWORD PTR [fn_OnRep_SquadId], rax +@TIP_sq_ok: + test rax, rax + jz @TIP_skip_sq + ; ProcessEvent(PlayerState, fn, NULL_params) + xor r8d, r8d + mov QWORD PTR [rsp + 28h], 0 ; zero params scratch + mov rcx, rsi + mov rdx, rax + lea r8, [rsp + 28h] + call QWORD PTR [ProcessEvent] + +@TIP_skip_sq: + ; Lazy-load OnRep_PlayerTeam + mov rax, QWORD PTR [fn_OnRep_PlayerTeam] + test rax, rax + jnz @TIP_pt_ok + lea rcx, [sz_OnRep_PlayerTeam] + call SDK_FindObject + mov QWORD PTR [fn_OnRep_PlayerTeam], rax +@TIP_pt_ok: + test rax, rax + jz @TIP_done + xor r8d, r8d + mov QWORD PTR [rsp + 28h], 0 + mov rcx, rsi + mov rdx, rax + lea r8, [rsp + 28h] + call QWORD PTR [ProcessEvent] + +@TIP_done: + add rsp, 60h + pop r12 + pop rdi + pop rsi + pop rbp + pop rbx + ret +Team_InitializePlayer ENDP + + +; PlayerTeams_New +; In : ECX = maxTeamSize (DWORD) +; Out: RAX = PlayerTeams* (heap-allocated) +; +; Stack: push rbx + sub 28h -> 1+8=16; 8-16=8; sub28h: 8-40=0 +PlayerTeams_New PROC + push rbx + sub rsp, 28h + + mov ebx, ecx ; EBX = maxTeamSize + + mov ecx, PT_SIZEOF + mov edx, 8 + call QWORD PTR [FMemory_Malloc] + test rax, rax + jz @PTN_done + + push rax + mov rcx, rax + xor edx, edx + mov r8d, PT_SIZEOF + call memset + pop rax + + mov DWORD PTR [rax + PT_maxTeamSize], ebx + ; lastAssignedIndex = 0, Teams array = zero (already zeroed) + +@PTN_done: + add rsp, 28h + pop rbx + ret +PlayerTeams_New ENDP + + +; PlayerTeams_GetNextSlot +; In : RCX = PlayerTeams* +; Out: EAX = EFortTeam value = Teams.Num + 2 +PlayerTeams_GetNextSlot PROC + mov eax, DWORD PTR [rcx + PT_Teams_Num] + add eax, 2 + ret +PlayerTeams_GetNextSlot ENDP + + +; PlayerTeams_FindAvailableSpot +; In : RCX = PlayerTeams* +; Out: RAX = Team* or NULL +; +; If maxTeamSize > 0: find first team with Num < maxTeamSize. +; If maxTeamSize < 0 (round-robin): abs(maxTeamSize) teams must exist; +; returns Teams[lastAssigned] and increments lastAssigned. +; +; Stack: push rbx push rbp + sub 28h -> 0 +PlayerTeams_FindAvailableSpot PROC + push rbx + push rbp + sub rsp, 28h + + mov rbx, rcx ; RBX = PlayerTeams* + + mov ebp, DWORD PTR [rbx + PT_maxTeamSize] + test ebp, ebp + js @PFas_roundrobin + + ; maxTeamSize >= 0: linear scan for available slot + mov ecx, DWORD PTR [rbx + PT_Teams_Num] + test ecx, ecx + jz @PFas_null + mov rdx, QWORD PTR [rbx + PT_Teams_Data] + test rdx, rdx + jz @PFas_null + + xor eax, eax ; i = 0 +@PFas_linear: + cmp eax, ecx + jge @PFas_null + mov r8, QWORD PTR [rdx + rax*8] ; Teams[i] (Team*) + test r8, r8 + jz @PFas_linear_next + mov r9d, DWORD PTR [r8 + TEAM_Members_Num] + cmp r9d, ebp + jl @PFas_found ; Num < maxTeamSize -> available +@PFas_linear_next: + inc eax + jmp @PFas_linear + +@PFas_roundrobin: + ; abs(maxTeamSize) teams must exist + neg ebp + mov ecx, DWORD PTR [rbx + PT_Teams_Num] + cmp ecx, ebp + jl @PFas_null ; not enough teams yet + + mov eax, DWORD PTR [rbx + PT_lastAssigned] + mov rdx, QWORD PTR [rbx + PT_Teams_Data] + test rdx, rdx + jz @PFas_null + mov rax, QWORD PTR [rdx + rax*8] ; Teams[lastAssigned] + + ; Increment lastAssigned (wrap at Teams.Num-1) + mov ecx, DWORD PTR [rbx + PT_lastAssigned] + inc ecx + mov edx, DWORD PTR [rbx + PT_Teams_Num] + cmp ecx, edx + jl @PFas_rr_nowrap + xor ecx, ecx +@PFas_rr_nowrap: + mov DWORD PTR [rbx + PT_lastAssigned], ecx + jmp @PFas_found + +@PFas_found: + add rsp, 28h + pop rbp + pop rbx + ret + +@PFas_null: + xor eax, eax + add rsp, 28h + pop rbp + pop rbx + ret +PlayerTeams_FindAvailableSpot ENDP + + +; PlayerTeams_AddPlayerToRandomTeam +; In : RCX = PlayerTeams* +; RDX = AFortPlayerController* (pc) +; +; Finds or creates a team with space, then adds the player. +; +; Stack: 4 pushes (32) + sub 38h (56) = 88; 8-88=-80=0 +PlayerTeams_AddPlayerToRandomTeam PROC + push rbx + push rbp + push rsi + push rdi + sub rsp, 38h + + mov rbx, rcx ; RBX = PlayerTeams* + mov rdi, rdx ; RDI = pc + + ; Try FindAvailableSpot + mov rcx, rbx + call PlayerTeams_FindAvailableSpot + test rax, rax + jz @PTAPART_new_team + + ; Available: add player to existing team + mov rcx, rax + mov rdx, rdi + call Team_AddPlayer + jmp @PTAPART_done + +@PTAPART_new_team: + ; No spot: create a new Team and add to PlayerTeams.Teams array + mov rcx, rbx + call PlayerTeams_GetNextSlot ; EAX = new TeamPosition + mov esi, eax ; ESI = TeamPosition + + ; Team_New(TeamPosition, maxTeamSize) + mov ecx, esi + mov edx, DWORD PTR [rbx + PT_maxTeamSize] + call Team_New + test rax, rax + jz @PTAPART_done + mov rbp, rax ; RBP = new Team* + + ; TArray_Add(&Teams, &newTeam, 8) + push rbp ; push Team* onto stack + lea rcx, [rbx + PT_Teams_Data] ; &TArrayHeader for Teams + lea rdx, [rsp] ; &(Team* on stack) + mov r8d, 8 + call TArray_Add + add rsp, 8 + + ; Team_AddPlayer(newTeam, pc) + mov rcx, rbp + mov rdx, rdi + call Team_AddPlayer + +@PTAPART_done: + add rsp, 38h + pop rdi + pop rsi + pop rbp + pop rbx + ret +PlayerTeams_AddPlayerToRandomTeam ENDP + + +; PlayerTeams_AddPlayerToTeam - alias for AddPlayerToRandomTeam +PlayerTeams_AddPlayerToTeam PROC + jmp PlayerTeams_AddPlayerToRandomTeam +PlayerTeams_AddPlayerToTeam ENDP + +END \ No newline at end of file diff --git a/ufunctionhooks.asm b/ufunctionhooks.asm index eac8c7b..81244d0 100644 --- a/ufunctionhooks.asm +++ b/ufunctionhooks.asm @@ -8,8 +8,8 @@ AFPCA_bIsDisconnecting EQU 1674h ; AFortPlayerControllerAthena::bIsDiscon AOBH_ListenPort EQU 0208h ; AOnlineBeaconHost::ListenPort (int32) AOBH_NetDriver EQU 0220h ; AOnlineBeaconHost::Driver (UNetDriver*) ABSMA_EditingPlayer EQU 0480h ; ABuildingSMActor::EditingPlayer -UWORLD_NetDriver EQU 0E8h ; UWorld::NetDriver (UNetDriver*) -UWORLD_AuthorityGameMode EQU 0118h ; UWorld::AuthorityGameMode (AGameMode*) +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 @@ -32,6 +32,11 @@ 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 ? ; Cached class pointers (used by ReadyToStartMatch) pClass_FortOnlineBeaconHost QWORD ? @@ -70,6 +75,11 @@ szFn_K2_DestroyActor DB "Function Engine.Actor.K2_DestroyActor", 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 @@ -194,7 +204,7 @@ PEHOOK_ServerAttemptInventoryDrop PROC mov rcx, rax mov rdx, rsi - call Inventory_Update ; TODO: replace with Inventory_OnDrop(PC, Params) + call Inventory_OnDrop @@done: add rsp, 32 @@ -225,7 +235,7 @@ PEHOOK_ServerHandlePickup PROC mov rcx, rax ; PC mov rdx, rsi ; Params - call Inventory_Update ; TODO: Inventory_OnPickup(PC, Params) + call Inventory_OnPickup @@done: add rsp, 32 @@ -718,25 +728,336 @@ PEHOOK_ServerAttemptAircraftJump ENDP ; RCX = AFortAthenaAircraft* (the aircraft) ; Iterates passengers and forces ServerAttemptAircraftJump on each. PEHOOK_OnAircraftExitedDropZone PROC - ; TODO: iterate aircraft->Passengers TArray, call jump for each + 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 - spawn a building piece 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 - start building edit mode 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 - apply edit and re-spawn building 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 + xor esi, esi + 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