diff --git a/CMakeLists.txt b/CMakeLists.txt index db98a962..8e0df61f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -675,6 +675,7 @@ list(APPEND uevr_SOURCES "src/mods/pluginloader/FFakeStereoRenderingFunctions.cpp" "src/mods/pluginloader/FRHITexture2DFunctions.cpp" "src/mods/pluginloader/FRenderTargetPoolHook.cpp" + "src/mods/pluginloader/FUObjectArrayFunctions.cpp" "src/mods/uobjecthook/SDKDumper.cpp" "src/mods/vr/Bindings.cpp" "src/mods/vr/CVarManager.cpp" @@ -713,6 +714,7 @@ list(APPEND uevr_SOURCES "src/mods/pluginloader/FFakeStereoRenderingFunctions.hpp" "src/mods/pluginloader/FRHITexture2DFunctions.hpp" "src/mods/pluginloader/FRenderTargetPoolHook.hpp" + "src/mods/pluginloader/FUObjectArrayFunctions.hpp" "src/mods/uobjecthook/SDKDumper.hpp" "src/mods/vr/CVarManager.hpp" "src/mods/vr/D3D11Component.hpp" diff --git a/examples/example_plugin/Plugin.cpp b/examples/example_plugin/Plugin.cpp index f84ce2dc..79973db2 100644 --- a/examples/example_plugin/Plugin.cpp +++ b/examples/example_plugin/Plugin.cpp @@ -170,179 +170,224 @@ class ExamplePlugin : public uevr::Plugin { return !ImGui::GetIO().WantCaptureMouse && !ImGui::GetIO().WantCaptureKeyboard; } - void on_pre_engine_tick(API::UGameEngine* engine, float delta) override { - PLUGIN_LOG_ONCE("Pre Engine Tick: %f", delta); + void print_all_objects() { + API::get()->log_info("Printing all objects..."); - static bool once = true; + API::get()->log_info("Chunked: %i", API::FUObjectArray::is_chunked()); + API::get()->log_info("Inlined: %i", API::FUObjectArray::is_inlined()); + API::get()->log_info("Objects offset: %i", API::FUObjectArray::get_objects_offset()); + API::get()->log_info("Item distance: %i", API::FUObjectArray::get_item_distance()); + API::get()->log_info("Object count: %i", API::FUObjectArray::get()->get_object_count()); - // Unit tests for the API basically. - if (once) { - once = false; + const auto objects = API::FUObjectArray::get(); - API::get()->log_info("Running once on pre engine tick"); - API::get()->execute_command(L"stat fps"); + if (objects == nullptr) { + API::get()->log_error("Failed to get FUObjectArray"); + return; + } - API::FName test_name{L"Left"}; - std::string name_narrow{std::wstring_convert>{}.to_bytes(test_name.to_string())}; - API::get()->log_info("Test FName: %s", name_narrow.c_str()); + for (int32_t i = 0; i < objects->get_object_count(); ++i) { + const auto object = objects->get_object(i); - // Test attaching skeletal mesh components with UObjectHook. - struct { - API::UClass* c; - API::TArray return_value{}; - } component_params; + if (object == nullptr) { + continue; + } - component_params.c = API::get()->find_uobject(L"Class /Script/Engine.SkeletalMeshComponent"); - const auto pawn = API::get()->get_local_pawn(0); + const auto name = object->get_full_name(); - if (component_params.c != nullptr && pawn != nullptr) { - // either or. - pawn->call_function(L"K2_GetComponentsByClass", &component_params); - pawn->call_function(L"GetComponentsByClass", &component_params); + if (name.empty()) { + continue; + } - if (component_params.return_value.empty()) { - API::get()->log_error("Failed to find any SkeletalMeshComponents"); - } + std::string name_narrow{std::wstring_convert>{}.to_bytes(name)}; - for (auto mesh : component_params.return_value) { - auto state = API::UObjectHook::get_or_add_motion_controller_state(mesh); - } - } else { - API::get()->log_error("Failed to find SkeletalMeshComponent class or local pawn"); + API::get()->log_info(" [%d]: %s", i, name_narrow.c_str()); + } + } + + // Test attaching skeletal mesh components with UObjectHook. + void test_mesh_attachment() { + struct { + API::UClass* c; + API::TArray return_value{}; + } component_params; + + component_params.c = API::get()->find_uobject(L"Class /Script/Engine.SkeletalMeshComponent"); + const auto pawn = API::get()->get_local_pawn(0); + + if (component_params.c != nullptr && pawn != nullptr) { + // either or. + pawn->call_function(L"K2_GetComponentsByClass", &component_params); + pawn->call_function(L"GetComponentsByClass", &component_params); + + if (component_params.return_value.empty()) { + API::get()->log_error("Failed to find any SkeletalMeshComponents"); + } + + for (auto mesh : component_params.return_value) { + auto state = API::UObjectHook::get_or_add_motion_controller_state(mesh); } + } else { + API::get()->log_error("Failed to find SkeletalMeshComponent class or local pawn"); + } + } + + void test_console_manager() { + const auto console_manager = API::get()->get_console_manager(); + + if (console_manager != nullptr) { + API::get()->log_info("Console manager @ 0x%p", console_manager); + const auto& objects = console_manager->get_console_objects(); - // Iterate over all console variables. - const auto console_manager = API::get()->get_console_manager(); - - if (console_manager != nullptr) { - API::get()->log_info("Console manager @ 0x%p", console_manager); - const auto& objects = console_manager->get_console_objects(); - - for (const auto& object : objects) { - if (object.key != nullptr) { - // convert from wide to narrow string (we do not have utility::narrow in this context). - std::string key_narrow{std::wstring_convert>{}.to_bytes(object.key)}; - if (object.value != nullptr) { - const auto command = object.value->as_command(); - - if (command != nullptr) { - API::get()->log_info(" Console COMMAND: %s @ 0x%p", key_narrow.c_str(), object.value); - } else { - API::get()->log_info(" Console VARIABLE: %s @ 0x%p", key_narrow.c_str(), object.value); - } + for (const auto& object : objects) { + if (object.key != nullptr) { + // convert from wide to narrow string (we do not have utility::narrow in this context). + std::string key_narrow{std::wstring_convert>{}.to_bytes(object.key)}; + if (object.value != nullptr) { + const auto command = object.value->as_command(); + + if (command != nullptr) { + API::get()->log_info(" Console COMMAND: %s @ 0x%p", key_narrow.c_str(), object.value); + } else { + API::get()->log_info(" Console VARIABLE: %s @ 0x%p", key_narrow.c_str(), object.value); } } } + } - auto cvar = console_manager->find_variable(L"r.Color.Min"); + auto cvar = console_manager->find_variable(L"r.Color.Min"); - if (cvar != nullptr) { - API::get()->log_info("Found r.Color.Min @ 0x%p (%f)", cvar, cvar->get_float()); - } else { - API::get()->log_error("Failed to find r.Color.Min"); - } + if (cvar != nullptr) { + API::get()->log_info("Found r.Color.Min @ 0x%p (%f)", cvar, cvar->get_float()); + } else { + API::get()->log_error("Failed to find r.Color.Min"); + } - auto cvar2 = console_manager->find_variable(L"r.Upscale.Quality"); + auto cvar2 = console_manager->find_variable(L"r.Upscale.Quality"); - if (cvar2 != nullptr) { - API::get()->log_info("Found r.Upscale.Quality @ 0x%p (%d)", cvar2, cvar2->get_int()); - cvar2->set(cvar2->get_int() + 1); - } else { - API::get()->log_error("Failed to find r.Upscale.Quality"); - } + if (cvar2 != nullptr) { + API::get()->log_info("Found r.Upscale.Quality @ 0x%p (%d)", cvar2, cvar2->get_int()); + cvar2->set(cvar2->get_int() + 1); } else { - API::get()->log_error("Failed to find console manager"); + API::get()->log_error("Failed to find r.Upscale.Quality"); } + } else { + API::get()->log_error("Failed to find console manager"); + } + } - // Log the UEngine name. - const auto uengine_name = engine->get_full_name(); - - // Convert from wide to narrow string (we do not have utility::narrow in this context). - std::string uengine_name_narrow{std::wstring_convert>{}.to_bytes(uengine_name)}; + void test_engine(API::UGameEngine* engine) { + // Log the UEngine name. + const auto uengine_name = engine->get_full_name(); - API::get()->log_info("Engine name: %s", uengine_name_narrow.c_str()); + // Convert from wide to narrow string (we do not have utility::narrow in this context). + std::string uengine_name_narrow{std::wstring_convert>{}.to_bytes(uengine_name)}; - // Go through all of engine's fields and log their names. - const auto engine_class_ours = (API::UStruct*)engine->get_class(); - for (auto super = engine_class_ours; super != nullptr; super = super->get_super()) { - for (auto field = super->get_child_properties(); field != nullptr; field = field->get_next()) { - const auto field_fname = field->get_fname(); - const auto field_name = field_fname->to_string(); - const auto field_class = field->get_class(); + API::get()->log_info("Engine name: %s", uengine_name_narrow.c_str()); - std::wstring prepend{}; + // Go through all of engine's fields and log their names. + const auto engine_class_ours = (API::UStruct*)engine->get_class(); + for (auto super = engine_class_ours; super != nullptr; super = super->get_super()) { + for (auto field = super->get_child_properties(); field != nullptr; field = field->get_next()) { + const auto field_fname = field->get_fname(); + const auto field_name = field_fname->to_string(); + const auto field_class = field->get_class(); - if (field_class != nullptr) { - const auto field_class_fname = field_class->get_fname(); - const auto field_class_name = field_class_fname->to_string(); + std::wstring prepend{}; - prepend = field_class_name + L" "; - } + if (field_class != nullptr) { + const auto field_class_fname = field_class->get_fname(); + const auto field_class_name = field_class_fname->to_string(); - // Convert from wide to narrow string (we do not have utility::narrow in this context). - std::string field_name_narrow{std::wstring_convert>{}.to_bytes(prepend + field_name)}; - API::get()->log_info(" Field name: %s", field_name_narrow.c_str()); + prepend = field_class_name + L" "; } - } - // Check if we can find the GameInstance and call is_a() on it. - const auto game_instance = engine->get_property(L"GameInstance"); + // Convert from wide to narrow string (we do not have utility::narrow in this context). + std::string field_name_narrow{std::wstring_convert>{}.to_bytes(prepend + field_name)}; + API::get()->log_info(" Field name: %s", field_name_narrow.c_str()); + } + } - if (game_instance != nullptr) { - const auto game_instance_class = API::get()->find_uobject(L"Class /Script/Engine.GameInstance"); + // Check if we can find the GameInstance and call is_a() on it. + const auto game_instance = engine->get_property(L"GameInstance"); - if (game_instance->is_a(game_instance_class)) { - const auto& local_players = game_instance->get_property>(L"LocalPlayers"); + if (game_instance != nullptr) { + const auto game_instance_class = API::get()->find_uobject(L"Class /Script/Engine.GameInstance"); - if (local_players.count > 0 && local_players.data != nullptr) { - const auto local_player = local_players.data[0]; + if (game_instance->is_a(game_instance_class)) { + const auto& local_players = game_instance->get_property>(L"LocalPlayers"); - - } else { - API::get()->log_error("Failed to find LocalPlayers"); - } + if (local_players.count > 0 && local_players.data != nullptr) { + const auto local_player = local_players.data[0]; - API::get()->log_info("GameInstance is a UGameInstance"); + } else { - API::get()->log_error("GameInstance is not a UGameInstance"); + API::get()->log_error("Failed to find LocalPlayers"); } + + API::get()->log_info("GameInstance is a UGameInstance"); } else { - API::get()->log_error("Failed to find GameInstance"); + API::get()->log_error("GameInstance is not a UGameInstance"); } + } else { + API::get()->log_error("Failed to find GameInstance"); + } - // Find the Engine object and compare it to the one we have. - const auto engine_class = API::get()->find_uobject(L"Class /Script/Engine.GameEngine"); - if (engine_class != nullptr) { - // Round 1, check if we can find it via get_first_object_by_class. - const auto engine_searched = engine_class->get_first_object_matching(false); + // Find the Engine object and compare it to the one we have. + const auto engine_class = API::get()->find_uobject(L"Class /Script/Engine.GameEngine"); + if (engine_class != nullptr) { + // Round 1, check if we can find it via get_first_object_by_class. + const auto engine_searched = engine_class->get_first_object_matching(false); - if (engine_searched != nullptr) { - if (engine_searched == engine) { - API::get()->log_info("Found Engine object @ 0x%p", engine_searched); - } else { - API::get()->log_error("Found Engine object @ 0x%p, but it's not the same as the one we have", engine_searched); - } + if (engine_searched != nullptr) { + if (engine_searched == engine) { + API::get()->log_info("Found Engine object @ 0x%p", engine_searched); } else { - API::get()->log_error("Failed to find Engine object"); + API::get()->log_error("Found Engine object @ 0x%p, but it's not the same as the one we have", engine_searched); } + } else { + API::get()->log_error("Failed to find Engine object"); + } - // Round 2, check if we can find it via get_objects_by_class. - const auto objects = engine_class->get_objects_matching(false); + // Round 2, check if we can find it via get_objects_by_class. + const auto objects = engine_class->get_objects_matching(false); - if (!objects.empty()) { - for (const auto& obj : objects) { - if (obj == engine) { - API::get()->log_info("Found Engine object @ 0x%p", obj); - } else { - API::get()->log_info("Found unrelated Engine object @ 0x%p", obj); - } + if (!objects.empty()) { + for (const auto& obj : objects) { + if (obj == engine) { + API::get()->log_info("Found Engine object @ 0x%p", obj); + } else { + API::get()->log_info("Found unrelated Engine object @ 0x%p", obj); } - } else { - API::get()->log_error("Failed to find Engine objects"); } } else { - API::get()->log_error("Failed to find Engine class"); + API::get()->log_error("Failed to find Engine objects"); } + } else { + API::get()->log_error("Failed to find Engine class"); + } + } + + void on_pre_engine_tick(API::UGameEngine* engine, float delta) override { + PLUGIN_LOG_ONCE("Pre Engine Tick: %f", delta); + + static bool once = true; + + // Unit tests for the API basically. + if (once) { + once = false; + + API::get()->log_info("Running once on pre engine tick"); + API::get()->execute_command(L"stat fps"); + + API::FName test_name{L"Left"}; + std::string name_narrow{std::wstring_convert>{}.to_bytes(test_name.to_string())}; + API::get()->log_info("Test FName: %s", name_narrow.c_str()); + + print_all_objects(); + test_mesh_attachment(); + test_console_manager(); + test_engine(engine); } if (m_initialized) { diff --git a/include/uevr/API.h b/include/uevr/API.h index 9b743d09..440c80e9 100644 --- a/include/uevr/API.h +++ b/include/uevr/API.h @@ -36,7 +36,7 @@ SOFTWARE. #define UEVR_OUT #define UEVR_PLUGIN_VERSION_MAJOR 2 -#define UEVR_PLUGIN_VERSION_MINOR 15 +#define UEVR_PLUGIN_VERSION_MINOR 16 #define UEVR_PLUGIN_VERSION_PATCH 0 #define UEVR_RENDERER_D3D11 0 @@ -257,8 +257,20 @@ typedef struct { void (*command_execute)(UEVR_IConsoleCommandHandle cmd, const wchar_t* args); } UEVR_ConsoleFunctions; +DECLARE_UEVR_HANDLE(UEVR_FUObjectItemHandle); + typedef struct { UEVR_UObjectHandle (*find_uobject)(const wchar_t* name); + + bool (*is_chunked)(); + bool (*is_inlined)(); + unsigned int (*get_objects_offset)(); + unsigned int (*get_item_distance)(); + + int (*get_object_count)(UEVR_UObjectArrayHandle array); + void* (*get_objects_ptr)(UEVR_UObjectArrayHandle array); + UEVR_UObjectHandle (*get_object)(UEVR_UObjectArrayHandle array, int index); + UEVR_FUObjectItemHandle (*get_item)(UEVR_UObjectArrayHandle array, int index); } UEVR_UObjectArrayFunctions; typedef struct { diff --git a/include/uevr/API.hpp b/include/uevr/API.hpp index 26c1cb1c..f11b4868 100644 --- a/include/uevr/API.hpp +++ b/include/uevr/API.hpp @@ -258,7 +258,7 @@ class API { return s_functions; } }; - + struct UObject { inline UEVR_UObjectHandle to_handle() { return (UEVR_UObjectHandle)this; } inline UEVR_UObjectHandle to_handle() const { return (UEVR_UObjectHandle)this; } @@ -694,9 +694,71 @@ class API { }; struct FUObjectArray { + inline UEVR_UObjectArrayHandle to_handle() { return (UEVR_UObjectArrayHandle)this; } + inline UEVR_UObjectArrayHandle to_handle() const { return (UEVR_UObjectArrayHandle)this; } + static FUObjectArray* get() { return API::get()->get_uobject_array(); } + + static bool is_chunked() { + static const auto fn = initialize()->is_chunked; + return fn(); + } + + static bool is_inlined() { + static const auto fn = initialize()->is_inlined; + return fn(); + } + + static size_t get_objects_offset() { + static const auto fn = initialize()->get_objects_offset; + return (size_t)fn(); + } + + static size_t get_item_distance() { + static const auto fn = initialize()->get_item_distance; + return (size_t)fn(); + } + + int32_t get_object_count() const { + static const auto fn = initialize()->get_object_count; + return fn(to_handle()); + } + + void* get_objects_ptr() const { + static const auto fn = initialize()->get_objects_ptr; + return fn(to_handle()); + } + + UObject* get_object(int32_t index) const { + static const auto fn = initialize()->get_object; + return (UObject*)fn(to_handle(), index); + } + + // Generally the same structure most of the time, not too much to worry about + // Not that this would generally be used raw - instead prefer get_object + struct FUObjectItem { + API::UObject* object; + int32_t flags; + int32_t cluster_index; + int32_t serial_number; + }; + + FUObjectItem* get_item(int32_t index) const { + static const auto fn = initialize()->get_item; + return (FUObjectItem*)fn(to_handle(), index); + } + + private: + static inline const UEVR_UObjectArrayFunctions* s_functions{nullptr}; + static inline const UEVR_UObjectArrayFunctions* initialize() { + if (s_functions == nullptr) { + s_functions = API::get()->sdk()->uobject_array; + } + + return s_functions; + } }; // One of the very few non-opaque structs diff --git a/src/CommitHash.hpp b/src/CommitHash.hpp index 60df53c0..7fc70e92 100644 --- a/src/CommitHash.hpp +++ b/src/CommitHash.hpp @@ -1,4 +1,4 @@ #pragma once -#define UEVR_COMMIT_HASH "3fe2879d17b4ea3cda92fb25b22e48ab95ffd47a" -#define UEVR_BUILD_DATE "03.03.2024" +#define UEVR_COMMIT_HASH "0ffc79987629f6bd09f564ec9fe61c1a77ef7efc" +#define UEVR_BUILD_DATE "04.03.2024" #define UEVR_BUILD_TIME "00:00" diff --git a/src/mods/PluginLoader.cpp b/src/mods/PluginLoader.cpp index 5fb03807..202af65d 100644 --- a/src/mods/PluginLoader.cpp +++ b/src/mods/PluginLoader.cpp @@ -25,6 +25,7 @@ #include "pluginloader/FFakeStereoRenderingFunctions.hpp" #include "pluginloader/FRenderTargetPoolHook.hpp" #include "pluginloader/FRHITexture2DFunctions.hpp" +#include "pluginloader/FUObjectArrayFunctions.hpp" #include "UObjectHook.hpp" #include "VR.hpp" @@ -368,13 +369,6 @@ UEVR_UObjectFunctions g_uobject_functions { }, }; -UEVR_UObjectArrayFunctions g_uobject_array_functions { - // find_uobject - [](const wchar_t* name) { - return (UEVR_UObjectHandle)sdk::find_uobject(name); - }, -}; - #define FFIELD(x) ((sdk::FField*)x) UEVR_FFieldFunctions g_ffield_functions { @@ -769,7 +763,7 @@ UEVR_SDKData g_sdk_data { &g_sdk_functions, &g_sdk_callbacks, &g_uobject_functions, - &g_uobject_array_functions, + &uevr::fuobjectarray::functions, &g_ffield_functions, &g_fproperty_functions, &g_ustruct_functions, diff --git a/src/mods/pluginloader/FFakeStereoRenderingFunctions.cpp b/src/mods/pluginloader/FFakeStereoRenderingFunctions.cpp index 5828c7e5..498fa51d 100644 --- a/src/mods/pluginloader/FFakeStereoRenderingFunctions.cpp +++ b/src/mods/pluginloader/FFakeStereoRenderingFunctions.cpp @@ -29,7 +29,7 @@ UEVR_FRHITexture2DHandle stereo_hook::get_ui_render_target() { } UEVR_FFakeStereoRenderingHookFunctions stereo_hook::functions { - &stereo_hook::get_scene_render_target, - &stereo_hook::get_ui_render_target + .get_scene_render_target = &stereo_hook::get_scene_render_target, + .get_ui_render_target = &stereo_hook::get_ui_render_target }; } \ No newline at end of file diff --git a/src/mods/pluginloader/FRHITexture2DFunctions.cpp b/src/mods/pluginloader/FRHITexture2DFunctions.cpp index 13093285..fa8e7bf2 100644 --- a/src/mods/pluginloader/FRHITexture2DFunctions.cpp +++ b/src/mods/pluginloader/FRHITexture2DFunctions.cpp @@ -15,7 +15,7 @@ void* get_native_resource(UEVR_FRHITexture2DHandle handle) { } UEVR_FRHITexture2DFunctions functions { - &uevr::frhitexture2d::get_native_resource + .get_native_resource = &uevr::frhitexture2d::get_native_resource }; } } \ No newline at end of file diff --git a/src/mods/pluginloader/FRenderTargetPoolHook.cpp b/src/mods/pluginloader/FRenderTargetPoolHook.cpp index f1cc6a24..3e865a17 100644 --- a/src/mods/pluginloader/FRenderTargetPoolHook.cpp +++ b/src/mods/pluginloader/FRenderTargetPoolHook.cpp @@ -19,7 +19,7 @@ UEVR_IPooledRenderTargetHandle render_target_pool_hook::get_render_target(const } UEVR_FRenderTargetPoolHookFunctions render_target_pool_hook::functions { - &render_target_pool_hook::activate, - &render_target_pool_hook::get_render_target + .activate = &render_target_pool_hook::activate, + .get_render_target = &render_target_pool_hook::get_render_target }; } \ No newline at end of file diff --git a/src/mods/pluginloader/FUObjectArrayFunctions.cpp b/src/mods/pluginloader/FUObjectArrayFunctions.cpp new file mode 100644 index 00000000..af26c710 --- /dev/null +++ b/src/mods/pluginloader/FUObjectArrayFunctions.cpp @@ -0,0 +1,63 @@ +#include + +#include "FUObjectArrayFunctions.hpp" + + +namespace uevr { +namespace fuobjectarray { +UEVR_UObjectHandle find_uobject(const wchar_t* name) { + return (UEVR_UObjectHandle)sdk::find_uobject(name); +} + +bool is_chunked() { + return sdk::FUObjectArray::is_chunked(); +} + +bool is_inlined() { + return sdk::FUObjectArray::is_inlined(); +} + +unsigned int get_objects_offset() { + return sdk::FUObjectArray::get_objects_offset(); +} + +unsigned int get_item_distance() { + return sdk::FUObjectArray::get_item_distance(); +} + +int get_object_count(UEVR_UObjectArrayHandle array) { + return ((sdk::FUObjectArray*)array)->get_object_count(); +} + +void* get_objects_ptr(UEVR_UObjectArrayHandle array) { + return ((sdk::FUObjectArray*)array)->get_objects_ptr(); +} + +UEVR_UObjectHandle get_object(UEVR_UObjectArrayHandle array, int index) { + auto item = ((sdk::FUObjectArray*)array)->get_object(index); + + if (item == nullptr) { + return nullptr; + } + + return (UEVR_UObjectHandle)item->object; +} + +// messed up naming, I know +UEVR_FUObjectItemHandle get_item(UEVR_UObjectArrayHandle array, int index) { + return (UEVR_FUObjectItemHandle)((sdk::FUObjectArray*)array)->get_object(index); +} + +UEVR_UObjectArrayFunctions functions { + .find_uobject = uevr::fuobjectarray::find_uobject, + .is_chunked = uevr::fuobjectarray::is_chunked, + .is_inlined = uevr::fuobjectarray::is_inlined, + .get_objects_offset = uevr::fuobjectarray::get_objects_offset, + .get_item_distance = uevr::fuobjectarray::get_item_distance, + .get_object_count = uevr::fuobjectarray::get_object_count, + .get_objects_ptr = uevr::fuobjectarray::get_objects_ptr, + .get_object = uevr::fuobjectarray::get_object, + .get_item = uevr::fuobjectarray::get_item +}; +} +} \ No newline at end of file diff --git a/src/mods/pluginloader/FUObjectArrayFunctions.hpp b/src/mods/pluginloader/FUObjectArrayFunctions.hpp new file mode 100644 index 00000000..362d9592 --- /dev/null +++ b/src/mods/pluginloader/FUObjectArrayFunctions.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "uevr/API.h" + +namespace uevr { +namespace fuobjectarray { +UEVR_UObjectHandle find_uobject(const wchar_t* name); +bool is_chunked(); +bool is_inlined(); +unsigned int get_objects_offset(); +unsigned int get_item_distance(); +int get_object_count(UEVR_UObjectArrayHandle array); +void* get_objects_ptr(UEVR_UObjectArrayHandle array); +UEVR_UObjectHandle get_object(UEVR_UObjectArrayHandle array, int index); +UEVR_FUObjectItemHandle get_item(UEVR_UObjectArrayHandle array, int index); + +extern UEVR_UObjectArrayFunctions functions; +} +} \ No newline at end of file