├── .gitignore ├── source ├── loadlib.hpp ├── loadlib_pos.cpp ├── loadlib_win.cpp └── main.cpp ├── includes └── modules │ ├── unrequire.lua │ └── require.lua ├── premake5.lua ├── readme.md ├── license.txt └── azure-pipelines.yml /.gitignore: -------------------------------------------------------------------------------- 1 | /projects/ 2 | -------------------------------------------------------------------------------- /source/loadlib.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern char GoodSeparator; 5 | extern char BadSeparator; 6 | 7 | extern const char ParentDirectory[]; 8 | extern const char CurrentDirectory[]; 9 | 10 | extern const char RelativePathToBin[]; 11 | extern const char RelativePathToLibraries[]; 12 | 13 | std::string GetSystemError( ); 14 | 15 | bool IsWhitelistedExtension( const std::string &ext ); 16 | 17 | void *OpenLibrary( const char *path ); 18 | 19 | bool CloseLibrary( void *handle ); 20 | 21 | GarrysMod::Lua::CFunc FindFunction( void *handle, const char *name ); 22 | 23 | std::string GetFullPath( const std::string &path ); 24 | -------------------------------------------------------------------------------- /includes/modules/unrequire.lua: -------------------------------------------------------------------------------- 1 | local _MODULES = _MODULES 2 | local package_loaded = package.loaded 3 | local _R = debug.getregistry() 4 | local _LOADLIB = _R._LOADLIB 5 | 6 | local is_windows = system.IsWindows() 7 | local is_linux = system.IsLinux() 8 | local is_osx = system.IsOSX() 9 | local is_x64 = jit.arch == "x64" 10 | local separator = is_windows and "\\" or "/" 11 | 12 | local dll_prefix = CLIENT and "gmcl" or "gmsv" 13 | local dll_suffix = assert( 14 | (is_windows and (is_x64 and "win64" or "win32")) or 15 | (is_linux and (is_x64 and "linux64" or "linux")) or 16 | (is_osx and (is_x64 and "osx64" or "osx")) 17 | ) 18 | 19 | local fmt = string.format( 20 | "^LOADLIB: .+%sgarrysmod%slua%sbin%s%s_%%s_%s.dll$", 21 | separator, 22 | separator, 23 | separator, 24 | separator, 25 | dll_prefix, 26 | dll_suffix 27 | ) 28 | 29 | function unrequire(name) 30 | _MODULES[name] = nil 31 | package_loaded[name] = nil 32 | 33 | local loadlib = string.format(fmt, name) 34 | for name, mod in pairs(_R) do 35 | if type(name) == "string" and string.find(name, loadlib) then 36 | _LOADLIB.__gc(mod) 37 | _R[name] = nil 38 | break 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /premake5.lua: -------------------------------------------------------------------------------- 1 | PROJECT_GENERATOR_VERSION = 2 2 | 3 | newoption({ 4 | trigger = "gmcommon", 5 | description = "Sets the path to the garrysmod_common (https://github.com/danielga/garrysmod_common) directory", 6 | value = "path to garrysmod_common directory" 7 | }) 8 | 9 | include(assert(_OPTIONS.gmcommon or os.getenv("GARRYSMOD_COMMON"), 10 | "you didn't provide a path to your garrysmod_common (https://github.com/danielga/garrysmod_common) directory")) 11 | 12 | CreateWorkspace({name = "require.core", abi_compatible = true}) 13 | CreateProject({serverside = true, manual_files = true}) 14 | IncludeLuaShared() 15 | 16 | files({"source/main.cpp", "source/loadlib.hpp"}) 17 | 18 | filter("system:windows") 19 | files("source/loadlib_win.cpp") 20 | 21 | filter("system:linux or macosx") 22 | files("source/loadlib_pos.cpp") 23 | links("dl") 24 | 25 | CreateProject({serverside = false, manual_files = true}) 26 | IncludeLuaShared() 27 | 28 | files({"source/main.cpp", "source/loadlib.hpp"}) 29 | 30 | filter("system:windows") 31 | files("source/loadlib_win.cpp") 32 | 33 | filter("system:linux or macosx") 34 | files("source/loadlib_pos.cpp") 35 | links("dl") 36 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # gmod\_require 2 | 3 | Modules for Garry's Mod for obtaining pointers to functions inside of modules (specially useful for loading Lua only modules). 4 | 5 | ## Compiling 6 | 7 | The only supported compilation platform for this project on Windows is **Visual Studio 2017** on **release** mode. However, it's possible it'll work with *Visual Studio 2015* and *Visual Studio 2019* because of the unified runtime. 8 | 9 | On Linux, everything should work fine as is, on **release** mode. 10 | 11 | For macOS, any **Xcode (using the GCC compiler)** version *MIGHT* work as long as the **Mac OSX 10.7 SDK** is used, on **release** mode. 12 | 13 | These restrictions are not random; they exist because of ABI compatibility reasons. 14 | 15 | If stuff starts erroring or fails to work, be sure to check the correct line endings (`\n` and such) are present in the files for each OS. 16 | 17 | ## Requirements 18 | 19 | This project requires [garrysmod\_common][1], a framework to facilitate the creation of compilations files (Visual Studio, make, XCode, etc). Simply set the environment variable `GARRYSMOD_COMMON` or the premake option `--gmcommon=path` to the path of your local copy of [garrysmod\_common][1]. 20 | 21 | [1]: https://github.com/danielga/garrysmod_common 22 | -------------------------------------------------------------------------------- /source/loadlib_pos.cpp: -------------------------------------------------------------------------------- 1 | #include "loadlib.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | char GoodSeparator = '/'; 8 | char BadSeparator = '\0'; 9 | 10 | const char ParentDirectory[] = "../"; 11 | const char CurrentDirectory[] = "./"; 12 | 13 | const char RelativePathToBin[] = "garrysmod/lua/bin/"; 14 | const char RelativePathToLibraries[] = "garrysmod/lua/libraries/"; 15 | 16 | std::string GetSystemError( ) 17 | { 18 | const char *errstr = dlerror( ); 19 | return errstr != nullptr ? errstr : "unknown system error"; 20 | } 21 | 22 | bool IsWhitelistedExtension( const std::string &ext ) 23 | { 24 | return ext == "dll" || ext == "so" || ext == "dylib"; 25 | } 26 | 27 | void *OpenLibrary( const char *path ) 28 | { 29 | return dlopen( path, RTLD_NOW ); 30 | } 31 | 32 | bool CloseLibrary( void *handle ) 33 | { 34 | return dlclose( handle ) == 0; 35 | } 36 | 37 | GarrysMod::Lua::CFunc FindFunction( void *handle, const char *name ) 38 | { 39 | return reinterpret_cast( dlsym( handle, name ) ); 40 | } 41 | 42 | std::string GetFullPath( const std::string &path ) 43 | { 44 | std::string fullpath; 45 | fullpath.resize( PATH_MAX ); 46 | if( realpath( path.c_str( ), &fullpath[0] ) != nullptr ) 47 | fullpath.resize( std::strlen( fullpath.c_str( ) ) ); 48 | else 49 | fullpath.clear( ); 50 | 51 | return fullpath; 52 | } 53 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | gmod_require 2 | Modules for Garry's Mod for obtaining pointers to functions inside of 3 | modules (specially useful for loading Lua only modules). 4 | ----------------------------------------------------------------------- 5 | Copyright (c) 2015-2021, Daniel Almeida 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions 10 | are met: 11 | 12 | 1. Redistributions of source code must retain the above copyright 13 | notice, this list of conditions and the following disclaimer. 14 | 15 | 2. Redistributions in binary form must reproduce the above copyright 16 | notice, this list of conditions and the following disclaimer in the 17 | documentation and/or other materials provided with the distribution. 18 | 19 | 3. Neither the name of the copyright holder nor the names of its 20 | contributors may be used to endorse or promote products derived from 21 | this software without specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 29 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 30 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 31 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | -------------------------------------------------------------------------------- /source/loadlib_win.cpp: -------------------------------------------------------------------------------- 1 | #include "loadlib.hpp" 2 | #include 3 | #include 4 | 5 | char GoodSeparator = '/'; 6 | char BadSeparator = '\0'; 7 | 8 | const char ParentDirectory[] = "../"; 9 | const char CurrentDirectory[] = "./"; 10 | 11 | const char RelativePathToBin[] = "garrysmod/lua/bin/"; 12 | const char RelativePathToLibraries[] = "garrysmod/lua/libraries/"; 13 | 14 | std::string GetSystemError( ) 15 | { 16 | char *temp = nullptr; 17 | std::string message; 18 | DWORD res = FormatMessage( 19 | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, 20 | nullptr, 21 | GetLastError( ), 22 | LANG_USER_DEFAULT, 23 | reinterpret_cast( &temp ), 24 | 1, 25 | nullptr 26 | ); 27 | if( res != 0 ) 28 | { 29 | message = temp; 30 | LocalFree( temp ); 31 | } 32 | else 33 | message = "unknown system error"; 34 | 35 | return message; 36 | } 37 | 38 | bool IsWhitelistedExtension( const std::string &ext ) 39 | { 40 | return ext == "dll"; 41 | } 42 | 43 | void *OpenLibrary( const char *path ) 44 | { 45 | return LoadLibrary( path ); 46 | } 47 | 48 | bool CloseLibrary( void *handle ) 49 | { 50 | return FreeLibrary( reinterpret_cast( handle ) ); 51 | } 52 | 53 | GarrysMod::Lua::CFunc FindFunction( void *handle, const char *name ) 54 | { 55 | return reinterpret_cast( GetProcAddress( 56 | reinterpret_cast( handle ), name ) ); 57 | } 58 | 59 | std::string GetFullPath( const std::string &path ) 60 | { 61 | std::string fullpath; 62 | DWORD size = MAX_PATH; 63 | fullpath.resize( size ); 64 | DWORD len = GetFullPathName( path.c_str( ), size, &fullpath[0], nullptr ); 65 | if( len == 0 ) 66 | fullpath.clear( ); 67 | else if( len >= size ) 68 | { 69 | fullpath.resize( len - 1 ); 70 | len = GetFullPathName( path.c_str( ), len, &fullpath[0], nullptr ); 71 | } 72 | 73 | return fullpath; 74 | } 75 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | MODULE_NAME: require.core 3 | DEPENDENCIES: $(System.DefaultWorkingDirectory)/dependencies 4 | GARRYSMOD_COMMON: $(System.DefaultWorkingDirectory)/dependencies/garrysmod_common 5 | GARRYSMOD_COMMON_BRANCH: master 6 | GARRYSMOD_COMMON_REPOSITORY: https://github.com/danielga/garrysmod_common.git 7 | PROJECT_GENERATOR_VERSION: 2 8 | REPOSITORY_DIR: $(System.DefaultWorkingDirectory) 9 | DISABLE_X86_64_BUILD: true 10 | trigger: 11 | tags: 12 | include: 13 | - '*' 14 | jobs: 15 | - job: windows 16 | displayName: Windows 17 | pool: 18 | name: Azure Pipelines 19 | vmImage: windows-2019 20 | timeoutInMinutes: 10 21 | variables: 22 | BOOTSTRAP_URL: https://raw.githubusercontent.com/danielga/garrysmod_common/master/build/bootstrap.ps1 23 | BUILD_SCRIPT: $(System.DefaultWorkingDirectory)/dependencies/garrysmod_common/build/build.ps1 24 | COMPILER_PLATFORM: vs2019 25 | PROJECT_OS: windows 26 | PREMAKE5: $(System.DefaultWorkingDirectory)/dependencies/windows/premake-core/premake5.exe 27 | PREMAKE5_URL: https://github.com/premake/premake-core/releases/download/v5.0.0-alpha15/premake-5.0.0-alpha15-windows.zip 28 | steps: 29 | - powershell: 'Invoke-Expression ((New-Object System.Net.WebClient).DownloadString("$env:BOOTSTRAP_URL"))' 30 | displayName: Bootstrap 31 | - powershell: '& "$env:BUILD_SCRIPT"' 32 | displayName: Build 33 | - task: CopyFiles@2 34 | displayName: 'Copy files to $(Build.ArtifactStagingDirectory)' 35 | inputs: 36 | SourceFolder: '$(System.DefaultWorkingDirectory)/projects/windows/vs2019' 37 | Contents: '*/Release/*.dll' 38 | TargetFolder: '$(Build.ArtifactStagingDirectory)' 39 | CleanTargetFolder: true 40 | flattenFolders: true 41 | preserveTimestamp: true 42 | - task: PublishBuildArtifacts@1 43 | displayName: 'Publish build artifacts' 44 | inputs: 45 | ArtifactName: windows 46 | - job: linux 47 | displayName: Linux 48 | pool: 49 | name: Azure Pipelines 50 | vmImage: ubuntu-16.04 51 | timeoutInMinutes: 10 52 | variables: 53 | BOOTSTRAP_URL: https://raw.githubusercontent.com/danielga/garrysmod_common/master/build/bootstrap.sh 54 | BUILD_SCRIPT: $(System.DefaultWorkingDirectory)/dependencies/garrysmod_common/build/build.sh 55 | COMPILER_PLATFORM: gmake 56 | PREMAKE5: $(System.DefaultWorkingDirectory)/dependencies/linux/premake-core/premake5 57 | PROJECT_OS: linux 58 | PREMAKE5_URL: https://github.com/premake/premake-core/releases/download/v5.0.0-alpha15/premake-5.0.0-alpha15-linux.tar.gz 59 | CC: gcc-9 60 | CXX: g++-9 61 | AR: gcc-ar-9 62 | NM: gcc-nm-9 63 | RANLIB: gcc-ranlib-9 64 | steps: 65 | - bash: 'curl -s -L "$BOOTSTRAP_URL" | bash' 66 | displayName: Bootstrap 67 | - bash: | 68 | sudo apt-get update && sudo apt-get install -y g++-9-multilib 69 | $BUILD_SCRIPT 70 | displayName: Build 71 | - task: CopyFiles@2 72 | displayName: 'Copy files to $(Build.ArtifactStagingDirectory)' 73 | inputs: 74 | SourceFolder: '$(System.DefaultWorkingDirectory)/projects/linux/gmake' 75 | Contents: '*/Release/*.dll' 76 | TargetFolder: '$(Build.ArtifactStagingDirectory)' 77 | CleanTargetFolder: true 78 | flattenFolders: true 79 | preserveTimestamp: true 80 | - task: PublishBuildArtifacts@1 81 | displayName: 'Publish build artifacts' 82 | inputs: 83 | ArtifactName: linux 84 | - job: publish 85 | displayName: Publish to GitHub Releases 86 | pool: 87 | name: Azure Pipelines 88 | vmImage: ubuntu-18.04 89 | timeoutInMinutes: 5 90 | dependsOn: 91 | - windows 92 | - linux 93 | steps: 94 | - task: DownloadBuildArtifacts@0 95 | displayName: 'Download build artifacts' 96 | inputs: 97 | downloadType: specific 98 | parallelizationLimit: 12 99 | - task: GitHubRelease@1 100 | displayName: 'Publish GitHub release $(build.sourceBranchName)' 101 | inputs: 102 | gitHubConnection: 'GitHub danielga' 103 | releaseNotesSource: inline 104 | assets: '$(System.ArtifactsDirectory)/**' 105 | addChangeLog: false 106 | -------------------------------------------------------------------------------- /includes/modules/require.lua: -------------------------------------------------------------------------------- 1 | local no_errors, returned_value = pcall(require, "string") 2 | if no_errors and returned_value == string then 3 | print("Default require() function seems to be working") 4 | return 5 | else 6 | ErrorNoHalt("Ignore the above error about being unable to include string.lua, it means Garry is being an ass\n") 7 | end 8 | 9 | assert(type(package.preload) == "table", "package.preload is not a table?") 10 | 11 | local io = {} 12 | package.preload.io = function() return io end 13 | package.preload.bit = function() return bit end 14 | package.preload.timer = function() return timer end 15 | package.preload.string = function() return string end 16 | package.preload.math = function() return math end 17 | package.preload.table = function() return table end 18 | package.preload.jit = function() return jit end 19 | package.preload["jit.opt"] = function() return jit.opt end 20 | package.preload["jit.util"] = function() return jit.util end 21 | package.preload.os = function() return os end 22 | package.preload.package = function() return package end 23 | 24 | require("require.core") 25 | 26 | local loadlib = loadlib 27 | local loadfile = loadfile 28 | 29 | local is_windows = system.IsWindows() 30 | local is_linux = system.IsLinux() 31 | local is_osx = system.IsOSX() 32 | local is_x64 = jit.arch == "x64" 33 | local separator = is_windows and "\\" or "/" 34 | 35 | local dll_prefix = CLIENT and "gmcl" or "gmsv" 36 | local dll_suffix = assert( 37 | (is_windows and (is_x64 and "win64" or "win32")) or 38 | (is_linux and (is_x64 and "linux64" or "linux")) or 39 | (is_osx and (is_x64 and "osx64" or "osx")) 40 | ) 41 | local libraries_extension = is_windows and "dll" or (is_linux and "so" or "dylib") 42 | 43 | local function loadfilemodule(name, file_path) 44 | local value, errstr, reason = loadfile(file_path) 45 | if not value then 46 | if reason == "open_fail" then 47 | return "\n\tno file '" .. file_path .. "'" 48 | elseif reason == "load_fail" then 49 | error("error loading module '" .. name .. "' from file '" .. file_path .. "':\n\t" .. errstr, 4) 50 | end 51 | end 52 | 53 | return value, errstr 54 | end 55 | 56 | local function loadlibmodule(name, file_path, entrypoint_name, isgmodmodule) 57 | if not file.Exists("lua/" .. (isgmodmodule and "bin" or "libraries") .. "/" .. file_path, "MOD") then 58 | return "\n\tno file '" .. file_path .. "'" 59 | end 60 | 61 | local result, msg, reason = loadlib(is_windows and string.gsub(file_path, "/", separator) or file_path, entrypoint_name, isgmodmodule) 62 | if not result then 63 | assert(reason == "load_fail" or reason == "no_func", reason .. " (" .. #reason .. ")") 64 | if reason == "load_fail" then 65 | error("error loading module '" .. name .. "' from file '" .. file_path .. "':\n\t" .. msg, 4) 66 | elseif reason == "no_func" then 67 | return "\n\tno module '" .. name .. "' in file '" .. file_path .. "'" 68 | end 69 | end 70 | 71 | return result 72 | end 73 | 74 | -- Garry's Mod modules always have priority to Lua ones 75 | package.loaders = { 76 | -- try to fetch the module from package.preload 77 | function(name) 78 | return package.preload[name] or "\n\tno field package.preload['" .. name .. "']" 79 | end, 80 | 81 | -- try to fetch the pure Lua module from lua/includes/modules ("à la" Garry's Mod) 82 | function(name) 83 | return loadfilemodule(name, "includes/modules/" .. name .. ".lua") 84 | end, 85 | 86 | -- try to fetch the pure Lua module from lua/libraries ("à la" Lua 5.1) 87 | function(name) 88 | return loadfilemodule(name, "libraries/" .. string.gsub(name, "%.", "/") .. ".lua") 89 | end, 90 | 91 | -- try to fetch the pure Lua module from lua/libraries//init.lua ("à la" Lua 5.1) 92 | function(name) 93 | return loadfilemodule(name, "libraries/" .. string.gsub(name, "%.", "/") .. "/init.lua") 94 | end, 95 | 96 | -- try to fetch the binary module from lua/bin ("à la" Garry's Mod) 97 | function(name) 98 | local file_path = dll_prefix .. "_" .. name .. "_" .. dll_suffix .. ".dll" 99 | local entrypoint_name = "gmod13_open" 100 | return loadlibmodule(name, file_path, entrypoint_name, true) 101 | end, 102 | 103 | -- try to fetch the binary module from lua/libraries ("à la" Lua 5.1) 104 | function(name) 105 | local file_path = string.gsub(name, "%.", "/") .. "." .. libraries_extension 106 | local entrypoint_name = "luaopen_" .. string.gsub(name, "%.", "_") 107 | return loadlibmodule(name, file_path, entrypoint_name, false) 108 | end, 109 | 110 | -- try to fetch the binary module from lua/libraries ("à la" Lua 5.1) 111 | function(name) 112 | local file_path = string.match(name, "^([^%.]*)") .. "." .. libraries_extension 113 | local entrypoint_name = "luaopen_" .. string.gsub(name, "%.", "_") 114 | return loadlibmodule(name, file_path, entrypoint_name, false) 115 | end 116 | } 117 | 118 | local sentinel 119 | do 120 | local function errorHandler() 121 | error("require() sentinel can't be indexed or updated", 2) 122 | end 123 | 124 | sentinel = newproxy and newproxy() or setmetatable({}, { 125 | __index = errorHandler, 126 | __newindex = errorHandler, 127 | __metatable = false 128 | }) 129 | end 130 | 131 | function require(name) 132 | assert(type(name) == "string", type(name)) 133 | 134 | local loaded_val = package.loaded[name] 135 | if loaded_val == sentinel then 136 | error("loop or previous error loading module '" .. name .. "'", 2) 137 | elseif loaded_val ~= nil then 138 | return loaded_val 139 | elseif _MODULES[name] then 140 | return _G[name] 141 | end 142 | 143 | local messages = {""} 144 | local loader = nil 145 | local luapath = nil 146 | 147 | for _, searcher in ipairs(package.loaders) do 148 | local result, path = searcher(name) 149 | if type(result) == "function" then 150 | loader = result 151 | luapath = path 152 | break 153 | elseif type(result) == "string" then 154 | messages[#messages + 1] = result 155 | end 156 | end 157 | 158 | if not loader then 159 | error("module '" .. name .. "' not found: " .. table.concat(messages), 2) 160 | else 161 | package.loaded[name] = sentinel 162 | 163 | if luapath ~= nil then 164 | PushLuaPath(luapath) 165 | end 166 | 167 | local result = loader(name) 168 | 169 | if luapath ~= nil then 170 | PopLuaPath() 171 | end 172 | 173 | if result ~= nil then 174 | package.loaded[name] = result 175 | end 176 | 177 | if package.loaded[name] == sentinel then 178 | package.loaded[name] = true 179 | end 180 | 181 | return package.loaded[name] 182 | end 183 | end 184 | -------------------------------------------------------------------------------- /source/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "loadlib.hpp" 8 | 9 | static SourceSDK::FactoryLoader lua_shared_loader( "lua_shared" ); 10 | static GarrysMod::Lua::ILuaShared *lua_shared = nullptr; 11 | 12 | static const size_t maximum_path_pushes = 100000; 13 | static size_t pushed_paths = 0; 14 | 15 | static int32_t PushError( GarrysMod::Lua::ILuaBase *LUA, int idxError, const char *reason ) 16 | { 17 | if( idxError < 0 ) 18 | idxError -= 1; 19 | 20 | LUA->PushNil( ); 21 | LUA->Push( idxError ); 22 | LUA->PushString( reason ); 23 | return 3; 24 | } 25 | 26 | static int32_t PushSystemError( GarrysMod::Lua::ILuaBase *LUA, const char *reason ) 27 | { 28 | LUA->PushString( GetSystemError( ).c_str( ) ); 29 | return PushError( LUA, -1, reason ); 30 | } 31 | 32 | static void SubstituteChar( std::string &path, char part, char sub ) 33 | { 34 | if( part == '\0' ) 35 | return; 36 | 37 | size_t pos = path.find( part ); 38 | while( pos != path.npos ) 39 | { 40 | path.erase( pos, 1 ); 41 | path.insert( pos, 1, sub ); 42 | pos = path.find( part, pos + 1 ); 43 | } 44 | } 45 | 46 | static void RemovePart( std::string &path, const std::string &part ) 47 | { 48 | size_t len = part.size( ), pos = path.find( part ); 49 | while( pos != path.npos ) 50 | { 51 | path.erase( pos, len ); 52 | pos = path.find( part, pos ); 53 | } 54 | } 55 | 56 | static bool HasWhitelistedExtension( const std::string &path ) 57 | { 58 | size_t extstart = path.rfind( '.' ); 59 | if( extstart != path.npos ) 60 | { 61 | size_t lastslash = path.rfind( GoodSeparator ); 62 | if( lastslash != path.npos && lastslash > extstart ) 63 | return false; 64 | 65 | std::string ext = path.substr( extstart + 1 ); 66 | return IsWhitelistedExtension( ext ); 67 | } 68 | 69 | return false; 70 | } 71 | 72 | LUA_FUNCTION( loadlib ) 73 | { 74 | LUA->CheckType( 1, GarrysMod::Lua::Type::STRING ); 75 | LUA->CheckType( 2, GarrysMod::Lua::Type::STRING ); 76 | LUA->CheckType( 3, GarrysMod::Lua::Type::BOOL ); 77 | 78 | { 79 | std::string lib = LUA->GetString( 1 ); 80 | SubstituteChar( lib, BadSeparator, GoodSeparator ); 81 | RemovePart( lib, CurrentDirectory ); 82 | LUA->PushString( lib.c_str( ) ); 83 | } 84 | 85 | const char *libpath = LUA->GetString( -1 ); 86 | if( std::strstr( libpath, ParentDirectory ) != nullptr ) 87 | LUA->ThrowError( "path provided has an unauthorized parent directory sequence" ); 88 | 89 | if( !HasWhitelistedExtension( libpath ) ) 90 | LUA->ThrowError( "path provided has an unauthorized extension" ); 91 | 92 | { 93 | std::string relpath = LUA->GetBool( 3 ) ? RelativePathToBin : RelativePathToLibraries; 94 | relpath += libpath; 95 | LUA->PushString( GetFullPath( relpath ).c_str( ) ); 96 | } 97 | 98 | const char *fullpath = LUA->GetString( -1 ); 99 | 100 | LUA->PushFormattedString( "LOADLIB: %s", libpath ); 101 | 102 | LUA->Push( -1 ); 103 | LUA->GetTable( GarrysMod::Lua::INDEX_REGISTRY ); 104 | 105 | GarrysMod::Lua::CFunc func = nullptr; 106 | if( !LUA->IsType( -1, GarrysMod::Lua::Type::NIL ) ) 107 | { 108 | void *libhandle = LUA->GetUserType( -1, GarrysMod::Lua::Type::USERDATA ); 109 | 110 | func = FindFunction( libhandle, LUA->GetString( 2 ) ); 111 | if( func == nullptr ) 112 | return PushSystemError( LUA, "no_func" ); 113 | } 114 | else 115 | { 116 | void *handle = OpenLibrary( fullpath ); 117 | if( handle == nullptr ) 118 | return PushSystemError( LUA, "load_fail" ); 119 | 120 | func = FindFunction( handle, LUA->GetString( 2 ) ); 121 | if( func == nullptr ) 122 | { 123 | CloseLibrary( handle ); 124 | return PushSystemError( LUA, "no_func" ); 125 | } 126 | 127 | LUA->Pop( 1 ); 128 | 129 | LUA->PushUserType( handle, GarrysMod::Lua::Type::USERDATA ); 130 | 131 | LUA->GetField( GarrysMod::Lua::INDEX_REGISTRY, "_LOADLIB" ); 132 | LUA->SetMetaTable( -2 ); 133 | 134 | LUA->SetTable( GarrysMod::Lua::INDEX_REGISTRY ); 135 | } 136 | 137 | LUA->PushCFunction( func ); 138 | return 1; 139 | } 140 | 141 | LUA_FUNCTION_STATIC( loadfile ) 142 | { 143 | if( !LUA->IsType( 1, GarrysMod::Lua::Type::STRING ) ) 144 | LUA->ThrowError( 145 | "This implementation of \"loadfile\" requires a filename to be provided!" ); 146 | 147 | if( LUA->Top( ) >= 2 ) 148 | { 149 | const char *mode = LUA->GetString( 2 ); 150 | if( std::strchr( mode, 'b' ) != nullptr ) 151 | LUA->ThrowError( 152 | "This implementation of \"loadfile\" doesn't accept binary Lua chunks!" ); 153 | } 154 | 155 | const char *path = LUA->GetString( 1 ); 156 | const bool hasenv = LUA->GetType( 3 ) > GarrysMod::Lua::Type::NIL; 157 | 158 | auto lua = static_cast( LUA ); 159 | 160 | GarrysMod::Lua::File *file = nullptr; 161 | 162 | std::string newpath; 163 | const char *relpath = lua->GetPath( ); 164 | if( relpath != nullptr ) 165 | { 166 | newpath = relpath; 167 | newpath += '/'; 168 | newpath += path; 169 | 170 | file = lua_shared->LoadFile( newpath.c_str( ), lua->GetPathID( ), lua->IsClient( ), true ); 171 | } 172 | 173 | if( file == nullptr ) 174 | { 175 | newpath = path; 176 | 177 | file = lua_shared->LoadFile( path, lua->GetPathID( ), lua->IsClient( ), true ); 178 | } 179 | 180 | if( file == nullptr ) 181 | { 182 | LUA->PushFormattedString( "cannot open %s: No such file or directory", path ); 183 | return PushError( LUA, -1, "open_fail" ); 184 | } 185 | 186 | lua->PushPath( newpath.c_str( ) ); 187 | const bool success = lua->RunStringEx( file->name.c_str( ), "", file->contents.c_str( ), 188 | false, false, false, false ); 189 | lua->PopPath( ); 190 | if( !success ) 191 | return PushError( LUA, -1, "load_fail" ); 192 | 193 | if( hasenv ) 194 | { 195 | LUA->Push( 3 ); 196 | LUA->SetFEnv( -2 ); 197 | } 198 | 199 | LUA->PushString( newpath.c_str( ) ); 200 | return 2; 201 | } 202 | 203 | LUA_FUNCTION_STATIC( PushLuaPath ) 204 | { 205 | if( pushed_paths >= maximum_path_pushes ) 206 | { 207 | LUA->PushBool( false ); 208 | return 1; 209 | } 210 | 211 | ++pushed_paths; 212 | const char *path = LUA->CheckString( 1 ); 213 | static_cast( LUA )->PushPath( path ); 214 | LUA->PushBool( true ); 215 | return 1; 216 | } 217 | 218 | LUA_FUNCTION_STATIC( PopLuaPath ) 219 | { 220 | if( pushed_paths == 0 ) 221 | { 222 | LUA->PushBool( false ); 223 | return 1; 224 | } 225 | 226 | --pushed_paths; 227 | static_cast( LUA )->PopPath( ); 228 | LUA->PushBool( true ); 229 | return 1; 230 | } 231 | 232 | GMOD_MODULE_OPEN( ) 233 | { 234 | lua_shared = 235 | lua_shared_loader.GetInterface( GMOD_LUASHARED_INTERFACE ); 236 | if( lua_shared == nullptr ) 237 | LUA->ThrowError( "unable to get ILuaShared!" ); 238 | 239 | LUA->PushCFunction( loadlib ); 240 | LUA->SetField( GarrysMod::Lua::INDEX_GLOBAL, "loadlib" ); 241 | 242 | LUA->PushCFunction( loadfile ); 243 | LUA->SetField( GarrysMod::Lua::INDEX_GLOBAL, "loadfile" ); 244 | 245 | LUA->PushCFunction( PushLuaPath ); 246 | LUA->SetField( GarrysMod::Lua::INDEX_GLOBAL, "PushLuaPath" ); 247 | 248 | LUA->PushCFunction( PopLuaPath ); 249 | LUA->SetField( GarrysMod::Lua::INDEX_GLOBAL, "PopLuaPath" ); 250 | 251 | return 0; 252 | } 253 | 254 | GMOD_MODULE_CLOSE( ) 255 | { 256 | LUA->PushNil( ); 257 | LUA->SetField( GarrysMod::Lua::INDEX_GLOBAL, "loadlib" ); 258 | 259 | LUA->PushNil( ); 260 | LUA->SetField( GarrysMod::Lua::INDEX_GLOBAL, "loadfile" ); 261 | 262 | LUA->PushNil( ); 263 | LUA->SetField( GarrysMod::Lua::INDEX_GLOBAL, "PushLuaPath" ); 264 | 265 | LUA->PushNil( ); 266 | LUA->SetField( GarrysMod::Lua::INDEX_GLOBAL, "PopLuaPath" ); 267 | 268 | return 0; 269 | } 270 | --------------------------------------------------------------------------------