diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8671ee4c..2b2953bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2.3.4 + with: + submodules: 'true' - name: Setup .NET Core SDK uses: actions/setup-dotnet@v1.7.2 @@ -135,6 +137,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2.3.4 + with: + submodules: 'true' - name: Setup .NET Core SDK uses: actions/setup-dotnet@v1.7.2 @@ -245,6 +249,8 @@ jobs: - name: Checkout uses: actions/checkout@v2.3.4 + with: + submodules: 'true' - name: Setup .NET Core SDK uses: actions/setup-dotnet@v1.7.2 @@ -311,6 +317,8 @@ jobs: - name: Checkout repository uses: actions/checkout@v2.3.4 + with: + submodules: 'true' - name: Setup .NET Core SDK uses: actions/setup-dotnet@v1.7.2 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..7983dcf8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "dynalo"] + path = gm_dotnet_native/external_includes/dynalo + url = https://github.com/Stat1cV01D/dynalo.git diff --git a/README.md b/README.md index 49cb83b1..c4cc6487 100644 --- a/README.md +++ b/README.md @@ -112,4 +112,6 @@ Gmod.NET is making use of or borrows code from the following projects: 4. [Libsodium](http://libsodium.org) by [Frank Denis](https://github.com/jedisct1) (ISC License) +5. [dynalo](https://github.com/Stat1cV01D/dynalo) originally by [maddouri](https://github.com/maddouri/dynalo) (MIT License) + See other copyright notices in the NOTICE file. diff --git a/gm_dotnet_native/.clang-format b/gm_dotnet_native/.clang-format new file mode 100644 index 00000000..04a30dce --- /dev/null +++ b/gm_dotnet_native/.clang-format @@ -0,0 +1,149 @@ + +--- +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +#AlignConsecutiveAssignments: AcrossComments +#AlignConsecutiveDeclarations: AcrossComments +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: false +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLambdasOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: Always + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + #BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: All +BreakBeforeBraces: Custom +BreakBeforeConceptDeclarations: true +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakStringLiterals: true +ColumnLimit: 140 +CommentPragmas: '^ IWYU pragma:|^!|^:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: false +DerivePointerAlignment: false +DisableFormat: false +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - forever # avoids { wrapped to next line + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + # C stdlib + - Regex: '^$' + Priority: 1 + # C++ stdlib + - Regex: '^<(algorithm|any|array|bit|bitset|deque|exception|execution|filesystem|forward_list|fstream|functional|future|initializer_list|iomanip|ios|iostream|istream|iterator|limits|list|locale|map|memory_resource|memory|mutex|new|numeric|optional|ostream|queue|random|ranges|ratio|regex|scoped_allocator|set|shared_mutex|span|sstream|stack|stdexcept|streambuf|string_view|string|strstream|syncstream|system_error|thread|tuple|type_traits|typeindex|typeinfo|unordered_map|unordered_set|utility|valarray|variant|vector)>$' + Priority: 2 + # C++ stdlib (experimental) + - Regex: '^$' + Priority: 3 + # Windows.h + - Regex: '^<[Ww]indows.h>$' + Priority: 8 + # Global + - Regex: '^<.+>$' + Priority: 9 + # Local (private) + - Regex: '^"private/.+"$' + Priority: 10 + # Local + - Regex: '^".+"$' + Priority: 11 +IncludeIsMainRegex: '(Test)?$' +#IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: true +IndentGotoLabels: false +IndentPPDirectives: None +IndentRequires: true +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertTrailingCommas: Wrapped +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: "" +MacroBlockEnd: "" +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 150 +PenaltyBreakBeforeFirstCallParameter: 300 +PenaltyBreakComment: 500 +PenaltyBreakFirstLessLess: 400 +PenaltyBreakString: 600 +PenaltyExcessCharacter: 50 +PenaltyReturnTypeOnItsOwnLine: 300 +PointerAlignment: Left +ReflowComments: false +SortIncludes: true #CaseSensitive +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceAroundPointerQualifiers: Before +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: false +SpaceBeforeInheritanceColon: false +SpaceBeforeParens: Never +SpaceBeforeRangeBasedForLoopColon: false +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Latest +StatementAttributeLikeMacros: [emit] +StatementMacros: [Q_UNUSED] +TabWidth: 4 +UseTab: Never diff --git a/gm_dotnet_native/CMakeLists.txt b/gm_dotnet_native/CMakeLists.txt index a7ffb4e3..2f9ae162 100644 --- a/gm_dotnet_native/CMakeLists.txt +++ b/gm_dotnet_native/CMakeLists.txt @@ -21,11 +21,24 @@ set(NET_CORE_VERSION "1.0.0" CACHE STRING "Version of bundled .NET Core Runtime" #Project name project(gm_dotnet_native) -add_library(gm_dotnet_native SHARED src/gm_dotnet.cpp dotnethelper-src/cleanup_function_type.h) -add_library(dotnethelper SHARED dotnethelper-src/dotnethelper.cpp dotnethelper-src/cleanup_function_type.h - dotnethelper-src/LuaAPIExposure.h dotnethelper-src/LuaAPIExposure.cpp) +add_library(gm_dotnet_native SHARED + src/gm_dotnet.cpp + dotnethelper-src/cleanup_function_type.h + utils/path.cpp +) +add_library(dotnethelper SHARED + dotnethelper-src/dotnethelper.cpp + dotnethelper-src/cleanup_function_type.h + dotnethelper-src/LuaAPIExposure.h + dotnethelper-src/LuaAPIExposure.cpp + dotnethelper-src/hostfxr_interop.cpp + utils/path.cpp +) #Set up external include libraries -include_directories ("${EXTERNAL_INCLUDES_PATH}") +include_directories ("${EXTERNAL_INCLUDES_PATH}" "${CMAKE_CURRENT_SOURCE_DIR}") +add_subdirectory ("${EXTERNAL_INCLUDES_PATH}/dynalo") +target_link_libraries(gm_dotnet_native PUBLIC dynalo) +target_link_libraries(dotnethelper PUBLIC dynalo) #Set up compile definitions target_compile_definitions(gm_dotnet_native PUBLIC SEM_VERSION="${SEM_VERSION}") target_compile_definitions(dotnethelper PUBLIC SEM_VERSION="${SEM_VERSION}") @@ -37,7 +50,11 @@ set_target_properties(dotnethelper PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_C RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../build) #Add linux helper lib if(CMAKE_SYSTEM_NAME MATCHES "Linux") - add_library(linuxhelper SHARED linux-helper-src/segv_signal_handler.h linux-helper-src/segv_signal_handler.cpp) + add_library(linuxhelper SHARED + linux-helper-src/segv_signal_handler.h + linux-helper-src/segv_signal_handler.cpp + utils/path.cpp + ) set_target_properties(linuxhelper PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../build RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../build) endif() diff --git a/gm_dotnet_native/dotnethelper-src/LuaAPIExposure.h b/gm_dotnet_native/dotnethelper-src/LuaAPIExposure.h index 12072e11..ecb6851c 100644 --- a/gm_dotnet_native/dotnethelper-src/LuaAPIExposure.h +++ b/gm_dotnet_native/dotnethelper-src/LuaAPIExposure.h @@ -1,9 +1,8 @@ // // Created by Gleb Krasilich on 06.10.2019. // +#pragma once -#ifndef GM_DOTNET_NATIVE_LUAAPIEXPOSURE_H -#define GM_DOTNET_NATIVE_LUAAPIEXPOSURE_H #include #include @@ -333,5 +332,3 @@ double export_check_number(GarrysMod::Lua::ILuaBase * lua, int iStackPos); /// \param lua ILuaBase pointer /// \param val function to push on stack void export_push_c_function_safe(GarrysMod::Lua::ILuaBase * lua, GarrysMod::Lua::CFunc safe_wrapper, GarrysMod::Lua::CFunc val); - -#endif //GM_DOTNET_NATIVE_LUAAPIEXPOSURE_H diff --git a/gm_dotnet_native/dotnethelper-src/cleanup_function_type.h b/gm_dotnet_native/dotnethelper-src/cleanup_function_type.h index 25913864..798e63cd 100644 --- a/gm_dotnet_native/dotnethelper-src/cleanup_function_type.h +++ b/gm_dotnet_native/dotnethelper-src/cleanup_function_type.h @@ -1,11 +1,8 @@ // // Created by glebc on 11.10.2020. // +#pragma once -#ifndef GM_DOTNET_NATIVE_CLEANUP_FUNCTION_TYPE_H -#define GM_DOTNET_NATIVE_CLEANUP_FUNCTION_TYPE_H #include typedef void(*cleanup_function_fn)(GarrysMod::Lua::ILuaBase* lua); - -#endif //GM_DOTNET_NATIVE_CLEANUP_FUNCTION_TYPE_H diff --git a/gm_dotnet_native/dotnethelper-src/dotnethelper.cpp b/gm_dotnet_native/dotnethelper-src/dotnethelper.cpp index 60db6736..febb3729 100644 --- a/gm_dotnet_native/dotnethelper-src/dotnethelper.cpp +++ b/gm_dotnet_native/dotnethelper-src/dotnethelper.cpp @@ -1,84 +1,49 @@ // // Created by Gleb Krasilich on 11.10.2020. // +#include +#include #include -#include -#include +#include #include -#include "cleanup_function_type.h" #include "LuaAPIExposure.h" -#include -#include -#include +#include "cleanup_function_type.h" +#include "hostfxr_interop.h" +#include "utils/path.h" #ifdef WIN32 #include #else #include -#include -#include #endif #ifdef WIN32 #define DYNAMIC_EXPORT _declspec(dllexport) -#define __T(x) L ## x #else #define DYNAMIC_EXPORT __attribute__((visibility("default"))) -#define __T(x) x #endif -#define _T(x) __T(x) - -typedef int (*managed_delegate_executor_fn)( - lua_State * luaState -); +typedef int (*managed_delegate_executor_fn)(lua_State* luaState); -typedef cleanup_function_fn(*managed_main_fn)( - GarrysMod::Lua::ILuaBase* lua, - const char* versionString, - int versionStringLength, - void** internalFunctionsParam, - GarrysMod::Lua::CFunc native_delegate_executor_ptr, - /* Out Param */ managed_delegate_executor_fn* managed_delegate_executor_ptr - ); +typedef cleanup_function_fn (*managed_main_fn)(GarrysMod::Lua::ILuaBase* lua, + const char* versionString, + int versionStringLength, + void** internalFunctionsParam, + GarrysMod::Lua::CFunc native_delegate_executor_ptr, + /* Out Param */ managed_delegate_executor_fn* managed_delegate_executor_ptr); using tstring = std::basic_string; std::ofstream error_log_file; managed_delegate_executor_fn managed_delegate_executor = nullptr; - managed_main_fn managed_main = nullptr; -const std::filesystem::path lua_bin_folder = _T("garrysmod/lua/bin"); -const std::filesystem::path hostfxr_path = (lua_bin_folder / _T("dotnet/host/fxr") / NET_CORE_VERSION).make_preferred(); -#ifdef WIN32 -HMODULE hostfxr_library_handle = LoadLibraryW((hostfxr_path / _T("hostfxr.dll")).c_str()); -#elif __APPLE__ -void* hostfxr_library_handle = dlopen((hostfxr_path / "libhostfxr.dylib").c_str(), RTLD_LAZY | RTLD_LOCAL); -#elif __gnu_linux__ -void* hostfxr_library_handle = dlopen((hostfxr_path / "libhostfxr.so").c_str(), RTLD_LAZY); -#endif - -template -bool LoadFunction(const char* function_name, T& out_func) -{ -#ifdef WIN32 - out_func = reinterpret_cast(GetProcAddress(hostfxr_library_handle, function_name)); -#else - out_func = reinterpret_cast(dlsym(hostfxr_library_handle, function_name)); -#endif - return (out_func != nullptr); -} -hostfxr_initialize_for_dotnet_command_line_fn hostfxr_initialize_for_dotnet_command_line = nullptr; -hostfxr_get_runtime_delegate_fn hostfxr_get_runtime_delegate = nullptr; -hostfxr_set_error_writer_fn hostfxr_set_error_writer = nullptr; - -void HOSTFXR_CALLTYPE dotnet_error_writer(const char_t *message) +void HOSTFXR_CALLTYPE dotnet_error_writer(const char_t* message) { error_log_file << message << std::endl; } -int native_delegate_executor(lua_State * luaState) +int native_delegate_executor(lua_State* luaState) { int return_val = managed_delegate_executor(luaState); @@ -94,7 +59,8 @@ int native_delegate_executor(lua_State * luaState) } } -void * params_to_managed_code[] = { +// clang-format off +void* params_to_managed_code[] = { reinterpret_cast(export_top), reinterpret_cast(export_push), reinterpret_cast(export_pop), @@ -151,6 +117,7 @@ void * params_to_managed_code[] = { reinterpret_cast(export_check_number), reinterpret_cast(export_push_c_function_safe) }; +// clang-format on extern "C" DYNAMIC_EXPORT cleanup_function_fn InitNetRuntime(GarrysMod::Lua::ILuaBase* lua) { @@ -161,74 +128,23 @@ extern "C" DYNAMIC_EXPORT cleanup_function_fn InitNetRuntime(GarrysMod::Lua::ILu if(managed_main == nullptr) { - if(!(LoadFunction("hostfxr_initialize_for_dotnet_command_line", hostfxr_initialize_for_dotnet_command_line) - && LoadFunction("hostfxr_get_runtime_delegate", hostfxr_get_runtime_delegate) - && LoadFunction("hostfxr_set_error_writer", hostfxr_set_error_writer))) - { - error_log_file << "Unable to load hostfxr library" << std::endl; - return nullptr; - } - - hostfxr_handle runtime_environment_handle; - - hostfxr_set_error_writer(dotnet_error_writer); - - const auto gmodnet_dll_relative_path = lua_bin_folder / _T("gmodnet/GmodNET.dll"); - const auto dotnet_root_path = (std::filesystem::current_path() / lua_bin_folder / "dotnet").make_preferred(); - - const char_t* dotnet_args[] = {_T("exec"), gmodnet_dll_relative_path.c_str()}; - - tstring game_exe_path(301, _T('\0')); -#ifdef WIN32 - GetModuleFileNameW(nullptr, game_exe_path.data(), static_cast(game_exe_path.size()) - 1); -#else - readlink("/proc/self/exe", game_exe_path.data(), game_exe_path.size() - 1); -#endif - hostfxr_initialize_parameters dotnet_runtime_params; - dotnet_runtime_params.size = sizeof(hostfxr_initialize_parameters); - dotnet_runtime_params.host_path = game_exe_path.c_str(); - dotnet_runtime_params.dotnet_root = dotnet_root_path.c_str(); - - int init_success_code = hostfxr_initialize_for_dotnet_command_line(static_cast(std::size(dotnet_args)), dotnet_args, - &dotnet_runtime_params, &runtime_environment_handle); - if(init_success_code != 0) - { - error_log_file << "Unable to initialize dotnet runtime. Error code: " << init_success_code << std::endl; - return nullptr; - } - if(runtime_environment_handle == nullptr) + try { - error_log_file << "runtime_environment_handle is null" << std::endl; + hostfxr_interop hostfxr{}; + hostfxr.set_error_writer(dotnet_error_writer); + managed_main = hostfxr.load_gmodnet_main(); } - get_function_pointer_fn get_function_pointer = nullptr; - int get_runtime_delegate_success_code = - hostfxr_get_runtime_delegate(runtime_environment_handle, hdt_get_function_pointer, reinterpret_cast(&get_function_pointer)); - if(get_runtime_delegate_success_code != 0) + catch(const std::runtime_error& ex) { - error_log_file << "Unable to get delegate of dotnet runtime. Error code: " << get_runtime_delegate_success_code << std::endl; - return nullptr; - } - if(get_function_pointer == nullptr) - { - error_log_file << "get_function_pointer is null" << std::endl; - return nullptr; - } - - int get_managed_main_success_code = get_function_pointer(_T("GmodNET.Startup, GmodNET"), _T("Main"), UNMANAGEDCALLERSONLY_METHOD, - nullptr, nullptr, reinterpret_cast(&managed_main)); - if(get_managed_main_success_code != 0) - { - error_log_file << "Unable to load managed entry point: Error code: " << get_managed_main_success_code << std::endl; - return nullptr; - } - if(managed_main == nullptr) - { - error_log_file << "Unable to load managed entry point: managed_main is null" << std::endl; + error_log_file << ex.what() << std::endl; return nullptr; } } - return managed_main(lua, SEM_VERSION, static_cast(std::strlen(SEM_VERSION)), params_to_managed_code, - native_delegate_executor, &managed_delegate_executor); + return managed_main(lua, + SEM_VERSION, + static_cast(std::strlen(SEM_VERSION)), + params_to_managed_code, + native_delegate_executor, + &managed_delegate_executor); } - diff --git a/gm_dotnet_native/dotnethelper-src/hostfxr_interop.cpp b/gm_dotnet_native/dotnethelper-src/hostfxr_interop.cpp new file mode 100644 index 00000000..0c58a164 --- /dev/null +++ b/gm_dotnet_native/dotnethelper-src/hostfxr_interop.cpp @@ -0,0 +1,59 @@ +#include "hostfxr_interop.h" +#include +#include +#include "utils/path.h" + +#ifdef WIN32 +#define STRINGIZE _CRT_STRINGIZE +#else +#define STRINGIZE_(x) #x +#define STRINGIZE(x) STRINGIZE_(x) +#endif + +const std::filesystem::path dotnet_folder("dotnet"); +const std::filesystem::path gmodnet_dll_path("gmodnet/GmodNET.dll"); +const dynalo::library hostfxr_library(utils::path::lua_bin_folder() / _T("dotnet/host/fxr") / NET_CORE_VERSION + / dynalo::to_native_name("hostfxr")); + +hostfxr_interop::hostfxr_interop() +{ +#define LOAD_FN(x) x = hostfxr_library.get_function>(STRINGIZE(hostfxr_##x)) + + LOAD_FN(initialize_for_dotnet_command_line); + LOAD_FN(get_runtime_delegate); + LOAD_FN(set_error_writer); + +#undef LOAD_FN +} + +hostfxr_handle hostfxr_interop::init_runtime_for_gmodnet() +{ + const auto dotnet_root_path = (std::filesystem::current_path() / utils::path::lua_bin_folder() / dotnet_folder).make_preferred(); + std::filesystem::path game_exe_path = utils::path::get_exe(); + + hostfxr_initialize_parameters dotnet_runtime_params; + dotnet_runtime_params.size = sizeof(hostfxr_initialize_parameters); + dotnet_runtime_params.host_path = game_exe_path.c_str(); + dotnet_runtime_params.dotnet_root = dotnet_root_path.c_str(); + + const auto gmodnet_dll_relative_path = utils::path::lua_bin_folder() / gmodnet_dll_path; + const char_t* dotnet_args[] = {_T("exec"), gmodnet_dll_relative_path.c_str()}; + + return validate_call( + [&](auto* result) { + return initialize_for_dotnet_command_line(static_cast(std::size(dotnet_args)), dotnet_args, &dotnet_runtime_params, result); + }, + "hostfxr_handle", + "Unable to initialize dotnet runtime"); +} + +get_function_pointer_fn hostfxr_interop::managed_function_pointer_getter() +{ + hostfxr_handle runtime_environment_handle = init_runtime_for_gmodnet(); + return validate_call( + [&](auto* result) { + return get_runtime_delegate(runtime_environment_handle, hdt_get_function_pointer, reinterpret_cast(result)); + }, + "get_function_pointer", + "Unable to get delegate of dotnet runtime"); +} diff --git a/gm_dotnet_native/dotnethelper-src/hostfxr_interop.h b/gm_dotnet_native/dotnethelper-src/hostfxr_interop.h new file mode 100644 index 00000000..b0b718f8 --- /dev/null +++ b/gm_dotnet_native/dotnethelper-src/hostfxr_interop.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include +#include + +#ifdef WIN32 +#define __T(x) L##x +#else +#define __T(x) x +#endif + +#define _T(x) __T(x) + +class hostfxr_interop +{ +public: + explicit hostfxr_interop(); + + template + T load_gmodnet_main(); + + hostfxr_set_error_writer_fn set_error_writer{}; + +private: + template + T validate_call(const std::function& function_call, const std::string& result_name, const std::string& error_msg); + hostfxr_handle init_runtime_for_gmodnet(); + get_function_pointer_fn managed_function_pointer_getter(); + +private: + hostfxr_initialize_for_dotnet_command_line_fn initialize_for_dotnet_command_line{}; + hostfxr_get_runtime_delegate_fn get_runtime_delegate{}; +}; + +#include "hostfxr_interop_p.h" diff --git a/gm_dotnet_native/dotnethelper-src/hostfxr_interop_p.h b/gm_dotnet_native/dotnethelper-src/hostfxr_interop_p.h new file mode 100644 index 00000000..bba12dbe --- /dev/null +++ b/gm_dotnet_native/dotnethelper-src/hostfxr_interop_p.h @@ -0,0 +1,38 @@ +// hostfxr_interop's template method definitions + +template +T hostfxr_interop::validate_call(const std::function& function_call, const std::string& result_name, const std::string& error_msg) +{ + static_assert(std::is_pointer_v, "T must be a pointer type"); + + T result = nullptr; + int error_code = function_call(&result); + if(error_code != 0) + { + throw std::runtime_error(error_msg + " (error code: " + std::to_string(error_code) + ")"); + } + if(result == nullptr) + { + throw std::runtime_error(result_name + " is null"); + } + + return result; +} + +template +T hostfxr_interop::load_gmodnet_main() +{ + get_function_pointer_fn get_function_pointer = managed_function_pointer_getter(); + + return validate_call( + [&](auto* result) { + return get_function_pointer(_T("GmodNET.Startup, GmodNET"), + _T("Main"), + UNMANAGEDCALLERSONLY_METHOD, + nullptr, + nullptr, + reinterpret_cast(result)); + }, + "managed_main", + "Unable to load managed entry point"); +} diff --git a/gm_dotnet_native/external_includes/dynalo b/gm_dotnet_native/external_includes/dynalo new file mode 160000 index 00000000..a1adfd99 --- /dev/null +++ b/gm_dotnet_native/external_includes/dynalo @@ -0,0 +1 @@ +Subproject commit a1adfd9963959354a1a4ebac9da3f7f280bf6487 diff --git a/gm_dotnet_native/linux-helper-src/segv_signal_handler.cpp b/gm_dotnet_native/linux-helper-src/segv_signal_handler.cpp index 5b2d4465..0abfaf96 100644 --- a/gm_dotnet_native/linux-helper-src/segv_signal_handler.cpp +++ b/gm_dotnet_native/linux-helper-src/segv_signal_handler.cpp @@ -7,6 +7,7 @@ #include #include #include +#include "utils/path.h" using namespace std; @@ -56,12 +57,10 @@ extern "C" __attribute__((__visibility__("default"))) void install_sigsegv_handl { if(!WasHandlerSet) { - char exe_path_buffer[300]; - int exe_path_length = readlink("/proc/self/exe", exe_path_buffer, 299); - exe_path_buffer[exe_path_length] = '\0'; + std::filesystem::path exe_path = utils::path::get_exe(); regex file_matcher = regex(".*\\/gmod$"); - if(!regex_match(exe_path_buffer, file_matcher)) + if(!regex_match(exe_path.native(), file_matcher)) { WasHandlerSet = true; return; diff --git a/gm_dotnet_native/linux-helper-src/segv_signal_handler.h b/gm_dotnet_native/linux-helper-src/segv_signal_handler.h index cd2ea8b3..81605a63 100644 --- a/gm_dotnet_native/linux-helper-src/segv_signal_handler.h +++ b/gm_dotnet_native/linux-helper-src/segv_signal_handler.h @@ -1,11 +1,7 @@ // // Created by Gleb Krasilich on 2/27/20. // - -#ifndef GM_DOTNET_NATIVE_SEGV_SIGNAL_HANDLER_H -#define GM_DOTNET_NATIVE_SEGV_SIGNAL_HANDLER_H +#pragma once /// Custom SIGSEGV handler for Garry's Mod extern "C" __attribute__((__visibility__("default"))) void install_sigsegv_handler(); - -#endif //GM_DOTNET_NATIVE_SEGV_SIGNAL_HANDLER_H diff --git a/gm_dotnet_native/src/gm_dotnet.cpp b/gm_dotnet_native/src/gm_dotnet.cpp index 29c862bb..2cfd6bf5 100644 --- a/gm_dotnet_native/src/gm_dotnet.cpp +++ b/gm_dotnet_native/src/gm_dotnet.cpp @@ -1,31 +1,33 @@ // // Created by Gleb Krasilich on 02.10.2019. // -#include #include #include +#include #ifdef WIN32 #include #else #include #endif // WIN32 #include -#include "../dotnethelper-src/cleanup_function_type.h" - -typedef cleanup_function_fn (*InitNetRuntime_fn)(GarrysMod::Lua::ILuaBase* lua); +#include +#include "dotnethelper-src/cleanup_function_type.h" +#include "utils/path.h" cleanup_function_fn cleanup_function = nullptr; +#ifdef __gnu_linux__ +const dynalo::library liblinuxhelper(utils::path::lua_bin_folder() / "liblinuxhelper.so"); +#endif + +const dynalo::library dotnethelper(utils::path::lua_bin_folder() / dynalo::to_native_name("dotnethelper")); + //Invoked by Garry's Mod on module load GMOD_MODULE_OPEN() { - const std::filesystem::path lua_bin_folder("garrysmod/lua/bin"); - // On Linux, modify SIGSEGV handling #ifdef __gnu_linux__ - void *linux_helper_handle = dlopen((lua_bin_folder / "liblinuxhelper.so").c_str(), RTLD_LAZY); - void (*pointer_to_install_sigsegv)(void); - pointer_to_install_sigsegv = (void(*)())dlsym(linux_helper_handle, "install_sigsegv_handler"); + auto pointer_to_install_sigsegv = liblinuxhelper.get_function("install_sigsegv_handler"); pointer_to_install_sigsegv(); #endif @@ -36,36 +38,22 @@ GMOD_MODULE_OPEN() LUA->Call(1, 0); LUA->Pop(1); - InitNetRuntime_fn InitNetRuntime = nullptr; - const char InitNetRuntime_fn_name[] = "InitNetRuntime"; - -#ifdef WIN32 - HMODULE dotnethelper_handle = LoadLibraryW((lua_bin_folder / "dotnethelper.dll").make_preferred().c_str()); - if (dotnethelper_handle != nullptr) - InitNetRuntime = reinterpret_cast(GetProcAddress(dotnethelper_handle, InitNetRuntime_fn_name)); -#elif __APPLE__ - void* dotnethelper_handle = dlopen((lua_bin_folder / "libdotnethelper.dylib").c_str(), RTLD_LAZY); -#elif __gnu_linux__ - void* dotnethelper_handle = dlopen((lua_bin_folder / "libdotnethelper.so").c_str(), RTLD_LAZY); -#endif - -#ifndef WIN32 - InitNetRuntime = reinterpret_cast(dlsym(dotnethelper_handle, InitNetRuntime_fn_name)); -#endif - - if(InitNetRuntime == nullptr) + try { + auto InitNetRuntime = dotnethelper.get_function("InitNetRuntime"); + cleanup_function = InitNetRuntime(LUA); + } + catch(const std::runtime_error& ex) + { + auto error_msg = std::string("::error::Unable to load dotnet helper library. ") + ex.what(); LUA->PushSpecial(GarrysMod::Lua::SPECIAL_GLOB); LUA->GetField(-1, "print"); - LUA->PushString("::error::Unable to load dotnet helper library."); + LUA->PushString(error_msg.c_str()); LUA->Call(1, 0); LUA->Pop(1); - return 0; } - cleanup_function = InitNetRuntime(LUA); - if(cleanup_function == nullptr) { LUA->PushSpecial(GarrysMod::Lua::SPECIAL_GLOB); diff --git a/gm_dotnet_native/utils/path.cpp b/gm_dotnet_native/utils/path.cpp new file mode 100644 index 00000000..8686ab29 --- /dev/null +++ b/gm_dotnet_native/utils/path.cpp @@ -0,0 +1,27 @@ +#include "path.h" +#ifdef WIN32 +#include +#else +#include +#endif + +namespace utils::path +{ + +std::filesystem::path lua_bin_folder() +{ + return {"garrysmod/lua/bin"}; +} + +std::filesystem::path get_exe() +{ + const int max_name_length = 301; + std::filesystem::path::string_type game_exe_path(max_name_length, '\0'); +#ifdef WIN32 + GetModuleFileNameW(nullptr, game_exe_path.data(), static_cast(game_exe_path.size()) - 1); +#else + int exe_path_length = readlink("/proc/self/exe", game_exe_path.data(), game_exe_path.size() - 1); +#endif + return game_exe_path; +} +} //namespace utils diff --git a/gm_dotnet_native/utils/path.h b/gm_dotnet_native/utils/path.h new file mode 100644 index 00000000..573d5040 --- /dev/null +++ b/gm_dotnet_native/utils/path.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace utils::path +{ +std::filesystem::path lua_bin_folder(); +std::filesystem::path get_exe(); +}