diff --git a/BG3Extender/GameDefinitions/PropertyMaps/UI.inl b/BG3Extender/GameDefinitions/PropertyMaps/UI.inl index e27d5044..c125d044 100644 --- a/BG3Extender/GameDefinitions/PropertyMaps/UI.inl +++ b/BG3Extender/GameDefinitions/PropertyMaps/UI.inl @@ -121,6 +121,9 @@ INHERIT(Noesis::DependencyObject) P_FREE_GETTER(VisualParent, Noesis::VisualHelpers::GetVisualParent) P_FREE_GETTER(VisualChildrenCount, Noesis::VisualHelpers::GetVisualChildrenCount) P_FUN(VisualChild, Noesis::VisualHelpers::GetVisualChild) +P_FUN(PointFromScreen, Noesis::VisualHelpers::PointFromScreen) +P_FUN(PointToScreen, Noesis::VisualHelpers::PointToScreen) +P_FUN(HitTest, Noesis::VisualHelpers::HitTest) END_CLS() diff --git a/BG3Extender/GameDefinitions/Symbols.h b/BG3Extender/GameDefinitions/Symbols.h index fe75701a..de2838b1 100644 --- a/BG3Extender/GameDefinitions/Symbols.h +++ b/BG3Extender/GameDefinitions/Symbols.h @@ -154,6 +154,9 @@ namespace bg3se void* Noesis__GUI__LoadXaml{ nullptr }; void* Noesis__Visual__AddVisualChild{ nullptr }; void* Noesis__Visual__RemoveVisualChild{ nullptr }; + void* Noesis__Visual__PointFromScreen{ nullptr }; + void* Noesis__Visual__PointToScreen{ nullptr }; + void* Noesis__VisualTreeHelper__HitTest{ nullptr }; GlobalSwitches** ls__GlobalSwitches{ nullptr }; diff --git a/BG3Extender/GameDefinitions/UI.h b/BG3Extender/GameDefinitions/UI.h index ac189ecd..f3171c81 100644 --- a/BG3Extender/GameDefinitions/UI.h +++ b/BG3Extender/GameDefinitions/UI.h @@ -16,6 +16,7 @@ #include #include #include +#include BEGIN_BARE_NS(Noesis) @@ -29,6 +30,8 @@ struct GridLengthHelper }; using LoadXamlProc = Ptr* (Ptr& ret, char const* path); +using VisualPointConversionProc = Point (Visual::*)(Point const& point) const; +using VisualTreeHelperHitTestProc = HitTestResult (*)(Visual* visual, Point const& point); struct SymbolManagerInternals { @@ -213,6 +216,9 @@ struct VisualHelpers static Visual* GetVisualParent(Visual const* o); static uint32_t GetVisualChildrenCount(lua_State* L, Visual const* o); static Visual* GetVisualChild(Visual const* o, uint32_t index); + static Point PointFromScreen(lua_State* L, Visual const* o, Point const& point); + static Point PointToScreen(lua_State* L, Visual const* o, Point const& point); + static Visual* HitTest(lua_State* L, Visual* o, Point const& point); }; struct UIElementDataHelpers diff --git a/BG3Extender/GameHooks/BinaryMappings.xml b/BG3Extender/GameHooks/BinaryMappings.xml index 412ca6be..6f0fb777 100644 --- a/BG3Extender/GameHooks/BinaryMappings.xml +++ b/BG3Extender/GameHooks/BinaryMappings.xml @@ -1010,6 +1010,42 @@ + + @target 48 89 5c 24 18 // mov [rsp+18h], rbx + 57 // push rdi + b8 00 01 00 00 // mov eax, 100h + e8 ?? ?? ?? ?? // call __alloca_probe + 48 2b e0 // sub rsp, rax + 0f 29 b4 24 f0 00 00 00 // movaps [rsp+0F0h], xmm6 + 0f 29 bc 24 e0 00 00 00 // movaps [rsp+0E0h], xmm7 + 48 8b 05 ?? ?? ?? ?? // mov rax, [__security_cookie] + 48 33 c4 // xor rax, rsp + 48 89 84 24 d0 00 00 00 // mov [rsp+0D0h], rax + f3 41 0f 10 30 // movss xmm6, dword ptr [r8] + 48 8b fa // mov rdi, rdx; Point return buffer + f3 41 0f 10 78 04 // movss xmm7, dword ptr [r8+4] + 48 8d 54 24 60 // lea rdx, [rsp+60h] + 48 8b d9 // mov rbx, rcx; Visual this + + + + + 4c 8d 47 18 // lea r8, [rdi+18h]; screen Point + 48 8d 54 24 20 // lea rdx, [rsp+20h]; Point return buffer + @ref0 e8 ?? ?? ?? ?? // call Noesis::Visual::PointFromScreen + 4c 8b c0 // mov r8, rax; local Point + 48 8b 97 a8 00 00 00 // mov rdx, [rdi+0A8h]; Visual + 48 8d 4c 24 28 // lea rcx, [rsp+28h]; HitTestResult return buffer + @ref1 e8 ?? ?? ?? ?? // call Noesis::VisualTreeHelper::HitTest + 48 83 38 00 // cmp qword ptr [rax], 0; result.visualHit + 0f 85 ?? ?? ?? ?? // jnz + + + + 48 8b 83 a8 01 00 00 // mov rax, [rbx+1A8h] diff --git a/BG3Extender/GameHooks/DataLibrariesBG3Game.cpp b/BG3Extender/GameHooks/DataLibrariesBG3Game.cpp index 98c6ee97..c762abc1 100644 --- a/BG3Extender/GameHooks/DataLibrariesBG3Game.cpp +++ b/BG3Extender/GameHooks/DataLibrariesBG3Game.cpp @@ -218,6 +218,9 @@ namespace bg3se SYM_OFF(Noesis__GUI__LoadXaml); SYM_OFF(Noesis__Visual__AddVisualChild); SYM_OFF(Noesis__Visual__RemoveVisualChild); + SYM_OFF(Noesis__Visual__PointFromScreen); + SYM_OFF(Noesis__Visual__PointToScreen); + SYM_OFF(Noesis__VisualTreeHelper__HitTest); SYM_OFF(AppInstance); diff --git a/BG3Extender/Lua/Libs/ClientUI/NsHelpers.inl b/BG3Extender/Lua/Libs/ClientUI/NsHelpers.inl index 92806506..47e49ffe 100644 --- a/BG3Extender/Lua/Libs/ClientUI/NsHelpers.inl +++ b/BG3Extender/Lua/Libs/ClientUI/NsHelpers.inl @@ -714,6 +714,73 @@ Visual* VisualHelpers::GetVisualChild(Visual const* o, uint32_t index) } } +namespace +{ + void EnsureVisualClientThread(lua_State* L, char const* functionName) + { + auto state = lua::State::FromLua(L); + if (state == nullptr + || !state->IsClient() + || GetCurrentContextType() != ContextType::Client) { + luaL_error(L, "%s() can only be called from Client Lua", functionName); + return; + } + + if (state->GetExtensionState().GetOwningThread() != GetCurrentThreadId()) { + luaL_error(L, "%s() can only be called on the Client owning thread", functionName); + } + } + + void* RequireNoesisMapping(lua_State* L, void* address, char const* symbol) + { + if (address == nullptr) { + luaL_error(L, "%s mapping is unavailable for the current game version", symbol); + return nullptr; + } + + return address; + } + + Point InvokeVisualPointConversion(Visual const* visual, Point const& point, void* address) + { + // Visual has multiple inheritance in its ancestry, so a MSVC member-function + // pointer may be wider than a code pointer. Zero-initialize its adjustment + // fields and copy only the mapped function address into its code slot. + VisualPointConversionProc proc{}; + static_assert(sizeof(proc) >= sizeof(address)); + memcpy(&proc, &address, sizeof(address)); + return (visual->*proc)(point); + } +} + +Point VisualHelpers::PointFromScreen(lua_State* L, Visual const* o, Point const& point) +{ + EnsureVisualClientThread(L, "PointFromScreen"); + auto address = RequireNoesisMapping(L, + GetStaticSymbols().Noesis__Visual__PointFromScreen, + "Noesis::Visual::PointFromScreen"); + return InvokeVisualPointConversion(o, point, address); +} + +Point VisualHelpers::PointToScreen(lua_State* L, Visual const* o, Point const& point) +{ + EnsureVisualClientThread(L, "PointToScreen"); + auto address = RequireNoesisMapping(L, + GetStaticSymbols().Noesis__Visual__PointToScreen, + "Noesis::Visual::PointToScreen"); + return InvokeVisualPointConversion(o, point, address); +} + +Visual* VisualHelpers::HitTest(lua_State* L, Visual* o, Point const& point) +{ + EnsureVisualClientThread(L, "HitTest"); + auto address = RequireNoesisMapping(L, + GetStaticSymbols().Noesis__VisualTreeHelper__HitTest, + "Noesis::VisualTreeHelper::HitTest"); + auto proc = reinterpret_cast(address); + return proc(o, point).visualHit; +} + RoutedEvent* UIElementDataHelpers::GetEvent(UIElementData const* o, Symbol evt) { auto it = o->mEvents.Find(evt); diff --git a/BG3Extender/Lua/LuaSerializers.h b/BG3Extender/Lua/LuaSerializers.h index 75d74f21..aad59a7b 100644 --- a/BG3Extender/Lua/LuaSerializers.h +++ b/BG3Extender/Lua/LuaSerializers.h @@ -115,6 +115,14 @@ namespace bg3se::lua inline LuaSerializer& serialize(LuaSerializer& s, STDWString& v) { return s.Visit(v); } inline LuaSerializer& serialize(LuaSerializer& s, StringView& v) { return s.Visit(v); } inline LuaSerializer& serialize(LuaSerializer& s, Noesis::Symbol& v) { return s.Visit(v); } + inline LuaSerializer& serialize(LuaSerializer& s, Noesis::Point& v) + { + s.BeginObject(); + s.VisitProperty("X", v.x); + s.VisitProperty("Y", v.y); + s.EndObject(); + return s; + } inline LuaSerializer& serialize(LuaSerializer& s, ImguiHandle& v) { return s.Visit(v); } inline LuaSerializer& serialize(LuaSerializer& s, Path& v) { return s.Visit(v); } inline LuaSerializer& serialize(LuaSerializer& s, Guid& v) { return s.Visit(v); }