From 52b4070bc2ff889dc42362932e64cd870036c7e6 Mon Sep 17 00:00:00 2001
From: pv
Date: Fri, 26 Nov 2021 01:31:04 +0300
Subject: [PATCH 1/8] Add support for out params in overrides
---
src/scripting/FunctionOverride.cpp | 59 ++++++++++++++++++++++++------
src/scripting/FunctionOverride.h | 4 +-
2 files changed, 49 insertions(+), 14 deletions(-)
diff --git a/src/scripting/FunctionOverride.cpp b/src/scripting/FunctionOverride.cpp
index bcd4e13b..fd0e31e2 100644
--- a/src/scripting/FunctionOverride.cpp
+++ b/src/scripting/FunctionOverride.cpp
@@ -136,6 +136,7 @@ bool FunctionOverride::HookRunPureScriptFunction(RED4ext::CClassFunction* apFunc
auto pAllocator = TiltedPhoques::Allocator::Get();
TiltedPhoques::Allocator::Set(&s_allocator);
TiltedPhoques::Vector args;
+ TiltedPhoques::Vector outArgs;
TiltedPhoques::Allocator::Set(pAllocator);
auto state = chain.pScripting->GetState();
@@ -158,13 +159,16 @@ bool FunctionOverride::HookRunPureScriptFunction(RED4ext::CClassFunction* apFunc
arg.value = pOffset;
args.push_back(Scripting::ToLua(state, arg));
+
+ if (p->flags.isOut)
+ outArgs.push_back(arg);
}
RED4ext::CStackType ret;
ret.value = apStack->GetResultAddr();
ret.type = apStack->GetType();
- return ExecuteChain(chain, lock, pContext, &args, &ret, apStack, a3, nullptr, 0);
+ return ExecuteChain(chain, lock, pContext, &args, &ret, &outArgs, apStack, a3, nullptr, 0);
}
if (chain.CollectGarbage)
@@ -209,6 +213,7 @@ void FunctionOverride::HandleOverridenFunction(RED4ext::IScriptable* apContext,
auto pAllocator = TiltedPhoques::Allocator::Get();
TiltedPhoques::Allocator::Set(&s_allocator);
TiltedPhoques::Vector args;
+ TiltedPhoques::Vector outArgs;
TiltedPhoques::Allocator::Set(pAllocator);
auto state = chain.pScripting->GetState();
@@ -269,6 +274,15 @@ void FunctionOverride::HandleOverridenFunction(RED4ext::IScriptable* apContext,
args.push_back(Scripting::ToLua(state, arg));
+ if (pArg->flags.isOut)
+ {
+ // This is an original arg, pInstance contains copy
+ if (apFrame->unk30)
+ arg.value = reinterpret_cast(apFrame->unk30);
+
+ outArgs.push_back(arg);
+ }
+
// Release inner values
if (isScriptRef)
{
@@ -277,8 +291,11 @@ void FunctionOverride::HandleOverridenFunction(RED4ext::IScriptable* apContext,
pScriptRef->innerType->GetAllocator()->Free(pScriptRef->ref);
}
- pType->Destroy(pInstance);
- pAllocator->Free(pInstance);
+ if (!pArg->flags.isOut || apFrame->unk30)
+ {
+ pType->Destroy(pInstance);
+ pAllocator->Free(pInstance);
+ }
}
apFrame->code++; // skip ParamEnd
@@ -291,7 +308,7 @@ void FunctionOverride::HandleOverridenFunction(RED4ext::IScriptable* apContext,
ret.value = apOut;
}
- ExecuteChain(chain, lock, apContext, &args, &ret, nullptr, apFrame, pCode, currentParam);
+ ExecuteChain(chain, lock, apContext, &args, &ret, &outArgs, nullptr, apFrame, pCode, currentParam);
return;
}
@@ -307,8 +324,9 @@ void FunctionOverride::HandleOverridenFunction(RED4ext::IScriptable* apContext,
bool FunctionOverride::ExecuteChain(const CallChain& aChain, std::shared_lock& aLock,
RED4ext::IScriptable* apContext, TiltedPhoques::Vector* apOrigArgs,
- RED4ext::CStackType* apResult, RED4ext::CScriptStack* apStack,
- RED4ext::CStackFrame* apFrame, char* apCode, uint8_t aParam)
+ RED4ext::CStackType* apResult, TiltedPhoques::Vector* apOutArgs,
+ RED4ext::CScriptStack* apStack, RED4ext::CStackFrame* apFrame,
+ char* apCode, uint8_t aParam)
{
if (!aChain.Before.empty())
{
@@ -329,19 +347,36 @@ bool FunctionOverride::ExecuteChain(const CallChain& aChain, std::shared_lockflags.isStatic ? 0 : 1;
sol::object luaContext = pRealFunction->flags.isStatic ? sol::nil : apOrigArgs->at(0);
- TiltedPhoques::Vector luaArgs(apOrigArgs->begin() + argSelfOffset, apOrigArgs->end());
+ TiltedPhoques::Vector luaArgs(apOrigArgs->begin() + (pRealFunction->flags.isStatic ? 0 : 1),
+ apOrigArgs->end());
auto lockedState = aChain.pScripting->GetState();
auto& luaState = lockedState.Get();
- auto wrapped = WrapNextOverride(aChain, 0, luaState, luaContext, luaArgs, pRealFunction, apContext, aLock);
- auto result = wrapped(as_args(luaArgs));
+ auto luaWrapped = WrapNextOverride(aChain, 0, luaState, luaContext, luaArgs, pRealFunction, apContext, aLock);
+ auto luaResult = luaWrapped(as_args(luaArgs));
- if (result.valid() && apResult && apResult->value)
+ if (luaResult.valid())
{
- Scripting::ToRED(result.get(), *apResult);
+ auto luaRetOffset = 0;
+
+ if (apResult && apResult->value)
+ {
+ Scripting::ToRED(luaResult.get(), *apResult);
+ ++luaRetOffset;
+ }
+
+ if (apOutArgs && !apOutArgs->empty())
+ {
+ for (auto i = 0; i < apOutArgs->size(); ++i)
+ {
+ auto luaOutArg = luaResult.get(i + luaRetOffset);
+
+ if (luaOutArg != sol::nil)
+ Scripting::ToRED(luaOutArg, apOutArgs->at(i));
+ }
+ }
}
}
else
diff --git a/src/scripting/FunctionOverride.h b/src/scripting/FunctionOverride.h
index 35c4f0c5..55ff5579 100644
--- a/src/scripting/FunctionOverride.h
+++ b/src/scripting/FunctionOverride.h
@@ -38,8 +38,8 @@ struct FunctionOverride
static bool HookRunPureScriptFunction(RED4ext::CClassFunction* apFunction, RED4ext::CScriptStack* apContext, RED4ext::CStackFrame* a3);
static bool ExecuteChain(const CallChain& aChain, std::shared_lock& aLock,
RED4ext::IScriptable* apContext, TiltedPhoques::Vector* apArgs,
- RED4ext::CStackType* apResult, RED4ext::CScriptStack* apStack,
- RED4ext::CStackFrame* apFrame, char* apCode, uint8_t aParam);
+ RED4ext::CStackType* apResult, TiltedPhoques::Vector* apOutArgs,
+ RED4ext::CScriptStack* apStack, RED4ext::CStackFrame* apFrame, char* apCode, uint8_t aParam);
static sol::function WrapNextOverride(const CallChain& aChain, int aStep, sol::state& aLuaState,
sol::object& aLuaContext, TiltedPhoques::Vector& aLuaArgs,
RED4ext::CBaseFunction* apRealFunction, RED4ext::IScriptable* apRealContext,
From 75e5f106b20472f412cb2ab8917a81c7d593a9ae Mon Sep 17 00:00:00 2001
From: pv
Date: Fri, 26 Nov 2021 01:33:01 +0300
Subject: [PATCH 2/8] Use weak handle for context in overrides
---
src/scripting/FunctionOverride.cpp | 13 +++++++------
src/scripting/Scripting.cpp | 2 +-
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/src/scripting/FunctionOverride.cpp b/src/scripting/FunctionOverride.cpp
index fd0e31e2..cf17ab0e 100644
--- a/src/scripting/FunctionOverride.cpp
+++ b/src/scripting/FunctionOverride.cpp
@@ -3,13 +3,12 @@
#include "FunctionOverride.h"
#include "Scripting.h"
#include "Utils.h"
-#include
+#include
#include
#include
static FunctionOverride* s_pOverride = nullptr;
-static RTTILocator s_inkGameControllerType("gameuiWidgetGameController");
using TRunPureScriptFunction = bool (*)(RED4ext::CBaseFunction* apFunction, RED4ext::CScriptStack*, void*);
using TCallScriptFunction = bool (*)(RED4ext::IFunction* apFunction, RED4ext::IScriptable* apContext,
@@ -144,8 +143,9 @@ bool FunctionOverride::HookRunPureScriptFunction(RED4ext::CClassFunction* apFunc
auto pContext = apStack->GetContext();
if (!apFunction->flags.isStatic && pContext)
{
- const auto handle = RED4ext::Handle(pContext);
- auto obj = sol::make_object(state.Get(), StrongReference(state, handle));
+ const auto weak = RED4ext::WeakHandle(
+ *(RED4ext::WeakHandle*)&pContext->ref);
+ auto obj = sol::make_object(state.Get(), WeakReference(state, weak));
args.push_back(obj);
}
@@ -233,8 +233,9 @@ void FunctionOverride::HandleOverridenFunction(RED4ext::IScriptable* apContext,
self.value = apFrame->context;
}
- const auto handle = RED4ext::Handle((RED4ext::IScriptable*)self.value);
- auto obj = sol::make_object(state.Get(), StrongReference(state, handle));
+ const auto ref = (RED4ext::WeakHandle*)&((RED4ext::IScriptable*)self.value)->ref;
+ const auto weak = RED4ext::WeakHandle(*ref);
+ auto obj = sol::make_object(state.Get(), WeakReference(state, weak));
args.push_back(obj);
}
diff --git a/src/scripting/Scripting.cpp b/src/scripting/Scripting.cpp
index 96d65052..71679eef 100644
--- a/src/scripting/Scripting.cpp
+++ b/src/scripting/Scripting.cpp
@@ -543,7 +543,7 @@ void Scripting::RegisterOverrides()
auto lua = m_lua.Lock();
auto& luaVm = lua.Get();
- luaVm["RegisterGlobalInputListener"] = [](StrongReference& aSelf, sol::this_environment aThisEnv) {
+ luaVm["RegisterGlobalInputListener"] = [](WeakReference& aSelf, sol::this_environment aThisEnv) {
sol::protected_function unregisterInputListener = aSelf.Index("UnregisterInputListener", aThisEnv);
sol::protected_function registerInputListener = aSelf.Index("RegisterInputListener", aThisEnv);
From ecad176efb21ed85ffd3a60256dec1b9bd459201 Mon Sep 17 00:00:00 2001
From: pv
Date: Fri, 26 Nov 2021 01:37:01 +0300
Subject: [PATCH 3/8] Reuse overridden functions on mods reload
---
src/scripting/FunctionOverride.cpp | 153 +++++++++++++----------------
src/scripting/FunctionOverride.h | 3 +-
2 files changed, 69 insertions(+), 87 deletions(-)
diff --git a/src/scripting/FunctionOverride.cpp b/src/scripting/FunctionOverride.cpp
index cf17ab0e..e911b371 100644
--- a/src/scripting/FunctionOverride.cpp
+++ b/src/scripting/FunctionOverride.cpp
@@ -72,45 +72,17 @@ void FunctionOverride::Clear()
// Reverse order as we want to swap from most recent to oldest change
for (auto& [pFunction, pContext] : m_functions)
{
- // Just an added function, not an override
- if (pContext.Trampoline == nullptr)
- {
- auto* pClassType = pFunction->parent;
- auto* pArray = &pClassType->funcs;
-
- if (pFunction->flags.isStatic)
- pClassType->staticFuncs;
-
- for (auto*& pItor : *pArray)
- {
- if (pItor == pFunction)
- {
- // Swap our self with the last element
- pItor = *(pArray->End() - 1);
- // Pop last
- pArray->size -= 1;
-
- break;
- }
- }
- }
- else
- {
- auto* pRealFunction = pContext.Trampoline;
+ auto* pRealFunction = pContext.Trampoline;
- std::array tmpBuffer;
- size_t funcSize = GetFunctionSize(pRealFunction);
+ std::array tmpBuffer;
+ size_t funcSize = GetFunctionSize(pRealFunction);
- std::memcpy(&tmpBuffer, pRealFunction, funcSize);
- std::memcpy(pRealFunction, pFunction, funcSize);
- std::memcpy(pFunction, &tmpBuffer, funcSize);
- }
+ std::memcpy(&tmpBuffer, pRealFunction, funcSize);
+ std::memcpy(pRealFunction, pFunction, funcSize);
+ std::memcpy(pFunction, &tmpBuffer, funcSize);
}
m_functions.clear();
-
- m_pBuffer = m_pBufferStart;
- m_size = kExecutableSize;
}
bool FunctionOverride::HookRunPureScriptFunction(RED4ext::CClassFunction* apFunction, RED4ext::CScriptStack* apStack, RED4ext::CStackFrame* a3)
@@ -517,7 +489,7 @@ void FunctionOverride::Hook(Options& aOptions) const
{
DWORD oldProtect;
VirtualProtect(pLocation, 0x40, PAGE_READWRITE, &oldProtect);
- *pFirstLocation = *pSecondLocation = std::max(sizeof(RED4ext::CClassFunction), sizeof(RED4ext::CScriptedFunction));
+ *pFirstLocation = *pSecondLocation = std::max(s_cMaxFunctionSize, sizeof(RED4ext::CScriptedFunction));
VirtualProtect(pLocation, 0x40, oldProtect, &oldProtect);
spdlog::info("Override function allocator patched!");
@@ -573,73 +545,82 @@ void FunctionOverride::Override(const std::string& acTypeName, const std::string
m_functions[pRealFunction] = {};
pEntry = &m_functions[pRealFunction];
- /*
- sub rsp, 56
- mov rax, 0xDEADBEEFC0DEBAAD
- mov qword ptr[rsp + 32], rax
- mov rax, 0xDEADBEEFC0DEBAAD
- call rax
- add rsp, 56
- ret
- */
- uint8_t payload[] = {0x48, 0x83, 0xEC, 0x38, 0x48, 0xB8, 0xAD, 0xBA, 0xDE, 0xC0, 0xEF, 0xBE,
- 0xAD, 0xDE, 0x48, 0x89, 0x44, 0x24, 0x20, 0x48, 0xB8, 0xAD, 0xBA, 0xDE,
- 0xC0, 0xEF, 0xBE, 0xAD, 0xDE, 0xFF, 0xD0, 0x48, 0x83, 0xC4, 0x38, 0xC3};
-
- auto funcAddr = reinterpret_cast(&FunctionOverride::HandleOverridenFunction);
-
- std::memcpy(payload + 6, &pRealFunction, 8);
- std::memcpy(payload + 21, &funcAddr, 8);
-
- using TNativeScriptFunction = void (*)(RED4ext::IScriptable*, RED4ext::CStackFrame*, void*, int64_t);
- auto* pExecutablePayload = static_cast(MakeExecutable(payload, std::size(payload)));
-
RED4ext::CBaseFunction* pFunc;
- if (pRealFunction->flags.isStatic)
+ if (!m_trampolines.contains(pRealFunction))
{
- if (pRealFunction->flags.isNative)
+ /*
+ sub rsp, 56
+ mov rax, 0xDEADBEEFC0DEBAAD
+ mov qword ptr[rsp + 32], rax
+ mov rax, 0xDEADBEEFC0DEBAAD
+ call rax
+ add rsp, 56
+ ret
+ */
+ uint8_t payload[] = {0x48, 0x83, 0xEC, 0x38, 0x48, 0xB8, 0xAD, 0xBA, 0xDE, 0xC0, 0xEF, 0xBE,
+ 0xAD, 0xDE, 0x48, 0x89, 0x44, 0x24, 0x20, 0x48, 0xB8, 0xAD, 0xBA, 0xDE,
+ 0xC0, 0xEF, 0xBE, 0xAD, 0xDE, 0xFF, 0xD0, 0x48, 0x83, 0xC4, 0x38, 0xC3};
+
+ auto funcAddr = reinterpret_cast(&FunctionOverride::HandleOverridenFunction);
+
+ std::memcpy(payload + 6, &pRealFunction, 8);
+ std::memcpy(payload + 21, &funcAddr, 8);
+
+ using TNativeScriptFunction = void (*)(RED4ext::IScriptable*, RED4ext::CStackFrame*, void*, int64_t);
+ auto* pExecutablePayload = static_cast(MakeExecutable(payload, std::size(payload)));
+
+ if (pRealFunction->flags.isStatic)
{
- pFunc = RED4ext::CClassStaticFunction::Create(pClassType, acFullName.c_str(), acFullName.c_str(),
- pExecutablePayload, pRealFunction->flags);
- reinterpret_cast(pFunc)->parent = pRealFunction->parent;
+ if (pRealFunction->flags.isNative)
+ {
+ pFunc = RED4ext::CClassStaticFunction::Create(pClassType, acFullName.c_str(), acFullName.c_str(),
+ pExecutablePayload, pRealFunction->flags);
+ reinterpret_cast(pFunc)->parent = pRealFunction->parent;
+ }
+ else
+ {
+ pFunc = RED4ext::CGlobalFunction::Create(acFullName.c_str(), acFullName.c_str(), pExecutablePayload);
+ }
}
else
{
- pFunc = RED4ext::CGlobalFunction::Create(acFullName.c_str(), acFullName.c_str(), pExecutablePayload);
+ pFunc = RED4ext::CClassFunction::Create(pClassType, acFullName.c_str(), acFullName.c_str(),
+ pExecutablePayload, pRealFunction->flags);
+ reinterpret_cast(pFunc)->parent = pRealFunction->parent;
}
- }
- else
- {
- pFunc = RED4ext::CClassFunction::Create(pClassType, acFullName.c_str(), acFullName.c_str(),
- pExecutablePayload, pRealFunction->flags);
- reinterpret_cast(pFunc)->parent = pRealFunction->parent;
- }
- pFunc->fullName = pRealFunction->fullName;
- pFunc->shortName = pRealFunction->shortName;
+ pFunc->fullName = pRealFunction->fullName;
+ pFunc->shortName = pRealFunction->shortName;
- pFunc->returnType = pRealFunction->returnType;
- for (auto* p : pRealFunction->params)
- {
- pFunc->params.PushBack(p);
- }
+ pFunc->returnType = pRealFunction->returnType;
+ for (auto* p : pRealFunction->params)
+ {
+ pFunc->params.PushBack(p);
+ }
+
+ for (auto* p : pRealFunction->localVars)
+ {
+ pFunc->localVars.PushBack(p);
+ }
+
+ pFunc->unk20 = pRealFunction->unk20;
+ pFunc->bytecode = pRealFunction->bytecode;
+ pFunc->unk48 = pRealFunction->unk48;
+ pFunc->unkAC = pRealFunction->unkAC;
+ pFunc->flags = pRealFunction->flags;
+ pFunc->flags.isNative = true;
- for (auto* p : pRealFunction->localVars)
+ m_trampolines[pRealFunction] = pFunc;
+ }
+ else
{
- pFunc->localVars.PushBack(p);
+ pFunc = m_trampolines[pRealFunction];
}
- pFunc->unk20 = pRealFunction->unk20;
- pFunc->bytecode = pRealFunction->bytecode;
- pFunc->unk48 = pRealFunction->unk48;
- pFunc->unkAC = pRealFunction->unkAC;
- pFunc->flags = pRealFunction->flags;
- pFunc->flags.isNative = true;
-
pEntry->Trampoline = pFunc;
pEntry->pScripting = m_pScripting;
- pEntry->CollectGarbage = aCollectGarbage || pClassType->IsA(s_inkGameControllerType);
+ pEntry->CollectGarbage = aCollectGarbage;
pEntry->IsEmpty = true;
// Swap the content of the real function with the one we just created
diff --git a/src/scripting/FunctionOverride.h b/src/scripting/FunctionOverride.h
index 55ff5579..f68e7e8d 100644
--- a/src/scripting/FunctionOverride.h
+++ b/src/scripting/FunctionOverride.h
@@ -57,7 +57,8 @@ struct FunctionOverride
void* m_pBufferStart;
void* m_pBuffer;
size_t m_size{ kExecutableSize };
- TiltedPhoques::Map m_functions;
+ TiltedPhoques::Map m_functions;
+ TiltedPhoques::Map m_trampolines;
Scripting* m_pScripting;
std::shared_mutex m_lock;
};
\ No newline at end of file
From 9b62ffef687aa052c1df2fe82cc56d3d2df6066d Mon Sep 17 00:00:00 2001
From: pv
Date: Fri, 26 Nov 2021 15:42:59 +0300
Subject: [PATCH 4/8] Fix for filesystems without symlink support
---
src/scripting/ScriptStore.cpp | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/src/scripting/ScriptStore.cpp b/src/scripting/ScriptStore.cpp
index 9995e208..6db8b2c2 100644
--- a/src/scripting/ScriptStore.cpp
+++ b/src/scripting/ScriptStore.cpp
@@ -25,10 +25,16 @@ void ScriptStore::LoadAll()
auto fPath = file.path();
- if (is_symlink(fPath))
- fPath = read_symlink(fPath);
- else if (is_symlink(fPath / "init.lua"))
- fPath = read_symlink(fPath / "init.lua").parent_path();
+ try
+ {
+ if (is_symlink(fPath))
+ fPath = read_symlink(fPath);
+ else if (is_symlink(fPath / "init.lua"))
+ fPath = read_symlink(fPath / "init.lua").parent_path();
+ }
+ catch (std::exception& e)
+ {
+ }
fPath = absolute(fPath);
auto fPathStr = fPath.string();
From a3838515aa495df88bbdb33aefeeda26b3a4c078 Mon Sep 17 00:00:00 2001
From: pv
Date: Fri, 26 Nov 2021 23:56:01 +0300
Subject: [PATCH 5/8] Add hook for early TweakDB init
---
src/scripting/LuaVM.cpp | 14 +++--
src/scripting/LuaVM.h | 6 +-
src/scripting/LuaVM_Hooks.cpp | 30 ++++++++-
src/scripting/ScriptContext.cpp | 9 +++
src/scripting/ScriptContext.h | 2 +
src/scripting/ScriptStore.cpp | 6 ++
src/scripting/ScriptStore.h | 1 +
src/scripting/Scripting.cpp | 104 ++++++++++++++++++--------------
src/scripting/Scripting.h | 4 +-
9 files changed, 124 insertions(+), 52 deletions(-)
diff --git a/src/scripting/LuaVM.cpp b/src/scripting/LuaVM.cpp
index 8e812971..5e7e946e 100644
--- a/src/scripting/LuaVM.cpp
+++ b/src/scripting/LuaVM.cpp
@@ -25,7 +25,7 @@ void LuaVM::Update(float aDeltaTime)
if (!m_initialized)
{
if (m_logCount.load(std::memory_order_relaxed) > 0)
- PostInitialize();
+ PostInitializeStage2();
return;
}
@@ -46,6 +46,7 @@ void LuaVM::ReloadAllMods()
if (m_initialized)
{
m_scripting.ReloadAllMods();
+ m_scripting.TriggerOnTweak();
m_scripting.TriggerOnInit();
if (CET::Get().GetOverlay().IsEnabled())
@@ -180,12 +181,17 @@ void LuaVM::RegisterTDBIDString(uint64_t aValue, uint64_t aBase, const std::stri
m_tdbidDerivedLookup[aBase].insert(aValue);
}
-void LuaVM::PostInitialize()
+void LuaVM::PostInitializeStage1()
{
- assert(!m_initialized);
+ m_scripting.PostInitializeStage1();
+ m_scripting.TriggerOnTweak();
+}
- m_scripting.PostInitialize();
+void LuaVM::PostInitializeStage2()
+{
+ assert(!m_initialized);
+ m_scripting.PostInitializeStage2();
m_scripting.TriggerOnInit();
if (CET::Get().GetOverlay().IsEnabled())
m_scripting.TriggerOnOverlayOpen();
diff --git a/src/scripting/LuaVM.h b/src/scripting/LuaVM.h
index 8d820399..ab4ff4a9 100644
--- a/src/scripting/LuaVM.h
+++ b/src/scripting/LuaVM.h
@@ -11,6 +11,7 @@ using TSetMousePosition = BOOL(void*, HWND, long, long);
using TTDBIDCtorDerive = TDBID*(const TDBID*, TDBID*, const char*);
using TRunningStateRun = bool(uintptr_t, uintptr_t);
using TSetLoadingState = uintptr_t(uintptr_t, int);
+using TTweakDBLoad = uint64_t(uintptr_t, uintptr_t);
struct TDBIDLookupEntry
{
@@ -51,7 +52,8 @@ struct LuaVM
void RegisterTDBIDString(uint64_t aValue, uint64_t aBase, const std::string& acString);
- void PostInitialize();
+ void PostInitializeStage1();
+ void PostInitializeStage2();
protected:
@@ -63,6 +65,7 @@ struct LuaVM
static TDBID* HookTDBIDCtorDerive(TDBID* apBase, TDBID* apThis, const char* acpName);
static bool HookRunningStateRun(uintptr_t aThis, uintptr_t aApp);
static uintptr_t HookSetLoadingState(uintptr_t aThis, int aState);
+ static uint64_t HookTweakDBLoad(uintptr_t aThis, uintptr_t aParam);
private:
@@ -77,6 +80,7 @@ struct LuaVM
TTDBIDCtorDerive* m_realTDBIDCtorDerive{ nullptr };
TRunningStateRun* m_realRunningStateRun{ nullptr };
TSetLoadingState* m_realSetLoadingState{ nullptr };
+ TTweakDBLoad* m_realTweakDBLoad{ nullptr };
std::chrono::time_point m_lastframe;
diff --git a/src/scripting/LuaVM_Hooks.cpp b/src/scripting/LuaVM_Hooks.cpp
index 30fc14a7..6b106fd8 100644
--- a/src/scripting/LuaVM_Hooks.cpp
+++ b/src/scripting/LuaVM_Hooks.cpp
@@ -141,13 +141,24 @@ uintptr_t LuaVM::HookSetLoadingState(uintptr_t aThis, int aState)
{
std::call_once(s_initBarrier, [&luavm]()
{
- luavm.PostInitialize();
+ luavm.PostInitializeStage2();
});
}
return luavm.m_realSetLoadingState(aThis, aState);
}
+uint64_t LuaVM::HookTweakDBLoad(uintptr_t aThis, uintptr_t aParam)
+{
+ auto& luavm = CET::Get().GetVM();
+
+ const auto ret = luavm.m_realTweakDBLoad(aThis, aParam);
+
+ luavm.PostInitializeStage1();
+
+ return ret;
+}
+
void LuaVM::Hook(Options& aOptions)
{
auto& gameImage = aOptions.GameImage;
@@ -241,6 +252,23 @@ void LuaVM::Hook(Options& aOptions)
}
}
}
+
+ {
+ const mem::pattern cPattern("48 89 5C 24 10 48 89 7C 24 18 4C 89 74 24 20 55 48 8B EC 48 83 EC 70 48");
+ const mem::default_scanner cScanner(cPattern);
+ uint8_t* pLocation = cScanner(gameImage.TextRegion).as();
+
+ if (pLocation)
+ {
+ if (MH_CreateHook(pLocation, &HookTweakDBLoad, reinterpret_cast(&m_realTweakDBLoad)) != MH_OK ||
+ MH_EnableHook(pLocation) != MH_OK)
+ spdlog::error("Could not hook TweakDB::Load function!");
+ else
+ {
+ spdlog::info("TweakDB::Load function hook complete!");
+ }
+ }
+ }
// Disable SetLoadingState hook temporarily and get back to log count workaround
// as it introduces major breaking change for onInit handler.
diff --git a/src/scripting/ScriptContext.cpp b/src/scripting/ScriptContext.cpp
index 8960ad2e..86b55032 100644
--- a/src/scripting/ScriptContext.cpp
+++ b/src/scripting/ScriptContext.cpp
@@ -43,6 +43,8 @@ ScriptContext::ScriptContext(LuaSandbox& aLuaSandbox, const std::filesystem::pat
{
if(acName == "onInit")
m_onInit = aCallback;
+ else if(acName == "onTweak")
+ m_onTweak = aCallback;
else if(acName == "onShutdown")
m_onShutdown = aCallback;
else if(acName == "onUpdate")
@@ -155,6 +157,13 @@ const TiltedPhoques::Vector& ScriptContext::GetBinds() const
return m_vkBindInfos;
}
+void ScriptContext::TriggerOnTweak() const
+{
+ auto state = m_sandbox.GetState();
+
+ TryLuaFunction(m_logger, m_onTweak);
+}
+
void ScriptContext::TriggerOnInit() const
{
auto state = m_sandbox.GetState();
diff --git a/src/scripting/ScriptContext.h b/src/scripting/ScriptContext.h
index 8be8af3b..83d3192e 100644
--- a/src/scripting/ScriptContext.h
+++ b/src/scripting/ScriptContext.h
@@ -12,6 +12,7 @@ struct ScriptContext
const TiltedPhoques::Vector& GetBinds() const;
+ void TriggerOnTweak() const;
void TriggerOnInit() const;
void TriggerOnUpdate(float aDeltaTime) const;
void TriggerOnDraw() const;
@@ -30,6 +31,7 @@ struct ScriptContext
LuaSandbox& m_sandbox;
size_t m_sandboxID;
sol::object m_object{ };
+ sol::function m_onTweak{ };
sol::function m_onInit{ };
sol::function m_onShutdown{ };
sol::function m_onUpdate{ };
diff --git a/src/scripting/ScriptStore.cpp b/src/scripting/ScriptStore.cpp
index 6db8b2c2..13b0a15c 100644
--- a/src/scripting/ScriptStore.cpp
+++ b/src/scripting/ScriptStore.cpp
@@ -74,6 +74,12 @@ const TiltedPhoques::Vector& ScriptStore::GetBinds() const
return m_vkBindInfos;
}
+void ScriptStore::TriggerOnTweak() const
+{
+ for (const auto& kvp : m_contexts)
+ kvp.second.TriggerOnTweak();
+}
+
void ScriptStore::TriggerOnInit() const
{
for (const auto& kvp : m_contexts)
diff --git a/src/scripting/ScriptStore.h b/src/scripting/ScriptStore.h
index 8265ea7c..85659a34 100644
--- a/src/scripting/ScriptStore.h
+++ b/src/scripting/ScriptStore.h
@@ -11,6 +11,7 @@ struct ScriptStore
const TiltedPhoques::Vector& GetBinds() const;
+ void TriggerOnTweak() const;
void TriggerOnInit() const;
void TriggerOnUpdate(float aDeltaTime) const;
void TriggerOnDraw() const;
diff --git a/src/scripting/Scripting.cpp b/src/scripting/Scripting.cpp
index 71679eef..646bd1ca 100644
--- a/src/scripting/Scripting.cpp
+++ b/src/scripting/Scripting.cpp
@@ -126,7 +126,7 @@ void Scripting::Initialize()
m_store.LoadAll();
}
-void Scripting::PostInitialize()
+void Scripting::PostInitializeStage1()
{
auto lua = m_lua.Lock();
auto& luaVm = lua.Get();
@@ -396,29 +396,6 @@ void Scripting::PostInitialize()
luaGlobal["LocKey"] = luaVm["LocKey"];
- luaGlobal["NewObject"] = [this](const std::string& acName, sol::this_environment aEnv) -> sol::object
- {
- auto* pRtti = RED4ext::CRTTISystem::Get();
- auto* pType = pRtti->GetType(RED4ext::FNV1a(acName.c_str()));
-
- if (!pType)
- {
- const sol::environment cEnv = aEnv;
- std::shared_ptr logger = cEnv["__logger"].get>();
- logger->info("Type '{}' not found.", acName);
- return sol::nil;
- }
-
- // Always try to return instance wrapped in Handle<> if the type allows it.
- // See NewHandle() for more info.
- return RTTIHelper::Get().NewHandle(pType, sol::nullopt);
- };
-
- luaGlobal["GetSingleton"] = [this](const std::string& acName, sol::this_environment aThisEnv)
- {
- return this->GetSingletonHandle(acName, aThisEnv);
- };
-
luaVm.new_usertype("GameOptions",
sol::meta_function::construct, sol::no_constructor,
"Print", &GameOptions::Print,
@@ -453,23 +430,6 @@ void Scripting::PostInitialize()
luaGlobal["TweakDB"] = TweakDB(m_lua.AsRef());
- luaGlobal["Override"] = [this](const std::string& acTypeName, const std::string& acFullName,
- sol::protected_function aFunction, sol::this_environment aThisEnv) -> void {
- m_override.Override(acTypeName, acFullName, aFunction, aThisEnv, true);
- };
-
- luaGlobal["ObserveBefore"] = [this](const std::string& acTypeName, const std::string& acFullName,
- sol::protected_function aFunction, sol::this_environment aThisEnv) -> void {
- m_override.Override(acTypeName, acFullName, aFunction, aThisEnv, false, false);
- };
-
- luaGlobal["ObserveAfter"] = [this](const std::string& acTypeName, const std::string& acFullName,
- sol::protected_function aFunction, sol::this_environment aThisEnv) -> void {
- m_override.Override(acTypeName, acFullName, aFunction, aThisEnv, false, true);
- };
-
- luaGlobal["Observe"] = luaGlobal["ObserveBefore"];
-
luaGlobal["GameDump"] = [this](Type* apType)
{
return apType ? apType->GameDump() : "Null";
@@ -522,18 +482,67 @@ void Scripting::PostInitialize()
};
#endif
+ luaVm["Game"] = this;
+ luaGlobal["Game"] = luaVm["Game"];
+
+ m_sandbox.PostInitialize();
+}
+
+void Scripting::PostInitializeStage2()
+{
+ auto lua = m_lua.Lock();
+ auto& luaVm = lua.Get();
+
+ sol::table luaGlobal = luaVm[m_global];
+
+ luaGlobal["NewObject"] = [this](const std::string& acName, sol::this_environment aEnv) -> sol::object
+ {
+ auto* pRtti = RED4ext::CRTTISystem::Get();
+ auto* pType = pRtti->GetType(RED4ext::FNV1a(acName.c_str()));
+
+ if (!pType)
+ {
+ const sol::environment cEnv = aEnv;
+ std::shared_ptr logger = cEnv["__logger"].get>();
+ logger->info("Type '{}' not found.", acName);
+ return sol::nil;
+ }
+
+ // Always try to return instance wrapped in Handle<> if the type allows it.
+ // See NewHandle() for more info.
+ return RTTIHelper::Get().NewHandle(pType, sol::nullopt);
+ };
+
+ luaGlobal["GetSingleton"] = [this](const std::string& acName, sol::this_environment aThisEnv)
+ {
+ return this->GetSingletonHandle(acName, aThisEnv);
+ };
+
+ luaGlobal["Override"] = [this](const std::string& acTypeName, const std::string& acFullName,
+ sol::protected_function aFunction, sol::this_environment aThisEnv) -> void {
+ m_override.Override(acTypeName, acFullName, aFunction, aThisEnv, true);
+ };
+
+ luaGlobal["ObserveBefore"] = [this](const std::string& acTypeName, const std::string& acFullName,
+ sol::protected_function aFunction, sol::this_environment aThisEnv) -> void {
+ m_override.Override(acTypeName, acFullName, aFunction, aThisEnv, false, false);
+ };
+
+ luaGlobal["ObserveAfter"] = [this](const std::string& acTypeName, const std::string& acFullName,
+ sol::protected_function aFunction, sol::this_environment aThisEnv) -> void {
+ m_override.Override(acTypeName, acFullName, aFunction, aThisEnv, false, true);
+ };
+
+ luaGlobal["Observe"] = luaGlobal["ObserveBefore"];
+
// Doesn't require RTTI, but still shouldn't be used before onInit as there is no guarantee that the required mod will be loaded before
luaGlobal["GetMod"] = [this](const std::string& acName) -> sol::object
{
return GetMod(acName);
};
- luaVm["Game"] = this;
- luaGlobal["Game"] = luaVm["Game"];
-
RTTIExtender::Initialize();
m_mapper.Register();
- m_sandbox.PostInitialize();
RegisterOverrides();
}
@@ -561,6 +570,11 @@ const TiltedPhoques::Vector& Scripting::GetBinds() const
return m_store.GetBinds();
}
+void Scripting::TriggerOnTweak() const
+{
+ m_store.TriggerOnTweak();
+}
+
void Scripting::TriggerOnInit() const
{
m_store.TriggerOnInit();
diff --git a/src/scripting/Scripting.h b/src/scripting/Scripting.h
index 5c341c83..b52ff8e2 100644
--- a/src/scripting/Scripting.h
+++ b/src/scripting/Scripting.h
@@ -15,10 +15,12 @@ struct Scripting
~Scripting() = default;
void Initialize();
- void PostInitialize();
+ void PostInitializeStage1();
+ void PostInitializeStage2();
const TiltedPhoques::Vector& GetBinds() const;
+ void TriggerOnTweak() const;
void TriggerOnInit() const;
void TriggerOnUpdate(float aDeltaTime) const;
void TriggerOnDraw() const;
From ffea5367661f4829d8c8392444b64ba5d4444613 Mon Sep 17 00:00:00 2001
From: pv
Date: Sat, 27 Nov 2021 11:30:00 +0300
Subject: [PATCH 6/8] Adjustments for TweakDB init event
---
src/scripting/Scripting.cpp | 118 ++++++++++++++++++------------------
src/scripting/Scripting.h | 2 +-
2 files changed, 60 insertions(+), 60 deletions(-)
diff --git a/src/scripting/Scripting.cpp b/src/scripting/Scripting.cpp
index 646bd1ca..81c86c10 100644
--- a/src/scripting/Scripting.cpp
+++ b/src/scripting/Scripting.cpp
@@ -135,8 +135,7 @@ void Scripting::PostInitializeStage1()
luaVm.new_usertype("__Game",
sol::meta_function::construct, sol::no_constructor,
- sol::meta_function::index, &Scripting::Index,
- sol::meta_function::new_index, &Scripting::NewIndex);
+ sol::meta_function::index, &Scripting::Index);
luaVm.new_usertype("__Type",
sol::meta_function::construct, sol::no_constructor,
@@ -430,61 +429,6 @@ void Scripting::PostInitializeStage1()
luaGlobal["TweakDB"] = TweakDB(m_lua.AsRef());
- luaGlobal["GameDump"] = [this](Type* apType)
- {
- return apType ? apType->GameDump() : "Null";
- };
-
- luaGlobal["Dump"] = [this](Type* apType, bool aDetailed)
- {
- return apType != nullptr ? apType->Dump(aDetailed) : Type::Descriptor{};
- };
-
- luaGlobal["DumpType"] = [this](const std::string& acName, bool aDetailed)
- {
- auto* pRtti = RED4ext::CRTTISystem::Get();
- auto* pType = pRtti->GetClass(RED4ext::FNV1a(acName.c_str()));
- if (!pType || pType->GetType() == RED4ext::ERTTIType::Simple)
- return Type::Descriptor();
-
- const ClassType type(m_lua.AsRef(), pType);
- return type.Dump(aDetailed);
- };
-
- luaGlobal["DumpAllTypeNames"] = [this](sol::this_environment aThisEnv)
- {
- auto* pRtti = RED4ext::CRTTISystem::Get();
-
- uint32_t count = 0;
- pRtti->types.for_each([&count](RED4ext::CName name, RED4ext::CBaseRTTIType*& type)
- {
- spdlog::info(name.ToString());
- count++;
- });
- const sol::environment cEnv = aThisEnv;
- std::shared_ptr logger = cEnv["__logger"].get>();
- logger->info("Dumped {} types", count);
- };
-
-#ifndef NDEBUG
- luaGlobal["DumpVtables"] = [this]()
- {
- // Hacky RTTI dump, this should technically only dump IScriptable instances and RTTI types as they are guaranteed to have a vtable
- // but there can occasionally be Class types that are not IScriptable derived that still have a vtable
- // some hierarchies may also not be accurately reflected due to hash ordering
- // technically this table is flattened and contains all hierarchy, but traversing the hierarchy first reduces
- // error when there are classes that instantiate a parent class but don't actually have a subclass instance
- GameMainThread::Get().AddTask(&GameDump::DumpVTablesTask::Run);
- };
- luaGlobal["DumpReflection"] = [this](bool aVerbose, bool aExtendedPath, bool aPropertyHolders)
- {
- RED4ext::GameReflection::Dump(m_paths.CETRoot() / "dumps", aVerbose, aExtendedPath, aPropertyHolders);
- };
-#endif
-
- luaVm["Game"] = this;
- luaGlobal["Game"] = luaVm["Game"];
-
m_sandbox.PostInitialize();
}
@@ -541,8 +485,64 @@ void Scripting::PostInitializeStage2()
return GetMod(acName);
};
+ luaGlobal["GameDump"] = [this](Type* apType)
+ {
+ return apType ? apType->GameDump() : "Null";
+ };
+
+ luaGlobal["Dump"] = [this](Type* apType, bool aDetailed)
+ {
+ return apType != nullptr ? apType->Dump(aDetailed) : Type::Descriptor{};
+ };
+
+ luaGlobal["DumpType"] = [this](const std::string& acName, bool aDetailed)
+ {
+ auto* pRtti = RED4ext::CRTTISystem::Get();
+ auto* pType = pRtti->GetClass(RED4ext::FNV1a(acName.c_str()));
+ if (!pType || pType->GetType() == RED4ext::ERTTIType::Simple)
+ return Type::Descriptor();
+
+ const ClassType type(m_lua.AsRef(), pType);
+ return type.Dump(aDetailed);
+ };
+
+ luaGlobal["DumpAllTypeNames"] = [this](sol::this_environment aThisEnv)
+ {
+ auto* pRtti = RED4ext::CRTTISystem::Get();
+
+ uint32_t count = 0;
+ pRtti->types.for_each([&count](RED4ext::CName name, RED4ext::CBaseRTTIType*& type)
+ {
+ spdlog::info(name.ToString());
+ count++;
+ });
+ const sol::environment cEnv = aThisEnv;
+ std::shared_ptr logger = cEnv["__logger"].get>();
+ logger->info("Dumped {} types", count);
+ };
+
+#ifndef NDEBUG
+ luaGlobal["DumpVtables"] = [this]()
+ {
+ // Hacky RTTI dump, this should technically only dump IScriptable instances and RTTI types as they are guaranteed to have a vtable
+ // but there can occasionally be Class types that are not IScriptable derived that still have a vtable
+ // some hierarchies may also not be accurately reflected due to hash ordering
+ // technically this table is flattened and contains all hierarchy, but traversing the hierarchy first reduces
+ // error when there are classes that instantiate a parent class but don't actually have a subclass instance
+ GameMainThread::Get().AddTask(&GameDump::DumpVTablesTask::Run);
+ };
+ luaGlobal["DumpReflection"] = [this](bool aVerbose, bool aExtendedPath, bool aPropertyHolders)
+ {
+ RED4ext::GameReflection::Dump(m_paths.CETRoot() / "dumps", aVerbose, aExtendedPath, aPropertyHolders);
+ };
+#endif
+
+ luaVm["Game"] = this;
+ luaGlobal["Game"] = luaVm["Game"];
+
RTTIExtender::Initialize();
m_mapper.Register();
+ m_sandbox.PostInitialize();
RegisterOverrides();
}
@@ -661,7 +661,7 @@ sol::object Scripting::Index(const std::string& acName, sol::this_state aState,
return InternalIndex(acName, aState, aEnv);
}
-sol::object Scripting::NewIndex(const std::string& acName, sol::object aParam)
+sol::object Scripting::InternalNewIndex(const std::string& acName, sol::object aParam)
{
auto& property = m_properties[acName];
property = std::move(aParam);
@@ -690,7 +690,7 @@ sol::protected_function Scripting::InternalIndex(const std::string& acName, sol:
return sol::nil;
}
- return NewIndex(acName, std::move(func));
+ return InternalNewIndex(acName, std::move(func));
}
sol::object Scripting::GetSingletonHandle(const std::string& acName, sol::this_environment aThisEnv)
diff --git a/src/scripting/Scripting.h b/src/scripting/Scripting.h
index b52ff8e2..57bae058 100644
--- a/src/scripting/Scripting.h
+++ b/src/scripting/Scripting.h
@@ -45,7 +45,7 @@ struct Scripting
void RegisterOverrides();
sol::object Index(const std::string& acName, sol::this_state aState, sol::this_environment aEnv);
- sol::object NewIndex(const std::string& acName, sol::object aParam);
+ sol::object InternalNewIndex(const std::string& acName, sol::object aParam);
sol::object GetSingletonHandle(const std::string& acName, sol::this_environment aThisEnv);
sol::protected_function InternalIndex(const std::string& acName, sol::this_state aState,
sol::this_environment aEnv);
From b483e486c991c3cc49ea39027bab0af293328eea Mon Sep 17 00:00:00 2001
From: pv
Date: Sun, 28 Nov 2021 03:05:04 +0300
Subject: [PATCH 7/8] Refactoring
---
src/scripting/Scripting.cpp | 16 +++-------------
src/scripting/Scripting.h | 3 ---
2 files changed, 3 insertions(+), 16 deletions(-)
diff --git a/src/scripting/Scripting.cpp b/src/scripting/Scripting.cpp
index 81c86c10..c501850a 100644
--- a/src/scripting/Scripting.cpp
+++ b/src/scripting/Scripting.cpp
@@ -658,18 +658,6 @@ sol::object Scripting::Index(const std::string& acName, sol::this_state aState,
return itor->second;
}
- return InternalIndex(acName, aState, aEnv);
-}
-
-sol::object Scripting::InternalNewIndex(const std::string& acName, sol::object aParam)
-{
- auto& property = m_properties[acName];
- property = std::move(aParam);
- return property;
-}
-
-sol::protected_function Scripting::InternalIndex(const std::string& acName, sol::this_state aState, sol::this_environment aEnv)
-{
auto func = RTTIHelper::Get().ResolveFunction(acName);
if (!func)
@@ -690,7 +678,9 @@ sol::protected_function Scripting::InternalIndex(const std::string& acName, sol:
return sol::nil;
}
- return InternalNewIndex(acName, std::move(func));
+ auto& property = m_properties[acName];
+ property = std::move(func);
+ return property;
}
sol::object Scripting::GetSingletonHandle(const std::string& acName, sol::this_environment aThisEnv)
diff --git a/src/scripting/Scripting.h b/src/scripting/Scripting.h
index 57bae058..a4fe05ae 100644
--- a/src/scripting/Scripting.h
+++ b/src/scripting/Scripting.h
@@ -45,10 +45,7 @@ struct Scripting
void RegisterOverrides();
sol::object Index(const std::string& acName, sol::this_state aState, sol::this_environment aEnv);
- sol::object InternalNewIndex(const std::string& acName, sol::object aParam);
sol::object GetSingletonHandle(const std::string& acName, sol::this_environment aThisEnv);
- sol::protected_function InternalIndex(const std::string& acName, sol::this_state aState,
- sol::this_environment aEnv);
private:
TiltedPhoques::Lockable m_lua;
From 60f96c898a50e3b50afd8536fd7117504f9fb43b Mon Sep 17 00:00:00 2001
From: pv
Date: Sat, 4 Dec 2021 12:34:15 +0300
Subject: [PATCH 8/8] Allow full uint64 range for LocKeys
---
src/scripting/Scripting.cpp | 34 ++++++++++++++++++++++++++++++----
1 file changed, 30 insertions(+), 4 deletions(-)
diff --git a/src/scripting/Scripting.cpp b/src/scripting/Scripting.cpp
index c501850a..205a389d 100644
--- a/src/scripting/Scripting.cpp
+++ b/src/scripting/Scripting.cpp
@@ -88,7 +88,7 @@ void Scripting::Initialize()
const auto resolution = m_d3d12.GetResolution();
return {static_cast(resolution.cx), static_cast(resolution.cy)};
};
-
+
luaGlobal["ModArchiveExists"] = [this](const std::string& acArchiveName) -> bool
{
const auto cAbsPath = absolute(m_paths.ArchiveModsRoot() / acArchiveName);
@@ -388,10 +388,36 @@ void Scripting::PostInitializeStage1()
luaVm.new_usertype("LocKey",
sol::constructors(),
- sol::call_constructor, sol::constructors(),
sol::meta_function::to_string, &gamedataLocKeyWrapper::ToString,
sol::meta_function::equal_to, &gamedataLocKeyWrapper::operator==,
- "hash", &gamedataLocKeyWrapper::hash);
+ sol::call_constructor, sol::factories([](sol::object aValue, sol::this_state aState) -> sol::object {
+ sol::state_view lua(aState);
+ gamedataLocKeyWrapper result(0);
+
+ if (aValue != sol::nil)
+ {
+ if (aValue.get_type() == sol::type::number)
+ {
+ result.hash = aValue.as();
+ }
+ else if (IsLuaCData(aValue))
+ {
+ std::string str = lua["tostring"](aValue);
+ result.hash = std::stoull(str);
+ }
+ else if (aValue.get_type() == sol::type::string)
+ {
+ result.hash = RED4ext::FNV1a64(aValue.as());
+ }
+ }
+
+ return sol::object(lua, sol::in_place, std::move(result));
+ }),
+ "hash", sol::property([](gamedataLocKeyWrapper& aThis, sol::this_state aState) -> sol::object {
+ sol::state_view lua(aState);
+ auto converted = lua.script(fmt::format("return {}ull", aThis.hash));
+ return converted.get();
+ }));
luaGlobal["LocKey"] = luaVm["LocKey"];
@@ -505,7 +531,7 @@ void Scripting::PostInitializeStage2()
const ClassType type(m_lua.AsRef(), pType);
return type.Dump(aDetailed);
};
-
+
luaGlobal["DumpAllTypeNames"] = [this](sol::this_environment aThisEnv)
{
auto* pRtti = RED4ext::CRTTISystem::Get();