From 2347677877a6aaa62b481baa0663d4ff79f42f9d Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 5 Mar 2024 16:44:45 -0800 Subject: [PATCH 01/37] Lua: Add LuaLoader, Lua API improvements --- CMakeLists.txt | 56 ++- cmake.toml | 23 +- lua-api/Main.cpp | 16 +- lua-api/{ => lib/include}/ScriptContext.hpp | 47 ++- lua-api/lib/include/ScriptState.hpp | 69 ++++ lua-api/{ => lib/src}/ScriptContext.cpp | 380 +++++++++++++------- lua-api/lib/src/ScriptState.cpp | 187 ++++++++++ src/Mods.cpp | 2 + src/mods/LuaLoader.cpp | 269 ++++++++++++++ src/mods/LuaLoader.hpp | 121 +++++++ src/mods/PluginLoader.cpp | 1 - src/mods/PluginLoader.hpp | 2 + 12 files changed, 1025 insertions(+), 148 deletions(-) rename lua-api/{ => lib/include}/ScriptContext.hpp (73%) create mode 100644 lua-api/lib/include/ScriptState.hpp rename lua-api/{ => lib/src}/ScriptContext.cpp (59%) create mode 100644 lua-api/lib/src/ScriptState.cpp create mode 100644 src/mods/LuaLoader.cpp create mode 100644 src/mods/LuaLoader.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index aa4a0407..b10c47dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -590,14 +590,60 @@ target_include_directories(sol2 INTERFACE unset(CMKR_TARGET) unset(CMKR_SOURCES) +# Target luavrlib +set(CMKR_TARGET luavrlib) +set(luavrlib_SOURCES "") + +list(APPEND luavrlib_SOURCES + "lua-api/lib/src/ScriptContext.cpp" + "lua-api/lib/src/ScriptState.cpp" + "lua-api/lib/include/ScriptContext.hpp" + "lua-api/lib/include/ScriptState.hpp" +) + +list(APPEND luavrlib_SOURCES + cmake.toml +) + +set(CMKR_SOURCES ${luavrlib_SOURCES}) +add_library(luavrlib STATIC) + +if(luavrlib_SOURCES) + target_sources(luavrlib PRIVATE ${luavrlib_SOURCES}) +endif() + +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${luavrlib_SOURCES}) + +target_compile_features(luavrlib PUBLIC + cxx_std_23 +) + +target_compile_options(luavrlib PUBLIC + "/bigobj" + "/EHa" + "/MP" +) + +target_include_directories(luavrlib PUBLIC + "include/" + "lua-api/lib/include" +) + +target_link_libraries(luavrlib PUBLIC + lua + sol2 + kananlib +) + +unset(CMKR_TARGET) +unset(CMKR_SOURCES) + # Target LuaVR set(CMKR_TARGET LuaVR) set(LuaVR_SOURCES "") list(APPEND LuaVR_SOURCES "lua-api/Main.cpp" - "lua-api/ScriptContext.cpp" - "lua-api/ScriptContext.hpp" ) list(APPEND LuaVR_SOURCES @@ -628,9 +674,7 @@ target_include_directories(LuaVR PUBLIC ) target_link_libraries(LuaVR PUBLIC - lua - sol2 - kananlib + luavrlib ) set_target_properties(LuaVR PROPERTIES @@ -669,6 +713,7 @@ list(APPEND uevr_SOURCES "src/hooks/XInputHook.cpp" "src/mods/FrameworkConfig.cpp" "src/mods/ImGuiThemeHelpers.cpp" + "src/mods/LuaLoader.cpp" "src/mods/PluginLoader.cpp" "src/mods/UObjectHook.cpp" "src/mods/VR.cpp" @@ -788,6 +833,7 @@ target_link_libraries(uevr PUBLIC DirectXTK12 sdkgenny asmjit + luavrlib ) target_link_libraries(uevr PUBLIC diff --git a/cmake.toml b/cmake.toml index 1651452e..ddb64c11 100644 --- a/cmake.toml +++ b/cmake.toml @@ -192,19 +192,29 @@ include-directories = ["dependencies/lua/src"] type = "interface" include-directories = ["dependencies/sol2/single/single/include"] -[target.LuaVR] -type = "shared" +[target.luavrlib] +type = "static" compile-features = ["cxx_std_23"] compile-options = ["/bigobj", "/EHa", "/MP"] -include-directories = ["include/"] -sources = ["lua-api/**.cpp", "lua-api/**.c"] -headers = ["lua-api/**.hpp", "lua-api/**.h"] +include-directories = ["include/", "lua-api/lib/include"] +sources = ["lua-api/lib/**.cpp", "lua-api/lib/**.c"] +headers = ["lua-api/lib/**.hpp", "lua-api/lib/**.h"] link-libraries = [ "lua", "sol2", "kananlib" ] +[target.LuaVR] +type = "shared" +compile-features = ["cxx_std_23"] +compile-options = ["/bigobj", "/EHa", "/MP"] +include-directories = ["include/"] +sources = ["lua-api/Main.cpp"] +link-libraries = [ + "luavrlib" +] + [target.LuaVR.properties] RUNTIME_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" @@ -241,7 +251,8 @@ link-libraries = [ "DirectXTK", "DirectXTK12", "sdkgenny", - "asmjit" + "asmjit", + "luavrlib" ] [template.ue4template.properties] diff --git a/lua-api/Main.cpp b/lua-api/Main.cpp index 666c961f..47bfb941 100644 --- a/lua-api/Main.cpp +++ b/lua-api/Main.cpp @@ -1,24 +1,26 @@ // Lua API to expose UEVR functionality to Lua scripts via UE4SS #include -#include "ScriptContext.hpp" +#include "lib/include/ScriptContext.hpp" + +std::shared_ptr g_script_context{}; // Main exported function that takes in the lua_State* extern "C" __declspec(dllexport) int luaopen_LuaVR(lua_State* L) { luaL_checkversion(L); - ScriptContext::log("Initializing LuaVR..."); + uevr::ScriptContext::log("Initializing LuaVR..."); - auto script_context = ScriptContext::reinitialize(L); + g_script_context = uevr::ScriptContext::create(L); - if (!script_context->valid()) { - ScriptContext::log("LuaVR failed to initialize! Make sure to inject VR first!"); + if (!g_script_context->valid()) { + uevr::ScriptContext::log("LuaVR failed to initialize! Make sure to inject VR first!"); return 0; } - ScriptContext::log("LuaVR initialized!"); + uevr::ScriptContext::log("LuaVR initialized!"); - return script_context->setup_bindings(); + return g_script_context->setup_bindings(); } BOOL APIENTRY DllMain(HMODULE module, DWORD ul_reason_for_call, LPVOID reserved) { diff --git a/lua-api/ScriptContext.hpp b/lua-api/lib/include/ScriptContext.hpp similarity index 73% rename from lua-api/ScriptContext.hpp rename to lua-api/lib/include/ScriptContext.hpp index 2a48a100..16dbbe24 100644 --- a/lua-api/ScriptContext.hpp +++ b/lua-api/lib/include/ScriptContext.hpp @@ -8,10 +8,12 @@ #include -class ScriptContext { +namespace uevr { +class ScriptContext : public std::enable_shared_from_this { public: - static std::shared_ptr get(); - static std::shared_ptr reinitialize(lua_State* l, UEVR_PluginInitializeParam* param = nullptr); + static std::shared_ptr create(lua_State* l, UEVR_PluginInitializeParam* param = nullptr) { + return std::make_shared(l, param); + } ScriptContext(lua_State* l, UEVR_PluginInitializeParam* param = nullptr); @@ -54,6 +56,34 @@ class ScriptContext { } } + auto& get_mutex() { + return m_mtx; + } + + void script_reset() { + std::scoped_lock _{m_mtx}; + + for (auto& cb : m_on_script_reset_callbacks) { + handle_protected_result(cb()); + } + } + + void frame() { + std::scoped_lock _{m_mtx}; + + for (auto& cb : m_on_frame_callbacks) { + handle_protected_result(cb()); + } + } + + void draw_ui() { + std::scoped_lock _{m_mtx}; + + for (auto& cb : m_on_draw_ui_callbacks) { + handle_protected_result(cb()); + } + } + private: std::vector m_callbacks_to_remove{}; @@ -69,6 +99,11 @@ class ScriptContext { std::vector m_on_pre_viewport_client_draw_callbacks{}; std::vector m_on_post_viewport_client_draw_callbacks{}; + // Custom UEVR callbacks + std::vector m_on_frame_callbacks{}; + std::vector m_on_draw_ui_callbacks{}; + std::vector m_on_script_reset_callbacks{}; + static void on_pre_engine_tick(UEVR_UGameEngineHandle engine, float delta_seconds); static void on_post_engine_tick(UEVR_UGameEngineHandle engine, float delta_seconds); static void on_pre_slate_draw_window_render_thread(UEVR_FSlateRHIRendererHandle renderer, UEVR_FViewportInfoHandle viewport_info); @@ -77,4 +112,8 @@ class ScriptContext { static void on_post_calculate_stereo_view_offset(UEVR_StereoRenderingDeviceHandle device, int view_index, float world_to_meters, UEVR_Vector3f* position, UEVR_Rotatorf* rotation, bool is_double); static void on_pre_viewport_client_draw(UEVR_UGameViewportClientHandle viewport_client, UEVR_FViewportHandle viewport, UEVR_FCanvasHandle canvas); static void on_post_viewport_client_draw(UEVR_UGameViewportClientHandle viewport_client, UEVR_FViewportHandle viewport, UEVR_FCanvasHandle canvas); -}; \ No newline at end of file + static void on_frame(); + static void on_draw_ui(); + static void on_script_reset(); +}; +} \ No newline at end of file diff --git a/lua-api/lib/include/ScriptState.hpp b/lua-api/lib/include/ScriptState.hpp new file mode 100644 index 00000000..aa70e118 --- /dev/null +++ b/lua-api/lib/include/ScriptState.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include + +#include "ScriptContext.hpp" + +namespace uevr { +class ScriptState { +public: + enum class GarbageCollectionHandler : uint32_t { + UEVR_MANAGED = 0, + LUA_MANAGED = 1, + LAST + }; + + enum class GarbageCollectionType : uint32_t { + STEP = 0, + FULL = 1, + LAST + }; + + enum class GarbageCollectionMode : uint32_t { + GENERATIONAL = 0, + INCREMENTAL = 1, + LAST + }; + + struct GarbageCollectionData { + GarbageCollectionHandler gc_handler{GarbageCollectionHandler::UEVR_MANAGED}; + GarbageCollectionType gc_type{GarbageCollectionType::FULL}; + GarbageCollectionMode gc_mode{GarbageCollectionMode::GENERATIONAL}; + std::chrono::microseconds gc_budget{1000}; + + uint32_t gc_minor_multiplier{1}; + uint32_t gc_major_multiplier{100}; + }; + + ScriptState(const GarbageCollectionData& gc_data, UEVR_PluginInitializeParam* param, bool is_main_state); + ~ScriptState(); + + void run_script(const std::string& p); + sol::protected_function_result handle_protected_result(sol::protected_function_result result); // because protected_functions don't throw + + void gc_data_changed(GarbageCollectionData data); + void on_frame(); + void on_draw_ui(); + void on_script_reset(); + + auto& context() { + return m_context; + } + + auto& lua() { return m_lua; } + +private: + sol::state m_lua{}; + std::shared_ptr m_context{nullptr}; + + GarbageCollectionData m_gc_data{}; + bool m_is_main_state; +}; +} \ No newline at end of file diff --git a/lua-api/ScriptContext.cpp b/lua-api/lib/src/ScriptContext.cpp similarity index 59% rename from lua-api/ScriptContext.cpp rename to lua-api/lib/src/ScriptContext.cpp index e0cdd74c..78e6604b 100644 --- a/lua-api/ScriptContext.cpp +++ b/lua-api/lib/src/ScriptContext.cpp @@ -8,17 +8,39 @@ #include "ScriptContext.hpp" -std::shared_ptr g_script_context{}; +namespace uevr { +class ScriptContexts { +public: + void add(std::shared_ptr ctx) { + std::scoped_lock _{mtx}; + + // Check if the context is already in the list + for (auto& c : list) { + if (c == ctx) { + return; + } + } -std::shared_ptr ScriptContext::get() { - return g_script_context; -} + list.push_back(ctx); + } -std::shared_ptr ScriptContext::reinitialize(lua_State* l, UEVR_PluginInitializeParam* param) { - g_script_context.reset(); - g_script_context = std::make_shared(l, param); - return g_script_context; -} + void remove(std::shared_ptr ctx) { + std::scoped_lock _{mtx}; + list.erase(std::remove(list.begin(), list.end(), ctx), list.end()); + } + + template + void for_each(T&& fn) { + std::scoped_lock _{mtx}; + for (auto& ctx : list) { + fn(ctx); + } + } + +private: + std::vector> list{}; + std::mutex mtx{}; +} g_contexts{}; ScriptContext::ScriptContext(lua_State* l, UEVR_PluginInitializeParam* param) : m_lua{l} @@ -44,6 +66,7 @@ ScriptContext::ScriptContext(lua_State* l, UEVR_PluginInitializeParam* param) ScriptContext::~ScriptContext() { std::scoped_lock _{m_mtx}; + // TODO: this probably does not support multiple states if (m_plugin_initialize_param != nullptr) { for (auto& cb : m_callbacks_to_remove) { m_plugin_initialize_param->functions->remove_callback(cb); @@ -51,6 +74,8 @@ ScriptContext::~ScriptContext() { m_callbacks_to_remove.clear(); } + + g_contexts.remove(shared_from_this()); } void ScriptContext::log(const std::string& message) { @@ -66,52 +91,68 @@ void ScriptContext::setup_callback_bindings() { auto cbs = m_plugin_initialize_param->sdk->callbacks; - g_script_context->add_callback(cbs->on_pre_engine_tick, on_pre_engine_tick); - g_script_context->add_callback(cbs->on_post_engine_tick, on_post_engine_tick); - g_script_context->add_callback(cbs->on_pre_slate_draw_window_render_thread, on_pre_slate_draw_window_render_thread); - g_script_context->add_callback(cbs->on_post_slate_draw_window_render_thread, on_post_slate_draw_window_render_thread); - g_script_context->add_callback(cbs->on_pre_calculate_stereo_view_offset, on_pre_calculate_stereo_view_offset); - g_script_context->add_callback(cbs->on_post_calculate_stereo_view_offset, on_post_calculate_stereo_view_offset); - g_script_context->add_callback(cbs->on_pre_viewport_client_draw, on_pre_viewport_client_draw); - g_script_context->add_callback(cbs->on_post_viewport_client_draw, on_post_viewport_client_draw); + add_callback(cbs->on_pre_engine_tick, on_pre_engine_tick); + add_callback(cbs->on_post_engine_tick, on_post_engine_tick); + add_callback(cbs->on_pre_slate_draw_window_render_thread, on_pre_slate_draw_window_render_thread); + add_callback(cbs->on_post_slate_draw_window_render_thread, on_post_slate_draw_window_render_thread); + add_callback(cbs->on_pre_calculate_stereo_view_offset, on_pre_calculate_stereo_view_offset); + add_callback(cbs->on_post_calculate_stereo_view_offset, on_post_calculate_stereo_view_offset); + add_callback(cbs->on_pre_viewport_client_draw, on_pre_viewport_client_draw); + add_callback(cbs->on_post_viewport_client_draw, on_post_viewport_client_draw); m_lua.new_usertype("UEVR_SDKCallbacks", - "on_pre_engine_tick", [](sol::function fn) { - std::scoped_lock _{ g_script_context->m_mtx }; - g_script_context->m_on_pre_engine_tick_callbacks.push_back(fn); + "on_pre_engine_tick", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_pre_engine_tick_callbacks.push_back(fn); + }, + "on_post_engine_tick", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_post_engine_tick_callbacks.push_back(fn); + }, + "on_pre_slate_draw_window_render_thread", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_pre_slate_draw_window_render_thread_callbacks.push_back(fn); }, - "on_post_engine_tick", [](sol::function fn) { - std::scoped_lock _{ g_script_context->m_mtx }; - g_script_context->m_on_post_engine_tick_callbacks.push_back(fn); + "on_post_slate_draw_window_render_thread", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_post_slate_draw_window_render_thread_callbacks.push_back(fn); }, - "on_pre_slate_draw_window_render_thread", [](sol::function fn) { - std::scoped_lock _{ g_script_context->m_mtx }; - g_script_context->m_on_pre_slate_draw_window_render_thread_callbacks.push_back(fn); + "on_pre_calculate_stereo_view_offset", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_pre_calculate_stereo_view_offset_callbacks.push_back(fn); }, - "on_post_slate_draw_window_render_thread", [](sol::function fn) { - std::scoped_lock _{ g_script_context->m_mtx }; - g_script_context->m_on_post_slate_draw_window_render_thread_callbacks.push_back(fn); + "on_post_calculate_stereo_view_offset", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_post_calculate_stereo_view_offset_callbacks.push_back(fn); }, - "on_pre_calculate_stereo_view_offset", [](sol::function fn) { - std::scoped_lock _{ g_script_context->m_mtx }; - g_script_context->m_on_pre_calculate_stereo_view_offset_callbacks.push_back(fn); + "on_pre_viewport_client_draw", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_pre_viewport_client_draw_callbacks.push_back(fn); }, - "on_post_calculate_stereo_view_offset", [](sol::function fn) { - std::scoped_lock _{ g_script_context->m_mtx }; - g_script_context->m_on_post_calculate_stereo_view_offset_callbacks.push_back(fn); + "on_post_viewport_client_draw", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_post_viewport_client_draw_callbacks.push_back(fn); }, - "on_pre_viewport_client_draw", [](sol::function fn) { - std::scoped_lock _{ g_script_context->m_mtx }; - g_script_context->m_on_pre_viewport_client_draw_callbacks.push_back(fn); + "on_frame", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_frame_callbacks.push_back(fn); }, - "on_post_viewport_client_draw", [](sol::function fn) { - std::scoped_lock _{ g_script_context->m_mtx }; - g_script_context->m_on_post_viewport_client_draw_callbacks.push_back(fn); + "on_draw_ui", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_draw_ui_callbacks.push_back(fn); + }, + "on_script_reset", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_script_reset_callbacks.push_back(fn); } ); } int ScriptContext::setup_bindings() { + g_contexts.add(shared_from_this()); + + m_lua.registry()["uevr_context"] = this; + m_lua.set_function("test_function", ScriptContext::test_function); m_lua.new_usertype("UEVR_PluginInitializeParam", @@ -347,19 +388,6 @@ int ScriptContext::setup_bindings() { "find_command", &uevr::API::FConsoleManager::find_command ); - m_lua.new_usertype("UEVR_API", - "sdk", &uevr::API::sdk, - "find_uobject", &uevr::API::find_uobject, - "get_engine", &uevr::API::get_engine, - "get_player_controller", &uevr::API::get_player_controller, - "get_local_pawn", &uevr::API::get_local_pawn, - "spawn_object", &uevr::API::spawn_object, - "execute_command", &uevr::API::execute_command, - "execute_command_ex", &uevr::API::execute_command_ex, - "get_uobject_array", &uevr::API::get_uobject_array, - "get_console_manager", &uevr::API::get_console_manager - ); - m_lua.new_usertype("UEVR_IConsoleObject", "as_command", &uevr::API::IConsoleObject::as_command ); @@ -396,106 +424,208 @@ int ScriptContext::setup_bindings() { "execute", &uevr::API::IConsoleCommand::execute ); + m_lua.new_usertype("UEVR_FUObjectArray", + "get", &uevr::API::FUObjectArray::get, + "is_chunked", &uevr::API::FUObjectArray::is_chunked, + "is_inlined", &uevr::API::FUObjectArray::is_inlined, + "get_objects_offset", &uevr::API::FUObjectArray::get_objects_offset, + "get_item_distance", &uevr::API::FUObjectArray::get_item_distance, + "get_object_count", &uevr::API::FUObjectArray::get_object_count, + "get_objects_ptr", &uevr::API::FUObjectArray::get_objects_ptr, + "get_object", &uevr::API::FUObjectArray::get_object, + "get_item", &uevr::API::FUObjectArray::get_item + ); + + m_lua.new_usertype("UEVR_API", + "sdk", &uevr::API::sdk, + "find_uobject", &uevr::API::find_uobject, + "get_engine", &uevr::API::get_engine, + "get_player_controller", &uevr::API::get_player_controller, + "get_local_pawn", &uevr::API::get_local_pawn, + "spawn_object", &uevr::API::spawn_object, + "execute_command", &uevr::API::execute_command, + "execute_command_ex", &uevr::API::execute_command_ex, + "get_uobject_array", &uevr::API::get_uobject_array, + "get_console_manager", &uevr::API::get_console_manager + ); + setup_callback_bindings(); auto out = m_lua.create_table(); out["params"] = m_plugin_initialize_param; out["api"] = uevr::API::get().get(); + out["types"] = m_lua.create_table_with( + "UObject", m_lua["UEVR_UObject"], + "UStruct", m_lua["UEVR_UStruct"], + "UClass", m_lua["UEVR_UClass"], + "UFunction", m_lua["UEVR_UFunction"], + "FField", m_lua["UEVR_FField"], + "FFieldClass", m_lua["UEVR_FFieldClass"], + "FConsoleManager", m_lua["UEVR_FConsoleManager"], + "IConsoleObject", m_lua["UEVR_IConsoleObject"], + "IConsoleVariable", m_lua["UEVR_IConsoleVariable"], + "IConsoleCommand", m_lua["UEVR_IConsoleCommand"], + "FName", m_lua["UEVR_FName"], + "FUObjectArray", m_lua["UEVR_FUObjectArray"] + ); + + out["plugin_callbacks"] = m_plugin_initialize_param->callbacks; + out["sdk"] = m_plugin_initialize_param->sdk; return out.push(m_lua.lua_state()); } void ScriptContext::on_pre_engine_tick(UEVR_UGameEngineHandle engine, float delta_seconds) { - std::scoped_lock _{ g_script_context->m_mtx }; - for (auto& fn : g_script_context->m_on_pre_engine_tick_callbacks) try { - g_script_context->handle_protected_result(fn(engine, delta_seconds)); - } catch (const std::exception& e) { - ScriptContext::log("Exception in on_pre_engine_tick: " + std::string(e.what())); - } catch (...) { - ScriptContext::log("Unknown exception in on_pre_engine_tick"); - } + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_pre_engine_tick_callbacks) try { + ctx->handle_protected_result(fn(engine, delta_seconds)); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_pre_engine_tick: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_pre_engine_tick"); + } + }); } void ScriptContext::on_post_engine_tick(UEVR_UGameEngineHandle engine, float delta_seconds) { - std::scoped_lock _{ g_script_context->m_mtx }; - - for (auto& fn : g_script_context->m_on_post_engine_tick_callbacks) try { - g_script_context->handle_protected_result(fn(engine, delta_seconds)); - } catch (const std::exception& e) { - ScriptContext::log("Exception in on_post_engine_tick: " + std::string(e.what())); - } catch (...) { - ScriptContext::log("Unknown exception in on_post_engine_tick"); - } + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_post_engine_tick_callbacks) try { + ctx->handle_protected_result(fn(engine, delta_seconds)); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_post_engine_tick: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_post_engine_tick"); + } + }); } void ScriptContext::on_pre_slate_draw_window_render_thread(UEVR_FSlateRHIRendererHandle renderer, UEVR_FViewportInfoHandle viewport_info) { - std::scoped_lock _{ g_script_context->m_mtx }; - - for (auto& fn : g_script_context->m_on_pre_slate_draw_window_render_thread_callbacks) try { - g_script_context->handle_protected_result(fn(renderer, viewport_info)); - } catch (const std::exception& e) { - ScriptContext::log("Exception in on_pre_slate_draw_window_render_thread: " + std::string(e.what())); - } catch (...) { - ScriptContext::log("Unknown exception in on_pre_slate_draw_window_render_thread"); - } + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_pre_slate_draw_window_render_thread_callbacks) try { + ctx->handle_protected_result(fn(renderer, viewport_info)); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_pre_slate_draw_window_render_thread: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_pre_slate_draw_window_render_thread"); + } + }); } void ScriptContext::on_post_slate_draw_window_render_thread(UEVR_FSlateRHIRendererHandle renderer, UEVR_FViewportInfoHandle viewport_info) { - std::scoped_lock _{ g_script_context->m_mtx }; - - for (auto& fn : g_script_context->m_on_post_slate_draw_window_render_thread_callbacks) try { - g_script_context->handle_protected_result(fn(renderer, viewport_info)); - } catch (const std::exception& e) { - ScriptContext::log("Exception in on_post_slate_draw_window_render_thread: " + std::string(e.what())); - } catch (...) { - ScriptContext::log("Unknown exception in on_post_slate_draw_window_render_thread"); - } + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_post_slate_draw_window_render_thread_callbacks) try { + ctx->handle_protected_result(fn(renderer, viewport_info)); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_post_slate_draw_window_render_thread: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_post_slate_draw_window_render_thread"); + } + }); } void ScriptContext::on_pre_calculate_stereo_view_offset(UEVR_StereoRenderingDeviceHandle device, int view_index, float world_to_meters, UEVR_Vector3f* position, UEVR_Rotatorf* rotation, bool is_double) { - std::scoped_lock _{ g_script_context->m_mtx }; - - for (auto& fn : g_script_context->m_on_pre_calculate_stereo_view_offset_callbacks) try { - g_script_context->handle_protected_result(fn(device, view_index, world_to_meters, position, rotation, is_double)); - } catch (const std::exception& e) { - ScriptContext::log("Exception in on_pre_calculate_stereo_view_offset: " + std::string(e.what())); - } catch (...) { - ScriptContext::log("Unknown exception in on_pre_calculate_stereo_view_offset"); - } + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_pre_calculate_stereo_view_offset_callbacks) try { + ctx->handle_protected_result(fn(device, view_index, world_to_meters, position, rotation, is_double)); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_pre_calculate_stereo_view_offset: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_pre_calculate_stereo_view_offset"); + } + }); } void ScriptContext::on_post_calculate_stereo_view_offset(UEVR_StereoRenderingDeviceHandle device, int view_index, float world_to_meters, UEVR_Vector3f* position, UEVR_Rotatorf* rotation, bool is_double) { - std::scoped_lock _{ g_script_context->m_mtx }; - - for (auto& fn : g_script_context->m_on_post_calculate_stereo_view_offset_callbacks) try { - g_script_context->handle_protected_result(fn(device, view_index, world_to_meters, position, rotation, is_double)); - } catch (const std::exception& e) { - ScriptContext::log("Exception in on_post_calculate_stereo_view_offset: " + std::string(e.what())); - } catch (...) { - ScriptContext::log("Unknown exception in on_post_calculate_stereo_view_offset"); - } + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_post_calculate_stereo_view_offset_callbacks) try { + ctx->handle_protected_result(fn(device, view_index, world_to_meters, position, rotation, is_double)); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_post_calculate_stereo_view_offset: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_post_calculate_stereo_view_offset"); + } + }); } void ScriptContext::on_pre_viewport_client_draw(UEVR_UGameViewportClientHandle viewport_client, UEVR_FViewportHandle viewport, UEVR_FCanvasHandle canvas) { - std::scoped_lock _{ g_script_context->m_mtx }; - - for (auto& fn : g_script_context->m_on_pre_viewport_client_draw_callbacks) try { - g_script_context->handle_protected_result(fn(viewport_client, viewport, canvas)); - } catch (const std::exception& e) { - ScriptContext::log("Exception in on_pre_viewport_client_draw: " + std::string(e.what())); - } catch (...) { - ScriptContext::log("Unknown exception in on_pre_viewport_client_draw"); - } + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_pre_viewport_client_draw_callbacks) try { + ctx->handle_protected_result(fn(viewport_client, viewport, canvas)); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_pre_viewport_client_draw: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_pre_viewport_client_draw"); + } + }); } void ScriptContext::on_post_viewport_client_draw(UEVR_UGameViewportClientHandle viewport_client, UEVR_FViewportHandle viewport, UEVR_FCanvasHandle canvas) { - std::scoped_lock _{ g_script_context->m_mtx }; - - for (auto& fn : g_script_context->m_on_post_viewport_client_draw_callbacks) try { - g_script_context->handle_protected_result(fn(viewport_client, viewport, canvas)); - } catch (const std::exception& e) { - ScriptContext::log("Exception in on_post_viewport_client_draw: " + std::string(e.what())); - } catch (...) { - ScriptContext::log("Unknown exception in on_post_viewport_client_draw"); - } + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_post_viewport_client_draw_callbacks) try { + ctx->handle_protected_result(fn(viewport_client, viewport, canvas)); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_post_viewport_client_draw: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_post_viewport_client_draw"); + } + }); +} + +void ScriptContext::on_frame() { + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_frame_callbacks) try { + ctx->handle_protected_result(fn()); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_frame: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_frame"); + } + }); +} + +void ScriptContext::on_draw_ui() { + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_draw_ui_callbacks) try { + ctx->handle_protected_result(fn()); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_draw_ui: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_draw_ui"); + } + }); +} + +void ScriptContext::on_script_reset() { + g_contexts.for_each([=](auto ctx) { + std::scoped_lock _{ ctx->m_mtx }; + + for (auto& fn : ctx->m_on_script_reset_callbacks) try { + ctx->handle_protected_result(fn()); + } catch (const std::exception& e) { + ScriptContext::log("Exception in on_script_reset: " + std::string(e.what())); + } catch (...) { + ScriptContext::log("Unknown exception in on_script_reset"); + } + }); +} } \ No newline at end of file diff --git a/lua-api/lib/src/ScriptState.cpp b/lua-api/lib/src/ScriptState.cpp new file mode 100644 index 00000000..af898fef --- /dev/null +++ b/lua-api/lib/src/ScriptState.cpp @@ -0,0 +1,187 @@ +#include + +#include + +#include "uevr/API.hpp" +#include "ScriptState.hpp" + +namespace api::ue { +void msg(const char* text) { + MessageBoxA(GetForegroundWindow(), text, "LuaLoader Message", MB_ICONINFORMATION | MB_OK); +} +} + +namespace uevr { +ScriptState::ScriptState(const ScriptState::GarbageCollectionData& gc_data, UEVR_PluginInitializeParam* param, bool is_main_state) { + if (param != nullptr) { + uevr::API::initialize(param); + } + + if (param != nullptr && param->functions != nullptr) { + param->functions->log_info("Creating new ScriptState..."); + } + + m_is_main_state = is_main_state; + m_lua.registry()["uevr_state"] = this; + m_lua.open_libraries(sol::lib::base, sol::lib::package, sol::lib::string, sol::lib::math, sol::lib::table, sol::lib::bit32, + sol::lib::utf8, sol::lib::os, sol::lib::coroutine); + + // Disable garbage collection. We will manually do it at the end of each frame. + gc_data_changed(gc_data); + + // Restrict os library + auto os = m_lua["os"]; + os["remove"] = sol::nil; + os["rename"] = sol::nil; + os["execute"] = sol::nil; + os["exit"] = sol::nil; + os["setlocale"] = sol::nil; + os["getenv"] = sol::nil; + + // TODO: Make this actually support multiple states + // This stores a global reference to itself, meaning it doesn't support multiple states + m_context = ScriptContext::create(m_lua.lua_state(), param); + + if (!m_context->valid()) { + if (param != nullptr && param->functions != nullptr) { + param->functions->log_error("Failed to create new ScriptState!"); + } + + return; + } + + const auto result = m_context->setup_bindings(); + + if (result != 0) { + const auto table = sol::stack::pop(m_lua); + m_lua["uevr"] = table; + } +} + +ScriptState::~ScriptState() { + +} + +void ScriptState::run_script(const std::string& p) { + uevr::API::get()->log_info(std::format("Running script {}...", p).c_str()); + + std::string old_package_path = m_lua["package"]["path"]; + std::string old_cpath = m_lua["package"]["cpath"]; + + try { + auto path = std::filesystem::path(p); + auto dir = path.parent_path(); + + std::string package_path = m_lua["package"]["path"]; + std::string cpath = m_lua["package"]["cpath"]; + + package_path = old_package_path + ";" + dir.string() + "/?.lua"; + package_path = package_path + ";" + dir.string() + "/?/init.lua"; + //package_path = package_path + ";" + dir.string() + "/?.dll"; + + cpath = old_cpath + ";" + dir.string() + "/?.dll"; + + m_lua["package"]["path"] = package_path; + m_lua.safe_script_file(p); + } catch (const std::exception& e) { + //LuaLoader::get()->spew_error(e.what()); + api::ue::msg(e.what()); + } catch (...) { + //LuaLoader::get()->spew_error((std::stringstream{} << "Unknown error when running script " << p).str()); + api::ue::msg((std::stringstream{} << "Unknown error when running script " << p).str().c_str()); + } + + m_lua["package"]["path"] = old_package_path; + m_lua["package"]["cpath"] = old_cpath; +} + +void ScriptState::gc_data_changed(GarbageCollectionData data) { + // Handler + switch (data.gc_handler) { + case ScriptState::GarbageCollectionHandler::UEVR_MANAGED: + lua_gc(m_lua, LUA_GCSTOP); + break; + case ScriptState::GarbageCollectionHandler::LUA_MANAGED: + lua_gc(m_lua, LUA_GCRESTART); + break; + default: + lua_gc(m_lua, LUA_GCRESTART); + data.gc_handler = ScriptState::GarbageCollectionHandler::LUA_MANAGED; + break; + } + + // Type + if (data.gc_type >= ScriptState::GarbageCollectionType::LAST) { + data.gc_type = ScriptState::GarbageCollectionType::STEP; + } + + // Mode + if (data.gc_mode >= ScriptState::GarbageCollectionMode::LAST) { + data.gc_mode = ScriptState::GarbageCollectionMode::GENERATIONAL; + } + + switch (data.gc_mode) { + case ScriptState::GarbageCollectionMode::GENERATIONAL: + lua_gc(m_lua, LUA_GCGEN, data.gc_minor_multiplier, data.gc_major_multiplier); + break; + case ScriptState::GarbageCollectionMode::INCREMENTAL: + lua_gc(m_lua, LUA_GCINC); + break; + default: + lua_gc(m_lua, LUA_GCGEN, data.gc_minor_multiplier, data.gc_major_multiplier); + data.gc_mode = ScriptState::GarbageCollectionMode::GENERATIONAL; + break; + } + + m_gc_data = data; +} + +void ScriptState::on_script_reset() { + if (m_context == nullptr) { + return; + } + + m_context->script_reset(); +} + +void ScriptState::on_frame() { + if (m_context != nullptr) { + m_context->frame(); + } + + if (m_gc_data.gc_handler != ScriptState::GarbageCollectionHandler::UEVR_MANAGED) { + return; + } + + // This is thread safe, so we don't need to lock the mutex + switch (m_gc_data.gc_type) { + case ScriptState::GarbageCollectionType::FULL: + lua_gc(m_lua, LUA_GCCOLLECT); + break; + case ScriptState::GarbageCollectionType::STEP: + { + const auto now = std::chrono::high_resolution_clock::now(); + + if (m_gc_data.gc_mode == ScriptState::GarbageCollectionMode::GENERATIONAL) { + lua_gc(m_lua, LUA_GCSTEP, 1); + } else { + while (lua_gc(m_lua, LUA_GCSTEP, 1) == 0) { + if (std::chrono::high_resolution_clock::now() - now >= m_gc_data.gc_budget) { + break; + } + } + } + } + break; + default: + lua_gc(m_lua, LUA_GCCOLLECT); + break; + }; +} + +void ScriptState::on_draw_ui() { + if (m_context != nullptr) { + m_context->draw_ui(); + } +} +} \ No newline at end of file diff --git a/src/Mods.cpp b/src/Mods.cpp index dc637b83..d5d546a4 100644 --- a/src/Mods.cpp +++ b/src/Mods.cpp @@ -5,6 +5,7 @@ #include "mods/FrameworkConfig.hpp" #include "mods/VR.hpp" #include "mods/PluginLoader.hpp" +#include "mods/LuaLoader.hpp" #include "mods/UObjectHook.hpp" #include "Mods.hpp" @@ -14,6 +15,7 @@ Mods::Mods() { m_mods.emplace_back(UObjectHook::get()); m_mods.emplace_back(PluginLoader::get()); + m_mods.emplace_back(LuaLoader::get()); } std::optional Mods::on_initialize() const { diff --git a/src/mods/LuaLoader.cpp b/src/mods/LuaLoader.cpp new file mode 100644 index 00000000..f526fd2d --- /dev/null +++ b/src/mods/LuaLoader.cpp @@ -0,0 +1,269 @@ +#include +#include + +#include "Framework.hpp" +#include "PluginLoader.hpp" +#include "LuaLoader.hpp" + +#include // weird include order because of sol +#include + +std::shared_ptr& LuaLoader::get() { + static auto instance = std::make_shared(); + return instance; +} + +std::optional LuaLoader::on_initialize_d3d_thread() { + // TODO? + return Mod::on_initialize_d3d_thread(); +} + +void LuaLoader::on_config_load(const utility::Config& cfg, bool set_defaults) { + std::scoped_lock _{m_access_mutex}; + + for (IModValue& option : m_options) { + option.config_load(cfg, set_defaults); + } + + if (m_main_state != nullptr) { + m_main_state->gc_data_changed(make_gc_data()); + } +} + +void LuaLoader::on_config_save(utility::Config& cfg) { + std::scoped_lock _{m_access_mutex}; + + for (IModValue& option : m_options) { + option.config_save(cfg); + } + + + // TODO: Add config save callback to ScriptState + if (m_main_state != nullptr) { + //m_main_state->on_config_save(); + } +} + +void LuaLoader::on_frame() { + std::scoped_lock _{m_access_mutex}; + + if (m_needs_first_reset) { + spdlog::info("[LuaLoader] Initializing Lua state for the first time..."); + + // Calling reset_scripts even though the scripts have never been set yet still works. + reset_scripts(); + m_needs_first_reset = false; + + spdlog::info("[LuaLoader] Lua state initialized."); + } + + for (auto state_to_delete : m_states_to_delete) { + std::erase_if(m_states, [&](std::shared_ptr state) { return state->lua().lua_state() == state_to_delete; }); + } + + m_states_to_delete.clear(); + + if (m_main_state == nullptr) { + return; + } + + for (auto &state : m_states) { + state->on_frame(); + } +} + +void LuaLoader::on_draw_ui() { + ImGui::SetNextItemOpen(false, ImGuiCond_::ImGuiCond_Once); + + if (ImGui::CollapsingHeader(get_name().data())) { + if (ImGui::Button("Run script")) { + OPENFILENAME ofn{}; + char file[260]{}; + + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = g_framework->get_window(); + ofn.lpstrFile = file; + ofn.nMaxFile = sizeof(file); + ofn.lpstrFilter = "Lua script files (*.lua)\0*.lua\0"; + ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR; + + if (GetOpenFileName(&ofn) != FALSE) { + std::scoped_lock _{ m_access_mutex }; + m_main_state->run_script(file); + m_loaded_scripts.emplace_back(std::filesystem::path{file}.filename().string()); + } + } + + ImGui::SameLine(); + + if (ImGui::Button("Reset scripts")) { + reset_scripts(); + } + + ImGui::SameLine(); + + if (ImGui::Button("Spawn Debug Console")) { + if (!m_console_spawned) { + AllocConsole(); + freopen("CONIN$", "r", stdin); + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + + m_console_spawned = true; + } + } + + //Garbage collection currently only showing from main lua state, might rework to show total later? + if (ImGui::TreeNode("Garbage Collection Stats")) { + std::scoped_lock _{ m_access_mutex }; + + auto g = G(m_main_state->lua().lua_state()); + const auto bytes_in_use = g->totalbytes + g->GCdebt; + + ImGui::Text("Megabytes in use: %.2f", (float)bytes_in_use / 1024.0f / 1024.0f); + + ImGui::TreePop(); + } + + if (m_gc_handler->draw("Garbage Collection Handler")) { + std::scoped_lock _{ m_access_mutex }; + m_main_state->gc_data_changed(make_gc_data()); + } + + if (m_gc_mode->draw("Garbage Collection Mode")) { + std::scoped_lock _{ m_access_mutex }; + m_main_state->gc_data_changed(make_gc_data()); + } + + if ((uint32_t)m_gc_mode->value() == (uint32_t)ScriptState::GarbageCollectionMode::GENERATIONAL) { + if (m_gc_minor_multiplier->draw("Minor GC Multiplier")) { + std::scoped_lock _{ m_access_mutex }; + m_main_state->gc_data_changed(make_gc_data()); + } + + if (m_gc_major_multiplier->draw("Major GC Multiplier")) { + std::scoped_lock _{ m_access_mutex }; + m_main_state->gc_data_changed(make_gc_data()); + } + } + + if (m_gc_handler->value() == (int32_t)ScriptState::GarbageCollectionHandler::UEVR_MANAGED) { + if (m_gc_type->draw("Garbage Collection Type")) { + std::scoped_lock _{ m_access_mutex }; + m_main_state->gc_data_changed(make_gc_data()); + } + + if ((uint32_t)m_gc_mode->value() != (uint32_t)ScriptState::GarbageCollectionMode::GENERATIONAL) { + if (m_gc_budget->draw("Garbage Collection Budget")) { + std::scoped_lock _{ m_access_mutex }; + m_main_state->gc_data_changed(make_gc_data()); + } + } + } + + m_log_to_disk->draw("Log Lua Errors to Disk"); + + if (!m_last_script_error.empty()) { + std::shared_lock _{m_script_error_mutex}; + + const auto now = std::chrono::system_clock::now(); + const auto diff = now - m_last_script_error_time; + const auto sec = std::chrono::duration(diff).count(); + + ImGui::TextWrapped("Last Error Time: %.2f seconds ago", sec); + + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); + ImGui::TextWrapped("Last Script Error: %s", m_last_script_error.data()); + ImGui::PopStyleColor(); + } else { + ImGui::TextWrapped("No Script Errors... yet!"); + } + + if (!m_known_scripts.empty()) { + ImGui::Text("Known scripts:"); + + for (auto&& name : m_known_scripts) { + if (ImGui::Checkbox(name.data(), &m_loaded_scripts_map[name])) { + reset_scripts(); + break; + } + } + } else { + ImGui::Text("No scripts loaded."); + } + } + + if (ImGui::CollapsingHeader("Script Generated UI")) { + std::scoped_lock _{m_access_mutex}; + + if (m_states.empty()) { + return; + } + + for (auto& state : m_states) { + state->on_draw_ui(); + } + } +} + +void LuaLoader::reset_scripts() { + spdlog::info("[LuaLoader] Resetting scripts..."); + + std::scoped_lock _{ m_access_mutex }; + + { + std::unique_lock _{ m_script_error_mutex }; + m_last_script_error.clear(); + } + + if (!m_states.empty()) { + /*auto& mods = g_framework->get_mods()->get_mods(); + + for (auto& mod : mods) { + mod->on_lua_state_destroyed(m_main_state->lua()); + }*/ + + m_main_state->on_script_reset(); + } + + m_main_state.reset(); + m_states.clear(); + + m_main_state = std::make_shared(make_gc_data(), &g_plugin_initialize_param, true); + m_states.insert(m_states.begin(), m_main_state); + + //callback functions for main lua state creation + /*auto& mods = g_framework->get_mods()->get_mods(); + for (auto& mod : mods) { + mod->on_lua_state_created(m_main_state->lua()); + }*/ + + m_loaded_scripts.clear(); + m_known_scripts.clear(); + + const auto autorun_path = Framework::get_persistent_dir() / "scripts"; + + spdlog::info("[LuaLoader] Creating directories {}", autorun_path.string()); + std::filesystem::create_directories(autorun_path); + spdlog::info("[LuaLoader] Loading scripts..."); + + for (auto&& entry : std::filesystem::directory_iterator{autorun_path}) { + auto&& path = entry.path(); + + if (path.has_extension() && path.extension() == ".lua") { + if (!m_loaded_scripts_map.contains(path.filename().string())) { + m_loaded_scripts_map.emplace(path.filename().string(), true); + } + + if (m_loaded_scripts_map[path.filename().string()] == true) { + m_main_state->run_script(path.string()); + m_loaded_scripts.emplace_back(path.filename().string()); + } + + m_known_scripts.emplace_back(path.filename().string()); + } + } + + std::sort(m_known_scripts.begin(), m_known_scripts.end()); + std::sort(m_loaded_scripts.begin(), m_loaded_scripts.end()); +} \ No newline at end of file diff --git a/src/mods/LuaLoader.hpp b/src/mods/LuaLoader.hpp new file mode 100644 index 00000000..c938964a --- /dev/null +++ b/src/mods/LuaLoader.hpp @@ -0,0 +1,121 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "ScriptContext.hpp" +#include "ScriptState.hpp" + +#include "Mod.hpp" + +using namespace uevr; + +class LuaLoader : public Mod { +public: + static std::shared_ptr& get(); + + std::string_view get_name() const override { return "LuaLoader"; } + bool is_advanced_mod() const override { return true; } + std::optional on_initialize_d3d_thread() override; + void on_draw_ui() override; + void on_frame() override; + + void on_config_load(const utility::Config& cfg, bool set_defaults) override; + void on_config_save(utility::Config& cfg) override; + + + const auto& get_state() { + return m_main_state; + } + + const auto& get_state(int index) { + return m_states[index]; + } + +private: + ScriptState::GarbageCollectionData make_gc_data() const { + ScriptState::GarbageCollectionData data{}; + + data.gc_handler = (decltype(ScriptState::GarbageCollectionData::gc_handler))m_gc_handler->value(); + data.gc_type = (decltype(ScriptState::GarbageCollectionData::gc_type))m_gc_type->value(); + data.gc_mode = (decltype(ScriptState::GarbageCollectionData::gc_mode))m_gc_mode->value(); + data.gc_budget = std::chrono::microseconds{(uint32_t)m_gc_budget->value()}; + data.gc_minor_multiplier = (uint32_t)m_gc_minor_multiplier->value(); + data.gc_major_multiplier = (uint32_t)m_gc_major_multiplier->value(); + + return data; + } + + std::shared_ptr m_main_state{}; + std::vector> m_states{}; + std::recursive_mutex m_access_mutex{}; + + // A list of Lua files that have been explicitly loaded either through the user manually loading the script, or + // because the script was in the autorun directory. + std::vector m_loaded_scripts{}; + std::vector m_known_scripts{}; + std::unordered_map m_loaded_scripts_map{}; + std::vector m_states_to_delete{}; + std::string m_last_script_error{}; + std::shared_mutex m_script_error_mutex{}; + std::chrono::system_clock::time_point m_last_script_error_time{}; + + bool m_console_spawned{false}; + bool m_needs_first_reset{true}; + + const ModToggle::Ptr m_log_to_disk{ ModToggle::create(generate_name("LogToDisk"), false) }; + + const ModCombo::Ptr m_gc_handler { + ModCombo::create(generate_name("GarbageCollectionHandler"), + { + "Managed by UEVR", + "Managed by Lua" + }, (int)ScriptState::GarbageCollectionHandler::UEVR_MANAGED) + }; + + const ModCombo::Ptr m_gc_type { + ModCombo::create(generate_name("GarbageCollectionType"), + { + "Step", + "Full", + }, (int)ScriptState::GarbageCollectionType::STEP) + }; + + const ModCombo::Ptr m_gc_mode { + ModCombo::create(generate_name("GarbageCollectionMode"), + { + "Generational", + "Incremental (Mark & Sweep)", + }, (int)ScriptState::GarbageCollectionMode::GENERATIONAL) + }; + + // Garbage collection budget in microseconds. + const ModSlider::Ptr m_gc_budget { + ModSlider::create(generate_name("GarbageCollectionBudget"), 0.0f, 2000.0f, 1000.0f) + }; + + const ModSlider::Ptr m_gc_minor_multiplier { + ModSlider::create(generate_name("GarbageCollectionMinorMultiplier"), 1.0f, 200.0f, 1.0f) + }; + + const ModSlider::Ptr m_gc_major_multiplier { + ModSlider::create(generate_name("GarbageCollectionMajorMultiplier"), 1.0f, 1000.0f, 100.0f) + }; + + ValueList m_options{ + *m_log_to_disk, + *m_gc_handler, + *m_gc_type, + *m_gc_mode, + *m_gc_budget, + *m_gc_minor_multiplier, + *m_gc_major_multiplier + }; + + // Resets the ScriptState and runs autorun scripts again. + void reset_scripts(); +}; \ No newline at end of file diff --git a/src/mods/PluginLoader.cpp b/src/mods/PluginLoader.cpp index fa5d14aa..60126bf1 100644 --- a/src/mods/PluginLoader.cpp +++ b/src/mods/PluginLoader.cpp @@ -1580,7 +1580,6 @@ void PluginLoader::on_xinput_set_state(uint32_t* retval, uint32_t user_index, XI } } - void PluginLoader::on_pre_engine_tick(sdk::UGameEngine* engine, float delta) { std::shared_lock _{m_api_cb_mtx}; diff --git a/src/mods/PluginLoader.hpp b/src/mods/PluginLoader.hpp index 18d7fa32..d97c2094 100644 --- a/src/mods/PluginLoader.hpp +++ b/src/mods/PluginLoader.hpp @@ -13,6 +13,8 @@ namespace uevr { extern UEVR_RendererData g_renderer_data; } +extern "C" __declspec(dllexport) UEVR_PluginInitializeParam g_plugin_initialize_param; + class PluginLoader : public Mod { public: static std::shared_ptr& get(); From 14fa2c6eeef6bc7a88401b5ee9d22c7b7871c181 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 5 Mar 2024 19:53:03 -0800 Subject: [PATCH 02/37] Lua: Fix initial run not executing on game thread --- src/mods/LuaLoader.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/mods/LuaLoader.cpp b/src/mods/LuaLoader.cpp index f526fd2d..89e8f744 100644 --- a/src/mods/LuaLoader.cpp +++ b/src/mods/LuaLoader.cpp @@ -5,6 +5,8 @@ #include "PluginLoader.hpp" #include "LuaLoader.hpp" +#include + #include // weird include order because of sol #include @@ -45,6 +47,14 @@ void LuaLoader::on_config_save(utility::Config& cfg) { } void LuaLoader::on_frame() { + // Only run on the game thread + // on_frame can sometimes run in the DXGI thread, this happens + // before tick is hooked, which is where the game thread is. + // once tick is hooked, on_frame will always run on the game thread. + if (!GameThreadWorker::get().is_same_thread()) { + return; + } + std::scoped_lock _{m_access_mutex}; if (m_needs_first_reset) { From 18e7b9eae50936065248b550e3ed31961002c7ba Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 5 Mar 2024 21:10:35 -0800 Subject: [PATCH 03/37] Plugins: Fix huge bug with remove_callback --- src/mods/PluginLoader.hpp | 42 ++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/mods/PluginLoader.hpp b/src/mods/PluginLoader.hpp index d97c2094..9aafc4e3 100644 --- a/src/mods/PluginLoader.hpp +++ b/src/mods/PluginLoader.hpp @@ -86,13 +86,13 @@ class PluginLoader : public Mod { bool add_on_post_viewport_client_draw(UEVR_ViewportClient_DrawCb cb); bool remove_callback(void* cb) { - std::scoped_lock lock{m_api_cb_mtx}; + std::unique_lock lock{m_api_cb_mtx}; for (auto& pcb_list : m_plugin_callback_lists) { auto& cb_list = *pcb_list; - cb_list.erase(std::remove_if(cb_list.begin(), cb_list.end(), [cb](auto& cb_func) { - return cb_func == cb; - })); + std::erase_if(cb_list, [cb](auto& cb_func) { + return cb_func.target() == cb; + }); } return true; @@ -117,29 +117,31 @@ class PluginLoader : public Mod { std::vector m_on_pre_viewport_client_draw_cbs{}; std::vector m_on_post_viewport_client_draw_cbs{}; - std::vector*> m_plugin_callback_lists{ + using generic_std_function = std::function; + + std::vector*> m_plugin_callback_lists{ // Plugin - (std::vector*)&m_on_present_cbs, - (std::vector*)&m_on_device_reset_cbs, + (std::vector*)&m_on_present_cbs, + (std::vector*)&m_on_device_reset_cbs, // VR Renderer - (std::vector*)&m_on_post_render_vr_framework_dx11_cbs, - (std::vector*)&m_on_post_render_vr_framework_dx12_cbs, + (std::vector*)&m_on_post_render_vr_framework_dx11_cbs, + (std::vector*)&m_on_post_render_vr_framework_dx12_cbs, // Windows CBs - (std::vector*)&m_on_message_cbs, - (std::vector*)&m_on_xinput_get_state_cbs, - (std::vector*)&m_on_xinput_set_state_cbs, + (std::vector*)&m_on_message_cbs, + (std::vector*)&m_on_xinput_get_state_cbs, + (std::vector*)&m_on_xinput_set_state_cbs, // SDK - (std::vector*)&m_on_pre_engine_tick_cbs, - (std::vector*)&m_on_post_engine_tick_cbs, - (std::vector*)&m_on_pre_slate_draw_window_render_thread_cbs, - (std::vector*)&m_on_post_slate_draw_window_render_thread_cbs, - (std::vector*)&m_on_pre_calculate_stereo_view_offset_cbs, - (std::vector*)&m_on_post_calculate_stereo_view_offset_cbs, - (std::vector*)&m_on_pre_viewport_client_draw_cbs, - (std::vector*)&m_on_post_viewport_client_draw_cbs + (std::vector*)&m_on_pre_engine_tick_cbs, + (std::vector*)&m_on_post_engine_tick_cbs, + (std::vector*)&m_on_pre_slate_draw_window_render_thread_cbs, + (std::vector*)&m_on_post_slate_draw_window_render_thread_cbs, + (std::vector*)&m_on_pre_calculate_stereo_view_offset_cbs, + (std::vector*)&m_on_post_calculate_stereo_view_offset_cbs, + (std::vector*)&m_on_pre_viewport_client_draw_cbs, + (std::vector*)&m_on_post_viewport_client_draw_cbs }; private: From 2c44167ff39f5ae36e92d6dc5ddc0b3cbf05d08c Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 5 Mar 2024 21:11:07 -0800 Subject: [PATCH 04/37] Lua: Stability improvements on script reset --- lua-api/lib/include/ScriptContext.hpp | 8 +++++--- lua-api/lib/src/ScriptContext.cpp | 27 +++++++++++++++++---------- src/mods/LuaLoader.cpp | 4 +++- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/lua-api/lib/include/ScriptContext.hpp b/lua-api/lib/include/ScriptContext.hpp index 16dbbe24..ebe18e07 100644 --- a/lua-api/lib/include/ScriptContext.hpp +++ b/lua-api/lib/include/ScriptContext.hpp @@ -12,11 +12,10 @@ namespace uevr { class ScriptContext : public std::enable_shared_from_this { public: static std::shared_ptr create(lua_State* l, UEVR_PluginInitializeParam* param = nullptr) { - return std::make_shared(l, param); + return std::shared_ptr(new ScriptContext(l, param)); } - ScriptContext(lua_State* l, UEVR_PluginInitializeParam* param = nullptr); - + ScriptContext() = delete; virtual ~ScriptContext(); int setup_bindings(); @@ -85,6 +84,9 @@ class ScriptContext : public std::enable_shared_from_this { } private: + // Private constructor to prevent direct instantiation + ScriptContext(lua_State* l, UEVR_PluginInitializeParam* param = nullptr); + std::vector m_callbacks_to_remove{}; sol::state_view m_lua; diff --git a/lua-api/lib/src/ScriptContext.cpp b/lua-api/lib/src/ScriptContext.cpp index 78e6604b..9a585115 100644 --- a/lua-api/lib/src/ScriptContext.cpp +++ b/lua-api/lib/src/ScriptContext.cpp @@ -11,11 +11,11 @@ namespace uevr { class ScriptContexts { public: - void add(std::shared_ptr ctx) { + void add(ScriptContext* ctx) { std::scoped_lock _{mtx}; // Check if the context is already in the list - for (auto& c : list) { + for (ScriptContext* c : list) { if (c == ctx) { return; } @@ -24,29 +24,36 @@ class ScriptContexts { list.push_back(ctx); } - void remove(std::shared_ptr ctx) { + void remove(ScriptContext* ctx) { std::scoped_lock _{mtx}; - list.erase(std::remove(list.begin(), list.end(), ctx), list.end()); + + ScriptContext::log("Removing context from list"); + std::erase_if(list, [ctx](ScriptContext* c) { + return c == ctx; + }); + ScriptContext::log(std::format("New context count: {}", list.size())); } template void for_each(T&& fn) { std::scoped_lock _{mtx}; for (auto& ctx : list) { - fn(ctx); + fn(ctx->shared_from_this()); } } private: - std::vector> list{}; + std::vector list{}; std::mutex mtx{}; } g_contexts{}; ScriptContext::ScriptContext(lua_State* l, UEVR_PluginInitializeParam* param) - : m_lua{l} + : m_lua{l} { std::scoped_lock _{m_mtx}; + g_contexts.add(this); + if (param != nullptr) { m_plugin_initialize_param = param; uevr::API::initialize(m_plugin_initialize_param); @@ -65,6 +72,7 @@ ScriptContext::ScriptContext(lua_State* l, UEVR_PluginInitializeParam* param) ScriptContext::~ScriptContext() { std::scoped_lock _{m_mtx}; + ScriptContext::log("ScriptContext destructor called"); // TODO: this probably does not support multiple states if (m_plugin_initialize_param != nullptr) { @@ -75,11 +83,12 @@ ScriptContext::~ScriptContext() { m_callbacks_to_remove.clear(); } - g_contexts.remove(shared_from_this()); + g_contexts.remove(this); } void ScriptContext::log(const std::string& message) { std::cout << "[LuaVR] " << message << std::endl; + API::get()->log_info("[LuaVR] %s", message.c_str()); } void ScriptContext::test_function() { @@ -149,8 +158,6 @@ void ScriptContext::setup_callback_bindings() { } int ScriptContext::setup_bindings() { - g_contexts.add(shared_from_this()); - m_lua.registry()["uevr_context"] = this; m_lua.set_function("test_function", ScriptContext::test_function); diff --git a/src/mods/LuaLoader.cpp b/src/mods/LuaLoader.cpp index 89e8f744..5aad714a 100644 --- a/src/mods/LuaLoader.cpp +++ b/src/mods/LuaLoader.cpp @@ -226,7 +226,7 @@ void LuaLoader::reset_scripts() { m_last_script_error.clear(); } - if (!m_states.empty()) { + if (m_main_state != nullptr) { /*auto& mods = g_framework->get_mods()->get_mods(); for (auto& mod : mods) { @@ -239,6 +239,8 @@ void LuaLoader::reset_scripts() { m_main_state.reset(); m_states.clear(); + spdlog::info("[LuaLoader] Destroyed all Lua states."); + m_main_state = std::make_shared(make_gc_data(), &g_plugin_initialize_param, true); m_states.insert(m_states.begin(), m_main_state); From 8fb6c3d01389672ec805398ed8cb4dc0820451f9 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 5 Mar 2024 21:13:47 -0800 Subject: [PATCH 05/37] Lua: Fix execute_command not working --- lua-api/lib/src/ScriptContext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua-api/lib/src/ScriptContext.cpp b/lua-api/lib/src/ScriptContext.cpp index 9a585115..d5f28f0f 100644 --- a/lua-api/lib/src/ScriptContext.cpp +++ b/lua-api/lib/src/ScriptContext.cpp @@ -450,7 +450,7 @@ int ScriptContext::setup_bindings() { "get_player_controller", &uevr::API::get_player_controller, "get_local_pawn", &uevr::API::get_local_pawn, "spawn_object", &uevr::API::spawn_object, - "execute_command", &uevr::API::execute_command, + "execute_command", [](uevr::API* api, std::string s) { api->execute_command(utility::widen(s).data()); }, "execute_command_ex", &uevr::API::execute_command_ex, "get_uobject_array", &uevr::API::get_uobject_array, "get_console_manager", &uevr::API::get_console_manager From c2994ca44b321b3fa6b884eeb38fe4c7bb5928fe Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 5 Mar 2024 21:25:36 -0800 Subject: [PATCH 06/37] Lua: improvement to UI layout --- src/mods/LuaLoader.cpp | 13 ++++++++----- src/mods/LuaLoader.hpp | 10 +++++++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/mods/LuaLoader.cpp b/src/mods/LuaLoader.cpp index 5aad714a..30ef9980 100644 --- a/src/mods/LuaLoader.cpp +++ b/src/mods/LuaLoader.cpp @@ -82,10 +82,8 @@ void LuaLoader::on_frame() { } } -void LuaLoader::on_draw_ui() { - ImGui::SetNextItemOpen(false, ImGuiCond_::ImGuiCond_Once); - - if (ImGui::CollapsingHeader(get_name().data())) { +void LuaLoader::on_draw_sidebar_entry(std::string_view in_entry) { + if (in_entry == "Main") { if (ImGui::Button("Run script")) { OPENFILENAME ofn{}; char file[260]{}; @@ -201,9 +199,12 @@ void LuaLoader::on_draw_ui() { } else { ImGui::Text("No scripts loaded."); } + + ImGui::TreePop(); } - if (ImGui::CollapsingHeader("Script Generated UI")) { + + if (in_entry == "Script UI") { std::scoped_lock _{m_access_mutex}; if (m_states.empty()) { @@ -213,6 +214,8 @@ void LuaLoader::on_draw_ui() { for (auto& state : m_states) { state->on_draw_ui(); } + + ImGui::TreePop(); } } diff --git a/src/mods/LuaLoader.hpp b/src/mods/LuaLoader.hpp index c938964a..b7ad5704 100644 --- a/src/mods/LuaLoader.hpp +++ b/src/mods/LuaLoader.hpp @@ -21,7 +21,15 @@ class LuaLoader : public Mod { std::string_view get_name() const override { return "LuaLoader"; } bool is_advanced_mod() const override { return true; } std::optional on_initialize_d3d_thread() override; - void on_draw_ui() override; + + std::vector get_sidebar_entries() override { + return { + {"Main", true}, + {"Script UI", true} + }; + } + + void on_draw_sidebar_entry(std::string_view in_entry); void on_frame() override; void on_config_load(const utility::Config& cfg, bool set_defaults) override; From bca07ecfbc6397c27b9af6937eb05dd658459e68 Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 15 Jun 2024 19:02:10 -0700 Subject: [PATCH 07/37] Lua: Some fixes, basic UObject property getters --- lua-api/lib/src/ScriptContext.cpp | 57 ++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/lua-api/lib/src/ScriptContext.cpp b/lua-api/lib/src/ScriptContext.cpp index d5f28f0f..ed46e2e8 100644 --- a/lua-api/lib/src/ScriptContext.cpp +++ b/lua-api/lib/src/ScriptContext.cpp @@ -345,12 +345,34 @@ int ScriptContext::setup_bindings() { ); m_lua.new_usertype("UEVR_UObject", + "get_address", [](uevr::API::UObject& self) { + return (uintptr_t)&self; + }, "static_class", &uevr::API::UObject::static_class, "get_fname", &uevr::API::UObject::get_fname, "get_full_name", &uevr::API::UObject::get_full_name, "is_a", &uevr::API::UObject::is_a, "get_class", &uevr::API::UObject::get_class, - "get_outer", &uevr::API::UObject::get_outer + "get_outer", &uevr::API::UObject::get_outer, + "get_bool_property", &uevr::API::UObject::get_bool_property, + "get_float_property", [](uevr::API::UObject& self, const std::wstring& name) { + return self.get_property(name); + }, + "get_double_property", [](uevr::API::UObject& self, const std::wstring& name) { + return self.get_property(name); + }, + "get_int_property", [](uevr::API::UObject& self, const std::wstring& name) { + return self.get_property(name); + }, + "get_uint_property", [](uevr::API::UObject& self, const std::wstring& name) { + return self.get_property(name); + }, + "get_fname_property", [](uevr::API::UObject& self, const std::wstring& name) { + return self.get_property(name); + }, + "get_uobject_property", [](uevr::API::UObject& self, const std::wstring& name) { + return self.get_property(name); + } ); m_lua.new_usertype("UEVR_UStruct", @@ -443,14 +465,40 @@ int ScriptContext::setup_bindings() { "get_item", &uevr::API::FUObjectArray::get_item ); + m_lua.new_usertype("UEVR_UObjectHook", + "activate", &uevr::API::UObjectHook::activate, + "exists", &uevr::API::UObjectHook::exists, + "is_disabled", &uevr::API::UObjectHook::is_disabled, + "set_disabled", &uevr::API::UObjectHook::set_disabled, + "get_first_object_by_class", [](uevr::API::UClass* c, sol::object allow_default_obj) { + bool allow_default = false; + if (allow_default_obj.is()) { + allow_default = allow_default_obj.as(); + } + return uevr::API::UObjectHook::get_first_object_by_class(c, allow_default); + }, + "get_objects_by_class", [](uevr::API::UClass* c, sol::object allow_default_obj) { + bool allow_default = false; + if (allow_default_obj.is()) { + allow_default = allow_default_obj.as(); + } + return uevr::API::UObjectHook::get_objects_by_class(c, allow_default); + }, + "get_or_add_motion_controller_state", &uevr::API::UObjectHook::get_or_add_motion_controller_state, + "get_motion_controller_state", &uevr::API::UObjectHook::get_motion_controller_state + ); + m_lua.new_usertype("UEVR_API", "sdk", &uevr::API::sdk, - "find_uobject", &uevr::API::find_uobject, + //"find_uobject", &uevr::API::find_uobject, + "find_uobject", [](uevr::API* api, const std::string& name) { + return api->find_uobject(utility::widen(name)); + }, "get_engine", &uevr::API::get_engine, "get_player_controller", &uevr::API::get_player_controller, "get_local_pawn", &uevr::API::get_local_pawn, "spawn_object", &uevr::API::spawn_object, - "execute_command", [](uevr::API* api, std::string s) { api->execute_command(utility::widen(s).data()); }, + "execute_command", [](uevr::API* api, const std::string& s) { api->execute_command(utility::widen(s).data()); }, "execute_command_ex", &uevr::API::execute_command_ex, "get_uobject_array", &uevr::API::get_uobject_array, "get_console_manager", &uevr::API::get_console_manager @@ -473,7 +521,8 @@ int ScriptContext::setup_bindings() { "IConsoleVariable", m_lua["UEVR_IConsoleVariable"], "IConsoleCommand", m_lua["UEVR_IConsoleCommand"], "FName", m_lua["UEVR_FName"], - "FUObjectArray", m_lua["UEVR_FUObjectArray"] + "FUObjectArray", m_lua["UEVR_FUObjectArray"], + "UObjectHook", m_lua["UEVR_UObjectHook"] ); out["plugin_callbacks"] = m_plugin_initialize_param->callbacks; From 4800848884e3ae52e8f1381e36521a3e26ce2511 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 16 Jun 2024 10:45:37 -0700 Subject: [PATCH 08/37] Lua: Add basic auto type-resolving get_property --- lua-api/lib/src/ScriptContext.cpp | 42 +++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/lua-api/lib/src/ScriptContext.cpp b/lua-api/lib/src/ScriptContext.cpp index ed46e2e8..7c3a0d41 100644 --- a/lua-api/lib/src/ScriptContext.cpp +++ b/lua-api/lib/src/ScriptContext.cpp @@ -372,6 +372,46 @@ int ScriptContext::setup_bindings() { }, "get_uobject_property", [](uevr::API::UObject& self, const std::wstring& name) { return self.get_property(name); + }, + "get_property", [](sol::this_state s, uevr::API::UObject& self, const std::wstring& name) -> sol::object { + const auto c = self.get_class(); + + if (c == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + const auto desc = c->find_property(name.c_str()); + + if (desc == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + const auto propc = desc->get_class(); + + if (propc == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + const auto name_hash = utility::hash(propc->get_fname()->to_string()); + + switch (name_hash) { + case L"BoolProperty"_fnv: + return sol::make_object(s, self.get_property(name)); + case L"FloatProperty"_fnv: + return sol::make_object(s, self.get_property(name)); + case L"DoubleProperty"_fnv: + return sol::make_object(s, self.get_property(name)); + case L"IntProperty"_fnv: + return sol::make_object(s, self.get_property(name)); + case L"UIntProperty"_fnv: + return sol::make_object(s, self.get_property(name)); + case L"NameProperty"_fnv: + return sol::make_object(s, self.get_property(name)); + case L"ObjectProperty"_fnv: + return sol::make_object(s, self.get_property(name)); + }; + + return sol::make_object(s, sol::lua_nil); } ); @@ -490,7 +530,6 @@ int ScriptContext::setup_bindings() { m_lua.new_usertype("UEVR_API", "sdk", &uevr::API::sdk, - //"find_uobject", &uevr::API::find_uobject, "find_uobject", [](uevr::API* api, const std::string& name) { return api->find_uobject(utility::widen(name)); }, @@ -499,7 +538,6 @@ int ScriptContext::setup_bindings() { "get_local_pawn", &uevr::API::get_local_pawn, "spawn_object", &uevr::API::spawn_object, "execute_command", [](uevr::API* api, const std::string& s) { api->execute_command(utility::widen(s).data()); }, - "execute_command_ex", &uevr::API::execute_command_ex, "get_uobject_array", &uevr::API::get_uobject_array, "get_console_manager", &uevr::API::get_console_manager ); From 62444ea53df0e6fc5b365e32695607dc32b5a90d Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 16 Jun 2024 12:11:55 -0700 Subject: [PATCH 09/37] Lua: Add support for TArray --- lua-api/lib/src/ScriptContext.cpp | 41 ++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/lua-api/lib/src/ScriptContext.cpp b/lua-api/lib/src/ScriptContext.cpp index 7c3a0d41..02c749a8 100644 --- a/lua-api/lib/src/ScriptContext.cpp +++ b/lua-api/lib/src/ScriptContext.cpp @@ -396,7 +396,7 @@ int ScriptContext::setup_bindings() { switch (name_hash) { case L"BoolProperty"_fnv: - return sol::make_object(s, self.get_property(name)); + return sol::make_object(s, self.get_bool_property(name)); case L"FloatProperty"_fnv: return sol::make_object(s, self.get_property(name)); case L"DoubleProperty"_fnv: @@ -404,11 +404,50 @@ int ScriptContext::setup_bindings() { case L"IntProperty"_fnv: return sol::make_object(s, self.get_property(name)); case L"UIntProperty"_fnv: + case L"UInt32Property"_fnv: return sol::make_object(s, self.get_property(name)); case L"NameProperty"_fnv: return sol::make_object(s, self.get_property(name)); case L"ObjectProperty"_fnv: return sol::make_object(s, self.get_property(name)); + case L"ArrayProperty"_fnv: + { + const auto inner_prop = ((uevr::API::FArrayProperty*)desc)->get_inner(); + + if (inner_prop == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + const auto inner_c = inner_prop->get_class(); + + if (inner_c == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + const auto inner_name_hash = utility::hash(inner_c->get_fname()->to_string()); + + switch (inner_name_hash) { + case "ObjectProperty"_fnv: + { + const auto& arr = self.get_property>(name); + + if (arr.data == nullptr || arr.count == 0) { + return sol::make_object(s, sol::lua_nil); + } + + auto lua_arr = std::vector{}; + + for (size_t i = 0; i < arr.count; ++i) { + lua_arr.push_back(arr.data[i]); + } + + return sol::make_object(s, lua_arr); + } + // TODO: Add support for other types + }; + + return sol::make_object(s, sol::lua_nil); + } }; return sol::make_object(s, sol::lua_nil); From 1dfa16613d9b63a19a7f230fc3f5dcabedf9090f Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 16 Jun 2024 12:17:33 -0700 Subject: [PATCH 10/37] Lua: Add example script --- lua-api/examples/hello_world.lua | 95 ++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 lua-api/examples/hello_world.lua diff --git a/lua-api/examples/hello_world.lua b/lua-api/examples/hello_world.lua new file mode 100644 index 00000000..c1e7e261 --- /dev/null +++ b/lua-api/examples/hello_world.lua @@ -0,0 +1,95 @@ +print("Initializing hello_world.lua") + +local api = uevr.api; +local uobjects = uevr.types.FUObjectArray.get() + +print("Printing first 5 UObjects") +for i=0, 5 do + local uobject = uobjects:get_object(i) + + if uobject ~= nil then + print(uobject:get_full_name()) + end +end + +local once = true +local last_world = nil +local last_level = nil + +uevr.sdk.callbacks.on_pre_engine_tick(function(engine, delta) + local game_engine_class = api:find_uobject("Class /Script/Engine.GameEngine") + local game_engine = UEVR_UObjectHook.get_first_object_by_class(game_engine_class) + + local viewport = game_engine:get_property("GameViewport") + if viewport == nil then + print("Viewport is nil") + return + end + local world = viewport:get_property("World") + + if world == nil then + print("World is nil") + return + end + + if world ~= last_world then + print("World changed") + end + + last_world = world + + local level = world:get_property("PersistentLevel") + + if level == nil then + print("Level is nil") + return + end + + if level ~= last_level then + print("Level changed") + print("Level name: " .. level:get_full_name()) + end + + last_level = level + + local game_instance = game_engine:get_property("GameInstance") + + if game_instance == nil then + print("GameInstance is nil") + return + end + + local local_players = game_instance:get_property("LocalPlayers") + + for i in ipairs(local_players) do + local player = local_players[i] + local player_controller = player:get_property("PlayerController") + local pawn = player_controller:get_property("Pawn") + + if pawn ~= nil then + print("Pawn: " .. pawn:get_full_name()) + end + + if player_controller ~= nil then + print("PlayerController: " .. player_controller:get_full_name()) + end + end + + print("Local players: " .. tostring(local_players)) + + if once then + print("executing stat fps") + uevr.api:execute_command("stat fps") + once = false + + print("executing stat unit") + uevr.api:execute_command("stat unit") + + print("GameEngine class: " .. game_engine_class:get_full_name()) + print("GameEngine object: " .. game_engine:get_full_name()) + end +end) + +uevr.sdk.callbacks.on_script_reset(function() + print("Resetting hello_world.lua") +end) \ No newline at end of file From 5c281ff92387f004cd405929aadf07bc088adfe1 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 16 Jun 2024 13:31:08 -0700 Subject: [PATCH 11/37] Lua: Add support for directly indexing properties --- lua-api/examples/hello_world.lua | 48 ++++----- lua-api/lib/src/ScriptContext.cpp | 160 ++++++++++++++++-------------- 2 files changed, 110 insertions(+), 98 deletions(-) diff --git a/lua-api/examples/hello_world.lua b/lua-api/examples/hello_world.lua index c1e7e261..d47d06d7 100644 --- a/lua-api/examples/hello_world.lua +++ b/lua-api/examples/hello_world.lua @@ -20,12 +20,12 @@ uevr.sdk.callbacks.on_pre_engine_tick(function(engine, delta) local game_engine_class = api:find_uobject("Class /Script/Engine.GameEngine") local game_engine = UEVR_UObjectHook.get_first_object_by_class(game_engine_class) - local viewport = game_engine:get_property("GameViewport") + local viewport = game_engine.GameViewport if viewport == nil then print("Viewport is nil") return end - local world = viewport:get_property("World") + local world = viewport.World if world == nil then print("World is nil") @@ -38,7 +38,7 @@ uevr.sdk.callbacks.on_pre_engine_tick(function(engine, delta) last_world = world - local level = world:get_property("PersistentLevel") + local level = world.PersistentLevel if level == nil then print("Level is nil") @@ -48,34 +48,34 @@ uevr.sdk.callbacks.on_pre_engine_tick(function(engine, delta) if level ~= last_level then print("Level changed") print("Level name: " .. level:get_full_name()) - end - - last_level = level - - local game_instance = game_engine:get_property("GameInstance") - - if game_instance == nil then - print("GameInstance is nil") - return - end - - local local_players = game_instance:get_property("LocalPlayers") - for i in ipairs(local_players) do - local player = local_players[i] - local player_controller = player:get_property("PlayerController") - local pawn = player_controller:get_property("Pawn") + local game_instance = game_engine.GameInstance - if pawn ~= nil then - print("Pawn: " .. pawn:get_full_name()) + if game_instance == nil then + print("GameInstance is nil") + return end - if player_controller ~= nil then - print("PlayerController: " .. player_controller:get_full_name()) + local local_players = game_instance.LocalPlayers + + for i in ipairs(local_players) do + local player = local_players[i] + local player_controller = player.PlayerController + local pawn = player_controller.Pawn + + if pawn ~= nil then + print("Pawn: " .. pawn:get_full_name()) + end + + if player_controller ~= nil then + print("PlayerController: " .. player_controller:get_full_name()) + end end + + print("Local players: " .. tostring(local_players)) end - print("Local players: " .. tostring(local_players)) + last_level = level if once then print("executing stat fps") diff --git a/lua-api/lib/src/ScriptContext.cpp b/lua-api/lib/src/ScriptContext.cpp index 02c749a8..7553b794 100644 --- a/lua-api/lib/src/ScriptContext.cpp +++ b/lua-api/lib/src/ScriptContext.cpp @@ -157,6 +157,86 @@ void ScriptContext::setup_callback_bindings() { ); } +sol::object prop_to_object(sol::this_state s, uevr::API::UObject& self, const std::wstring& name) { + const auto c = self.get_class(); + + if (c == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + const auto desc = c->find_property(name.c_str()); + + if (desc == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + const auto propc = desc->get_class(); + + if (propc == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + const auto name_hash = utility::hash(propc->get_fname()->to_string()); + + switch (name_hash) { + case L"BoolProperty"_fnv: + return sol::make_object(s, self.get_bool_property(name)); + case L"FloatProperty"_fnv: + return sol::make_object(s, self.get_property(name)); + case L"DoubleProperty"_fnv: + return sol::make_object(s, self.get_property(name)); + case L"IntProperty"_fnv: + return sol::make_object(s, self.get_property(name)); + case L"UIntProperty"_fnv: + case L"UInt32Property"_fnv: + return sol::make_object(s, self.get_property(name)); + case L"NameProperty"_fnv: + return sol::make_object(s, self.get_property(name)); + case L"ObjectProperty"_fnv: + return sol::make_object(s, self.get_property(name)); + case L"ArrayProperty"_fnv: + { + const auto inner_prop = ((uevr::API::FArrayProperty*)desc)->get_inner(); + + if (inner_prop == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + const auto inner_c = inner_prop->get_class(); + + if (inner_c == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + const auto inner_name_hash = utility::hash(inner_c->get_fname()->to_string()); + + switch (inner_name_hash) { + case "ObjectProperty"_fnv: + { + const auto& arr = self.get_property>(name); + + if (arr.data == nullptr || arr.count == 0) { + return sol::make_object(s, sol::lua_nil); + } + + auto lua_arr = std::vector{}; + + for (size_t i = 0; i < arr.count; ++i) { + lua_arr.push_back(arr.data[i]); + } + + return sol::make_object(s, lua_arr); + } + // TODO: Add support for other types + }; + + return sol::make_object(s, sol::lua_nil); + } + }; + + return sol::make_object(s, sol::lua_nil); +} + int ScriptContext::setup_bindings() { m_lua.registry()["uevr_context"] = this; @@ -374,83 +454,15 @@ int ScriptContext::setup_bindings() { return self.get_property(name); }, "get_property", [](sol::this_state s, uevr::API::UObject& self, const std::wstring& name) -> sol::object { - const auto c = self.get_class(); - - if (c == nullptr) { - return sol::make_object(s, sol::lua_nil); - } - - const auto desc = c->find_property(name.c_str()); - - if (desc == nullptr) { - return sol::make_object(s, sol::lua_nil); - } - - const auto propc = desc->get_class(); - - if (propc == nullptr) { - return sol::make_object(s, sol::lua_nil); - } - - const auto name_hash = utility::hash(propc->get_fname()->to_string()); - - switch (name_hash) { - case L"BoolProperty"_fnv: - return sol::make_object(s, self.get_bool_property(name)); - case L"FloatProperty"_fnv: - return sol::make_object(s, self.get_property(name)); - case L"DoubleProperty"_fnv: - return sol::make_object(s, self.get_property(name)); - case L"IntProperty"_fnv: - return sol::make_object(s, self.get_property(name)); - case L"UIntProperty"_fnv: - case L"UInt32Property"_fnv: - return sol::make_object(s, self.get_property(name)); - case L"NameProperty"_fnv: - return sol::make_object(s, self.get_property(name)); - case L"ObjectProperty"_fnv: - return sol::make_object(s, self.get_property(name)); - case L"ArrayProperty"_fnv: - { - const auto inner_prop = ((uevr::API::FArrayProperty*)desc)->get_inner(); - - if (inner_prop == nullptr) { - return sol::make_object(s, sol::lua_nil); - } - - const auto inner_c = inner_prop->get_class(); - - if (inner_c == nullptr) { - return sol::make_object(s, sol::lua_nil); - } - - const auto inner_name_hash = utility::hash(inner_c->get_fname()->to_string()); - - switch (inner_name_hash) { - case "ObjectProperty"_fnv: - { - const auto& arr = self.get_property>(name); - - if (arr.data == nullptr || arr.count == 0) { - return sol::make_object(s, sol::lua_nil); - } - - auto lua_arr = std::vector{}; - - for (size_t i = 0; i < arr.count; ++i) { - lua_arr.push_back(arr.data[i]); - } - - return sol::make_object(s, lua_arr); - } - // TODO: Add support for other types - }; - + return prop_to_object(s, self, name); + }, + sol::meta_function::index, [](sol::this_state s, uevr::API::UObject& self, sol::object index_obj) -> sol::object { + if (!index_obj.is()) { return sol::make_object(s, sol::lua_nil); } - }; - return sol::make_object(s, sol::lua_nil); + const auto name = utility::widen(index_obj.as()); + return prop_to_object(s, self, name); } ); From a88cab390ad0c5c075400b53b3239bcdda1b21d3 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 16 Jun 2024 13:47:10 -0700 Subject: [PATCH 12/37] Lua: Add support for setting properties --- lua-api/lib/src/ScriptContext.cpp | 60 +++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/lua-api/lib/src/ScriptContext.cpp b/lua-api/lib/src/ScriptContext.cpp index 7553b794..93e6463c 100644 --- a/lua-api/lib/src/ScriptContext.cpp +++ b/lua-api/lib/src/ScriptContext.cpp @@ -237,6 +237,57 @@ sol::object prop_to_object(sol::this_state s, uevr::API::UObject& self, const st return sol::make_object(s, sol::lua_nil); } +void set_property(sol::this_state s, uevr::API::UObject& self, const std::wstring& name, sol::object value) { + const auto c = self.get_class(); + + if (c == nullptr) { + return; + } + + const auto desc = c->find_property(name.c_str()); + + if (desc == nullptr) { + return; + } + + const auto propc = desc->get_class(); + + if (propc == nullptr) { + return; + } + + const auto name_hash = utility::hash(propc->get_fname()->to_string()); + + switch (name_hash) { + case L"BoolProperty"_fnv: + self.set_bool_property(name, value.as()); + return; + case L"FloatProperty"_fnv: + self.get_property(name) = value.as(); + return; + case L"DoubleProperty"_fnv: + self.get_property(name) = value.as(); + return; + case L"IntProperty"_fnv: + self.get_property(name) = value.as(); + return; + case L"UIntProperty"_fnv: + case L"UInt32Property"_fnv: + self.get_property(name) = value.as(); + return; + case L"NameProperty"_fnv: + //return sol::make_object(s, self.get_property(name)); + throw sol::error("Setting FName properties is not supported (yet)"); + case L"ObjectProperty"_fnv: + self.get_property(name) = value.as(); + return; + case L"ArrayProperty"_fnv: + throw sol::error("Setting array properties is not supported (yet)"); + }; + + // NONE +} + int ScriptContext::setup_bindings() { m_lua.registry()["uevr_context"] = this; @@ -456,6 +507,7 @@ int ScriptContext::setup_bindings() { "get_property", [](sol::this_state s, uevr::API::UObject& self, const std::wstring& name) -> sol::object { return prop_to_object(s, self, name); }, + "set_property", &set_property, sol::meta_function::index, [](sol::this_state s, uevr::API::UObject& self, sol::object index_obj) -> sol::object { if (!index_obj.is()) { return sol::make_object(s, sol::lua_nil); @@ -463,6 +515,14 @@ int ScriptContext::setup_bindings() { const auto name = utility::widen(index_obj.as()); return prop_to_object(s, self, name); + }, + sol::meta_function::new_index, [](sol::this_state s, uevr::API::UObject& self, sol::object index_obj, sol::object value) { + if (!index_obj.is()) { + return; + } + + const auto name = utility::widen(index_obj.as()); + set_property(s, self, name, value); } ); From 43859e55c8767e94e34537b6b9f88b8b4bebdce8 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 16 Jun 2024 21:15:51 -0700 Subject: [PATCH 13/37] Lua: Add basic function calling --- lua-api/examples/hello_world.lua | 25 ++++ lua-api/lib/src/ScriptContext.cpp | 239 ++++++++++++++++++++++++++---- 2 files changed, 232 insertions(+), 32 deletions(-) diff --git a/lua-api/examples/hello_world.lua b/lua-api/examples/hello_world.lua index d47d06d7..322cac13 100644 --- a/lua-api/examples/hello_world.lua +++ b/lua-api/examples/hello_world.lua @@ -65,6 +65,31 @@ uevr.sdk.callbacks.on_pre_engine_tick(function(engine, delta) if pawn ~= nil then print("Pawn: " .. pawn:get_full_name()) + --pawn.BaseEyeHeight = 0.0 + --pawn.bActorEnableCollision = not pawn.bActorEnableCollision + + local pawn_level = pawn:GetLevel() + + print("Pawn level: " .. pawn_level:get_full_name()) + + local is_actor_tick_enabled = pawn:IsActorTickEnabled() + print("IsActorTickEnabled: " .. tostring(is_actor_tick_enabled)) + + pawn:SetActorTickEnabled(not is_actor_tick_enabled) + is_actor_tick_enabled = pawn:IsActorTickEnabled() + print("New IsActorTickEnabled: " .. tostring(is_actor_tick_enabled)) + + pawn:SetActorTickEnabled(not is_actor_tick_enabled) -- resets it back to default + + local life_span = pawn:GetLifeSpan() + local og_life_span = life_span + print("LifeSpan: " .. tostring(life_span)) + + pawn:SetLifeSpan(10.0) + life_span = pawn:GetLifeSpan() + + print("New LifeSpan: " .. tostring(life_span)) + pawn:SetLifeSpan(og_life_span) -- resets it back to default end if player_controller ~= nil then diff --git a/lua-api/lib/src/ScriptContext.cpp b/lua-api/lib/src/ScriptContext.cpp index 93e6463c..dae13b02 100644 --- a/lua-api/lib/src/ScriptContext.cpp +++ b/lua-api/lib/src/ScriptContext.cpp @@ -157,19 +157,9 @@ void ScriptContext::setup_callback_bindings() { ); } -sol::object prop_to_object(sol::this_state s, uevr::API::UObject& self, const std::wstring& name) { - const auto c = self.get_class(); - - if (c == nullptr) { - return sol::make_object(s, sol::lua_nil); - } - - const auto desc = c->find_property(name.c_str()); - - if (desc == nullptr) { - return sol::make_object(s, sol::lua_nil); - } +sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API::UFunction* fn, sol::variadic_args args); +sol::object prop_to_object(sol::this_state s, void* self, uevr::API::FProperty* desc) { const auto propc = desc->get_class(); if (propc == nullptr) { @@ -177,23 +167,27 @@ sol::object prop_to_object(sol::this_state s, uevr::API::UObject& self, const st } const auto name_hash = utility::hash(propc->get_fname()->to_string()); + const auto offset = desc->get_offset(); switch (name_hash) { case L"BoolProperty"_fnv: - return sol::make_object(s, self.get_bool_property(name)); + { + const auto fbp = (uevr::API::FBoolProperty*)desc; + return sol::make_object(s, fbp->get_value_from_object(self)); + } case L"FloatProperty"_fnv: - return sol::make_object(s, self.get_property(name)); + return sol::make_object(s, *(float*)((uintptr_t)self + offset)); case L"DoubleProperty"_fnv: - return sol::make_object(s, self.get_property(name)); + return sol::make_object(s, *(double*)((uintptr_t)self + offset)); case L"IntProperty"_fnv: - return sol::make_object(s, self.get_property(name)); + return sol::make_object(s, *(int32_t*)((uintptr_t)self + offset)); case L"UIntProperty"_fnv: case L"UInt32Property"_fnv: - return sol::make_object(s, self.get_property(name)); + return sol::make_object(s, *(uint32_t*)((uintptr_t)self + offset)); case L"NameProperty"_fnv: - return sol::make_object(s, self.get_property(name)); + return sol::make_object(s, *(uevr::API::FName*)((uintptr_t)self + offset)); case L"ObjectProperty"_fnv: - return sol::make_object(s, self.get_property(name)); + return sol::make_object(s, *(uevr::API::UObject**)((uintptr_t)self + offset)); case L"ArrayProperty"_fnv: { const auto inner_prop = ((uevr::API::FArrayProperty*)desc)->get_inner(); @@ -213,7 +207,7 @@ sol::object prop_to_object(sol::this_state s, uevr::API::UObject& self, const st switch (inner_name_hash) { case "ObjectProperty"_fnv: { - const auto& arr = self.get_property>(name); + const auto& arr = *(uevr::API::TArray*)((uintptr_t)self + offset); if (arr.data == nullptr || arr.count == 0) { return sol::make_object(s, sol::lua_nil); @@ -237,8 +231,31 @@ sol::object prop_to_object(sol::this_state s, uevr::API::UObject& self, const st return sol::make_object(s, sol::lua_nil); } -void set_property(sol::this_state s, uevr::API::UObject& self, const std::wstring& name, sol::object value) { - const auto c = self.get_class(); +sol::object prop_to_object(sol::this_state s, uevr::API::UObject* self, const std::wstring& name) { + const auto c = self->get_class(); + + if (c == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + const auto desc = c->find_property(name.c_str()); + + if (desc == nullptr) { + if (auto fn = c->find_function(name.c_str()); fn != nullptr) { + /*return sol::make_object(s, [self, s, fn](sol::variadic_args args) { + return call_function(s, self, fn, args); + });*/ + return sol::make_object(s, fn); + } + + return sol::make_object(s, sol::lua_nil); + } + + return prop_to_object(s, self, desc); +} + +void set_property(sol::this_state s, uevr::API::UObject* self, const std::wstring& name, sol::object value) { + const auto c = self->get_class(); if (c == nullptr) { return; @@ -257,29 +274,34 @@ void set_property(sol::this_state s, uevr::API::UObject& self, const std::wstrin } const auto name_hash = utility::hash(propc->get_fname()->to_string()); + const auto offset = desc->get_offset(); switch (name_hash) { case L"BoolProperty"_fnv: - self.set_bool_property(name, value.as()); + { + const auto fbp = (uevr::API::FBoolProperty*)desc; + fbp->set_value_in_object(self, value.as()); return; + } case L"FloatProperty"_fnv: - self.get_property(name) = value.as(); + //self.get_property(name) = value.as(); + *(float*)((uintptr_t)self + offset) = value.as(); return; case L"DoubleProperty"_fnv: - self.get_property(name) = value.as(); + *(double*)((uintptr_t)self + offset) = value.as(); return; case L"IntProperty"_fnv: - self.get_property(name) = value.as(); - return; + *(int32_t*)((uintptr_t)self + offset) = value.as(); + return; case L"UIntProperty"_fnv: case L"UInt32Property"_fnv: - self.get_property(name) = value.as(); + *(uint32_t*)((uintptr_t)self + offset) = value.as(); return; case L"NameProperty"_fnv: //return sol::make_object(s, self.get_property(name)); throw sol::error("Setting FName properties is not supported (yet)"); case L"ObjectProperty"_fnv: - self.get_property(name) = value.as(); + *(uevr::API::UObject**)((uintptr_t)self + offset) = value.as(); return; case L"ArrayProperty"_fnv: throw sol::error("Setting array properties is not supported (yet)"); @@ -288,6 +310,152 @@ void set_property(sol::this_state s, uevr::API::UObject& self, const std::wstrin // NONE } +sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API::UFunction* fn, sol::variadic_args args) { + const auto fn_args = fn->get_child_properties(); + + if (fn_args == nullptr) { + fn->call(self, nullptr); + return sol::make_object(s, sol::lua_nil); + } + + std::vector params{}; + size_t args_index{0}; + + const auto ps = fn->get_properties_size(); + const auto ma = fn->get_min_alignment(); + + if (ma > 1) { + params.resize(((ps + ma - 1) / ma) * ma); + } else { + params.resize(ps); + } + + params.resize(fn->get_properties_size()); + + uevr::API::FProperty* return_prop{nullptr}; + bool ret_is_bool{false}; + + for (auto arg_desc = fn_args; arg_desc != nullptr; arg_desc = arg_desc->get_next()) { + const auto arg_c = arg_desc->get_class(); + + if (arg_c == nullptr) { + continue; + } + + const auto arg_c_name = arg_c->get_fname()->to_string(); + + if (!arg_c_name.contains(L"Property")) { + continue; + } + + const auto prop_desc = (uevr::API::FProperty*)arg_desc; + + if (!prop_desc->is_param()) { + continue; + } + + if (prop_desc->is_return_param()) { + return_prop = prop_desc; + + if (arg_c_name == L"BoolProperty") { + ret_is_bool = true; + } + + continue; + } + + const auto arg_hash = utility::hash(arg_c_name); + const auto offset = prop_desc->get_offset(); + + switch (arg_hash) { + case L"BoolProperty"_fnv: + { + const bool arg = args[args_index++].as(); + const auto fbp = (uevr::API::FBoolProperty*)prop_desc; + fbp->set_value_in_object(params.data(), arg); + continue; + } + case L"FloatProperty"_fnv: + { + const float arg = args[args_index++].as(); + *(float*)¶ms[offset] = arg; + continue; + } + case L"DoubleProperty"_fnv: + { + const double arg = args[args_index++].as(); + *(double*)¶ms[offset] = arg; + continue; + } + case L"IntProperty"_fnv: + case L"UIntProperty"_fnv: + case L"UInt32Property"_fnv: + { + const int32_t arg = args[args_index++].as(); + *(int32_t*)¶ms[offset] = arg; + continue; + } + case L"NameProperty"_fnv: + { + const auto arg_obj = args[args_index++]; + + if (arg_obj.is()) { + const auto arg = utility::widen(arg_obj.as()); + *(uevr::API::FName*)¶ms[offset] = uevr::API::FName{arg}; + } else if (arg_obj.is()) { + const auto arg = arg_obj.as(); + *(uevr::API::FName*)¶ms[offset] = uevr::API::FName{arg}; + } else if (arg_obj.is()) { + const auto arg = arg_obj.as(); + *(uevr::API::FName*)¶ms[offset] = arg; + } else { + throw sol::error("Invalid argument type for FName"); + } + + continue; + } + case L"ObjectProperty"_fnv: + { + const auto arg = args[args_index++].as(); + *(uevr::API::UObject**)¶ms[offset] = arg; + continue; + } + case L"ArrayProperty"_fnv: + // TODO + throw sol::error("Array properties are not supported (yet)"); + continue; + }; + } + + fn->call(self, params.data()); + + if (return_prop != nullptr) { + if (ret_is_bool) { + return sol::make_object(s, ((uevr::API::FBoolProperty*)return_prop)->get_value_from_propbase(params.data())); + } + + return prop_to_object(s, params.data(), return_prop); + } + + return sol::make_object(s, sol::lua_nil); +} + +sol::object call_function(sol::this_state s, uevr::API::UObject* self, const std::wstring& name, sol::variadic_args args) { + const auto c = self->get_class(); + + if (c == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + const auto fn = c->find_function(name.c_str()); + + if (fn == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + return call_function(s, self, fn, args); +} + int ScriptContext::setup_bindings() { m_lua.registry()["uevr_context"] = this; @@ -504,19 +672,23 @@ int ScriptContext::setup_bindings() { "get_uobject_property", [](uevr::API::UObject& self, const std::wstring& name) { return self.get_property(name); }, - "get_property", [](sol::this_state s, uevr::API::UObject& self, const std::wstring& name) -> sol::object { + "get_property", [](sol::this_state s, uevr::API::UObject* self, const std::wstring& name) -> sol::object { return prop_to_object(s, self, name); }, "set_property", &set_property, - sol::meta_function::index, [](sol::this_state s, uevr::API::UObject& self, sol::object index_obj) -> sol::object { + "call", [](sol::this_state s, uevr::API::UObject* self, const std::wstring& name, sol::variadic_args args) -> sol::object { + return call_function(s, self, name, args); + }, + sol::meta_function::index, [](sol::this_state s, uevr::API::UObject* self, sol::object index_obj) -> sol::object { if (!index_obj.is()) { return sol::make_object(s, sol::lua_nil); } const auto name = utility::widen(index_obj.as()); + return prop_to_object(s, self, name); }, - sol::meta_function::new_index, [](sol::this_state s, uevr::API::UObject& self, sol::object index_obj, sol::object value) { + sol::meta_function::new_index, [](sol::this_state s, uevr::API::UObject* self, sol::object index_obj, sol::object value) { if (!index_obj.is()) { return; } @@ -544,6 +716,9 @@ int ScriptContext::setup_bindings() { ); m_lua.new_usertype("UEVR_UFunction", + sol::meta_function::call, [](sol::this_state s, uevr::API::UFunction* fn, uevr::API::UObject* obj, sol::variadic_args args) -> sol::object { + return call_function(s, obj, fn, args); + }, sol::base_classes, sol::bases(), "static_class", &uevr::API::UFunction::static_class, "call", &uevr::API::UFunction::call, From 64e8e8c0378b55bc31904637ff6a197a819a1e51 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 16 Jun 2024 21:26:58 -0700 Subject: [PATCH 14/37] Lua: Fix incorrect return value --- lua-api/lib/src/ScriptContext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua-api/lib/src/ScriptContext.cpp b/lua-api/lib/src/ScriptContext.cpp index dae13b02..88ca0d42 100644 --- a/lua-api/lib/src/ScriptContext.cpp +++ b/lua-api/lib/src/ScriptContext.cpp @@ -431,7 +431,7 @@ sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API if (return_prop != nullptr) { if (ret_is_bool) { - return sol::make_object(s, ((uevr::API::FBoolProperty*)return_prop)->get_value_from_propbase(params.data())); + return sol::make_object(s, ((uevr::API::FBoolProperty*)return_prop)->get_value_from_object(params.data())); } return prop_to_object(s, params.data(), return_prop); From eb6f0d7ddd9a9b1734686c98e467989368df8f9f Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 17 Jun 2024 13:57:43 -0700 Subject: [PATCH 15/37] Lua: Throw error on set_property if it doesn't exist --- lua-api/lib/src/ScriptContext.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua-api/lib/src/ScriptContext.cpp b/lua-api/lib/src/ScriptContext.cpp index 88ca0d42..de631046 100644 --- a/lua-api/lib/src/ScriptContext.cpp +++ b/lua-api/lib/src/ScriptContext.cpp @@ -258,19 +258,19 @@ void set_property(sol::this_state s, uevr::API::UObject* self, const std::wstrin const auto c = self->get_class(); if (c == nullptr) { - return; + throw sol::error("[set_property] Object has no class"); } const auto desc = c->find_property(name.c_str()); if (desc == nullptr) { - return; + throw sol::error(std::format("[set_property] Property '{}' not found", utility::narrow(name))); } const auto propc = desc->get_class(); if (propc == nullptr) { - return; + throw sol::error(std::format("[set_property] Property '{}' has no class", utility::narrow(name))); } const auto name_hash = utility::hash(propc->get_fname()->to_string()); From 459eb1bf785062fd2f6689d791d75ce9b4d72b9b Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 19 Jun 2024 15:42:43 -0700 Subject: [PATCH 16/37] Lua: Add support for StrProperty parameters, ClassProperty --- lua-api/lib/src/ScriptContext.cpp | 72 ++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 7 deletions(-) diff --git a/lua-api/lib/src/ScriptContext.cpp b/lua-api/lib/src/ScriptContext.cpp index de631046..28c73c07 100644 --- a/lua-api/lib/src/ScriptContext.cpp +++ b/lua-api/lib/src/ScriptContext.cpp @@ -187,7 +187,17 @@ sol::object prop_to_object(sol::this_state s, void* self, uevr::API::FProperty* case L"NameProperty"_fnv: return sol::make_object(s, *(uevr::API::FName*)((uintptr_t)self + offset)); case L"ObjectProperty"_fnv: + if (*(uevr::API::UObject**)((uintptr_t)self + offset) == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + return sol::make_object(s, *(uevr::API::UObject**)((uintptr_t)self + offset)); + case L"ClassProperty"_fnv: + if (*(uevr::API::UClass**)((uintptr_t)self + offset) == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + return sol::make_object(s, *(uevr::API::UClass**)((uintptr_t)self + offset)); case L"ArrayProperty"_fnv: { const auto inner_prop = ((uevr::API::FArrayProperty*)desc)->get_inner(); @@ -303,6 +313,9 @@ void set_property(sol::this_state s, uevr::API::UObject* self, const std::wstrin case L"ObjectProperty"_fnv: *(uevr::API::UObject**)((uintptr_t)self + offset) = value.as(); return; + case L"ClassProperty"_fnv: + *(uevr::API::UClass**)((uintptr_t)self + offset) = value.as(); + return; case L"ArrayProperty"_fnv: throw sol::error("Setting array properties is not supported (yet)"); }; @@ -335,6 +348,9 @@ sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API uevr::API::FProperty* return_prop{nullptr}; bool ret_is_bool{false}; + //std::vector dynamic_data{}; + std::vector dynamic_strings{}; + for (auto arg_desc = fn_args; arg_desc != nullptr; arg_desc = arg_desc->get_next()) { const auto arg_c = arg_desc->get_class(); @@ -420,10 +436,45 @@ sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API *(uevr::API::UObject**)¶ms[offset] = arg; continue; } + case L"ClassProperty"_fnv: + { + const auto arg = args[args_index++].as(); + *(uevr::API::UClass**)¶ms[offset] = arg; + continue; + } case L"ArrayProperty"_fnv: // TODO throw sol::error("Array properties are not supported (yet)"); continue; + case L"StrProperty"_fnv: + { + const auto arg_obj = args[args_index++]; + using FString = API::TArray; + + auto& fstr = *(FString*)¶ms[offset]; + + if (arg_obj.is()) { + dynamic_strings.push_back(arg_obj.as()); + + fstr.count = dynamic_strings.back().size() + 1; + fstr.data = (wchar_t*)dynamic_strings.back().c_str(); + } else if (arg_obj.is()) { + dynamic_strings.push_back(utility::widen(arg_obj.as())); + + fstr.count = dynamic_strings.back().size() + 1; + fstr.data = (wchar_t*)dynamic_strings.back().c_str(); + } else if (arg_obj.is()) { + dynamic_strings.push_back(arg_obj.as()); + + fstr.count = dynamic_strings.back().size() + 1; + } else { + throw sol::error("Invalid argument type for FString"); + } + continue; + } + default: + //spdlog::warn("Unknown property type when calling '{}': {}", utility::narrow(fn->get_fname()->to_string()), utility::narrow(arg_c_name)); + API::get()->log_warn(std::format("Unknown property type when calling '{}': {}", utility::narrow(fn->get_fname()->to_string()), utility::narrow(arg_c_name)).c_str()); }; } @@ -704,7 +755,8 @@ int ScriptContext::setup_bindings() { "get_super_struct", &uevr::API::UStruct::get_super_struct, "get_super", &uevr::API::UStruct::get_super, "find_function", &uevr::API::UStruct::find_function, - "get_child_properties", &uevr::API::UStruct::get_child_properties + "get_child_properties", &uevr::API::UStruct::get_child_properties, + "get_properties_size", &uevr::API::UStruct::get_properties_size ); m_lua.new_usertype("UEVR_UClass", @@ -738,9 +790,15 @@ int ScriptContext::setup_bindings() { m_lua.new_usertype("UEVR_FConsoleManager", "get_console_objects", &uevr::API::FConsoleManager::get_console_objects, - "find_object", &uevr::API::FConsoleManager::find_object, - "find_variable", &uevr::API::FConsoleManager::find_variable, - "find_command", &uevr::API::FConsoleManager::find_command + "find_object", [](uevr::API::FConsoleManager& self, const std::wstring& name) { + return self.find_object(name); + }, + "find_variable", [](uevr::API::FConsoleManager& self, const std::wstring& name) { + return self.find_variable(name); + }, + "find_command", [](uevr::API::FConsoleManager& self, const std::wstring& name) { + return self.find_command(name); + } ); m_lua.new_usertype("UEVR_IConsoleObject", @@ -816,14 +874,14 @@ int ScriptContext::setup_bindings() { m_lua.new_usertype("UEVR_API", "sdk", &uevr::API::sdk, - "find_uobject", [](uevr::API* api, const std::string& name) { - return api->find_uobject(utility::widen(name)); + "find_uobject", [](uevr::API* api, const std::wstring& name) { + return api->find_uobject(name); }, "get_engine", &uevr::API::get_engine, "get_player_controller", &uevr::API::get_player_controller, "get_local_pawn", &uevr::API::get_local_pawn, "spawn_object", &uevr::API::spawn_object, - "execute_command", [](uevr::API* api, const std::string& s) { api->execute_command(utility::widen(s).data()); }, + "execute_command", [](uevr::API* api, const std::wstring& s) { api->execute_command(s.data()); }, "get_uobject_array", &uevr::API::get_uobject_array, "get_console_manager", &uevr::API::get_console_manager ); From 9ccb3740aa5c1a99f5faf30d9ce0ade79e74a855 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 19 Jun 2024 15:53:15 -0700 Subject: [PATCH 17/37] Plugins: Fix cases where object finder could return default objects --- src/mods/PluginLoader.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/mods/PluginLoader.cpp b/src/mods/PluginLoader.cpp index 6ecbbb09..35215e9e 100644 --- a/src/mods/PluginLoader.cpp +++ b/src/mods/PluginLoader.cpp @@ -483,12 +483,14 @@ namespace uobjecthook { return 0; } - const auto default_object = ((sdk::UClass*)klass)->get_class_default_object(); - unsigned int i = 0; for (auto&& obj : objects) { - if (!allow_default && obj == default_object) { - continue; + if (!allow_default) { + const auto c = obj->get_class(); + + if (c == nullptr || c->get_class_default_object() == obj) { + continue; + } } if (i < max_objects && out_objects != nullptr) { @@ -522,12 +524,14 @@ namespace uobjecthook { return (UEVR_UObjectHandle)*objects.begin(); } - const auto default_object = ((sdk::UClass*)klass)->get_class_default_object(); - for (auto&& obj : objects) { - if (obj != default_object) { - return (UEVR_UObjectHandle)obj; + const auto c = obj->get_class(); + + if (c == nullptr || c->get_class_default_object() == obj) { + continue; } + + return (UEVR_UObjectHandle)obj; } return (UEVR_UObjectHandle)nullptr; From 735ca89adfacf72da69cf9e9cefd21c9d9a29f44 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 19 Jun 2024 16:19:50 -0700 Subject: [PATCH 18/37] Lua: Return class if possible, add conversion functions --- lua-api/lib/src/ScriptContext.cpp | 50 ++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/lua-api/lib/src/ScriptContext.cpp b/lua-api/lib/src/ScriptContext.cpp index 28c73c07..53195066 100644 --- a/lua-api/lib/src/ScriptContext.cpp +++ b/lua-api/lib/src/ScriptContext.cpp @@ -702,6 +702,27 @@ int ScriptContext::setup_bindings() { "get_fname", &uevr::API::UObject::get_fname, "get_full_name", &uevr::API::UObject::get_full_name, "is_a", &uevr::API::UObject::is_a, + "as_class", [](uevr::API::UObject& self) -> uevr::API::UClass* { + if (auto c = self.dcast()) { + return c; + } + + return nullptr; + }, + "as_struct", [](uevr::API::UObject& self) -> uevr::API::UStruct* { + if (auto c = self.dcast()) { + return c; + } + + return nullptr; + }, + "as_function", [](uevr::API::UObject& self) -> uevr::API::UFunction* { + if (auto c = self.dcast()) { + return c; + } + + return nullptr; + }, "get_class", &uevr::API::UObject::get_class, "get_outer", &uevr::API::UObject::get_outer, "get_bool_property", &uevr::API::UObject::get_bool_property, @@ -854,12 +875,23 @@ int ScriptContext::setup_bindings() { "exists", &uevr::API::UObjectHook::exists, "is_disabled", &uevr::API::UObjectHook::is_disabled, "set_disabled", &uevr::API::UObjectHook::set_disabled, - "get_first_object_by_class", [](uevr::API::UClass* c, sol::object allow_default_obj) { + "get_first_object_by_class", [](sol::this_state s, uevr::API::UClass* c, sol::object allow_default_obj) -> sol::object { bool allow_default = false; if (allow_default_obj.is()) { allow_default = allow_default_obj.as(); } - return uevr::API::UObjectHook::get_first_object_by_class(c, allow_default); + + auto result = uevr::API::UObjectHook::get_first_object_by_class(c, allow_default); + + if (result == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + if (result->is_a(uevr::API::UClass::static_class())) { + return sol::make_object(s, (uevr::API::UClass*)result); + } + + return sol::make_object(s, result); }, "get_objects_by_class", [](uevr::API::UClass* c, sol::object allow_default_obj) { bool allow_default = false; @@ -874,8 +906,18 @@ int ScriptContext::setup_bindings() { m_lua.new_usertype("UEVR_API", "sdk", &uevr::API::sdk, - "find_uobject", [](uevr::API* api, const std::wstring& name) { - return api->find_uobject(name); + "find_uobject", [](sol::this_state s, uevr::API* api, const std::wstring& name) -> sol::object { + auto result = api->find_uobject(name); + + if (result == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + if (result->is_a(uevr::API::UClass::static_class())) { + return sol::make_object(s, (uevr::API::UClass*)result); + } + + return sol::make_object(s, result); }, "get_engine", &uevr::API::get_engine, "get_player_controller", &uevr::API::get_player_controller, From 4cedaa7a53f4b3ca430c3a74e4d33bb891267a07 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 25 Jun 2024 22:12:03 -0700 Subject: [PATCH 19/37] Plugins: Add FStructProperty functions --- include/uevr/API.h | 8 +++++++- include/uevr/API.hpp | 20 ++++++++++++++++++++ src/mods/PluginLoader.cpp | 10 +++++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/include/uevr/API.h b/include/uevr/API.h index 8d1f918c..48fa6e62 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 24 +#define UEVR_PLUGIN_VERSION_MINOR 25 #define UEVR_PLUGIN_VERSION_PATCH 0 #define UEVR_RENDERER_D3D11 0 @@ -78,6 +78,7 @@ DECLARE_UEVR_HANDLE(UEVR_FRHITexture2DHandle); DECLARE_UEVR_HANDLE(UEVR_UScriptStructHandle); DECLARE_UEVR_HANDLE(UEVR_FArrayPropertyHandle); DECLARE_UEVR_HANDLE(UEVR_FBoolPropertyHandle); +DECLARE_UEVR_HANDLE(UEVR_FStructPropertyHandle); /* OpenXR stuff */ DECLARE_UEVR_HANDLE(UEVR_XrInstance); @@ -411,6 +412,10 @@ typedef struct { void (*set_value_in_propbase)(UEVR_FBoolPropertyHandle prop, void* addr, bool value); } UEVR_FBoolPropertyFunctions; +typedef struct { + UEVR_UScriptStructHandle (*get_struct)(UEVR_FStructPropertyHandle prop); +} UEVR_FStructPropertyFunctions; + typedef struct { const UEVR_SDKFunctions* functions; const UEVR_SDKCallbacks* callbacks; @@ -432,6 +437,7 @@ typedef struct { const UEVR_UScriptStructFunctions* uscriptstruct; const UEVR_FArrayPropertyFunctions* farrayproperty; const UEVR_FBoolPropertyFunctions* fboolproperty; + const UEVR_FStructPropertyFunctions* fstructproperty; } UEVR_SDKData; DECLARE_UEVR_HANDLE(UEVR_IVRSystem); diff --git a/include/uevr/API.hpp b/include/uevr/API.hpp index a774c6fd..51f2750d 100644 --- a/include/uevr/API.hpp +++ b/include/uevr/API.hpp @@ -722,6 +722,26 @@ class API { } }; + struct FStructProperty : public FProperty { + inline UEVR_FStructPropertyHandle to_handle() { return (UEVR_FStructPropertyHandle)this; } + inline UEVR_FStructPropertyHandle to_handle() const { return (UEVR_FStructPropertyHandle)this; } + + UScriptStruct* get_struct() const { + static const auto fn = initialize()->get_struct; + return (UScriptStruct*)fn(to_handle()); + } + + private: + static inline const UEVR_FStructPropertyFunctions* s_functions{nullptr}; + static inline const UEVR_FStructPropertyFunctions* initialize() { + if (s_functions == nullptr) { + s_functions = API::get()->sdk()->fstructproperty; + } + + return s_functions; + } + }; + struct FFieldClass { inline UEVR_FFieldClassHandle to_handle() { return (UEVR_FFieldClassHandle)this; } inline UEVR_FFieldClassHandle to_handle() const { return (UEVR_FFieldClassHandle)this; } diff --git a/src/mods/PluginLoader.cpp b/src/mods/PluginLoader.cpp index 35215e9e..c6b96456 100644 --- a/src/mods/PluginLoader.cpp +++ b/src/mods/PluginLoader.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "pluginloader/FFakeStereoRenderingFunctions.hpp" #include "pluginloader/FRenderTargetPoolHook.hpp" @@ -842,6 +843,12 @@ UEVR_FBoolPropertyFunctions g_fbool_property_functions { } }; +UEVR_FStructPropertyFunctions g_fstruct_property_functions { + .get_struct = [](UEVR_FStructPropertyHandle prop) -> UEVR_UScriptStructHandle { + return (UEVR_UScriptStructHandle)((sdk::FStructProperty*)prop)->get_struct(); + } +}; + UEVR_SDKData g_sdk_data { &g_sdk_functions, &g_sdk_callbacks, @@ -862,7 +869,8 @@ UEVR_SDKData g_sdk_data { &uevr::frhitexture2d::functions, &uevr::uscriptstruct::functions, &g_farray_property_functions, - &g_fbool_property_functions + &g_fbool_property_functions, + &g_fstruct_property_functions }; namespace uevr { From 2891e9041eed91bcb998a5af31efc8b79c5a0e01 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 25 Jun 2024 22:30:12 -0700 Subject: [PATCH 20/37] Lua: Initial work on Vector types --- CMakeLists.txt | 3 ++ cmake.toml | 3 +- lua-api/lib/include/ScriptContext.hpp | 1 - lua-api/lib/include/datatypes/Vector.hpp | 18 +++++++ lua-api/lib/src/ScriptContext.cpp | 64 ++++++++++++++++++++++-- lua-api/lib/src/datatypes/Vector.cpp | 38 ++++++++++++++ 6 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 lua-api/lib/include/datatypes/Vector.hpp create mode 100644 lua-api/lib/src/datatypes/Vector.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c65282a..bd34f3b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -597,8 +597,10 @@ set(luavrlib_SOURCES "") list(APPEND luavrlib_SOURCES "lua-api/lib/src/ScriptContext.cpp" "lua-api/lib/src/ScriptState.cpp" + "lua-api/lib/src/datatypes/Vector.cpp" "lua-api/lib/include/ScriptContext.hpp" "lua-api/lib/include/ScriptState.hpp" + "lua-api/lib/include/datatypes/Vector.hpp" ) list(APPEND luavrlib_SOURCES @@ -633,6 +635,7 @@ target_link_libraries(luavrlib PUBLIC lua sol2 kananlib + glm ) unset(CMKR_TARGET) diff --git a/cmake.toml b/cmake.toml index 45a8f819..e4b4b1d0 100644 --- a/cmake.toml +++ b/cmake.toml @@ -202,7 +202,8 @@ headers = ["lua-api/lib/**.hpp", "lua-api/lib/**.h"] link-libraries = [ "lua", "sol2", - "kananlib" + "kananlib", + "glm" ] [target.LuaVR] diff --git a/lua-api/lib/include/ScriptContext.hpp b/lua-api/lib/include/ScriptContext.hpp index ebe18e07..9827a137 100644 --- a/lua-api/lib/include/ScriptContext.hpp +++ b/lua-api/lib/include/ScriptContext.hpp @@ -43,7 +43,6 @@ class ScriptContext : public std::enable_shared_from_this { } static void log(const std::string& message); - static void test_function(); template void add_callback(T1&& adder, T2&& cb) { diff --git a/lua-api/lib/include/datatypes/Vector.hpp b/lua-api/lib/include/datatypes/Vector.hpp new file mode 100644 index 00000000..40640e15 --- /dev/null +++ b/lua-api/lib/include/datatypes/Vector.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace lua::datatypes { + using Vector2f = glm::vec2; + using Vector2d = glm::dvec2; + using Vector3f = glm::vec3; + using Vector3d = glm::dvec3; + using Vector4f = glm::vec4; + using Vector4d = glm::dvec4; + + void bind_vectors(sol::state_view& lua); +} \ No newline at end of file diff --git a/lua-api/lib/src/ScriptContext.cpp b/lua-api/lib/src/ScriptContext.cpp index 53195066..f2658993 100644 --- a/lua-api/lib/src/ScriptContext.cpp +++ b/lua-api/lib/src/ScriptContext.cpp @@ -6,6 +6,8 @@ #include +#include "datatypes/Vector.hpp" + #include "ScriptContext.hpp" namespace uevr { @@ -91,10 +93,6 @@ void ScriptContext::log(const std::string& message) { API::get()->log_info("[LuaVR] %s", message.c_str()); } -void ScriptContext::test_function() { - log("Test function called!"); -} - void ScriptContext::setup_callback_bindings() { std::scoped_lock _{ m_mtx }; @@ -159,6 +157,31 @@ void ScriptContext::setup_callback_bindings() { sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API::UFunction* fn, sol::variadic_args args); +uevr::API::UScriptStruct* get_vector_struct() { + static auto vector_struct = []() { + const auto modern_class = uevr::API::get()->find_uobject(L"ScriptStruct /Script/CoreUObject.Vector"); + const auto old_class = modern_class == nullptr ? uevr::API::get()->find_uobject(L"ScriptStruct /Script/CoreUObject.Object.Vector") : nullptr; + + return modern_class != nullptr ? modern_class : old_class; + }(); + + return vector_struct; +} + +bool is_ue5() { + static auto cached_result = []() { + const auto c = get_vector_struct(); + + if (c == nullptr) { + return false; + } + + return c->get_struct_size() == sizeof(glm::dvec3); + }(); + + return cached_result; +} + sol::object prop_to_object(sol::this_state s, void* self, uevr::API::FProperty* desc) { const auto propc = desc->get_class(); @@ -198,6 +221,37 @@ sol::object prop_to_object(sol::this_state s, void* self, uevr::API::FProperty* } return sol::make_object(s, *(uevr::API::UClass**)((uintptr_t)self + offset)); + case L"StructProperty"_fnv: + { + const auto struct_data = (void*)((uintptr_t)self + offset); + const auto struct_desc = ((uevr::API::FStructProperty*)desc)->get_struct(); + + if (struct_desc == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + /*const auto struct_name_hash = utility::hash(struct_desc->get_fname()->to_string()); + + switch (struct_name_hash) { + case L"Vector"_fnv: + if (is_ue5()) { + return sol::make_object(s, (lua::datatypes::Vector3f*)struct_data); + } + + return sol::make_object(s, (lua::datatypes::Vector3f*)struct_data); + };*/ + + if (struct_desc == get_vector_struct()) { + if (is_ue5()) { + return sol::make_object(s, (lua::datatypes::Vector3d*)struct_data); + } + + return sol::make_object(s, (lua::datatypes::Vector3f*)struct_data); + } + + // TODO: Return a reflected struct + return sol::make_object(s, sol::lua_nil); + } case L"ArrayProperty"_fnv: { const auto inner_prop = ((uevr::API::FArrayProperty*)desc)->get_inner(); @@ -510,7 +564,7 @@ sol::object call_function(sol::this_state s, uevr::API::UObject* self, const std int ScriptContext::setup_bindings() { m_lua.registry()["uevr_context"] = this; - m_lua.set_function("test_function", ScriptContext::test_function); + lua::datatypes::bind_vectors(m_lua); m_lua.new_usertype("UEVR_PluginInitializeParam", "uevr_module", &UEVR_PluginInitializeParam::uevr_module, diff --git a/lua-api/lib/src/datatypes/Vector.cpp b/lua-api/lib/src/datatypes/Vector.cpp new file mode 100644 index 00000000..7ace25df --- /dev/null +++ b/lua-api/lib/src/datatypes/Vector.cpp @@ -0,0 +1,38 @@ +#define GLM_ENABLE_EXPERIMENTAL +#include +#include + +#include + +namespace lua::datatypes { + void bind_vectors(sol::state_view& lua) { + #define BIND_VECTOR3_LIKE(name, datatype) \ + lua.new_usertype(#name, \ + "clone", [](name& v) -> name { return v; }, \ + "x", &name::x, \ + "y", &name::y, \ + "z", &name::z, \ + "dot", [](name& v1, name& v2) { return glm::dot(v1, v2); }, \ + "cross", [](name& v1, name& v2) { return glm::cross(v1, v2); }, \ + "length", [](name& v) { return glm::length(v); }, \ + "normalize", [](name& v) { v = glm::normalize(v); }, \ + "normalized", [](name& v) { return glm::normalize(v); }, \ + "reflect", [](name& v, name& normal) { return glm::reflect(v, normal); }, \ + "refract", [](name& v, name& normal, datatype eta) { return glm::refract(v, normal, eta); }, \ + "lerp", [](name& v1, name& v2, datatype t) { return glm::lerp(v1, v2, t); }, \ + sol::meta_function::addition, [](name& lhs, name& rhs) { return lhs + rhs; }, \ + sol::meta_function::subtraction, [](name& lhs, name& rhs) { return lhs - rhs; }, \ + sol::meta_function::multiplication, [](name& lhs, datatype scalar) { return lhs * scalar; } + + #define BIND_VECTOR3_LIKE_END() \ + ); + + BIND_VECTOR3_LIKE(Vector3f, float), + sol::meta_function::construct, sol::constructors() + BIND_VECTOR3_LIKE_END(); + + BIND_VECTOR3_LIKE(Vector3d, double), + sol::meta_function::construct, sol::constructors() + BIND_VECTOR3_LIKE_END(); + } +} \ No newline at end of file From 0bbc7ae13e2b20437d5692929066baddfc01a5dd Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 25 Jun 2024 23:15:25 -0700 Subject: [PATCH 21/37] Lua: Fix Vector types returned from functions --- lua-api/lib/src/ScriptContext.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lua-api/lib/src/ScriptContext.cpp b/lua-api/lib/src/ScriptContext.cpp index f2658993..b156fc39 100644 --- a/lua-api/lib/src/ScriptContext.cpp +++ b/lua-api/lib/src/ScriptContext.cpp @@ -182,7 +182,7 @@ bool is_ue5() { return cached_result; } -sol::object prop_to_object(sol::this_state s, void* self, uevr::API::FProperty* desc) { +sol::object prop_to_object(sol::this_state s, void* self, uevr::API::FProperty* desc, bool is_self_temporary = false) { const auto propc = desc->get_class(); if (propc == nullptr) { @@ -243,10 +243,18 @@ sol::object prop_to_object(sol::this_state s, void* self, uevr::API::FProperty* if (struct_desc == get_vector_struct()) { if (is_ue5()) { - return sol::make_object(s, (lua::datatypes::Vector3d*)struct_data); + if (is_self_temporary) { + return sol::make_object(s, *(lua::datatypes::Vector3d*)struct_data); + } else { + return sol::make_object(s, (lua::datatypes::Vector3d*)struct_data); + } } - return sol::make_object(s, (lua::datatypes::Vector3f*)struct_data); + if (is_self_temporary) { + return sol::make_object(s, *(lua::datatypes::Vector3f*)struct_data); + } else { + return sol::make_object(s, (lua::datatypes::Vector3f*)struct_data); + } } // TODO: Return a reflected struct @@ -539,7 +547,7 @@ sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API return sol::make_object(s, ((uevr::API::FBoolProperty*)return_prop)->get_value_from_object(params.data())); } - return prop_to_object(s, params.data(), return_prop); + return prop_to_object(s, params.data(), return_prop, true); } return sol::make_object(s, sol::lua_nil); From 5286661bce369e47f1c4fad07aeed96d816b9835 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 26 Jun 2024 00:03:53 -0700 Subject: [PATCH 22/37] Lua: Initial support for passing Vector types to functions --- lua-api/lib/src/ScriptContext.cpp | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/lua-api/lib/src/ScriptContext.cpp b/lua-api/lib/src/ScriptContext.cpp index b156fc39..57d4b9b1 100644 --- a/lua-api/lib/src/ScriptContext.cpp +++ b/lua-api/lib/src/ScriptContext.cpp @@ -504,6 +504,39 @@ sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API *(uevr::API::UClass**)¶ms[offset] = arg; continue; } + case L"StructProperty"_fnv: + { + const auto arg_obj = args[args_index++]; + const auto struct_desc = ((uevr::API::FStructProperty*)prop_desc)->get_struct(); + + if (struct_desc == nullptr) { + throw sol::error("Struct property has no struct"); + } + + if (struct_desc == get_vector_struct()) { + if (arg_obj.is()) { + const auto arg = arg_obj.as(); + + if (is_ue5()) { + *(lua::datatypes::Vector3d*)¶ms[offset] = arg; + } else { + *(lua::datatypes::Vector3f*)¶ms[offset] = arg; + } + } else if (arg_obj.is()) { + const auto arg = arg_obj.as(); + + if (is_ue5()) { + *(lua::datatypes::Vector3d*)¶ms[offset] = arg; + } else { + *(lua::datatypes::Vector3f*)¶ms[offset] = arg; + } + } else { + throw sol::error("Invalid argument type for FVector"); + } + } + + continue; + } case L"ArrayProperty"_fnv: // TODO throw sol::error("Array properties are not supported (yet)"); From 329001f6284d1a7cfff136186df587a2673109e3 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 26 Jun 2024 00:53:02 -0700 Subject: [PATCH 23/37] Lua: Migrate utility functions to separate files --- CMakeLists.txt | 4 + lua-api/lib/include/ScriptUtility.hpp | 16 + .../lib/include/datatypes/StructObject.hpp | 15 + lua-api/lib/include/datatypes/Vector.hpp | 5 + lua-api/lib/src/ScriptContext.cpp | 460 +----------------- lua-api/lib/src/ScriptUtility.cpp | 457 +++++++++++++++++ lua-api/lib/src/datatypes/StructObject.cpp | 11 + lua-api/lib/src/datatypes/Vector.cpp | 4 - 8 files changed, 515 insertions(+), 457 deletions(-) create mode 100644 lua-api/lib/include/ScriptUtility.hpp create mode 100644 lua-api/lib/include/datatypes/StructObject.hpp create mode 100644 lua-api/lib/src/ScriptUtility.cpp create mode 100644 lua-api/lib/src/datatypes/StructObject.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bd34f3b0..6a4cb529 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -597,9 +597,13 @@ set(luavrlib_SOURCES "") list(APPEND luavrlib_SOURCES "lua-api/lib/src/ScriptContext.cpp" "lua-api/lib/src/ScriptState.cpp" + "lua-api/lib/src/ScriptUtility.cpp" + "lua-api/lib/src/datatypes/StructObject.cpp" "lua-api/lib/src/datatypes/Vector.cpp" "lua-api/lib/include/ScriptContext.hpp" "lua-api/lib/include/ScriptState.hpp" + "lua-api/lib/include/ScriptUtility.hpp" + "lua-api/lib/include/datatypes/StructObject.hpp" "lua-api/lib/include/datatypes/Vector.hpp" ) diff --git a/lua-api/lib/include/ScriptUtility.hpp b/lua-api/lib/include/ScriptUtility.hpp new file mode 100644 index 00000000..315c8e2d --- /dev/null +++ b/lua-api/lib/include/ScriptUtility.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +namespace lua::utility { + uevr::API::UScriptStruct* get_vector_struct(); + bool is_ue5(); + + sol::object prop_to_object(sol::this_state s, void* self, uevr::API::FProperty* desc, bool is_self_temporary = false); + sol::object prop_to_object(sol::this_state s, uevr::API::UObject* self, const std::wstring& name); + void set_property(sol::this_state s, uevr::API::UObject* self, const std::wstring& name, sol::object value); + + sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API::UFunction* fn, sol::variadic_args args); + sol::object call_function(sol::this_state s, uevr::API::UObject* self, const std::wstring& name, sol::variadic_args args); +} \ No newline at end of file diff --git a/lua-api/lib/include/datatypes/StructObject.hpp b/lua-api/lib/include/datatypes/StructObject.hpp new file mode 100644 index 00000000..1e6f3f27 --- /dev/null +++ b/lua-api/lib/include/datatypes/StructObject.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +namespace lua::datatypes { + struct StructObject { + StructObject(void* obj, uevr::API::UStruct* def) : object{ obj }, desc{ def } {} + + void* object{ nullptr }; + uevr::API::UStruct* desc{ nullptr }; + }; + + void bind_struct_object(sol::state_view& lua); +} \ No newline at end of file diff --git a/lua-api/lib/include/datatypes/Vector.hpp b/lua-api/lib/include/datatypes/Vector.hpp index 40640e15..30547df3 100644 --- a/lua-api/lib/include/datatypes/Vector.hpp +++ b/lua-api/lib/include/datatypes/Vector.hpp @@ -1,8 +1,13 @@ #pragma once +#define GLM_ENABLE_EXPERIMENTAL + #include #include #include +#include +#include + #include #include diff --git a/lua-api/lib/src/ScriptContext.cpp b/lua-api/lib/src/ScriptContext.cpp index 57d4b9b1..37097ccd 100644 --- a/lua-api/lib/src/ScriptContext.cpp +++ b/lua-api/lib/src/ScriptContext.cpp @@ -8,6 +8,7 @@ #include "datatypes/Vector.hpp" +#include "ScriptUtility.hpp" #include "ScriptContext.hpp" namespace uevr { @@ -155,453 +156,6 @@ void ScriptContext::setup_callback_bindings() { ); } -sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API::UFunction* fn, sol::variadic_args args); - -uevr::API::UScriptStruct* get_vector_struct() { - static auto vector_struct = []() { - const auto modern_class = uevr::API::get()->find_uobject(L"ScriptStruct /Script/CoreUObject.Vector"); - const auto old_class = modern_class == nullptr ? uevr::API::get()->find_uobject(L"ScriptStruct /Script/CoreUObject.Object.Vector") : nullptr; - - return modern_class != nullptr ? modern_class : old_class; - }(); - - return vector_struct; -} - -bool is_ue5() { - static auto cached_result = []() { - const auto c = get_vector_struct(); - - if (c == nullptr) { - return false; - } - - return c->get_struct_size() == sizeof(glm::dvec3); - }(); - - return cached_result; -} - -sol::object prop_to_object(sol::this_state s, void* self, uevr::API::FProperty* desc, bool is_self_temporary = false) { - const auto propc = desc->get_class(); - - if (propc == nullptr) { - return sol::make_object(s, sol::lua_nil); - } - - const auto name_hash = utility::hash(propc->get_fname()->to_string()); - const auto offset = desc->get_offset(); - - switch (name_hash) { - case L"BoolProperty"_fnv: - { - const auto fbp = (uevr::API::FBoolProperty*)desc; - return sol::make_object(s, fbp->get_value_from_object(self)); - } - case L"FloatProperty"_fnv: - return sol::make_object(s, *(float*)((uintptr_t)self + offset)); - case L"DoubleProperty"_fnv: - return sol::make_object(s, *(double*)((uintptr_t)self + offset)); - case L"IntProperty"_fnv: - return sol::make_object(s, *(int32_t*)((uintptr_t)self + offset)); - case L"UIntProperty"_fnv: - case L"UInt32Property"_fnv: - return sol::make_object(s, *(uint32_t*)((uintptr_t)self + offset)); - case L"NameProperty"_fnv: - return sol::make_object(s, *(uevr::API::FName*)((uintptr_t)self + offset)); - case L"ObjectProperty"_fnv: - if (*(uevr::API::UObject**)((uintptr_t)self + offset) == nullptr) { - return sol::make_object(s, sol::lua_nil); - } - - return sol::make_object(s, *(uevr::API::UObject**)((uintptr_t)self + offset)); - case L"ClassProperty"_fnv: - if (*(uevr::API::UClass**)((uintptr_t)self + offset) == nullptr) { - return sol::make_object(s, sol::lua_nil); - } - - return sol::make_object(s, *(uevr::API::UClass**)((uintptr_t)self + offset)); - case L"StructProperty"_fnv: - { - const auto struct_data = (void*)((uintptr_t)self + offset); - const auto struct_desc = ((uevr::API::FStructProperty*)desc)->get_struct(); - - if (struct_desc == nullptr) { - return sol::make_object(s, sol::lua_nil); - } - - /*const auto struct_name_hash = utility::hash(struct_desc->get_fname()->to_string()); - - switch (struct_name_hash) { - case L"Vector"_fnv: - if (is_ue5()) { - return sol::make_object(s, (lua::datatypes::Vector3f*)struct_data); - } - - return sol::make_object(s, (lua::datatypes::Vector3f*)struct_data); - };*/ - - if (struct_desc == get_vector_struct()) { - if (is_ue5()) { - if (is_self_temporary) { - return sol::make_object(s, *(lua::datatypes::Vector3d*)struct_data); - } else { - return sol::make_object(s, (lua::datatypes::Vector3d*)struct_data); - } - } - - if (is_self_temporary) { - return sol::make_object(s, *(lua::datatypes::Vector3f*)struct_data); - } else { - return sol::make_object(s, (lua::datatypes::Vector3f*)struct_data); - } - } - - // TODO: Return a reflected struct - return sol::make_object(s, sol::lua_nil); - } - case L"ArrayProperty"_fnv: - { - const auto inner_prop = ((uevr::API::FArrayProperty*)desc)->get_inner(); - - if (inner_prop == nullptr) { - return sol::make_object(s, sol::lua_nil); - } - - const auto inner_c = inner_prop->get_class(); - - if (inner_c == nullptr) { - return sol::make_object(s, sol::lua_nil); - } - - const auto inner_name_hash = utility::hash(inner_c->get_fname()->to_string()); - - switch (inner_name_hash) { - case "ObjectProperty"_fnv: - { - const auto& arr = *(uevr::API::TArray*)((uintptr_t)self + offset); - - if (arr.data == nullptr || arr.count == 0) { - return sol::make_object(s, sol::lua_nil); - } - - auto lua_arr = std::vector{}; - - for (size_t i = 0; i < arr.count; ++i) { - lua_arr.push_back(arr.data[i]); - } - - return sol::make_object(s, lua_arr); - } - // TODO: Add support for other types - }; - - return sol::make_object(s, sol::lua_nil); - } - }; - - return sol::make_object(s, sol::lua_nil); -} - -sol::object prop_to_object(sol::this_state s, uevr::API::UObject* self, const std::wstring& name) { - const auto c = self->get_class(); - - if (c == nullptr) { - return sol::make_object(s, sol::lua_nil); - } - - const auto desc = c->find_property(name.c_str()); - - if (desc == nullptr) { - if (auto fn = c->find_function(name.c_str()); fn != nullptr) { - /*return sol::make_object(s, [self, s, fn](sol::variadic_args args) { - return call_function(s, self, fn, args); - });*/ - return sol::make_object(s, fn); - } - - return sol::make_object(s, sol::lua_nil); - } - - return prop_to_object(s, self, desc); -} - -void set_property(sol::this_state s, uevr::API::UObject* self, const std::wstring& name, sol::object value) { - const auto c = self->get_class(); - - if (c == nullptr) { - throw sol::error("[set_property] Object has no class"); - } - - const auto desc = c->find_property(name.c_str()); - - if (desc == nullptr) { - throw sol::error(std::format("[set_property] Property '{}' not found", utility::narrow(name))); - } - - const auto propc = desc->get_class(); - - if (propc == nullptr) { - throw sol::error(std::format("[set_property] Property '{}' has no class", utility::narrow(name))); - } - - const auto name_hash = utility::hash(propc->get_fname()->to_string()); - const auto offset = desc->get_offset(); - - switch (name_hash) { - case L"BoolProperty"_fnv: - { - const auto fbp = (uevr::API::FBoolProperty*)desc; - fbp->set_value_in_object(self, value.as()); - return; - } - case L"FloatProperty"_fnv: - //self.get_property(name) = value.as(); - *(float*)((uintptr_t)self + offset) = value.as(); - return; - case L"DoubleProperty"_fnv: - *(double*)((uintptr_t)self + offset) = value.as(); - return; - case L"IntProperty"_fnv: - *(int32_t*)((uintptr_t)self + offset) = value.as(); - return; - case L"UIntProperty"_fnv: - case L"UInt32Property"_fnv: - *(uint32_t*)((uintptr_t)self + offset) = value.as(); - return; - case L"NameProperty"_fnv: - //return sol::make_object(s, self.get_property(name)); - throw sol::error("Setting FName properties is not supported (yet)"); - case L"ObjectProperty"_fnv: - *(uevr::API::UObject**)((uintptr_t)self + offset) = value.as(); - return; - case L"ClassProperty"_fnv: - *(uevr::API::UClass**)((uintptr_t)self + offset) = value.as(); - return; - case L"ArrayProperty"_fnv: - throw sol::error("Setting array properties is not supported (yet)"); - }; - - // NONE -} - -sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API::UFunction* fn, sol::variadic_args args) { - const auto fn_args = fn->get_child_properties(); - - if (fn_args == nullptr) { - fn->call(self, nullptr); - return sol::make_object(s, sol::lua_nil); - } - - std::vector params{}; - size_t args_index{0}; - - const auto ps = fn->get_properties_size(); - const auto ma = fn->get_min_alignment(); - - if (ma > 1) { - params.resize(((ps + ma - 1) / ma) * ma); - } else { - params.resize(ps); - } - - params.resize(fn->get_properties_size()); - - uevr::API::FProperty* return_prop{nullptr}; - bool ret_is_bool{false}; - - //std::vector dynamic_data{}; - std::vector dynamic_strings{}; - - for (auto arg_desc = fn_args; arg_desc != nullptr; arg_desc = arg_desc->get_next()) { - const auto arg_c = arg_desc->get_class(); - - if (arg_c == nullptr) { - continue; - } - - const auto arg_c_name = arg_c->get_fname()->to_string(); - - if (!arg_c_name.contains(L"Property")) { - continue; - } - - const auto prop_desc = (uevr::API::FProperty*)arg_desc; - - if (!prop_desc->is_param()) { - continue; - } - - if (prop_desc->is_return_param()) { - return_prop = prop_desc; - - if (arg_c_name == L"BoolProperty") { - ret_is_bool = true; - } - - continue; - } - - const auto arg_hash = utility::hash(arg_c_name); - const auto offset = prop_desc->get_offset(); - - switch (arg_hash) { - case L"BoolProperty"_fnv: - { - const bool arg = args[args_index++].as(); - const auto fbp = (uevr::API::FBoolProperty*)prop_desc; - fbp->set_value_in_object(params.data(), arg); - continue; - } - case L"FloatProperty"_fnv: - { - const float arg = args[args_index++].as(); - *(float*)¶ms[offset] = arg; - continue; - } - case L"DoubleProperty"_fnv: - { - const double arg = args[args_index++].as(); - *(double*)¶ms[offset] = arg; - continue; - } - case L"IntProperty"_fnv: - case L"UIntProperty"_fnv: - case L"UInt32Property"_fnv: - { - const int32_t arg = args[args_index++].as(); - *(int32_t*)¶ms[offset] = arg; - continue; - } - case L"NameProperty"_fnv: - { - const auto arg_obj = args[args_index++]; - - if (arg_obj.is()) { - const auto arg = utility::widen(arg_obj.as()); - *(uevr::API::FName*)¶ms[offset] = uevr::API::FName{arg}; - } else if (arg_obj.is()) { - const auto arg = arg_obj.as(); - *(uevr::API::FName*)¶ms[offset] = uevr::API::FName{arg}; - } else if (arg_obj.is()) { - const auto arg = arg_obj.as(); - *(uevr::API::FName*)¶ms[offset] = arg; - } else { - throw sol::error("Invalid argument type for FName"); - } - - continue; - } - case L"ObjectProperty"_fnv: - { - const auto arg = args[args_index++].as(); - *(uevr::API::UObject**)¶ms[offset] = arg; - continue; - } - case L"ClassProperty"_fnv: - { - const auto arg = args[args_index++].as(); - *(uevr::API::UClass**)¶ms[offset] = arg; - continue; - } - case L"StructProperty"_fnv: - { - const auto arg_obj = args[args_index++]; - const auto struct_desc = ((uevr::API::FStructProperty*)prop_desc)->get_struct(); - - if (struct_desc == nullptr) { - throw sol::error("Struct property has no struct"); - } - - if (struct_desc == get_vector_struct()) { - if (arg_obj.is()) { - const auto arg = arg_obj.as(); - - if (is_ue5()) { - *(lua::datatypes::Vector3d*)¶ms[offset] = arg; - } else { - *(lua::datatypes::Vector3f*)¶ms[offset] = arg; - } - } else if (arg_obj.is()) { - const auto arg = arg_obj.as(); - - if (is_ue5()) { - *(lua::datatypes::Vector3d*)¶ms[offset] = arg; - } else { - *(lua::datatypes::Vector3f*)¶ms[offset] = arg; - } - } else { - throw sol::error("Invalid argument type for FVector"); - } - } - - continue; - } - case L"ArrayProperty"_fnv: - // TODO - throw sol::error("Array properties are not supported (yet)"); - continue; - case L"StrProperty"_fnv: - { - const auto arg_obj = args[args_index++]; - using FString = API::TArray; - - auto& fstr = *(FString*)¶ms[offset]; - - if (arg_obj.is()) { - dynamic_strings.push_back(arg_obj.as()); - - fstr.count = dynamic_strings.back().size() + 1; - fstr.data = (wchar_t*)dynamic_strings.back().c_str(); - } else if (arg_obj.is()) { - dynamic_strings.push_back(utility::widen(arg_obj.as())); - - fstr.count = dynamic_strings.back().size() + 1; - fstr.data = (wchar_t*)dynamic_strings.back().c_str(); - } else if (arg_obj.is()) { - dynamic_strings.push_back(arg_obj.as()); - - fstr.count = dynamic_strings.back().size() + 1; - } else { - throw sol::error("Invalid argument type for FString"); - } - continue; - } - default: - //spdlog::warn("Unknown property type when calling '{}': {}", utility::narrow(fn->get_fname()->to_string()), utility::narrow(arg_c_name)); - API::get()->log_warn(std::format("Unknown property type when calling '{}': {}", utility::narrow(fn->get_fname()->to_string()), utility::narrow(arg_c_name)).c_str()); - }; - } - - fn->call(self, params.data()); - - if (return_prop != nullptr) { - if (ret_is_bool) { - return sol::make_object(s, ((uevr::API::FBoolProperty*)return_prop)->get_value_from_object(params.data())); - } - - return prop_to_object(s, params.data(), return_prop, true); - } - - return sol::make_object(s, sol::lua_nil); -} - -sol::object call_function(sol::this_state s, uevr::API::UObject* self, const std::wstring& name, sol::variadic_args args) { - const auto c = self->get_class(); - - if (c == nullptr) { - return sol::make_object(s, sol::lua_nil); - } - - const auto fn = c->find_function(name.c_str()); - - if (fn == nullptr) { - return sol::make_object(s, sol::lua_nil); - } - - return call_function(s, self, fn, args); -} - int ScriptContext::setup_bindings() { m_lua.registry()["uevr_context"] = this; @@ -840,11 +394,11 @@ int ScriptContext::setup_bindings() { return self.get_property(name); }, "get_property", [](sol::this_state s, uevr::API::UObject* self, const std::wstring& name) -> sol::object { - return prop_to_object(s, self, name); + return lua::utility::prop_to_object(s, self, name); }, - "set_property", &set_property, + "set_property", &lua::utility::set_property, "call", [](sol::this_state s, uevr::API::UObject* self, const std::wstring& name, sol::variadic_args args) -> sol::object { - return call_function(s, self, name, args); + return lua::utility::call_function(s, self, name, args); }, sol::meta_function::index, [](sol::this_state s, uevr::API::UObject* self, sol::object index_obj) -> sol::object { if (!index_obj.is()) { @@ -853,7 +407,7 @@ int ScriptContext::setup_bindings() { const auto name = utility::widen(index_obj.as()); - return prop_to_object(s, self, name); + return lua::utility::prop_to_object(s, self, name); }, sol::meta_function::new_index, [](sol::this_state s, uevr::API::UObject* self, sol::object index_obj, sol::object value) { if (!index_obj.is()) { @@ -861,7 +415,7 @@ int ScriptContext::setup_bindings() { } const auto name = utility::widen(index_obj.as()); - set_property(s, self, name, value); + lua::utility::set_property(s, self, name, value); } ); @@ -885,7 +439,7 @@ int ScriptContext::setup_bindings() { m_lua.new_usertype("UEVR_UFunction", sol::meta_function::call, [](sol::this_state s, uevr::API::UFunction* fn, uevr::API::UObject* obj, sol::variadic_args args) -> sol::object { - return call_function(s, obj, fn, args); + return lua::utility::call_function(s, obj, fn, args); }, sol::base_classes, sol::bases(), "static_class", &uevr::API::UFunction::static_class, diff --git a/lua-api/lib/src/ScriptUtility.cpp b/lua-api/lib/src/ScriptUtility.cpp new file mode 100644 index 00000000..03fb2f02 --- /dev/null +++ b/lua-api/lib/src/ScriptUtility.cpp @@ -0,0 +1,457 @@ +#include +#include +#include + +#include + +#include +#include + +namespace lua::utility { +sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API::UFunction* fn, sol::variadic_args args); + +uevr::API::UScriptStruct* get_vector_struct() { + static auto vector_struct = []() { + const auto modern_class = uevr::API::get()->find_uobject(L"ScriptStruct /Script/CoreUObject.Vector"); + const auto old_class = modern_class == nullptr ? uevr::API::get()->find_uobject(L"ScriptStruct /Script/CoreUObject.Object.Vector") : nullptr; + + return modern_class != nullptr ? modern_class : old_class; + }(); + + return vector_struct; +} + +bool is_ue5() { + static auto cached_result = []() { + const auto c = get_vector_struct(); + + if (c == nullptr) { + return false; + } + + return c->get_struct_size() == sizeof(glm::dvec3); + }(); + + return cached_result; +} + +sol::object prop_to_object(sol::this_state s, void* self, uevr::API::FProperty* desc, bool is_self_temporary) { + const auto propc = desc->get_class(); + + if (propc == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + const auto name_hash = ::utility::hash(propc->get_fname()->to_string()); + const auto offset = desc->get_offset(); + + switch (name_hash) { + case L"BoolProperty"_fnv: + { + const auto fbp = (uevr::API::FBoolProperty*)desc; + return sol::make_object(s, fbp->get_value_from_object(self)); + } + case L"FloatProperty"_fnv: + return sol::make_object(s, *(float*)((uintptr_t)self + offset)); + case L"DoubleProperty"_fnv: + return sol::make_object(s, *(double*)((uintptr_t)self + offset)); + case L"IntProperty"_fnv: + return sol::make_object(s, *(int32_t*)((uintptr_t)self + offset)); + case L"UIntProperty"_fnv: + case L"UInt32Property"_fnv: + return sol::make_object(s, *(uint32_t*)((uintptr_t)self + offset)); + case L"NameProperty"_fnv: + return sol::make_object(s, *(uevr::API::FName*)((uintptr_t)self + offset)); + case L"ObjectProperty"_fnv: + if (*(uevr::API::UObject**)((uintptr_t)self + offset) == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + return sol::make_object(s, *(uevr::API::UObject**)((uintptr_t)self + offset)); + case L"ClassProperty"_fnv: + if (*(uevr::API::UClass**)((uintptr_t)self + offset) == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + return sol::make_object(s, *(uevr::API::UClass**)((uintptr_t)self + offset)); + case L"StructProperty"_fnv: + { + const auto struct_data = (void*)((uintptr_t)self + offset); + const auto struct_desc = ((uevr::API::FStructProperty*)desc)->get_struct(); + + if (struct_desc == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + /*const auto struct_name_hash = utility::hash(struct_desc->get_fname()->to_string()); + + switch (struct_name_hash) { + case L"Vector"_fnv: + if (is_ue5()) { + return sol::make_object(s, (lua::datatypes::Vector3f*)struct_data); + } + + return sol::make_object(s, (lua::datatypes::Vector3f*)struct_data); + };*/ + + if (struct_desc == get_vector_struct()) { + if (is_ue5()) { + if (is_self_temporary) { + return sol::make_object(s, *(lua::datatypes::Vector3d*)struct_data); + } else { + return sol::make_object(s, (lua::datatypes::Vector3d*)struct_data); + } + } + + if (is_self_temporary) { + return sol::make_object(s, *(lua::datatypes::Vector3f*)struct_data); + } else { + return sol::make_object(s, (lua::datatypes::Vector3f*)struct_data); + } + } + + // TODO: Return a reflected struct + return sol::make_object(s, sol::lua_nil); + } + case L"ArrayProperty"_fnv: + { + const auto inner_prop = ((uevr::API::FArrayProperty*)desc)->get_inner(); + + if (inner_prop == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + const auto inner_c = inner_prop->get_class(); + + if (inner_c == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + const auto inner_name_hash = ::utility::hash(inner_c->get_fname()->to_string()); + + switch (inner_name_hash) { + case "ObjectProperty"_fnv: + { + const auto& arr = *(uevr::API::TArray*)((uintptr_t)self + offset); + + if (arr.data == nullptr || arr.count == 0) { + return sol::make_object(s, sol::lua_nil); + } + + auto lua_arr = std::vector{}; + + for (size_t i = 0; i < arr.count; ++i) { + lua_arr.push_back(arr.data[i]); + } + + return sol::make_object(s, lua_arr); + } + // TODO: Add support for other types + }; + + return sol::make_object(s, sol::lua_nil); + } + }; + + return sol::make_object(s, sol::lua_nil); +} + +sol::object prop_to_object(sol::this_state s, uevr::API::UObject* self, const std::wstring& name) { + const auto c = self->get_class(); + + if (c == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + const auto desc = c->find_property(name.c_str()); + + if (desc == nullptr) { + if (auto fn = c->find_function(name.c_str()); fn != nullptr) { + /*return sol::make_object(s, [self, s, fn](sol::variadic_args args) { + return call_function(s, self, fn, args); + });*/ + return sol::make_object(s, fn); + } + + return sol::make_object(s, sol::lua_nil); + } + + return prop_to_object(s, self, desc); +} + +void set_property(sol::this_state s, uevr::API::UObject* self, const std::wstring& name, sol::object value) { + const auto c = self->get_class(); + + if (c == nullptr) { + throw sol::error("[set_property] Object has no class"); + } + + const auto desc = c->find_property(name.c_str()); + + if (desc == nullptr) { + throw sol::error(std::format("[set_property] Property '{}' not found", ::utility::narrow(name))); + } + + const auto propc = desc->get_class(); + + if (propc == nullptr) { + throw sol::error(std::format("[set_property] Property '{}' has no class", ::utility::narrow(name))); + } + + const auto name_hash = ::utility::hash(propc->get_fname()->to_string()); + const auto offset = desc->get_offset(); + + switch (name_hash) { + case L"BoolProperty"_fnv: + { + const auto fbp = (uevr::API::FBoolProperty*)desc; + fbp->set_value_in_object(self, value.as()); + return; + } + case L"FloatProperty"_fnv: + //self.get_property(name) = value.as(); + *(float*)((uintptr_t)self + offset) = value.as(); + return; + case L"DoubleProperty"_fnv: + *(double*)((uintptr_t)self + offset) = value.as(); + return; + case L"IntProperty"_fnv: + *(int32_t*)((uintptr_t)self + offset) = value.as(); + return; + case L"UIntProperty"_fnv: + case L"UInt32Property"_fnv: + *(uint32_t*)((uintptr_t)self + offset) = value.as(); + return; + case L"NameProperty"_fnv: + //return sol::make_object(s, self.get_property(name)); + throw sol::error("Setting FName properties is not supported (yet)"); + case L"ObjectProperty"_fnv: + *(uevr::API::UObject**)((uintptr_t)self + offset) = value.as(); + return; + case L"ClassProperty"_fnv: + *(uevr::API::UClass**)((uintptr_t)self + offset) = value.as(); + return; + case L"ArrayProperty"_fnv: + throw sol::error("Setting array properties is not supported (yet)"); + }; + + // NONE +} + +sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API::UFunction* fn, sol::variadic_args args) { + const auto fn_args = fn->get_child_properties(); + + if (fn_args == nullptr) { + fn->call(self, nullptr); + return sol::make_object(s, sol::lua_nil); + } + + std::vector params{}; + size_t args_index{0}; + + const auto ps = fn->get_properties_size(); + const auto ma = fn->get_min_alignment(); + + if (ma > 1) { + params.resize(((ps + ma - 1) / ma) * ma); + } else { + params.resize(ps); + } + + params.resize(fn->get_properties_size()); + + uevr::API::FProperty* return_prop{nullptr}; + bool ret_is_bool{false}; + + //std::vector dynamic_data{}; + std::vector dynamic_strings{}; + + for (auto arg_desc = fn_args; arg_desc != nullptr; arg_desc = arg_desc->get_next()) { + const auto arg_c = arg_desc->get_class(); + + if (arg_c == nullptr) { + continue; + } + + const auto arg_c_name = arg_c->get_fname()->to_string(); + + if (!arg_c_name.contains(L"Property")) { + continue; + } + + const auto prop_desc = (uevr::API::FProperty*)arg_desc; + + if (!prop_desc->is_param()) { + continue; + } + + if (prop_desc->is_return_param()) { + return_prop = prop_desc; + + if (arg_c_name == L"BoolProperty") { + ret_is_bool = true; + } + + continue; + } + + const auto arg_hash = ::utility::hash(arg_c_name); + const auto offset = prop_desc->get_offset(); + + switch (arg_hash) { + case L"BoolProperty"_fnv: + { + const bool arg = args[args_index++].as(); + const auto fbp = (uevr::API::FBoolProperty*)prop_desc; + fbp->set_value_in_object(params.data(), arg); + continue; + } + case L"FloatProperty"_fnv: + { + const float arg = args[args_index++].as(); + *(float*)¶ms[offset] = arg; + continue; + } + case L"DoubleProperty"_fnv: + { + const double arg = args[args_index++].as(); + *(double*)¶ms[offset] = arg; + continue; + } + case L"IntProperty"_fnv: + case L"UIntProperty"_fnv: + case L"UInt32Property"_fnv: + { + const int32_t arg = args[args_index++].as(); + *(int32_t*)¶ms[offset] = arg; + continue; + } + case L"NameProperty"_fnv: + { + const auto arg_obj = args[args_index++]; + + if (arg_obj.is()) { + const auto arg = ::utility::widen(arg_obj.as()); + *(uevr::API::FName*)¶ms[offset] = uevr::API::FName{arg}; + } else if (arg_obj.is()) { + const auto arg = arg_obj.as(); + *(uevr::API::FName*)¶ms[offset] = uevr::API::FName{arg}; + } else if (arg_obj.is()) { + const auto arg = arg_obj.as(); + *(uevr::API::FName*)¶ms[offset] = arg; + } else { + throw sol::error("Invalid argument type for FName"); + } + + continue; + } + case L"ObjectProperty"_fnv: + { + const auto arg = args[args_index++].as(); + *(uevr::API::UObject**)¶ms[offset] = arg; + continue; + } + case L"ClassProperty"_fnv: + { + const auto arg = args[args_index++].as(); + *(uevr::API::UClass**)¶ms[offset] = arg; + continue; + } + case L"StructProperty"_fnv: + { + const auto arg_obj = args[args_index++]; + const auto struct_desc = ((uevr::API::FStructProperty*)prop_desc)->get_struct(); + + if (struct_desc == nullptr) { + throw sol::error("Struct property has no struct"); + } + + if (struct_desc == get_vector_struct()) { + if (arg_obj.is()) { + const auto arg = arg_obj.as(); + + if (is_ue5()) { + *(lua::datatypes::Vector3d*)¶ms[offset] = arg; + } else { + *(lua::datatypes::Vector3f*)¶ms[offset] = arg; + } + } else if (arg_obj.is()) { + const auto arg = arg_obj.as(); + + if (is_ue5()) { + *(lua::datatypes::Vector3d*)¶ms[offset] = arg; + } else { + *(lua::datatypes::Vector3f*)¶ms[offset] = arg; + } + } else { + throw sol::error("Invalid argument type for FVector"); + } + } + + continue; + } + case L"ArrayProperty"_fnv: + // TODO + throw sol::error("Array properties are not supported (yet)"); + continue; + case L"StrProperty"_fnv: + { + const auto arg_obj = args[args_index++]; + using FString = uevr::API::TArray; + + auto& fstr = *(FString*)¶ms[offset]; + + if (arg_obj.is()) { + dynamic_strings.push_back(arg_obj.as()); + + fstr.count = dynamic_strings.back().size() + 1; + fstr.data = (wchar_t*)dynamic_strings.back().c_str(); + } else if (arg_obj.is()) { + dynamic_strings.push_back(::utility::widen(arg_obj.as())); + + fstr.count = dynamic_strings.back().size() + 1; + fstr.data = (wchar_t*)dynamic_strings.back().c_str(); + } else if (arg_obj.is()) { + dynamic_strings.push_back(arg_obj.as()); + + fstr.count = dynamic_strings.back().size() + 1; + } else { + throw sol::error("Invalid argument type for FString"); + } + continue; + } + default: + //spdlog::warn("Unknown property type when calling '{}': {}", utility::narrow(fn->get_fname()->to_string()), utility::narrow(arg_c_name)); + uevr::API::get()->log_warn(std::format("Unknown property type when calling '{}': {}", ::utility::narrow(fn->get_fname()->to_string()), ::utility::narrow(arg_c_name)).c_str()); + }; + } + + fn->call(self, params.data()); + + if (return_prop != nullptr) { + if (ret_is_bool) { + return sol::make_object(s, ((uevr::API::FBoolProperty*)return_prop)->get_value_from_object(params.data())); + } + + return prop_to_object(s, params.data(), return_prop, true); + } + + return sol::make_object(s, sol::lua_nil); +} + +sol::object call_function(sol::this_state s, uevr::API::UObject* self, const std::wstring& name, sol::variadic_args args) { + const auto c = self->get_class(); + + if (c == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + const auto fn = c->find_function(name.c_str()); + + if (fn == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + return call_function(s, self, fn, args); +} +} \ No newline at end of file diff --git a/lua-api/lib/src/datatypes/StructObject.cpp b/lua-api/lib/src/datatypes/StructObject.cpp new file mode 100644 index 00000000..071a5e7c --- /dev/null +++ b/lua-api/lib/src/datatypes/StructObject.cpp @@ -0,0 +1,11 @@ +#include + +namespace lua::datatypes { + void bind_struct_object(sol::state_view& lua) { + lua.new_usertype("StructObject", + "get_address", [](StructObject& self) { return (uintptr_t)self.object; }, + "get_struct", [](StructObject& self) { return self.desc; } + + ); + } +} \ No newline at end of file diff --git a/lua-api/lib/src/datatypes/Vector.cpp b/lua-api/lib/src/datatypes/Vector.cpp index 7ace25df..97400295 100644 --- a/lua-api/lib/src/datatypes/Vector.cpp +++ b/lua-api/lib/src/datatypes/Vector.cpp @@ -1,7 +1,3 @@ -#define GLM_ENABLE_EXPERIMENTAL -#include -#include - #include namespace lua::datatypes { From eb7f4986a88764c8321025cf4608a522e467fdf1 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 26 Jun 2024 01:22:38 -0700 Subject: [PATCH 24/37] Lua: Add support for reading/modifying StructProperty properties --- lua-api/lib/include/ScriptUtility.hpp | 3 ++ lua-api/lib/src/ScriptContext.cpp | 6 +++- lua-api/lib/src/ScriptUtility.cpp | 32 ++++++++++++++-------- lua-api/lib/src/datatypes/StructObject.cpp | 27 +++++++++++++++++- 4 files changed, 55 insertions(+), 13 deletions(-) diff --git a/lua-api/lib/include/ScriptUtility.hpp b/lua-api/lib/include/ScriptUtility.hpp index 315c8e2d..ea03da92 100644 --- a/lua-api/lib/include/ScriptUtility.hpp +++ b/lua-api/lib/include/ScriptUtility.hpp @@ -8,7 +8,10 @@ namespace lua::utility { bool is_ue5(); sol::object prop_to_object(sol::this_state s, void* self, uevr::API::FProperty* desc, bool is_self_temporary = false); + sol::object prop_to_object(sol::this_state s, void* self, uevr::API::UStruct* desc, const std::wstring& name); sol::object prop_to_object(sol::this_state s, uevr::API::UObject* self, const std::wstring& name); + + void set_property(sol::this_state s, void* self, uevr::API::UStruct* desc, const std::wstring& name, sol::object value); void set_property(sol::this_state s, uevr::API::UObject* self, const std::wstring& name, sol::object value); sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API::UFunction* fn, sol::variadic_args args); diff --git a/lua-api/lib/src/ScriptContext.cpp b/lua-api/lib/src/ScriptContext.cpp index 37097ccd..e6a1e15e 100644 --- a/lua-api/lib/src/ScriptContext.cpp +++ b/lua-api/lib/src/ScriptContext.cpp @@ -7,6 +7,7 @@ #include #include "datatypes/Vector.hpp" +#include "datatypes/StructObject.hpp" #include "ScriptUtility.hpp" #include "ScriptContext.hpp" @@ -160,6 +161,7 @@ int ScriptContext::setup_bindings() { m_lua.registry()["uevr_context"] = this; lua::datatypes::bind_vectors(m_lua); + lua::datatypes::bind_struct_object(m_lua); m_lua.new_usertype("UEVR_PluginInitializeParam", "uevr_module", &UEVR_PluginInitializeParam::uevr_module, @@ -396,7 +398,9 @@ int ScriptContext::setup_bindings() { "get_property", [](sol::this_state s, uevr::API::UObject* self, const std::wstring& name) -> sol::object { return lua::utility::prop_to_object(s, self, name); }, - "set_property", &lua::utility::set_property, + "set_property", [](sol::this_state s, uevr::API::UObject* self, const std::wstring& name, sol::object value) { + lua::utility::set_property(s, self, name, value); + }, "call", [](sol::this_state s, uevr::API::UObject* self, const std::wstring& name, sol::variadic_args args) -> sol::object { return lua::utility::call_function(s, self, name, args); }, diff --git a/lua-api/lib/src/ScriptUtility.cpp b/lua-api/lib/src/ScriptUtility.cpp index 03fb2f02..9d9c9ed2 100644 --- a/lua-api/lib/src/ScriptUtility.cpp +++ b/lua-api/lib/src/ScriptUtility.cpp @@ -5,6 +5,7 @@ #include #include +#include #include namespace lua::utility { @@ -110,8 +111,9 @@ sol::object prop_to_object(sol::this_state s, void* self, uevr::API::FProperty* } } - // TODO: Return a reflected struct - return sol::make_object(s, sol::lua_nil); + auto struct_object = lua::datatypes::StructObject{struct_data, struct_desc}; + + return sol::make_object(s, struct_object); } case L"ArrayProperty"_fnv: { @@ -156,13 +158,7 @@ sol::object prop_to_object(sol::this_state s, void* self, uevr::API::FProperty* return sol::make_object(s, sol::lua_nil); } -sol::object prop_to_object(sol::this_state s, uevr::API::UObject* self, const std::wstring& name) { - const auto c = self->get_class(); - - if (c == nullptr) { - return sol::make_object(s, sol::lua_nil); - } - +sol::object prop_to_object(sol::this_state s, void* self, uevr::API::UStruct* c, const std::wstring& name) { const auto desc = c->find_property(name.c_str()); if (desc == nullptr) { @@ -179,13 +175,17 @@ sol::object prop_to_object(sol::this_state s, uevr::API::UObject* self, const st return prop_to_object(s, self, desc); } -void set_property(sol::this_state s, uevr::API::UObject* self, const std::wstring& name, sol::object value) { +sol::object prop_to_object(sol::this_state s, uevr::API::UObject* self, const std::wstring& name) { const auto c = self->get_class(); if (c == nullptr) { - throw sol::error("[set_property] Object has no class"); + return sol::make_object(s, sol::lua_nil); } + return prop_to_object(s, self, c, name); +} + +void set_property(sol::this_state s, void* self, uevr::API::UStruct* c, const std::wstring& name, sol::object value) { const auto desc = c->find_property(name.c_str()); if (desc == nullptr) { @@ -238,6 +238,16 @@ void set_property(sol::this_state s, uevr::API::UObject* self, const std::wstrin // NONE } +void set_property(sol::this_state s, uevr::API::UObject* self, const std::wstring& name, sol::object value) { + const auto c = self->get_class(); + + if (c == nullptr) { + throw sol::error("[set_property] Object has no class"); + } + + set_property(s, self, c, name, value); +} + sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API::UFunction* fn, sol::variadic_args args) { const auto fn_args = fn->get_child_properties(); diff --git a/lua-api/lib/src/datatypes/StructObject.cpp b/lua-api/lib/src/datatypes/StructObject.cpp index 071a5e7c..8bb7fb26 100644 --- a/lua-api/lib/src/datatypes/StructObject.cpp +++ b/lua-api/lib/src/datatypes/StructObject.cpp @@ -1,11 +1,36 @@ +#include + +#include #include namespace lua::datatypes { void bind_struct_object(sol::state_view& lua) { lua.new_usertype("StructObject", "get_address", [](StructObject& self) { return (uintptr_t)self.object; }, - "get_struct", [](StructObject& self) { return self.desc; } + "get_struct", [](StructObject& self) { return self.desc; }, + "get_property", [](sol::this_state s, StructObject* self, const std::wstring& name) -> sol::object { + return lua::utility::prop_to_object(s, self->object, self->desc, name); + }, + "set_property", [](sol::this_state s, StructObject* self, const std::wstring& name, sol::object value) { + lua::utility::set_property(s, self->object, self->desc, name, value); + }, + sol::meta_function::index, [](sol::this_state s, StructObject* self, sol::object index_obj) -> sol::object { + if (!index_obj.is()) { + return sol::make_object(s, sol::lua_nil); + } + + const auto name = ::utility::widen(index_obj.as()); + + return lua::utility::prop_to_object(s, self->object, self->desc, name); + }, + sol::meta_function::new_index, [](sol::this_state s, StructObject* self, sol::object index_obj, sol::object value) { + if (!index_obj.is()) { + return; + } + const auto name = ::utility::widen(index_obj.as()); + lua::utility::set_property(s, self->object, self->desc, name, value); + } ); } } \ No newline at end of file From ff35a24837cd53b0af0085a594e5ceb95664c5b2 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 26 Jun 2024 01:35:20 -0700 Subject: [PATCH 25/37] Lua: Add support for passing StructObjects to functions --- lua-api/lib/src/ScriptUtility.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lua-api/lib/src/ScriptUtility.cpp b/lua-api/lib/src/ScriptUtility.cpp index 9d9c9ed2..fcdc1440 100644 --- a/lua-api/lib/src/ScriptUtility.cpp +++ b/lua-api/lib/src/ScriptUtility.cpp @@ -376,7 +376,19 @@ sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API throw sol::error("Struct property has no struct"); } - if (struct_desc == get_vector_struct()) { + if (arg_obj.is()) { + const auto arg = arg_obj.as(); + + if (arg.desc != struct_desc) { + if (arg.desc != nullptr) { + throw sol::error(std::format("Invalid argument type for function '{}', expected '{}', got '{}'", ::utility::narrow(fn->get_fname()->to_string()), ::utility::narrow(struct_desc->get_fname()->to_string()), ::utility::narrow(arg.desc->get_fname()->to_string()))); + } else { + throw sol::error(std::format("Invalid argument type for function '{}', expected '{}', got 'nil'", ::utility::narrow(fn->get_fname()->to_string()), ::utility::narrow(struct_desc->get_fname()->to_string()))); + } + } + + memcpy(¶ms[offset], arg.object, struct_desc->get_struct_size()); + } else if (struct_desc == get_vector_struct()) { if (arg_obj.is()) { const auto arg = arg_obj.as(); @@ -396,6 +408,8 @@ sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API } else { throw sol::error("Invalid argument type for FVector"); } + } else { + throw sol::error("Invalid argument type for struct property"); } continue; From 525b785f08264ccf2af2f3b618229382ac7d2a0b Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 26 Jun 2024 01:37:59 -0700 Subject: [PATCH 26/37] Lua: update example script with more tests --- lua-api/examples/hello_world.lua | 79 +++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/lua-api/examples/hello_world.lua b/lua-api/examples/hello_world.lua index 322cac13..67a6c982 100644 --- a/lua-api/examples/hello_world.lua +++ b/lua-api/examples/hello_world.lua @@ -1,5 +1,7 @@ print("Initializing hello_world.lua") +UEVR_UObjectHook.activate() + local api = uevr.api; local uobjects = uevr.types.FUObjectArray.get() @@ -16,7 +18,24 @@ local once = true local last_world = nil local last_level = nil +uevr.sdk.callbacks.on_post_engine_tick(function(engine, delta) + +end) + +local spawn_once = true + uevr.sdk.callbacks.on_pre_engine_tick(function(engine, delta) + --[[if spawn_once then + local cheat_manager_c = api:find_uobject("Class /Script/Engine.CheatManager") + local cheat_manager = UEVR_UObjectHook.get_first_object_by_class(cheat_manager_c) + + print(tostring(cheat_manager_c)) + + cheat_manager:Summon("Something_C") + + spawn_once = false + end]] + local game_engine_class = api:find_uobject("Class /Script/Engine.GameEngine") local game_engine = UEVR_UObjectHook.get_first_object_by_class(game_engine_class) @@ -68,9 +87,61 @@ uevr.sdk.callbacks.on_pre_engine_tick(function(engine, delta) --pawn.BaseEyeHeight = 0.0 --pawn.bActorEnableCollision = not pawn.bActorEnableCollision - local pawn_level = pawn:GetLevel() + local actor_component_c = api:find_uobject("Class /Script/Engine.ActorComponent"); + print("actor_component_c class: " .. tostring(actor_component_c)) + local test_component = pawn:GetComponentByClass(actor_component_c) + + print("TestComponent: " .. tostring(test_component)) + + local controller = pawn.Controller + + if controller ~= nil then + print("Controller: " .. controller:get_full_name()) + + local velocity = controller:GetVelocity() + print("Velocity: " .. tostring(velocity.x) .. ", " .. tostring(velocity.y) .. ", " .. tostring(velocity.z)) + + local test = Vector3d.new(1.337, 1.0, 1.0) + print("Test: " .. tostring(test.x) .. ", " .. tostring(test.y) .. ", " .. tostring(test.z)) - print("Pawn level: " .. pawn_level:get_full_name()) + controller:SetActorScale3D(Vector3d.new(1.337, 1.0, 1.0)) + + local actor_scale_3d = controller:GetActorScale3D() + print("ActorScale3D: " .. tostring(actor_scale_3d.x) .. ", " .. tostring(actor_scale_3d.y) .. ", " .. tostring(actor_scale_3d.z)) + + + local control_rotation = controller:GetControlRotation() + + print("ControlRotation: " .. tostring(control_rotation.Pitch) .. ", " .. tostring(control_rotation.Yaw) .. ", " .. tostring(control_rotation.Roll)) + control_rotation.Pitch = 1.337 + + controller:SetControlRotation(control_rotation) + control_rotation = controller:GetControlRotation() + + print("New ControlRotation: " .. tostring(control_rotation.Pitch) .. ", " .. tostring(control_rotation.Yaw) .. ", " .. tostring(control_rotation.Roll)) + end + + local primary_actor_tick = pawn.PrimaryActorTick + + if primary_actor_tick ~= nil then + print("PrimaryActorTick: " .. tostring(primary_actor_tick)) + + -- Print various properties, this is testing of StructProperty as PrimaryActorTick is a struct + local tick_interval = primary_actor_tick.TickInterval + print("TickInterval: " .. tostring(tick_interval)) + + print("bAllowTickOnDedicatedServer: " .. tostring(primary_actor_tick.bAllowTickOnDedicatedServer)) + print("bCanEverTick: " .. tostring(primary_actor_tick.bCanEverTick)) + print("bStartWithTickEnabled: " .. tostring(primary_actor_tick.bStartWithTickEnabled)) + print("bTickEvenWhenPaused: " .. tostring(primary_actor_tick.bTickEvenWhenPaused)) + else + print("PrimaryActorTick is nil") + end + + local control_input_vector = pawn.ControlInputVector + pawn.ControlInputVector.x = 1.337 + + print("ControlInputVector: " .. tostring(control_input_vector.x) .. ", " .. tostring(control_input_vector.y) .. ", " .. tostring(control_input_vector.z)) local is_actor_tick_enabled = pawn:IsActorTickEnabled() print("IsActorTickEnabled: " .. tostring(is_actor_tick_enabled)) @@ -90,6 +161,10 @@ uevr.sdk.callbacks.on_pre_engine_tick(function(engine, delta) print("New LifeSpan: " .. tostring(life_span)) pawn:SetLifeSpan(og_life_span) -- resets it back to default + + local net_driver_name = pawn.NetDriverName:to_string() + + print("NetDriverName: " .. net_driver_name) end if player_controller ~= nil then From e20ba575efc7eb10735e7a46703180e102b19281 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 26 Jun 2024 03:00:53 -0700 Subject: [PATCH 27/37] Lua: Add StructObject.new(UStruct*) --- .../lib/include/datatypes/StructObject.hpp | 7 ++++ lua-api/lib/src/datatypes/StructObject.cpp | 42 ++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/lua-api/lib/include/datatypes/StructObject.hpp b/lua-api/lib/include/datatypes/StructObject.hpp index 1e6f3f27..e499e9e3 100644 --- a/lua-api/lib/include/datatypes/StructObject.hpp +++ b/lua-api/lib/include/datatypes/StructObject.hpp @@ -6,9 +6,16 @@ namespace lua::datatypes { struct StructObject { StructObject(void* obj, uevr::API::UStruct* def) : object{ obj }, desc{ def } {} + StructObject(uevr::API::UStruct* def); // Allocates a new structure given a definition + StructObject(uevr::API::UObject* obj); + ~StructObject(); + + void construct(uevr::API::UStruct* def); void* object{ nullptr }; uevr::API::UStruct* desc{ nullptr }; + + std::vector created_object{}; // Only used when the object is created by second constructor }; void bind_struct_object(sol::state_view& lua); diff --git a/lua-api/lib/src/datatypes/StructObject.cpp b/lua-api/lib/src/datatypes/StructObject.cpp index 8bb7fb26..b2903c4c 100644 --- a/lua-api/lib/src/datatypes/StructObject.cpp +++ b/lua-api/lib/src/datatypes/StructObject.cpp @@ -4,6 +4,45 @@ #include namespace lua::datatypes { + void StructObject::construct(uevr::API::UStruct* def) { + // TODO: Call constructor? Not important for now + if (def->is_a(uevr::API::UScriptStruct::static_class())) { + auto script_struct = static_cast(def); + + created_object.resize(script_struct->get_struct_size()); + memset(created_object.data(), 0, created_object.size()); + } else { + created_object.resize(def->get_properties_size()); + } + + object = created_object.data(); + desc = def; + } + + StructObject::StructObject(uevr::API::UStruct* def) { + if (def == nullptr) { + throw sol::error("Cannot create a StructObject from a nullptr UStruct"); + } + + construct(def); + } + + StructObject::StructObject(uevr::API::UObject* obj) { + if (obj == nullptr) { + throw sol::error("Cannot create a StructObject from a nullptr UObject"); + } + + if (!obj->is_a(uevr::API::UStruct::static_class())) { + throw sol::error("Cannot create a StructObject from a UObject that is not a UStruct"); + } + + construct(static_cast(obj)); + } + + StructObject::~StructObject() { + + } + void bind_struct_object(sol::state_view& lua) { lua.new_usertype("StructObject", "get_address", [](StructObject& self) { return (uintptr_t)self.object; }, @@ -30,7 +69,8 @@ namespace lua::datatypes { const auto name = ::utility::widen(index_obj.as()); lua::utility::set_property(s, self->object, self->desc, name, value); - } + }, + sol::meta_function::construct, sol::constructors() ); } } \ No newline at end of file From ab955876f16906a5b8ed8eb0c5ca97788152df80 Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 28 Jun 2024 07:26:38 -0700 Subject: [PATCH 28/37] Lua: Pass correct pos/rot structs through stereo callbacks --- lua-api/lib/src/ScriptContext.cpp | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/lua-api/lib/src/ScriptContext.cpp b/lua-api/lib/src/ScriptContext.cpp index e6a1e15e..2b8e6beb 100644 --- a/lua-api/lib/src/ScriptContext.cpp +++ b/lua-api/lib/src/ScriptContext.cpp @@ -665,11 +665,21 @@ void ScriptContext::on_post_slate_draw_window_render_thread(UEVR_FSlateRHIRender } void ScriptContext::on_pre_calculate_stereo_view_offset(UEVR_StereoRenderingDeviceHandle device, int view_index, float world_to_meters, UEVR_Vector3f* position, UEVR_Rotatorf* rotation, bool is_double) { + const auto ue5_position = (lua::datatypes::Vector3d*)position; + const auto ue4_position = (lua::datatypes::Vector3f*)position; + const auto ue5_rotation = (lua::datatypes::Vector3d*)rotation; + const auto ue4_rotation = (lua::datatypes::Vector3f*)rotation; + const auto is_ue5 = lua::utility::is_ue5(); + g_contexts.for_each([=](auto ctx) { std::scoped_lock _{ ctx->m_mtx }; for (auto& fn : ctx->m_on_pre_calculate_stereo_view_offset_callbacks) try { - ctx->handle_protected_result(fn(device, view_index, world_to_meters, position, rotation, is_double)); + if (is_ue5) { + ctx->handle_protected_result(fn(device, view_index, world_to_meters, ue5_position, ue5_rotation, is_double)); + } else { + ctx->handle_protected_result(fn(device, view_index, world_to_meters, ue4_position, ue4_rotation, is_double)); + } } catch (const std::exception& e) { ScriptContext::log("Exception in on_pre_calculate_stereo_view_offset: " + std::string(e.what())); } catch (...) { @@ -679,11 +689,21 @@ void ScriptContext::on_pre_calculate_stereo_view_offset(UEVR_StereoRenderingDevi } void ScriptContext::on_post_calculate_stereo_view_offset(UEVR_StereoRenderingDeviceHandle device, int view_index, float world_to_meters, UEVR_Vector3f* position, UEVR_Rotatorf* rotation, bool is_double) { + const auto ue5_position = (lua::datatypes::Vector3d*)position; + const auto ue4_position = (lua::datatypes::Vector3f*)position; + const auto ue5_rotation = (lua::datatypes::Vector3d*)rotation; + const auto ue4_rotation = (lua::datatypes::Vector3f*)rotation; + const auto is_ue5 = lua::utility::is_ue5(); + g_contexts.for_each([=](auto ctx) { std::scoped_lock _{ ctx->m_mtx }; for (auto& fn : ctx->m_on_post_calculate_stereo_view_offset_callbacks) try { - ctx->handle_protected_result(fn(device, view_index, world_to_meters, position, rotation, is_double)); + if (is_ue5) { + ctx->handle_protected_result(fn(device, view_index, world_to_meters, ue5_position, ue5_rotation, is_double)); + } else { + ctx->handle_protected_result(fn(device, view_index, world_to_meters, ue4_position, ue4_rotation, is_double)); + } } catch (const std::exception& e) { ScriptContext::log("Exception in on_post_calculate_stereo_view_offset: " + std::string(e.what())); } catch (...) { From 6eecdb9b0d846af928e4ffa56123369506c3271d Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 28 Jun 2024 07:32:39 -0700 Subject: [PATCH 29/37] Lua: Vector property parity with UE naming --- lua-api/lib/src/datatypes/Vector.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lua-api/lib/src/datatypes/Vector.cpp b/lua-api/lib/src/datatypes/Vector.cpp index 97400295..5cab4841 100644 --- a/lua-api/lib/src/datatypes/Vector.cpp +++ b/lua-api/lib/src/datatypes/Vector.cpp @@ -8,6 +8,9 @@ namespace lua::datatypes { "x", &name::x, \ "y", &name::y, \ "z", &name::z, \ + "X", &name::x, \ + "Y", &name::y, \ + "Z", &name::z, \ "dot", [](name& v1, name& v2) { return glm::dot(v1, v2); }, \ "cross", [](name& v1, name& v2) { return glm::cross(v1, v2); }, \ "length", [](name& v) { return glm::length(v); }, \ From 3d59cc84a04531e276114b3831fecd41105905ce Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 28 Jun 2024 07:50:27 -0700 Subject: [PATCH 30/37] Plugins: Activate UObjectHook if any functions are called --- src/mods/PluginLoader.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/mods/PluginLoader.cpp b/src/mods/PluginLoader.cpp index c6b96456..6277eed3 100644 --- a/src/mods/PluginLoader.cpp +++ b/src/mods/PluginLoader.cpp @@ -474,11 +474,17 @@ namespace uobjecthook { } bool exists(UEVR_UObjectHandle obj) { - return UObjectHook::get()->exists((sdk::UObject*)obj); + auto& instance = UObjectHook::get(); + instance->activate(); + + return instance->exists((sdk::UObject*)obj); } int get_objects_by_class(UEVR_UClassHandle klass, UEVR_UObjectHandle* out_objects, unsigned int max_objects, bool allow_default) { - const auto objects = UObjectHook::get()->get_objects_by_class((sdk::UClass*)klass); + auto& instance = UObjectHook::get(); + instance->activate(); + + const auto objects = instance->get_objects_by_class((sdk::UClass*)klass); if (objects.empty()) { return 0; @@ -515,7 +521,10 @@ namespace uobjecthook { } UEVR_UObjectHandle get_first_object_by_class(UEVR_UClassHandle klass, bool allow_default) { - const auto objects = UObjectHook::get()->get_objects_by_class((sdk::UClass*)klass); + auto& instance = UObjectHook::get(); + instance->activate(); + + const auto objects = instance->get_objects_by_class((sdk::UClass*)klass); if (objects.empty()) { return nullptr; From a16461a183d9eba793c969b23b5ccbbfd9ffe598 Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 28 Jun 2024 08:07:21 -0700 Subject: [PATCH 31/37] Plugins: Add FEnumProperty functions --- include/uevr/API.h | 11 ++++++++++- include/uevr/API.hpp | 33 +++++++++++++++++++++++++++++++++ src/mods/PluginLoader.cpp | 13 ++++++++++++- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/include/uevr/API.h b/include/uevr/API.h index 48fa6e62..41393f84 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 25 +#define UEVR_PLUGIN_VERSION_MINOR 26 #define UEVR_PLUGIN_VERSION_PATCH 0 #define UEVR_RENDERER_D3D11 0 @@ -79,6 +79,9 @@ DECLARE_UEVR_HANDLE(UEVR_UScriptStructHandle); DECLARE_UEVR_HANDLE(UEVR_FArrayPropertyHandle); DECLARE_UEVR_HANDLE(UEVR_FBoolPropertyHandle); DECLARE_UEVR_HANDLE(UEVR_FStructPropertyHandle); +DECLARE_UEVR_HANDLE(UEVR_FEnumPropertyHandle); +DECLARE_UEVR_HANDLE(UEVR_UEnumHandle); +DECLARE_UEVR_HANDLE(UEVR_FNumericPropertyHandle); /* OpenXR stuff */ DECLARE_UEVR_HANDLE(UEVR_XrInstance); @@ -416,6 +419,11 @@ typedef struct { UEVR_UScriptStructHandle (*get_struct)(UEVR_FStructPropertyHandle prop); } UEVR_FStructPropertyFunctions; +typedef struct { + UEVR_FNumericPropertyHandle (*get_underlying_prop)(UEVR_FEnumPropertyHandle prop); + UEVR_UEnumHandle (*get_enum)(UEVR_FEnumPropertyHandle prop); +} UEVR_FEnumPropertyFunctions; + typedef struct { const UEVR_SDKFunctions* functions; const UEVR_SDKCallbacks* callbacks; @@ -438,6 +446,7 @@ typedef struct { const UEVR_FArrayPropertyFunctions* farrayproperty; const UEVR_FBoolPropertyFunctions* fboolproperty; const UEVR_FStructPropertyFunctions* fstructproperty; + const UEVR_FEnumPropertyFunctions* fenumproperty; } UEVR_SDKData; DECLARE_UEVR_HANDLE(UEVR_IVRSystem); diff --git a/include/uevr/API.hpp b/include/uevr/API.hpp index 51f2750d..b9943ad1 100644 --- a/include/uevr/API.hpp +++ b/include/uevr/API.hpp @@ -742,6 +742,39 @@ class API { } }; + struct UEnum : public UObject { + + }; + + struct FNumericProperty : public FProperty { + + }; + + struct FEnumProperty : public FProperty { + inline UEVR_FEnumPropertyHandle to_handle() { return (UEVR_FEnumPropertyHandle)this; } + inline UEVR_FEnumPropertyHandle to_handle() const { return (UEVR_FEnumPropertyHandle)this; } + + FNumericProperty* get_underlying_prop() const { + static const auto fn = initialize()->get_underlying_prop; + return (FNumericProperty*)fn(to_handle()); + } + + UEnum* get_enum() const { + static const auto fn = initialize()->get_enum; + return (UEnum*)fn(to_handle()); + } + + private: + static inline const UEVR_FEnumPropertyFunctions* s_functions{nullptr}; + static inline const UEVR_FEnumPropertyFunctions* initialize() { + if (s_functions == nullptr) { + s_functions = API::get()->sdk()->fenumproperty; + } + + return s_functions; + } + }; + struct FFieldClass { inline UEVR_FFieldClassHandle to_handle() { return (UEVR_FFieldClassHandle)this; } inline UEVR_FFieldClassHandle to_handle() const { return (UEVR_FFieldClassHandle)this; } diff --git a/src/mods/PluginLoader.cpp b/src/mods/PluginLoader.cpp index 6277eed3..54ee5078 100644 --- a/src/mods/PluginLoader.cpp +++ b/src/mods/PluginLoader.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "pluginloader/FFakeStereoRenderingFunctions.hpp" #include "pluginloader/FRenderTargetPoolHook.hpp" @@ -858,6 +859,15 @@ UEVR_FStructPropertyFunctions g_fstruct_property_functions { } }; +UEVR_FEnumPropertyFunctions g_fenum_property_functions { + .get_underlying_prop = [](UEVR_FEnumPropertyHandle prop) -> UEVR_FNumericPropertyHandle { + return (UEVR_FNumericPropertyHandle)((sdk::FEnumProperty*)prop)->get_underlying_prop(); + }, + .get_enum = [](UEVR_FEnumPropertyHandle prop) -> UEVR_UEnumHandle { + return (UEVR_UEnumHandle)((sdk::FEnumProperty*)prop)->get_enum(); + } +}; + UEVR_SDKData g_sdk_data { &g_sdk_functions, &g_sdk_callbacks, @@ -879,7 +889,8 @@ UEVR_SDKData g_sdk_data { &uevr::uscriptstruct::functions, &g_farray_property_functions, &g_fbool_property_functions, - &g_fstruct_property_functions + &g_fstruct_property_functions, + &g_fenum_property_functions }; namespace uevr { From 21aaf9a3fe1d52633ae251a7d5006bcbb6ee6991 Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 28 Jun 2024 08:33:30 -0700 Subject: [PATCH 32/37] Lua: Add support for enum properties and some other primitives --- lua-api/lib/src/ScriptContext.cpp | 4 +- lua-api/lib/src/ScriptUtility.cpp | 129 +++++++++++++++++++++++++++++- 2 files changed, 130 insertions(+), 3 deletions(-) diff --git a/lua-api/lib/src/ScriptContext.cpp b/lua-api/lib/src/ScriptContext.cpp index 2b8e6beb..cf6ca8af 100644 --- a/lua-api/lib/src/ScriptContext.cpp +++ b/lua-api/lib/src/ScriptContext.cpp @@ -428,7 +428,9 @@ int ScriptContext::setup_bindings() { "static_class", &uevr::API::UStruct::static_class, "get_super_struct", &uevr::API::UStruct::get_super_struct, "get_super", &uevr::API::UStruct::get_super, - "find_function", &uevr::API::UStruct::find_function, + "find_function", [](uevr::API::UStruct& self, const std::wstring& name) { + return self.find_function(name); + }, "get_child_properties", &uevr::API::UStruct::get_child_properties, "get_properties_size", &uevr::API::UStruct::get_properties_size ); diff --git a/lua-api/lib/src/ScriptUtility.cpp b/lua-api/lib/src/ScriptUtility.cpp index fcdc1440..5a029810 100644 --- a/lua-api/lib/src/ScriptUtility.cpp +++ b/lua-api/lib/src/ScriptUtility.cpp @@ -56,11 +56,66 @@ sol::object prop_to_object(sol::this_state s, void* self, uevr::API::FProperty* return sol::make_object(s, *(float*)((uintptr_t)self + offset)); case L"DoubleProperty"_fnv: return sol::make_object(s, *(double*)((uintptr_t)self + offset)); + case L"ByteProperty"_fnv: + return sol::make_object(s, *(uint8_t*)((uintptr_t)self + offset)); + case L"Int8Property"_fnv: + return sol::make_object(s, *(int8_t*)((uintptr_t)self + offset)); + case L"Int16Property"_fnv: + return sol::make_object(s, *(int16_t*)((uintptr_t)self + offset)); + case L"UInt16Property"_fnv: + return sol::make_object(s, *(uint16_t*)((uintptr_t)self + offset)); case L"IntProperty"_fnv: return sol::make_object(s, *(int32_t*)((uintptr_t)self + offset)); case L"UIntProperty"_fnv: case L"UInt32Property"_fnv: return sol::make_object(s, *(uint32_t*)((uintptr_t)self + offset)); + case L"UInt64Property"_fnv: + return sol::make_object(s, *(uint64_t*)((uintptr_t)self + offset)); + case L"Int64Property"_fnv: + return sol::make_object(s, *(int64_t*)((uintptr_t)self + offset)); + case L"EnumProperty"_fnv: + { + const auto ep = (uevr::API::FEnumProperty*)desc; + const auto np = ep->get_underlying_prop(); + + if (np == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + const auto np_c = np->get_class(); + + if (np_c == nullptr) { + return sol::make_object(s, sol::lua_nil); + } + + const auto np_name_hash = ::utility::hash(np_c->get_fname()->to_string()); + + switch (np_name_hash) { + case L"FloatProperty"_fnv: + return sol::make_object(s, *(float*)((uintptr_t)self + offset)); + case L"DoubleProperty"_fnv: + return sol::make_object(s, *(double*)((uintptr_t)self + offset)); + case L"ByteProperty"_fnv: + return sol::make_object(s, *(uint8_t*)((uintptr_t)self + offset)); + case L"Int8Property"_fnv: + return sol::make_object(s, *(int8_t*)((uintptr_t)self + offset)); + case L"Int16Property"_fnv: + return sol::make_object(s, *(int16_t*)((uintptr_t)self + offset)); + case L"UInt16Property"_fnv: + return sol::make_object(s, *(uint16_t*)((uintptr_t)self + offset)); + case L"IntProperty"_fnv: + return sol::make_object(s, *(int32_t*)((uintptr_t)self + offset)); + case L"UIntProperty"_fnv: + case L"UInt32Property"_fnv: + return sol::make_object(s, *(uint32_t*)((uintptr_t)self + offset)); + case L"UInt64Property"_fnv: + return sol::make_object(s, *(uint64_t*)((uintptr_t)self + offset)); + case L"Int64Property"_fnv: + return sol::make_object(s, *(int64_t*)((uintptr_t)self + offset)); + }; + + return sol::make_object(s, sol::lua_nil); + } case L"NameProperty"_fnv: return sol::make_object(s, *(uevr::API::FName*)((uintptr_t)self + offset)); case L"ObjectProperty"_fnv: @@ -209,19 +264,89 @@ void set_property(sol::this_state s, void* self, uevr::API::UStruct* c, const st return; } case L"FloatProperty"_fnv: - //self.get_property(name) = value.as(); *(float*)((uintptr_t)self + offset) = value.as(); return; case L"DoubleProperty"_fnv: *(double*)((uintptr_t)self + offset) = value.as(); return; + case L"ByteProperty"_fnv: + *(uint8_t*)((uintptr_t)self + offset) = value.as(); + return; + case L"Int8Property"_fnv: + *(int8_t*)((uintptr_t)self + offset) = value.as(); + return; + case L"Int16Property"_fnv: + *(int16_t*)((uintptr_t)self + offset) = value.as(); + return; + case L"UInt16Property"_fnv: + *(uint16_t*)((uintptr_t)self + offset) = value.as(); + return; case L"IntProperty"_fnv: *(int32_t*)((uintptr_t)self + offset) = value.as(); - return; + return; case L"UIntProperty"_fnv: case L"UInt32Property"_fnv: *(uint32_t*)((uintptr_t)self + offset) = value.as(); return; + case L"UInt64Property"_fnv: + *(uint64_t*)((uintptr_t)self + offset) = value.as(); + return; + case L"Int64Property"_fnv: + *(int64_t*)((uintptr_t)self + offset) = value.as(); + return; + case L"EnumProperty"_fnv: + { + const auto ep = (uevr::API::FEnumProperty*)desc; + const auto np = ep->get_underlying_prop(); + + if (np == nullptr) { + throw sol::error("Enum property has no underlying property"); + } + + const auto np_c = np->get_class(); + + if (np_c == nullptr) { + throw sol::error("Enum property's underlying property has no class"); + } + + const auto np_name_hash = ::utility::hash(np_c->get_fname()->to_string()); + + switch (np_name_hash) { + case L"FloatProperty"_fnv: + *(float*)((uintptr_t)self + offset) = value.as(); + return; + case L"DoubleProperty"_fnv: + *(double*)((uintptr_t)self + offset) = value.as(); + return; + case L"ByteProperty"_fnv: + *(uint8_t*)((uintptr_t)self + offset) = value.as(); + return; + case L"Int8Property"_fnv: + *(int8_t*)((uintptr_t)self + offset) = value.as(); + return; + case L"Int16Property"_fnv: + *(int16_t*)((uintptr_t)self + offset) = value.as(); + return; + case L"UInt16Property"_fnv: + *(uint16_t*)((uintptr_t)self + offset) = value.as(); + return; + case L"IntProperty"_fnv: + *(int32_t*)((uintptr_t)self + offset) = value.as(); + return; + case L"UIntProperty"_fnv: + case L"UInt32Property"_fnv: + *(uint32_t*)((uintptr_t)self + offset) = value.as(); + return; + case L"UInt64Property"_fnv: + *(uint64_t*)((uintptr_t)self + offset) = value.as(); + return; + case L"Int64Property"_fnv: + *(int64_t*)((uintptr_t)self + offset) = value.as(); + return; + }; + + throw sol::error("Could not set enum property"); + } case L"NameProperty"_fnv: //return sol::make_object(s, self.get_property(name)); throw sol::error("Setting FName properties is not supported (yet)"); From 238ef07d054ff259022e6a944ad51a0d7756a93a Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 28 Jun 2024 09:03:55 -0700 Subject: [PATCH 33/37] Lua: Use set_property for call_function args --- lua-api/lib/include/ScriptUtility.hpp | 3 +- lua-api/lib/src/ScriptUtility.cpp | 188 ++++++++++---------------- 2 files changed, 70 insertions(+), 121 deletions(-) diff --git a/lua-api/lib/include/ScriptUtility.hpp b/lua-api/lib/include/ScriptUtility.hpp index ea03da92..ab3e3505 100644 --- a/lua-api/lib/include/ScriptUtility.hpp +++ b/lua-api/lib/include/ScriptUtility.hpp @@ -11,7 +11,8 @@ namespace lua::utility { sol::object prop_to_object(sol::this_state s, void* self, uevr::API::UStruct* desc, const std::wstring& name); sol::object prop_to_object(sol::this_state s, uevr::API::UObject* self, const std::wstring& name); - void set_property(sol::this_state s, void* self, uevr::API::UStruct* desc, const std::wstring& name, sol::object value); + void set_property(sol::this_state s, void* self, uevr::API::UStruct* c, uevr::API::FProperty* desc, sol::object value); + void set_property(sol::this_state s, void* self, uevr::API::UStruct* c, const std::wstring& name, sol::object value); void set_property(sol::this_state s, uevr::API::UObject* self, const std::wstring& name, sol::object value); sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API::UFunction* fn, sol::variadic_args args); diff --git a/lua-api/lib/src/ScriptUtility.cpp b/lua-api/lib/src/ScriptUtility.cpp index 5a029810..70e2e480 100644 --- a/lua-api/lib/src/ScriptUtility.cpp +++ b/lua-api/lib/src/ScriptUtility.cpp @@ -247,10 +247,14 @@ void set_property(sol::this_state s, void* self, uevr::API::UStruct* c, const st throw sol::error(std::format("[set_property] Property '{}' not found", ::utility::narrow(name))); } + set_property(s, self, c, desc, value); +} + +void set_property(sol::this_state s, void* self, uevr::API::UStruct* owner_c, uevr::API::FProperty* desc, sol::object value) { const auto propc = desc->get_class(); if (propc == nullptr) { - throw sol::error(std::format("[set_property] Property '{}' has no class", ::utility::narrow(name))); + throw sol::error(std::format("[set_property] Property '{}' has no class", ::utility::narrow(desc->get_fname()->to_string()))); } const auto name_hash = ::utility::hash(propc->get_fname()->to_string()); @@ -348,8 +352,20 @@ void set_property(sol::this_state s, void* self, uevr::API::UStruct* c, const st throw sol::error("Could not set enum property"); } case L"NameProperty"_fnv: - //return sol::make_object(s, self.get_property(name)); - throw sol::error("Setting FName properties is not supported (yet)"); + if (value.is()) { + const auto arg = ::utility::widen(value.as()); + *(uevr::API::FName*)((uintptr_t)self + offset) = uevr::API::FName{arg}; + } else if (value.is()) { + const auto arg = value.as(); + *(uevr::API::FName*)((uintptr_t)self + offset) = uevr::API::FName{arg}; + } else if (value.is()) { + const auto arg = value.as(); + *(uevr::API::FName*)((uintptr_t)self + offset) = arg; + } else { + throw sol::error("Invalid argument type for FName"); + } + + return; case L"ObjectProperty"_fnv: *(uevr::API::UObject**)((uintptr_t)self + offset) = value.as(); return; @@ -358,6 +374,52 @@ void set_property(sol::this_state s, void* self, uevr::API::UStruct* c, const st return; case L"ArrayProperty"_fnv: throw sol::error("Setting array properties is not supported (yet)"); + case L"StructProperty"_fnv: + { + const auto struct_desc = ((uevr::API::FStructProperty*)desc)->get_struct(); + + if (struct_desc == nullptr) { + throw sol::error("Struct property has no struct"); + } + + if (value.is()) { + const auto arg = value.as(); + + if (arg.desc != struct_desc) { + if (arg.desc != nullptr) { + throw sol::error(std::format("Invalid struct type for struct property (expected {}, got {})", ::utility::narrow(struct_desc->get_fname()->to_string()), ::utility::narrow(arg.desc->get_fname()->to_string()))); + } else { + throw sol::error(std::format("Invalid struct type for struct property (expected {})", ::utility::narrow(struct_desc->get_fname()->to_string()))); + } + } + + memcpy((void*)((uintptr_t)self + offset), arg.object, struct_desc->get_struct_size()); + } else if (struct_desc == get_vector_struct()) { + if (value.is()) { + const auto arg = value.as(); + + if (is_ue5()) { + *(lua::datatypes::Vector3d*)((uintptr_t)self + offset) = arg; + } else { + *(lua::datatypes::Vector3f*)((uintptr_t)self + offset) = arg; + } + } else if (value.is()) { + const auto arg = value.as(); + + if (is_ue5()) { + *(lua::datatypes::Vector3d*)((uintptr_t)self + offset) = arg; + } else { + *(lua::datatypes::Vector3f*)((uintptr_t)self + offset) = arg; + } + } else { + throw sol::error("Invalid argument type for FVector"); + } + } else { + throw sol::error("Invalid argument type for struct property"); + } + + return; + } }; // NONE @@ -433,118 +495,7 @@ sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API const auto arg_hash = ::utility::hash(arg_c_name); const auto offset = prop_desc->get_offset(); - switch (arg_hash) { - case L"BoolProperty"_fnv: - { - const bool arg = args[args_index++].as(); - const auto fbp = (uevr::API::FBoolProperty*)prop_desc; - fbp->set_value_in_object(params.data(), arg); - continue; - } - case L"FloatProperty"_fnv: - { - const float arg = args[args_index++].as(); - *(float*)¶ms[offset] = arg; - continue; - } - case L"DoubleProperty"_fnv: - { - const double arg = args[args_index++].as(); - *(double*)¶ms[offset] = arg; - continue; - } - case L"IntProperty"_fnv: - case L"UIntProperty"_fnv: - case L"UInt32Property"_fnv: - { - const int32_t arg = args[args_index++].as(); - *(int32_t*)¶ms[offset] = arg; - continue; - } - case L"NameProperty"_fnv: - { - const auto arg_obj = args[args_index++]; - - if (arg_obj.is()) { - const auto arg = ::utility::widen(arg_obj.as()); - *(uevr::API::FName*)¶ms[offset] = uevr::API::FName{arg}; - } else if (arg_obj.is()) { - const auto arg = arg_obj.as(); - *(uevr::API::FName*)¶ms[offset] = uevr::API::FName{arg}; - } else if (arg_obj.is()) { - const auto arg = arg_obj.as(); - *(uevr::API::FName*)¶ms[offset] = arg; - } else { - throw sol::error("Invalid argument type for FName"); - } - - continue; - } - case L"ObjectProperty"_fnv: - { - const auto arg = args[args_index++].as(); - *(uevr::API::UObject**)¶ms[offset] = arg; - continue; - } - case L"ClassProperty"_fnv: - { - const auto arg = args[args_index++].as(); - *(uevr::API::UClass**)¶ms[offset] = arg; - continue; - } - case L"StructProperty"_fnv: - { - const auto arg_obj = args[args_index++]; - const auto struct_desc = ((uevr::API::FStructProperty*)prop_desc)->get_struct(); - - if (struct_desc == nullptr) { - throw sol::error("Struct property has no struct"); - } - - if (arg_obj.is()) { - const auto arg = arg_obj.as(); - - if (arg.desc != struct_desc) { - if (arg.desc != nullptr) { - throw sol::error(std::format("Invalid argument type for function '{}', expected '{}', got '{}'", ::utility::narrow(fn->get_fname()->to_string()), ::utility::narrow(struct_desc->get_fname()->to_string()), ::utility::narrow(arg.desc->get_fname()->to_string()))); - } else { - throw sol::error(std::format("Invalid argument type for function '{}', expected '{}', got 'nil'", ::utility::narrow(fn->get_fname()->to_string()), ::utility::narrow(struct_desc->get_fname()->to_string()))); - } - } - - memcpy(¶ms[offset], arg.object, struct_desc->get_struct_size()); - } else if (struct_desc == get_vector_struct()) { - if (arg_obj.is()) { - const auto arg = arg_obj.as(); - - if (is_ue5()) { - *(lua::datatypes::Vector3d*)¶ms[offset] = arg; - } else { - *(lua::datatypes::Vector3f*)¶ms[offset] = arg; - } - } else if (arg_obj.is()) { - const auto arg = arg_obj.as(); - - if (is_ue5()) { - *(lua::datatypes::Vector3d*)¶ms[offset] = arg; - } else { - *(lua::datatypes::Vector3f*)¶ms[offset] = arg; - } - } else { - throw sol::error("Invalid argument type for FVector"); - } - } else { - throw sol::error("Invalid argument type for struct property"); - } - - continue; - } - case L"ArrayProperty"_fnv: - // TODO - throw sol::error("Array properties are not supported (yet)"); - continue; - case L"StrProperty"_fnv: - { + if (arg_hash == L"StrProperty"_fnv) { const auto arg_obj = args[args_index++]; using FString = uevr::API::TArray; @@ -567,12 +518,9 @@ sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API } else { throw sol::error("Invalid argument type for FString"); } - continue; + } else { + set_property(s, params.data(), fn, prop_desc, args[args_index++]); } - default: - //spdlog::warn("Unknown property type when calling '{}': {}", utility::narrow(fn->get_fname()->to_string()), utility::narrow(arg_c_name)); - uevr::API::get()->log_warn(std::format("Unknown property type when calling '{}': {}", ::utility::narrow(fn->get_fname()->to_string()), ::utility::narrow(arg_c_name)).c_str()); - }; } fn->call(self, params.data()); From 0aa85752a09e2e084bdaf12f756339398eee39b2 Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 28 Jun 2024 09:28:50 -0700 Subject: [PATCH 34/37] Lua: Cleanup TArray return values --- lua-api/lib/src/ScriptUtility.cpp | 41 ++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/lua-api/lib/src/ScriptUtility.cpp b/lua-api/lib/src/ScriptUtility.cpp index 70e2e480..64779727 100644 --- a/lua-api/lib/src/ScriptUtility.cpp +++ b/lua-api/lib/src/ScriptUtility.cpp @@ -459,6 +459,7 @@ sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API uevr::API::FProperty* return_prop{nullptr}; bool ret_is_bool{false}; + bool ret_is_array{false}; //std::vector dynamic_data{}; std::vector dynamic_strings{}; @@ -487,6 +488,8 @@ sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API if (arg_c_name == L"BoolProperty") { ret_is_bool = true; + } else if (arg_c_name == L"ArrayProperty") { + ret_is_array = true; } continue; @@ -530,7 +533,43 @@ sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API return sol::make_object(s, ((uevr::API::FBoolProperty*)return_prop)->get_value_from_object(params.data())); } - return prop_to_object(s, params.data(), return_prop, true); + auto result = prop_to_object(s, params.data(), return_prop, true); + + if (ret_is_array) { + const auto inner_prop = ((uevr::API::FArrayProperty*)return_prop)->get_inner(); + + if (inner_prop == nullptr) { + return result; + } + + const auto inner_c = inner_prop->get_class(); + + if (inner_c == nullptr) { + return result; + } + + const auto inner_name_hash = ::utility::hash(inner_c->get_fname()->to_string()); + + switch (inner_name_hash) { + case L"ObjectProperty"_fnv: + { + //printf("ArrayProperty cleanup\n"); + auto& arr = *(uevr::API::TArray*)¶ms[return_prop->get_offset()]; + arr.~TArray(); + break; + } + default: + { + //printf("ArrayProperty cleanup\n"); + // This will not work correctly on non-trivial types, but... we'll deal with that later + auto& arr = *(uevr::API::TArray*)¶ms[return_prop->get_offset()]; + arr.~TArray(); + break; + } + } + } + + return result; } return sol::make_object(s, sol::lua_nil); From fa9005243321e23f7ad5b8e48afc0da626a3800a Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 28 Jun 2024 10:17:08 -0700 Subject: [PATCH 35/37] Lua: Fix redundant param resize --- lua-api/lib/src/ScriptUtility.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/lua-api/lib/src/ScriptUtility.cpp b/lua-api/lib/src/ScriptUtility.cpp index 64779727..5b845c2b 100644 --- a/lua-api/lib/src/ScriptUtility.cpp +++ b/lua-api/lib/src/ScriptUtility.cpp @@ -455,8 +455,6 @@ sol::object call_function(sol::this_state s, uevr::API::UObject* self, uevr::API params.resize(ps); } - params.resize(fn->get_properties_size()); - uevr::API::FProperty* return_prop{nullptr}; bool ret_is_bool{false}; bool ret_is_array{false}; From 4545fe4353f18c52904e8f10279400a40a6c7106 Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 4 Jul 2024 07:03:09 -0700 Subject: [PATCH 36/37] CI: Attempt at including LuaVR.dll in builds --- .github/workflows/dev-release.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/dev-release.yml b/.github/workflows/dev-release.yml index f6e196a0..a5838837 100644 --- a/.github/workflows/dev-release.yml +++ b/.github/workflows/dev-release.yml @@ -25,6 +25,9 @@ jobs: - name: Build plugin nullifier run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target vr-plugin-nullifier + - name: Build Lua API (shared DLL) + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target LuaVR + - name: Checkout frontend uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 with: @@ -47,6 +50,7 @@ jobs: path: | ${{github.workspace}}/build/bin/${{matrix.target}}/* ${{github.workspace}}/build/bin/vr-plugin-nullifier/UEVRPluginNullifier.dll + ${{github.workspace}}/build/bin/LuaVR/LuaVR.dll if-no-files-found: error - name: Compress artifacts @@ -54,6 +58,7 @@ jobs: echo ${{github.sha}} > ${{github.workspace}}/revision.txt 7z a ${{github.workspace}}/${{matrix.target}}.zip ${{github.workspace}}/build/bin/${{matrix.target}}/* 7z a ${{github.workspace}}/${{matrix.target}}.zip ${{github.workspace}}/build/bin/vr-plugin-nullifier/UEVRPluginNullifier.dll + 7z a ${{github.workspace}}/${{matrix.target}}.zip ${{github.workspace}}/build/bin/LuaVR/LuaVR.dll 7z a ${{github.workspace}}/${{matrix.target}}.zip ${{github.workspace}}/revision.txt - name: Hash zip From c0302b7b446781f7203d242104571d8a2b93bfb9 Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 4 Jul 2024 10:09:52 -0700 Subject: [PATCH 37/37] Lua: Fix undefined behavior when loading LuaVR.dll --- lua-api/Main.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/lua-api/Main.cpp b/lua-api/Main.cpp index 47bfb941..2bae3174 100644 --- a/lua-api/Main.cpp +++ b/lua-api/Main.cpp @@ -9,8 +9,6 @@ std::shared_ptr g_script_context{}; extern "C" __declspec(dllexport) int luaopen_LuaVR(lua_State* L) { luaL_checkversion(L); - uevr::ScriptContext::log("Initializing LuaVR..."); - g_script_context = uevr::ScriptContext::create(L); if (!g_script_context->valid()) {