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 diff --git a/CMakeLists.txt b/CMakeLists.txt index dd290c8f..6a4cb529 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -590,14 +590,67 @@ 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/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" +) + +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 + glm +) + +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 +681,7 @@ target_include_directories(LuaVR PUBLIC ) target_link_libraries(LuaVR PUBLIC - lua - sol2 - kananlib + luavrlib ) set_target_properties(LuaVR PROPERTIES @@ -669,6 +720,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 +840,7 @@ target_link_libraries(uevr PUBLIC DirectXTK12 sdkgenny asmjit + luavrlib ) target_link_libraries(uevr PUBLIC diff --git a/cmake.toml b/cmake.toml index afe8ccf6..e4b4b1d0 100644 --- a/cmake.toml +++ b/cmake.toml @@ -192,17 +192,28 @@ include-directories = ["dependencies/lua/src"] type = "interface" include-directories = ["dependencies/sol2/single/single/include"] +[target.luavrlib] +type = "static" +compile-features = ["cxx_std_23"] +compile-options = ["/bigobj", "/EHa", "/MP"] +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", + "glm" +] + [target.LuaVR] type = "shared" 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"] +sources = ["lua-api/Main.cpp"] link-libraries = [ - "lua", - "sol2", - "kananlib" + "luavrlib" ] [target.LuaVR.properties] @@ -241,7 +252,8 @@ link-libraries = [ "DirectXTK", "DirectXTK12", "sdkgenny", - "asmjit" + "asmjit", + "luavrlib" ] [template.ue4template.properties] diff --git a/include/uevr/API.h b/include/uevr/API.h index 2b0b3761..f56cef89 100644 --- a/include/uevr/API.h +++ b/include/uevr/API.h @@ -78,6 +78,10 @@ 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); +DECLARE_UEVR_HANDLE(UEVR_FEnumPropertyHandle); +DECLARE_UEVR_HANDLE(UEVR_UEnumHandle); +DECLARE_UEVR_HANDLE(UEVR_FNumericPropertyHandle); /* OpenXR stuff */ DECLARE_UEVR_HANDLE(UEVR_XrInstance); @@ -413,6 +417,15 @@ 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 { + 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; @@ -434,6 +447,8 @@ typedef struct { const UEVR_UScriptStructFunctions* uscriptstruct; 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 a774c6fd..b9943ad1 100644 --- a/include/uevr/API.hpp +++ b/include/uevr/API.hpp @@ -722,6 +722,59 @@ 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 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/lua-api/Main.cpp b/lua-api/Main.cpp index 666c961f..2bae3174 100644 --- a/lua-api/Main.cpp +++ b/lua-api/Main.cpp @@ -1,24 +1,24 @@ // 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..."); - - 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.cpp b/lua-api/ScriptContext.cpp deleted file mode 100644 index e0cdd74c..00000000 --- a/lua-api/ScriptContext.cpp +++ /dev/null @@ -1,501 +0,0 @@ -// This can be considered a binding of the C API. -#include -#include - -#include - -#include - -#include "ScriptContext.hpp" - -std::shared_ptr g_script_context{}; - -std::shared_ptr ScriptContext::get() { - return g_script_context; -} - -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; -} - -ScriptContext::ScriptContext(lua_State* l, UEVR_PluginInitializeParam* param) - : m_lua{l} -{ - std::scoped_lock _{m_mtx}; - - if (param != nullptr) { - m_plugin_initialize_param = param; - uevr::API::initialize(m_plugin_initialize_param); - return; - } - - const auto unreal_vr_backend = GetModuleHandleA("UEVRBackend.dll"); - - if (unreal_vr_backend == nullptr) { - return; - } - - m_plugin_initialize_param = (UEVR_PluginInitializeParam*)GetProcAddress(unreal_vr_backend, "g_plugin_initialize_param"); - uevr::API::initialize(m_plugin_initialize_param); -} - -ScriptContext::~ScriptContext() { - std::scoped_lock _{m_mtx}; - - if (m_plugin_initialize_param != nullptr) { - for (auto& cb : m_callbacks_to_remove) { - m_plugin_initialize_param->functions->remove_callback(cb); - } - - m_callbacks_to_remove.clear(); - } -} - -void ScriptContext::log(const std::string& message) { - std::cout << "[LuaVR] " << message << std::endl; -} - -void ScriptContext::test_function() { - log("Test function called!"); -} - -void ScriptContext::setup_callback_bindings() { - std::scoped_lock _{ m_mtx }; - - 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); - - 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_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_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_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_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_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_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_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); - } - ); -} - -int ScriptContext::setup_bindings() { - m_lua.set_function("test_function", ScriptContext::test_function); - - m_lua.new_usertype("UEVR_PluginInitializeParam", - "uevr_module", &UEVR_PluginInitializeParam::uevr_module, - "version", &UEVR_PluginInitializeParam::version, - "functions", &UEVR_PluginInitializeParam::functions, - "callbacks", &UEVR_PluginInitializeParam::callbacks, - "renderer", &UEVR_PluginInitializeParam::renderer, - "vr", &UEVR_PluginInitializeParam::vr, - "openvr", &UEVR_PluginInitializeParam::openvr, - "openxr", &UEVR_PluginInitializeParam::openxr, - "sdk", &UEVR_PluginInitializeParam::sdk - ); - - m_lua.new_usertype("UEVR_PluginVersion", - "major", &UEVR_PluginVersion::major, - "minor", &UEVR_PluginVersion::minor, - "patch", &UEVR_PluginVersion::patch - ); - - m_lua.new_usertype("UEVR_PluginFunctions", - "log_error", &UEVR_PluginFunctions::log_error, - "log_warn", &UEVR_PluginFunctions::log_warn, - "log_info", &UEVR_PluginFunctions::log_info, - "is_drawing_ui", &UEVR_PluginFunctions::is_drawing_ui - ); - - m_lua.new_usertype("UEVR_RendererData", - "renderer_type", &UEVR_RendererData::renderer_type, - "device", &UEVR_RendererData::device, - "swapchain", &UEVR_RendererData::swapchain, - "command_queue", &UEVR_RendererData::command_queue - ); - - m_lua.new_usertype("UEVR_SDKFunctions", - "get_uengine", &UEVR_SDKFunctions::get_uengine, - "set_cvar_int", &UEVR_SDKFunctions::set_cvar_int, - "get_uobject_array", &UEVR_SDKFunctions::get_uobject_array, - "get_player_controller", &UEVR_SDKFunctions::get_player_controller, - "get_local_pawn", &UEVR_SDKFunctions::get_local_pawn, - "spawn_object", &UEVR_SDKFunctions::spawn_object, - - "execute_command", &UEVR_SDKFunctions::execute_command, - "execute_command_ex", &UEVR_SDKFunctions::execute_command_ex, - - "get_console_manager", &UEVR_SDKFunctions::get_console_manager - ); - - m_lua.new_usertype("UEVR_UObjectHookFunctions", - "activate", &UEVR_UObjectHookFunctions::activate, - "exists", &UEVR_UObjectHookFunctions::exists, - "get_first_object_by_class", &UEVR_UObjectHookFunctions::get_first_object_by_class, - "get_first_object_by_class_name", &UEVR_UObjectHookFunctions::get_first_object_by_class_name - // The other functions are really C-oriented so... we will just wrap the C++ API for the rest - ); - - m_lua.new_usertype("UEVR_SDKData", - "functions", &UEVR_SDKData::functions, - "callbacks", &UEVR_SDKData::callbacks, - "uobject", &UEVR_SDKData::uobject, - "uobject_array", &UEVR_SDKData::uobject_array, - "ffield", &UEVR_SDKData::ffield, - "fproperty", &UEVR_SDKData::fproperty, - "ustruct", &UEVR_SDKData::ustruct, - "uclass", &UEVR_SDKData::uclass, - "ufunction", &UEVR_SDKData::ufunction, - "uobject_hook", &UEVR_SDKData::uobject_hook, - "ffield_class", &UEVR_SDKData::ffield_class, - "fname", &UEVR_SDKData::fname, - "console", &UEVR_SDKData::console - ); - - m_lua.new_usertype("UEVR_VRData", - "is_runtime_ready", &UEVR_VRData::is_runtime_ready, - "is_openvr", &UEVR_VRData::is_openvr, - "is_openxr", &UEVR_VRData::is_openxr, - "is_hmd_active", &UEVR_VRData::is_hmd_active, - "get_standing_origin", &UEVR_VRData::get_standing_origin, - "get_rotation_offset", &UEVR_VRData::get_rotation_offset, - "set_standing_origin", &UEVR_VRData::set_standing_origin, - "set_rotation_offset", &UEVR_VRData::set_rotation_offset, - "get_hmd_index", &UEVR_VRData::get_hmd_index, - "get_left_controller_index", &UEVR_VRData::get_left_controller_index, - "get_right_controller_index", &UEVR_VRData::get_right_controller_index, - "get_pose", &UEVR_VRData::get_pose, - "get_transform", &UEVR_VRData::get_transform, - "get_eye_offset", &UEVR_VRData::get_eye_offset, - "get_ue_projection_matrix", &UEVR_VRData::get_ue_projection_matrix, - "get_left_joystick_source", &UEVR_VRData::get_left_joystick_source, - "get_right_joystick_source", &UEVR_VRData::get_right_joystick_source, - "get_action_handle", &UEVR_VRData::get_action_handle, - "is_action_active", &UEVR_VRData::is_action_active, - "get_joystick_axis", &UEVR_VRData::get_joystick_axis, - "trigger_haptic_vibration", &UEVR_VRData::trigger_haptic_vibration, - "is_using_controllers", &UEVR_VRData::is_using_controllers, - "get_lowest_xinput_index", &UEVR_VRData::get_lowest_xinput_index, - "recenter_view", &UEVR_VRData::recenter_view, - "recenter_horizon", &UEVR_VRData::recenter_horizon, - "get_aim_method", &UEVR_VRData::get_aim_method, - "set_aim_method", &UEVR_VRData::set_aim_method, - "is_aim_allowed", &UEVR_VRData::is_aim_allowed, - "set_aim_allowed", &UEVR_VRData::set_aim_allowed - ); - - // TODO: Add operators to these types - m_lua.new_usertype("UEVR_Vector2f", - "x", &UEVR_Vector2f::x, - "y", &UEVR_Vector2f::y - ); - - m_lua.new_usertype("UEVR_Vector3f", - "x", &UEVR_Vector3f::x, - "y", &UEVR_Vector3f::y, - "z", &UEVR_Vector3f::z - ); - - m_lua.new_usertype("UEVR_Vector3d", - "x", &UEVR_Vector3d::x, - "y", &UEVR_Vector3d::y, - "z", &UEVR_Vector3d::z - ); - - m_lua.new_usertype("UEVR_Vector4f", - "x", &UEVR_Vector4f::x, - "y", &UEVR_Vector4f::y, - "z", &UEVR_Vector4f::z, - "w", &UEVR_Vector4f::w - ); - - m_lua.new_usertype("UEVR_Quaternionf", - "x", &UEVR_Quaternionf::x, - "y", &UEVR_Quaternionf::y, - "z", &UEVR_Quaternionf::z, - "w", &UEVR_Quaternionf::w - ); - - m_lua.new_usertype("UEVR_Rotatorf", - "pitch", &UEVR_Rotatorf::pitch, - "yaw", &UEVR_Rotatorf::yaw, - "roll", &UEVR_Rotatorf::roll - ); - - m_lua.new_usertype("UEVR_Rotatord", - "pitch", &UEVR_Rotatord::pitch, - "yaw", &UEVR_Rotatord::yaw, - "roll", &UEVR_Rotatord::roll - ); - - m_lua.new_usertype("UEVR_Matrix4x4f", - sol::meta_function::index, [](sol::this_state s, UEVR_Matrix4x4f& lhs, sol::object index_obj) -> sol::object { - if (!index_obj.is()) { - return sol::make_object(s, sol::lua_nil); - } - - const auto index = index_obj.as(); - - if (index >= 4) { - return sol::make_object(s, sol::lua_nil); - } - - return sol::make_object(s, &lhs.m[index]); - } - ); - - m_lua.new_usertype("UEVR_Matrix4x4d", - sol::meta_function::index, [](sol::this_state s, UEVR_Matrix4x4d& lhs, sol::object index_obj) -> sol::object { - if (!index_obj.is()) { - return sol::make_object(s, sol::lua_nil); - } - - const auto index = index_obj.as(); - - if (index >= 4) { - return sol::make_object(s, sol::lua_nil); - } - - return sol::make_object(s, &lhs.m[index]); - } - ); - - m_lua.new_usertype("UEVR_FName", - "to_string", &uevr::API::FName::to_string - ); - - m_lua.new_usertype("UEVR_UObject", - "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 - ); - - m_lua.new_usertype("UEVR_UStruct", - sol::base_classes, sol::bases(), - "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, - "get_child_properties", &uevr::API::UStruct::get_child_properties - ); - - m_lua.new_usertype("UEVR_UClass", - sol::base_classes, sol::bases(), - "static_class", &uevr::API::UClass::static_class, - "get_class_default_object", &uevr::API::UClass::get_class_default_object, - "get_objects_matching", &uevr::API::UClass::get_objects_matching, - "get_first_object_matching", &uevr::API::UClass::get_first_object_matching - ); - - m_lua.new_usertype("UEVR_UFunction", - sol::base_classes, sol::bases(), - "static_class", &uevr::API::UFunction::static_class, - "call", &uevr::API::UFunction::call, - "get_native_function", &uevr::API::UFunction::get_native_function - ); - - m_lua.new_usertype("UEVR_FField", - "get_next", &uevr::API::FField::get_next, - "get_fname", &uevr::API::FField::get_fname, - "get_class", &uevr::API::FField::get_class - ); - - m_lua.new_usertype("UEVR_FFieldClass", - "get_fname", &uevr::API::FFieldClass::get_fname, - "get_name", &uevr::API::FFieldClass::get_name - ); - - 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 - ); - - 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 - ); - - m_lua.new_usertype("UEVR_IConsoleVariable", - sol::base_classes, sol::bases(), - "set", [](sol::this_state s, uevr::API::IConsoleVariable* self, sol::object value) { - if (value.is()) { - self->set(value.as()); - } else if (value.is()) { - self->set(value.as()); - } else if (value.is()) { - self->set(value.as()); - } else if (value.is()) { - const auto str = utility::widen(value.as()); - self->set(str); - } else { - throw sol::error("Invalid type for IConsoleVariable::set"); - } - }, - "set_float", [](uevr::API::IConsoleVariable& self, float value) { - self.set(value); - }, - "set_int", [](uevr::API::IConsoleVariable& self, int value) { - self.set(value); - }, - "set_ex", &uevr::API::IConsoleVariable::set_ex, - "get_int", &uevr::API::IConsoleVariable::get_int, - "get_float", &uevr::API::IConsoleVariable::get_float - ); - - m_lua.new_usertype("UEVR_IConsoleCommand", - sol::base_classes, sol::bases(), - "execute", &uevr::API::IConsoleCommand::execute - ); - - setup_callback_bindings(); - - auto out = m_lua.create_table(); - out["params"] = m_plugin_initialize_param; - out["api"] = uevr::API::get().get(); - - 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"); - } -} - -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"); - } -} - -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"); - } -} - -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"); - } -} - -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"); - } -} - -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"); - } -} - -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"); - } -} - -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"); - } -} \ No newline at end of file diff --git a/lua-api/examples/hello_world.lua b/lua-api/examples/hello_world.lua new file mode 100644 index 00000000..67a6c982 --- /dev/null +++ b/lua-api/examples/hello_world.lua @@ -0,0 +1,195 @@ +print("Initializing hello_world.lua") + +UEVR_UObjectHook.activate() + +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_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) + + local viewport = game_engine.GameViewport + if viewport == nil then + print("Viewport is nil") + return + end + local world = viewport.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.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()) + + local game_instance = game_engine.GameInstance + + if game_instance == nil then + print("GameInstance is nil") + return + end + + 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()) + --pawn.BaseEyeHeight = 0.0 + --pawn.bActorEnableCollision = not pawn.bActorEnableCollision + + 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)) + + 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)) + + 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 + + local net_driver_name = pawn.NetDriverName:to_string() + + print("NetDriverName: " .. net_driver_name) + end + + if player_controller ~= nil then + print("PlayerController: " .. player_controller:get_full_name()) + end + end + + print("Local players: " .. tostring(local_players)) + end + + last_level = level + + 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 diff --git a/lua-api/ScriptContext.hpp b/lua-api/lib/include/ScriptContext.hpp similarity index 71% rename from lua-api/ScriptContext.hpp rename to lua-api/lib/include/ScriptContext.hpp index 2a48a100..9827a137 100644 --- a/lua-api/ScriptContext.hpp +++ b/lua-api/lib/include/ScriptContext.hpp @@ -8,13 +8,14 @@ #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); - - ScriptContext(lua_State* l, UEVR_PluginInitializeParam* param = nullptr); + static std::shared_ptr create(lua_State* l, UEVR_PluginInitializeParam* param = nullptr) { + return std::shared_ptr(new ScriptContext(l, param)); + } + ScriptContext() = delete; virtual ~ScriptContext(); int setup_bindings(); @@ -42,7 +43,6 @@ class ScriptContext { } static void log(const std::string& message); - static void test_function(); template void add_callback(T1&& adder, T2&& cb) { @@ -54,7 +54,38 @@ 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: + // 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; @@ -69,6 +100,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 +113,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/lib/include/ScriptUtility.hpp b/lua-api/lib/include/ScriptUtility.hpp new file mode 100644 index 00000000..ab3e3505 --- /dev/null +++ b/lua-api/lib/include/ScriptUtility.hpp @@ -0,0 +1,20 @@ +#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, 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* 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); + 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..e499e9e3 --- /dev/null +++ b/lua-api/lib/include/datatypes/StructObject.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +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); +} \ No newline at end of file diff --git a/lua-api/lib/include/datatypes/Vector.hpp b/lua-api/lib/include/datatypes/Vector.hpp new file mode 100644 index 00000000..30547df3 --- /dev/null +++ b/lua-api/lib/include/datatypes/Vector.hpp @@ -0,0 +1,23 @@ +#pragma once + +#define GLM_ENABLE_EXPERIMENTAL + +#include +#include +#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 new file mode 100644 index 00000000..cf6ca8af --- /dev/null +++ b/lua-api/lib/src/ScriptContext.cpp @@ -0,0 +1,786 @@ +// This can be considered a binding of the C API. +#include +#include + +#include + +#include + +#include "datatypes/Vector.hpp" +#include "datatypes/StructObject.hpp" + +#include "ScriptUtility.hpp" +#include "ScriptContext.hpp" + +namespace uevr { +class ScriptContexts { +public: + void add(ScriptContext* ctx) { + std::scoped_lock _{mtx}; + + // Check if the context is already in the list + for (ScriptContext* c : list) { + if (c == ctx) { + return; + } + } + + list.push_back(ctx); + } + + void remove(ScriptContext* ctx) { + std::scoped_lock _{mtx}; + + 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->shared_from_this()); + } + } + +private: + std::vector list{}; + std::mutex mtx{}; +} g_contexts{}; + +ScriptContext::ScriptContext(lua_State* l, UEVR_PluginInitializeParam* param) + : 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); + return; + } + + const auto unreal_vr_backend = GetModuleHandleA("UEVRBackend.dll"); + + if (unreal_vr_backend == nullptr) { + return; + } + + m_plugin_initialize_param = (UEVR_PluginInitializeParam*)GetProcAddress(unreal_vr_backend, "g_plugin_initialize_param"); + uevr::API::initialize(m_plugin_initialize_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) { + for (auto& cb : m_callbacks_to_remove) { + m_plugin_initialize_param->functions->remove_callback(cb); + } + + m_callbacks_to_remove.clear(); + } + + 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::setup_callback_bindings() { + std::scoped_lock _{ m_mtx }; + + auto cbs = m_plugin_initialize_param->sdk->callbacks; + + 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", [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_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_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_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_viewport_client_draw", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_pre_viewport_client_draw_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_frame", [this](sol::function fn) { + std::scoped_lock _{ m_mtx }; + m_on_frame_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() { + 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, + "version", &UEVR_PluginInitializeParam::version, + "functions", &UEVR_PluginInitializeParam::functions, + "callbacks", &UEVR_PluginInitializeParam::callbacks, + "renderer", &UEVR_PluginInitializeParam::renderer, + "vr", &UEVR_PluginInitializeParam::vr, + "openvr", &UEVR_PluginInitializeParam::openvr, + "openxr", &UEVR_PluginInitializeParam::openxr, + "sdk", &UEVR_PluginInitializeParam::sdk + ); + + m_lua.new_usertype("UEVR_PluginVersion", + "major", &UEVR_PluginVersion::major, + "minor", &UEVR_PluginVersion::minor, + "patch", &UEVR_PluginVersion::patch + ); + + m_lua.new_usertype("UEVR_PluginFunctions", + "log_error", &UEVR_PluginFunctions::log_error, + "log_warn", &UEVR_PluginFunctions::log_warn, + "log_info", &UEVR_PluginFunctions::log_info, + "is_drawing_ui", &UEVR_PluginFunctions::is_drawing_ui + ); + + m_lua.new_usertype("UEVR_RendererData", + "renderer_type", &UEVR_RendererData::renderer_type, + "device", &UEVR_RendererData::device, + "swapchain", &UEVR_RendererData::swapchain, + "command_queue", &UEVR_RendererData::command_queue + ); + + m_lua.new_usertype("UEVR_SDKFunctions", + "get_uengine", &UEVR_SDKFunctions::get_uengine, + "set_cvar_int", &UEVR_SDKFunctions::set_cvar_int, + "get_uobject_array", &UEVR_SDKFunctions::get_uobject_array, + "get_player_controller", &UEVR_SDKFunctions::get_player_controller, + "get_local_pawn", &UEVR_SDKFunctions::get_local_pawn, + "spawn_object", &UEVR_SDKFunctions::spawn_object, + + "execute_command", &UEVR_SDKFunctions::execute_command, + "execute_command_ex", &UEVR_SDKFunctions::execute_command_ex, + + "get_console_manager", &UEVR_SDKFunctions::get_console_manager + ); + + m_lua.new_usertype("UEVR_UObjectHookFunctions", + "activate", &UEVR_UObjectHookFunctions::activate, + "exists", &UEVR_UObjectHookFunctions::exists, + "get_first_object_by_class", &UEVR_UObjectHookFunctions::get_first_object_by_class, + "get_first_object_by_class_name", &UEVR_UObjectHookFunctions::get_first_object_by_class_name + // The other functions are really C-oriented so... we will just wrap the C++ API for the rest + ); + + m_lua.new_usertype("UEVR_SDKData", + "functions", &UEVR_SDKData::functions, + "callbacks", &UEVR_SDKData::callbacks, + "uobject", &UEVR_SDKData::uobject, + "uobject_array", &UEVR_SDKData::uobject_array, + "ffield", &UEVR_SDKData::ffield, + "fproperty", &UEVR_SDKData::fproperty, + "ustruct", &UEVR_SDKData::ustruct, + "uclass", &UEVR_SDKData::uclass, + "ufunction", &UEVR_SDKData::ufunction, + "uobject_hook", &UEVR_SDKData::uobject_hook, + "ffield_class", &UEVR_SDKData::ffield_class, + "fname", &UEVR_SDKData::fname, + "console", &UEVR_SDKData::console + ); + + m_lua.new_usertype("UEVR_VRData", + "is_runtime_ready", &UEVR_VRData::is_runtime_ready, + "is_openvr", &UEVR_VRData::is_openvr, + "is_openxr", &UEVR_VRData::is_openxr, + "is_hmd_active", &UEVR_VRData::is_hmd_active, + "get_standing_origin", &UEVR_VRData::get_standing_origin, + "get_rotation_offset", &UEVR_VRData::get_rotation_offset, + "set_standing_origin", &UEVR_VRData::set_standing_origin, + "set_rotation_offset", &UEVR_VRData::set_rotation_offset, + "get_hmd_index", &UEVR_VRData::get_hmd_index, + "get_left_controller_index", &UEVR_VRData::get_left_controller_index, + "get_right_controller_index", &UEVR_VRData::get_right_controller_index, + "get_pose", &UEVR_VRData::get_pose, + "get_transform", &UEVR_VRData::get_transform, + "get_eye_offset", &UEVR_VRData::get_eye_offset, + "get_ue_projection_matrix", &UEVR_VRData::get_ue_projection_matrix, + "get_left_joystick_source", &UEVR_VRData::get_left_joystick_source, + "get_right_joystick_source", &UEVR_VRData::get_right_joystick_source, + "get_action_handle", &UEVR_VRData::get_action_handle, + "is_action_active", &UEVR_VRData::is_action_active, + "get_joystick_axis", &UEVR_VRData::get_joystick_axis, + "trigger_haptic_vibration", &UEVR_VRData::trigger_haptic_vibration, + "is_using_controllers", &UEVR_VRData::is_using_controllers, + "get_lowest_xinput_index", &UEVR_VRData::get_lowest_xinput_index, + "recenter_view", &UEVR_VRData::recenter_view, + "recenter_horizon", &UEVR_VRData::recenter_horizon, + "get_aim_method", &UEVR_VRData::get_aim_method, + "set_aim_method", &UEVR_VRData::set_aim_method, + "is_aim_allowed", &UEVR_VRData::is_aim_allowed, + "set_aim_allowed", &UEVR_VRData::set_aim_allowed + ); + + // TODO: Add operators to these types + m_lua.new_usertype("UEVR_Vector2f", + "x", &UEVR_Vector2f::x, + "y", &UEVR_Vector2f::y + ); + + m_lua.new_usertype("UEVR_Vector3f", + "x", &UEVR_Vector3f::x, + "y", &UEVR_Vector3f::y, + "z", &UEVR_Vector3f::z + ); + + m_lua.new_usertype("UEVR_Vector3d", + "x", &UEVR_Vector3d::x, + "y", &UEVR_Vector3d::y, + "z", &UEVR_Vector3d::z + ); + + m_lua.new_usertype("UEVR_Vector4f", + "x", &UEVR_Vector4f::x, + "y", &UEVR_Vector4f::y, + "z", &UEVR_Vector4f::z, + "w", &UEVR_Vector4f::w + ); + + m_lua.new_usertype("UEVR_Quaternionf", + "x", &UEVR_Quaternionf::x, + "y", &UEVR_Quaternionf::y, + "z", &UEVR_Quaternionf::z, + "w", &UEVR_Quaternionf::w + ); + + m_lua.new_usertype("UEVR_Rotatorf", + "pitch", &UEVR_Rotatorf::pitch, + "yaw", &UEVR_Rotatorf::yaw, + "roll", &UEVR_Rotatorf::roll + ); + + m_lua.new_usertype("UEVR_Rotatord", + "pitch", &UEVR_Rotatord::pitch, + "yaw", &UEVR_Rotatord::yaw, + "roll", &UEVR_Rotatord::roll + ); + + m_lua.new_usertype("UEVR_Matrix4x4f", + sol::meta_function::index, [](sol::this_state s, UEVR_Matrix4x4f& lhs, sol::object index_obj) -> sol::object { + if (!index_obj.is()) { + return sol::make_object(s, sol::lua_nil); + } + + const auto index = index_obj.as(); + + if (index >= 4) { + return sol::make_object(s, sol::lua_nil); + } + + return sol::make_object(s, &lhs.m[index]); + } + ); + + m_lua.new_usertype("UEVR_Matrix4x4d", + sol::meta_function::index, [](sol::this_state s, UEVR_Matrix4x4d& lhs, sol::object index_obj) -> sol::object { + if (!index_obj.is()) { + return sol::make_object(s, sol::lua_nil); + } + + const auto index = index_obj.as(); + + if (index >= 4) { + return sol::make_object(s, sol::lua_nil); + } + + return sol::make_object(s, &lhs.m[index]); + } + ); + + m_lua.new_usertype("UEVR_FName", + "to_string", &uevr::API::FName::to_string + ); + + 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, + "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, + "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); + }, + "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", [](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); + }, + 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 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()) { + return; + } + + const auto name = utility::widen(index_obj.as()); + lua::utility::set_property(s, self, name, value); + } + ); + + m_lua.new_usertype("UEVR_UStruct", + sol::base_classes, sol::bases(), + "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& 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 + ); + + m_lua.new_usertype("UEVR_UClass", + sol::base_classes, sol::bases(), + "static_class", &uevr::API::UClass::static_class, + "get_class_default_object", &uevr::API::UClass::get_class_default_object, + "get_objects_matching", &uevr::API::UClass::get_objects_matching, + "get_first_object_matching", &uevr::API::UClass::get_first_object_matching + ); + + 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 lua::utility::call_function(s, obj, fn, args); + }, + sol::base_classes, sol::bases(), + "static_class", &uevr::API::UFunction::static_class, + "call", &uevr::API::UFunction::call, + "get_native_function", &uevr::API::UFunction::get_native_function + ); + + m_lua.new_usertype("UEVR_FField", + "get_next", &uevr::API::FField::get_next, + "get_fname", &uevr::API::FField::get_fname, + "get_class", &uevr::API::FField::get_class + ); + + m_lua.new_usertype("UEVR_FFieldClass", + "get_fname", &uevr::API::FFieldClass::get_fname, + "get_name", &uevr::API::FFieldClass::get_name + ); + + m_lua.new_usertype("UEVR_FConsoleManager", + "get_console_objects", &uevr::API::FConsoleManager::get_console_objects, + "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", + "as_command", &uevr::API::IConsoleObject::as_command + ); + + m_lua.new_usertype("UEVR_IConsoleVariable", + sol::base_classes, sol::bases(), + "set", [](sol::this_state s, uevr::API::IConsoleVariable* self, sol::object value) { + if (value.is()) { + self->set(value.as()); + } else if (value.is()) { + self->set(value.as()); + } else if (value.is()) { + self->set(value.as()); + } else if (value.is()) { + const auto str = utility::widen(value.as()); + self->set(str); + } else { + throw sol::error("Invalid type for IConsoleVariable::set"); + } + }, + "set_float", [](uevr::API::IConsoleVariable& self, float value) { + self.set(value); + }, + "set_int", [](uevr::API::IConsoleVariable& self, int value) { + self.set(value); + }, + "set_ex", &uevr::API::IConsoleVariable::set_ex, + "get_int", &uevr::API::IConsoleVariable::get_int, + "get_float", &uevr::API::IConsoleVariable::get_float + ); + + m_lua.new_usertype("UEVR_IConsoleCommand", + sol::base_classes, sol::bases(), + "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_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", [](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(); + } + + 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; + 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", [](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, + "get_local_pawn", &uevr::API::get_local_pawn, + "spawn_object", &uevr::API::spawn_object, + "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 + ); + + 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"], + "UObjectHook", m_lua["UEVR_UObjectHook"] + ); + + 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) { + 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) { + 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) { + 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) { + 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) { + 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 { + 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 (...) { + 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) { + 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 { + 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 (...) { + 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) { + 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) { + 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/lua-api/lib/src/ScriptUtility.cpp b/lua-api/lib/src/ScriptUtility.cpp new file mode 100644 index 00000000..5b845c2b --- /dev/null +++ b/lua-api/lib/src/ScriptUtility.cpp @@ -0,0 +1,591 @@ +#include +#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"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: + 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); + } + } + + auto struct_object = lua::datatypes::StructObject{struct_data, struct_desc}; + + return sol::make_object(s, struct_object); + } + 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, void* self, uevr::API::UStruct* c, const std::wstring& name) { + 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); +} + +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); + } + + 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) { + 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(desc->get_fname()->to_string()))); + } + + 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: + *(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; + 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: + 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; + 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)"); + 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 +} + +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(); + + 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); + } + + uevr::API::FProperty* return_prop{nullptr}; + bool ret_is_bool{false}; + bool ret_is_array{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; + } else if (arg_c_name == L"ArrayProperty") { + ret_is_array = true; + } + + continue; + } + + const auto arg_hash = ::utility::hash(arg_c_name); + const auto offset = prop_desc->get_offset(); + + if (arg_hash == 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"); + } + } else { + set_property(s, params.data(), fn, prop_desc, args[args_index++]); + } + } + + 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())); + } + + 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); +} + +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..b2903c4c --- /dev/null +++ b/lua-api/lib/src/datatypes/StructObject.cpp @@ -0,0 +1,76 @@ +#include + +#include +#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; }, + "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); + }, + sol::meta_function::construct, sol::constructors() + ); + } +} \ No newline at end of file diff --git a/lua-api/lib/src/datatypes/Vector.cpp b/lua-api/lib/src/datatypes/Vector.cpp new file mode 100644 index 00000000..5cab4841 --- /dev/null +++ b/lua-api/lib/src/datatypes/Vector.cpp @@ -0,0 +1,37 @@ +#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, \ + "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 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..30ef9980 --- /dev/null +++ b/src/mods/LuaLoader.cpp @@ -0,0 +1,284 @@ +#include +#include + +#include "Framework.hpp" +#include "PluginLoader.hpp" +#include "LuaLoader.hpp" + +#include + +#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() { + // 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) { + 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_sidebar_entry(std::string_view in_entry) { + if (in_entry == "Main") { + 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."); + } + + ImGui::TreePop(); + } + + + if (in_entry == "Script UI") { + std::scoped_lock _{m_access_mutex}; + + if (m_states.empty()) { + return; + } + + for (auto& state : m_states) { + state->on_draw_ui(); + } + + ImGui::TreePop(); + } +} + +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_main_state != nullptr) { + /*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(); + + 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); + + //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..b7ad5704 --- /dev/null +++ b/src/mods/LuaLoader.hpp @@ -0,0 +1,129 @@ +#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; + + 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; + 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 3634b24e..f04984eb 100644 --- a/src/mods/PluginLoader.cpp +++ b/src/mods/PluginLoader.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include "pluginloader/FFakeStereoRenderingFunctions.hpp" #include "pluginloader/FRenderTargetPoolHook.hpp" @@ -496,22 +498,30 @@ 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; } - 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) { @@ -535,7 +545,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; @@ -545,12 +558,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; @@ -861,6 +876,21 @@ 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_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, @@ -881,7 +911,9 @@ 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, + &g_fenum_property_functions }; namespace uevr { @@ -1745,7 +1777,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 dda46090..802d99da 100644 --- a/src/mods/PluginLoader.hpp +++ b/src/mods/PluginLoader.hpp @@ -15,6 +15,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(); @@ -86,13 +88,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; @@ -141,29 +143,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: