Skip to content

Commit

Permalink
SDK: Bruteforce UProperty/FField Name/Next offsets
Browse files Browse the repository at this point in the history
  • Loading branch information
praydog committed Jul 7, 2023
1 parent b9288ee commit af9f7f0
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ list(APPEND sdk_SOURCES
"shared/sdk/UObject.cpp"
"shared/sdk/UObjectArray.cpp"
"shared/sdk/UObjectBase.cpp"
"shared/sdk/UProperty.cpp"
"shared/sdk/Utility.cpp"
"shared/sdk/CVar.hpp"
"shared/sdk/ConsoleManager.hpp"
Expand All @@ -354,6 +355,7 @@ list(APPEND sdk_SOURCES
"shared/sdk/UObject.hpp"
"shared/sdk/UObjectArray.hpp"
"shared/sdk/UObjectBase.hpp"
"shared/sdk/UProperty.hpp"
"shared/sdk/Utility.hpp"
"shared/sdk/threading/GameThreadWorker.hpp"
"shared/sdk/threading/RHIThreadWorker.hpp"
Expand Down Expand Up @@ -423,6 +425,7 @@ list(APPEND sdk-nolog_SOURCES
"shared/sdk/UObject.cpp"
"shared/sdk/UObjectArray.cpp"
"shared/sdk/UObjectBase.cpp"
"shared/sdk/UProperty.cpp"
"shared/sdk/Utility.cpp"
"shared/sdk/CVar.hpp"
"shared/sdk/ConsoleManager.hpp"
Expand All @@ -445,6 +448,7 @@ list(APPEND sdk-nolog_SOURCES
"shared/sdk/UObject.hpp"
"shared/sdk/UObjectArray.hpp"
"shared/sdk/UObjectBase.hpp"
"shared/sdk/UProperty.hpp"
"shared/sdk/Utility.hpp"
"shared/sdk/threading/GameThreadWorker.hpp"
"shared/sdk/threading/RHIThreadWorker.hpp"
Expand Down
118 changes: 117 additions & 1 deletion shared/sdk/UClass.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#include <windows.h>
#include <spdlog/spdlog.h>

#include <utility/String.hpp>

#include "UObjectArray.hpp"
#include "UProperty.hpp"

#include "UClass.hpp"

Expand Down Expand Up @@ -54,6 +57,8 @@ void UStruct::update_offsets() {
SPDLOG_INFO("[UStruct] UStruct: 0x{:x}", (uintptr_t)struct_class);
SPDLOG_INFO("[UStruct] UClass: 0x{:x}", (uintptr_t)class_class);

bool found_super_struct = false;

if (class_class != nullptr && struct_class != nullptr && field_class != nullptr) {
for (auto i = sdk::UObjectBase::get_class_size(); i < 0x100; i += sizeof(void*)) try {
const auto value = *(sdk::UStruct**)((uintptr_t)class_class + i);
Expand All @@ -63,6 +68,7 @@ void UStruct::update_offsets() {
*(sdk::UStruct**)((uintptr_t)struct_class + i) == field_class &&
*(sdk::UStruct**)((uintptr_t)field_class + i) == object_class)
{
found_super_struct = true;
SPDLOG_INFO("[UStruct] Found SuperStruct at offset 0x{:X}", i);
break;
}
Expand All @@ -73,6 +79,116 @@ void UStruct::update_offsets() {
SPDLOG_ERROR("[UStruct] Failed to find classes!");
}

const auto child_search_start = found_super_struct ? sdk::UObjectBase::get_class_size() : s_super_struct_offset + sizeof(void*);

const auto matrix_scriptstruct = sdk::find_uobject(L"ScriptStruct /Script/CoreUObject.Matrix");
const auto vector_scriptstruct = sdk::find_uobject(L"ScriptStruct /Script/CoreUObject.Vector");

if (matrix_scriptstruct != nullptr && vector_scriptstruct != nullptr) {
bool found = false;

for (auto i = child_search_start; i < 0x300; i += sizeof(void*)) try {
// These can be either FField or UField (UProperty or FProperty)
// quantum physics... brutal.
const auto value = *(void**)((uintptr_t)vector_scriptstruct + i);

if (value == nullptr || IsBadReadPtr(value, sizeof(void*)) || ((uintptr_t)value & 1) != 0) {
continue;
}

const auto vtable = *(void**)value;

if (vtable == nullptr || IsBadReadPtr(vtable, sizeof(void*)) || ((uintptr_t)vtable & 1) != 0) {
continue;
}

const auto vfunc = *(void**)vtable;

if (vfunc == nullptr || IsBadReadPtr(vfunc, sizeof(void*))) {
continue;
}

// Now we need to walk the memory of the value to see if calling FName::ToString results in "X" or "Y" or "Z"
// this means we found something that looks like the fields array, as well as resolving the offset to the name index.
for (auto j = sizeof(void*); j < 0x100; ++j) try {
const auto& possible_fname_a1 = *(FName*)((uintptr_t)value + j);
const auto possible_name_a1 = possible_fname_a1.to_string();

if (possible_name_a1 == L"X" || possible_name_a1 == L"Y" || possible_name_a1 == L"Z") {
SPDLOG_INFO("[UStruct] Found Children at offset 0x{:X}", i);
SPDLOG_INFO("[UStruct] Found UProperty/FProperty Name at offset 0x{:X} ({})", j, utility::narrow(possible_name_a1));
s_children_offset = i;
UProperty::s_name_offset = j;

// Now we need to locate the "Next" field of the UField struct.
// We cant start at UObjectBase::get_class_size() because it can be an FField or UField.
for (auto k = 0; k < 0x100; k += sizeof(void*)) try {
const auto potential_field = *(sdk::UField**)((uintptr_t)value + k);

if (potential_field == nullptr || IsBadReadPtr(potential_field, sizeof(void*)) || ((uintptr_t)potential_field & 1) != 0) {
continue;
}

const auto potential_field_vtable = *(void**)potential_field;

if (potential_field_vtable == nullptr || IsBadReadPtr(potential_field_vtable, sizeof(void*)) || ((uintptr_t)potential_field_vtable & 1) != 0) {
continue;
}

const auto potential_field_vfunc = *(void**)potential_field_vtable;

if (potential_field_vfunc == nullptr || IsBadReadPtr(potential_field_vfunc, sizeof(void*))) {
continue;
}

const auto& potential_field_fname = ((UProperty*)potential_field)->get_property_name();
const auto potential_next_field_name = potential_field_fname.to_string();

if (possible_name_a1 != potential_next_field_name &&
(potential_next_field_name == L"X" || potential_next_field_name == L"Y" || potential_next_field_name == L"Z")) {
SPDLOG_INFO("[UStruct] Found true UField Next at offset 0x{:X} ({})", k, utility::narrow(potential_next_field_name));
UField::s_next_offset = k;

const auto next_next = (sdk::UProperty*)potential_field->get_next();

if (next_next != nullptr) try {
const auto& next_next_fname = next_next->get_property_name();
const auto next_next_name = next_next_fname.to_string();

SPDLOG_INFO("[UStruct] Next Next Name: {}", utility::narrow(next_next_name));
} catch(...) {
SPDLOG_ERROR("[UStruct] Failed to get Next Next Name!");
}

found = true;
break;
}
} catch(...) {
continue;
}
}

if (found) {
break;
}
} catch(...) {
continue;
}

if (found) {
break;
}
} catch(...) {
continue;
}

if (!found) {
SPDLOG_ERROR("[UStruct] Failed to find Children and Name!");
}
} else {
SPDLOG_ERROR("[UStruct] Failed to find Matrix/Vector!");
}

UField::update_offsets();
}

Expand Down Expand Up @@ -153,7 +269,7 @@ void UScriptStruct::update_offsets() {
constexpr auto VECTOR_SIZE_FLOAT = 3 * sizeof(float);
constexpr auto VECTOR_SIZE_DOUBLE = 3 * sizeof(double);

for (auto i = UStruct::s_super_struct_offset; i < 0x300; i += sizeof(void*)) try {
for (auto i = UStruct::s_super_struct_offset + sizeof(void*); i < 0x300; i += sizeof(void*)) try {
const auto value = *(sdk::UScriptStruct::StructOps**)((uintptr_t)matrix_scriptstruct + i);

if (value == nullptr || IsBadReadPtr(value, sizeof(void*)) || ((uintptr_t)value & 1) != 0) {
Expand Down
9 changes: 9 additions & 0 deletions shared/sdk/UClass.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@

namespace sdk {
class UClass;
class UStruct;

class UField : public UObject {
public:
static UClass* static_class();
static void update_offsets();

UField* get_next() const {
return *(UField**)((uintptr_t)this + s_next_offset);
}

protected:
static inline bool s_attempted_update_offsets{false};
static inline uint32_t s_next_offset{0x30}; // not correct always, we bruteforce it later

friend class UStruct;
};

class UStruct : public UField {
Expand All @@ -37,6 +44,8 @@ class UStruct : public UField {
protected:
static inline bool s_attempted_update_offsets{false};
static inline uint32_t s_super_struct_offset{0x40}; // not correct always, we bruteforce it later
static inline uint32_t s_children_offset{0x48}; // not correct always, we bruteforce it later
static inline uint32_t s_properties_size_offset{0x50}; // not correct always, we bruteforce it later

friend class UField;
};
Expand Down
2 changes: 2 additions & 0 deletions shared/sdk/UObjectArray.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "UObject.hpp"
#include "UClass.hpp"
#include "UProperty.hpp"
#include "FName.hpp"
#include "UObjectArray.hpp"

Expand Down Expand Up @@ -351,6 +352,7 @@ FUObjectArray* FUObjectArray::get() {
sdk::UStruct::update_offsets();
sdk::UClass::update_offsets();
sdk::UScriptStruct::update_offsets();
sdk::UProperty::update_offsets();

for (auto i = 0; i < result->get_object_count(); ++i) {
auto item = result->get_object(i);
Expand Down
23 changes: 23 additions & 0 deletions shared/sdk/UProperty.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include <spdlog/spdlog.h>

#include "UObjectArray.hpp"

#include "UProperty.hpp"

namespace sdk{
UClass* UProperty::static_class() {
return (UClass*)sdk::find_uobject(L"Class /Script/CoreUObject.Property");
}

void UProperty::update_offsets() {
if (s_attempted_update_offsets) {
return;
}

s_attempted_update_offsets = true;

UField::update_offsets();

SPDLOG_INFO("[UProperty] Bruteforcing offsets...");
}
}
22 changes: 22 additions & 0 deletions shared/sdk/UProperty.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

#include "UClass.hpp"

namespace sdk {
class UProperty : public UField {
public:
static UClass* static_class();
static void update_offsets();

FName& get_property_name() const {
return *(FName*)((uintptr_t)this + s_name_offset);
}

private:
static inline bool s_attempted_update_offsets{false};
static inline uint32_t s_name_offset{0x0}; // idk what it is usually.
static inline uint32_t s_structure_size{0x0}; // idk what it is usually.

friend class UStruct;
};
}

0 comments on commit af9f7f0

Please sign in to comment.