diff --git a/src/reverse/BasicTypes.cpp b/src/reverse/BasicTypes.cpp index 43d75374..f7b30111 100644 --- a/src/reverse/BasicTypes.cpp +++ b/src/reverse/BasicTypes.cpp @@ -65,6 +65,11 @@ bool CName::operator==(const CName& acRhs) const noexcept return hash == acRhs.hash; } +void CName::Add(const std::string& aName) +{ + RED4ext::CNamePool::Add(aName.c_str()); +} + std::string TweakDBID::ToString() const noexcept { return fmt::format("ToTweakDBID{{ hash = 0x{0:08X}, length = {1:d} }}", name_hash, name_length); diff --git a/src/reverse/BasicTypes.h b/src/reverse/BasicTypes.h index d076852a..da6cadaf 100644 --- a/src/reverse/BasicTypes.h +++ b/src/reverse/BasicTypes.h @@ -92,6 +92,8 @@ struct CName std::string ToString() const noexcept; bool operator==(const CName& acRhs) const noexcept; + + static void Add(const std::string& aName); }; #pragma pack(push, 1) diff --git a/src/reverse/Converter.h b/src/reverse/Converter.h index e35414c6..43eaa44c 100644 --- a/src/reverse/Converter.h +++ b/src/reverse/Converter.h @@ -212,21 +212,23 @@ struct ClassConverter : LuaRED { 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: - // When the overloaded function takes an array and an object for the same arg the implicit conversion - // can produce an empty instance making the unwanted overload callable. So for better experience it's - // important to distinguish between linear array and array of props. - - // Size check excludes non-empty linear arrays since only the table with sequential and integral keys - // has size (length). And iterator check excludes empty tables `{}`. - sol::table props = aObject.as(); - if (props.size() == 0 && props.begin() != props.end()) - result.value = RTTIHelper::Get().NewInstance(apRtti, props, apAllocator); - else - result.value = nullptr; - } + // Disabled until new allocator is implemented + // Current implementation can leak + //else if (aObject.get_type() == sol::type::table) + //{ + // // The implicit table to instance conversion `Game.FindEntityByID({ hash = 1 })` has potential issue: + // // When the overloaded function takes an array and an object for the same arg the implicit conversion + // // can produce an empty instance making the unwanted overload callable. So for better experience it's + // // important to distinguish between linear array and array of props. + // + // // Size check excludes non-empty linear arrays since only the table with sequential and integral keys + // // has size (length). And iterator check excludes empty tables `{}`. + // sol::table props = aObject.as(); + // if (props.size() == 0 && props.begin() != props.end()) + // result.value = RTTIHelper::Get().NewInstance(apRtti, props, apAllocator); + // else + // result.value = nullptr; + //} else { result.value = nullptr; diff --git a/src/reverse/RTTIHelper.cpp b/src/reverse/RTTIHelper.cpp index 0e7e559e..97f703de 100644 --- a/src/reverse/RTTIHelper.cpp +++ b/src/reverse/RTTIHelper.cpp @@ -5,6 +5,7 @@ #include "Type.h" #include "ClassReference.h" #include "StrongReference.h" +#include "WeakReference.h" #include "scripting/Scripting.h" #include "Utils.h" #include @@ -114,6 +115,39 @@ void RTTIHelper::AddFunctionAlias(const std::string& acAliasClassName, const std } } +bool RTTIHelper::IsFunctionAlias(RED4ext::CBaseFunction* apFunc) +{ + static const auto s_cTweakDBInterfaceHash = RED4ext::FNV1a("gamedataTweakDBInterface"); + + if (m_extendedFunctions.contains(kGlobalHash)) + { + auto& extendedFuncs = m_extendedFunctions.at(kGlobalHash); + + if (extendedFuncs.contains(apFunc->fullName.hash)) + return true; + } + + if (apFunc->GetParent()) + { + const auto cClassHash = apFunc->GetParent()->name.hash; + + // TweakDBInterface is special. + // All of its methods are non-static, but they can only be used as static ones. + if (cClassHash == s_cTweakDBInterfaceHash) + return true; + + if (m_extendedFunctions.contains(cClassHash)) + { + auto& extendedFuncs = m_extendedFunctions.at(cClassHash); + + if (extendedFuncs.contains(apFunc->fullName.hash)) + return true; + } + } + + return false; +} + sol::function RTTIHelper::GetResolvedFunction(const uint64_t acFuncHash) const { return GetResolvedFunction(kGlobalHash, acFuncHash, false); @@ -373,12 +407,14 @@ sol::function RTTIHelper::MakeInvokableFunction(RED4ext::CBaseFunction* apFunc) auto lockedState = m_lua.Lock(); auto& luaState = lockedState.Get(); - return MakeSolFunction(luaState, [this, apFunc](sol::variadic_args aArgs, sol::this_state aState, sol::this_environment aEnv) -> sol::variadic_results { + const bool cAllowNull = IsFunctionAlias(apFunc); + + return MakeSolFunction(luaState, [this, apFunc, cAllowNull](sol::variadic_args aArgs, sol::this_state aState, sol::this_environment aEnv) -> sol::variadic_results { uint64_t argOffset = 0; RED4ext::ScriptInstance pHandle = ResolveHandle(apFunc, aArgs, argOffset); std::string errorMessage; - auto result = ExecuteFunction(apFunc, pHandle, aArgs, argOffset, errorMessage); + auto result = ExecuteFunction(apFunc, pHandle, aArgs, argOffset, errorMessage, cAllowNull); if (!errorMessage.empty()) { @@ -501,10 +537,19 @@ RED4ext::ScriptInstance RTTIHelper::ResolveHandle(RED4ext::CBaseFunction* apFunc if (cArg.is()) { - pHandle = cArg.as().GetHandle(); + pHandle = cArg.as()->GetHandle(); + + if (cArg.is() || cArg.is()) + { + ++aArgOffset; - if (pHandle || cArg.is()) + if (pHandle && !reinterpret_cast(pHandle)->GetType()->IsA(apFunc->GetParent())) + pHandle = nullptr; + } + else if (cArg.is()) + { ++aArgOffset; + } } } } @@ -514,7 +559,7 @@ RED4ext::ScriptInstance RTTIHelper::ResolveHandle(RED4ext::CBaseFunction* apFunc sol::variadic_results RTTIHelper::ExecuteFunction(RED4ext::CBaseFunction* apFunc, RED4ext::ScriptInstance apHandle, sol::variadic_args aLuaArgs, uint64_t aLuaArgOffset, - std::string& aErrorMessage) const + std::string& aErrorMessage, bool aAllowNull) const { static thread_local TiltedPhoques::ScratchAllocator s_scratchMemory(1 << 14); static thread_local uint32_t s_callDepth = 0u; @@ -533,6 +578,14 @@ sol::variadic_results RTTIHelper::ExecuteFunction(RED4ext::CBaseFunction* apFunc // args is implemented (should check if a compatible arg is actually // passed at the expected position + same for optionals). + if (!apFunc->flags.isStatic && !apHandle && !aAllowNull) + { + aErrorMessage = fmt::format("Function '{}' context must be '{}'.", + apFunc->shortName.ToString(), + apFunc->GetParent()->name.ToString()); + return {}; + } + auto numArgs = aLuaArgs.size() - aLuaArgOffset; auto minArgs = 0u; auto maxArgs = 0u; @@ -738,11 +791,7 @@ RED4ext::ScriptInstance RTTIHelper::NewInstance(RED4ext::CBaseRTTIType* apType, auto* pInstance = pClass->AllocInstance(); if (aProps.has_value()) - { - bool success; - for (const auto& cProp : aProps.value()) - SetProperty(pClass, pInstance, cProp.first.as(), cProp.second, success); - } + SetProperties(pClass, pInstance, aProps.value()); if (apType->GetType() == RED4ext::ERTTIType::Handle) return apAllocator->New>((RED4ext::IScriptable*)pInstance); @@ -759,12 +808,18 @@ sol::object RTTIHelper::NewInstance(RED4ext::CBaseRTTIType* apType, sol::optiona RED4ext::CStackType result; result.type = apType; - result.value = NewInstance(apType, aProps, &allocator); + result.value = NewInstance(apType, sol::nullopt, &allocator); auto lockedState = m_lua.Lock(); auto instance = Scripting::ToLua(lockedState, result); FreeInstance(result, true, true, &allocator); + + if (aProps.has_value()) + { + const auto pInstance = instance.as(); + SetProperties(pInstance->GetClass(), pInstance->GetHandle(), aProps); + } return instance; } @@ -776,9 +831,6 @@ sol::object RTTIHelper::NewHandle(RED4ext::CBaseRTTIType* apType, sol::optional< // The behavior is similar to what can be seen in scripts, where variables of IScriptable // types are declared with the ref<> modifier (which means Handle<>). - // The Handle<> wrapper prevents memory leaks that can occur in IRTTIType::Assign() - // when called directly on an IScriptable instance. - if (!m_pRtti) return sol::nil; @@ -786,7 +838,7 @@ sol::object RTTIHelper::NewHandle(RED4ext::CBaseRTTIType* apType, sol::optional< RED4ext::CStackType result; result.type = apType; - result.value = NewInstance(apType, aProps, &allocator); + result.value = NewInstance(apType, sol::nullopt, &allocator); // Wrap IScriptable descendants in Handle if (result.value && apType->GetType() == RED4ext::ERTTIType::Class) @@ -811,6 +863,12 @@ sol::object RTTIHelper::NewHandle(RED4ext::CBaseRTTIType* apType, sol::optional< FreeInstance(result, true, true, &allocator); + if (aProps.has_value()) + { + const auto pInstance = instance.as(); + SetProperties(pInstance->GetClass(), pInstance->GetHandle(), aProps); + } + return instance; } @@ -866,6 +924,14 @@ void RTTIHelper::SetProperty(RED4ext::CClass* apClass, RED4ext::ScriptInstance a } } +void RTTIHelper::SetProperties(RED4ext::CClass* apClass, RED4ext::ScriptInstance apHandle, sol::optional aProps) const +{ + bool success; + + 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 { diff --git a/src/reverse/RTTIHelper.h b/src/reverse/RTTIHelper.h index 424507b6..4e44062e 100644 --- a/src/reverse/RTTIHelper.h +++ b/src/reverse/RTTIHelper.h @@ -20,7 +20,7 @@ struct RTTIHelper RED4ext::ScriptInstance ResolveHandle(RED4ext::CBaseFunction* apFunc, sol::variadic_args& aArgs, uint64_t& aArgOffset) const; sol::variadic_results ExecuteFunction(RED4ext::CBaseFunction* apFunc, RED4ext::ScriptInstance apHandle, sol::variadic_args aLuaArgs, uint64_t aLuaArgOffset, - std::string& aErrorMessage) const; + std::string& aErrorMessage, bool aAllowNull = false) const; RED4ext::ScriptInstance NewInstance(RED4ext::CBaseRTTIType* apType, sol::optional aProps, TiltedPhoques::Allocator* apAllocator) const; @@ -29,6 +29,7 @@ struct RTTIHelper sol::object GetProperty(RED4ext::CClass* apClass, RED4ext::ScriptInstance apHandle, const std::string& acPropName, bool& aSuccess) const; void SetProperty(RED4ext::CClass* apClass, RED4ext::ScriptInstance apHandle, const std::string& acPropName, sol::object aPropValue, bool& aSuccess) const; + void SetProperties(RED4ext::CClass* apClass, RED4ext::ScriptInstance apHandle, sol::optional aProps) const; RED4ext::CBaseFunction* FindFunction(const uint64_t acFullNameHash) const; RED4ext::CBaseFunction* FindFunction(RED4ext::CClass* apClass, const uint64_t acFullNameHash) const; @@ -46,6 +47,8 @@ struct RTTIHelper void InitializeRTTI(); void ParseGlobalStatics(); + bool IsFunctionAlias(RED4ext::CBaseFunction* apFunc); + sol::function GetResolvedFunction(const uint64_t acFuncHash) const; sol::function GetResolvedFunction(const uint64_t acClassHash, const uint64_t acFuncHash, bool aIsMember) const; void AddResolvedFunction(const uint64_t acFuncHash, sol::function& acFunc); diff --git a/src/reverse/StrongReference.cpp b/src/reverse/StrongReference.cpp index 1ed3e9f7..53089c6d 100644 --- a/src/reverse/StrongReference.cpp +++ b/src/reverse/StrongReference.cpp @@ -1,9 +1,12 @@ #include #include "StrongReference.h" +#include "RTTILocator.h" #include "CET.h" +static RTTILocator s_sIScriptableType{RED4ext::FNV1a("IScriptable")}; + StrongReference::StrongReference(const TiltedPhoques::Lockable::Ref& aView, RED4ext::Handle aStrongHandle) : ClassType(aView, nullptr) @@ -15,6 +18,20 @@ StrongReference::StrongReference(const TiltedPhoques::Lockable::Ref& aView, + RED4ext::Handle aStrongHandle, + RED4ext::CHandle* apStrongHandleType) + : ClassType(aView, nullptr) + , m_strongHandle(std::move(aStrongHandle)) +{ + if (m_strongHandle) + { + auto const cpClass = reinterpret_cast(apStrongHandleType->GetInnerType()); + + m_pType = cpClass->IsA(s_sIScriptableType) ? m_strongHandle->GetType() : cpClass; + } +} + StrongReference::~StrongReference() { // Nasty hack so that the Lua VM doesn't try to release game memory on shutdown diff --git a/src/reverse/StrongReference.h b/src/reverse/StrongReference.h index e12d541b..f03f104c 100644 --- a/src/reverse/StrongReference.h +++ b/src/reverse/StrongReference.h @@ -6,6 +6,9 @@ struct StrongReference : ClassType { StrongReference(const TiltedPhoques::Lockable::Ref& aView, RED4ext::Handle aStrongHandle); + StrongReference(const TiltedPhoques::Lockable::Ref& aView, + RED4ext::Handle aStrongHandle, + RED4ext::CHandle* apStrongHandleType); virtual ~StrongReference(); protected: diff --git a/src/reverse/Type.h b/src/reverse/Type.h index 433eed2c..9d796299 100644 --- a/src/reverse/Type.h +++ b/src/reverse/Type.h @@ -49,6 +49,8 @@ struct ClassType : Type Descriptor Dump(bool aWithHashes) const override; sol::object Index_Impl(const std::string& acName, sol::this_environment aThisEnv) override; sol::object NewIndex_Impl(const std::string& acName, sol::object aParam) override; + + RED4ext::CClass* GetClass() const { return reinterpret_cast(m_pType); }; }; struct UnknownType : Type diff --git a/src/reverse/WeakReference.cpp b/src/reverse/WeakReference.cpp index b74380e9..b6abc456 100644 --- a/src/reverse/WeakReference.cpp +++ b/src/reverse/WeakReference.cpp @@ -1,9 +1,12 @@ #include #include "WeakReference.h" +#include "RTTILocator.h" #include "CET.h" +static RTTILocator s_sIScriptableType{RED4ext::FNV1a("IScriptable")}; + WeakReference::WeakReference(const TiltedPhoques::Lockable::Ref& aView, RED4ext::WeakHandle aWeakHandle) : ClassType(aView, nullptr) @@ -16,6 +19,21 @@ WeakReference::WeakReference(const TiltedPhoques::Lockable::Ref& aView, + RED4ext::WeakHandle aWeakHandle, + RED4ext::CWeakHandle* apWeakHandleType) + : ClassType(aView, nullptr) + , m_weakHandle(std::move(aWeakHandle)) +{ + auto ref = m_weakHandle.Lock(); + if (ref) + { + auto const cpClass = reinterpret_cast(apWeakHandleType->GetInnerType()); + + m_pType = cpClass->IsA(s_sIScriptableType) ? ref->GetType() : cpClass; + } +} + WeakReference::~WeakReference() { // Nasty hack so that the Lua VM doesn't try to release game memory on shutdown diff --git a/src/reverse/WeakReference.h b/src/reverse/WeakReference.h index 31524216..ff0ca564 100644 --- a/src/reverse/WeakReference.h +++ b/src/reverse/WeakReference.h @@ -6,6 +6,9 @@ struct WeakReference : ClassType { WeakReference(const TiltedPhoques::Lockable::Ref& aView, RED4ext::WeakHandle aWeakHandle); + WeakReference(const TiltedPhoques::Lockable::Ref& aView, + RED4ext::WeakHandle aWeakHandle, + RED4ext::CWeakHandle* apWeakHandleType); virtual ~WeakReference(); protected: diff --git a/src/scripting/FunctionOverride.cpp b/src/scripting/FunctionOverride.cpp index 073ab0d2..ed031c86 100644 --- a/src/scripting/FunctionOverride.cpp +++ b/src/scripting/FunctionOverride.cpp @@ -26,7 +26,7 @@ bool FunctionOverride::HookRunPureScriptFunction(RED4ext::CClassFunction* apFunc return false; } - bool isOverride = false; + bool isOverridden = false; if (!itor->second.Calls.empty()) { @@ -66,6 +66,9 @@ bool FunctionOverride::HookRunPureScriptFunction(RED4ext::CClassFunction* apFunc const auto& calls = itor->second.Calls; for (const auto& call : calls) { + if (!call->Forward && isOverridden) + continue; + const auto result = call->ScriptFunction(as_args(args)); if (!result.valid()) @@ -79,8 +82,7 @@ bool FunctionOverride::HookRunPureScriptFunction(RED4ext::CClassFunction* apFunc if (result.valid() && ret.value && ret.type) Scripting::ToRED(result.get(), &ret); - isOverride = true; - break; + isOverridden = true; } } } @@ -88,7 +90,7 @@ bool FunctionOverride::HookRunPureScriptFunction(RED4ext::CClassFunction* apFunc if (itor->second.CollectGarbage) s_pOverride->m_pScripting->CollectGarbage(); - if (isOverride) + if (isOverridden) return true; auto* pTrampoline = itor->second.Trampoline; @@ -196,7 +198,7 @@ void FunctionOverride::HandleOverridenFunction(RED4ext::IScriptable* apContext, } const auto& context = itor->second; - bool isOverride = false; + bool isOverridden = false; // Save state so we can rollback to it after we popped for ourself auto* pCode = apFrame->code; @@ -283,6 +285,9 @@ void FunctionOverride::HandleOverridenFunction(RED4ext::IScriptable* apContext, const auto& calls = context.Calls; for (const auto& call : calls) { + if (!call->Forward && isOverridden) + continue; + const auto result = call->ScriptFunction(as_args(args)); if (!result.valid()) @@ -303,8 +308,7 @@ void FunctionOverride::HandleOverridenFunction(RED4ext::IScriptable* apContext, Scripting::ToRED(result.get(), &redResult); } - isOverride = true; - break; + isOverridden = true; } } } @@ -312,7 +316,7 @@ void FunctionOverride::HandleOverridenFunction(RED4ext::IScriptable* apContext, if (context.CollectGarbage) s_pOverride->m_pScripting->CollectGarbage(); - if (isOverride) + if (isOverridden) return; RED4ext::IFunction* pRealFunction = context.Trampoline; @@ -495,6 +499,16 @@ void FunctionOverride::Override(const std::string& acTypeName, const std::string pContext->Environment = aEnvironment; pContext->Forward = !aAbsolute; - pEntry->Calls.emplace_back(std::move(pContext)); + if (aAbsolute || pEntry->Calls.empty()) + { + pEntry->Calls.emplace_back(std::move(pContext)); + } + else + { + auto pos = std::find_if(pEntry->Calls.rbegin(), pEntry->Calls.rend(), + [](TiltedPhoques::UniquePtr& aContext) { return aContext->Forward; }); + + pEntry->Calls.insert(pos.base(), std::move(pContext)); + } } } diff --git a/src/scripting/Scripting.cpp b/src/scripting/Scripting.cpp index 5a96edae..bdcbb6ac 100644 --- a/src/scripting/Scripting.cpp +++ b/src/scripting/Scripting.cpp @@ -132,36 +132,42 @@ void Scripting::PostInitialize() sol::meta_function::index, &Type::Index, sol::meta_function::new_index, &Type::NewIndex); + luaVm.new_usertype("__ClassType", + sol::meta_function::construct, sol::no_constructor, + sol::base_classes, sol::bases(), + sol::meta_function::index, &ClassType::Index, + sol::meta_function::new_index, &ClassType::NewIndex); + luaVm.new_usertype("Descriptor", sol::meta_function::to_string, &Type::Descriptor::ToString); luaVm.new_usertype("StrongReference", sol::meta_function::construct, sol::no_constructor, - sol::base_classes, sol::bases(), + sol::base_classes, sol::bases(), sol::meta_function::index, &StrongReference::Index, sol::meta_function::new_index, &StrongReference::NewIndex); luaVm.new_usertype("WeakReference", sol::meta_function::construct, sol::no_constructor, - sol::base_classes, sol::bases(), + sol::base_classes, sol::bases(), sol::meta_function::index, &WeakReference::Index, sol::meta_function::new_index, &WeakReference::NewIndex); luaVm.new_usertype("SingletonReference", sol::meta_function::construct, sol::no_constructor, - sol::base_classes, sol::bases(), + sol::base_classes, sol::bases(), sol::meta_function::index, &SingletonReference::Index, sol::meta_function::new_index, &SingletonReference::NewIndex); luaVm.new_usertype("ClassReference", sol::meta_function::construct, sol::no_constructor, - sol::base_classes, sol::bases(), + sol::base_classes, sol::bases(), sol::meta_function::index, &ClassReference::Index, sol::meta_function::new_index, &ClassReference::NewIndex); luaVm.new_usertype("ResourceAsyncReference", sol::meta_function::construct, sol::no_constructor, - sol::base_classes, sol::bases(), + sol::base_classes, sol::bases(), sol::meta_function::index, &ResourceAsyncReference::Index, sol::meta_function::new_index, &ResourceAsyncReference::NewIndex, "hash", sol::property(&ResourceAsyncReference::GetLuaHash)); @@ -303,7 +309,8 @@ void Scripting::PostInitialize() sol::meta_function::equal_to, &CName::operator==, "hash_lo", &CName::hash_lo, "hash_hi", &CName::hash_hi, - "value", sol::property(&CName::AsString)); + "value", sol::property(&CName::AsString), + "add", &CName::Add); luaGlobal["CName"] = luaVm["CName"]; luaGlobal["ToCName"] = [](sol::table table) -> CName @@ -712,13 +719,13 @@ sol::object Scripting::ToLua(LockedState& aState, RED4ext::CStackType& aResult) { const auto handle = *static_cast*>(aResult.value); if (handle) - return make_object(state, StrongReference(aState, handle)); + return make_object(state, StrongReference(aState, handle, static_cast(pType))); } else if (pType->GetType() == RED4ext::ERTTIType::WeakHandle) { const auto handle = *static_cast*>(aResult.value); if (!handle.Expired()) - return make_object(state, WeakReference(aState, handle)); + return make_object(state, WeakReference(aState, handle, static_cast(pType))); } else if (pType->GetType() == RED4ext::ERTTIType::Array) {