diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaDefs.h index d533baef6c1..de1a300220b 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaDefs.h +++ b/Client/mods/deathmatch/logic/luadefs/CLuaDefs.h @@ -64,4 +64,51 @@ class CLuaDefs static CClientDFFManager* m_pDFFManager; static CClientColModelManager* m_pColModelManager; static CRegisteredCommands* m_pRegisteredCommands; -}; \ No newline at end of file + +protected: + // Old style: Only warn on failure. This should + // not be used for new functions. ReturnOnError + // must be a value to use as result on invalid argument + template + static inline int ArgumentParserWarn(lua_State* L) + { + return CLuaFunctionParser()(L, m_pScriptDebugging); + } + + // Special case for overloads + // This combines multiple functions into one (via CLuaOverloadParser) + template + static inline int ArgumentParserWarn(lua_State* L) + { + // Pad functions to have the same number of parameters by + // filling both up to the larger number of parameters with dummy_type arguments + using PaddedFunctionA = pad_func_with_func; + using PaddedFunctionB = pad_func_with_func; + // Combine functions + using Overload = CLuaOverloadParser; + + return ArgumentParserWarn(L); + } + + // New style: hard error on usage mistakes + template + static inline int ArgumentParser(lua_State* L) + { + return CLuaFunctionParser()(L, m_pScriptDebugging); + } + + // Special case for overloads + // This combines multiple functions into one (via CLuaOverloadParser) + template + static inline int ArgumentParser(lua_State* L) + { + // Pad functions to have the same number of parameters by + // filling both up to the larger number of parameters with dummy_type arguments + using PaddedFunctionA = pad_func_with_func; + using PaddedFunctionB = pad_func_with_func; + // Combine functions + using Overload = CLuaOverloadParser; + + return ArgumentParser(L); + } +}; diff --git a/Server/mods/deathmatch/StdInc.h b/Server/mods/deathmatch/StdInc.h index 801dc017ea3..d9f5672ecdb 100644 --- a/Server/mods/deathmatch/StdInc.h +++ b/Server/mods/deathmatch/StdInc.h @@ -113,6 +113,9 @@ struct SAclRequest; #include "packets/CServerInfoSyncPacket.h" #include "packets/CDiscordJoinPacket.h" +// has to be included early to prevent "unknown type name 'CRemoteCall'" in CLuaFunctionParser.h +#include "CRemoteCalls.h" + // Lua function definitions #include "luadefs/CLuaElementDefs.h" #include "luadefs/CLuaAccountDefs.h" @@ -142,9 +145,6 @@ struct SAclRequest; #include "luadefs/CLuaWaterDefs.h" #include "luadefs/CLuaWorldDefs.h" -// has to be included before CLuaFunctionParseHelpers to prevent "invalid use of incomplete type ‘class CRemoteCalls´ -#include "CRemoteCalls.h" - // Lua includes #include "lua/LuaCommon.h" #include "lua/CLuaMain.h" diff --git a/Server/mods/deathmatch/logic/CGame.h b/Server/mods/deathmatch/logic/CGame.h index 47c56886099..d93fd914f46 100644 --- a/Server/mods/deathmatch/logic/CGame.h +++ b/Server/mods/deathmatch/logic/CGame.h @@ -57,12 +57,12 @@ class CGame; #include "lua/CLuaManager.h" #include "CLightsyncManager.h" +#include "CBanManager.h" // Forward declarations class ASE; class CAccessControlListManager; class CAccountManager; -class CBanManager; class CBlipManager; class CClock; class CColManager; diff --git a/Server/mods/deathmatch/logic/luadefs/CLuaDefs.h b/Server/mods/deathmatch/logic/luadefs/CLuaDefs.h index 0fb4473690a..bce533c5dde 100644 --- a/Server/mods/deathmatch/logic/luadefs/CLuaDefs.h +++ b/Server/mods/deathmatch/logic/luadefs/CLuaDefs.h @@ -31,6 +31,7 @@ #include "../CVehicleManager.h" #include "../CRegistry.h" #include "../CDatabaseManager.h" +#include // Used by script handlers to verify elements #define SCRIPT_VERIFY_BLIP(blip) (m_pBlipManager->Exists(blip)&&!blip->IsBeingDeleted()) @@ -84,4 +85,51 @@ class CLuaDefs static CResourceManager* m_pResourceManager; static CAccessControlListManager* m_pACLManager; static CMainConfig* m_pMainConfig; + +protected: + // Old style: Only warn on failure. This should + // not be used for new functions. ReturnOnError + // must be a value to use as result on invalid argument + template + static inline int ArgumentParserWarn(lua_State* L) + { + return CLuaFunctionParser()(L, m_pScriptDebugging); + } + + // Special case for overloads + // This combines multiple functions into one (via CLuaOverloadParser) + template + static inline int ArgumentParserWarn(lua_State* L) + { + // Pad functions to have the same number of parameters by + // filling both up to the larger number of parameters with dummy_type arguments + using PaddedFunctionA = pad_func_with_func; + using PaddedFunctionB = pad_func_with_func; + // Combine functions + using Overload = CLuaOverloadParser; + + return ArgumentParserWarn(L); + } + + // New style: hard error on usage mistakes + template + static inline int ArgumentParser(lua_State* L) + { + return CLuaFunctionParser()(L, m_pScriptDebugging); + } + + // Special case for overloads + // This combines multiple functions into one (via CLuaOverloadParser) + template + static inline int ArgumentParser(lua_State* L) + { + // Pad functions to have the same number of parameters by + // filling both up to the larger number of parameters with dummy_type arguments + using PaddedFunctionA = pad_func_with_func; + using PaddedFunctionB = pad_func_with_func; + // Combine functions + using Overload = CLuaOverloadParser; + + return ArgumentParser(L); + } }; diff --git a/Shared/mods/deathmatch/logic/lua/CLuaFunctionParser.h b/Shared/mods/deathmatch/logic/lua/CLuaFunctionParser.h new file mode 100644 index 00000000000..f126a3f49dc --- /dev/null +++ b/Shared/mods/deathmatch/logic/lua/CLuaFunctionParser.h @@ -0,0 +1,446 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ +#pragma once + +#include +#include +#include +#include "lua/CLuaOverloadParser.h" +#include "lua/CLuaFunctionParseHelpers.h" +#include "lua/CLuaStackChecker.h" +#include "lua/LuaBasic.h" + +struct CLuaFunctionParserBase +{ + std::size_t iIndex = 1; + std::string strError = ""; + std::string strErrorFoundType = ""; + + // Translates a variant type to a list of names separated by slashes + // std::variant => bool/int/float + template + inline void TypeToNameVariant(SString& accumulator) + { + using param = typename is_variant::param1_t; + if (accumulator.length() == 0) + accumulator = TypeToName(); + else + accumulator += "/" + TypeToName(); + + if constexpr (is_variant::count != 1) + return TypeToNameVariant::rest_t>(accumulator); + } + + template + inline SString TypeToName() + { + if constexpr (std::is_same_v || std::is_same_v) + return "string"; + else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v) + return "number"; + else if constexpr (std::is_same_v) + return "boolean"; + else if constexpr (std::is_enum_v) + return "enum"; + else if constexpr (is_specialization::value) + { + using param_t = typename is_specialization::param_t; + return TypeToName(); + } + else if constexpr (is_2specialization::value) + return "table"; + else if constexpr (is_5specialization::value) + return "table"; + else if constexpr (std::is_same_v) + return "function"; + else if constexpr (std::is_same_v) + return ""; // not reachable + else if constexpr (is_variant::value) + { + SString strTypes; + TypeToNameVariant(strTypes); + return strTypes; + } + else if constexpr (std::is_pointer_v && std::is_class_v>) + return GetClassTypeName((T)0); + else if constexpr (std::is_same_v) + return ""; + } + + // Reads the parameter type (& value in some cases) at a given index + // For example a 42 on the Lua stack is returned as 'number (42)' + static SString ReadParameterAsString(lua_State* L, std::size_t index) + { + switch (lua_type(L, index)) + { + case LUA_TNUMBER: + return SString("number (%d)", lua_tonumber(L, index)); + case LUA_TSTRING: + { + std::size_t iLen; + const char* szValue = lua_tolstring(L, index, &iLen); + std::string strValue(szValue, iLen); + if (strValue.length() > 10) + { + // Limit to 10 characters + strValue.resize(10); + strValue[9] = '.'; + strValue[8] = '.'; + strValue[7] = '.'; + } + // Avoid printing binary data + if (std::find_if(strValue.begin(), strValue.end(), [](char ch) { return !(std::isprint(ch)); }) != strValue.end()) + return "string"; + else + { + return SString("string (\"%s\")", strValue.c_str()); + } + } + case LUA_TBOOLEAN: + return SString("boolean (%s)", lua_toboolean(L, index) == 1 ? "true" : "false"); + case LUA_TNIL: + return "nil"; + case LUA_TNONE: + return "none"; + case LUA_TTABLE: + return "table"; + case LUA_TFUNCTION: + return "function"; + case LUA_TTHREAD: + return "coroutine"; + case LUA_TUSERDATA: + return GetUserDataClassName(*((void**)lua_touserdata(L, index)), L); + case LUA_TLIGHTUSERDATA: + return GetUserDataClassName(lua_touserdata(L, index), L); + } + return ""; + } + + // Pop should remove a T from the Lua Stack after verifying that it is a valid type + template + inline T Pop(lua_State* L, std::size_t& index) + { + if (!TypeMatch(L, index)) + { + SString strReceived = ReadParameterAsString(L, index); + SString strExpected = TypeToName(); + SString strMessage("Bad argument @ '%s' [Expected %s at argument %d, got %s]", lua_tostring(L, lua_upvalueindex(1)), strExpected.c_str(), index, + strReceived.c_str()); + strError = strMessage; + return T{}; + } + return PopUnsafe(L, index); + } + + // Special type matcher for variants. Returns -1 if the type does not match + // returns n if the nth type of the variant matches + template + inline int TypeMatchVariant(lua_State* L, std::size_t index) + { + // If the variant is empty, we have exhausted all options + // The type therefore doesn't match the variant + if constexpr (std::is_same_v>) + return -1; + else + { + // Try to match the first type of the variant + // If it matches, we've found our index + using first_t = typename is_variant::param1_t; + using next_t = typename is_variant::rest_t; + if (TypeMatch(L, index)) + return 0; + + // Else try the remaining types of the variant + int iResult = TypeMatchVariant(L, index); + if (iResult == -1) + return -1; + return 1 + iResult; + } + } + + // TypeMatch should return true if the value on top of the Lua stack can be popped via + // PopUnsafe. This must accurately reflect the associated PopUnsafe. Note that TypeMatch + // should only check for obvious type violations (e.g. false is not a string) but not + // for internal type errors (passing a vehicle to a function expecting a ped) + template + inline bool TypeMatch(lua_State* L, std::size_t index) + { + int iArgument = lua_type(L, index); + // primitive types + if constexpr (std::is_same_v || std::is_same_v) + return (iArgument == LUA_TSTRING || iArgument == LUA_TNUMBER); + if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v) + return (iArgument == LUA_TSTRING || iArgument == LUA_TNUMBER); + if constexpr (std::is_same_v) + return (iArgument == LUA_TBOOLEAN); + + // Advanced types + // Enums are represented as strings to Lua + if constexpr (std::is_enum_v) + return iArgument == LUA_TSTRING; + + // std::optional is used for optional parameters + // which may also be in the middle of a parameter list + // therefore it is always valid to attempt to read an + // optional + if constexpr (is_specialization::value) + return true; + + // std::vector is used for arrays built from tables + if constexpr (is_2specialization::value) + return iArgument == LUA_TTABLE; + + // std::unordered_map is used for maps built from tables + if constexpr (is_5specialization::value) + return iArgument == LUA_TTABLE; + + // CLuaFunctionRef is used for functions + if constexpr (std::is_same_v) + return iArgument == LUA_TFUNCTION; + + // lua_State* can be taken as first argument of any function + if constexpr (std::is_same_v) + return index == 0; + + // variants can be used by any of the underlying types + // thus recursively use this function + if constexpr (is_variant::value) + return TypeMatchVariant(L, index) != -1; + + // Catch all for class pointer types, assume all classes are valid script entities + // and can be fetched from a userdata + if constexpr (std::is_pointer_v && std::is_class_v>) + return iArgument == LUA_TUSERDATA || iArgument == LUA_TLIGHTUSERDATA; + + // dummy type is used as overload extension if one overload has fewer arguments + // thus it is only allowed if there are no further args on the Lua side + if constexpr (std::is_same_v) + return iArgument == LUA_TNONE; + } + + // Special PopUnsafe for variants + template + inline T PopUnsafeVariant(lua_State* L, std::size_t& index, int vindex) + { + // As std::variant<> cannot be constructed, we simply return the first value + // in the error case. This is actually unreachable in the regular path, + // due to TypeMatch making sure that vindex < is_variant::count + if constexpr (is_variant::count == currIndex) + { + using type_t = typename is_variant::param1_t; + return type_t{}; + } + else + { + // If we haven't reached the target index go to the next type + if (vindex != currIndex) + return PopUnsafeVariant(L, index, vindex); + + // Pop the actual type + using type_t = std::remove_reference_t(T{}))>; + return PopUnsafe(L, index); + } + } + + template + inline T PopUnsafe(lua_State* L, std::size_t& index) + { + // Expect no change in stack size + LUA_STACK_EXPECT(0); + // the dummy type is not read from Lua + if constexpr (std::is_same_v) + return dummy_type{}; + // primitive types are directly popped + else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v) + return lua::PopPrimitive(L, index); + // floats/doubles may not be NaN + else if constexpr (std::is_same_v || std::is_same_v) + { + T value = lua::PopPrimitive(L, index); + if (std::isnan(value)) + { + SString strMessage("Bad argument @ '%s' [Expected number at argument %d, got NaN]", lua_tostring(L, lua_upvalueindex(1)), index); + strError = strMessage; + } + return value; + } + else if constexpr (std::is_enum_v) + { + // Enums are considered strings in Lua + std::string strValue = lua::PopPrimitive(L, index); + T eValue; + if (StringToEnum(strValue, eValue)) + return eValue; + else + { + SString strReceived = ReadParameterAsString(L, index); + SString strExpected = GetEnumTypeName((T)0); + SString strMessage("Bad argument @ '%s' [Expected %s at argument %d, got %s]", lua_tostring(L, lua_upvalueindex(1)), strExpected.c_str(), index, + strReceived.c_str()); + strError = strMessage; + return static_cast(0); + } + } + else if constexpr (is_specialization::value) + { + // optionals may either type match the desired value, or be nullopt + using param = typename is_specialization::param_t; + if (TypeMatch(L, index)) + return PopUnsafe(L, index); + else + return std::nullopt; + } + + else if constexpr (is_2specialization::value) // 2 specialization due to allocator + { + using param = typename is_2specialization::param1_t; + T vecData; + lua_pushnil(L); /* first key */ + while (lua_next(L, index) != 0) + { + if (!TypeMatch(L, -1)) + { + // skip + lua_pop(L, 1); + continue; + } + + std::size_t i = -1; + vecData.emplace_back(PopUnsafe(L, i)); + lua_pop(L, 1); // drop value, keep key for lua_next + } + ++index; + return vecData; + } + else if constexpr (is_5specialization::value) + { + using key_t = typename is_5specialization::param1_t; + using value_t = typename is_5specialization::param2_t; + T map; + lua_pushnil(L); /* first key */ + while (lua_next(L, index) != 0) + { + if (!TypeMatch(L, -1) || !TypeMatch(L, -2)) + { + // skip + lua_pop(L, 1); + continue; + } + + std::size_t i = -2; + auto k = PopUnsafe(L, i); + auto v = PopUnsafe(L, i); + map.emplace(std::move(k), std::move(v)); + lua_pop(L, 1); // drop value, keep key for lua_next + } + ++index; + return map; + } + else if constexpr (std::is_same_v) + { + return luaM_toref(L, index++); + } + else if constexpr (std::is_same_v) + return L; + // variants can be used by any of the underlying types + // thus recursively use this function + else if constexpr (is_variant::value) + { + int iMatch = TypeMatchVariant(L, index); + return PopUnsafeVariant(L, index, iMatch); + } + + // Catch all for class pointer types, assume all classes are valid script entities + // and can be fetched from a userdata + else if constexpr (std::is_pointer_v && std::is_class_v>) + { + bool isLightUserData = lua_type(L, index) == LUA_TLIGHTUSERDATA; + void* pValue = lua::PopPrimitive(L, index); + using class_t = std::remove_pointer_t; + auto result = + isLightUserData ? UserDataCast((class_t*)0, pValue, L) : UserDataCast((class_t*)0, *reinterpret_cast(pValue), L); + if (result == nullptr) + { + SString strReceived = isLightUserData ? GetUserDataClassName(pValue, L) : GetUserDataClassName(*(void**)pValue, L); + SString strExpected = GetClassTypeName((T)0); + SString strMessage("Bad argument @ '%s' [Expected %s at argument %d, got %s]", lua_tostring(L, lua_upvalueindex(1)), + strExpected.c_str(), index, strReceived.c_str()); + strError = strMessage; + return nullptr; + } + return static_cast(result); + } + } +}; + +template +struct CLuaFunctionParser +{ +}; + +template Ret> +struct CLuaFunctionParser : CLuaFunctionParserBase +{ + template + inline auto Call(lua_State* L, Params&&... ps) + { + if (strError.length() != 0) + { + return -1; + } + if constexpr (sizeof...(Params) == sizeof...(Args)) + { + if constexpr (std::is_same_v) + { + Func(std::forward(ps)...); + return 0; + } + else + { + return lua::Push(L, Func(std::forward(ps)...)); + } + } + else + { + return Call(L, ps..., Pop::type>(L, iIndex)); + } + } + + inline int operator()(lua_State* L, CScriptDebugging* pScriptDebugging) + { + int iResult = 0; + try + { + iResult = Call(L); + } + catch (std::invalid_argument& e) + { + // This exception can be thrown from the called function + // as an additional way to provide further argument errors + strError = e.what(); + } + if (strError.length() != 0) + { + if constexpr (ErrorOnFailure) + { + luaL_error(L, strError.c_str()); + } + else + { + pScriptDebugging->LogCustom(L, strError.c_str()); + lua::Push(L, ReturnOnFailure); + } + return 1; + } + return iResult; + } +}; diff --git a/Shared/mods/deathmatch/logic/lua/CLuaOverloadParser.h b/Shared/mods/deathmatch/logic/lua/CLuaOverloadParser.h new file mode 100644 index 00000000000..a8883afbd63 --- /dev/null +++ b/Shared/mods/deathmatch/logic/lua/CLuaOverloadParser.h @@ -0,0 +1,90 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ +#pragma once + +#include +#include +#include + +template +struct CLuaOverloadParser +{ +}; + +template Ret, auto (*Func2)(Args2...)->Ret2> +struct CLuaOverloadParser +{ +private: + enum class ChosenFunction + { + FUNCA, + FUNCB, + COMMON, + FAIL + }; + + template + static ChosenFunction MakeAllChoice(std::variant var, Us... us) + { + ChosenFunction result = MakeChoice(var); + if constexpr (sizeof...(Us) == 0) + return result; + else + { + ChosenFunction rest = MakeAllChoice(us...); + // If types match for overload X, use overload X + if (result == rest) + return result; + // If types don't match, check if this variable matches + // both. If so, use the other type + else if (result == ChosenFunction::COMMON) + return rest; + // If the rest matches both overloads, use the current one + else if (rest == ChosenFunction::COMMON) + return result; + // result != rest && result != 0 && rest != 0 + // Type error + return ChosenFunction::FAIL; + } + } + + // Chose an overload that the Nth paramter matches + template + static ChosenFunction MakeChoice(std::variant var) + { + return std::visit( + [](auto&& f) { + using ft = decltype(f); + if constexpr (!std::is_convertible_v>) + return ChosenFunction::FUNCB; // if it cannot match A, B it is + if constexpr (!std::is_convertible_v>) + return ChosenFunction::FUNCA; // if it cannot match B, A it is + return ChosenFunction::COMMON; // Both match + }, + var); + } + +public: + typename common_variant::type + static Call(typename common_variant::type... args) + { + ChosenFunction choice = MakeAllChoice<0>(args...); + if (choice == ChosenFunction::FUNCA) + return Func(std::get(args)...); + else if (choice == ChosenFunction::FUNCB) + return Func2(std::get(args)...); + else if (choice == ChosenFunction::FAIL) + { + // User provided incorrect arguments + throw std::invalid_argument("Overload resolution failed."); + } + // If this exception is thrown, there is a bug in the overload, as they are not unique + throw std::invalid_argument("MTA:SA Bug. Please file an issue at https://git.io/blue-issues with the parameters used for calling."); + } +}; diff --git a/Shared/mods/deathmatch/logic/lua/CLuaStackChecker.h b/Shared/mods/deathmatch/logic/lua/CLuaStackChecker.h new file mode 100644 index 00000000000..10b457db61c --- /dev/null +++ b/Shared/mods/deathmatch/logic/lua/CLuaStackChecker.h @@ -0,0 +1,56 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ +#pragma once +#include +/* + * Simple utility to verify the Lua stack. + * The general idea is to simply write + * LUA_STACK_EXPECT(N); + * at the beginning of a function, where N + * is the expected amount of change in the Lua + * stack size. + * + * As an example: A __index metamethod receives + * two parameters on the stack and returns a + * single value. Therefor each __index + * implementation should decrease the Lua stack + * size by one. Therefore write: + * LUA_STACK_EXPECT(-1); + */ +namespace lua +{ + struct CLuaStackChecker + { + lua_State* L; + int m_iTop = 0; + int m_iExpected = 0; + CLuaStackChecker(lua_State* L, int expected) : L(L) + { + m_iTop = lua_gettop(L); + m_iExpected = expected; + } + + ~CLuaStackChecker() noexcept(false) + { + int iNewtop = lua_gettop(L); + int iChange = iNewtop - m_iTop; + if (iChange != m_iExpected) + { + printf("Lua Stack Error: top should be %d is %d (change should be %d is %d)\n", m_iTop + m_iExpected, iNewtop, m_iExpected, iChange); + throw std::runtime_error("Lua Stack Error"); + } + } + }; +} // namespace lua + +#ifdef MTA_DEBUG +#define LUA_STACK_EXPECT(i) lua::CLuaStackChecker invalidHiddenName(L, i) +#else +#define LUA_STACK_EXPECT(i) +#endif diff --git a/Shared/mods/deathmatch/logic/lua/LuaBasic.cpp b/Shared/mods/deathmatch/logic/lua/LuaBasic.cpp new file mode 100644 index 00000000000..8a99072e661 --- /dev/null +++ b/Shared/mods/deathmatch/logic/lua/LuaBasic.cpp @@ -0,0 +1,73 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ +#include +#include "LuaBasic.h" + +namespace lua +{ + template <> + std::string PopPrimitive(lua_State* L, std::size_t& index) + { + uint uiLength = lua_strlen(L, index); + std::string outValue; + outValue.assign(lua_tostring(L, index++), uiLength); + return outValue; + } + + + template <> + std::string_view PopPrimitive(lua_State* L, std::size_t& index) + { + uint uiLength = lua_strlen(L, index); + std::string_view outValue(lua_tostring(L, index++), uiLength); + return outValue; + } + + template <> + int PopPrimitive(lua_State* L, std::size_t& index) + { + return static_cast(lua_tonumber(L, index++)); + } + + template <> + unsigned int PopPrimitive(lua_State* L, std::size_t& index) + { + return static_cast(lua_tonumber(L, index++)); + } + + template <> + uint64_t PopPrimitive(lua_State* L, std::size_t& index) + { + return static_cast(lua_tonumber(L, index++)); + } + + template <> + float PopPrimitive(lua_State* L, std::size_t& index) + { + return static_cast(lua_tonumber(L, index++)); + } + + template <> + double PopPrimitive(lua_State* L, std::size_t& index) + { + return static_cast(lua_tonumber(L, index++)); + } + + template <> + bool PopPrimitive(lua_State* L, std::size_t& index) + { + return static_cast(lua_toboolean(L, index++)); + } + + template <> + void* PopPrimitive(lua_State* L, std::size_t& index) + { + return lua_touserdata(L, index++); + } +} // namespace mta::impl diff --git a/Shared/mods/deathmatch/logic/lua/LuaBasic.h b/Shared/mods/deathmatch/logic/lua/LuaBasic.h new file mode 100644 index 00000000000..37bd4ed0ed6 --- /dev/null +++ b/Shared/mods/deathmatch/logic/lua/LuaBasic.h @@ -0,0 +1,132 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto + * LICENSE: See LICENSE in the top level directory + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ +#pragma once +#include +#include + +/* + Basic Lua operations: + int Push(L, T value) + T PopPrimitive(L, std::size_t stackIndex) +*/ + + +namespace lua +{ + // PopTrival should read a simple value of type T from the stack without extra type checks + // If whatever is at that point in the stack is not convertible to T, the behavior is undefined + template + inline T PopPrimitive(lua_State* L, std::size_t& index); + + + // Push should push a value of type T to the Lua Stack + // The return value must be the net amount of items pushed to the stack, which should + // be 1 for most types (e.g. Push) but may be any number for special cases + // like tuples, in order to allow returning multiple values from a function + inline int Push(lua_State* L, int value) + { + lua_pushnumber(L, value); + return 1; + } + inline int Push(lua_State* L, unsigned int value) + { + lua_pushnumber(L, value); + return 1; + } + inline int Push(lua_State* L, float value) + { + lua_pushnumber(L, value); + return 1; + } + inline int Push(lua_State* L, double value) + { + lua_pushnumber(L, value); + return 1; + } + + inline int Push(lua_State* L, bool value) + { + lua_pushboolean(L, value); + return 1; + } + + inline int Push(lua_State* L, nullptr_t) + { + lua_pushnil(L); + return 1; + } + + inline int Push(lua_State* L, const std::string& value) + { + lua_pushlstring(L, value.data(), value.length()); + return 1; + } + + template + int Push(lua_State* L, const std::variant&& val) + { + return std::visit([L](auto&& value) -> int { return Push(L, value); }, val); + } + + template + int Push(lua_State* L, const std::optional&& val) + { + if (val.has_value()) + return Push(L, val.value()); + else + return Push(L, nullptr); + } + + template + int Push(lua_State* L, const std::vector&& val) + { + lua_newtable(L); + int i = 1; + for (auto&& v : val) + { + Push(L, i++); + Push(L, v); + lua_settable(L, -3); + } + + // Only the table remains on the stack + return 1; + } + + template + int Push(lua_State* L, const std::unordered_map&& val) + { + lua_newtable(L); + for (auto&& [k, v] : val) + { + Push(L, k); + Push(L, v); + lua_settable(L, -3); + } + + // Only the table remains on the stack + return 1; + } + + // Overload for enum types only + template + typename std::enable_if_t, int> Push(lua_State* L, const T&& val) + { + return Push(L, EnumToString(val)); + } + + // Overload for pointers to classes. We boldly assume that these are script entities + template + typename std::enable_if_t<(std::is_pointer_v && std::is_class_v>), int> Push(lua_State* L, const T&& val) + { + lua_pushelement(L, val); + return 1; + } + +} diff --git a/Shared/mods/deathmatch/logic/luadefs/CLuaCryptDefs.cpp b/Shared/mods/deathmatch/logic/luadefs/CLuaCryptDefs.cpp index ab444625b68..96a87258deb 100644 --- a/Shared/mods/deathmatch/logic/luadefs/CLuaCryptDefs.cpp +++ b/Shared/mods/deathmatch/logic/luadefs/CLuaCryptDefs.cpp @@ -9,17 +9,18 @@ *****************************************************************************/ #include "StdInc.h" #include +#include void CLuaCryptDefs::LoadFunctions() { std::map functions{ - {"md5", Md5}, - {"sha256", Sha256}, - {"hash", Hash}, - {"teaEncode", TeaEncode}, - {"teaDecode", TeaDecode}, - {"base64Encode", Base64encode}, - {"base64Decode", Base64decode}, + {"md5", ArgumentParserWarn}, + {"sha256", ArgumentParserWarn}, + {"hash", ArgumentParserWarn}, + {"teaEncode", ArgumentParserWarn}, + {"teaDecode", ArgumentParserWarn}, + {"base64Encode", ArgumentParserWarn}, + {"base64Decode", ArgumentParserWarn}, {"passwordHash", PasswordHash}, {"passwordVerify", PasswordVerify}, {"encodeString", EncodeString}, @@ -33,158 +34,49 @@ void CLuaCryptDefs::LoadFunctions() } } -int CLuaCryptDefs::Md5(lua_State* luaVM) +std::string CLuaCryptDefs::Md5(std::string strMd5) { - SString strMd5 = ""; - CScriptArgReader argStream(luaVM); - argStream.ReadString(strMd5); - - if (!argStream.HasErrors()) - { - MD5 md5bytes; - char szResult[33]; - CMD5Hasher hasher; - hasher.Calculate(strMd5, strMd5.length(), md5bytes); - hasher.ConvertToHex(md5bytes, szResult); - lua_pushstring(luaVM, szResult); - return 1; - } - else - m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); - - lua_pushboolean(luaVM, false); - return 1; + MD5 md5bytes; + char szResult[33]; + CMD5Hasher hasher; + hasher.Calculate(strMd5.data(), strMd5.length(), md5bytes); + hasher.ConvertToHex(md5bytes, szResult); + return szResult; } -int CLuaCryptDefs::Sha256(lua_State* luaVM) +std::string CLuaCryptDefs::Sha256(std::string strSourceData) { - // string sha256 ( string str ) - SString strSourceData; - - CScriptArgReader argStream(luaVM); - argStream.ReadString(strSourceData); - - if (!argStream.HasErrors()) - { - SString strResult = GenerateSha256HexString(strSourceData); - lua_pushstring(luaVM, strResult); - return 1; - } - else - m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); - - lua_pushboolean(luaVM, false); - return 1; + return GenerateSha256HexString(strSourceData); } -int CLuaCryptDefs::Hash(lua_State* luaVM) +std::string CLuaCryptDefs::Hash(EHashFunctionType hashFunction, std::string strSourceData) { - // string hash ( string type, string data ) - EHashFunctionType hashFunction; - SString strSourceData; - - CScriptArgReader argStream(luaVM); - argStream.ReadEnumString(hashFunction); - argStream.ReadString(strSourceData); - - if (!argStream.HasErrors()) - { - SString strResult = GenerateHashHexString(hashFunction, strSourceData); - lua_pushstring(luaVM, strResult.ToLower()); - return 1; - } - else - m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); - - lua_pushboolean(luaVM, false); - return 1; + return GenerateHashHexString(hashFunction, strSourceData); } -int CLuaCryptDefs::TeaEncode(lua_State* luaVM) +std::string CLuaCryptDefs::TeaEncode(std::string str, std::string key) { - SString str; - SString key; - - CScriptArgReader argStream(luaVM); - argStream.ReadString(str); - argStream.ReadString(key); - - if (!argStream.HasErrors()) - { - SString result; - SString humanReadableResult; - SharedUtil::TeaEncode(str, key, &result); - humanReadableResult = SharedUtil::Base64encode(result); - lua_pushstring(luaVM, humanReadableResult); - return 1; - } - else - m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); - - lua_pushboolean(luaVM, false); - return 1; + SString result; + SharedUtil::TeaEncode(str, key, &result); + return SharedUtil::Base64encode(result); } -int CLuaCryptDefs::TeaDecode(lua_State* luaVM) +std::string CLuaCryptDefs::TeaDecode(std::string str, std::string key) { - SString str; - SString key; - - CScriptArgReader argStream(luaVM); - argStream.ReadString(str); - argStream.ReadString(key); - - if (!argStream.HasErrors()) - { - SString result = SharedUtil::Base64decode(str); - SharedUtil::TeaDecode(result, key, &str); - lua_pushstring(luaVM, str); - return 1; - } - else - m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); - - lua_pushboolean(luaVM, false); - return 1; + SString result = SharedUtil::Base64decode(str); + SString strOutResult; + SharedUtil::TeaDecode(result, key, &strOutResult); + return strOutResult; } -int CLuaCryptDefs::Base64encode(lua_State* luaVM) +std::string CLuaCryptDefs::Base64encode(std::string str) { - SString str; - - CScriptArgReader argStream(luaVM); - argStream.ReadString(str); - - if (!argStream.HasErrors()) - { - lua_pushstring(luaVM, SharedUtil::Base64encode(str)); - return 1; - } - else - m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); - - lua_pushboolean(luaVM, false); - return 1; + return SharedUtil::Base64encode(str); } -int CLuaCryptDefs::Base64decode(lua_State* luaVM) +std::string CLuaCryptDefs::Base64decode(std::string str) { - SString str; - - CScriptArgReader argStream(luaVM); - argStream.ReadString(str); - - if (!argStream.HasErrors()) - { - SString result = SharedUtil::Base64decode(str); - lua_pushlstring(luaVM, result, result.length()); - return 1; - } - else - m_pScriptDebugging->LogCustom(luaVM, argStream.GetFullErrorMessage()); - - lua_pushboolean(luaVM, false); - return 1; + return SharedUtil::Base64decode(str); } int CLuaCryptDefs::PasswordHash(lua_State* luaVM) @@ -283,6 +175,7 @@ int CLuaCryptDefs::PasswordHash(lua_State* luaVM) return 1; } + int CLuaCryptDefs::PasswordVerify(lua_State* luaVM) { // bool passwordVerify(string password, string hash [, table options, function callback]) diff --git a/Shared/mods/deathmatch/logic/luadefs/CLuaCryptDefs.h b/Shared/mods/deathmatch/logic/luadefs/CLuaCryptDefs.h index 5c6e652b48a..5da930a1865 100644 --- a/Shared/mods/deathmatch/logic/luadefs/CLuaCryptDefs.h +++ b/Shared/mods/deathmatch/logic/luadefs/CLuaCryptDefs.h @@ -10,20 +10,24 @@ #pragma once #include "luadefs/CLuaDefs.h" +#include +#include class CLuaCryptDefs : public CLuaDefs { public: static void LoadFunctions(); - LUA_DECLARE(Md5); - LUA_DECLARE(Sha256); - LUA_DECLARE(Hash); - LUA_DECLARE(TeaEncode); - LUA_DECLARE(TeaDecode); - LUA_DECLARE(Base64encode); - LUA_DECLARE(Base64decode); + static std::string Md5(std::string strMd5); + + static std::string Hash(EHashFunctionType hashFunction, std::string strSourceData); + + static std::string TeaEncode(std::string str, std::string key); + static std::string TeaDecode(std::string str, std::string key); + static std::string Base64encode(std::string str); + static std::string Base64decode(std::string str); LUA_DECLARE(PasswordHash); + static std::string Sha256(std::string strSourceData); LUA_DECLARE(PasswordVerify); LUA_DECLARE(EncodeString); LUA_DECLARE(DecodeString); diff --git a/Shared/sdk/SharedUtil.Template.h b/Shared/sdk/SharedUtil.Template.h new file mode 100644 index 00000000000..f038d78f53d --- /dev/null +++ b/Shared/sdk/SharedUtil.Template.h @@ -0,0 +1,247 @@ +#pragma once + +/** + is_Nspecialization + + These structs allow testing whether a type is a specialization of a + class template + + Note: Take care of optional parameters. For example std::unordered_map + has 5 template arguments, thus is_5specialization needs to be used, rather + than is_2specialization (Key, Value). + + Usage can be the following: + if constexpr(is_2specialization::value) + { + // T is a vector! + using param_t = typename is_2specialization::param_t + // param_t is the type of the content of the vector + } + + For each version of is_Nspecialization we have two class templates: + - The first one (inheriting from std::false_type) is used to provide a + default case, where something isn't a match. It does not impose restrictions + on anything apart from the Test parameter (which is required to take N template + parameters). Thus it matches anything. + + - The second one (std::true_type) is used to perform the actual match + by specializting the template for Test +**/ + +template class Ref> +struct is_specialization : std::false_type +{ +}; + +template