diff --git a/CMakeLists.txt b/CMakeLists.txt index 77121437..0c8dc0d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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" @@ -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" @@ -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" @@ -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" diff --git a/shared/sdk/UClass.cpp b/shared/sdk/UClass.cpp index 797a9c8d..1911bf5a 100644 --- a/shared/sdk/UClass.cpp +++ b/shared/sdk/UClass.cpp @@ -1,7 +1,10 @@ #include #include +#include + #include "UObjectArray.hpp" +#include "UProperty.hpp" #include "UClass.hpp" @@ -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); @@ -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; } @@ -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(); } @@ -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) { diff --git a/shared/sdk/UClass.hpp b/shared/sdk/UClass.hpp index fa62f09c..36c48bbe 100644 --- a/shared/sdk/UClass.hpp +++ b/shared/sdk/UClass.hpp @@ -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 { @@ -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; }; diff --git a/shared/sdk/UObjectArray.cpp b/shared/sdk/UObjectArray.cpp index 9d87a976..632c3e9f 100644 --- a/shared/sdk/UObjectArray.cpp +++ b/shared/sdk/UObjectArray.cpp @@ -9,6 +9,7 @@ #include "UObject.hpp" #include "UClass.hpp" +#include "UProperty.hpp" #include "FName.hpp" #include "UObjectArray.hpp" @@ -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); diff --git a/shared/sdk/UProperty.cpp b/shared/sdk/UProperty.cpp new file mode 100644 index 00000000..c8de6073 --- /dev/null +++ b/shared/sdk/UProperty.cpp @@ -0,0 +1,23 @@ +#include + +#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..."); +} +} \ No newline at end of file diff --git a/shared/sdk/UProperty.hpp b/shared/sdk/UProperty.hpp new file mode 100644 index 00000000..ec82902f --- /dev/null +++ b/shared/sdk/UProperty.hpp @@ -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; +}; +} \ No newline at end of file