diff --git a/CMakeLists.txt b/CMakeLists.txt index 48bc025c..3de551b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,6 +83,15 @@ FetchContent_Declare(safetyhook ) FetchContent_MakeAvailable(safetyhook) +message(STATUS "Fetching sdkgenny (f58077f8da8a271490c17558e93b75843b3afd19)...") +FetchContent_Declare(sdkgenny + GIT_REPOSITORY + "https://github.com/cursey/sdkgenny" + GIT_TAG + f58077f8da8a271490c17558e93b75843b3afd19 +) +FetchContent_MakeAvailable(sdkgenny) + message(STATUS "Fetching openxr (458984d7f59d1ae6dc1b597d94b02e4f7132eaba)...") FetchContent_Declare(openxr GIT_REPOSITORY @@ -790,6 +799,7 @@ list(APPEND ue4poc_SOURCES "src/mods/PluginLoader.cpp" "src/mods/UObjectHook.cpp" "src/mods/VR.cpp" + "src/mods/uobjecthook/SDKDumper.cpp" "src/mods/vr/Bindings.cpp" "src/mods/vr/CVarManager.cpp" "src/mods/vr/D3D11Component.cpp" @@ -822,6 +832,7 @@ list(APPEND ue4poc_SOURCES "src/mods/PluginLoader.hpp" "src/mods/UObjectHook.hpp" "src/mods/VR.hpp" + "src/mods/uobjecthook/SDKDumper.hpp" "src/mods/vr/CVarManager.hpp" "src/mods/vr/D3D11Component.hpp" "src/mods/vr/D3D12Component.hpp" @@ -895,6 +906,7 @@ target_link_libraries(ue4poc PUBLIC TracyClient DirectXTK DirectXTK12 + sdkgenny ) target_link_libraries(ue4poc PUBLIC diff --git a/cmake.toml b/cmake.toml index 5e80b170..fbdcb049 100644 --- a/cmake.toml +++ b/cmake.toml @@ -56,6 +56,10 @@ cmake-before=""" set(SAFETYHOOK_FETCH_ZYDIS ON) """ +[fetch-content.sdkgenny] +git = "https://github.com/cursey/sdkgenny" +tag = "f58077f8da8a271490c17558e93b75843b3afd19" + [fetch-content.openxr] git = "https://github.com/KhronosGroup/OpenXR-SDK" tag = "458984d7f59d1ae6dc1b597d94b02e4f7132eaba" @@ -249,7 +253,8 @@ link-libraries = [ "Version", "TracyClient", "DirectXTK", - "DirectXTK12" + "DirectXTK12", + "sdkgenny" ] [template.ue4template.properties] diff --git a/shared/sdk/UObjectArray.hpp b/shared/sdk/UObjectArray.hpp index db4448c8..5528953b 100644 --- a/shared/sdk/UObjectArray.hpp +++ b/shared/sdk/UObjectArray.hpp @@ -26,6 +26,18 @@ struct FUObjectArray { return s_is_chunked; } + static bool is_inlined() { + return s_is_inlined_array; + } + + static size_t get_objects_offset() { + return OBJECTS_OFFSET; + } + + static size_t get_item_distance() { + return s_item_distance; + } + int32_t get_object_count() { if (s_is_inlined_array) { constexpr auto offs = OBJECTS_OFFSET + (MAX_INLINED_CHUNKS * sizeof(void*)); @@ -80,7 +92,7 @@ struct FUObjectArray { return *(void**)((uintptr_t)this + OBJECTS_OFFSET); } -private: +public: // for <= 4.10 constexpr static inline auto MAX_INLINED_CHUNKS = ((8 * 1024 * 1024) + 16384 - 1) / 16384; constexpr static inline auto OBJECTS_PER_CHUNK_INLINED = 16384; @@ -88,6 +100,7 @@ struct FUObjectArray { // for some newer versions constexpr static inline auto OBJECTS_PER_CHUNK = 64 * 1024; +private: // has remained true for a long time constexpr static inline auto OBJECTS_OFFSET = 0x10; diff --git a/shared/sdk/UObjectBase.hpp b/shared/sdk/UObjectBase.hpp index 4148c2c0..88597b84 100644 --- a/shared/sdk/UObjectBase.hpp +++ b/shared/sdk/UObjectBase.hpp @@ -38,6 +38,30 @@ class UObjectBase { return s_outer_private_offset + sizeof(void*); } + static uint32_t get_process_event_index() { + return s_process_event_index; + } + + static uint32_t get_object_flags_offset() { + return s_object_flags_offset; + } + + static uint32_t get_internal_index_offset() { + return s_internal_index_offset; + } + + static uint32_t get_class_private_offset() { + return s_class_private_offset; + } + + static uint32_t get_fname_offset() { + return s_fname_offset; + } + + static uint32_t get_outer_private_offset() { + return s_outer_private_offset; + } + static std::optional get_vtable(); static std::optional get_destructor(); static std::optional get_add_object(); diff --git a/src/mods/UObjectHook.cpp b/src/mods/UObjectHook.cpp index 5694a0a9..b4f90c75 100644 --- a/src/mods/UObjectHook.cpp +++ b/src/mods/UObjectHook.cpp @@ -24,6 +24,7 @@ #include #include +#include "uobjecthook/SDKDumper.hpp" #include "VR.hpp" #include "UObjectHook.hpp" @@ -1522,7 +1523,7 @@ sdk::UObject* UObjectHook::StatePath::resolve() const { break; } - // Need to handle StructProperty and ArrayProperty but whatever. + // Need to handle StructProperty but whatever. default: SPDLOG_ERROR("[UObjectHook] Unsupported persistent property type {}", utility::narrow(prop_t_name)); break; @@ -1579,6 +1580,8 @@ void UObjectHook::on_draw_sidebar_entry(std::string_view in_entry) { draw_main(); } else if (in_entry == "Config") { draw_config(); + } else if (in_entry == "Developer") { + draw_developer(); } } @@ -1588,6 +1591,12 @@ void UObjectHook::draw_config() { m_attach_lerp_speed->draw("Attach Lerp Speed"); } +void UObjectHook::draw_developer() { + if (ImGui::Button("Dump SDK")) { + SDKDumper::dump(); + } +} + void UObjectHook::draw_main() { if (!m_motion_controller_attached_components.empty()) { const auto made = ImGui::TreeNode("Attached Components"); diff --git a/src/mods/UObjectHook.hpp b/src/mods/UObjectHook.hpp index 291f521a..8aee454c 100644 --- a/src/mods/UObjectHook.hpp +++ b/src/mods/UObjectHook.hpp @@ -52,7 +52,8 @@ class UObjectHook : public Mod { std::vector get_sidebar_entries() override { return { { "Main", true }, - { "Config", false } + { "Config", false }, + { "Developer", true } }; } @@ -64,6 +65,7 @@ class UObjectHook : public Mod { void on_draw_ui() override; void draw_config(); + void draw_developer(); void draw_main(); void on_pre_calculate_stereo_view_offset(void* stereo_device, const int32_t view_index, Rotator* view_rotation, diff --git a/src/mods/uobjecthook/SDKDumper.cpp b/src/mods/uobjecthook/SDKDumper.cpp new file mode 100644 index 00000000..e34bbe74 --- /dev/null +++ b/src/mods/uobjecthook/SDKDumper.cpp @@ -0,0 +1,521 @@ +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "Framework.hpp" + +#include "SDKDumper.hpp" + +namespace detail { +std::vector get_all_structs() { + std::vector classes{}; + + const auto objects = sdk::FUObjectArray::get(); + + if (objects == nullptr) { + return classes; + } + + const auto class_c = sdk::UStruct::static_class(); + const auto function_c = sdk::UFunction::static_class(); + const auto uenum_c = sdk::UEnum::static_class(); + + for (auto i = 0; i < objects->get_object_count(); ++i) { + const auto entry = objects->get_object(i); + + if (entry == nullptr) { + continue; + } + + const auto object = entry->object; + + if (object == nullptr) { + continue; + } + + const auto object_class = object->get_class(); + + if (object_class == nullptr) { + continue; + } + + if (object_class->is_a(class_c) && !object_class->is_a(function_c) && !object_class->is_a(uenum_c)) { + classes.push_back(reinterpret_cast(object)); + } + } + + return classes; +} +} + +void SDKDumper::dump() { + auto dumper = std::make_unique(); + dumper->dump_internal(); +} + +void SDKDumper::dump_internal() { + initialize_sdk(); + populate_sdk(); + write_sdk(); +} + +void SDKDumper::initialize_sdk() { + m_sdk = std::make_unique(); + m_sdk->include("cstdint"); + + auto g = m_sdk->global_ns(); + + g->type("int8_t")->size(1); + g->type("int16_t")->size(2); + g->type("int32_t")->size(4); + g->type("int64_t")->size(8); + g->type("uint8_t")->size(1); + g->type("uint16_t")->size(2); + g->type("uint32_t")->size(4); + g->type("uint64_t")->size(8); + g->type("float")->size(4); + g->type("double")->size(8); + g->type("bool")->size(1); + g->type("char")->size(1); + g->type("int")->size(4); + g->type("void")->size(0); + + initialize_boilerplate_classes(); +} + +void SDKDumper::initialize_boilerplate_classes() { + initialize_tarray(); + initialize_uobject(); + initialize_uobject_array(); +} + +void SDKDumper::initialize_tarray() { + ExtraSdkClass tarray{}; + tarray.name = "TArray"; + tarray.header = +R"(#pragma once + +#include + +template +class TArray { +public: + T* data; + int32_t count; + int32_t capacity; +};)"; + + m_extra_classes.push_back(tarray); +} + +void SDKDumper::initialize_uobject() { + const auto uobject = sdk::UObject::static_class(); + const auto ufunction = sdk::UFunction::static_class(); + + if (uobject == nullptr || ufunction == nullptr) { + return; + } + + auto s = get_or_generate_struct(uobject); + + if (s == nullptr) { + return; + } + + auto g = m_sdk->global_ns(); + + auto process_event = s->virtual_function("ProcessEvent"); + process_event->vtable_index(sdk::UObjectBase::get_process_event_index()); + process_event->returns(g->type("void")); + process_event->param("func")->type(get_or_generate_struct(ufunction)->ptr()); + process_event->param("params")->type(g->type("void")->ptr()); +} + +void SDKDumper::initialize_uobject_array() { + const auto objs = sdk::FUObjectArray::get(); + + if (objs == nullptr) { + return; + } + + const auto module_within = utility::get_module_within((uintptr_t)objs); + + if (!module_within.has_value()) { + return; + } + + const auto module_path_str = utility::get_module_path(*module_within); + + if (!module_path_str.has_value()) { + return; + } + + std::filesystem::path module_path{module_path_str.value()}; + + // Now strip the module name from the path + const auto module_name = module_path.filename(); + const auto offset = (uintptr_t)objs - (uintptr_t)module_within.value(); + + const auto uobject = sdk::UObject::static_class(); + + if (uobject == nullptr) { + return; + } + + + auto g = m_sdk->global_ns(); + + auto uobject_array = g->class_("FUObjectArray"); + auto uobject_item = g->struct_("FUObjectItem"); + uobject_item->variable("object")->type(get_or_generate_struct(uobject)->ptr())->offset(0); + uobject_item->variable("flags")->type(g->type("int32_t"))->offset(sizeof(void*) * 1); + uobject_item->variable("cluster_index")->type(g->type("int32_t"))->offset((sizeof(void*) * 1) + (sizeof(int32_t) * 1)); + uobject_item->variable("serial_number")->type(g->type("int32_t"))->offset((sizeof(void*) * 1) + (sizeof(int32_t) * 2)); + + + // ::get + auto getter = uobject_array->static_function("get"); + + getter->returns(uobject_array->ptr()); + getter->procedure(std::format("static const auto module = GetModuleHandleA(\"{}\");", module_name.string()) + "\n" + + std::format("static const auto offset = 0x{:x};", offset) + "\n" + + std::format("return (FUObjectArray*)((uintptr_t)module + offset);") + ); + + // ::is_chunked + auto is_chunked = uobject_array->static_function("is_chunked"); + is_chunked->returns(g->type("bool")); + is_chunked->procedure(std::format("return {};", + objs->is_chunked() ? "true" : "false" + )); + + // ::is_inlined + auto is_inlined = uobject_array->static_function("is_inlined"); + is_inlined->returns(g->type("bool")); + is_inlined->procedure(std::format("return {};", + objs->is_inlined() ? "true" : "false" + )); + + // ::get_objects_offset + auto get_objects_offset = uobject_array->static_function("get_objects_offset"); + get_objects_offset->returns(g->type("size_t")); + get_objects_offset->procedure(std::format("return 0x{:x};", objs->get_objects_offset())); + + // ::get_item_distance + auto get_item_distance = uobject_array->static_function("get_item_distance"); + get_item_distance->returns(g->type("size_t")); + get_item_distance->procedure(std::format("return 0x{:x};", objs->get_item_distance())); + + // ::get_object_count + auto get_object_count = uobject_array->function("get_object_count"); + get_object_count->returns(g->type("int32_t")); + + if (objs->is_inlined()) { + const auto offs = sdk::FUObjectArray::get_objects_offset() + (sdk::FUObjectArray::MAX_INLINED_CHUNKS * sizeof(void*)); + get_object_count->procedure(std::format("return *(int32_t*)((uintptr_t)this + 0x{:x});", offs)); + } else if (objs->is_chunked()) { + const auto offs = sdk::FUObjectArray::get_objects_offset() + sizeof(void*) + sizeof(void*) + 0x4; + get_object_count->procedure(std::format("return *(int32_t*)((uintptr_t)this + 0x{:x});", offs)); + } else { + get_object_count->procedure(std::format("return *(int32_t*)((uintptr_t)this + 0x{:x});", sdk::FUObjectArray::get_objects_offset() + 0x8)); + } + + // ::get_object + auto get_object = uobject_array->function("get_object"); + get_object->returns(uobject_item->ptr()); + get_object->param("index")->type(g->type("int32_t")); + + if (objs->is_inlined()) { + get_object->procedure( + std::format("const auto chunk_index = index / {};", + sdk::FUObjectArray::OBJECTS_PER_CHUNK_INLINED + ) + "\n" + + std::format("const auto chunk_offset = index % {};", + sdk::FUObjectArray::OBJECTS_PER_CHUNK_INLINED + ) + "\n" + + std::format("const auto chunk = *(void**)((uintptr_t)get_objects_ptr() + (chunk_index * sizeof(void*)));") + "\n" + + std::format("if (chunk == nullptr) {{ return nullptr; }}") + "\n" + + std::format("return (FUObjectItem*)((uintptr_t)chunk + (chunk_offset * {}));", + objs->get_item_distance() + ) + ); + } else if (objs->is_chunked()) { + get_object->procedure( + std::format("const auto chunk_index = index / {};", + sdk::FUObjectArray::OBJECTS_PER_CHUNK + ) + "\n" + + std::format("const auto chunk_offset = index % {};", + sdk::FUObjectArray::OBJECTS_PER_CHUNK + ) + "\n" + + std::format("const auto chunk = *(void**)((uintptr_t)get_objects_ptr() + (chunk_index * sizeof(void*)));") + "\n" + + std::format("if (chunk == nullptr) {{ return nullptr; }}") + "\n" + + std::format("return (FUObjectItem*)((uintptr_t)chunk + (chunk_offset * {}));", + objs->get_item_distance() + ) + ); + } else { + get_object->procedure( + std::format("return (FUObjectItem*)((uintptr_t)get_objects_ptr() + (index * {}));", + objs->get_item_distance() + ) + ); + } + + // ::get_objects_ptr + auto get_objects_ptr = uobject_array->function("get_objects_ptr"); + get_objects_ptr->returns(g->type("void")->ptr()); + + if (objs->is_inlined()) { + get_objects_ptr->procedure( + std::format("return (void*)((uintptr_t)this + {});", + sdk::FUObjectArray::get_objects_offset() + (sdk::FUObjectArray::MAX_INLINED_CHUNKS * sizeof(void*)) + ) + ); + } else { + get_objects_ptr->procedure( + std::format("return *(void**)((uintptr_t)this + {});", + sdk::FUObjectArray::get_objects_offset() + ) + ); + } + + m_sdk->include("Windows.h"); +} + +void SDKDumper::populate_sdk() { + const auto structs = detail::get_all_structs(); + + for (const auto ustruct : structs) { + get_or_generate_struct(ustruct); + } +} + +void SDKDumper::write_sdk() { + const auto persistent_dir = Framework::get_persistent_dir("sdkdump"); + std::filesystem::create_directories(persistent_dir); + + for (const auto& extra_class : m_extra_classes) { + const auto header_path = persistent_dir / (extra_class.name + ".hpp"); + std::ofstream header{header_path}; + + header << extra_class.header; + + if (extra_class.source.has_value()) { + const auto source_path = persistent_dir / (extra_class.name + ".cpp"); + std::ofstream source{source_path}; + + source << extra_class.source.value(); + } + } + + m_sdk->generate(persistent_dir); +} + +sdkgenny::Struct* SDKDumper::get_or_generate_struct(sdk::UStruct* ustruct) { + auto g = m_sdk->global_ns(); + auto ns = get_or_generate_namespace_chain(ustruct); + + if (ns == nullptr) { + return nullptr; + } + + const auto struct_name = utility::narrow(ustruct->get_fname().to_string()); + + if (auto existing = ns->find(struct_name); existing != nullptr) { + return existing; + } + + auto s = ns->struct_(utility::narrow(ustruct->get_fname().to_string())); + + generate_inheritance(s, ustruct); + generate_properties(s, ustruct); + generate_functions(s, ustruct); + + return s; +} + +sdkgenny::Namespace* SDKDumper::get_or_generate_namespace_chain(sdk::UStruct* ustruct) { + auto g = m_sdk->global_ns(); + + std::vector outers{}; + + for (auto outer = ustruct->get_outer(); outer != nullptr; outer = outer->get_outer()) { + outers.push_back(outer); + } + + sdkgenny::Namespace* current = g; + + static auto ustruct_c = sdk::UStruct::static_class(); + + for (auto it = outers.rbegin(); it != outers.rend(); ++it) { + const auto outer = *it; + const auto name = utility::narrow(outer->get_fname().to_string()); + + if (outer->is_a(sdk::UStruct::static_class())) { + // uh... dont know how to handle this... yet + } else { + if (auto existing = current->find(name); existing != nullptr) { + current = existing; + } else { + current = current->namespace_(name); + } + } + } + + return current; +} + +void SDKDumper::generate_inheritance(sdkgenny::Struct* s, sdk::UStruct* ustruct) { + auto super = ustruct->get_super_struct(); + + if (super != nullptr && super != ustruct) { + s->parent(get_or_generate_struct(super)); // get_or_generate_struct will generate the inheritance chain for us. + } +} + +void SDKDumper::generate_properties(sdkgenny::Struct* s, sdk::UStruct* ustruct) { + for (auto prop = ustruct->get_child_properties(); prop != nullptr; prop = prop->get_next()) { + auto c = prop->get_class(); + + if (c == nullptr) { + continue; + } + + auto c_name = utility::narrow(c->get_name().to_string()); + + // Janky way to check if it's a property + if (!c_name.contains("Property")) { + continue; + } + + generate_property(s, (sdk::FProperty*)prop); + } +} + +sdkgenny::Variable* SDKDumper::generate_property(sdkgenny::Struct* s, sdk::FProperty* fprop) { + auto g = m_sdk->global_ns(); + const auto c = fprop->get_class(); + const auto c_name = utility::narrow(c->get_name().to_string()); + const auto prop_name = utility::narrow(fprop->get_field_name().to_string()); + + auto getter = s->function("get_" + prop_name); + + auto make_primitive_getter = [&](const std::string_view type_name) { + getter->returns(g->type(type_name)->ref()); + getter->procedure( + std::format("return *({}*)((uintptr_t)this + 0x{:x});", type_name, fprop->get_offset()) + ); + }; + + switch (utility::hash(c_name)) { + case "BoolProperty"_fnv: + { + const auto boolprop = (sdk::FBoolProperty*)fprop; + // Create a getter and setter for the property + const auto byte_offset = boolprop->get_byte_offset(); + const auto byte_mask = boolprop->get_byte_mask(); + + getter->returns(g->type("bool")); + getter->procedure( + std::format("return (*(uint8_t*)((uintptr_t)this + 0x{:x} + {})) & {} != 0;", fprop->get_offset(), byte_offset, byte_mask) + ); + + auto setter = s->function("set_" + prop_name); + setter->returns(g->type("void")); + setter->param("value")->type(g->type("bool")); + setter->procedure( + std::format("const auto cur_value = *(uint8_t*)((uintptr_t)this + 0x{:x} + {});", fprop->get_offset(), byte_offset) + "\n" + + std::format("*(uint8_t*)((uintptr_t)this + 0x{:x} + {}) = (cur_value & ~{}) | (value ? {} : 0);", fprop->get_offset(), byte_offset, byte_mask, byte_mask) + ); + + + break; + } + case "FloatProperty"_fnv: + { + make_primitive_getter("float"); + break; + } + case "DoubleProperty"_fnv: + { + make_primitive_getter("double"); + break; + } + case "IntProperty"_fnv: + { + make_primitive_getter("int32_t"); + break; + } + case "ObjectProperty"_fnv: + { + const auto objprop = (sdk::FObjectProperty*)fprop; + const auto objtype = objprop->get_property_class(); + + if (objtype != nullptr) { + auto obj_sdkgenny = get_or_generate_struct(objtype); + auto obj_ns = get_or_generate_namespace_chain(objtype); + + std::string ns_chain{obj_ns != nullptr ? obj_ns->usable_name() : ""}; + + if (obj_ns != nullptr) { + for (auto ns = obj_ns->owner(); ns != nullptr; ns = ns->owner()) { + if (ns->usable_name().length() > 0) { + ns_chain = ns->usable_name() + "::" + ns_chain; + } + } + } + + getter->returns(obj_sdkgenny->ptr()->ref()); + getter->procedure( + std::format("return *({}**)((uintptr_t)this + 0x{:x});", ns_chain + "::" + obj_sdkgenny->usable_name(), fprop->get_offset()) + ); + } else { + getter->returns(g->type("void")->ptr()->ref()); + getter->procedure( + std::format("return *(void**)((uintptr_t)this + 0x{:x});", fprop->get_offset()) + ); + } + + break; + } + default: + { + // Create just a getter for the property that returns a void* directly to the property inside the struct + getter->returns(g->type("void")->ptr()); + getter->procedure( + std::format("return (void*)((uintptr_t)this + 0x{:x});", fprop->get_offset()) + ); + + break; + } + }; + + return nullptr; +} + +void SDKDumper::generate_functions(sdkgenny::Struct* s, sdk::UStruct* ustruct) { + auto g = m_sdk->global_ns(); + + // obviously first thing to do is generate the static_class! + auto sc_func = s->static_function("static_class"); + sc_func->returns(g->class_("UClass")->ptr()); + sc_func->procedure("return nullptr;"); // todo +} + diff --git a/src/mods/uobjecthook/SDKDumper.hpp b/src/mods/uobjecthook/SDKDumper.hpp new file mode 100644 index 00000000..8384e860 --- /dev/null +++ b/src/mods/uobjecthook/SDKDumper.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include + +#include + +namespace sdk { +class UStruct; +class UClass; +class FProperty; +} + +class SDKDumper { +public: + virtual ~SDKDumper() = default; + +public: + static void dump(); + +private: + void dump_internal(); + +private: + void initialize_sdk(); + void initialize_boilerplate_classes(); + void initialize_tarray(); + void initialize_uobject(); + void initialize_uobject_array(); + void populate_sdk(); + void write_sdk(); + +private: + sdkgenny::Struct* get_or_generate_struct(sdk::UStruct* ustruct); + sdkgenny::Namespace* get_or_generate_namespace_chain(sdk::UStruct* uclass); + void generate_inheritance(sdkgenny::Struct* s, sdk::UStruct* ustruct); + void generate_properties(sdkgenny::Struct* s, sdk::UStruct* ustruct); + sdkgenny::Variable* generate_property(sdkgenny::Struct* s, sdk::FProperty* fprop); + void generate_functions(sdkgenny::Struct* s, sdk::UStruct* ustruct); + +private: + std::unique_ptr m_sdk{}; + + struct ExtraSdkClass { + std::string name{}; + std::string header{}; + std::optional source{}; + }; + + std::vector m_extra_classes{}; +}; \ No newline at end of file