From 0602f33cc1d2009cc1a9290e307f2a5ef5fb31bb Mon Sep 17 00:00:00 2001 From: psiberx Date: Fri, 19 Sep 2025 23:48:49 +0300 Subject: [PATCH 01/10] Use lua env from override registration --- src/scripting/FunctionOverride.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripting/FunctionOverride.cpp b/src/scripting/FunctionOverride.cpp index 2716d4ab..e79d2a68 100644 --- a/src/scripting/FunctionOverride.cpp +++ b/src/scripting/FunctionOverride.cpp @@ -463,14 +463,14 @@ sol::function FunctionOverride::WrapNextOverride( { return MakeSolFunction( aLuaState, - [&](sol::variadic_args aWrapArgs, sol::this_state aState, sol::this_environment aEnv) -> sol::variadic_results + [&](sol::variadic_args aWrapArgs, sol::this_state aState) -> sol::variadic_results { std::string errorMessage; sol::variadic_results results = RTTIHelper::Get().ExecuteFunction(apRealFunction, apRealContext, aWrapArgs, 0, errorMessage); if (!errorMessage.empty()) { - const sol::environment cEnv = aEnv; + const sol::environment cEnv = aChain.Overrides.front()->Environment; const auto logger = cEnv["__logger"].get>(); logger->error("Error: {}", errorMessage); From 4944e05b7488bc3b1cd11f10cb6729f8e2cac7f9 Mon Sep 17 00:00:00 2001 From: psiberx Date: Fri, 19 Sep 2025 23:49:25 +0300 Subject: [PATCH 02/10] Fix TweakDBID ctor crash --- src/reverse/BasicTypes.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reverse/BasicTypes.h b/src/reverse/BasicTypes.h index d1f82c85..8502bd98 100644 --- a/src/reverse/BasicTypes.h +++ b/src/reverse/BasicTypes.h @@ -132,14 +132,14 @@ struct TweakDBID TweakDBID(const std::string_view aName) { - name_hash = RED4ext::CRC32(aName.data(), 0); + name_hash = RED4ext::CRC32(reinterpret_cast(aName.data()), aName.size(), 0); name_length = static_cast(aName.size()); unk5 = unk7 = 0; } TweakDBID(const TweakDBID& aBase, const std::string_view aName) { - name_hash = RED4ext::CRC32(aName.data(), aBase.name_hash); + name_hash = RED4ext::CRC32(reinterpret_cast(aName.data()), aName.size(), aBase.name_hash); name_length = static_cast(aName.size() + aBase.name_length); unk5 = unk7 = 0; } From 11552b650e7e734fe54726693399dcf1a538d00c Mon Sep 17 00:00:00 2001 From: psiberx Date: Fri, 19 Sep 2025 23:49:42 +0300 Subject: [PATCH 03/10] Fix NewProxy crash --- src/reverse/NativeProxy.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/reverse/NativeProxy.cpp b/src/reverse/NativeProxy.cpp index a09cbe5f..85abf32d 100644 --- a/src/reverse/NativeProxy.cpp +++ b/src/reverse/NativeProxy.cpp @@ -158,15 +158,12 @@ void NativeProxy::Callback(RED4ext::IScriptable* apSelf, RED4ext::CStackFrame* a if (functionIt == self->m_functions.end()) return; - TiltedPhoques::StackAllocator<1 << 13> allocator; - const auto pAllocator = TiltedPhoques::Allocator::Get(); - TiltedPhoques::Allocator::Set(&allocator); - TiltedPhoques::Vector args(0); - TiltedPhoques::Allocator::Set(pAllocator); - auto lockedState = self->m_lua.Lock(); auto& luaState = lockedState.Get(); + TiltedPhoques::Vector args(0); + args.reserve(apFrame->func->params.size); + for (const auto& param : apFrame->func->params) { RED4ext::CStackType arg; From 515f19148945f0dc2635deca2587bfa445b7ad2c Mon Sep 17 00:00:00 2001 From: psiberx Date: Fri, 19 Sep 2025 23:50:52 +0300 Subject: [PATCH 04/10] Add basic bitfield support --- src/reverse/Converter.cpp | 3 +- src/reverse/Converter.h | 69 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/reverse/Converter.cpp b/src/reverse/Converter.cpp index 129e13ce..e2712d62 100644 --- a/src/reverse/Converter.cpp +++ b/src/reverse/Converter.cpp @@ -12,7 +12,8 @@ auto s_metaVisitor = }(LuaRED(), LuaRED(), LuaRED(), LuaRED(), LuaRED(), LuaRED(), LuaRED(), LuaRED(), LuaRED(), LuaRED(), LuaRED(), LuaRED(), LuaRED(), LuaRED(), LuaRED(), LuaRED(), LuaRED(), - LuaRED(), LuaRED(), CNameConverter(), TweakDBIDConverter(), EnumConverter(), ClassConverter(), + LuaRED(), LuaRED(), CNameConverter(), TweakDBIDConverter(), EnumConverter(), BitFieldConverter(), + ClassConverter(), RawConverter() // Should always be last resort ); diff --git a/src/reverse/Converter.h b/src/reverse/Converter.h index 33a2d240..924697bb 100644 --- a/src/reverse/Converter.h +++ b/src/reverse/Converter.h @@ -183,6 +183,75 @@ struct EnumConverter : LuaRED } }; +// Specialization manages wrapping and converting RT_BitField +struct BitFieldConverter : LuaRED +{ + sol::object ToLua(RED4ext::CStackType& aResult, TiltedPhoques::Locked& aLua) + { + uint64_t value; + + switch (m_pRtti->GetSize()) + { + case sizeof(uint8_t): value = *static_cast(aResult.value); break; + case sizeof(uint16_t): value = *static_cast(aResult.value); break; + case sizeof(uint32_t): value = *static_cast(aResult.value); break; + case sizeof(uint64_t): value = *static_cast(aResult.value); break; + } + + auto res{aLua.Get().script(fmt::format("return {}ull", value))}; + assert(res.valid()); + return res.get(); + } + + RED4ext::CStackType ToRED(sol::object aObject, RED4ext::CBaseRTTIType* apRtti, TiltedPhoques::Allocator* apAllocator) + { + RED4ext::CStackType result; + result.type = apRtti; + result.value = apAllocator->Allocate(apRtti->GetSize()); + + ToRED(aObject, &result); + + return result; + } + + void ToRED(sol::object aObject, RED4ext::CStackType* apType) + { + uint64_t value = 0; + + if (aObject.get_type() == sol::type::number) + { + value = aObject.as(); + } + else if (IsLuaCData(aObject)) + { + sol::state_view v(aObject.lua_state()); + std::string str = v["tostring"](aObject); + value = std::stoull(str); + } + + switch (m_pRtti->GetSize()) + { + case sizeof(uint8_t): *static_cast(apType->value) = static_cast(value); break; + case sizeof(uint16_t): *static_cast(apType->value) = static_cast(value); break; + case sizeof(uint32_t): *static_cast(apType->value) = static_cast(value); break; + case sizeof(uint64_t): *static_cast(apType->value) = value; break; + } + } + + size_t Size() const noexcept { return m_pRtti ? m_pRtti->GetSize() : 0; } + + bool Is(RED4ext::CBaseRTTIType* apRtti) const + { + if (apRtti->GetType() == RED4ext::ERTTIType::BitField) + { + m_pRtti = apRtti; + return true; + } + + return false; + } +}; + // Specialization manages wrapping RT_Class struct ClassConverter : LuaRED { From b5a3f11b94d4a195ce03253f6eb1a1d908311731 Mon Sep 17 00:00:00 2001 From: psiberx Date: Fri, 19 Sep 2025 23:52:02 +0300 Subject: [PATCH 05/10] Fix UnknownType memory leak --- src/reverse/Type.cpp | 21 +++++++++++++++------ src/reverse/Type.h | 7 ++++--- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/reverse/Type.cpp b/src/reverse/Type.cpp index 745cd25e..51d80867 100644 --- a/src/reverse/Type.cpp +++ b/src/reverse/Type.cpp @@ -1,8 +1,8 @@ #include #include "Type.h" - #include "RTTIHelper.h" +#include "CET.h" #include @@ -278,12 +278,21 @@ sol::object ClassType::NewIndex_Impl(const std::string& acName, sol::object aPar return Type::NewIndex_Impl(acName, aParam); } -UnknownType::UnknownType(const TiltedPhoques::Lockable::Ref& aView, RED4ext::CBaseRTTIType* apClass, RED4ext::ScriptInstance apInstance) - : Type(aView, apClass) +UnknownType::UnknownType(const TiltedPhoques::Lockable::Ref& aView, RED4ext::CBaseRTTIType* apType, RED4ext::ScriptInstance apInstance) + : Type(aView, apType) { - // Hack for now until we use their allocators - m_pInstance = std::make_unique(apClass->GetSize()); - memcpy(m_pInstance.get(), apInstance, apClass->GetSize()); + m_pInstance = m_pType->GetAllocator()->AllocAligned(m_pType->GetSize(), m_pType->GetAlignment()).memory; + m_pType->Construct(m_pInstance); + m_pType->Assign(m_pInstance, apInstance); +} + +UnknownType::~UnknownType() +{ + if (m_pInstance && CET::IsRunning()) + { + m_pType->Destruct(m_pInstance); + m_pType->GetAllocator()->Free(m_pInstance); + } } Type::Descriptor UnknownType::Dump(bool aWithHashes) const diff --git a/src/reverse/Type.h b/src/reverse/Type.h index bd17c644..0f1d41f8 100644 --- a/src/reverse/Type.h +++ b/src/reverse/Type.h @@ -56,11 +56,12 @@ struct ClassType : Type struct UnknownType : Type { - UnknownType(const TiltedPhoques::Lockable::Ref& aView, RED4ext::CBaseRTTIType* apClass, RED4ext::ScriptInstance apInstance); + UnknownType(const TiltedPhoques::Lockable::Ref& aView, RED4ext::CBaseRTTIType* apType, RED4ext::ScriptInstance apInstance); + ~UnknownType() override; Descriptor Dump(bool aWithHashes) const override; - RED4ext::ScriptInstance GetHandle() const override { return m_pInstance.get(); } + RED4ext::ScriptInstance GetHandle() const override { return m_pInstance; } private: - std::unique_ptr m_pInstance; + void* m_pInstance; }; \ No newline at end of file From 9ea8214f08ca838923a838abc3fb96955843180b Mon Sep 17 00:00:00 2001 From: psiberx Date: Sat, 20 Sep 2025 14:02:40 +0300 Subject: [PATCH 06/10] Remove dead code --- src/reverse/Converter.cpp | 20 -------- src/reverse/Converter.h | 100 +++----------------------------------- src/reverse/LuaRED.h | 48 ------------------ 3 files changed, 6 insertions(+), 162 deletions(-) diff --git a/src/reverse/Converter.cpp b/src/reverse/Converter.cpp index e2712d62..598b48a2 100644 --- a/src/reverse/Converter.cpp +++ b/src/reverse/Converter.cpp @@ -86,23 +86,3 @@ RED4ext::CStackType Converter::ToRED(sol::object aObject, RED4ext::CBaseRTTIType return r; } - -void Converter::ToRED(sol::object aObject, RED4ext::CStackType* apType) -{ - auto initStackType = [&](auto& x) - { - if (x.Is(apType->type)) - { - x.ToRED(aObject, apType); - return true; - } - return false; - }; - - auto f = [initStackType](auto&&... xs) - { - (... && !initStackType(xs)); - }; - - s_metaVisitor(f); -} diff --git a/src/reverse/Converter.h b/src/reverse/Converter.h index 924697bb..ab861c0e 100644 --- a/src/reverse/Converter.h +++ b/src/reverse/Converter.h @@ -15,7 +15,6 @@ namespace Converter size_t Size(RED4ext::CBaseRTTIType* apRtti); sol::object ToLua(RED4ext::CStackType& aResult, TiltedPhoques::Locked& aLua); RED4ext::CStackType ToRED(sol::object aObject, RED4ext::CBaseRTTIType* apRtti, TiltedPhoques::Allocator* apAllocator); -void ToRED(sol::object aObject, RED4ext::CStackType* apType); } // namespace Converter // Specialization manages special case implicit casting @@ -38,20 +37,6 @@ struct CNameConverter : LuaRED return result; } - - void ToRED(sol::object aObject, RED4ext::CStackType* apType) - { - if (aObject != sol::nil && aObject.get_type() == sol::type::string) // CName from String implicit cast - { - sol::state_view v(aObject.lua_state()); - std::string str = v["tostring"](aObject); - *static_cast(apType->value) = CName(str); - } - else - { - return LuaRED::ToRED(aObject, apType); - } - } }; // Specialization manages special case implicit casting for TweakDBID @@ -72,18 +57,6 @@ struct TweakDBIDConverter : LuaRED return result; } - - void ToRED(sol::object aObject, RED4ext::CStackType* apType) - { - if (aObject != sol::nil && aObject.get_type() == sol::type::string) - { - *static_cast(apType->value) = TweakDBID(aObject.as()); - } - else - { - LuaRED::ToRED(aObject, apType); - } - } }; // Specialization manages wrapping and converting RT_Enum @@ -137,38 +110,6 @@ struct EnumConverter : LuaRED return result; } - void ToRED(sol::object aObject, RED4ext::CStackType* apType) - { - if (aObject.is()) - { - auto* pEnum = aObject.as(); - if (pEnum->GetType() == apType->type) - { - pEnum->Set(*apType); - } - } - else if (aObject == sol::nil) - { - auto* enumType = static_cast(apType->type); - const Enum en(enumType, 0); - en.Set(*apType); - } - else if (aObject.get_type() == sol::type::number) // Enum from number cast - { - auto* enumType = static_cast(apType->type); - const Enum en(enumType, aObject.as()); - en.Set(*apType); - } - else if (aObject.get_type() == sol::type::string) // Enum from string cast - { - auto* enumType = static_cast(apType->type); - sol::state_view v(aObject.lua_state()); - std::string str = v["tostring"](aObject); - const Enum en(enumType, str); - en.Set(*apType); - } - } - size_t Size() const noexcept { return m_pRtti ? m_pRtti->GetSize() : 0; } bool Is(RED4ext::CBaseRTTIType* apRtti) const @@ -209,13 +150,6 @@ struct BitFieldConverter : LuaRED result.type = apRtti; result.value = apAllocator->Allocate(apRtti->GetSize()); - ToRED(aObject, &result); - - return result; - } - - void ToRED(sol::object aObject, RED4ext::CStackType* apType) - { uint64_t value = 0; if (aObject.get_type() == sol::type::number) @@ -231,11 +165,13 @@ struct BitFieldConverter : LuaRED switch (m_pRtti->GetSize()) { - case sizeof(uint8_t): *static_cast(apType->value) = static_cast(value); break; - case sizeof(uint16_t): *static_cast(apType->value) = static_cast(value); break; - case sizeof(uint32_t): *static_cast(apType->value) = static_cast(value); break; - case sizeof(uint64_t): *static_cast(apType->value) = value; break; + case sizeof(uint8_t): *static_cast(result.value) = static_cast(value); break; + case sizeof(uint16_t): *static_cast(result.value) = static_cast(value); break; + case sizeof(uint32_t): *static_cast(result.value) = static_cast(value); break; + case sizeof(uint64_t): *static_cast(result.value) = value; break; } + + return result; } size_t Size() const noexcept { return m_pRtti ? m_pRtti->GetSize() : 0; } @@ -297,18 +233,6 @@ struct ClassConverter : LuaRED return result; } - void ToRED(sol::object aObject, RED4ext::CStackType* apType) - { - if (aObject.is()) - { - apType->value = aObject.as()->GetHandle(); - } - else - { - apType->value = nullptr; - } - } - size_t Size() const noexcept { return m_pRtti ? m_pRtti->GetSize() : 0; } bool Is(RED4ext::CBaseRTTIType* apRtti) const @@ -347,18 +271,6 @@ struct RawConverter : LuaRED return result; } - void ToRED(sol::object aObject, RED4ext::CStackType* apType) - { - if (aObject.is()) - { - apType->value = aObject.as()->GetHandle(); - } - else - { - apType->value = nullptr; - } - } - size_t Size() const noexcept { return m_pRtti ? m_pRtti->GetSize() : 0; } bool Is(RED4ext::CBaseRTTIType* apRtti) const diff --git a/src/reverse/LuaRED.h b/src/reverse/LuaRED.h index 9b18b4fa..383b6202 100644 --- a/src/reverse/LuaRED.h +++ b/src/reverse/LuaRED.h @@ -77,54 +77,6 @@ template struct LuaRED return result; } - void ToRED(sol::object aObject, RED4ext::CStackType* apType) - { - if (aObject == sol::nil) - { - *reinterpret_cast(apType->value) = T{}; - } - else if constexpr (std::is_integral_v && sizeof(T) == sizeof(uint64_t)) - { - if (aObject.get_type() == sol::type::number) - { - sol::state_view v(aObject.lua_state()); - double value = v["tonumber"](aObject); - *reinterpret_cast(apType->value) = static_cast(value); - } - else if (IsLuaCData(aObject)) - { - sol::state_view v(aObject.lua_state()); - std::string str = v["tostring"](aObject); - if constexpr (std::is_signed_v) - *reinterpret_cast(apType->value) = std::stoll(str); - else - *reinterpret_cast(apType->value) = std::stoull(str); - } - } - else if constexpr (std::is_same_v) - { - if (aObject.get_type() == sol::type::boolean) - *reinterpret_cast(apType->value) = aObject.as(); - } - else if constexpr (std::is_arithmetic_v) - { - if (aObject.get_type() == sol::type::number) - { - *reinterpret_cast(apType->value) = aObject.as(); - } - else if (IsLuaCData(aObject)) - { - sol::state_view v(aObject.lua_state()); - double value = v["tonumber"](aObject); - *reinterpret_cast(apType->value) = static_cast(value); - } - } - else if (aObject.is()) - { - *reinterpret_cast(apType->value) = aObject.as(); - } - } - size_t Size() const noexcept { return sizeof(T); } bool Is(RED4ext::CBaseRTTIType* apRtti) const From 8873f4e52055ae662eb9ce82cea757c6a30a8172 Mon Sep 17 00:00:00 2001 From: psiberx Date: Sun, 21 Sep 2025 16:55:39 +0300 Subject: [PATCH 07/10] Fix UnknownType move semantic --- src/reverse/Type.cpp | 12 ++++++++++++ src/reverse/Type.h | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/reverse/Type.cpp b/src/reverse/Type.cpp index 51d80867..af20e5ee 100644 --- a/src/reverse/Type.cpp +++ b/src/reverse/Type.cpp @@ -286,6 +286,13 @@ UnknownType::UnknownType(const TiltedPhoques::LockableAssign(m_pInstance, apInstance); } +UnknownType::UnknownType(UnknownType&& aOther) noexcept + : Type(aOther) + , m_pInstance(aOther.m_pInstance) +{ + aOther.m_pInstance = nullptr; +} + UnknownType::~UnknownType() { if (m_pInstance && CET::IsRunning()) @@ -295,6 +302,11 @@ UnknownType::~UnknownType() } } +RED4ext::ScriptInstance UnknownType::GetHandle() const +{ + return m_pInstance; +} + Type::Descriptor UnknownType::Dump(bool aWithHashes) const { auto descriptor = Type::Dump(aWithHashes); diff --git a/src/reverse/Type.h b/src/reverse/Type.h index 0f1d41f8..105b3d3f 100644 --- a/src/reverse/Type.h +++ b/src/reverse/Type.h @@ -57,10 +57,11 @@ struct ClassType : Type struct UnknownType : Type { UnknownType(const TiltedPhoques::Lockable::Ref& aView, RED4ext::CBaseRTTIType* apType, RED4ext::ScriptInstance apInstance); + UnknownType(UnknownType&& aOther) noexcept; ~UnknownType() override; Descriptor Dump(bool aWithHashes) const override; - RED4ext::ScriptInstance GetHandle() const override { return m_pInstance; } + RED4ext::ScriptInstance GetHandle() const override; private: void* m_pInstance; From f045f8560502a75eb20f9db146d43f067093e432 Mon Sep 17 00:00:00 2001 From: psiberx Date: Sun, 21 Sep 2025 16:56:36 +0300 Subject: [PATCH 08/10] Handle circular requires --- src/scripting/LuaSandbox.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scripting/LuaSandbox.cpp b/src/scripting/LuaSandbox.cpp index a2db0cfb..85e97e34 100644 --- a/src/scripting/LuaSandbox.cpp +++ b/src/scripting/LuaSandbox.cpp @@ -461,6 +461,7 @@ void LuaSandbox::InitializeIOForSandbox(Sandbox& aSandbox, const sol::state& acp sol::protected_function_result result{}; try { + m_modules[cKey] = make_object(stateView, sol::nil); result = func(); } catch (std::exception& e) From a29dd806dd28cf67aaebc0db3a04d12319f90834 Mon Sep 17 00:00:00 2001 From: psiberx Date: Sun, 21 Sep 2025 17:09:19 +0300 Subject: [PATCH 09/10] Improve memory management when converting lua data to RED types --- src/reverse/Converter.h | 8 ++-- src/reverse/RTTIHelper.cpp | 93 ++++++------------------------------- src/reverse/RTTIHelper.h | 3 -- src/scripting/Scripting.cpp | 76 ++++++++++++++++++++++-------- src/scripting/Scripting.h | 2 + 5 files changed, 75 insertions(+), 107 deletions(-) diff --git a/src/reverse/Converter.h b/src/reverse/Converter.h index ab861c0e..161e7254 100644 --- a/src/reverse/Converter.h +++ b/src/reverse/Converter.h @@ -204,12 +204,12 @@ struct ClassConverter : LuaRED { result.value = aObject.as()->GetHandle(); } - else if (aObject == sol::nil) - { - result.value = RTTIHelper::Get().NewInstance(apRtti, sol::nullopt, apAllocator); - } // Disabled until new allocator is implemented // Current implementation can leak + // else if (aObject == sol::nil) + // { + // result.value = RTTIHelper::Get().NewInstance(apRtti, sol::nullopt, apAllocator); + // } // else if (aObject.get_type() == sol::type::table) //{ // // The implicit table to instance conversion `Game.FindEntityByID({ hash = 1 })` has potential issue: diff --git a/src/reverse/RTTIHelper.cpp b/src/reverse/RTTIHelper.cpp index e2c962fb..4684b72c 100644 --- a/src/reverse/RTTIHelper.cpp +++ b/src/reverse/RTTIHelper.cpp @@ -630,9 +630,10 @@ sol::variadic_results RTTIHelper::ExecuteFunction( TiltedPhoques::Allocator::Set(&s_scratchMemory); TiltedPhoques::Vector callArgs(apFunc->params.size); TiltedPhoques::Vector callArgToParam(apFunc->params.size); - TiltedPhoques::Vector argNeedsFree(apFunc->params.size, false); + TiltedPhoques::Vector ownedArgs(apFunc->params.size, false); TiltedPhoques::Allocator::Set(pAllocator); + RED4ext::CStackType result; uint32_t callArgOffset = 0u; ScopeGuard argsGuard( @@ -640,9 +641,12 @@ sol::variadic_results RTTIHelper::ExecuteFunction( { for (auto j = 0u; j < callArgOffset; ++j) { - const bool isNew = argNeedsFree[j]; + Scripting::DestructRED(callArgs[j], ownedArgs[j]); + } - FreeInstance(callArgs[j], !isNew, isNew, &s_scratchMemory); + if (result.value) + { + Scripting::DestructRED(result, true); } }); @@ -672,7 +676,7 @@ sol::variadic_results RTTIHelper::ExecuteFunction( // and memory allocation is enough. callArgs[callArgOffset].type = cpParam->type; callArgs[callArgOffset].value = NewPlaceholder(cpParam->type, &s_scratchMemory); - argNeedsFree[callArgOffset] = true; + ownedArgs[callArgOffset] = true; // But ToRED conversion is necessary for implementing required out params. // callArgs[callArgOffset] = Scripting::ToRED(sol::nil, cpParam->type, &s_scratchMemory); @@ -717,13 +721,11 @@ sol::variadic_results RTTIHelper::ExecuteFunction( const bool hasReturnType = apFunc->returnType != nullptr && apFunc->returnType->type != nullptr; - uint8_t buffer[1000]{0}; - RED4ext::CStackType result; - if (hasReturnType) { - result.value = &buffer; + result.value = s_scratchMemory.Allocate(apFunc->returnType->type->GetSize()); result.type = apFunc->returnType->type; + result.type->Construct(result.value); } const auto success = ExecuteFunction(apFunc, apContext, callArgs, result); @@ -741,15 +743,7 @@ sol::variadic_results RTTIHelper::ExecuteFunction( if (hasReturnType) { - // There is a special case when a non-native function returns a specific class or something that holds that - // class, which leads to another memory leak. So far the only known class that is causing the issue is - // gameTargetSearchFilter. When it returned by a non-native function it is wrapped in the Variant or similar - // structure. To prevent the leak the underlying value must be unwrapped and the wrapper explicitly destroyed. - // The workaround has been proven to work, but it seems too much for only one known case, so it is not included - // for now. - results.emplace_back(Scripting::ToLua(lockedState, result)); - FreeInstance(result, false, false, &s_scratchMemory); } for (auto i = 0u; i < callArgOffset; ++i) @@ -872,7 +866,7 @@ sol::object RTTIHelper::NewInstance(RED4ext::CBaseRTTIType* apType, sol::optiona auto lockedState = m_lua.Lock(); auto instance = Scripting::ToLua(lockedState, result); - FreeInstance(result, true, true, &allocator); + Scripting::DestructRED(result, true); if (aProps.has_value()) { @@ -920,7 +914,7 @@ sol::object RTTIHelper::NewHandle(RED4ext::CBaseRTTIType* apType, sol::optional< auto lockedState = m_lua.Lock(); auto instance = Scripting::ToLua(lockedState, result); - FreeInstance(result, true, true, &allocator); + Scripting::DestructRED(result, true); if (aProps.has_value()) { @@ -975,7 +969,7 @@ void RTTIHelper::SetProperty(RED4ext::CClass* apClass, RED4ext::ScriptInstance a { pProp->SetValue(apHandle, stackType.value); - FreeInstance(stackType, true, false, &s_scratchMemory); + Scripting::DestructRED(stackType, false); aSuccess = true; } } @@ -987,64 +981,3 @@ void RTTIHelper::SetProperties(RED4ext::CClass* apClass, RED4ext::ScriptInstance for (const auto& cProp : aProps.value()) SetProperty(apClass, apHandle, cProp.first.as(), cProp.second, success); } - -// Check if type is implemented using ClassReference -bool RTTIHelper::IsClassReferenceType(RED4ext::CClass* apClass) const -{ - static constexpr auto s_cHashVector3 = RED4ext::FNV1a64("Vector3"); - static constexpr auto s_cHashVector4 = RED4ext::FNV1a64("Vector4"); - static constexpr auto s_cHashEulerAngles = RED4ext::FNV1a64("EulerAngles"); - static constexpr auto s_cHashQuaternion = RED4ext::FNV1a64("Quaternion"); - static constexpr auto s_cHashItemID = RED4ext::FNV1a64("gameItemID"); - - return apClass->name.hash != s_cHashVector3 && apClass->name.hash != s_cHashVector4 && apClass->name.hash != s_cHashEulerAngles && apClass->name.hash != s_cHashQuaternion && - apClass->name.hash != s_cHashItemID; -} - -void RTTIHelper::FreeInstance(RED4ext::CStackType& aStackType, bool aOwn, bool aNew, TiltedPhoques::Allocator* apAllocator) const -{ - FreeInstance(aStackType.type, aStackType.value, aOwn, aNew, apAllocator); -} - -void RTTIHelper::FreeInstance(RED4ext::CBaseRTTIType* apType, void* apValue, bool aOwn, bool aNew, TiltedPhoques::Allocator*) const -{ - if (!apValue) - return; - - if (aOwn) - { - if (apType->GetType() == RED4ext::ERTTIType::Class) - { - // Free instances created with AllocInstance() - if (aNew) - { - auto* pClass = reinterpret_cast(apType); - - // Skip basic types - if (IsClassReferenceType(pClass)) - { - pClass->DestructCls(apValue); - pClass->GetAllocator()->Free(apValue); - } - } - } - else - { - apType->Destruct(apValue); - } - } - else - { - if (aNew || apType->GetType() != RED4ext::ERTTIType::Class) - apType->Destruct(apValue); - } - - // Right now it's a workaround that doesn't cover all cases but most. - // It also requires explicit calls to FreeInstance(). - // Should probably be refactored into a custom allocator that - // combines ScratchAllocator and managed logic for: - // 1. Instances created with AllocInstance() - // 2. DynArray - // 3. Handle - // 4. WeakHandle -} diff --git a/src/reverse/RTTIHelper.h b/src/reverse/RTTIHelper.h index 03ae424a..e7fdc62f 100644 --- a/src/reverse/RTTIHelper.h +++ b/src/reverse/RTTIHelper.h @@ -56,9 +56,6 @@ struct RTTIHelper sol::function MakeInvokableOverload(std::map aOverloadedFuncs) const; RED4ext::ScriptInstance NewPlaceholder(RED4ext::CBaseRTTIType* apType, TiltedPhoques::Allocator* apAllocator) const; - bool IsClassReferenceType(RED4ext::CClass* apClass) const; - void FreeInstance(RED4ext::CStackType& aStackType, bool aOwnValue, bool aNewValue, TiltedPhoques::Allocator* apAllocator) const; - void FreeInstance(RED4ext::CBaseRTTIType* apType, void* apValue, bool aOwnValue, bool aNewValue, TiltedPhoques::Allocator* apAllocator) const; bool ExecuteFunction(RED4ext::CBaseFunction* apFunc, RED4ext::IScriptable* apContext, TiltedPhoques::Vector& aArgs, RED4ext::CStackType& aResult) const; diff --git a/src/scripting/Scripting.cpp b/src/scripting/Scripting.cpp index 6207f04c..b6504661 100644 --- a/src/scripting/Scripting.cpp +++ b/src/scripting/Scripting.cpp @@ -881,16 +881,15 @@ RED4ext::CStackType Scripting::ToRED(sol::object aObject, RED4ext::CBaseRTTIType if (tbl.size() > 0) { auto* pArrayInnerType = pArrayType->GetInnerType(); - const auto shouldDestroy = pArrayInnerType->GetType() != RED4ext::ERTTIType::Class; // Copy elements from the table into the array pArrayType->Resize(pMemory, static_cast(tbl.size())); for (uint32_t i = 1; i <= tbl.size(); ++i) { - const RED4ext::CStackType type = ToRED(tbl.get(i), pArrayInnerType, apAllocator); + RED4ext::CStackType data = ToRED(tbl.get(i), pArrayInnerType, apAllocator); // Break on first incompatible element - if (!type.value) + if (!data.value) { pArrayType->Destruct(pMemory); pMemory = nullptr; @@ -898,10 +897,9 @@ RED4ext::CStackType Scripting::ToRED(sol::object aObject, RED4ext::CBaseRTTIType } const auto pElement = pArrayType->GetElement(pMemory, i - 1); - pArrayInnerType->Assign(pElement, type.value); + pArrayInnerType->Assign(pElement, data.value); - if (shouldDestroy) - pArrayInnerType->Destruct(type.value); + DestructRED(data, false); } } } @@ -911,16 +909,19 @@ RED4ext::CStackType Scripting::ToRED(sol::object aObject, RED4ext::CBaseRTTIType } else if (apRttiType->GetType() == RED4ext::ERTTIType::ScriptReference) { - auto* pInnerType = static_cast(apRttiType)->innerType; - const RED4ext::CStackType innerValue = ToRED(aObject, pInnerType, apAllocator); - - if (innerValue.value) + if (hasData) { - auto* pScriptRef = apAllocator->New>(); - pScriptRef->innerType = innerValue.type; - pScriptRef->ref = innerValue.value; - pScriptRef->hash = apRttiType->GetName(); - result.value = pScriptRef; + auto* pInnerType = static_cast(apRttiType)->innerType; + const RED4ext::CStackType innerValue = ToRED(aObject, pInnerType, apAllocator); + + if (innerValue.value) + { + auto* pScriptRef = apAllocator->New>(); + pScriptRef->innerType = innerValue.type; + pScriptRef->ref = innerValue.value; + pScriptRef->hash = apRttiType->GetName(); + result.value = pScriptRef; + } } } else if (apRttiType->GetType() == RED4ext::ERTTIType::ResourceAsyncReference || apRttiType == s_resRefType) @@ -989,12 +990,47 @@ void Scripting::ToRED(sol::object aObject, RED4ext::CStackType& aRet) { static thread_local TiltedPhoques::ScratchAllocator s_scratchMemory(1 << 14); - const auto result = ToRED(aObject, aRet.type, &s_scratchMemory); - + auto result = ToRED(aObject, aRet.type, &s_scratchMemory); aRet.type->Assign(aRet.value, result.value); - - if (aRet.type->GetType() != RED4ext::ERTTIType::Class && result.value) - aRet.type->Destruct(result.value); + DestructRED(result, false); s_scratchMemory.Reset(); } + +void Scripting::DestructRED(const RED4ext::CStackType& aStackType, bool aOwned) +{ + if (!aStackType.value) + return; + + if (aStackType.type->GetType() == RED4ext::ERTTIType::ScriptReference) + { + auto pRef = reinterpret_cast*>(aStackType.value); + DestructRED({pRef->innerType, pRef->ref}, aOwned); + return; + } + + // Destroy only data created and owned by the caller, + // and types that always produce a copy when converted from lua to RED. + if (aOwned || IsConvertedByCopying(aStackType.type)) + { + aStackType.type->Destruct(aStackType.value); + } +} + +bool Scripting::IsConvertedByCopying(RED4ext::CBaseRTTIType* aType) +{ + if (aType == s_stringType) + { + return true; + } + + switch (aType->GetType()) + { + case RED4ext::ERTTIType::Array: + case RED4ext::ERTTIType::Handle: + case RED4ext::ERTTIType::WeakHandle: + return true; + } + + return false; +} diff --git a/src/scripting/Scripting.h b/src/scripting/Scripting.h index b6d25600..0f95f65f 100644 --- a/src/scripting/Scripting.h +++ b/src/scripting/Scripting.h @@ -45,6 +45,8 @@ struct Scripting static sol::object ToLua(LockedState& aState, RED4ext::CStackType& aResult); static RED4ext::CStackType ToRED(sol::object aObject, RED4ext::CBaseRTTIType* apRttiType, TiltedPhoques::Allocator* apAllocator); static void ToRED(sol::object aObject, RED4ext::CStackType& apType); + static void DestructRED(const RED4ext::CStackType& aStackType, bool aOwned); + static bool IsConvertedByCopying(RED4ext::CBaseRTTIType* aType); protected: void RegisterOverrides(); From b8c3ecfe6e7fc1dd3fef7ea0606057e319b51268 Mon Sep 17 00:00:00 2001 From: psiberx Date: Thu, 25 Sep 2025 19:41:53 +0300 Subject: [PATCH 10/10] Fix random crash when modifying TweakDB --- src/scripting/Scripting.cpp | 5 +++++ vendor/RED4ext.SDK | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/scripting/Scripting.cpp b/src/scripting/Scripting.cpp index b6504661..778ee690 100644 --- a/src/scripting/Scripting.cpp +++ b/src/scripting/Scripting.cpp @@ -516,6 +516,11 @@ void Scripting::PostInitializeTweakDB() m_sandbox.PostInitializeTweakDB(); + // Due to how TweakDB modifications are implemented in CET, some REDengine functions may attempt to allocate + // new data in the buffer. But these functions don't resize the buffer, so they can get out of bounds and crash. + // This edge case can be fixed by resizing TweakDB flat buffer to max capacity beforehand. + RED4ext::TweakDB::Get()->UpsizeFlatDataBufferToMax(); + TriggerOnTweak(); } diff --git a/vendor/RED4ext.SDK b/vendor/RED4ext.SDK index 3ccf163b..7b8170a3 160000 --- a/vendor/RED4ext.SDK +++ b/vendor/RED4ext.SDK @@ -1 +1 @@ -Subproject commit 3ccf163b23183636f192a94da4c29b45f8d0cfb3 +Subproject commit 7b8170a32740f3b86b0a0008b30f8fef3feb72e6