SDK globals, TArray/FString/UObject infrastructure

This commit is contained in:
ApfelTeeSaft
2026-05-05 10:06:03 +02:00
parent 26fc916e9b
commit f93a5d4ccc
6 changed files with 1109 additions and 6 deletions
+1 -1
View File
@@ -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
-5
View File
@@ -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
+815
View File
@@ -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<wchar_t>
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<T>(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
+201
View File
@@ -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<UFortEngine>("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
+3
View File
@@ -0,0 +1,3 @@
INCLUDE include\master.inc
END
+89
View File
@@ -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<ABuildingActor*> 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