From 18abe44ea91f00e734e4c8858fdd6d7d281cc330 Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 27 Oct 2023 20:34:45 -0700 Subject: [PATCH] SDK: Bruteforce FBoolProperty offsets --- CMakeLists.txt | 2 + shared/sdk/FBoolProperty.cpp | 116 +++++++++++++++++++++++++++++++++++ shared/sdk/FBoolProperty.hpp | 52 ++++++++++++++++ shared/sdk/FProperty.hpp | 2 +- shared/sdk/UObjectArray.cpp | 2 + src/mods/UObjectHook.cpp | 10 +-- 6 files changed, 179 insertions(+), 5 deletions(-) create mode 100644 shared/sdk/FBoolProperty.cpp create mode 100644 shared/sdk/FBoolProperty.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c7cb963f..1654721f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -322,6 +322,7 @@ list(APPEND sdk_SOURCES "shared/sdk/ConsoleManager.cpp" "shared/sdk/DynamicRHI.cpp" "shared/sdk/EngineModule.cpp" + "shared/sdk/FBoolProperty.cpp" "shared/sdk/FField.cpp" "shared/sdk/FMalloc.cpp" "shared/sdk/FName.cpp" @@ -359,6 +360,7 @@ list(APPEND sdk_SOURCES "shared/sdk/ConsoleManager.hpp" "shared/sdk/DynamicRHI.hpp" "shared/sdk/EngineModule.hpp" + "shared/sdk/FBoolProperty.hpp" "shared/sdk/FField.hpp" "shared/sdk/FFieldClass.hpp" "shared/sdk/FMalloc.hpp" diff --git a/shared/sdk/FBoolProperty.cpp b/shared/sdk/FBoolProperty.cpp new file mode 100644 index 00000000..8a46a28c --- /dev/null +++ b/shared/sdk/FBoolProperty.cpp @@ -0,0 +1,116 @@ +#include + +#include "UObjectArray.hpp" +#include "UClass.hpp" + +#include "FBoolProperty.hpp" + +namespace sdk { +void FBoolProperty::update_offsets() { + if (s_updated_offsets) { + return; + } + + s_updated_offsets = true; + + SPDLOG_INFO("[FBoolProperty::update_offsets] Updating offsets"); + + const auto fhitresult = sdk::find_uobject(L"ScriptStruct /Script/Engine.HitResult"); + + if (fhitresult == nullptr) { + SPDLOG_ERROR("[FBoolProperty::update_offsets] Failed to find FHitResult"); + return; + } + + // Need to iterate over FHItResult's properties to find the first bool property + // on <= 4.27 or so it's always the first property but not in the newer ones. + FBoolProperty* first_bool_property{nullptr}; + + for (auto prop = fhitresult->get_child_properties(); prop != nullptr; prop = prop->get_next()) { + const auto c = prop->get_class(); + + if (c == nullptr) { + continue; + } + + if (c->get_name().to_string() == L"BoolProperty") { + first_bool_property = (FBoolProperty*)prop; + break; + } + } + + if (first_bool_property == nullptr) { + SPDLOG_ERROR("[FBoolProperty::update_offsets] Failed to find first bool property"); + return; + } + + FBoolProperty* second_bool_property{nullptr}; + + auto next_prop = first_bool_property->get_next(); + + if (next_prop == nullptr) { + SPDLOG_ERROR("[FBoolProperty::update_offsets] Failed to find second bool property"); + return; + } + + second_bool_property = (FBoolProperty*)next_prop; + + const auto second_c = next_prop->get_class(); + + if (second_c == nullptr || second_c->get_name().to_string() != L"BoolProperty") { + SPDLOG_ERROR("[FBoolProperty::update_offsets] Failed to find second bool property (class check)"); + return; + } + + // Start from FProperty's offset offset, and bruteforce until we find the correct offset + // Since there's two BoolProperties sequentially laid out, we should be able to figure out what the mask is + const auto initial_start = FProperty::s_offset_offset + 4 + sizeof(void*) + sizeof(void*); + // align up to sizeof(void*) + const auto start = (initial_start + sizeof(void*) - 1) & ~(sizeof(void*) - 1); + for (auto i = start; i < start + 0x100; ++i) try { + const auto potential_field_size_a = *(uint8_t*)((uintptr_t)first_bool_property + i); + const auto potential_field_size_b = *(uint8_t*)((uintptr_t)second_bool_property + i); + const auto potential_byte_offset_a = *(uint8_t*)((uintptr_t)first_bool_property + i + 1); + const auto potential_byte_offset_b = *(uint8_t*)((uintptr_t)second_bool_property + i + 1); + const auto potential_byte_mask_a = *(uint8_t*)((uintptr_t)first_bool_property + i + 2); + const auto potential_byte_mask_b = *(uint8_t*)((uintptr_t)second_bool_property + i + 2); + const auto potential_field_mask_a = *(uint8_t*)((uintptr_t)first_bool_property + i + 3); + const auto potential_field_mask_b = *(uint8_t*)((uintptr_t)second_bool_property + i + 3); + + // Log all of these for debugging even if they arent right + /*SPDLOG_INFO("[FBoolProperty::update_offsets] Field size A: 0x{:X} ({:x})", potential_field_size_a, i); + SPDLOG_INFO("[FBoolProperty::update_offsets] Field size B: 0x{:X} ({:x})", potential_field_size_b, i); + SPDLOG_INFO("[FBoolProperty::update_offsets] Byte offset A: 0x{:X} ({:x})", potential_byte_offset_a, i + 1); + SPDLOG_INFO("[FBoolProperty::update_offsets] Byte offset B: 0x{:X} ({:x})", potential_byte_offset_b, i + 1); + SPDLOG_INFO("[FBoolProperty::update_offsets] Byte mask A: 0x{:X} ({:x})", potential_byte_mask_a, i + 2); + SPDLOG_INFO("[FBoolProperty::update_offsets] Byte mask B: 0x{:X} ({:x})", potential_byte_mask_b, i + 2); + SPDLOG_INFO("[FBoolProperty::update_offsets] Field mask A: 0x{:X} ({:x})", potential_field_mask_a, i + 3); + SPDLOG_INFO("[FBoolProperty::update_offsets] Field mask B: 0x{:X} ({:x})", potential_field_mask_b, i + 3);*/ + + if (potential_field_size_a == 1 && potential_field_size_b == 1 && + potential_byte_offset_a == 0 && potential_byte_offset_b == 0 && + potential_byte_mask_a == 1 && potential_byte_mask_b == 2 && + potential_field_mask_a == 1 && potential_field_mask_b == 2) + { + SPDLOG_INFO("[FBoolProperty::update_offsets] Field size offset: 0x{:X}", i); + SPDLOG_INFO("[FBoolProperty::update_offsets] Byte offset offset: 0x{:X}", i + 1); + SPDLOG_INFO("[FBoolProperty::update_offsets] Byte mask offset: 0x{:X}", i + 2); + SPDLOG_INFO("[FBoolProperty::update_offsets] Field mask offset: 0x{:X}", i + 3); + s_field_size_offset = i; + s_byte_offset_offset = i + 1; + s_byte_mask_offset = i + 2; + s_field_mask_offset = i + 3; + break; + } + } catch(...) { + continue; + } + + if (s_field_size_offset == 0) { + SPDLOG_ERROR("[FBoolProperty::update_offsets] Failed to find offsets"); + return; + } + + SPDLOG_INFO("[FBoolProperty::update_offsets] Found offsets"); +} +} \ No newline at end of file diff --git a/shared/sdk/FBoolProperty.hpp b/shared/sdk/FBoolProperty.hpp new file mode 100644 index 00000000..944d1606 --- /dev/null +++ b/shared/sdk/FBoolProperty.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include + +#include "FProperty.hpp" + +namespace sdk { +class FBoolProperty : public FProperty { +public: + static void update_offsets(); + + uint8_t get_field_size() const { + return *(uint8_t*)((uintptr_t)this + s_field_size_offset); + } + + uint8_t get_byte_offset() const { + return *(uint8_t*)((uintptr_t)this + s_byte_offset_offset); + } + + uint8_t get_byte_mask() const { + return *(uint8_t*)((uintptr_t)this + s_byte_mask_offset); + } + + uint8_t get_field_mask() const { + return *(uint8_t*)((uintptr_t)this + s_field_mask_offset); + } + + bool get_value_from_object(void* object) const { + return get_value_from_propbase((void*)((uintptr_t)object + get_offset())); + } + + bool get_value_from_propbase(void* addr) const { + return (*(uint8_t*)((uintptr_t)addr + get_byte_offset()) & get_byte_mask()) != 0; + } + + void set_value_in_object(void* object, bool value) const { + set_value_in_propbase((void*)((uintptr_t)object + get_offset()), value); + } + + void set_value_in_propbase(void* addr, bool value) const { + const auto cur_value = *(uint8_t*)((uintptr_t)addr + get_byte_offset()); + *(uint8_t*)((uintptr_t)addr + get_byte_offset()) = (cur_value & ~get_byte_mask()) | (value ? get_byte_mask() : 0); + } + +private: + static inline bool s_updated_offsets{false}; + static inline uint32_t s_field_size_offset{0x0}; // idk + static inline uint32_t s_byte_offset_offset{0x0}; // idk + static inline uint32_t s_byte_mask_offset{0x0}; // idk + static inline uint32_t s_field_mask_offset{0x0}; // idk +}; +} \ No newline at end of file diff --git a/shared/sdk/FProperty.hpp b/shared/sdk/FProperty.hpp index 8709987d..3953b5df 100644 --- a/shared/sdk/FProperty.hpp +++ b/shared/sdk/FProperty.hpp @@ -18,7 +18,7 @@ class FProperty : public FField { // Given xyz props from FVector, find the offset which matches up with all of them static void bruteforce_fproperty_offset(FProperty* x_prop, FProperty* y_prop, FProperty* z_prop); -private: +protected: static inline uint32_t s_offset_offset{0x0}; // idk friend class UStruct; diff --git a/shared/sdk/UObjectArray.cpp b/shared/sdk/UObjectArray.cpp index 9002f7e6..5ec5fb19 100644 --- a/shared/sdk/UObjectArray.cpp +++ b/shared/sdk/UObjectArray.cpp @@ -19,6 +19,7 @@ #include "FName.hpp" #include "FField.hpp" #include "FStructProperty.hpp" +#include "FBoolProperty.hpp" #include "UObjectArray.hpp" namespace sdk { @@ -387,6 +388,7 @@ FUObjectArray* FUObjectArray::get() try { sdk::UScriptStruct::update_offsets(); sdk::UProperty::update_offsets(); sdk::FStructProperty::update_offsets(); + sdk::FBoolProperty::update_offsets(); #ifdef TESTING_GUOBJECTARRAY try { diff --git a/src/mods/UObjectHook.cpp b/src/mods/UObjectHook.cpp index 480e0141..4de786d0 100644 --- a/src/mods/UObjectHook.cpp +++ b/src/mods/UObjectHook.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "VR.hpp" @@ -1214,12 +1215,13 @@ void UObjectHook::ui_handle_properties(void* object, sdk::UStruct* uclass) { ImGui::DragInt(utility::narrow(prop->get_field_name().to_string()).data(), &value, 1); } break; - - // TODO: handle bitfields case "BoolProperty"_fnv: { - auto& value = *(bool*)((uintptr_t)object + ((sdk::FProperty*)prop)->get_offset()); - ImGui::Checkbox(utility::narrow(prop->get_field_name().to_string()).data(), &value); + auto boolprop = (sdk::FBoolProperty*)prop; + auto value = boolprop->get_value_from_object(object); + if (ImGui::Checkbox(utility::narrow(prop->get_field_name().to_string()).data(), &value)) { + boolprop->set_value_in_object(object, value); + } } break; case "ObjectProperty"_fnv: