diff --git a/code/components/citizen-scripting-core/include/ScriptInvoker.h b/code/components/citizen-scripting-core/include/ScriptInvoker.h deleted file mode 100644 index 01dcde5b55..0000000000 --- a/code/components/citizen-scripting-core/include/ScriptInvoker.h +++ /dev/null @@ -1,326 +0,0 @@ -/* - * This file is part of the CitizenFX project - http://citizen.re/ - * - * See LICENSE and MENTIONS in the root of the source tree for information - * regarding licensing. - */ - -#pragma once - -#include "fxNativeContext.h" - -// scrString corresponds to a binary string: may contain null-terminators, i.e, -// lua_pushlstring != lua_pushstring, and/or non-UTF valid characters. -#define SCRSTRING_MAGIC_BINARY 0xFEED1212 - -#ifdef COMPILING_CITIZEN_SCRIPTING_CORE -#define CSCRC_EXPORT DLL_EXPORT -#else -#define CSCRC_EXPORT DLL_IMPORT -#endif - -namespace fx::invoker -{ -template -inline constexpr bool always_false_v = false; - -enum class MetaField : uint8_t -{ - PointerValueInt, - PointerValueFloat, - PointerValueVector, - ReturnResultAnyway, - ResultAsInteger, - ResultAsLong, - ResultAsFloat, - ResultAsString, - ResultAsVector, - ResultAsObject, - Max -}; - -struct ScrObject -{ - const char* data; - uintptr_t length; -}; - -struct ScrString -{ - const char* str; - size_t len; - uint32_t magic; -}; - -struct ScrVector -{ - alignas(8) float x; - alignas(8) float y; - alignas(8) float z; - - ScrVector() - : x(0.f), y(0.f), z(0.f) - { - } - - ScrVector(float x, float y, float z) - : x(x), y(y), z(z) - { - } -}; - -struct PointerFieldEntry -{ - bool empty; - uintptr_t value; - PointerFieldEntry() - : empty(true), value(0) - { - } -}; - -struct PointerField -{ - PointerFieldEntry data[64]; -}; - -struct ArgumentType -{ - uint32_t Size : 30; - uint32_t IsString : 1; - uint32_t IsPointer : 1; -}; - -struct IsolatedBuffer -{ - size_t Size; - uint8_t* SafeBuffer; - uint8_t* NativeBuffer; -}; - -struct CSCRC_EXPORT ScriptNativeContext : fxNativeContext -{ - uintptr_t initialArguments[32]; - ArgumentType types[32]; - - uint32_t pointerMask = 0; - const uint32_t* typeInfo = nullptr; - - PointerField* pointerFields; - - int numReturnValues = 0; // return values and their types - uintptr_t retvals[16]; - MetaField rettypes[16]; - MetaField returnValueCoercion = MetaField::Max; // coercion for the result value - - IsolatedBuffer isolatedBuffers[8]; - int numIsolatedBuffers = 0; - - static uint8_t s_metaFields[(size_t)MetaField::Max]; - - ScriptNativeContext(uint64_t hash, PointerField* fields); - ScriptNativeContext(const ScriptNativeContext&) = delete; - ScriptNativeContext(ScriptNativeContext&&) = delete; - - virtual void DoSetError(const char* msg) = 0; - - template - bool SetError(std::string_view string, const Args&... args); - - bool PushRaw(uintptr_t value, ArgumentType type); - - template - bool Push(const T& value, size_t size = 0); - - bool PushReturnValue(MetaField field, bool zero); - bool PushMetaPointer(uint8_t* ptr); - - template - bool ProcessResults(Visitor&& visitor); - - template - bool ProcessPrimaryResult(Visitor&& visitor); - - template - bool ProcessExtraResults(Visitor&& visitor); - - bool PreInvoke(); - bool PostInvoke(); - - bool CheckArguments(); - bool CheckResults(); - - bool IsolatePointer(int index); -}; - -template -inline bool ScriptNativeContext::SetError(std::string_view string, const Args&... args) -{ - DoSetError(vva(string, fmt::make_printf_args(args...))); - - return false; -} - -template -inline bool ScriptNativeContext::Push(const T& value, size_t size) -{ - using TVal = std::decay_t; - - uintptr_t raw = 0; - - if constexpr (std::is_pointer_v) - { - raw = reinterpret_cast(value); - } - else if constexpr (std::is_integral_v) - { - raw = (uintptr_t)(int64_t)value; // TODO: Limit native integers to 32 bits. - } - else if constexpr (std::is_same_v) - { - raw = *reinterpret_cast(&value); - } - else - { - static_assert(always_false_v, "Invalid argument type"); - } - - ArgumentType type; - type.IsPointer = std::is_pointer_v; - type.IsString = std::is_same_v || std::is_same_v; - type.Size = size; - - return PushRaw(raw, type); -} - -template -inline bool ScriptNativeContext::ProcessResults(Visitor&& visitor) -{ - // if no other result was requested, or we need to return the result anyway, push the result - if ((numReturnValues == 0) || (returnValueCoercion != MetaField::Max)) - { - if (!ProcessPrimaryResult(visitor)) - { - return false; - } - } - - if (!ProcessExtraResults(visitor)) - { - return false; - } - - return true; -} - -template -inline bool ScriptNativeContext::ProcessPrimaryResult(Visitor&& visitor) -{ - // handle the type coercion - switch (returnValueCoercion) - { - case MetaField::ResultAsString: - { - auto strString = reinterpret_cast(&arguments[0]); - - if (strString->str && strString->magic == SCRSTRING_MAGIC_BINARY) - { - return visitor(*strString); - } - else - { - return visitor(strString->str); - } - } - - case MetaField::ResultAsFloat: - { - return visitor(*reinterpret_cast(&arguments[0])); - } - - case MetaField::ResultAsVector: - { - return visitor(*reinterpret_cast(&arguments[0])); - } - - case MetaField::ResultAsObject: - { - return visitor(*reinterpret_cast(&arguments[0])); - } - - case MetaField::ResultAsInteger: - { - return visitor(*reinterpret_cast(&arguments[0])); - } - - case MetaField::ResultAsLong: - { - return visitor(*reinterpret_cast(&arguments[0])); - } - - default: - { - const int32_t integer = *reinterpret_cast(&arguments[0]); - - if (integer == 0) - { - return visitor(false); - } - else - { - return visitor(integer); - } - } - } -} - -template -inline bool ScriptNativeContext::ProcessExtraResults(Visitor&& visitor) -{ - // loop over the return value pointers - for (int i = 0; i < numReturnValues;) - { - switch (rettypes[i]) - { - case MetaField::PointerValueInt: - { - if (!visitor(*reinterpret_cast(&retvals[i]))) - { - return false; - } - - i++; - break; - } - - case MetaField::PointerValueFloat: - { - if (!visitor(*reinterpret_cast(&retvals[i]))) - { - return false; - } - - i++; - break; - } - - case MetaField::PointerValueVector: - { - if (!visitor(*reinterpret_cast(&retvals[i]))) - { - return false; - } - - i += 3; - break; - } - - default: - continue; - } - } - - return true; -} - -} diff --git a/code/components/citizen-scripting-core/src/ScriptInvoker.cpp b/code/components/citizen-scripting-core/src/ScriptInvoker.cpp deleted file mode 100644 index de6ce0de88..0000000000 --- a/code/components/citizen-scripting-core/src/ScriptInvoker.cpp +++ /dev/null @@ -1,570 +0,0 @@ -/* - * This file is part of the CitizenFX project - http://citizen.re/ - * - * See LICENSE and MENTIONS in the root of the source tree for information - * regarding licensing. - */ - -#include "StdInc.h" - -#include "ScriptInvoker.h" - -#include - -#include - -#if __has_include() -#include -#endif - -#define PAS_ARG_POINTER 0x80000000 // This argument is a pointer -#define PAS_ARG_STRING 0x40000000 // This argument is a string -#define PAS_ARG_BUFFER 0x20000000 // This argument is a read-only buffer (and the next argument is its length) -#define PAS_ARG_SIZE 0x1FFFFFFF // The minimum allowable size of this pointer argument - -// The return type of the function -#define PAS_RET_VOID 0 -#define PAS_RET_INT 1 -#define PAS_RET_FLOAT 2 -#define PAS_RET_LONG 3 -#define PAS_RET_VECTOR3 4 -#define PAS_RET_STRING 5 -#define PAS_RET_SCRSTRING 6 -#define PAS_RET_SCROBJECT 7 - -static constexpr size_t g_IsolatedBufferSize = 3 * 4096; -static uint8_t* g_IsolatedBuffers[32]; -static size_t g_IsolatedBufferIndex = 0; -static bool g_EnforceTypeInfo = false; - -namespace fx::invoker -{ -uint8_t ScriptNativeContext::s_metaFields[(size_t)MetaField::Max]; - -ScriptNativeContext::ScriptNativeContext(uint64_t hash, PointerField* fields) - : pointerFields(fields) -{ - nativeIdentifier = hash; - numArguments = 0; - numResults = 0; - -#if __has_include() - typeInfo = fx::scripting::GetNativeTypeInfo(hash); -#endif -} - -bool ScriptNativeContext::PushRaw(uintptr_t value, ArgumentType type) -{ - if (numArguments >= 32) - { - return SetError("too many arguments"); - } - - arguments[numArguments] = value; - types[numArguments] = type; - pointerMask |= (uint32_t)type.IsPointer << numArguments; - - ++numArguments; - - return true; -} - -bool ScriptNativeContext::PushReturnValue(MetaField field, bool zero) -{ - int slots = (field == MetaField::PointerValueVector) ? 3 : 1; - - if (numReturnValues + slots > std::size(retvals)) - { - return SetError("too many return value arguments"); - } - - uintptr_t* arg = &retvals[numReturnValues]; - - if (zero) - { - for (int i = 0; i < slots; ++i) - { - arg[i] = 0; - } - } - - rettypes[numReturnValues] = field; - numReturnValues += slots; - - return Push(arg, slots * sizeof(uintptr_t)); -} - -bool ScriptNativeContext::PushMetaPointer(uint8_t* ptr) -{ - // if the pointer is a metafield - if (ptr >= s_metaFields && ptr < &s_metaFields[(int)MetaField::Max]) - { - MetaField metaField = static_cast(ptr - s_metaFields); - - // switch on the metafield - switch (metaField) - { - case MetaField::PointerValueInt: - case MetaField::PointerValueFloat: - case MetaField::PointerValueVector: - { - return PushReturnValue(metaField, true); - break; - } - case MetaField::ReturnResultAnyway: - // We are going to return the result anyway if they've given us the type. - if (returnValueCoercion == MetaField::Max) - returnValueCoercion = metaField; - break; - case MetaField::ResultAsInteger: - case MetaField::ResultAsLong: - case MetaField::ResultAsString: - case MetaField::ResultAsFloat: - case MetaField::ResultAsVector: - case MetaField::ResultAsObject: - returnValueCoercion = metaField; - break; - default: - break; - } - } - // or if the pointer is a runtime pointer field - else if (ptr >= reinterpret_cast(pointerFields) && ptr < (reinterpret_cast(pointerFields) + (sizeof(PointerField) * 2))) - { - // guess the type based on the pointer field type - const intptr_t ptrField = ptr - reinterpret_cast(pointerFields); - const MetaField metaField = static_cast(ptrField / sizeof(PointerField)); - - if (metaField == MetaField::PointerValueInt || metaField == MetaField::PointerValueFloat) - { - auto ptrFieldEntry = reinterpret_cast(ptr); - - retvals[numReturnValues] = ptrFieldEntry->value; - ptrFieldEntry->empty = true; - - return PushReturnValue(metaField, false); - } - } - else - { - return SetError("unknown userdata pointer"); - } - - return true; -} - -bool ScriptNativeContext::PreInvoke() -{ - if (!CheckArguments()) - { - return false; - } - - // This native might have new arguments added that we don't know about, so zero some extra arguments - for (int i = numArguments, j = std::min(i + 3, 32); i < j; ++i) - { - arguments[i] = 0; - } - - // Make a copy of the arguments, so we can do some checks afterwards. - for (int i = 0; i < numArguments; ++i) - { - initialArguments[i] = arguments[i]; - } - - return true; -} - -bool ScriptNativeContext::PostInvoke() -{ - if (!CheckResults()) - { - return false; - } - - // Copy any data back out - for (int i = 0; i < numIsolatedBuffers; ++i) - { - auto& buffer = isolatedBuffers[i]; - - std::memcpy(buffer.NativeBuffer, buffer.SafeBuffer, buffer.Size); - } - - // Don't allow returning a pointer which was passed in. - switch (returnValueCoercion) - { - case MetaField::ResultAsLong: - { - if (uintptr_t result = arguments[0]) - { - for (int i = 0; i < numArguments; ++i) - { - // Avoid leaking the addresses of any pointers - if (result == initialArguments[i] && types[i].IsPointer) - { - return SetError("pointer result matches an argument"); - } - } - } - - break; - } - - case MetaField::ResultAsString: - { - if (uintptr_t result = arguments[0]) - { - for (int i = 0; i < numArguments; ++i) - { - // Avoid reading arguments as pointers - if (result == initialArguments[i]) - { - // We are returning a pointer which matches what we passed in. - // However, allow it if we are just returning a string we already passed in (i.e GET_CONVAR default) - if (types[i].IsString) - { - continue; - } - - return SetError("pointer result matches an argument"); - } - } - } - - // The caller passed in a ScrString marker, and it's still there. - if (static_cast(arguments[2]) == SCRSTRING_MAGIC_BINARY && initialArguments[2] == arguments[2]) - { - return SetError("unexpected scrstring marker"); - } - - break; - } - - case MetaField::ResultAsObject: - { - if (uintptr_t result = arguments[0]) - { - for (int i = 0; i < numArguments; ++i) - { - // An object should never match one which was passed in - if (result == initialArguments[i]) - { - return SetError("pointer result matches an argument"); - } - } - } - - break; - } - } - - return true; -} - -bool ScriptNativeContext::CheckArguments() -{ - int nargs = 0; - int nvalid = 0; - - if (typeInfo) - { - nargs = (int)typeInfo[0]; - - // Nullified native - if (nargs == -1) - { - if (!g_EnforceTypeInfo) - { - return true; - } - - return SetError("native is disabled"); - } - - uint32_t pmask = typeInfo[1]; - - // Fast path for natives which don't expect any pointers, and none were passed in. - if (pmask == 0x00000000 && pmask == pointerMask) - { - if (nargs == numArguments) - { - // We've been given the correct number of arguments, and none are pointers (nor should they be). - return true; - } - - // Don't bother checking arguments we already know about, since they aren't pointers. - nvalid = nargs; - } - - // Some natives have extra arguments we don't know about, so don't be too strict - if (numArguments < nargs) - { - return SetError("not enough arguments (%i < %i)", numArguments, nargs); - } - } - else if (g_EnforceTypeInfo) - { - // TODO: Nullify any unknown natives instead? - nargs = 0; - } - else - { - return true; - } - - for (int i = nvalid; i < nargs; ++i) - { - ArgumentType type = types[i]; - - uint32_t expected = typeInfo[i + 3]; - - if (!(expected & PAS_ARG_POINTER)) // Argument should not be a pointer - { - if (type.IsPointer) - { - return SetError("arg[%i]: expected non-pointer", i); - } - - continue; - } - - if (!type.IsPointer) - { - if (arguments[i] != 0) // Argument should be a pointer, but also allow NULL - { - return SetError("arg[%i]: expected pointer, got non-zero integer", i); - } - - continue; - } - - if (expected & PAS_ARG_STRING) // Argument should be a string - { - if (!type.IsString) - { - return SetError("arg[%i]: expected string", i); - } - - continue; - } - - uint32_t minSize = expected & PAS_ARG_SIZE; - - if (type.Size < minSize) - { - return SetError("arg[%i]: buffer too small (%u < %u)", i, type.Size, minSize); - } - - if (minSize != 0) // We know how large this argument should be, so don't bother isolating it. - { - // TODO: Don't trust the pointer size for now. There are too many natives with incorrect pointer types. - // continue; - } - - if (expected & PAS_ARG_BUFFER) // Argument is a read-only buffer, followed by its length - { - size_t length = arguments[i + 1]; - - if (length > type.Size) - { - return SetError("arg[%i]: buffer length too large (%u > %u)", i, length, type.Size); - } - - continue; - } - - // We're not really sure how large this pointer should be, so isolate it. - if (!IsolatePointer(i)) - { - return false; - } - } - - // Process any unknown arguments which might have been added in later versions, or this is an unknown native. - for (int i = nargs; i < numArguments; ++i) - { - if (types[i].IsPointer) - { - // We have no idea how this pointer might be used, so isolate it. - if (!IsolatePointer(i)) - { - return false; - } - } - else - { - // If the argument isn't 0 (might be a NULL pointer), replace the upper bits so it's never a valid pointer. - if (arguments[i] != 0) - { - arguments[i] = 0xDEADBEEF00000000 | (arguments[i] & 0xFFFFFFFF); - } - } - } - - return true; -} - -bool ScriptNativeContext::CheckResults() -{ - if (!typeInfo) - { - return true; - } - - uint32_t rtype = typeInfo[2]; - - switch (returnValueCoercion) - { - case MetaField::ResultAsLong: - { - if (rtype == PAS_RET_INT) - { - arguments[0] &= 0xFFFFFFFF; - } - else if (rtype != PAS_RET_LONG) - { - return SetError("result type is not a long"); - } - - break; - } - - case MetaField::ResultAsString: - { - if (rtype == PAS_RET_STRING) - { - // Clear potential ScrString marker - arguments[1] = 0; - arguments[2] = 0; - } - else if (rtype != PAS_RET_SCRSTRING) - { - return SetError("result type is not a string"); - } - - break; - } - - case MetaField::ResultAsVector: - { - if (rtype != PAS_RET_VECTOR3) - { - return SetError("result type is not a vector"); - } - - break; - } - - case MetaField::ResultAsObject: - { - if (rtype != PAS_RET_SCROBJECT) - { - return SetError("result type is not an object"); - } - - break; - } - - default: - { - if (rtype != PAS_RET_INT && rtype != PAS_RET_FLOAT) - { - if (arguments[0] != 0) // Preserve zero/false - { - arguments[0] = 0xDEADBEEF7FEDCAFE; // Lower 32 bits are NaN - } - } - - break; - } - } - - return true; -} - -bool ScriptNativeContext::IsolatePointer(int index) -{ - ArgumentType type = types[index]; - - if (!type.IsPointer) - { - return SetError("arg[%i]: is not a pointer", index); - } - - if (numIsolatedBuffers >= std::size(isolatedBuffers)) - { - return SetError("too many unknown pointers"); - } - - // Too large! - if (type.Size >= g_IsolatedBufferSize) - { - return SetError("arg[%i]: buffer too large to isolate", index); - } - - auto& buffer = isolatedBuffers[numIsolatedBuffers++]; - - buffer.Size = type.Size; - buffer.SafeBuffer = g_IsolatedBuffers[g_IsolatedBufferIndex]; - g_IsolatedBufferIndex = (g_IsolatedBufferIndex + 1) % std::size(g_IsolatedBuffers); - - buffer.NativeBuffer = std::exchange(*reinterpret_cast(&arguments[index]), buffer.SafeBuffer); - - // Copy in any existing data. - std::memcpy(buffer.SafeBuffer, buffer.NativeBuffer, buffer.Size); - - // These buffers shouldn't contain any sensitive data, so just add a terminator, don't bother zeroing the rest. - buffer.SafeBuffer[buffer.Size] = type.IsString ? 0x00 : 0xFF; - - return true; -} - -} - -static -#ifdef IS_FXSERVER -InitFunction -#else -HookFunction // FIXME: ICoreGameInit isn't registered when our InitFunctions runs -#endif -initFunction([] -{ - if (!launch::IsSDK()) - { -#ifdef GTA_FIVE - g_EnforceTypeInfo = true; - - Instance::Get()->OnSetVariable.Connect([](const std::string& name, bool value) - { - if (name == "storyMode") - { - g_EnforceTypeInfo = !value; - } - }); -#else - // TODO: Generate type info for other games -#endif - } - - size_t total_size = (g_IsolatedBufferSize + 4096) * std::size(g_IsolatedBuffers); - - uint8_t* region = -#ifdef _WIN32 - (uint8_t*)VirtualAlloc(NULL, total_size, MEM_COMMIT, PAGE_NOACCESS); -#else - new uint8_t[total_size]{}; // TODO: Use mmap -#endif - - for (auto& buffer : g_IsolatedBuffers) - { - buffer = region; - region += g_IsolatedBufferSize + 4096; - -#ifdef _WIN32 - DWORD oldProtect; - VirtualProtect(buffer, g_IsolatedBufferSize, PAGE_READWRITE, &oldProtect); -#else - // TODO: Use mprotect -#endif - } -}); diff --git a/code/components/citizen-scripting-lua/include/LuaScriptNatives.h b/code/components/citizen-scripting-lua/include/LuaScriptNatives.h index 8e91cbbe8a..e95a8a9604 100644 --- a/code/components/citizen-scripting-lua/include/LuaScriptNatives.h +++ b/code/components/citizen-scripting-lua/include/LuaScriptNatives.h @@ -11,11 +11,121 @@ #include +// scrString corresponds to a binary string: may contain null-terminators, i.e, +// lua_pushlstring != lua_pushstring, and/or non-UTF valid characters. +#define SCRSTRING_MAGIC_BINARY 0xFEED1212 + // we fast-path non-FXS using direct RAGE calls #if defined(GTA_FIVE) || defined(IS_RDR3) || defined(GTA_NY) #include #endif +#if defined(IS_FXSERVER) || defined(GTA_NY) +struct scrVector +{ + float x; + +private: + uint32_t pad; + +public: + float y; + +private: + uint32_t pad2; + +public: + float z; + +private: + uint32_t pad3; +}; +#endif + +struct scrObject +{ + const char* data; + uintptr_t length; +}; + +struct scrString +{ + const char* str; + size_t len; + uint32_t magic; +}; + +struct scrVectorLua +{ + alignas(8) float x; + alignas(8) float y; + alignas(8) float z; + + scrVectorLua() + : x(0.f), y(0.f), z(0.f) + { + } + + scrVectorLua(float x, float y, float z) + : x(x), y(y), z(z) + { + } +}; + +template +struct alignas(16) fxLuaNativeContext : fxNativeContext +{ + fxLuaNativeContext() + : fxNativeContext() + { + memset(arguments, 0, sizeof(arguments)); + numArguments = 0; + numResults = 0; + } +}; + +#if defined(GTA_FIVE) || defined(IS_RDR3) || defined(GTA_NY) +template<> +struct alignas(16) fxLuaNativeContext : NativeContextRaw +{ + int numArguments; + alignas(16) uintptr_t arguments[32]; + + fxLuaNativeContext() + : NativeContextRaw(arguments, 0), numArguments(0) + { + memset(arguments, 0, sizeof(arguments[0]) * 4); + } +}; +#endif + +typedef struct alignas(16) fxLuaResult +{ + fx::PointerField* pointerFields; + + int numReturnValues; // return values and their types + uintptr_t retvals[16]; + LuaMetaFields rettypes[16]; + LuaMetaFields returnValueCoercion; // coercion for the result value + bool returnResultAnyway; // flag to return a result even if a pointer return value is passed + + fxLuaResult(fx::PointerField* _fields) + : pointerFields(_fields), numReturnValues(0), returnValueCoercion(LuaMetaFields::Max), returnResultAnyway(false) + { + auto* const __restrict rv = retvals; + for (size_t i = 0; i < std::size(retvals); ++i) + { + rv[i] = 0; + } + + auto* const __restrict rt = rettypes; + for (size_t i = 0; i < std::size(rettypes); ++i) + { + rt[i] = LuaMetaFields::Max; + } + } +} fxLuaResult; + #if defined(_DEBUG) #define LUA_SCRIPT_LINKAGE LUA_API #else diff --git a/code/components/citizen-scripting-lua/include/LuaScriptRuntime.h b/code/components/citizen-scripting-lua/include/LuaScriptRuntime.h index c4d62258cd..a0912b4955 100644 --- a/code/components/citizen-scripting-lua/include/LuaScriptRuntime.h +++ b/code/components/citizen-scripting-lua/include/LuaScriptRuntime.h @@ -18,8 +18,6 @@ #include -#include - #include #include @@ -65,10 +63,41 @@ enum class LuaProfilingMode : uint8_t Shutdown, }; +enum class LuaMetaFields : uint8_t +{ + PointerValueInt, + PointerValueFloat, + PointerValueVector, + ReturnResultAnyway, + ResultAsInteger, + ResultAsLong, + ResultAsFloat, + ResultAsString, + ResultAsVector, + ResultAsObject, + AwaitSentinel, + Max +}; + /// /// namespace fx { +struct PointerFieldEntry +{ + bool empty; + uintptr_t value; + PointerFieldEntry() + : empty(true), value(0) + { + } +}; + +struct PointerField +{ + PointerFieldEntry data[64]; +}; + #if LUA_VERSION_NUM >= 504 && defined(_WIN32) #define LUA_USE_RPMALLOC #endif @@ -187,7 +216,7 @@ class LuaScriptRuntime : public OMClass) +#include +#endif + #include #include @@ -33,131 +37,233 @@ extern LUA_INTERNAL_LINKAGE #endif } -using namespace fx::invoker; +// @TODO: Technically unsafe +extern uint8_t g_metaFields[]; +extern uint64_t g_tickTime; extern bool g_hadProfiler; -#if LUA_VERSION_NUM == 504 -/* -** -** Convert an acceptable index to a pointer to its respective value. -** Non-valid indices return the special nil value 'G(L)->nilvalue'. -*/ -static LUA_INLINE const TValue* __index2value(lua_State* L, int idx) +/// +/// +template +static int Lua_PushContextArgument(lua_State* L, int idx, fxLuaNativeContext& context, fxLuaResult& result); + +/// +/// Pushing function +/// +template +static LUA_INLINE void fxLuaNativeContext_PushArgument(fxLuaNativeContext& context, T value) { - const CallInfo* ci = L->ci; - if (idx > 0) + using TVal = std::decay_t; + const int na = context.numArguments; + + if (context.numArguments >= std::size(context.arguments)) { - const StkId o = ci->func + idx; - api_check(L, idx <= L->ci->top - (ci->func + 1), "unacceptable index"); - return (o >= L->top) ? &G(L)->nilvalue : s2v(o); + return; } - else /* negative index */ + + LUA_IF_CONSTEXPR(sizeof(TVal) < sizeof(uintptr_t)) { - api_check(L, idx != 0 && -idx <= L->top - (ci->func + 1), "invalid index"); - return s2v(L->top + idx); + LUA_IF_CONSTEXPR(std::is_integral_v) + { + LUA_IF_CONSTEXPR(std::is_signed_v) + { + *reinterpret_cast(&context.arguments[na]) = (uintptr_t)(uint32_t)value; + } + else + { + *reinterpret_cast(&context.arguments[na]) = (uintptr_t)value; + } + } + else + { + *reinterpret_cast(&context.arguments[na]) = *reinterpret_cast(&value); + } + } + else + { + *reinterpret_cast(&context.arguments[na]) = value; } + + context.numArguments = na + 1; } -#endif -#if LUA_VERSION_NUM == 504 -#define LUA_VALUE(L, I) __index2value((L), (I)) -#else -#define LUA_VALUE(L, I) lua_getvalue((L), (I)) -#endif +template +static LUA_INLINE int fxLuaNativeContext_PushPointer(lua_State* L, fxLuaNativeContext& context, fxLuaResult& result, LuaMetaFields metaField) +{ + if (result.numReturnValues >= std::size(result.retvals)) + { + lua_pushliteral(L, "too many return value arguments"); + return lua_error(L); + } -#ifndef IS_FXSERVER -#include + // push the offset and set the type + fxLuaNativeContext_PushArgument(context, &result.retvals[result.numReturnValues]); + result.rettypes[result.numReturnValues] = metaField; -#include -struct FastNativeHandler -{ - uint64_t hash; - rage::scrEngine::NativeHandler handler; -}; + // increment the counter + if (metaField == LuaMetaFields::PointerValueVector) + result.numReturnValues += 3; + else + result.numReturnValues += 1; -LUA_SCRIPT_LINKAGE int Lua_GetNativeHandler(lua_State* L) -{ - auto hash = lua_tointeger(L, 1); // TODO: Use luaL_checkinteger - auto handler = (!launch::IsSDK()) ? rage::scrEngine::GetNativeHandler(hash) : nullptr; + return 1; +} - auto nativeHandler = (FastNativeHandler*)lua_newuserdata(L, sizeof(FastNativeHandler)); - nativeHandler->hash = hash; - nativeHandler->handler = handler; +// table parsing implementation +template +static LUA_INLINE int fxLuaNativeContext_PushTable(lua_State* L, int idx, fxLuaNativeContext& context, fxLuaResult& result) +{ + const int t_idx = lua_absindex(L, idx); + luaL_checkstack(L, 2, "table arguments"); + lua_pushliteral(L, "__data"); - if (luaL_newmetatable(L, "FastNativeHandler")) + // get the type and decide what to do based on it + auto validType = [](int t) { - lua_pushcfunction(L, [](lua_State* L) -> int { - auto handlerRef = (FastNativeHandler*)luaL_checkudata(L, 1, "FastNativeHandler"); - lua_pushstring(L, va("native_%016llx", handlerRef->hash)); - return 1; - }); +#if LUA_VERSION_NUM == 504 + return t == LUA_TBOOLEAN || t == LUA_TNUMBER || t == LUA_TSTRING || t == LUA_TVECTOR; +#else + return t == LUA_TBOOLEAN || t == LUA_TNUMBER || t == LUA_TSTRING || t == LUA_TVECTOR2 || t == LUA_TVECTOR3 || t == LUA_TVECTOR4 || t == LUA_TQUAT; +#endif + }; - lua_setfield(L, -2, "__tostring"); + if (validType(lua_rawget(L, t_idx))) // Account for pushstring if idx < 0 + { + Lua_PushContextArgument(L, -1, context, result); + lua_pop(L, 1); } + else + { + lua_pop(L, 1); // [...] + if (luaL_getmetafield(L, idx, "__data") == LUA_TFUNCTION) // [..., metafield] + { + // The __data function can only allow one return value (no LUA_MULTRET) + // to avoid additional implicitly expanded types during native execution. + lua_pushvalue(L, t_idx); // [..., function, argument] + lua_call(L, 1, 1); // [..., value] + } - lua_setmetatable(L, -2); - + if (validType(lua_type(L, -1))) + { + Lua_PushContextArgument(L, -1, context, result); + lua_pop(L, 1); // [...] + } + else + { + lua_pop(L, 1); + lua_pushliteral(L, "Invalid Lua type in __data"); + return lua_error(L); + } + } return 1; } -static LONG ShouldHandleUnwind(PEXCEPTION_POINTERS ep, DWORD exceptionCode, uint64_t identifier); - -static uint64_t g_nativeIdentifier; -static void* exceptionAddress; - -static __declspec(noinline) LONG FilterFunc(PEXCEPTION_POINTERS exceptionInformation) +template +static LUA_INLINE void fxLuaNativeContext_PushUserdata(lua_State* L, fxLuaNativeContext& context, fxLuaResult& result, uint8_t* ptr) { - exceptionAddress = exceptionInformation->ExceptionRecord->ExceptionAddress; - - return ShouldHandleUnwind(exceptionInformation, exceptionInformation->ExceptionRecord->ExceptionCode, g_nativeIdentifier); -} + // if the pointer is a metafield + if (ptr >= g_metaFields && ptr < &g_metaFields[(int)LuaMetaFields::Max]) + { + LuaMetaFields metaField = static_cast(ptr - g_metaFields); -static LUA_INLINE void CallHandler(rage::scrEngine::NativeHandler handler, uint64_t nativeIdentifier, fxNativeContext& context) -{ - NativeContextRaw rageContext(context.arguments, context.numArguments); + // switch on the metafield + switch (metaField) + { + case LuaMetaFields::PointerValueInt: + case LuaMetaFields::PointerValueFloat: + case LuaMetaFields::PointerValueVector: + { + result.retvals[result.numReturnValues] = 0; - // call the original function - __try + if (metaField == LuaMetaFields::PointerValueVector) + { + result.retvals[result.numReturnValues + 1] = 0; + result.retvals[result.numReturnValues + 2] = 0; + } + + fxLuaNativeContext_PushPointer(L, context, result, metaField); + break; + } + case LuaMetaFields::ReturnResultAnyway: + result.returnResultAnyway = true; + break; + case LuaMetaFields::ResultAsInteger: + case LuaMetaFields::ResultAsLong: + case LuaMetaFields::ResultAsString: + case LuaMetaFields::ResultAsFloat: + case LuaMetaFields::ResultAsVector: + case LuaMetaFields::ResultAsObject: + result.returnResultAnyway = true; + result.returnValueCoercion = metaField; + break; + default: + break; + } + } + // or if the pointer is a runtime pointer field + else if (ptr >= reinterpret_cast(result.pointerFields) && ptr < (reinterpret_cast(result.pointerFields) + (sizeof(fx::PointerField) * 2))) { - g_nativeIdentifier = nativeIdentifier; - handler(&rageContext); + // guess the type based on the pointer field type + const intptr_t ptrField = ptr - reinterpret_cast(result.pointerFields); + const LuaMetaFields metaField = static_cast(ptrField / sizeof(fx::PointerField)); + + if (metaField == LuaMetaFields::PointerValueInt || metaField == LuaMetaFields::PointerValueFloat) + { + auto ptrFieldEntry = reinterpret_cast(ptr); + + result.retvals[result.numReturnValues] = ptrFieldEntry->value; + ptrFieldEntry->empty = true; - // append vector3 result components - rageContext.SetVectorResults(); + fxLuaNativeContext_PushPointer(L, context, result, metaField); + } } - __except (FilterFunc(GetExceptionInformation())) + else { - throw std::exception(va("Error executing native 0x%016llx at address %s.", nativeIdentifier, FormatModuleAddress(exceptionAddress))); + // @TODO: Throw an error? + fxLuaNativeContext_PushArgument(context, ptr); } } -#endif -struct LuaScriptNativeContext : ScriptNativeContext +#if LUA_VERSION_NUM == 504 +/* +** +** Convert an acceptable index to a pointer to its respective value. +** Non-valid indices return the special nil value 'G(L)->nilvalue'. +*/ +static LUA_INLINE const TValue* __index2value(lua_State* L, int idx) { - LuaScriptNativeContext(uint64_t hash, lua_State* L, fx::LuaScriptRuntime& runtime) - : ScriptNativeContext(hash, runtime.GetPointerFields()) - , L(L), runtime(runtime) + const CallInfo* ci = L->ci; + if (idx > 0) { + const StkId o = ci->func + idx; + api_check(L, idx <= L->ci->top - (ci->func + 1), "unacceptable index"); + return (o >= L->top) ? &G(L)->nilvalue : s2v(o); } - - [[noreturn]] void DoSetError(const char* msg) override + else /* negative index */ { - lua_pushstring(L, msg); - lua_error(L); + api_check(L, idx != 0 && -idx <= L->top - (ci->func + 1), "invalid index"); + return s2v(L->top + idx); } +} +#endif - void PushArgument(int idx); - void PushTableArgument(int idx); - - template - void ProcessResult(const T& value); - - lua_State* L; - fx::LuaScriptRuntime& runtime; -}; +#if LUA_VERSION_NUM == 504 +#define LUA_VALUE(L, I) __index2value((L), (I)) +#else +#define LUA_VALUE(L, I) lua_getvalue((L), (I)) +#endif -void LuaScriptNativeContext::PushArgument(int idx) +/// +/// Consider the possibly converting SHRSTR's to VLNGSTR's to avoid the handler +/// from invalidating internalized strings. +/// +/// +/// +/// +/// +template +static int Lua_PushContextArgument(lua_State* L, int idx, fxLuaNativeContext& context, fxLuaResult& result) { #if LUA_VERSION_NUM == 504 const TValue* value = LUA_VALUE(L, idx); @@ -165,30 +271,30 @@ void LuaScriptNativeContext::PushArgument(int idx) { case LUA_VNIL: case LUA_VFALSE: - Push(0); + fxLuaNativeContext_PushArgument(context, 0); break; case LUA_VTRUE: - Push(1); + fxLuaNativeContext_PushArgument(context, 1); break; case LUA_VNUMINT: - Push(ivalue(value)); + fxLuaNativeContext_PushArgument(context, ivalue(value)); break; case LUA_VNUMFLT: - Push(static_cast(fltvalue(value))); + fxLuaNativeContext_PushArgument(context, static_cast(fltvalue(value))); break; case LUA_VSHRSTR: #if defined(GRIT_POWER_BLOB) case LUA_VBLOBSTR: #endif case LUA_VLNGSTR: - Push(svalue(value), vslen(value)); + fxLuaNativeContext_PushArgument(context, svalue(value)); break; case LUA_VVECTOR2: { const glmVector& v = vvalue(value); - Push(static_cast(v.v4.x)); - Push(static_cast(v.v4.y)); + fxLuaNativeContext_PushArgument(context, static_cast(v.v4.x)); + fxLuaNativeContext_PushArgument(context, static_cast(v.v4.y)); break; } @@ -196,9 +302,9 @@ void LuaScriptNativeContext::PushArgument(int idx) { const glmVector& v = vvalue(value); - Push(static_cast(v.v4.x)); - Push(static_cast(v.v4.y)); - Push(static_cast(v.v4.z)); + fxLuaNativeContext_PushArgument(context, static_cast(v.v4.x)); + fxLuaNativeContext_PushArgument(context, static_cast(v.v4.y)); + fxLuaNativeContext_PushArgument(context, static_cast(v.v4.z)); break; } @@ -206,10 +312,10 @@ void LuaScriptNativeContext::PushArgument(int idx) { const glmVector& v = vvalue(value); - Push(static_cast(v.v4.x)); - Push(static_cast(v.v4.y)); - Push(static_cast(v.v4.z)); - Push(static_cast(v.v4.w)); + fxLuaNativeContext_PushArgument(context, static_cast(v.v4.x)); + fxLuaNativeContext_PushArgument(context, static_cast(v.v4.y)); + fxLuaNativeContext_PushArgument(context, static_cast(v.v4.z)); + fxLuaNativeContext_PushArgument(context, static_cast(v.v4.w)); break; } @@ -217,27 +323,26 @@ void LuaScriptNativeContext::PushArgument(int idx) { const glmVector& v = vvalue(value); - Push(static_cast(v.q.x)); - Push(static_cast(v.q.y)); - Push(static_cast(v.q.z)); - Push(static_cast(v.q.w)); + fxLuaNativeContext_PushArgument(context, static_cast(v.q.x)); + fxLuaNativeContext_PushArgument(context, static_cast(v.q.y)); + fxLuaNativeContext_PushArgument(context, static_cast(v.q.z)); + fxLuaNativeContext_PushArgument(context, static_cast(v.q.w)); break; } case LUA_VTABLE: // table (high-level class with __data field) { - PushTableArgument(idx); + fxLuaNativeContext_PushTable(L, idx, context, result); break; } case LUA_VLIGHTUSERDATA: { - uint8_t* ptr = reinterpret_cast(pvalue(value)); - PushMetaPointer(ptr); + fxLuaNativeContext_PushUserdata(L, context, result, reinterpret_cast(pvalue(value))); break; } default: { - SetError("invalid lua type: %s", lua_typename(L, ttype(value))); + return luaL_error(L, "Invalid Lua type: %s", lua_typename(L, ttype(value))); } } #else @@ -251,7 +356,7 @@ void LuaScriptNativeContext::PushArgument(int idx) // nil case LUA_TNIL: { - Push(0); + fxLuaNativeContext_PushArgument(context, 0); break; } // integer/float @@ -259,173 +364,265 @@ void LuaScriptNativeContext::PushArgument(int idx) { if (lua_valueisinteger(L, value)) { - Push(lua_valuetointeger(L, value)); + fxLuaNativeContext_PushArgument(context, lua_valuetointeger(L, value)); } else if (lua_valueisfloat(L, value)) { - Push(static_cast(lua_valuetonumber(L, value))); + fxLuaNativeContext_PushArgument(context, static_cast(lua_valuetonumber(L, value))); } break; } // boolean case LUA_TBOOLEAN: { - Push(lua_valuetoboolean(L, value)); + fxLuaNativeContext_PushArgument(context, lua_valuetoboolean(L, value)); break; } // table (high-level class with __data field) case LUA_TTABLE: { - PushTableArgument(idx); + fxLuaNativeContext_PushTable(L, idx, context, result); break; } // string case LUA_TSTRING: { - Push(lua_valuetostring(L, value), vslen(value)); + fxLuaNativeContext_PushArgument(context, lua_valuetostring(L, value)); break; } // vectors case LUA_TVECTOR2: { auto f4 = lua_valuetofloat4(L, value); - Push(f4.x); - Push(f4.y); + + fxLuaNativeContext_PushArgument(context, f4.x); + fxLuaNativeContext_PushArgument(context, f4.y); + break; } case LUA_TVECTOR3: { auto f4 = lua_valuetofloat4(L, value); - Push(f4.x); - Push(f4.y); - Push(f4.z); + + fxLuaNativeContext_PushArgument(context, f4.x); + fxLuaNativeContext_PushArgument(context, f4.y); + fxLuaNativeContext_PushArgument(context, f4.z); + break; } case LUA_TVECTOR4: case LUA_TQUAT: { auto f4 = lua_valuetofloat4(L, value); - Push(f4.x); - Push(f4.y); - Push(f4.z); - Push(f4.w); + + fxLuaNativeContext_PushArgument(context, f4.x); + fxLuaNativeContext_PushArgument(context, f4.y); + fxLuaNativeContext_PushArgument(context, f4.z); + fxLuaNativeContext_PushArgument(context, f4.w); + break; } // metafield case LUA_TLIGHTUSERDATA: { uint8_t* ptr = reinterpret_cast(lua_valuetolightuserdata(L, value)); - PushMetaPointer(ptr); + fxLuaNativeContext_PushUserdata(L, context, result, ptr); break; } default: { - SetError("invalud lua type: %s", lua_typename(L, type)); + return luaL_error(L, "Invalid Lua type: %s", lua_typename(L, type)); } } #endif + return 1; } -// table parsing implementation -void LuaScriptNativeContext::PushTableArgument(int idx) +#ifndef IS_FXSERVER +#include + +#include +struct FastNativeHandler { - const int t_idx = lua_absindex(L, idx); - luaL_checkstack(L, 2, "table arguments"); - lua_pushliteral(L, "__data"); + uint64_t hash; + rage::scrEngine::NativeHandler handler; +}; - // get the type and decide what to do based on it - auto validType = [](int t) - { -#if LUA_VERSION_NUM == 504 - return t == LUA_TBOOLEAN || t == LUA_TNUMBER || t == LUA_TSTRING || t == LUA_TVECTOR; -#else - return t == LUA_TBOOLEAN || t == LUA_TNUMBER || t == LUA_TSTRING || t == LUA_TVECTOR2 || t == LUA_TVECTOR3 || t == LUA_TVECTOR4 || t == LUA_TQUAT; -#endif - }; +LUA_SCRIPT_LINKAGE int Lua_GetNativeHandler(lua_State* L) +{ + auto hash = lua_tointeger(L, 1); + auto handler = (!launch::IsSDK()) ? rage::scrEngine::GetNativeHandler(hash) : nullptr; - if (validType(lua_rawget(L, t_idx))) // Account for pushstring if idx < 0 + auto nativeHandler = (FastNativeHandler*)lua_newuserdata(L, sizeof(FastNativeHandler)); + nativeHandler->hash = hash; + nativeHandler->handler = handler; + + return 1; +} + +static LONG ShouldHandleUnwind(PEXCEPTION_POINTERS ep, DWORD exceptionCode, uint64_t identifier); + +static uint64_t g_nativeIdentifier; +static void* exceptionAddress; + +static __declspec(noinline) LONG FilterFunc(PEXCEPTION_POINTERS exceptionInformation) +{ + exceptionAddress = exceptionInformation->ExceptionRecord->ExceptionAddress; + + return ShouldHandleUnwind(exceptionInformation, exceptionInformation->ExceptionRecord->ExceptionCode, g_nativeIdentifier); +} + +static LUA_INLINE void CallHandler(void* handler, uint64_t nativeIdentifier, rage::scrNativeCallContext& rageContext) +{ + // call the original function + __try { - PushArgument(-1); - lua_pop(L, 1); + g_nativeIdentifier = nativeIdentifier; + + auto rageHandler = (rage::scrEngine::NativeHandler)handler; + rageHandler(&rageContext); } - else + __except (FilterFunc(GetExceptionInformation())) { - lua_pop(L, 1); // [...] - if (luaL_getmetafield(L, idx, "__data") == LUA_TFUNCTION) // [..., metafield] - { - // The __data function can only allow one return value (no LUA_MULTRET) - // to avoid additional implicitly expanded types during native execution. - lua_pushvalue(L, t_idx); // [..., function, argument] - lua_call(L, 1, 1); // [..., value] - } - - if (validType(lua_type(L, -1))) - { - PushArgument(-1); - lua_pop(L, 1); // [...] - } - else - { - lua_pop(L, 1); - SetError("invalid lua type in __data"); - } + throw std::exception(va("Error executing native 0x%016llx at address %s.", nativeIdentifier, FormatModuleAddress(exceptionAddress))); } } -template -void LuaScriptNativeContext::ProcessResult(const T& value) +static void __declspec(safebuffers) CallHandlerSdk(void* handler, uint64_t nativeIdentifier, rage::scrNativeCallContext& rageContext) { - if constexpr (std::is_same_v) - { - lua_pushboolean(L, value); - } - else if constexpr (std::is_same_v) + fxNativeContext context; + memcpy(context.arguments, rageContext.GetArgumentBuffer(), sizeof(void*) * rageContext.GetArgumentCount()); + + auto& luaRuntime = fx::LuaScriptRuntime::GetCurrent(); + fx::OMPtr scriptHost = luaRuntime->GetScriptHost(); + + HRESULT hr = scriptHost->InvokeNative(context); + + if (!FX_SUCCEEDED(hr)) { - lua_pushinteger(L, value); + char* error = "Unknown"; + scriptHost->GetLastErrorText(&error); + + throw std::runtime_error(va("%s", error)); } - else if constexpr (std::is_same_v) + + memcpy(rageContext.GetArgumentBuffer(), context.arguments, sizeof(void*) * 3); +} + +static void __declspec(safebuffers) CallHandlerUniversal(void* handler, uint64_t nativeIdentifier, rage::scrNativeCallContext& rageContext); +static decltype(&CallHandlerUniversal) g_callHandler = &CallHandlerUniversal; + +static void __declspec(safebuffers) CallHandlerUniversal(void* handler, uint64_t nativeIdentifier, rage::scrNativeCallContext& rageContext) +{ + if (launch::IsSDK()) { - lua_pushinteger(L, value); + g_callHandler = &CallHandlerSdk; } - else if constexpr (std::is_same_v) + else { - lua_pushnumber(L, value); + g_callHandler = &CallHandler; } - else if constexpr (std::is_same_v) + + return g_callHandler(handler, nativeIdentifier, rageContext); +} +#endif + +// +// Sanitization for string result types +// Loops through all values given by the ScRT and denies any that equals the result value which isn't of the string type +// Uhm what happened to all these context types? o.O +void NativeStringResultSanitization(lua_State* state, int inputSize, uint64_t hash, uintptr_t* arguments, int numArguments, uintptr_t* initialArguments) +{ + const auto resultValue = arguments[0]; + + // Step 1: quick compare all values until we found a hit + // By not switching between all the buffers (incl. input arguments) we'll not introduce unnecessary cache misses. + for (int a = 0; a < numArguments; ++a) { + if (initialArguments[a] == resultValue) + { + // Step 2: loop our input list for as many times as `a` was increased + for (int i = 2; i < inputSize; ++i) + { + // `a` can be reused by simply decrementing it, we'll go negative when we hit our goal as we decrement before checking (e.g.: `0 - 1 = -1` or `0 - 4 = -4`) + const auto luaType = lua_type(state, i); + switch (luaType) + { #if LUA_VERSION_NUM == 504 - glm_pushvec3(L, glm::vec<3, glm_Float>(value.x, value.y, value.z)); + case LUA_VVECTOR2: #else - lua_pushvector3(L, value.x, value.y, value.z); + case LUA_TVECTOR2: #endif + a -= 2; + break; +#if LUA_VERSION_NUM == 504 + case LUA_VVECTOR3: +#else + case LUA_TVECTOR3: +#endif + a -= 3; + break; +#if LUA_VERSION_NUM == 504 + case LUA_VQUAT: + case LUA_VVECTOR4: +#else + case LUA_TQUAT: + case LUA_TVECTOR4: +#endif + a -= 4; + break; + default: + a--; + break; + } + + // string type is allowed + if (a < 0) + { + if (luaType != LUA_TSTRING) + { + fx::ScriptTrace("Warning: Sanitized coerced string result for native %016x.\n", hash); + arguments[0] = 0; + } + + return; // we found our arg, no more to check + } + } + + return; // found our value, no more to check + } } - else if constexpr (std::is_same_v) - { - lua_pushstring(L, value); - } - else if constexpr (std::is_same_v) - { - lua_pushlstring(L, value.str, value.len); - } - else if constexpr (std::is_same_v) +} + +template +static int __Lua_InvokeNative(lua_State* L) +{ + std::conditional_t hash; + uint64_t origHash; + + LUA_IF_CONSTEXPR(!IsPtr) { - runtime.ResultAsObject(L, std::string_view{ value.data, value.length }); + // get the hash + origHash = hash = lua_tointeger(L, 1); } +#ifndef IS_FXSERVER else { - static_assert(always_false_v, "Invalid return type"); - } -} + auto handlerRef = (FastNativeHandler*)lua_touserdata(L, 1); + if (!handlerRef) + { + luaL_error(L, "not a userdata"); + return 0; + } -static int __Lua_InvokeNative(lua_State* L, uint64_t hash -#ifndef IS_FXSERVER - , rage::scrEngine::NativeHandler handler = nullptr + hash = handlerRef->handler; + origHash = handlerRef->hash; + } #endif -) -{ + #ifdef GTA_FIVE // hacky super fast path for 323 GET_HASH_KEY in GTA - if (hash == 0xD24D37CC275948CC) + if (origHash == 0xD24D37CC275948CC) { // if NULL or an integer, return 0 if (lua_isnil(L, 2) || lua_type(L, 2) == LUA_TNUMBER) @@ -442,82 +639,281 @@ static int __Lua_InvokeNative(lua_State* L, uint64_t hash } #endif - // Note: SetError/lua_error doesn't return, so don't need to check return values - // get required entries auto& luaRuntime = fx::LuaScriptRuntime::GetCurrent(); + fx::OMPtr scriptHost = luaRuntime->GetScriptHost(); - LuaScriptNativeContext context(hash, L, *luaRuntime.GetRef()); + // variables to hold state + fxLuaNativeContext context; + fxLuaResult result(luaRuntime->GetPointerFields()); - for (int arg = 2, top = lua_gettop(L); arg <= top; arg++) + LUA_IF_CONSTEXPR(!IsPtr) { - context.PushArgument(arg); + context.nativeIdentifier = (uint64_t)hash; } - context.PreInvoke(); + // get argument count for the loop + const int numArgs = lua_gettop(L); + for (int arg = 2; arg <= numArgs; arg++) + { + if (!Lua_PushContextArgument(L, arg, context, result)) + { + return luaL_error(L, "Unexpected context result"); + } + } + +#if __has_include() + fx::scripting::ResultType resultTypeIntent = fx::scripting::ResultType::None; + + if (!result.returnResultAnyway) + { + resultTypeIntent = fx::scripting::ResultType::Void; + } + else if (result.returnValueCoercion == LuaMetaFields::ResultAsString) + { + resultTypeIntent = fx::scripting::ResultType::String; + } + else if (result.returnValueCoercion == LuaMetaFields::ResultAsInteger || + result.returnValueCoercion == LuaMetaFields::ResultAsLong || + result.returnValueCoercion == LuaMetaFields::ResultAsFloat || + result.returnValueCoercion == LuaMetaFields::Max /* bool */) + { + resultTypeIntent = fx::scripting::ResultType::Scalar; + } + else if (result.returnValueCoercion == LuaMetaFields::ResultAsVector) + { + resultTypeIntent = fx::scripting::ResultType::Vector; + } +#endif // invoke the native on the script host #ifndef IS_FXSERVER - if (handler) + // preemptive safety check for the input argument to *not* show up in the output + bool needsResultCheck = (result.numReturnValues == 0 || result.returnResultAnyway); + auto a1type = lua_type(L, 2); + bool hadComplexType = (a1type != LUA_TNUMBER && a1type != LUA_TNIL && a1type != LUA_TBOOLEAN); + + decltype(context.arguments) initialArguments; + memcpy(initialArguments, context.arguments, sizeof(context.arguments)); + + LUA_IF_CONSTEXPR(IsPtr) { - NativeContextRaw rageContext(context.arguments, context.numArguments); + // zero out three following arguments + if (context.numArguments <= 29) + { + context.arguments[context.numArguments + 0] = 0; + context.arguments[context.numArguments + 1] = 0; + context.arguments[context.numArguments + 2] = 0; + } + + auto handler = hash; + context.SetArgumentCount(context.numArguments); try { - CallHandler(handler, hash, context); + if (handler) + { + g_callHandler(handler, origHash, context); + } + + // append vector3 result components + context.SetVectorResults(); } catch (std::exception& e) { fx::ScriptTrace("%s: execution failed: %s\n", __func__, e.what()); - - context.SetError("Execution of native %016llx in script host failed: %s", hash, e.what()); + lua_pushstring(L, va("Execution of native %016llx in script host failed: %s", origHash, e.what())); + lua_error(L); } } else #endif { - fx::OMPtr scriptHost = luaRuntime->GetScriptHost(); + result_t hr = scriptHost->InvokeNative(context); - if (!FX_SUCCEEDED(scriptHost->InvokeNative(context))) + if (!FX_SUCCEEDED(hr)) { char* error = "Unknown"; scriptHost->GetLastErrorText(&error); - context.SetError("Execution of native %016llx in script host failed: %s", hash, error); + lua_pushstring(L, va("Execution of native %016llx in script host failed: %s", origHash, error)); + lua_error(L); } } - context.PostInvoke(); +#ifndef IS_FXSERVER + // clean up the result + if ((needsResultCheck || hadComplexType) && context.numArguments > 0) + { + // if this is scrstring but nothing changed (very weird!), fatally fail + if (static_cast(context.arguments[2]) == SCRSTRING_MAGIC_BINARY && initialArguments[2] == context.arguments[2]) + { + FatalError("Invalid native call in resource '%s'. Please see https://aka.cfx.re/scrstring-mitigation for more information.", luaRuntime->GetResourceName()); + } - int numResults = lua_gettop(L); + // If return coercion is String type + // Don't allow deref'ing arbitrary pointers passed in any of the arguments. + if (result.returnValueCoercion == LuaMetaFields::ResultAsString && context.arguments[0]) + { + NativeStringResultSanitization(L, numArgs, origHash, context.arguments, context.numArguments, initialArguments); + } + // if the first value (usually result) is the same as the initial argument, clear the result (usually, result was no-op) + // (if vector results, these aren't directly unsafe, and may get incorrectly seen as complex) + else if (context.arguments[0] == initialArguments[0] && result.returnValueCoercion != LuaMetaFields::ResultAsVector) + { + // complex type in first result means we have to clear that result + if (hadComplexType) + { + context.arguments[0] = 0; + } - context.ProcessResults([&](auto&& value) + // if any result is requested and there was *no* change, zero out + context.arguments[1] = 0; + context.arguments[2] = 0; + context.arguments[3] = 0; + } + } +#endif + +#if __has_include() + if (resultTypeIntent != fx::scripting::ResultType::None) { - context.ProcessResult(value); + fx::scripting::PointerArgumentHints::CleanNativeResult(origHash, resultTypeIntent, context.arguments); + } +#endif - return true; - }); + // number of Lua results + int numResults = 0; + + // if no other result was requested, or we need to return the result anyway, push the result + if (result.numReturnValues == 0 || result.returnResultAnyway) + { + // increment the result count + numResults++; + + // handle the type coercion + switch (result.returnValueCoercion) + { + case LuaMetaFields::ResultAsString: + { + auto strString = reinterpret_cast(&context.arguments[0]); + + if (strString->magic == SCRSTRING_MAGIC_BINARY) + { + lua_pushlstring(L, strString->str, strString->len); + } + else if (strString->str) + { + lua_pushstring(L, strString->str); + } + else + { + lua_pushnil(L); + } + + break; + } + case LuaMetaFields::ResultAsFloat: + { + lua_pushnumber(L, *reinterpret_cast(&context.arguments[0])); + break; + } + case LuaMetaFields::ResultAsVector: + { + const scrVector& vector = *reinterpret_cast(&context.arguments[0]); +#if LUA_VERSION_NUM == 504 + glm_pushvec3(L, glm::vec<3, glm_Float>(vector.x, vector.y, vector.z)); +#else + lua_pushvector3(L, vector.x, vector.y, vector.z); +#endif + break; + } + case LuaMetaFields::ResultAsObject: + { + scrObject object = *reinterpret_cast(&context.arguments[0]); + luaRuntime->ResultAsObject(L, std::string_view{ object.data, object.length }); + break; + } + case LuaMetaFields::ResultAsInteger: + { + lua_pushinteger(L, *reinterpret_cast(&context.arguments[0])); + break; + } + case LuaMetaFields::ResultAsLong: + { + lua_pushinteger(L, *reinterpret_cast(&context.arguments[0])); + break; + } + default: + { + const int32_t integer = *reinterpret_cast(&context.arguments[0]); + + if ((integer & 0xFFFFFFFF) == 0) + { + lua_pushboolean(L, false); + } + else + { + lua_pushinteger(L, integer); + } + break; + } + } + } - numResults = lua_gettop(L) - numResults; + // loop over the return value pointers + { + int i = 0; + while (i < result.numReturnValues) + { + switch (result.rettypes[i]) + { + case LuaMetaFields::PointerValueInt: + { + lua_pushinteger(L, *reinterpret_cast(&result.retvals[i])); + i++; + break; + } + case LuaMetaFields::PointerValueFloat: + { + lua_pushnumber(L, *reinterpret_cast(&result.retvals[i])); + i++; + break; + } + case LuaMetaFields::PointerValueVector: + { + const scrVector& vector = *reinterpret_cast(&result.retvals[i]); +#if LUA_VERSION_NUM == 504 + glm_pushvec3(L, glm::vec<3, glm_Float>(vector.x, vector.y, vector.z)); +#else + lua_pushvector3(L, vector.x, vector.y, vector.z); +#endif + + i += 3; + break; + } + default: + break; + } + + numResults++; + } + } + + // and return with the 'desired' amount of results return numResults; } LUA_SCRIPT_LINKAGE int Lua_InvokeNative(lua_State* L) { - uint64_t hash = lua_tointeger(L, 1); // TODO: Use luaL_checkinteger - - return __Lua_InvokeNative(L, hash); + return __Lua_InvokeNative(L); } -#ifndef IS_FXSERVER LUA_SCRIPT_LINKAGE int Lua_InvokeNative2(lua_State* L) { - auto handlerRef = (FastNativeHandler*)luaL_checkudata(L, 1, "FastNativeHandler"); - - return __Lua_InvokeNative(L, handlerRef->hash, handlerRef->handler); + return __Lua_InvokeNative(L); } -#endif #pragma region Lua_LoadNative @@ -625,10 +1021,6 @@ LUA_SCRIPT_LINKAGE int Lua_LoadNative(lua_State* L) using Lua_NativeMap = std::map>; -using scrVectorLua = ScrVector; -using scrObject = ScrObject; -using scrString = ScrString; - struct LuaArgumentParser { // Parsing function arguments. @@ -896,7 +1288,6 @@ LUA_INLINE void LuaArgumentParser::PushObject(lua_State* L, co if (!lua_asserttop(L, count)) \ return 0; -// TODO: Remove this! struct LuaNativeWrapper { LUA_INLINE LuaNativeWrapper(uint64_t) @@ -951,7 +1342,7 @@ struct LuaNativeContext : public fxNativeContext *reinterpret_cast(&arguments[numArguments]) = val; - LUA_IF_CONSTEXPR(sizeof(TVal) == sizeof(scrVectorLua)) + LUA_IF_CONSTEXPR(sizeof(TVal) == sizeof(scrVector)) { numArguments += 3; } @@ -1074,7 +1465,7 @@ struct LuaNativeContext *reinterpret_cast(&arguments[numArguments]) = val; - LUA_IF_CONSTEXPR(sizeof(TVal) == sizeof(scrVectorLua)) + LUA_IF_CONSTEXPR(sizeof(TVal) == sizeof(scrVector)) { numArguments += 3; } @@ -1118,6 +1509,38 @@ static InitFunction initFunction([]() { self->OnTick.Connect([self]() { + constexpr uint64_t GET_GAME_TIMER = +#ifdef IS_FXSERVER + 0xa4ea0691 +#elif defined(GTA_FIVE) + 0x9CD27B0045628463 +#elif defined(IS_RDR3) + 0x4F67E8ECA7D3F667 +#elif defined(GTA_NY) + 0x022B2DA9 +#else +#error Undefined GET_GAME_TIMER +#endif + ; + +#ifdef IS_FXSERVER + if (fx::LuaScriptRuntime::GetLastHost()) +#endif + { + static LuaNativeWrapper nW(GET_GAME_TIMER); + LuaNativeContext nCtx(&nW, 0); +#ifdef GTA_NY + nCtx.Push(&g_tickTime); +#endif + nCtx.Invoke(nullptr, GET_GAME_TIMER); + +#ifdef IS_FXSERVER + g_tickTime = nCtx.GetResult(); +#elif !defined(GTA_NY) + g_tickTime = nCtx.GetResult(); +#endif + } + g_hadProfiler = self->GetComponent()->IsRecording(); }, INT32_MIN); diff --git a/code/components/citizen-scripting-lua/src/LuaScriptRuntime.cpp b/code/components/citizen-scripting-lua/src/LuaScriptRuntime.cpp index 6d57ab7c30..73c41391d6 100644 --- a/code/components/citizen-scripting-lua/src/LuaScriptRuntime.cpp +++ b/code/components/citizen-scripting-lua/src/LuaScriptRuntime.cpp @@ -9,7 +9,6 @@ #include "fxScripting.h" #include -#include #include "LuaScriptRuntime.h" #include "LuaScriptNatives.h" @@ -40,8 +39,6 @@ extern LUA_INTERNAL_LINKAGE #include } -using namespace fx::invoker; - #if defined(GTA_FIVE) static constexpr std::pair g_scriptVersionPairs[] = { { "natives_21e43a33.lua", guid_t{ 0 } }, @@ -78,7 +75,9 @@ static constexpr std::pair g_scriptVersionPairs[] } \ lua_pop((L), 1) -static uint8_t g_awaitSentinel; +/// +/// +uint8_t g_metaFields[(size_t)LuaMetaFields::Max]; /// /// @@ -88,6 +87,7 @@ static fx::OMPtr g_currentLuaRuntime; /// static IScriptHost* g_lastScriptHost; +uint64_t g_tickTime; bool g_hadProfiler; #if defined(LUA_USE_RPMALLOC) @@ -688,17 +688,10 @@ static int Lua_SubmitBoundaryEnd(lua_State* L) return 0; } -template +template static int Lua_GetMetaField(lua_State* L) { - lua_pushlightuserdata(L, &ScriptNativeContext::s_metaFields[(int)metaField]); - - return 1; -} - -static int Lua_GetAwaitSentinel(lua_State* L) -{ - lua_pushlightuserdata(L, &g_awaitSentinel); + lua_pushlightuserdata(L, &g_metaFields[(int)metaField]); return 1; } @@ -738,10 +731,10 @@ static int Lua_ResultAsObject(lua_State* L) lua_remove(L, eh); }); - return Lua_GetMetaField(L); + return Lua_GetMetaField(L); } -template +template static int Lua_GetPointerField(lua_State* L) { auto& runtime = LuaScriptRuntime::GetCurrent(); @@ -750,7 +743,7 @@ static int Lua_GetPointerField(lua_State* L) auto pointerFieldStart = &pointerFields[(int)MetaField]; static uintptr_t dummyOut; - fx::invoker::PointerFieldEntry* pointerField = nullptr; + PointerFieldEntry* pointerField = nullptr; for (int i = 0; i < _countof(pointerFieldStart->data); i++) { @@ -765,13 +758,13 @@ static int Lua_GetPointerField(lua_State* L) { pointerField->value = 0; } - else if (MetaField == MetaField::PointerValueFloat) + else if (MetaField == LuaMetaFields::PointerValueFloat) { float value = static_cast(luaL_checknumber(L, 1)); pointerField->value = *reinterpret_cast(&value); } - else if (MetaField == MetaField::PointerValueInt) + else if (MetaField == LuaMetaFields::PointerValueInt) { intptr_t value = luaL_checkinteger(L, 1); @@ -962,7 +955,7 @@ bool LuaScriptRuntime::RunBookmark(uint64_t bookmark) auto userData = lua_touserdata(thread, -1); lua_pop(thread, 1); - if (userData == &g_awaitSentinel) + if (userData == &g_metaFields[(int)LuaMetaFields::AwaitSentinel]) { // resume again with a reattach callback lua_pushlightuserdata(thread, this); @@ -1197,20 +1190,20 @@ static const struct luaL_Reg g_citizenLib[] = { { "SubmitBoundaryEnd", Lua_SubmitBoundaryEnd }, { "SetStackTraceRoutine", Lua_SetStackTraceRoutine }, // metafields - { "PointerValueIntInitialized", Lua_GetPointerField }, - { "PointerValueFloatInitialized", Lua_GetPointerField }, - { "PointerValueInt", Lua_GetMetaField }, - { "PointerValueFloat", Lua_GetMetaField }, - { "PointerValueVector", Lua_GetMetaField }, - { "ReturnResultAnyway", Lua_GetMetaField }, - { "ResultAsInteger", Lua_GetMetaField }, - { "ResultAsLong", Lua_GetMetaField }, - { "ResultAsFloat", Lua_GetMetaField }, - { "ResultAsString", Lua_GetMetaField }, - { "ResultAsVector", Lua_GetMetaField }, + { "PointerValueIntInitialized", Lua_GetPointerField }, + { "PointerValueFloatInitialized", Lua_GetPointerField }, + { "PointerValueInt", Lua_GetMetaField }, + { "PointerValueFloat", Lua_GetMetaField }, + { "PointerValueVector", Lua_GetMetaField }, + { "ReturnResultAnyway", Lua_GetMetaField }, + { "ResultAsInteger", Lua_GetMetaField }, + { "ResultAsLong", Lua_GetMetaField }, + { "ResultAsFloat", Lua_GetMetaField }, + { "ResultAsString", Lua_GetMetaField }, + { "ResultAsVector", Lua_GetMetaField }, { "ResultAsObject", Lua_Noop }, // for compatibility { "ResultAsObject2", Lua_ResultAsObject }, - { "AwaitSentinel", Lua_GetAwaitSentinel }, + { "AwaitSentinel", Lua_GetMetaField }, { nullptr, nullptr } }; } diff --git a/code/components/citizen-scripting-v8/src/V8ScriptRuntime.cpp b/code/components/citizen-scripting-v8/src/V8ScriptRuntime.cpp index 2235f63d1a..d3012f95dd 100644 --- a/code/components/citizen-scripting-v8/src/V8ScriptRuntime.cpp +++ b/code/components/citizen-scripting-v8/src/V8ScriptRuntime.cpp @@ -9,6 +9,10 @@ #include "fxScripting.h" #include +#if __has_include() +#include +#endif + #include #include @@ -19,12 +23,9 @@ #include #include "fxScriptBuffer.h" -#include #include -using namespace fx::invoker; - #ifndef IS_FXSERVER #include #include @@ -157,6 +158,22 @@ static Platform* GetV8Platform(); static node::IsolateData* GetNodeIsolate(); #endif +struct PointerFieldEntry +{ + bool empty; + uintptr_t value; + + PointerFieldEntry() + { + empty = true; + } +}; + +struct PointerField +{ + PointerFieldEntry data[64]; +}; + // this is *technically* per-isolate, but we only register the callback for our host isolate static std::atomic g_isV8InGc; @@ -208,7 +225,7 @@ class V8ScriptRuntime : public OMClass m_stringValues[50]; @@ -303,7 +320,7 @@ class V8ScriptRuntime : public OMClass& value, size_t* length); + const char* AssignStringValue(const Local& value); NS_DECL_ISCRIPTRUNTIME; @@ -544,6 +561,23 @@ void ScriptTrace(const char* string, const TArgs& ... args) ScriptTraceV(string, fmt::make_printf_args(args...)); } +enum class V8MetaFields +{ + PointerValueInt, + PointerValueFloat, + PointerValueVector, + ReturnResultAnyway, + ResultAsInteger, + ResultAsLong, + ResultAsFloat, + ResultAsString, + ResultAsVector, + ResultAsObject, + Max +}; + +static uint8_t g_metaFields[(int)V8MetaFields::Max]; + static void V8_SetTickFunction(const v8::FunctionCallbackInfo& args) { V8ScriptRuntime* runtime = GetScriptRuntimeFromArgs(args); @@ -984,12 +1018,10 @@ static void V8_GetTickCount(const v8::FunctionCallbackInfo& args) args.GetReturnValue().Set((double)msec().count()); } -const char* V8ScriptRuntime::AssignStringValue(const Local& value, size_t* length) +const char* V8ScriptRuntime::AssignStringValue(const Local& value) { auto stringValue = std::make_unique(GetV8Isolate(), value); - const char* str = **(stringValue.get()); - *length = stringValue->length(); // take ownership m_stringValues[m_curStringValue] = std::move(stringValue); @@ -1001,381 +1033,673 @@ const char* V8ScriptRuntime::AssignStringValue(const Local& value, size_t return str; } -struct V8ScriptNativeContext : ScriptNativeContext +struct StringHashGetter { - V8ScriptNativeContext(uint64_t hash, V8ScriptRuntime* runtime, v8::Isolate* isolate) - : ScriptNativeContext(hash, runtime->GetPointerFields()) - , isolateScope(GetV8Isolate()), runtime(runtime), isolate(isolate), cxt(runtime->GetContext()) - { - } + static const int BaseArgs = 1; - void DoSetError(const char* msg) override + uint64_t operator()(const v8::FunctionCallbackInfo& args) { - isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, msg).ToLocalChecked())); + String::Utf8Value hashString(GetV8Isolate(), args[0]); + return strtoull(*hashString, nullptr, 16); } +}; - bool PushArgument(v8::Local arg); +struct IntHashGetter +{ + static const int BaseArgs = 2; - template - v8::Local ProcessResult(const T& value); + uint64_t operator()(const v8::FunctionCallbackInfo& args) + { + auto scrt = V8ScriptRuntime::GetCurrent(); - v8::Isolate::Scope isolateScope; - V8ScriptRuntime* runtime; - v8::Isolate* isolate; - v8::Local cxt; + return (args[1]->Uint32Value(scrt->GetContext()).ToChecked() | (((uint64_t)args[0]->Uint32Value(scrt->GetContext()).ToChecked()) << 32)); + } }; -bool V8ScriptNativeContext::PushArgument(v8::Local arg) +// +// Sanitization for string result types +// Loops through all values given by the ScRT and deny any that equals the result value which isn't of the string type +template +void NativeStringResultSanitization(const v8::FunctionCallbackInfo& inputArguments, fxNativeContext& context, decltype(context.arguments)& initialArguments) { - if (arg->IsNumber()) - { - double value = arg->NumberValue(cxt).ToChecked(); - int64_t intValue = static_cast(value); + const auto resultValue = context.arguments[0]; - if (intValue == value) - { - return Push(intValue); - } - else + // Step 1: quick compare all values until we found a hit + // By not switching between all the buffers (incl. input arguments) we'll not introduce unnecessary cache misses. + for (int a = 0; a < context.numArguments; ++a) + { + if (initialArguments[a] == resultValue) { - return Push(static_cast(value)); + // Step 2: loop our input list for as many times as `a` was increased + const int inputSize = inputArguments.Length(); + for (int i = HashGetter::BaseArgs; i < inputSize; ++i) + { + const auto& v8Input = inputArguments[i]; + + // `a` can be reused by simply decrementing it, we'll go negative when we hit our goal as we decrement before checking (e.g.: `0 - 1 = -1` or `0 - 4 = -4`) + a -= v8Input->IsArray() ? std::min(Local::Cast(v8Input)->Length(), 4u) : 1u; + + // string type is allowed + if (a < 0) + { + if (!v8Input->IsString()) + { + ScriptTrace("Warning: Sanitized coerced string result for native %016x.\n", context.nativeIdentifier); + context.arguments[0] = 0; + } + + return; // we found our arg, no more to check + } + } + + return; // found our value, no more to check } } - else if (arg->IsBoolean() || arg->IsBooleanObject()) - { - return Push(arg->BooleanValue(isolate)); - } - else if (arg->IsString()) +} + +template +static void V8_InvokeNative(const v8::FunctionCallbackInfo& args) +{ + // get required entries + V8ScriptRuntime* runtime = GetScriptRuntimeFromArgs(args); + OMPtr scriptHost = runtime->GetScriptHost(); + + v8::Isolate::Scope isolateScope(GetV8Isolate()); + + auto pointerFields = runtime->GetPointerFields(); + auto isolate = args.GetIsolate(); + + // exception thrower + auto throwException = [&](const std::string & exceptionString) { - // TODO: Should these be marked as immutable? - size_t length = 0; - const char* data = runtime->AssignStringValue(arg, &length); - return Push(data, length); - } - // null/undefined: add '0' - else if (arg->IsNull() || arg->IsUndefined()) + args.GetIsolate()->ThrowException(Exception::Error(String::NewFromUtf8(args.GetIsolate(), exceptionString.c_str()).ToLocalChecked())); + }; + + // variables to hold state + fxNativeContext context = { 0 }; + + // return values and their types + int numReturnValues = 0; + uintptr_t retvals[16] = { 0 }; + V8MetaFields rettypes[16]; + + // coercion for the result value + V8MetaFields returnValueCoercion = V8MetaFields::Max; + + // flag to return a result even if a pointer return value is passed + bool returnResultAnyway = false; + + // get argument count for the loop + int numArgs = args.Length(); + + // verify argument count + if (numArgs < HashGetter::BaseArgs) { - return Push(0); + return throwException("wrong argument count (needs at least a hash string)"); } - // metafield - else if (arg->IsExternal()) + + // get the hash + uint64_t hash = HashGetter()(args); + + context.nativeIdentifier = hash; + + // pushing function + auto push = [&](const auto & value) { - uint8_t* ptr = reinterpret_cast(Local::Cast(arg)->Value()); + if (context.numArguments >= std::size(context.arguments)) + { + return; + } + + *reinterpret_cast(&context.arguments[context.numArguments]) = 0; + *reinterpret_cast*>(&context.arguments[context.numArguments]) = value; + context.numArguments++; + }; + + auto cxt = runtime->GetContext(); - return PushMetaPointer(ptr); + // the big argument loop + Local a1; + if (HashGetter::BaseArgs < numArgs) + { + a1 = args[HashGetter::BaseArgs]; } - // placeholder vectors - else if (arg->IsArray()) + + for (int i = HashGetter::BaseArgs; i < numArgs; i++) { - Local array = Local::Cast(arg); - float x = 0.0f, y = 0.0f, z = 0.0f, w = 0.0f; + // get the type and decide what to do based on it + auto arg = args[i]; - auto getNumber = [&](int idx) + if (arg->IsNumber()) { - Local value; + double value = arg->NumberValue(cxt).ToChecked(); + int64_t intValue = static_cast(value); - if (!array->Get(cxt, idx).ToLocal(&value)) + if (intValue == value) { - return NAN; + push(intValue); } - - if (value.IsEmpty() || !value->IsNumber()) + else { - return NAN; + push(static_cast(value)); } - - return static_cast(value->NumberValue(cxt).ToChecked()); - }; - - if (array->Length() < 2 || array->Length() > 4) + } + else if (arg->IsBoolean() || arg->IsBooleanObject()) { - return SetError("arrays should be vectors (wrong number of values)"); + push(arg->BooleanValue(isolate)); } - - if (array->Length() >= 2) + else if (arg->IsString()) { - x = getNumber(0); - y = getNumber(1); + push(runtime->AssignStringValue(arg)); + } + // null/undefined: add '0' + else if (arg->IsNull() || arg->IsUndefined()) + { + push(0); + } + // metafield + else if (arg->IsExternal()) + { + auto pushPtr = [&](V8MetaFields metaField) + { + if (numReturnValues >= _countof(retvals)) + { + throwException("too many return value arguments"); + return false; + } + + // push the offset and set the type + push(&retvals[numReturnValues]); + rettypes[numReturnValues] = metaField; + + // increment the counter + if (metaField == V8MetaFields::PointerValueVector) + { + numReturnValues += 3; + } + else + { + numReturnValues += 1; + } + + return true; + }; + + uint8_t* ptr = reinterpret_cast(Local::Cast(arg)->Value()); - if (x == NAN || y == NAN) + // if the pointer is a metafield + if (ptr >= g_metaFields && ptr < &g_metaFields[(int)V8MetaFields::Max]) { - return SetError("invalid vector array value"); + V8MetaFields metaField = static_cast(ptr - g_metaFields); + + // switch on the metafield + switch (metaField) + { + case V8MetaFields::PointerValueInt: + case V8MetaFields::PointerValueFloat: + case V8MetaFields::PointerValueVector: + { + if (!pushPtr(metaField)) + { + return; + } + + break; + } + case V8MetaFields::ReturnResultAnyway: + returnResultAnyway = true; + break; + case V8MetaFields::ResultAsInteger: + case V8MetaFields::ResultAsLong: + case V8MetaFields::ResultAsString: + case V8MetaFields::ResultAsFloat: + case V8MetaFields::ResultAsVector: + case V8MetaFields::ResultAsObject: + returnResultAnyway = true; + returnValueCoercion = metaField; + break; + } } + // or if the pointer is a runtime pointer field + else if (ptr >= reinterpret_cast(pointerFields) && ptr < (reinterpret_cast(pointerFields) + (sizeof(PointerField) * 2))) + { + // guess the type based on the pointer field type + intptr_t ptrField = ptr - reinterpret_cast(pointerFields); + V8MetaFields metaField = static_cast(ptrField / sizeof(PointerField)); + + if (metaField == V8MetaFields::PointerValueInt || metaField == V8MetaFields::PointerValueFloat) + { + auto ptrFieldEntry = reinterpret_cast(ptr); - if (!Push(x) || !Push(y)) + retvals[numReturnValues] = ptrFieldEntry->value; + ptrFieldEntry->empty = true; + + if (!pushPtr(metaField)) + { + return; + } + } + } + else { - return false; + push(ptr); } } - - if (array->Length() >= 3) + // placeholder vectors + else if (arg->IsArray()) { - z = getNumber(2); + Local array = Local::Cast(arg); + float x = 0.0f, y = 0.0f, z = 0.0f, w = 0.0f; - if (z == NAN) + auto getNumber = [&](int idx) { - return SetError("invalid vector array value"); - } + Local value; + + if (!array->Get(cxt, idx).ToLocal(&value)) + { + return NAN; + } + + if (value.IsEmpty() || !value->IsNumber()) + { + return NAN; + } + + return static_cast(value->NumberValue(cxt).ToChecked()); + }; - if (!Push(z)) + if (array->Length() < 2 || array->Length() > 4) { - return false; + return throwException("arrays should be vectors (wrong number of values)"); } - } - - if (array->Length() >= 4) - { - w = getNumber(3); - if (w == NAN) + if (array->Length() >= 2) { - return SetError("invalid vector array value"); + x = getNumber(0); + y = getNumber(1); + + if (x == NAN || y == NAN) + { + return throwException("invalid vector array value"); + } + + push(x); + push(y); } - if (!Push(w)) + if (array->Length() >= 3) { - return false; + z = getNumber(2); + + if (z == NAN) + { + return throwException("invalid vector array value"); + } + + push(z); } - } - } - else if (arg->IsArrayBufferView()) - { - Local abv = arg.As(); - Local buffer = abv->Buffer(); - auto abs = buffer->GetBackingStore(); - return Push((uint8_t*)abs->Data() + abv->ByteOffset(), abs->ByteLength()); - } - // this should be the last entry, I'd guess - else if (arg->IsObject()) - { - Local object = arg->ToObject(cxt).ToLocalChecked(); - Local data; + if (array->Length() >= 4) + { + w = getNumber(3); - if (!object->Get(cxt, String::NewFromUtf8(GetV8Isolate(), "__data").ToLocalChecked()).ToLocal(&data)) - { - return SetError("__data field does not contain a number"); + if (w == NAN) + { + return throwException("invalid vector array value"); + } + + push(w); + } } + else if (arg->IsArrayBufferView()) + { + Local abv = arg.As(); + Local buffer = abv->Buffer(); - if (!data.IsEmpty() && data->IsNumber()) + auto abs = buffer->GetBackingStore(); + push((char*)abs->Data() + abv->ByteOffset()); + } + // this should be the last entry, I'd guess + else if (arg->IsObject()) { - auto n = data->ToNumber(runtime->GetContext()); - v8::Local number; + Local object = arg->ToObject(cxt).ToLocalChecked(); + Local data; + + if (!object->Get(cxt, String::NewFromUtf8(GetV8Isolate(), "__data").ToLocalChecked()).ToLocal(&data)) + { + return throwException("__data field does not contain a number"); + } + + if (!data.IsEmpty() && data->IsNumber()) + { + auto n = data->ToNumber(runtime->GetContext()); + v8::Local number; - if (n.ToLocal(&number)) + if (n.ToLocal(&number)) + { + push(number->Int32Value(cxt).ToChecked()); + } + } + else { - return Push(number->Int32Value(cxt).ToChecked()); + return throwException("__data field does not contain a number"); } } else { - return SetError("__data field does not contain a number"); + String::Utf8Value str(GetV8Isolate(), arg); + + return throwException(va("Invalid V8 value: %s", *str)); } } - else - { - String::Utf8Value str(GetV8Isolate(), arg); - return SetError(va("Invalid V8 value: %s", *str)); - } - return true; -} +#if __has_include() + fx::scripting::ResultType resultTypeIntent = fx::scripting::ResultType::None; -template -v8::Local V8ScriptNativeContext::ProcessResult(const T& value) -{ - if constexpr (std::is_same_v) + if (!returnResultAnyway) { - return Boolean::New(isolate, value); + resultTypeIntent = fx::scripting::ResultType::Void; } - else if constexpr (std::is_same_v) + else if (returnValueCoercion == V8MetaFields::ResultAsString) { - return Int32::New(isolate, value); + resultTypeIntent = fx::scripting::ResultType::String; } - else if constexpr (std::is_same_v) + else if (returnValueCoercion == V8MetaFields::ResultAsInteger || returnValueCoercion == V8MetaFields::ResultAsLong || returnValueCoercion == V8MetaFields::ResultAsFloat || returnValueCoercion == V8MetaFields::Max /* bool */) { - return Number::New(isolate, (double) value); + resultTypeIntent = fx::scripting::ResultType::Scalar; } - else if constexpr (std::is_same_v) + else if (returnValueCoercion == V8MetaFields::ResultAsVector) { - return Number::New(isolate, value); + resultTypeIntent = fx::scripting::ResultType::Vector; } - else if constexpr (std::is_same_v) - { - Local result = Array::New(isolate, 3); +#endif - result->Set(cxt, 0, Number::New(isolate, value.x)); - result->Set(cxt, 1, Number::New(isolate, value.y)); - result->Set(cxt, 2, Number::New(isolate, value.z)); +#ifndef IS_FXSERVER + bool needsResultCheck = (numReturnValues == 0 || returnResultAnyway); + bool hadComplexType = !a1.IsEmpty() && (!a1->IsNumber() && !a1->IsBoolean() && !a1->IsNull()); + + decltype(context.arguments) initialArguments; + memcpy(initialArguments, context.arguments, sizeof(context.arguments)); +#endif + + // invoke the native on the script host + if (!FX_SUCCEEDED(scriptHost->InvokeNative(context))) + { + char* error = "Unknown"; + scriptHost->GetLastErrorText(&error); - return result; + return throwException(fmt::sprintf("Execution of native %016x in script host failed: %s", hash, error)); } - else if constexpr (std::is_same_v) + +#ifndef IS_FXSERVER + // clean up the result + if ((needsResultCheck || hadComplexType) && context.numArguments > 0) { - if (value) + // if this is scrstring but nothing changed (very weird!), fatally fail + if (static_cast(context.arguments[2]) == 0xFEED1212 && initialArguments[2] == context.arguments[2]) { - return String::NewFromUtf8(isolate, value).ToLocalChecked(); + FatalError("Invalid native call in V8 resource '%s'. Please see https://aka.cfx.re/scrstring-mitigation for more information.", runtime->GetResourceName()); } - else + + // If return coercion is String type + // Don't allow deref'ing arbitrary pointers passed in any of the arguments. + if (returnValueCoercion == V8MetaFields::ResultAsString && context.arguments[0]) { - return Null(isolate); + NativeStringResultSanitization(args, context, initialArguments); + } + // if the first value (usually result) is the same as the initial argument, clear the result (usually, result was no-op) + // (if vector results, these aren't directly unsafe, and may get incorrectly seen as complex) + else if (context.arguments[0] == initialArguments[0] && returnValueCoercion != V8MetaFields::ResultAsVector) + { + // complex type in first result means we have to clear that result + if (hadComplexType) + { + context.arguments[0] = 0; + } + + // if any result is requested and there was *no* change, zero out + context.arguments[1] = 0; + context.arguments[2] = 0; + context.arguments[3] = 0; } } - else if constexpr (std::is_same_v) +#endif + +#if __has_include() + if (resultTypeIntent != fx::scripting::ResultType::None) { - // FIXME: This wasn't properly supported before. Should it return a Uint8Buffer buffer instead? - return String::NewFromUtf8(isolate, value.str, v8::NewStringType::kNormal, (int) value.len).ToLocalChecked(); + fx::scripting::PointerArgumentHints::CleanNativeResult(hash, resultTypeIntent, context.arguments); } - else if constexpr (std::is_same_v) +#endif + + // padded vector struct + struct scrVector + { + float x; + + private: + uint32_t pad0; + + public: + float y; + + private: + uint32_t pad1; + + public: + float z; + + private: + uint32_t pad2; + }; + + struct scrObject { - try + const char* data; + uintptr_t length; + }; + + // JS results + Local returnValue = Undefined(args.GetIsolate()); + int numResults = 0; + + // if no other result was requested, or we need to return the result anyway, push the result + if (numReturnValues == 0 || returnResultAnyway) + { + // increment the result count + numResults++; + + // handle the type coercion + switch (returnValueCoercion) + { + case V8MetaFields::ResultAsString: + { + const char* str = *reinterpret_cast(&context.arguments[0]); + + if (str) + { + returnValue = String::NewFromUtf8(args.GetIsolate(), str).ToLocalChecked(); + } + else + { + returnValue = Null(args.GetIsolate()); + } + + break; + } + case V8MetaFields::ResultAsFloat: + returnValue = Number::New(args.GetIsolate(), *reinterpret_cast(&context.arguments[0])); + break; + case V8MetaFields::ResultAsVector: { - msgpack::unpacked unpacked; - msgpack::unpack(unpacked, value.data, value.length); + scrVector vector = *reinterpret_cast(&context.arguments[0]); - // and convert to a rapidjson object - rapidjson::Document document; - ConvertToJSON(unpacked.get(), document, document.GetAllocator()); + Local vectorArray = Array::New(args.GetIsolate(), 3); + vectorArray->Set(cxt, 0, Number::New(args.GetIsolate(), vector.x)); + vectorArray->Set(cxt, 1, Number::New(args.GetIsolate(), vector.y)); + vectorArray->Set(cxt, 2, Number::New(args.GetIsolate(), vector.z)); + + returnValue = vectorArray; + + break; + } + case V8MetaFields::ResultAsObject: + { + scrObject object = *reinterpret_cast(&context.arguments[0]); - // write as a json string - rapidjson::StringBuffer sb; - rapidjson::Writer writer(sb); + // try parsing + returnValue = Null(GetV8Isolate()); - if (document.Accept(writer)) + try { - if (sb.GetString() && sb.GetSize()) - { - auto maybeString = String::NewFromUtf8(GetV8Isolate(), sb.GetString(), NewStringType::kNormal, sb.GetSize()); - Local string; + msgpack::unpacked unpacked; + msgpack::unpack(unpacked, object.data, object.length); + + // and convert to a rapidjson object + rapidjson::Document document; + ConvertToJSON(unpacked.get(), document, document.GetAllocator()); + + // write as a json string + rapidjson::StringBuffer sb; + rapidjson::Writer writer(sb); - if (maybeString.ToLocal(&string)) + if (document.Accept(writer)) + { + if (sb.GetString() && sb.GetSize()) { - auto maybeValue = v8::JSON::Parse(runtime->GetContext(), string); - Local value; + auto maybeString = String::NewFromUtf8(GetV8Isolate(), sb.GetString(), NewStringType::kNormal, sb.GetSize()); + Local string; - if (maybeValue.ToLocal(&value)) + if (maybeString.ToLocal(&string)) { - return value; + auto maybeValue = v8::JSON::Parse(runtime->GetContext(), string); + Local value; + + if (maybeValue.ToLocal(&value)) + { + returnValue = value; + } } } } } + catch (std::exception& e) + { + } + + break; } - catch (std::exception& /*e*/) + case V8MetaFields::ResultAsInteger: + returnValue = Int32::New(args.GetIsolate(), *reinterpret_cast(&context.arguments[0])); + break; + case V8MetaFields::ResultAsLong: + returnValue = Number::New(args.GetIsolate(), double(*reinterpret_cast(&context.arguments[0]))); + break; + default: { - } - - return Null(GetV8Isolate()); - } - else - { - static_assert(always_false_v, "Invalid return type"); - } -} + int32_t integer = *reinterpret_cast(&context.arguments[0]); -static void V8_InvokeNative(const v8::FunctionCallbackInfo& args, uint64_t hash, int baseArgs) -{ - // get required entries - V8ScriptRuntime* runtime = GetScriptRuntimeFromArgs(args); - - // variables to hold state - V8ScriptNativeContext context(hash, runtime, args.GetIsolate()); - - // get argument count for the loop - int numArgs = args.Length(); - - // verify argument count - if (numArgs < baseArgs) - { - context.SetError("wrong argument count (needs at least a hash string)"); - return; - } - - for (int i = baseArgs; i < numArgs; i++) - { - if (!context.PushArgument(args[i])) - { - return; + if ((integer & 0xFFFFFFFF) == 0) + { + returnValue = Boolean::New(args.GetIsolate(), false); + } + else + { + returnValue = Int32::New(args.GetIsolate(), integer); + } + } } } - if (!context.PreInvoke()) - { - return; - } - - OMPtr scriptHost = runtime->GetScriptHost(); - - // invoke the native on the script host - if (!FX_SUCCEEDED(scriptHost->InvokeNative(context))) + // loop over the return value pointers { - char* error = "Unknown"; - scriptHost->GetLastErrorText(&error); + int i = 0; - context.SetError("Execution of native %016x in script host failed: %s", hash, error); - return; - } + // fast path non-array result + if (numReturnValues == 1 && numResults == 0) + { + switch (rettypes[0]) + { + case V8MetaFields::PointerValueInt: + returnValue = Int32::New(args.GetIsolate(), retvals[0]); + break; - if (!context.PostInvoke()) - { - return; - } + case V8MetaFields::PointerValueFloat: + returnValue = Number::New(args.GetIsolate(), *reinterpret_cast(&retvals[0])); + break; - // For a single result, return it directly. - // For multiple results, store them in an array. - Local returnValue = Undefined(context.isolate); - int numResults = 0; + case V8MetaFields::PointerValueVector: + { + scrVector vector = *reinterpret_cast(&retvals[0]); - context.ProcessResults([&](auto&& value) { - Local val = context.ProcessResult(value); + Local vectorArray = Array::New(args.GetIsolate(), 3); + vectorArray->Set(cxt, 0, Number::New(args.GetIsolate(), vector.x)); + vectorArray->Set(cxt, 1, Number::New(args.GetIsolate(), vector.y)); + vectorArray->Set(cxt, 2, Number::New(args.GetIsolate(), vector.z)); - if (numResults == 0) - { - returnValue = val; + returnValue = vectorArray; + break; + } + } } - else + else if (numReturnValues > 0) { - if (numResults == 1) + Local arrayValue = Array::New(args.GetIsolate()); + + // transform into array { - Local arrayValue = Array::New(context.isolate); - arrayValue->Set(context.cxt, 0, returnValue); + Local oldValue = returnValue; + returnValue = arrayValue; + arrayValue->Set(cxt, 0, oldValue); } - returnValue.As()->Set(context.cxt, numResults, val); - } + while (i < numReturnValues) + { + switch (rettypes[i]) + { + case V8MetaFields::PointerValueInt: + arrayValue->Set(cxt, numResults, Int32::New(args.GetIsolate(), retvals[i])); + i++; + break; - ++numResults; + case V8MetaFields::PointerValueFloat: + arrayValue->Set(cxt, numResults, Number::New(args.GetIsolate(), *reinterpret_cast(&retvals[i]))); + i++; + break; - return true; - }); + case V8MetaFields::PointerValueVector: + { + scrVector vector = *reinterpret_cast(&retvals[i]); - // and set the return value(s) - args.GetReturnValue().Set(returnValue); -} + Local vectorArray = Array::New(args.GetIsolate(), 3); + vectorArray->Set(cxt, 0, Number::New(args.GetIsolate(), vector.x)); + vectorArray->Set(cxt, 1, Number::New(args.GetIsolate(), vector.y)); + vectorArray->Set(cxt, 2, Number::New(args.GetIsolate(), vector.z)); + arrayValue->Set(cxt, numResults, vectorArray); -static void V8_InvokeNativeString(const v8::FunctionCallbackInfo& args) -{ - String::Utf8Value hashString(GetV8Isolate(), args[0]); - uint64_t hash = strtoull(*hashString, nullptr, 16); - return V8_InvokeNative(args, hash, 1); -} + i += 3; + break; + } + } -static void V8_InvokeNativeHash(const v8::FunctionCallbackInfo& args) -{ - auto scrt = V8ScriptRuntime::GetCurrent(); - uint64_t hash = (args[1]->Uint32Value(scrt->GetContext()).ToChecked() | (((uint64_t)args[0]->Uint32Value(scrt->GetContext()).ToChecked()) << 32)); - return V8_InvokeNative(args, hash, 2); + numResults++; + } + } + } + + // and set the return value(s) + args.GetReturnValue().Set(returnValue); } -template +template static void V8_GetMetaField(const v8::FunctionCallbackInfo& args) { - args.GetReturnValue().Set(External::New(GetV8Isolate(), &ScriptNativeContext::s_metaFields[(int)MetaField])); + args.GetReturnValue().Set(External::New(GetV8Isolate(), &g_metaFields[(int)MetaField])); } -template +template static void V8_GetPointerField(const v8::FunctionCallbackInfo& args) { V8ScriptRuntime* runtime = GetScriptRuntimeFromArgs(args); @@ -1395,13 +1719,13 @@ static void V8_GetPointerField(const v8::FunctionCallbackInfo& args) auto arg = args[0]; - if (MetaField == MetaField::PointerValueFloat) + if (MetaField == V8MetaFields::PointerValueFloat) { float value = static_cast(arg->NumberValue(runtime->GetContext()).ToChecked()); pointerField->value = *reinterpret_cast(&value); } - else if (MetaField == MetaField::PointerValueInt) + else if (MetaField == V8MetaFields::PointerValueInt) { intptr_t value = arg->IntegerValue(runtime->GetContext()).ToChecked(); @@ -1651,8 +1975,8 @@ static std::pair g_citizenFunctions[] = { "makeFunctionReference", V8_MakeFunctionReference }, // internals { "getTickCount", V8_GetTickCount }, - { "invokeNative", V8_InvokeNativeString }, - { "invokeNativeByHash", V8_InvokeNativeHash }, + { "invokeNative", V8_InvokeNative }, + { "invokeNativeByHash", V8_InvokeNative }, #ifndef IS_FXSERVER { "invokeNativeRaw", V8_InvokeNativeRaw }, // not yet! @@ -1667,18 +1991,18 @@ static std::pair g_citizenFunctions[] = { "submitBoundaryEnd", V8_SubmitBoundaryEnd }, { "setStackTraceFunction", V8_SetStackTraceRoutine }, // metafields - { "pointerValueIntInitialized", V8_GetPointerField }, - { "pointerValueFloatInitialized", V8_GetPointerField }, - { "pointerValueInt", V8_GetMetaField }, - { "pointerValueFloat", V8_GetMetaField }, - { "pointerValueVector", V8_GetMetaField }, - { "returnResultAnyway", V8_GetMetaField }, - { "resultAsInteger", V8_GetMetaField }, - { "resultAsLong", V8_GetMetaField }, - { "resultAsFloat", V8_GetMetaField }, - { "resultAsString", V8_GetMetaField }, - { "resultAsVector", V8_GetMetaField }, - { "resultAsObject2", V8_GetMetaField }, + { "pointerValueIntInitialized", V8_GetPointerField }, + { "pointerValueFloatInitialized", V8_GetPointerField }, + { "pointerValueInt", V8_GetMetaField }, + { "pointerValueFloat", V8_GetMetaField }, + { "pointerValueVector", V8_GetMetaField }, + { "returnResultAnyway", V8_GetMetaField }, + { "resultAsInteger", V8_GetMetaField }, + { "resultAsLong", V8_GetMetaField }, + { "resultAsFloat", V8_GetMetaField }, + { "resultAsString", V8_GetMetaField }, + { "resultAsVector", V8_GetMetaField }, + { "resultAsObject2", V8_GetMetaField }, { "getResourcePath", V8_GetResourcePath }, }; diff --git a/code/components/extra-natives-five/src/RuntimeAssetNatives.cpp b/code/components/extra-natives-five/src/RuntimeAssetNatives.cpp index b82d54ef43..2a86ac2333 100644 --- a/code/components/extra-natives-five/src/RuntimeAssetNatives.cpp +++ b/code/components/extra-natives-five/src/RuntimeAssetNatives.cpp @@ -108,11 +108,11 @@ class RuntimeTxd public: RuntimeTxd(const char* name); - std::shared_ptr CreateTexture(const char* name, int width, int height); + RuntimeTex* CreateTexture(const char* name, int width, int height); - std::shared_ptr CreateTextureFromImage(const char* name, const char* fileName); + RuntimeTex* CreateTextureFromImage(const char* name, const char* fileName); - std::shared_ptr CreateTextureFromDui(const char* name, const char* duiHandle); + RuntimeTex* CreateTextureFromDui(const char* name, const char* duiHandle); private: void EnsureTxd(); @@ -282,7 +282,7 @@ void RuntimeTxd::EnsureTxd() } } -std::shared_ptr RuntimeTxd::CreateTexture(const char* name, int width, int height) +RuntimeTex* RuntimeTxd::CreateTexture(const char* name, int width, int height) { if (!m_txd) { @@ -309,7 +309,8 @@ std::shared_ptr RuntimeTxd::CreateTexture(const char* name, int widt m_textures[name] = tex; - return tex; + scrBindAddSafePointer(tex.get()); + return tex.get(); } extern void TextureReplacement_OnTextureCreate(const std::string& txd, const std::string& txn); @@ -324,7 +325,7 @@ static void OnNextMainFrame(std::function&& fn) nextFrameQueue.push(std::move(fn)); } -std::shared_ptr RuntimeTxd::CreateTextureFromDui(const char* name, const char* duiHandle) +RuntimeTex* RuntimeTxd::CreateTextureFromDui(const char* name, const char* duiHandle) { if (!m_txd) { @@ -354,7 +355,8 @@ std::shared_ptr RuntimeTxd::CreateTextureFromDui(const char* name, c m_textures[name] = tex; - return tex; + scrBindAddSafePointer(tex.get()); + return tex.get(); } #pragma comment(lib, "windowscodecs.lib") @@ -439,7 +441,7 @@ static ComPtr ImageToBitmapSource(std::string_view fileName) return source; } -std::shared_ptr RuntimeTxd::CreateTextureFromImage(const char* name, const char* fileName) +RuntimeTex* RuntimeTxd::CreateTextureFromImage(const char* name, const char* fileName) { if (!m_txd) { @@ -482,7 +484,8 @@ std::shared_ptr RuntimeTxd::CreateTextureFromImage(const char* name, m_textures[name] = tex; - return tex; + scrBindAddSafePointer(tex.get()); + return tex.get(); } } @@ -1093,7 +1096,7 @@ static InitFunction initFunction([]() }); scrBindClass() - .AddConstructor("CREATE_RUNTIME_TXD") + .AddConstructor("CREATE_RUNTIME_TXD") .AddMethod("CREATE_RUNTIME_TEXTURE", &RuntimeTxd::CreateTexture) .AddMethod("CREATE_RUNTIME_TEXTURE_FROM_IMAGE", &RuntimeTxd::CreateTextureFromImage) .AddMethod("CREATE_RUNTIME_TEXTURE_FROM_DUI_HANDLE", &RuntimeTxd::CreateTextureFromDui); @@ -1154,6 +1157,156 @@ static InitFunction initFunction([]() } }); + fx::ScriptEngine::RegisterNativeHandler("REGISTER_ENTITIES", [](fx::ScriptContext& context) + { + fx::OMPtr runtime; + + std::string factoryRef = context.CheckArgument(0); + + if (FX_SUCCEEDED(fx::GetCurrentScriptRuntime(&runtime))) + { + fx::Resource* resource = reinterpret_cast(runtime->GetParentObject()); + + if (resource) + { + msgpack::unpacked unpacked; + auto factoryObject = resource->GetManager()->CallReferenceUnpacked(factoryRef, &unpacked); + + if (factoryObject.type != msgpack::type::ARRAY && factoryObject.type != msgpack::type::MAP) + { + throw std::runtime_error("Wrong type in REGISTER_ENTITIES."); + } + + std::vector> entities = + (factoryObject.type == msgpack::type::ARRAY) ? + factoryObject.as>>() : + std::vector>{ factoryObject.as>() }; + + static int idx; + std::string nameRef = fmt::sprintf("reg_ents_%d", idx++); + + CMapData* mapData = new CMapData(); + + // 1604, temp + assert(!xbr::IsGameBuildOrGreater<1868>()); + + *(uintptr_t*)mapData = 0x1419343E0; + mapData->name = HashString(nameRef.c_str()); + mapData->contentFlags = 73; + + float aabbMin[3]; + float aabbMax[3]; + + aabbMin[0] = FLT_MAX; + aabbMin[1] = FLT_MAX; + aabbMin[2] = FLT_MAX; + + aabbMax[0] = 0.0f - FLT_MAX; + aabbMax[1] = 0.0f - FLT_MAX; + aabbMax[2] = 0.0f - FLT_MAX; + + mapData->entities.Expand(entities.size()); + + size_t i = 0; + + for (const auto& entityData : entities) + { + fwEntityDef* entityDef = (fwEntityDef*)MakeStructFromMsgPack("CEntityDef", entityData); + mapData->entities.Set(i, entityDef); + + rage::fwModelId modelId; + fwArchetype* archetype = rage::fwArchetypeManager::GetArchetypeFromHashKeySafe(entityDef->archetypeName, modelId); + + if (archetype) + { + float radius = archetype->radius; + + if (archetype->radius < 0.01f) + { + radius = 250.f; + } + + // update AABB + float xMin = entityDef->position[0] - radius; + float yMin = entityDef->position[1] - radius; + float zMin = entityDef->position[2] - radius; + + float xMax = entityDef->position[0] + radius; + float yMax = entityDef->position[1] + radius; + float zMax = entityDef->position[2] + radius; + + aabbMin[0] = (xMin < aabbMin[0]) ? xMin : aabbMin[0]; + aabbMin[1] = (yMin < aabbMin[1]) ? yMin : aabbMin[1]; + aabbMin[2] = (zMin < aabbMin[2]) ? zMin : aabbMin[2]; + + aabbMax[0] = (xMax > aabbMax[0]) ? xMax : aabbMax[0]; + aabbMax[1] = (yMax > aabbMax[1]) ? yMax : aabbMax[1]; + aabbMax[2] = (zMax > aabbMax[2]) ? zMax : aabbMax[2]; + } + + i++; + } + + mapData->entitiesExtentsMin[0] = aabbMin[0]; + mapData->entitiesExtentsMin[1] = aabbMin[1]; + mapData->entitiesExtentsMin[2] = aabbMin[2]; + + mapData->entitiesExtentsMax[0] = aabbMax[0]; + mapData->entitiesExtentsMax[1] = aabbMax[1]; + mapData->entitiesExtentsMax[2] = aabbMax[2]; + + mapData->streamingExtentsMin[0] = aabbMin[0]; + mapData->streamingExtentsMin[1] = aabbMin[1]; + mapData->streamingExtentsMin[2] = aabbMin[2]; + + mapData->streamingExtentsMax[0] = aabbMax[0]; + mapData->streamingExtentsMax[1] = aabbMax[1]; + mapData->streamingExtentsMax[2] = aabbMax[2]; + + auto mapTypesStore = streaming::Manager::GetInstance()->moduleMgr.GetStreamingModule("ytyp"); + auto mapDataStore = streaming::Manager::GetInstance()->moduleMgr.GetStreamingModule("ymap"); + + uint32_t mehId; + mapTypesStore->FindSlot(&mehId, "v_int_1"); + + uint32_t mapId; + mapDataStore->FindSlotFromHashKey(&mapId, nameRef.c_str()); + + if (mapId != -1) + { + void* unkRef[4] = { 0 }; + unkRef[0] = mapData; + + // 1604, temp (pso store placement cookie) + ((void(*)(void*, uint32_t, const void*))0x14158FCD4)((void*)0x142DC9678, mapId + mapDataStore->baseIdx, unkRef); + + auto pool = (atPoolBase*)((char*)mapDataStore + 56); + *(int32_t*)(pool->GetAt(mapId) + 32) |= 2048; + *(int16_t*)(pool->GetAt(mapId) + 38) = 1; + *(int32_t*)(pool->GetAt(mapId) + 24) = mehId; // TODO: FIGURE OUT + + //auto contents = (CMapDataContents*)mapDataStore->GetPtr(mapId); + auto mapMeta = (void*)mapDataStore->GetDataPtr(mapId); // not sure? + + // TODO: leak + mapData->CreateMapDataContents()->PrepareInteriors(mapMeta, mapData, mapId); + + // reference is ignored but we pass it for formality - it actually uses PSO store placement cookies + streaming::strAssetReference ref; + ref.asset = mapData; + + mapDataStore->SetResource(mapId, ref); + streaming::Manager::GetInstance()->Entries[mapId + mapDataStore->baseIdx].flags |= (512 << 8) | 1; + + // 1604 + ((void(*)(int))0x1408CF07C)(0); + + ((void(*)(void*))((*(void***)0x142DCA970)[2]))((void*)0x142DCA970); + } + } + } + }); + fx::ScriptEngine::RegisterNativeHandler("REGISTER_STREAMING_FILE_FROM_CACHE", [](fx::ScriptContext& context) { std::string resourceName = context.CheckArgument(0); diff --git a/code/components/nui-resources/src/ResourceUIScripting.cpp b/code/components/nui-resources/src/ResourceUIScripting.cpp index 4296577d0a..d0743c2c07 100644 --- a/code/components/nui-resources/src/ResourceUIScripting.cpp +++ b/code/components/nui-resources/src/ResourceUIScripting.cpp @@ -329,7 +329,7 @@ static InitFunction initFunction([] () }; scrBindClass() - .AddConstructor("CREATE_DUI") + .AddConstructor("CREATE_DUI") .AddMethod("SET_DUI_URL", &NUIWindowWrapper::SetURL) .AddMethod("SEND_DUI_MESSAGE", &NUIWindowWrapper::SendMessage) .AddMethod("GET_DUI_HANDLE", &NUIWindowWrapper::GetHandle) diff --git a/code/components/rage-scripting-five/include/PointerArgumentHints.h b/code/components/rage-scripting-five/include/PointerArgumentHints.h index c44eb4c9c8..6112047a7c 100644 --- a/code/components/rage-scripting-five/include/PointerArgumentHints.h +++ b/code/components/rage-scripting-five/include/PointerArgumentHints.h @@ -2,12 +2,24 @@ namespace fx::scripting { +enum class ResultType +{ + None, + Void, + String, + Scalar, + Vector, +}; +class #ifdef COMPILING_RAGE_SCRIPTING_FIVE DLL_EXPORT #else DLL_IMPORT #endif -const uint32_t* GetNativeTypeInfo(uint64_t hash); - +PointerArgumentHints +{ +public: + static void CleanNativeResult(uint64_t nativeIdentifier, ResultType resultType, void* resultBuffer); +}; } diff --git a/code/components/rage-scripting-five/src/PointerArgumentSafety.cpp b/code/components/rage-scripting-five/src/PointerArgumentSafety.cpp index 687322e027..7c1b9976b6 100644 --- a/code/components/rage-scripting-five/src/PointerArgumentSafety.cpp +++ b/code/components/rage-scripting-five/src/PointerArgumentSafety.cpp @@ -8,35 +8,111 @@ #include +static bool g_sdk; + +static ptrdiff_t GetMainImageSize() +{ + MODULEINFO mi; + auto mainModule = GetModuleHandle(NULL); + if (GetModuleInformation(GetCurrentProcess(), mainModule, &mi, sizeof(mi))) + { + return mi.SizeOfImage; + } + + return 0x68000000; +} + +static uintptr_t rangeStart = (uintptr_t)GetModuleHandle(NULL); +static uintptr_t rangeEnd = rangeStart + GetMainImageSize(); +static ICoreGameInit* icgi; bool storyMode; +static bool ValidateArg(void* arg) +{ + if (storyMode) + { + return true; + } + + if ((uintptr_t)arg >= rangeStart && (uintptr_t)arg < rangeEnd) + { + return false; + } + + return true; +} + +static void NullifyAny(void*& arg) +{ + if (!storyMode) + { + arg = nullptr; + } +} + +static void NullifyVoid(rage::scrNativeCallContext* cxt) +{ + if (!storyMode) + { + cxt->SetResult(0, intptr_t(0)); + } +} + +template +static void ResultCleaner(void* results, fx::scripting::ResultType hint) +{ + using fx::scripting::ResultType; + + if constexpr (std::is_same_v>) + { + if (hint != ResultType::String) + { + *(uintptr_t*)results = 0; + return; + } + } + else if constexpr (std::is_arithmetic_v || std::is_same_v, bool>) + { + if (hint == ResultType::String) + { + *(uintptr_t*)results = 0; + return; + } + } +} + namespace rage { extern uint64_t MapNative(uint64_t inNative); } -static std::unordered_map g_nativeTypes; +static std::unordered_map)> g_resultCleaners; -static void RegisterNativeTypeInfo(uint64_t hash, const uint32_t* typeInfo) +static void AddResultCleaner(uint64_t hash, decltype(g_resultCleaners)::mapped_type cleaner) { - g_nativeTypes[rage::MapNative(hash)] = typeInfo; + g_resultCleaners[rage::MapNative(hash)] = cleaner; } namespace fx::scripting { - -const uint32_t* fx::scripting::GetNativeTypeInfo(uint64_t hash) +void PointerArgumentHints::CleanNativeResult(uint64_t nativeIdentifier, ResultType resultType, void* resultBuffer) { - hash = rage::MapNative(hash); - - if (auto find = g_nativeTypes.find(hash); find != g_nativeTypes.end()) + if (resultType == fx::scripting::ResultType::None || g_sdk) { - return find->second; + return; } - return nullptr; -} + nativeIdentifier = rage::MapNative(nativeIdentifier); + if (auto cleaner = g_resultCleaners.find(nativeIdentifier); cleaner != g_resultCleaners.end()) + { + cleaner->second(resultBuffer, resultType); + } + else if (auto handler = rage::scrEngine::GetNativeHandler(nativeIdentifier); !handler) + { + *(uintptr_t*)resultBuffer = 0; + } +} } #if __has_include("PASPriv.h") @@ -50,9 +126,18 @@ void PointerArgumentSafety() PointerArgumentSafety_Impl(); } +static InitFunction initFunction([] +{ + if (launch::IsSDK()) + { + g_sdk = true; + } +}); + static HookFunction hookFunction([] { - Instance::Get()->OnSetVariable.Connect([](const std::string& name, bool value) + icgi = Instance::Get(); + icgi->OnSetVariable.Connect([](const std::string& name, bool value) { if (name == "storyMode") { diff --git a/code/components/scrbind-base/include/scrBind.h b/code/components/scrbind-base/include/scrBind.h index 40b8c6480a..00b37b2f79 100644 --- a/code/components/scrbind-base/include/scrBind.h +++ b/code/components/scrbind-base/include/scrBind.h @@ -17,318 +17,606 @@ #include #include -// This pool needs to support arbitrary types, support up/down casting, and work across dll boundaries - -// Override this to allow upcasting pointers -template -struct scrBindParent +// class +template +struct scrBindArgument { - using type = void; + static TArg Get(fx::ScriptContext& cxt, int i) + { + return cxt.GetArgument(i); + } }; -#ifdef _WIN32 -#define SCRBIND_TYPE_NAME(EXPR) typeid(EXPR).raw_name() -#else -#define SCRBIND_TYPE_NAME(EXPR) typeid(EXPR).name() -#endif - -struct scrBindPointer +template +struct scrBindArgument> { - uint32_t Handle; - std::shared_ptr Pointer; - - scrBindPointer(uint32_t handle, std::shared_ptr pointer) - : Handle(handle), Pointer(std::move(pointer)) - {} + using TArg = std::shared_ptr; - virtual ~scrBindPointer() = default; + static TArg Get(fx::ScriptContext& cxt, int i) + { + return *cxt.GetArgument(i); + } +}; - virtual void* Cast(std::string_view type) = 0; +struct scrObjectRef +{ + size_t length; + char data[]; +}; - template - std::shared_ptr Cast() +namespace +{ + template + auto DeserializeObject(scrObjectRef* ref) { - if (void* ptr = Cast(SCRBIND_TYPE_NAME(T))) - return std::shared_ptr { Pointer, static_cast(ptr) }; + return msgpack::unpack(ref->data, ref->length).get().as(); + } +} + +template +struct scrBindArgument> +{ + using TArg = std::vector; - return nullptr; + static TArg Get(fx::ScriptContext& cxt, int i) + { + return DeserializeObject(cxt.CheckArgument(i)); } }; -template::type> -struct scrBindPointerT - : scrBindPointerT +template +struct scrBindArgument&&> { - using scrBindPointerT::scrBindPointerT; + using TArg = std::vector; - void* Cast(std::string_view type) override + static TArg Get(fx::ScriptContext& cxt, int i) { - return (type == SCRBIND_TYPE_NAME(U)) - ? static_cast(static_cast(static_cast(this->Pointer.get()))) - : scrBindPointerT::Cast(type); + return DeserializeObject(cxt.CheckArgument(i)); } }; -template -struct scrBindPointerT - : scrBindPointer +template +struct scrBindResult { - using scrBindPointer::scrBindPointer; + static void Set(fx::ScriptContext& cxt, const TArg& result) + { + cxt.SetResult(result); + } +}; - void* Cast(std::string_view type) override +template +struct scrBindResult> +{ + static void Set(fx::ScriptContext& cxt, std::shared_ptr ref) { - // One final attempt to cast based on the pointer's actual typeid (might be different if it's polymorphic) - return (type == SCRBIND_TYPE_NAME(*static_cast(this->Pointer.get()))) - ? this->Pointer.get() - : nullptr; + // TODO: destroy natives? + cxt.SetResult(new std::shared_ptr(ref)); } }; -struct scrBindPool +namespace { - std::unordered_map> Handles; - std::unordered_map Pointers; - uint32_t UID = 0; + template + class scrBindFunc + { + + }; - template - uint32_t MakeHandle(std::shared_ptr value) + template + class scrBindFunc { - if (value == nullptr) + typedef TRet(* TFunc)(Args...); + + public: + static constexpr int GetArgumentCount() { - return 0; + return sizeof...(Args); } - auto find = Pointers.find(value.get()); + static TRet Call(TFunc fn, Args... args) + { + return fn(args...); + } + }; - if (find != Pointers.end()) - return find->second->Handle; + template + class scrBindFunc + { + typedef TRet(TObj::* TFunc)(Args...); + + public: + static constexpr int GetArgumentCount() + { + return sizeof...(Args); + } - // "Randomize" the handles to make it more obvious when passing in an incorrect value. - uint32_t handle = ++UID; - handle += 0xDEADBEEF; - handle ^= 0xDEADBEEF; - handle *= 0x9E3779B9; + static TRet Call(TObj* object, TFunc fn, Args... args) + { + return (object->*fn)(args...); + } + }; - auto data = Handles.emplace(handle, std::make_unique>(handle, value)); + template + class scrBindFunc + { + typedef TRet(TObj::* TFunc)(Args...) const; - if (!data.second) + public: + static constexpr int GetArgumentCount() { - throw std::runtime_error("scrbind handle collision"); + return sizeof...(Args); } - Pointers.emplace(value.get(), data.first->second.get()); + static TRet Call(const TObj* object, TFunc fn, Args... args) + { + return (object->*fn)(args...); + } + }; - return handle; - } + template + class scrBindFunc + { + typedef TRet(*TFunc)(TObj*, Args...); + + public: + static constexpr int GetArgumentCount() + { + return sizeof...(Args); + } + + static TRet Call(TObj* object, TFunc fn, Args... args) + { + return fn(object, args...); + } + }; + + template + struct scrBindConstructor + { + + }; - template - std::shared_ptr FromHandle(uint32_t handle) + template + struct scrBindConstructor { - if (auto find = Handles.find(handle); find != Handles.end()) + static void Call(fx::ScriptContext& context) { - if (auto ptr = find->second->Cast()) - return ptr; + context.SetResult(new TClass()); } + }; - throw std::runtime_error("invalid handle"); - } + template + struct scrBindConstructor + { + static void Call(fx::ScriptContext& context) + { + context.SetResult(new TClass( + context.GetArgument(0) + )); + } + }; - template - void ErasePointer(std::shared_ptr pointer) + template + struct scrBindConstructor { - auto find = Pointers.find(pointer.get()); + static void Call(fx::ScriptContext& context) + { + context.SetResult(new TClass( + context.GetArgument(0), + context.GetArgument(1) + )); + } + }; - if (find != Pointers.end()) + template + struct scrBindConstructor + { + static void Call(fx::ScriptContext& context) { - Handles.erase(find->second->Handle); - Pointers.erase(find); + context.SetResult(new TClass( + context.GetArgument(0), + context.GetArgument(1), + context.GetArgument(2) + )); } - } + }; - void Reset() + template + struct scrBindConstructor { - Pointers.clear(); - Handles.clear(); - UID = 0; - } -}; + static void Call(fx::ScriptContext& context) + { + context.SetResult(new TClass( + context.GetArgument(0), + context.GetArgument(1), + context.GetArgument(2), + context.GetArgument(3) + )); + } + }; -#ifdef COMPILING_SCRBIND_BASE -DLL_EXPORT -#else -DLL_IMPORT -#endif -extern scrBindPool g_ScrBindPool; + template + struct scrBindConstructor + { + static void Call(fx::ScriptContext& context) + { + context.SetResult(new TClass( + context.GetArgument(0), + context.GetArgument(1), + context.GetArgument(2), + context.GetArgument(3), + context.GetArgument(4) + )); + } + }; -// TODO: Add PAS_ARG_OBJECT type -struct scrObjectRef -{ - size_t length; - char data[]; -}; + template + struct scrBindConstructor + { + static void Call(fx::ScriptContext& context) + { + context.SetResult(new TClass( + context.GetArgument(0), + context.GetArgument(1), + context.GetArgument(2), + context.GetArgument(3), + context.GetArgument(4), + context.GetArgument(5) + )); + } + }; -template -struct scrBindArgument -{ - static TArg Get(fx::ScriptContext& cxt, int i) + template + struct scrBindMethod + { + + }; + + template + struct scrBindMethod { - if constexpr (std::is_trivial_v) + typedef TRet(TClass::*TFunc)(TArgs...); + + template + static TRet CallInternal(TClass* obj, TFunc func, fx::ScriptContext& context, std::index_sequence) { - return cxt.GetArgument(i); + return (obj->*func)( + scrBindArgument::Get(context, Is + 1)... + ); } - else + + static TRet Call(TClass* obj, TFunc func, fx::ScriptContext& context) { - scrObjectRef* ref = cxt.CheckArgument(i); + return CallInternal(obj, func, context, std::make_index_sequence()); + } + }; + + template + struct scrBindMethod + { + typedef TRet(*TFunc)(TClass*, TArgs...); - return msgpack::unpack(ref->data, ref->length).get().as(); + template + static TRet CallInternal(TClass* obj, TFunc func, fx::ScriptContext& context, std::index_sequence) + { + return func( + obj, + scrBindArgument::Get(context, Is + 1)... + ); } - } -}; -template -struct scrBindArgument>> -{ - using TArg = TPtr*; + static TRet Call(TClass* obj, TFunc func, fx::ScriptContext& context) + { + return CallInternal(obj, func, context, std::make_index_sequence()); + } + }; - static TArg Get(fx::ScriptContext& cxt, int i) + template + struct scrBindGlobalMethod { - // The shared pointer will be destroyed after returning, but the underlying object should still exist. - return g_ScrBindPool.FromHandle(cxt.GetArgument(i)).get(); - } -}; -template -struct scrBindArgument> -{ - using TArg = std::shared_ptr; + }; - static TArg Get(fx::ScriptContext& cxt, int i) + template + struct scrBindGlobalMethod { - return g_ScrBindPool.FromHandle(cxt.GetArgument(i)); - } -}; + typedef TRet(*TFunc)(TArgs...); -template -struct scrBindResult -{ - static void Set(fx::ScriptContext& cxt, const TArg& result) + template + static TRet CallInternal(TFunc func, fx::ScriptContext& context, std::index_sequence) + { + return func( + scrBindArgument::Get(context, Is)... + ); + } + + static TRet Call(TFunc func, fx::ScriptContext& context) + { + return CallInternal(func, context, std::make_index_sequence()); + } + }; + + template + struct scrBindCallResultSpec { - cxt.SetResult(result); - } -}; + typedef TRet(TClass::* TFunc)(Args...); -template -struct scrBindResult>> -{ - // Don't allow returning raw object pointers - static void Set(fx::ScriptContext& cxt, TPtr* ref) = delete; -}; + static void Call(TClass* obj, TFunc func, fx::ScriptContext& context) + { + scrBindResult::Set(context, scrBindMethod::Call(obj, func, context)); + } + }; -template -struct scrBindResult> -{ - using TArg = std::shared_ptr; + template + struct scrBindCallResultSpec + { + typedef void(TClass::* TFunc)(Args...); + + static void Call(TClass* obj, TFunc func, fx::ScriptContext& context) + { + scrBindMethod::Call(obj, func, context); + } + }; - static void Set(fx::ScriptContext& cxt, TArg ref) + template + struct scrBindCallStaticResultSpec { - cxt.SetResult(g_ScrBindPool.MakeHandle(ref)); - } -}; + typedef TRet(*TFunc)(TClass*, Args...); + + static void Call(TClass* obj, TFunc func, fx::ScriptContext& context) + { + scrBindResult::Set(context, scrBindMethod::Call(obj, func, context)); + } + }; + + template + struct scrBindCallStaticResultSpec + { + typedef void(*TFunc)(TClass*, Args...); + + static void Call(TClass* obj, TFunc func, fx::ScriptContext& context) + { + scrBindMethod::Call(obj, func, context); + } + }; + + template + struct scrBindCallGlobalResultSpec + { + typedef TRet(*TFunc)(Args...); + + static void Call(TFunc func, fx::ScriptContext& context) + { + scrBindResult::Set(context, scrBindGlobalMethod::Call(func, context)); + } + }; + + template + struct scrBindCallGlobalResultSpec + { + typedef void(*TFunc)(Args...); + + static void Call(TFunc func, fx::ScriptContext& context) + { + scrBindGlobalMethod::Call(func, context); + } + }; + + template + struct scrBindCallResult + { + + }; -template -struct scrBindFunctionBase; + template + struct scrBindCallResult + { + typedef TRet(TClass::* TFunc)(Args...); + + static void Call(TClass* obj, TFunc func, fx::ScriptContext& context) + { + scrBindCallResultSpec::Call(obj, func, context); + } + }; + + template + struct scrBindCallResult, TRet(TClass::*)(Args...)> + { + typedef TRet(TClass::* TFunc)(Args...); + + static void Call(std::shared_ptr* obj, TFunc func, fx::ScriptContext& context) + { + scrBindCallResultSpec::Call(obj->get(), func, context); + } + }; -template -struct scrBindFunctionBase, Args...> + template + struct scrBindCallResult + { + typedef TRet(TClass::* TFunc)(Args...) const; + + static void Call(TClass* obj, TFunc func, fx::ScriptContext& context) + { + scrBindCallResultSpec::Call(obj, reinterpret_cast(func), context); + } + }; + + template + struct scrBindCallResult, TRet(TClass::*)(Args...) const> + { + typedef TRet(TClass::* TFunc)(Args...) const; + + static void Call(std::shared_ptr* obj, TFunc func, fx::ScriptContext& context) + { + scrBindCallResultSpec::Call(obj->get(), reinterpret_cast(func), context); + } + }; + + template + struct scrBindCallResult + { + typedef TRet(* TFunc)(TClass*, Args...); + + static void Call(TClass* obj, TFunc func, fx::ScriptContext& context) + { + scrBindCallStaticResultSpec::Call(obj, func, context); + } + }; + + template + struct scrBindCallResult, TRet(*)(TClass*, Args...)> + { + typedef TRet(* TFunc)(TClass*, Args...); + + static void Call(std::shared_ptr* obj, TFunc func, fx::ScriptContext& context) + { + scrBindCallStaticResultSpec::Call(obj->get(), func, context); + } + }; + + template + struct scrBindCallGlobalResult + { + + }; + + template + struct scrBindCallGlobalResult + { + typedef TRet(*TFunc)(Args...); + + static void Call(TFunc func, fx::ScriptContext& context) + { + scrBindCallGlobalResultSpec::Call(func, context); + } + }; +} + +void SCRBIND_EXPORT scrBindAddSafePointer(void* classPtr); + +bool SCRBIND_EXPORT scrBindIsSafePointer(void* classPtr); + +typedef void(*scrBindNativeMethodStub)(fx::ScriptContext&, void*); + +fx::TNativeHandler SCRBIND_EXPORT scrBindCreateNativeMethodStub(scrBindNativeMethodStub, void*); + +template +class scrBindClassBase { - template - static auto Bind(const Func& func) +public: + // define a constructor for the class + template + TSelf& AddConstructor(const char* constructorName) { - return [func](fx::ScriptContext& context) + fx::ScriptEngine::RegisterNativeHandler(constructorName, [] (fx::ScriptContext& context) { - if (context.GetArgumentCount() != sizeof...(Args)) + if (context.GetArgumentCount() != scrBindFunc::GetArgumentCount() + 1) { - throw std::runtime_error("invalid argument count"); + return; } - const auto invoke = [&func, &context] + scrBindConstructor::Call(context); + + scrBindAddSafePointer(context.GetResult()); + }); + + return *static_cast(this); + } + + TSelf& AddDestructor(const char* destructorName) + { + fx::ScriptEngine::RegisterNativeHandler(destructorName, [](fx::ScriptContext& context) + { + if (context.GetArgumentCount() != 1) { - return std::invoke(func, scrBindArgument>::Get(context, (int)Is)...); - }; + return; + } + + auto ptr = context.GetArgument(0); + + if (!scrBindIsSafePointer(ptr)) + { + return; + } + + delete ptr; + }); + + return *static_cast(this); + } - using Result = decltype(invoke()); + template + TSelf& AddMethod(const char* methodName, TFunc method) + { + fx::ScriptEngine::RegisterNativeHandler(methodName, scrBindCreateNativeMethodStub([] (fx::ScriptContext& context, void* ufunc) + { + TFunc* udata = (TFunc*)ufunc; - if constexpr (std::is_same_v) + if (context.GetArgumentCount() != scrBindFunc::GetArgumentCount() + 1) { - invoke(); + return; } - else + + TClass* obj = context.GetArgument(0); + + if (!scrBindIsSafePointer(obj)) { - scrBindResult::Set(context, invoke()); + return; } - }; + + scrBindCallResult::Call(obj, *udata, context); + }, new TFunc(method))); + + return *static_cast(this); } }; -template -struct scrBindFunction - : scrBindFunctionBase, Args...> +template +void scrBindGlobal(const char* methodName, TFunc method) { + fx::ScriptEngine::RegisterNativeHandler(methodName, scrBindCreateNativeMethodStub([](fx::ScriptContext& context, void* ufunc) + { + TFunc* udata = (TFunc*)ufunc; -}; + if (context.GetArgumentCount() < scrBindFunc::GetArgumentCount()) + { + return; + } -template -void scrBindGlobal(const char* methodName, Result(*method)(Args...)) -{ - fx::ScriptEngine::RegisterNativeHandler(methodName, scrBindFunction::Bind(method)); + scrBindCallGlobalResult::Call(*udata, context); + }, new TFunc(method))); } template class scrBindClass + : public scrBindClassBase> { -public: - // define a constructor for the class - template - scrBindClass& AddConstructor(const char* constructorName) - { - fx::ScriptEngine::RegisterNativeHandler(constructorName, scrBindFunction::Bind([](const Args&... args) - { - return std::make_shared(args...); - })); - - return *this; - } +}; + +template +class scrBindClass> + : public scrBindClassBase, scrBindClass>> +{ +public: scrBindClass& AddDestructor(const char* destructorName) { - fx::ScriptEngine::RegisterNativeHandler(destructorName, scrBindFunction>::Bind([](std::shared_ptr pointer) + fx::ScriptEngine::RegisterNativeHandler(destructorName, [](fx::ScriptContext& context) { - g_ScrBindPool.ErasePointer(std::move(pointer)); - })); - - return *this; - } - - template - scrBindClass& AddMethod(const char* methodName, Result (TClass::*method)(Args...)) - { - fx::ScriptEngine::RegisterNativeHandler(methodName, scrBindFunction::Bind(method)); - - return *this; - } + if (context.GetArgumentCount() != 1) + { + return; + } - template - scrBindClass& AddMethod(const char* methodName, Result (TClass::*method)(Args...) const) - { - fx::ScriptEngine::RegisterNativeHandler(methodName, scrBindFunction::Bind(method)); + auto ptr = context.GetArgument*>(0); - return *this; - } + if (!scrBindIsSafePointer(ptr)) + { + return; + } - template - scrBindClass& AddMethod(const char* methodName, Result(*method)(TClass*, Args...)) - { - fx::ScriptEngine::RegisterNativeHandler(methodName, scrBindFunction::Bind(method)); + delete ptr; + }); return *this; } diff --git a/code/components/scrbind-base/src/scrBind.cpp b/code/components/scrbind-base/src/scrBind.cpp index bddb2d05f5..1db3b56509 100644 --- a/code/components/scrbind-base/src/scrBind.cpp +++ b/code/components/scrbind-base/src/scrBind.cpp @@ -9,16 +9,20 @@ #include "scrBind.h" #include "Hooking.h" -#include +void scrBindAddSafePointer(void* classPtr) +{ + +} -scrBindPool g_ScrBindPool{}; +bool scrBindIsSafePointer(void* classPtr) +{ + return true; +} -static InitFunction initFunction([]() +fx::TNativeHandler scrBindCreateNativeMethodStub(scrBindNativeMethodStub stub, void* udata) { -#ifndef IS_FXSERVER - Instance::Get()->OnShutdownSession.Connect([]() + return [stub, udata](fx::ScriptContext& context) { - g_ScrBindPool.Reset(); - }); -#endif -}); + stub(context, udata); + }; +} diff --git a/code/components/scrbind-formats/src/Drawable.cpp b/code/components/scrbind-formats/src/Drawable.cpp new file mode 100644 index 0000000000..afed06dd86 --- /dev/null +++ b/code/components/scrbind-formats/src/Drawable.cpp @@ -0,0 +1,292 @@ +/* + * This file is part of the CitizenFX project - http://citizen.re/ + * + * See LICENSE and MENTIONS in the root of the source tree for information + * regarding licensing. + */ + +#include "StdInc.h" +#include "scrBind.h" +#include + +// setup gtaDrawable format +#if defined(GTA_NY) +#define RAGE_FORMATS_GAME ny +#endif + +#include + +using namespace rage::RAGE_FORMATS_GAME; + +class ScriptShader : public fwRefCountable +{ + +}; + +class ScriptGeometry : public fwRefCountable +{ +private: + fwRefContainer m_shader; + + uint16_t* m_indices; + + uint32_t m_numIndices; + + void* m_vertices; + + uint32_t m_vertexStride; + + uint32_t m_vertexCount; + + uint32_t m_vertexUsageMask; + + uint64_t m_vertexTypeMask; + +public: + ScriptGeometry(); + + ~ScriptGeometry(); + + inline void SetShader(ScriptShader* shader) + { + if (!scrBindIsSafePointer(shader)) + { + return; + } + + m_shader = shader; + } + + inline void* AllocateIndices(uint32_t size) + { + if (m_indices) + { + delete[] m_indices; + } + + m_indices = new uint16_t[size]; + + scrBindAddSafePointer(m_indices); + + return m_indices; + } + + inline void* AllocateVertices(uint32_t count, uint32_t stride) + { + if (m_vertices) + { + delete[] m_vertices; + } + + m_vertices = new char[count * stride]; + + scrBindAddSafePointer(m_vertices); + + return m_vertices; + } + + inline void SetVertexFormat(uint32_t usageMask, uint32_t typeMaskLow, uint32_t typeMaskHigh) + { + m_vertexTypeMask = ((uint64_t)typeMaskHigh << 32) | typeMaskLow; + m_vertexUsageMask = usageMask; + } + + inline bool IsValid() + { + if (!m_shader.GetRef()) + { + return false; + } + + if (!m_indices) + { + return false; + } + + if (!m_vertices) + { + return false; + } + + return true; + } +}; + +class ScriptModel : public fwRefCountable +{ +private: + Vector4 m_geometryBounds; + + std::vector> m_geometries; + + uint32_t m_setFlags; + +#if 0 + Vector4 m_aabbMin; + Vector4 m_aabbMax; +#endif + +public: + ScriptModel(); + + inline void SetBounds(float x, float y, float z, float radius) + { + m_geometryBounds = Vector4(x, y, z, radius); + m_setFlags |= 1; + } + + inline ScriptGeometry* AddGeometry() + { + fwRefContainer geometry = new ScriptGeometry(); + m_geometries.push_back(geometry); + + // return pointer + scrBindAddSafePointer(geometry.GetRef()); + + return geometry.GetRef(); + } + + inline bool IsValid() + { + if ((m_setFlags & 1) != 1) + { + return false; + } + + if (m_geometries.size() == 0) + { + return false; + } + + return true; + } +}; + +ScriptModel::ScriptModel() + : m_setFlags(0) +{ + +} + +class ScriptLodGroup +{ +private: + Vector3 m_minBound; + Vector3 m_maxBound; + Vector3 m_center; + float m_radius; + + fwRefContainer m_models[4]; + + uint32_t m_setFlags; + +public: + ScriptLodGroup(); + + inline void SetMinBound(float x, float y, float z) + { + m_minBound = Vector3(x, y, z); + m_setFlags |= 1; + } + + inline void SetMaxBound(float x, float y, float z) + { + m_maxBound = Vector3(x, y, z); + m_setFlags |= 2; + } + + inline void SetCenter(float x, float y, float z) + { + m_center = Vector3(x, y, z); + m_setFlags |= 4; + } + + inline void SetRadius(float radius) + { + m_radius = radius; + m_setFlags |= 8; + } + + inline ScriptModel* CreateModel(int lod) + { + if (lod < 0 || lod >= _countof(m_models)) + { + return nullptr; + } + + m_models[lod] = new ScriptModel(); + + // return the pointer + ScriptModel* model = m_models[lod].GetRef(); + + scrBindAddSafePointer(model); + + return model; + } + + inline bool IsValid() + { + if ((m_setFlags & 15) != 15) + { + return false; + } + + return true; + } +}; + +ScriptLodGroup::ScriptLodGroup() + : m_radius(0.0f), m_setFlags(0) +{ + +} + +class ScriptDrawable +{ +private: + ScriptLodGroup m_lodGroup; + +public: + ScriptDrawable(); + ~ScriptDrawable(); + + inline ScriptLodGroup* GetLodGroup() + { + return &m_lodGroup; + } +}; + +ScriptDrawable::ScriptDrawable() +{ + scrBindAddSafePointer(&m_lodGroup); +} + +ScriptDrawable::~ScriptDrawable() +{ + // TODO: delete safe pointer +} + +static InitFunction initFunction([] () +{ + using namespace rage; + + scrBindClass() + .AddConstructor("CREATE_DRAWABLE") + .AddMethod("GET_DRAWABLE_LODGROUP", &ScriptDrawable::GetLodGroup); + + scrBindClass() + .AddMethod("SET_LODGROUP_BOUNDS_MIN", &ScriptLodGroup::SetMinBound) + .AddMethod("SET_LODGROUP_BOUNDS_MAX", &ScriptLodGroup::SetMaxBound) + .AddMethod("SET_LODGROUP_CENTER", &ScriptLodGroup::SetCenter) + .AddMethod("SET_LODGROUP_RADIUS", &ScriptLodGroup::SetRadius) + .AddMethod("CREATE_LODGROUP_MODEL", &ScriptLodGroup::CreateModel); + + /*scrEngine::OnScriptInit.Connect([] () + { + void* drawable = NativeInvoke::Invoke<0x33486661, void*>(1, 2, "lovely"); + + NativeInvoke::Invoke<0x7DFC57A4, int>(drawable, 1, 2, 3, 4, 5, 6); + + __debugbreak(); + }, 100);*/ +}); \ No newline at end of file diff --git a/code/components/voip-mumble/src/MumbleAudioOutput.cpp b/code/components/voip-mumble/src/MumbleAudioOutput.cpp index 8c56e697f6..4ea0b29609 100644 --- a/code/components/voip-mumble/src/MumbleAudioOutput.cpp +++ b/code/components/voip-mumble/src/MumbleAudioOutput.cpp @@ -1867,30 +1867,6 @@ struct scrBindArgument } }; -template <> -struct scrBindParent -{ - using type = lab::AudioBasicProcessorNode; -}; - -template<> -struct scrBindParent -{ - using type = lab::AudioBasicProcessorNode; -}; - -template<> -struct scrBindParent -{ - using type = lab::AudioNode; -}; - -template<> -struct scrBindParent -{ - using type = lab::AudioNode; -}; - static std::shared_ptr createBiquadFilterNode(lab::AudioContext* self) { return std::make_shared(); @@ -1908,7 +1884,7 @@ static std::shared_ptr getSource(lab::AudioContext* self) static InitFunction initFunctionScript([]() { - scrBindClass() + scrBindClass>() .AddMethod("AUDIOCONTEXT_CONNECT", &lab::AudioContext::connect) .AddMethod("AUDIOCONTEXT_DISCONNECT", &lab::AudioContext::disconnect) .AddMethod("AUDIOCONTEXT_GET_CURRENT_TIME", &lab::AudioContext::currentTime) @@ -1917,7 +1893,7 @@ static InitFunction initFunctionScript([]() .AddMethod("AUDIOCONTEXT_CREATE_BIQUADFILTERNODE", &createBiquadFilterNode) .AddMethod("AUDIOCONTEXT_CREATE_WAVESHAPERNODE", &createWaveShaperNode); - scrBindClass() + scrBindClass>() .AddMethod("BIQUADFILTERNODE_SET_TYPE", &lab::BiquadFilterNode::setType) .AddMethod("BIQUADFILTERNODE_Q", &lab::BiquadFilterNode::q) // needs msgpack wrappers for in/out and a weird lock thing @@ -1927,13 +1903,13 @@ static InitFunction initFunctionScript([]() .AddMethod("BIQUADFILTERNODE_DETUNE", &lab::BiquadFilterNode::detune) .AddDestructor("BIQUADFILTERNODE_DESTROY"); - scrBindClass() + scrBindClass>() // this is if 0'd? //.AddMethod("WAVESHAPERNODE_GET_CURVE", &lab::WaveShaperNode::curve) .AddMethod("WAVESHAPERNODE_SET_CURVE", &lab::WaveShaperNode::setCurve) .AddDestructor("WAVESHAPERNODE_DESTROY"); - scrBindClass() + scrBindClass>() .AddMethod("AUDIOPARAM_SET_VALUE", &lab::AudioParam::setValue) .AddMethod("AUDIOPARAM_SET_VALUE_AT_TIME", &lab::AudioParam::setValueAtTime) .AddMethod("AUDIOPARAM_SET_VALUE_CURVE_AT_TIME", &lab::AudioParam::setValueCurveAtTime) diff --git a/ext/natives/codegen_out_native_lua.lua b/ext/natives/codegen_out_native_lua.lua index 7a3dfde573..4be8922f6c 100644 --- a/ext/natives/codegen_out_native_lua.lua +++ b/ext/natives/codegen_out_native_lua.lua @@ -82,9 +82,7 @@ local function isSafeNative(native) local nativeType = arg.type.nativeType or 'Any' if arg.pointer then - if arg.type.name == 'Any' then -- nativeType of Any is int - safe = false - elseif singlePointer then + if singlePointer then safe = false elseif nativeType ~= "vector3" and not safeArguments[nativeType] then safe = false diff --git a/ext/natives/codegen_out_pointer_args.lua b/ext/natives/codegen_out_pointer_args.lua index 0848d6a85c..da45e00176 100644 --- a/ext/natives/codegen_out_pointer_args.lua +++ b/ext/natives/codegen_out_pointer_args.lua @@ -1,159 +1,92 @@ -PAS_ARG_POINTER = 0x80000000 -PAS_ARG_STRING = 0x40000000 -PAS_ARG_BUFFER = 0x20000000 -PAS_ARG_SIZE = 0x1FFFFFFF - -PAS_RET_VOID = 0 -PAS_RET_INT = 1 -PAS_RET_FLOAT = 2 -PAS_RET_LONG = 3 -PAS_RET_VECTOR3 = 4 -PAS_RET_STRING = 5 -PAS_RET_SCRSTRING = 6 -PAS_RET_SCROBJECT = 7 - -print('void PointerArgumentSafety_Impl()\n{') - -local typeSizes = { - ['Vector3'] = 24, -} - -local paramOverrides = { - ['CFX/SET_RUNTIME_TEXTURE_ARGB_DATA/buffer'] = PAS_ARG_POINTER | PAS_ARG_BUFFER, - ['CFX/SET_STATE_BAG_VALUE/valueData'] = PAS_ARG_POINTER | PAS_ARG_BUFFER, - ['CFX/TRIGGER_EVENT_INTERNAL/eventPayload'] = PAS_ARG_POINTER | PAS_ARG_BUFFER, - ['CFX/TRIGGER_LATENT_SERVER_EVENT_INTERNAL/eventPayload'] = PAS_ARG_POINTER | PAS_ARG_BUFFER, - ['CFX/TRIGGER_SERVER_EVENT_INTERNAL/eventPayload'] = PAS_ARG_POINTER | PAS_ARG_BUFFER, - - -- Matrix44 - ['CFX/DRAW_GIZMO/matrixPtr'] = PAS_ARG_POINTER | 64, - ['CFX/GET_MAPDATA_ENTITY_MATRIX/matrixPtr'] = PAS_ARG_POINTER | 64, +local function hasPointerArg(v) + for _, v in ipairs(v.arguments) do + if v.pointer or v.type.name == 'Any' or v.type.name == 'charPtr' then + return true + end + end + + return false +end - -- scrArray - ['PED/GET_PED_NEARBY_PEDS/sizeAndPeds'] = PAS_ARG_POINTER, - ['PED/GET_PED_NEARBY_VEHICLES/sizeAndVehs'] = PAS_ARG_POINTER, - ['SCRIPT/TRIGGER_SCRIPT_EVENT/eventData'] = PAS_ARG_POINTER, - ['SCRIPT/_TRIGGER_SCRIPT_EVENT_2/eventData'] = PAS_ARG_POINTER, - ['VEHICLE/_GET_ALL_VEHICLES/vehArray'] = PAS_ARG_POINTER, -} +local function hasVoid(v) + return v.returns == nil +end -local seen_types = {} -local num_types = 0 +print('void PointerArgumentSafety_Impl()\n{') for _, v in pairs(_natives) do - if matchApiSet(v) then - local nullify = false - local pmask = 0 - local i = 0 - - -- Uses raw pointers - if v.ns == 'DATAFILE' then - nullify = true - end + local printed = false - local rtype = PAS_RET_VOID - - if v.returns then - if (v.name == 'LOAD_RESOURCE_FILE') then - rtype = PAS_RET_SCRSTRING - elseif (v.returns.name == 'long') then - rtype = PAS_RET_LONG - elseif (v.returns.nativeType == 'int') or (v.returns.nativeType == 'bool') then - rtype = PAS_RET_INT - elseif v.returns.name == 'float' then - rtype = PAS_RET_FLOAT - elseif v.returns.name == 'Vector3' then - rtype = PAS_RET_VECTOR3 - elseif v.returns.name == 'charPtr' then - rtype = PAS_RET_STRING - elseif v.returns.name == 'AnyPtr' then - nullify = true -- DATAFILE/DATAOBJECT - elseif v.returns.name == 'object' then - rtype = PAS_RET_SCROBJECT - else - print('INVALID RETURN TYPE!', v.returns.name) - end - end - - local args = { #v.arguments, 0, rtype } - local undocumented = true - - -- Someone named this, so it's probably got roughly the right types. - if v.name ~= ('0x%016X'):format(v.hash) then - undocumented = false - end + if matchApiSet(v) and (hasPointerArg(v) or hasVoid(v)) then + local avs = '' + local i = 0 for _, a in ipairs(v.arguments) do - local argx = 0 - local override = paramOverrides[('%s/%s/%s'):format(v.ns, v.name, a.name)] - - if override ~= nil then - argx = override - elseif (a.name == 'networkHandle') then - argx = PAS_ARG_POINTER -- These are incorrectly labelled as intPtr - elseif (a.type.name == 'charPtr') or (a.type.name == 'func') then - argx = PAS_ARG_POINTER | PAS_ARG_STRING - elseif a.pointer then - argx = PAS_ARG_POINTER - - if a.type.name == 'Any' then - if v.name:match('^_?GET_') then -- only allow GET_* natives - -- Unknown size, use IsolatedBuffer - else - nullify = true - end - else - argx = argx | (typeSizes[a.type.name] or 4) - end - else - -- Not a pointer + -- 'Any*' can't be guaranteed-safe + if a.pointer and a.type.name == 'Any' and not v.name:match('^_?GET_') then -- hackaround to allow GET_ dataview prevalent-stuff + avs = avs .. ('\tNullifyAny(cxt->GetArgument(%d)); // Any* %s\n'):format(i, a.name) end - if a.type.name ~= 'Any' then - undocumented = false + if a.pointer or a.type.name == 'Any' or a.type.name == 'charPtr' then + avs = avs .. ('\tif (!ValidateArg(cxt->GetArgument(%d))) { return; }\n'):format(i) end - if (argx & PAS_ARG_POINTER) ~= 0 then - pmask = pmask | (1 << i) - end - - table.insert(args, argx) i = i + 1 end - args[2] = pmask - - if undocumented then - args = { 0, 0, rtype } -- Treat all the arguments as unknown + local rvs = '' + if hasVoid(v) then + rvs = rvs .. '\tNullifyVoid(cxt);\n' end - if nullify then - args = { 0xFFFFFFFF } -- Disable this native entirely + avs = avs:gsub("%s+$", "") + rvs = rvs:gsub("%s+$", "") + + if avs ~= '' then + avs = avs .. '\n' end - a = '' + if rvs ~= '' then + rvs = '\n' .. rvs + end - for k, v in pairs(args) do - if a ~= '' then - a = a .. ', ' - end + local a = (([[ +// NAME +static auto nh_HASH = rage::scrEngine::GetNativeHandler(HASH); +rage::scrEngine::RegisterNativeHandler(HASH, [](rage::scrNativeCallContext* cxt) +{ +ARG_VALIDATORS\tnh_HASH(cxt);RESULT_VALIDATORS +}); + ]]):gsub('\\t', "\t"):gsub('HASH', v.hash):gsub("ARG_VALIDATORS", avs):gsub("RESULT_VALIDATORS", rvs)):gsub('NAME', v.ns .. '/' .. v.name):gsub('\n', '\n\t'):gsub('^', '\t') + + print(a) + printed = true + end - a = a .. ('0x%08X'):format(v) + if matchApiSet(v) and v.returns then + local returnType = '' + + if v.returns.nativeType == 'string' then + returnType = 'char*' + elseif v.returns.nativeType == 'float' then + returnType = 'float' + elseif v.returns.nativeType == 'bool' then + returnType = 'bool' + elseif v.returns.nativeType == 'int' then + returnType = 'int' + elseif v.returns.nativeType == 'Any' and not v.returns.pointer then + returnType = 'int' + elseif v.returns.nativeType == 'Vector3' then + returnType = 'int' end - local tname = seen_types[a] + if returnType ~= '' then + if not printed then + print(("\t// %s (result cleaner only)"):format((v.ns or '') .. '/' .. v.name)) + end - if tname == nil then - tname = ('nt_%d'):format(num_types) - num_types = num_types + 1 - seen_types[a] = tname - print(('\tstatic const uint32_t %s[] = { %s };'):format(tname, a)) - print() + print(("\tAddResultCleaner(%s, ResultCleaner<%s>);\n"):format(v.hash, returnType)) end - - print(('\t// %s/%s'):format(v.ns, v.name)) - print(('\tRegisterNativeTypeInfo(%s, %s);'):format(v.hash, tname)) - print() end end