diff --git a/logger.asm b/logger.asm index dc32c8c..d30fe8c 100644 --- a/logger.asm +++ b/logger.asm @@ -20,7 +20,7 @@ Logger_Initialize PROC Logger_Initialize ENDP Logger_LogInfo PROC - ; Entry: RSP ≡ 8 mod 16 (return address pushed on aligned stack) + ; Entry: RSP = 8 mod 16 (return address pushed on aligned stack) push rbp mov rbp, rsp sub rsp, 32 ; shadow space; push rbp -> RSP = 0 mod 16; sub 32 -> 0 diff --git a/raider.asm b/raider.asm index 3fb6009..def4219 100644 --- a/raider.asm +++ b/raider.asm @@ -24,11 +24,6 @@ szBaseAddrFmt DB "[INFO] Base Address: 0x%I64X", 0Ah, 0 ; [rbp- 8] = local: NetDebug function pointer (8 bytes) ; [rbp- 16] = alignment pad (8 bytes) ; [rbp- 48] … [rbp-17] = 32-byte shadow space for callees ([rsp+0..31]) -; -; Alignment check: -; entry RSP ≡ 8 mod 16 (ret addr pushed on 16-byte-aligned stack) -; push rbp → RSP ≡ 0 mod 16 -; sub rsp, 48 (48 mod 16 = 0) → RSP ≡ 0 mod 16 at every CALL site Main PROC push rbp mov rbp, rsp diff --git a/sdk/sdk_classes.asm b/sdk/sdk_classes.asm new file mode 100644 index 0000000..9a2e014 --- /dev/null +++ b/sdk/sdk_classes.asm @@ -0,0 +1,815 @@ +INCLUDE include\master.inc + +; CRT helpers +EXTRN memcpy :PROC +EXTRN memcmp :PROC +EXTRN memmove :PROC +EXTRN strcmp :PROC +EXTRN strstr :PROC +EXTRN strcpy :PROC +EXTRN strcat :PROC +EXTRN strlen :PROC +EXTRN wcslen :PROC + +.data? + +szNameBuf BYTE 256 DUP (?) ; narrow name of one object component +szFullNameBuf BYTE 512 DUP (?) ; assembled full name output + +.code + + +; TArray_Num(arr:QWORD) -> EAX:DWORD +; Returns arr->Count. +TArray_Num PROC + mov eax, DWORD PTR [rcx + 8] ; return arr->Count + ret +TArray_Num ENDP + +; TArray_GetByIndex(arr:QWORD, idx:DWORD, elem_size:DWORD) -> RAX:QWORD +; Returns ptr to element at index idx. +; RAX = arr->Data + (idx * elem_size) +TArray_GetByIndex PROC + ; RCX = arr, EDX = idx, R8D = elem_size + mov rax, QWORD PTR [rcx] ; rax = Data + movsx rdx, edx ; sign-extend idx to 64-bit + movsx r8, r8d ; sign-extend elem_size + imul rdx, r8 ; rdx = idx * elem_size + add rax, rdx ; rax = Data + offset + ret +TArray_GetByIndex ENDP + +; TArray_Add(arr:QWORD, elem:QWORD, elem_size:DWORD) -> EAX:DWORD +; Appends elem (by value copy) to arr. Grows via FMemory_Realloc if full. +; Returns new Count. +; +; Stack: push rbp rbx rsi rdi r12 (5 pushes) + sub 32 = 0 mod 16 +TArray_Add PROC + ; RCX = arr, RDX = elem, R8D = elem_size + push rbp + push rbx + push rsi + push rdi + push r12 + sub rsp, 32 ; shadow (5 pushes -> RSP = 0 mod 16; sub 32 -> 0) + + mov rbx, rcx ; rbx = arr + mov rsi, rdx ; rsi = elem + movsx r12, r8d ; r12 = elem_size (sign-extended) + + ; Check if Count < Max (space available without realloc) + mov ecx, DWORD PTR [rbx + 8] ; ecx = Count + cmp ecx, DWORD PTR [rbx + 12] ; Count vs Max + jl @@copy_elem + + ; Grow array: FMemory_Realloc(Data, (Count+1)*elem_size, 0) + mov rcx, QWORD PTR [rbx] ; rcx = Data + lea eax, [ecx + 1] ; eax = Count + 1 + cdqe ; sign-extend to RAX + imul rax, r12 ; rax = (Count+1) * elem_size + mov rdx, rax ; rdx = new byte size + xor r8d, r8d ; alignment = 0 + call QWORD PTR [FMemory_Realloc] + mov QWORD PTR [rbx], rax ; arr->Data = new ptr + mov ecx, DWORD PTR [rbx + 8] ; reload Count (unchanged) + lea eax, [ecx + 1] + mov DWORD PTR [rbx + 12], eax ; arr->Max = Count + 1 + +@@copy_elem: + ; rdi = &arr->Data[Count] = Data + Count*elem_size + mov rdi, QWORD PTR [rbx] ; rdi = Data + movsx rax, ecx ; rax = Count (from ecx, set above) + imul rax, r12 ; rax = Count * elem_size + add rdi, rax ; rdi = destination slot + + ; memcpy(dst=rdi, src=rsi, size=r12) + mov rcx, rdi + mov rdx, rsi + mov r8, r12 + call memcpy + + ; Count++ + inc DWORD PTR [rbx + 8] + mov eax, DWORD PTR [rbx + 8] ; return new Count + + add rsp, 32 + pop r12 + pop rdi + pop rsi + pop rbx + pop rbp + ret +TArray_Add ENDP + +; TArray_RemoveAt(arr:QWORD, idx:DWORD, elem_size:DWORD) +; Removes element at idx by swapping with the last element (unordered). +; +; Stack: push rbp rbx rsi rdi (4 pushes) + sub 40 = 0 mod 16 +TArray_RemoveAt PROC + ; RCX = arr, EDX = idx, R8D = elem_size + push rbp + push rbx + push rsi + push rdi + sub rsp, 40 ; 4 pushes (RSP = 0 mod 16 after 4); sub 40 (40=8) -> 8 mod 16 + ; Wait: entry 8, push×4 = 8 mod 16 (even pushes restore parity). 8-mod-16 - 40(=8) = 0 + + mov rbx, rcx ; rbx = arr + movsx rsi, edx ; rsi = idx (64-bit) + movsx rdi, r8d ; rdi = elem_size + + mov ecx, DWORD PTR [rbx + 8] ; ecx = Count + test ecx, ecx + jz @@done + movsx rcx, ecx ; rcx = Count (64-bit) + cmp rsi, rcx + jge @@done ; idx >= Count: no-op + + lea rax, [rcx - 1] ; rax = last = Count - 1 + cmp rsi, rax + je @@dec_only ; idx == last: just decrement + + ; Swap: Data[idx] = Data[last] + mov rdx, QWORD PTR [rbx] ; rdx = Data + imul rsi, rdi ; rsi = idx * elem_size + imul rax, rdi ; rax = last * elem_size + lea rcx, [rdx + rsi] ; dst = Data + idx*elem_size + lea rdx, [rdx + rax] ; src = Data + last*elem_size + mov r8, rdi ; size = elem_size + call memcpy + +@@dec_only: + dec DWORD PTR [rbx + 8] ; Count-- + +@@done: + add rsp, 40 + pop rdi + pop rsi + pop rbx + pop rbp + ret +TArray_RemoveAt ENDP + +; TArray_RemoveSingle(arr:QWORD, idx:DWORD, elem_size:DWORD) -> AL:BYTE +; Removes the element at idx (same as RemoveAt) and returns 1 on success. +TArray_RemoveSingle PROC + ; RCX = arr, EDX = idx, R8D = elem_size + push rbp + push rbx + sub rsp, 40 ; 2 pushes (entry 8, ×2 -> 0), sub 40(=8) -> 8 mod 16... hmm + ; Actually: entry 8, push rbp -> 0, push rbx -> 8. sub 40 (=8): 8-8=0 + + mov rbx, rcx ; rbx = arr + mov ecx, DWORD PTR [rbx + 8] ; ecx = Count + movsx rax, edx ; rax = idx + xor eax, eax ; default return = false + + cmp edx, DWORD PTR [rbx + 8] + jge @@done ; idx >= Count + + ; Call TArray_RemoveAt(arr=rbx, idx=edx, elem_size=r8d) + mov rcx, rbx + ; edx already = idx + ; r8d already = elem_size + call TArray_RemoveAt + + mov al, 1 ; return true + +@@done: + add rsp, 40 + pop rbx + pop rbp + ret +TArray_RemoveSingle ENDP + +; TArray_FreeArray(arr:QWORD) +; Frees arr->Data, zeros all fields. +TArray_FreeArray PROC + ; RCX = arr + push rbp + push rbx + sub rsp, 40 + + mov rbx, rcx + mov rcx, QWORD PTR [rbx] ; rcx = Data ptr + test rcx, rcx + jz @@zero_fields + call QWORD PTR [FMemory_Free] + +@@zero_fields: + xor eax, eax + mov QWORD PTR [rbx], rax ; Data = null + mov DWORD PTR [rbx + 8], eax ; Count = 0 + mov DWORD PTR [rbx + 12], eax ; Max = 0 + + add rsp, 40 + pop rbx + pop rbp + ret +TArray_FreeArray ENDP + +; TArray_MarkArrayDirty(serializer:QWORD) +; Increments ArrayReplicationKey on FFastArraySerializer base +; ArrayReplicationKey is at offset 0x00 in the serializer (int32_t). +TArray_MarkArrayDirty PROC + ; RCX = FFastArraySerializer* (base of the owning list) + ; ArrayReplicationKey is the first field of FFastArraySerializer + inc DWORD PTR [rcx] + ret +TArray_MarkArrayDirty ENDP + +; TArray_MarkItemDirty(serializer:QWORD, item:QWORD) +; Marks a single replicated entry as dirty +; Increments the item's ReplicationID field (offset 0x00 in FFortItemEntry +; base, which is the first 12 bytes = FFastArraySerializerItem) +TArray_MarkItemDirty PROC + ; RCX = FFastArraySerializer*, RDX = item ptr + push rbp + push rbx + sub rsp, 40 + + mov rbx, rdx ; rbx = item + ; Increment item's MostRecentArrayReplicationKey at [item + 4] (WORD) + inc WORD PTR [rbx + 4] + + ; Also mark the array dirty + ; RCX already = serializer + call TArray_MarkArrayDirty + + add rsp, 40 + pop rbx + pop rbp + ret +TArray_MarkItemDirty ENDP + + +; FString_Init(str:QWORD) +; Zero-initialises a TArrayHeader (FString) to an empty string. +FString_Init PROC + ; RCX = TArrayHeader* (FString*) + xor eax, eax + mov QWORD PTR [rcx], rax ; Data = null + mov DWORD PTR [rcx + 8], eax ; Count = 0 + mov DWORD PTR [rcx + 12], eax ; Max = 0 + ret +FString_Init ENDP + +; FString_FromWideChar(out:QWORD, wstr:QWORD) +; Constructs an FString from a null-terminated wchar_t* source. +; Allocates memory via FMemory_Malloc. +; out->Data = FMemory_Malloc((wcslen(wstr)+1) * 2) +; Copies wstr including null terminator. +; +; Stack: push rbp rbx rsi rdi (4 pushes) + sub 40 = 0 mod 16 +FString_FromWideChar PROC + ; RCX = TArrayHeader* (out), RDX = wchar_t* (wstr) + push rbp + push rbx + push rsi + push rdi + sub rsp, 40 ; 4 pushes: entry 8->0->8->0->8; sub 40(=8) -> 0 + + mov rdi, rcx ; rdi = out + mov rsi, rdx ; rsi = wstr + + ; wcslen(wstr) -> RAX = char count (not including null) + mov rcx, rsi + call wcslen + mov rbx, rax ; rbx = len + + ; FMemory_Malloc((len+1)*2, 0) + lea ecx, [eax + 1] ; ecx = len + 1 + shl ecx, 1 ; ecx = (len+1) * 2 bytes + xor edx, edx ; alignment = 0 + call QWORD PTR [FMemory_Malloc] + + ; Fill out TArrayHeader + mov QWORD PTR [rdi], rax ; Data = malloc result + lea ecx, [ebx + 1] + mov DWORD PTR [rdi + 8], ecx ; Count = len+1 + mov DWORD PTR [rdi + 12], ecx ; Max = len+1 + + ; memcpy(Data, wstr, (len+1)*2) + mov rcx, rax ; dst = Data + mov rdx, rsi ; src = wstr + lea r8, [rbx + 1] + shl r8, 1 ; size = (len+1)*2 + call memcpy + + add rsp, 40 + pop rdi + pop rsi + pop rbx + pop rbp + ret +FString_FromWideChar ENDP + +; FString_Free(str:QWORD) +; Releases an FString's Data buffer and zeros the header +; Equivalent to FreeArray on TArray +FString_Free PROC + ; RCX = TArrayHeader* (FString*) + jmp TArray_FreeArray ; identical logic +FString_Free ENDP + +; FName_Init_FromIndex(out:QWORD, index:DWORD) +; Constructs an FName with ComparisonIndex=index, Number=0. +FName_Init_FromIndex PROC + ; RCX = FName* (8 bytes: ComparisonIndex DWORD + Number DWORD) + ; EDX = ComparisonIndex + mov DWORD PTR [rcx], edx ; ComparisonIndex + mov DWORD PTR [rcx + 4], 0 ; Number = 0 + ret +FName_Init_FromIndex ENDP + + +; FGuid_Reset(guid:QWORD) +; Zeroes all four DWORD fields. +FGuid_Reset PROC + ; RCX = FGuid* (4 × DWORD = 16 bytes) + xor eax, eax + mov DWORD PTR [rcx], eax + mov DWORD PTR [rcx + 4], eax + mov DWORD PTR [rcx + 8], eax + mov DWORD PTR [rcx + 12], eax + ret +FGuid_Reset ENDP + +; FGuid_Equals(a:QWORD, b:QWORD) -> AL:BYTE (1=equal, 0=not) +; Compares two FGuid structs field by field. +FGuid_Equals PROC + ; RCX = FGuid* a, RDX = FGuid* b + mov eax, DWORD PTR [rcx] + cmp eax, DWORD PTR [rdx] + jne @@neq + mov eax, DWORD PTR [rcx + 4] + cmp eax, DWORD PTR [rdx + 4] + jne @@neq + mov eax, DWORD PTR [rcx + 8] + cmp eax, DWORD PTR [rdx + 8] + jne @@neq + mov eax, DWORD PTR [rcx + 12] + cmp eax, DWORD PTR [rdx + 12] + jne @@neq + mov al, 1 + ret +@@neq: + xor al, al + ret +FGuid_Equals ENDP + +; SDK_GetObjectByIndex(index:DWORD) -> RAX:QWORD (UObject*) +; GObjects->Objects[index * 24] -> first QWORD = UObject* +SDK_GetObjectByIndex PROC + ; ECX = index + mov rax, QWORD PTR [GObjects] ; rax = TUObjectArray* + test rax, rax + jz @@null + mov rax, QWORD PTR [rax] ; rax = Objects (uint8_t*) + movsx rcx, ecx ; sign-extend index + imul rcx, rcx, 24 ; offset = index * 24 + mov rax, QWORD PTR [rax + rcx] ; rax = *(UObject**)(Objects + offset) + ret +@@null: + xor eax, eax + ret +SDK_GetObjectByIndex ENDP + +; Internal helper: UObject_GetNameBuf(obj:QWORD, outBuf:QWORD, maxLen:DWORD) +; Writes null-terminated narrow ASCII name of obj into outBuf (max maxLen bytes). +; Strips path prefix (everything up to and including last '/'). +; +; Stack: push rbp rbx rsi rdi r12 r13 (6 pushes) + sub 56 = 0 mod 16 +; 6 pushes: entry 8 -> 0 -> 8 -> 0 -> 8 -> 0 -> 8 (mod 16 after 6 pushes = 8) +; sub 56 (56=8): 8-8 = 0 +UObject_GetNameBuf PROC + ; RCX = UObject*, RDX = char* outBuf, R8D = maxLen + push rbp + push rbx + push rsi + push rdi + push r12 + push r13 + sub rsp, 56 ; 6 pushes (8 mod 16 after all); sub 56(=8) -> 0 + + mov rbx, rcx ; rbx = obj + mov rdi, rdx ; rdi = outBuf + movsx r12, r8d ; r12 = maxLen + + ; Null guard + test rbx, rbx + jz @@empty + test rdi, rdi + jz @@done + + ; Allocate a local FString header on stack for FNameToString output + ; [rsp+32..47] = TArrayHeader (Data:QWORD, Count:DWORD, Max:DWORD) + xor eax, eax + mov QWORD PTR [rsp + 32], rax ; Data = null + mov DWORD PTR [rsp + 40], eax ; Count = 0 + mov DWORD PTR [rsp + 44], eax ; Max = 0 + + ; FNameToString(&obj->Name, &stack_fstr) + ; Check FNameToString is resolved + mov rax, QWORD PTR [FNameToString] + test rax, rax + jz @@empty + + lea rcx, [rbx + 18h] ; rcx = &obj->Name (FName*) + lea rdx, [rsp + 32] ; rdx = &stack_fstr (TArrayHeader*) + call rax ; FNameToString(name_ptr, fstr_ptr) + + ; rsi = wide string Data ptr + mov rsi, QWORD PTR [rsp + 32] ; rsi = fstr.Data (wchar_t*) + test rsi, rsi + jz @@done + + ; Find last '/' in wide string and skip past it + ; Scan forward to find the last L'/'. + ; also find total char count. + mov r13, rsi ; r13 = scan ptr, starts at beginning + mov rdx, rsi ; rdx = current best (start of name portion) +@@scan_slash: + movzx eax, WORD PTR [r13] + test ax, ax + jz @@slash_done + cmp ax, 2Fh ; L'/' + jne @@not_slash + lea rdx, [r13 + 2] ; rdx = char after last '/' +@@not_slash: + add r13, 2 + jmp @@scan_slash +@@slash_done: + ; rdx = start of name portion (past last '/') + + ; Copy wide chars to narrow into rdi, up to r12 chars + mov rcx, r12 ; rcx = remaining capacity + test rcx, rcx + jz @@free_fstr + dec rcx ; leave room for null + +@@narrow_loop: + movzx eax, WORD PTR [rdx] + test ax, ax + jz @@narrow_done + mov BYTE PTR [rdi], al + add rdx, 2 + inc rdi + dec rcx + jnz @@narrow_loop + +@@narrow_done: + mov BYTE PTR [rdi], 0 ; null terminate + +@@free_fstr: + ; FMemory_Free(fstr.Data) + mov rcx, QWORD PTR [rsp + 32] + test rcx, rcx + jz @@done + call QWORD PTR [FMemory_Free] + jmp @@done + +@@empty: + ; Write empty string + test rdi, rdi + jz @@done + mov BYTE PTR [rdi], 0 + +@@done: + add rsp, 56 + pop r13 + pop r12 + pop rdi + pop rsi + pop rbx + pop rbp + ret +UObject_GetNameBuf ENDP + +; UObject_IsA(obj:QWORD, cmp:QWORD) -> AL:BYTE (1=yes, 0=no) +; Walks the Class->SuperField chain to check class inheritance. +; SuperField of UClass/UStruct is at offset 0x30. +UObject_IsA PROC + ; RCX = UObject* obj, RDX = UClass* cmp + test rcx, rcx + jz @@no + test rdx, rdx + jz @@no + + mov rax, QWORD PTR [rcx + 10h] ; rax = obj->Class (UClass*) +@@loop: + test rax, rax + jz @@no + cmp rax, rdx + je @@yes + mov rax, QWORD PTR [rax + 30h] ; rax = UStruct::SuperField + jmp @@loop + +@@yes: + mov al, 1 + ret +@@no: + xor al, al + ret +UObject_IsA ENDP + +; ------------------------------------------------------------ +; UObject_GetFullName(obj:QWORD, outBuf:QWORD, maxLen:DWORD) +; Builds "ClassName OuterPath.ObjectName" into outBuf. +; +; Algorithm: +; 1. Get Class->Name -> write to buf as "ClassName" +; 2. Append " " +; 3. Collect up to 8 outers into a local ptr array (outer_ptrs[]) +; 4. Iterate outers outermost-first, append each name with "." separator +; 5. Append "." + own name +; +; Stack frame: +; outer_ptrs: 8 × QWORD = 64 bytes at [rsp+32..95] +; 6 push regs + sub 112 = 0 mod 16 +; 6 pushes after entry: 8->0->8->0->8->0->8 mod 16 (= 8 after 6 pushes) +; sub 112 (112=0): 8-0 = 8 mod 16... need to adjust. +; Actually 6 pushes: entry 8; +6×8=48 -> RSP=entry-48. 8-48=8-0=8 mod 16. +; sub 112 (112 mod 16 = 0): RSP = 8-0 = 8. +; Need sub N where N = 8 mod 16: e.g. sub 120. +; outer_ptrs at [rsp+32..95] (8 slots × 8 bytes), plus shadow [rsp+0..31]. +UObject_GetFullName PROC + ; RCX = UObject*, RDX = char* outBuf, R8D = maxLen + push rbp + push rbx + push rsi + push rdi + push r12 + push r13 + sub rsp, 120 ; 6 pushes -> RSP = 8 mod 16; sub 120(=8) -> 0 + ; Layout: [rsp+32..95] = outer_ptrs[8] (64 bytes), + ; [rsp+96..119] = scratch pad (24 bytes) + + mov rbx, rcx ; rbx = obj + mov rdi, rdx ; rdi = outBuf + movsx r12, r8d ; r12 = maxLen + + ; Start with empty outBuf + test rdi, rdi + jz @@done + mov BYTE PTR [rdi], 0 + + test rbx, rbx + jz @@done + + ; Get class name into szNameBuf + mov rax, QWORD PTR [rbx + 10h] ; rax = Class (UClass*) + test rax, rax + jz @@skip_class_name + + mov rcx, rax + lea rdx, szNameBuf + mov r8d, 255 + call UObject_GetNameBuf + + ; strcat(outBuf, szNameBuf) + mov rcx, rdi + lea rdx, szNameBuf + call strcat + + ; strcat(outBuf, " ") + mov rcx, rdi + lea rdx, szSpace + call strcat + +@@skip_class_name: + ; Collect outer chain + ; outer_ptrs stored at [rsp+32..95], R13D = depth counter + xor r13d, r13d + mov rax, QWORD PTR [rbx + 20h] ; rax = this->Outer + +@@collect_outers: + test rax, rax + jz @@outers_done + cmp r13d, 8 + jge @@outers_done + ; outer_ptrs[r13] = rax + movsx rcx, r13d + mov QWORD PTR [rsp + 32 + rcx*8], rax + inc r13d + mov rax, QWORD PTR [rax + 20h] ; rax = outer->Outer + jmp @@collect_outers + +@@outers_done: + ; Append outers outermost-first (index depth-1 .. 0) + mov esi, r13d ; esi = depth (loop counter, going down) + test esi, esi + jz @@own_name + +@@outer_loop: + dec esi + movsx rcx, esi + mov rax, QWORD PTR [rsp + 32 + rcx*8] ; outer_ptrs[i] + mov rcx, rax + lea rdx, szNameBuf + mov r8d, 255 + call UObject_GetNameBuf + + ; strcat(outBuf, szNameBuf) + mov rcx, rdi + lea rdx, szNameBuf + call strcat + + ; Append "." between path segments (and before own name) + mov rcx, rdi + lea rdx, szDot + call strcat + + test esi, esi + jnz @@outer_loop + +@@own_name: + ; Append own name + mov rcx, rbx + lea rdx, szNameBuf + mov r8d, 255 + call UObject_GetNameBuf + + mov rcx, rdi + lea rdx, szNameBuf + call strcat + +@@done: + add rsp, 120 + pop r13 + pop r12 + pop rdi + pop rsi + pop rbx + pop rbp + ret +UObject_GetFullName ENDP + +; SDK_FindObject(searchKey:QWORD) -> RAX:QWORD (UObject*) +; Iterates GObjects and returns the first object whose full name +; contains searchKey as a substring (strstr match). +; Maps to: UObject::FindObject(name) +; Stack: push rbp rbx rsi rdi r12 (5 pushes) + sub 40 = 0 mod 16 +SDK_FindObject PROC + ; RCX = const char* searchKey + push rbp + push rbx + push rsi + push rdi + push r12 + sub rsp, 40 ; 5 pushes (8 mod 16); sub 40(=8) -> 0 + + mov r12, rcx ; r12 = searchKey + + ; Null / not-ready guards + test r12, r12 + jz @@not_found + mov rbx, QWORD PTR [GObjects] + test rbx, rbx + jz @@not_found + + ; Get NumElements from [rbx + 0Ch] + mov edi, DWORD PTR [rbx + 0Ch] ; edi = NumElements + xor esi, esi ; esi = index + +@@scan_loop: + cmp esi, edi + jge @@not_found + + ; obj = GObjects->Objects[esi * 24] + mov rax, QWORD PTR [rbx] ; rax = Objects ptr + movsx rcx, esi + imul rcx, rcx, 24 + mov rax, QWORD PTR [rax + rcx] ; rax = UObject* + inc esi + test rax, rax + jz @@scan_loop ; skip null slots + + ; Get full name into szFullNameBuf + mov rcx, rax + lea rdx, szFullNameBuf + mov r8d, 511 + call UObject_GetFullName + + ; strstr(szFullNameBuf, searchKey) + lea rcx, szFullNameBuf + mov rdx, r12 + call strstr + test rax, rax + jz @@scan_loop + + ; Found: reload the object ptr (re-derive from esi-1) + mov rax, QWORD PTR [rbx] + lea rcx, [rsi - 1] + imul rcx, rcx, 24 + mov rax, QWORD PTR [rax + rcx] + jmp @@done + +@@not_found: + xor eax, eax + +@@done: + add rsp, 40 + pop r12 + pop rdi + pop rsi + pop rbx + pop rbp + ret +SDK_FindObject ENDP + +; SDK_FindClass(searchKey:QWORD) -> RAX:QWORD (UClass*) +; Returns the first UClass whose full name exactly equals searchKey. +; Maps to: UObject::FindClass(name) +SDK_FindClass PROC + ; RCX = const char* searchKey (exact full name) + push rbp + push rbx + push rsi + push rdi + push r12 + sub rsp, 40 + + mov r12, rcx + + test r12, r12 + jz @@not_found + mov rbx, QWORD PTR [GObjects] + test rbx, rbx + jz @@not_found + + mov edi, DWORD PTR [rbx + 0Ch] + xor esi, esi + +@@scan_loop: + cmp esi, edi + jge @@not_found + + mov rax, QWORD PTR [rbx] + movsx rcx, esi + imul rcx, rcx, 24 + mov rax, QWORD PTR [rax + rcx] + inc esi + test rax, rax + jz @@scan_loop + + ; Build full name + mov rcx, rax + lea rdx, szFullNameBuf + mov r8d, 511 + call UObject_GetFullName + + ; strcmp(szFullNameBuf, searchKey) + lea rcx, szFullNameBuf + mov rdx, r12 + call strcmp + test eax, eax + jnz @@scan_loop + + ; Exact match — reload object ptr + mov rax, QWORD PTR [rbx] + lea rcx, [rsi - 1] + imul rcx, rcx, 24 + mov rax, QWORD PTR [rax + rcx] + jmp @@done + +@@not_found: + xor eax, eax + +@@done: + add rsp, 40 + pop r12 + pop rdi + pop rsi + pop rbx + pop rbp + ret +SDK_FindClass ENDP + +; ProcessEvent_Call(obj:QWORD, fn:QWORD, params:QWORD) +; Indirect call through the ProcessEvent function pointer. +; Maps to: ProcessEvent(Object, Function, Params) +ProcessEvent_Call PROC + ; RCX = UObject*, RDX = UFunction*, R8 = void* params + push rbp + push rbx + sub rsp, 40 + + mov rax, QWORD PTR [ProcessEvent] + test rax, rax + jz @@done + call rax ; ProcessEvent(obj, fn, params) — rcx/rdx/r8 already set + +@@done: + add rsp, 40 + pop rbx + pop rbp + ret +ProcessEvent_Call ENDP + +.const + +szSpace DB " ", 0 +szDot DB ".", 0 + +END \ No newline at end of file diff --git a/sdk/sdk_fort.asm b/sdk/sdk_fort.asm new file mode 100644 index 0000000..02cf132 --- /dev/null +++ b/sdk/sdk_fort.asm @@ -0,0 +1,201 @@ +INCLUDE include\master.inc + +.data? + +pCachedEngine QWORD ? ; UFortEngine* (cached after first call) + +; UFunction pointers needed for CreateConsole (found via FindObject) +pFn_SpawnObject QWORD ? ; UFunction* for GameplayStatics.STATIC_SpawnObject + +.const + +; Used to locate the UFortEngine singleton +szFortEngineKey DB "FortEngine_", 0 + +; Used to locate static classes needed for CreateConsole +szUConsoleClass DB "Class Engine.Console", 0 +szGameViewportClass DB "Class Engine.GameViewportClient", 0 +szSpawnObjectFn DB "Function Engine.GameplayStatics.SpawnObject", 0 + +.code + +; SDK_GetEngine() -> RAX:QWORD (UFortEngine*) +; Returns the global UFortEngine singleton. +; Caches the result in pCachedEngine after the first successful lookup. +; Mirrors UE4.h: GetEngine() { static auto engine = FindObject("FortEngine_"); } +SDK_GetEngine PROC + push rbp + push rbx + sub rsp, 40 ; 2 pushes (entry 8->0->8); sub 40(=8) -> 0 + + ; Return cached pointer if already resolved + mov rax, QWORD PTR [pCachedEngine] + test rax, rax + jnz @@done + + ; FindObject("FortEngine_") + lea rcx, szFortEngineKey + call SDK_FindObject + test rax, rax + jz @@done + + mov QWORD PTR [pCachedEngine], rax ; cache it + +@@done: + add rsp, 40 + pop rbx + pop rbp + ret +SDK_GetEngine ENDP + +; SDK_GetWorld() -> RAX:QWORD (UWorld*) +; Returns GetEngine()->GameViewport->World. +; UEngine::GameViewport at [Engine + 0x0728] +; UGameViewportClient::World at [GameViewport + 0x0088] +SDK_GetWorld PROC + push rbp + push rbx + sub rsp, 40 + + call SDK_GetEngine + test rax, rax + jz @@null + + mov rax, QWORD PTR [rax + 0728h] ; GameViewport (UGameViewportClient*) + test rax, rax + jz @@null + + mov rax, QWORD PTR [rax + 0088h] ; World (UWorld*) + jmp @@done + +@@null: + xor eax, eax + +@@done: + add rsp, 40 + pop rbx + pop rbp + ret +SDK_GetWorld ENDP + +; SDK_CreateConsole() +; Spawns a UConsole object and attaches it to GameViewport. +; Stack: push rbp rbx rsi rdi r12 (5 pushes) + sub 72 = 0 mod 16 +; 5 pushes: entry 8 -> 0->8->0->8->0 (after 5 pushes = 0 mod 16... wait) +; Actually: push×5 subtracts 40. entry RSP 8; 8-40=8-40... in terms of mod: +; 8 mod 16 - 5×8 mod 16 = 8 - 40 mod 16 = 8 - 8 = 0 mod 16. +; sub 72 (72=8): 0-8 = 8 ✗. Need sub 64 (64=0): 0-0 = 0 . +SDK_CreateConsole PROC + push rbp + push rbx + push rsi + push rdi + push r12 + sub rsp, 64 ; 5 pushes (RSP = 0 mod 16); sub 64(=0) -> 0 + ; [rsp+32..63] = SpawnObject params struct (32 bytes) + + ; Get Engine and GameViewport + call SDK_GetEngine + test rax, rax + jz @@done + mov rbx, rax ; rbx = Engine + + mov rsi, QWORD PTR [rbx + 0728h] ; rsi = GameViewport + test rsi, rsi + jz @@done + + ; Get UConsole::StaticClass + lea rcx, szUConsoleClass + call SDK_FindClass + test rax, rax + jz @@done + mov r12, rax ; r12 = UConsoleClass + + ; Find SpawnObject UFunction + mov rdi, QWORD PTR [pFn_SpawnObject] + test rdi, rdi + jnz @@have_fn + + lea rcx, szSpawnObjectFn + call SDK_FindObject + test rax, rax + jz @@done + mov rdi, rax + mov QWORD PTR [pFn_SpawnObject], rdi + +@@have_fn: + ; Build params struct for STATIC_SpawnObject on stack + ; STATIC_SpawnObject(ObjectClass:UClass*, Outer:UObject*) -> UObject* + ; Params layout (ProcessEvent param block): + ; [+0x00] ObjectClass: QWORD (UClass*) + ; [+0x08] Outer: QWORD (UObject*) + ; [+0x10] ReturnValue: QWORD (UObject*) <- filled by ProcessEvent + mov QWORD PTR [rsp + 32], r12 ; ObjectClass = UConsoleClass + mov QWORD PTR [rsp + 32 + 8], rsi ; Outer = GameViewport + mov QWORD PTR [rsp + 32 + 16], 0 ; ReturnValue = null (filled by fn) + + ; Call ProcessEvent on GameplayStatics static class + mov rcx, rsi ; RCX = 'this' (GameViewport = outer) + mov rdx, rdi ; RDX = UFunction* SpawnObject + lea r8, [rsp + 32] ; R8 = params struct + call ProcessEvent_Call + + ; Store result in GameViewport->ViewportConsole + mov rax, QWORD PTR [rsp + 32 + 16] ; ReturnValue = UConsole* + test rax, rax + jz @@done + mov QWORD PTR [rsi + 0040h], rax ; GameViewport->ViewportConsole = console + +@@done: + add rsp, 64 + pop r12 + pop rdi + pop rsi + pop rbx + pop rbp + ret +SDK_CreateConsole ENDP + +; FortHelper_GetAbilitySystemComponent(pawn:QWORD) -> RAX:QWORD +; Returns the UAbilitySystemComponent* from a pawn. +; AbilitySystemComponent is at pawn+0x0280 (ACharacter field). +FortHelper_GetAbilitySystemComponent PROC + ; RCX = APlayerPawn_Athena_C* pawn + test rcx, rcx + jz @@null + mov rax, QWORD PTR [rcx + 0280h] ; AbilitySystemComponent at +0x0280 + ret +@@null: + xor eax, eax + ret +FortHelper_GetAbilitySystemComponent ENDP + +; FortHelper_GetWorldInventory(pc:QWORD) -> RAX:QWORD +; Returns AFortInventory* from a player controller. +; AFortPlayerControllerAthena::WorldInventory = AFPC_WorldInventory = 0x1ED8 +FortHelper_GetWorldInventory PROC + ; RCX = AFortPlayerControllerAthena* pc + test rcx, rcx + jz @@null + mov rax, QWORD PTR [rcx + AFPC_WorldInventory] ; +0x1ED8 + ret +@@null: + xor eax, eax + ret +FortHelper_GetWorldInventory ENDP + +; FortHelper_GetQuickBars(pc:QWORD) -> RAX:QWORD +; Returns AFortQuickBars* from a player controller. +; AFortPlayerControllerAthena::QuickBars = AFPC_QuickBars = 0x1C38 +FortHelper_GetQuickBars PROC + ; RCX = AFortPlayerControllerAthena* pc + test rcx, rcx + jz @@null + mov rax, QWORD PTR [rcx + AFPC_QuickBars] ; +0x1C38 + ret +@@null: + xor eax, eax + ret +FortHelper_GetQuickBars ENDP + +END \ No newline at end of file diff --git a/sdk/sdk_structs.asm b/sdk/sdk_structs.asm new file mode 100644 index 0000000..7dc4aaa --- /dev/null +++ b/sdk/sdk_structs.asm @@ -0,0 +1,3 @@ +INCLUDE include\master.inc + +END \ No newline at end of file diff --git a/sdk_globals.asm b/sdk_globals.asm new file mode 100644 index 0000000..cba86d5 --- /dev/null +++ b/sdk_globals.asm @@ -0,0 +1,89 @@ +INCLUDE include\master.inc + +.data? + +; Core UE4 runtime function pointers +ProcessEvent QWORD ? ; void (*)(UObject*, UFunction*, void*) +Imagebase QWORD ? ; uintptr_t — base of game .exe module + +FMemory_Malloc QWORD ? ; void* (*)(int32 size, int32 alignment) +FMemory_Realloc QWORD ? ; void* (*)(void* mem, int64 newSize, uint32 align) +FMemory_Free QWORD ? ; void (*)(void* mem) +FNameToString QWORD ? ; void (*)(FName* this, FString& out) +GObjects QWORD ? ; TUObjectArray* — global UObject table + +; Native:: function pointers +; Actor +Native_Actor_GetNetMode QWORD ? + +; PlayerController +Native_PlayerController_GetPlayerViewPoint QWORD ? + +; LocalPlayer +Native_LocalPlayer_SpawnPlayActor QWORD ? + +; Garbage collection +Native_GC_CollectGarbage QWORD ? + +; AbilitySystemComponent +Native_AbilitySystemComponent_GiveAbility QWORD ? +Native_AbilitySystemComponent_InternalTryActivateAbility QWORD ? +Native_AbilitySystemComponent_MarkAbilitySpecDirty QWORD ? +Native_AbilitySystemComponent_FindAbilitySpecFromHandle QWORD ? + +; NetDriver +Native_NetDriver_TickFlush QWORD ? +Native_NetDriver_InitListen QWORD ? + +; ReplicationDriver +Native_ReplicationDriver_ServerReplicateActors QWORD ? + +; NetConnection +Native_NetConnection_ReceiveFString QWORD ? +Native_NetConnection_ReceiveUniqueIdRepl QWORD ? + +; OnlineSession +Native_OnlineSession_KickPlayer QWORD ? + +; OnlineBeacon +Native_OnlineBeacon_PauseBeaconRequests QWORD ? +Native_OnlineBeacon_NotifyAcceptingConnection QWORD ? + +; OnlineBeaconHost +Native_OnlineBeaconHost_InitHost QWORD ? +Native_OnlineBeaconHost_NotifyControlMessage QWORD ? +Native_OnlineBeaconHost_NotifyAcceptingConnection QWORD ? +Native_OnlineBeaconHost_PauseBeaconRequests QWORD ? + +; World +Native_World_RemoveNetworkActor QWORD ? +Native_World_WelcomePlayer QWORD ? +Native_World_NotifyControlMessage QWORD ? +Native_World_SpawnActor QWORD ? +Native_World_SpawnPlayActor QWORD ? +Native_World_NotifyAcceptingConnection QWORD ? + +; Engine +Native_Engine_SeamlessTravelHandlerForWorld QWORD ? + +; GameViewportClient +Native_GameViewportClient_PostRender QWORD ? + +; Game state globals +bTraveled BYTE ? ; bool — server has traveled to map +bPlayButton BYTE ? ; bool — play button clicked +bListening BYTE ? ; bool — server is listening for connections +bStartedBus BYTE ? ; bool — battle bus has launched +bSpawnedFloorLoot BYTE ? ; bool — floor loot has been spawned + BYTE 3 DUP (?) ; pad to QWORD boundary + +; Pointer globals +HostBeacon QWORD ? ; AFortOnlineBeaconHost* + +; ExistingBuildings — std::vector represented as TArrayHeader +; Layout matches TArrayHeader: Data(QWORD), Count(DWORD), Max(DWORD) +ExistingBuildings QWORD ? ; Data: ABuildingActor** array ptr +ExistingBuildingsNum DWORD ? ; Count +ExistingBuildingsMax DWORD ? ; Max + +END \ No newline at end of file