Skip to content

Commit

Permalink
Merge pull request #619 from psiberx/master
Browse files Browse the repository at this point in the history
Stricter context check, better ISerializable support, add new CNames, guaranteed observers, fixed parameterized struct constructor
  • Loading branch information
Yamashi authored Oct 2, 2021
2 parents a2e38e3 + 3895e23 commit 8ab3d43
Show file tree
Hide file tree
Showing 12 changed files with 190 additions and 48 deletions.
5 changes: 5 additions & 0 deletions src/reverse/BasicTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/reverse/BasicTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
32 changes: 17 additions & 15 deletions src/reverse/Converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,21 +212,23 @@ struct ClassConverter : LuaRED<ClassReference, "ClassReference">
{
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<sol::table>();
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<sol::table>();
// if (props.size() == 0 && props.begin() != props.end())
// result.value = RTTIHelper::Get().NewInstance(apRtti, props, apAllocator);
// else
// result.value = nullptr;
//}
else
{
result.value = nullptr;
Expand Down
96 changes: 81 additions & 15 deletions src/reverse/RTTIHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "Type.h"
#include "ClassReference.h"
#include "StrongReference.h"
#include "WeakReference.h"
#include "scripting/Scripting.h"
#include "Utils.h"
#include <common/ScopeGuard.h>
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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())
{
Expand Down Expand Up @@ -501,10 +537,19 @@ RED4ext::ScriptInstance RTTIHelper::ResolveHandle(RED4ext::CBaseFunction* apFunc

if (cArg.is<Type>())
{
pHandle = cArg.as<Type>().GetHandle();
pHandle = cArg.as<Type*>()->GetHandle();

if (cArg.is<StrongReference>() || cArg.is<WeakReference>())
{
++aArgOffset;

if (pHandle || cArg.is<SingletonReference>())
if (pHandle && !reinterpret_cast<RED4ext::IScriptable*>(pHandle)->GetType()->IsA(apFunc->GetParent()))
pHandle = nullptr;
}
else if (cArg.is<SingletonReference>())
{
++aArgOffset;
}
}
}
}
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<std::string>(), cProp.second, success);
}
SetProperties(pClass, pInstance, aProps.value());

if (apType->GetType() == RED4ext::ERTTIType::Handle)
return apAllocator->New<RED4ext::Handle<RED4ext::IScriptable>>((RED4ext::IScriptable*)pInstance);
Expand All @@ -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<ClassType*>();
SetProperties(pInstance->GetClass(), pInstance->GetHandle(), aProps);
}

return instance;
}
Expand All @@ -776,17 +831,14 @@ 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;

TiltedPhoques::StackAllocator<1 << 10> allocator;

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)
Expand All @@ -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<ClassType*>();
SetProperties(pInstance->GetClass(), pInstance->GetHandle(), aProps);
}

return instance;
}

Expand Down Expand Up @@ -866,6 +924,14 @@ void RTTIHelper::SetProperty(RED4ext::CClass* apClass, RED4ext::ScriptInstance a
}
}

void RTTIHelper::SetProperties(RED4ext::CClass* apClass, RED4ext::ScriptInstance apHandle, sol::optional<sol::table> aProps) const
{
bool success;

for (const auto& cProp : aProps.value())
SetProperty(apClass, apHandle, cProp.first.as<std::string>(), cProp.second, success);
}

// Check if type is implemented using ClassReference
bool RTTIHelper::IsClassReferenceType(RED4ext::CClass* apClass) const
{
Expand Down
5 changes: 4 additions & 1 deletion src/reverse/RTTIHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<sol::table> aProps,
TiltedPhoques::Allocator* apAllocator) const;
Expand All @@ -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<sol::table> aProps) const;

RED4ext::CBaseFunction* FindFunction(const uint64_t acFullNameHash) const;
RED4ext::CBaseFunction* FindFunction(RED4ext::CClass* apClass, const uint64_t acFullNameHash) const;
Expand All @@ -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);
Expand Down
17 changes: 17 additions & 0 deletions src/reverse/StrongReference.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
#include <stdafx.h>

#include "StrongReference.h"
#include "RTTILocator.h"

#include "CET.h"

static RTTILocator s_sIScriptableType{RED4ext::FNV1a("IScriptable")};

StrongReference::StrongReference(const TiltedPhoques::Lockable<sol::state, std::recursive_mutex>::Ref& aView,
RED4ext::Handle<RED4ext::IScriptable> aStrongHandle)
: ClassType(aView, nullptr)
Expand All @@ -15,6 +18,20 @@ StrongReference::StrongReference(const TiltedPhoques::Lockable<sol::state, std::
}
}

StrongReference::StrongReference(const TiltedPhoques::Lockable<sol::state, std::recursive_mutex>::Ref& aView,
RED4ext::Handle<RED4ext::IScriptable> aStrongHandle,
RED4ext::CHandle* apStrongHandleType)
: ClassType(aView, nullptr)
, m_strongHandle(std::move(aStrongHandle))
{
if (m_strongHandle)
{
auto const cpClass = reinterpret_cast<RED4ext::CClass*>(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
Expand Down
3 changes: 3 additions & 0 deletions src/reverse/StrongReference.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ struct StrongReference : ClassType
{
StrongReference(const TiltedPhoques::Lockable<sol::state, std::recursive_mutex>::Ref& aView,
RED4ext::Handle<RED4ext::IScriptable> aStrongHandle);
StrongReference(const TiltedPhoques::Lockable<sol::state, std::recursive_mutex>::Ref& aView,
RED4ext::Handle<RED4ext::IScriptable> aStrongHandle,
RED4ext::CHandle* apStrongHandleType);
virtual ~StrongReference();

protected:
Expand Down
2 changes: 2 additions & 0 deletions src/reverse/Type.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<RED4ext::CClass*>(m_pType); };
};

struct UnknownType : Type
Expand Down
18 changes: 18 additions & 0 deletions src/reverse/WeakReference.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
#include <stdafx.h>

#include "WeakReference.h"
#include "RTTILocator.h"

#include "CET.h"

static RTTILocator s_sIScriptableType{RED4ext::FNV1a("IScriptable")};

WeakReference::WeakReference(const TiltedPhoques::Lockable<sol::state, std::recursive_mutex>::Ref& aView,
RED4ext::WeakHandle<RED4ext::IScriptable> aWeakHandle)
: ClassType(aView, nullptr)
Expand All @@ -16,6 +19,21 @@ WeakReference::WeakReference(const TiltedPhoques::Lockable<sol::state, std::recu
}
}

WeakReference::WeakReference(const TiltedPhoques::Lockable<sol::state, std::recursive_mutex>::Ref& aView,
RED4ext::WeakHandle<RED4ext::IScriptable> aWeakHandle,
RED4ext::CWeakHandle* apWeakHandleType)
: ClassType(aView, nullptr)
, m_weakHandle(std::move(aWeakHandle))
{
auto ref = m_weakHandle.Lock();
if (ref)
{
auto const cpClass = reinterpret_cast<RED4ext::CClass*>(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
Expand Down
3 changes: 3 additions & 0 deletions src/reverse/WeakReference.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ struct WeakReference : ClassType
{
WeakReference(const TiltedPhoques::Lockable<sol::state, std::recursive_mutex>::Ref& aView,
RED4ext::WeakHandle<RED4ext::IScriptable> aWeakHandle);
WeakReference(const TiltedPhoques::Lockable<sol::state, std::recursive_mutex>::Ref& aView,
RED4ext::WeakHandle<RED4ext::IScriptable> aWeakHandle,
RED4ext::CWeakHandle* apWeakHandleType);
virtual ~WeakReference();

protected:
Expand Down
Loading

0 comments on commit 8ab3d43

Please sign in to comment.