diff --git a/AMBuildScript b/AMBuildScript index 9a5bf77..4f50230 100644 --- a/AMBuildScript +++ b/AMBuildScript @@ -38,6 +38,7 @@ PossibleSDKs = { 'insurgency': SDK('HL2SDKINSURGENCY', '2.insurgency', '19', 'INSURGENCY', WinLinuxMac, 'insurgency'), 'contagion': SDK('HL2SDKCONTAGION', '2.contagion', '14', 'CONTAGION', WinOnly, 'contagion'), 'bms': SDK('HL2SDKBMS', '2.bms', '10', 'BMS', WinLinux, 'bms'), + 's2': SDK('HL2SDKS2', '2.s2', '22', 'SOURCE2', WinOnly, 's2'), } def ResolveEnvPath(env, folder): @@ -112,6 +113,9 @@ class MMSConfig(object): cfg = builder.DetectCompilers() cxx = cfg.cxx + if builder.options.source2 == '1': + cfg.defines += ['SOURCE2_BUILD=1'] + if cxx.behavior == 'gcc': cfg.defines += [ 'stricmp=strcasecmp', @@ -246,7 +250,7 @@ class MMSConfig(object): compiler.cxxincludes += [ os.path.join(context.currentSourcePath), os.path.join(context.currentSourcePath, 'sourcehook'), - os.path.join(context.sourcePath, 'loader'), + os.path.join(context.sourcePath, 'public', 'loader'), ] defines = ['SE_' + self.sdks[i].define + '=' + self.sdks[i].code for i in self.sdks] @@ -278,13 +282,13 @@ class MMSConfig(object): else: compiler.defines += ['COMPILER_GCC'] - if sdk.name in ['css', 'hl2dm', 'dods', 'sdk2013', 'bms', 'tf2', 'l4d', 'nucleardawn', 'l4d2', 'dota']: + if sdk.name in ['css', 'hl2dm', 'dods', 'sdk2013', 'bms', 'tf2', 'l4d', 'nucleardawn', 'l4d2', 'dota', 's2']: if builder.target_platform in ['linux', 'mac']: compiler.defines += ['NO_HOOK_MALLOC', 'NO_MALLOC_OVERRIDE'] - + if sdk.name == 'csgo' and builder.target_platform == 'linux': compiler.linkflags += ['-lstdc++'] - + for path in paths: compiler.cxxincludes += [os.path.join(sdk.path, *path)] @@ -335,7 +339,7 @@ class MMSConfig(object): else: compiler.postlink += [compiler.Dep(os.path.join(lib_folder, 'tier1_i486.a'))] - if sdk.name in ['blade', 'insurgency', 'csgo', 'dota']: + if sdk.name in ['blade', 'insurgency', 'csgo', 'dota', 's2']: compiler.postlink += [compiler.Dep(os.path.join(lib_folder, 'interfaces_i486.a'))] binary = self.LibraryBuilder(compiler, name) @@ -345,7 +349,7 @@ class MMSConfig(object): compiler.linkflags[0:0] = ['-lm'] if sdk.name in ['css', 'hl2dm', 'dods', 'tf2', 'sdk2013', 'bms', 'nucleardawn', 'l4d2']: dynamic_libs = ['libtier0_srv.so', 'libvstdlib_srv.so'] - elif sdk.name in ['l4d', 'blade', 'insurgency', 'csgo', 'dota']: + elif sdk.name in ['l4d', 'blade', 'insurgency', 'csgo', 'dota', 's2']: dynamic_libs = ['libtier0.so', 'libvstdlib.so'] else: dynamic_libs = ['tier0_i486.so', 'vstdlib_i486.so'] @@ -354,7 +358,7 @@ class MMSConfig(object): dynamic_libs = ['libtier0.dylib', 'libvstdlib.dylib'] elif builder.target_platform == 'windows': libs = ['tier0', 'tier1', 'vstdlib'] - if sdk.name in ['swarm', 'blade', 'insurgency', 'csgo', 'dota']: + if sdk.name in ['swarm', 'blade', 'insurgency', 'csgo', 'dota', 's2']: libs.append('interfaces') for lib in libs: lib_path = os.path.join(sdk.path, 'lib', 'public', lib) + '.lib' @@ -391,9 +395,10 @@ if MMS.use_auto_versioning(): ) BuildScripts = [ - 'loader/AMBuilder', - 'core-legacy/AMBuilder', - 'core/AMBuilder', +# 'loader/AMBuilder', + 'loader2/AMBuilder', + 'core-legacy/AMBuilder', + 'core/AMBuilder', ] if builder.backend == 'amb2': diff --git a/configure.py b/configure.py index 3b3b313..e9a2e23 100644 --- a/configure.py +++ b/configure.py @@ -23,4 +23,6 @@ run.options.add_option('--enable-optimize', action='store_const', const='1', des run.options.add_option('-s', '--sdks', default='all', dest='sdks', help='Build against specified SDKs; valid args are "all", "present", or ' 'comma-delimited list of engine names (default: %default)') +run.options.add_option('--source2-build', action='store_const', const='1', dest='source2', + help='Build loader for Source2') run.Configure() diff --git a/core/ISmmPluginExt.h b/core/ISmmPluginExt.h index 4f245a5..31b6cb2 100644 --- a/core/ISmmPluginExt.h +++ b/core/ISmmPluginExt.h @@ -59,6 +59,7 @@ #define SOURCE_ENGINE_INSURGENCY 21 /**< Insurgency */ #define SOURCE_ENGINE_CONTAGION 22 /**< Contagion */ #define SOURCE_ENGINE_BMS 23 /**< Black Mesa Multiplayer */ +#define SOURCE_ENGINE_SOURCE2 24 #define METAMOD_PLAPI_VERSION 15 /**< Version of this header file */ #define METAMOD_PLAPI_NAME "ISmmPlugin" /**< Name of the plugin interface */ diff --git a/core/gamedll_bridge.cpp b/core/gamedll_bridge.cpp index 21d730f..8efe541 100644 --- a/core/gamedll_bridge.cpp +++ b/core/gamedll_bridge.cpp @@ -63,7 +63,11 @@ public: SourceHook::MemFuncInfo mfi; mfi.isVirtual = false; +#if SOURCE_ENGINE == SE_SOURCE2 + SourceHook::GetFuncInfo(&IServerGameDLL::Shutdown, mfi); +#else SourceHook::GetFuncInfo(&IServerGameDLL::DLLShutdown, mfi); +#endif assert(mfi.isVirtual); assert(mfi.vtbloffs == 0); assert(mfi.thisptroffs == 0); diff --git a/core/metamod.cpp b/core/metamod.cpp index 9d9fc0b..9581dd6 100644 --- a/core/metamod.cpp +++ b/core/metamod.cpp @@ -98,7 +98,6 @@ static ConVar *mm_basedir = NULL; static CreateInterfaceFn engine_factory = NULL; static CreateInterfaceFn physics_factory = NULL; static CreateInterfaceFn filesystem_factory = NULL; -static CGlobalVars *gpGlobals = NULL; static CHookManagerAutoGen g_SH_HookManagerAutoGen(&g_SourceHook); static META_RES last_meta_res; static IServerPluginCallbacks *vsp_callbacks = NULL; @@ -143,6 +142,21 @@ SourceMM::ISmmAPI *g_pMetamod = &g_Metamod; } \ } +#if SOURCE_ENGINE == SE_SOURCE2 +void meta_game_init(const CCommand &args) +{ + Handler_GameInit(); +} +void meta_level_init(const CCommand &args) +{ + Handler_LevelInit("dummy_level", "", "", "", false, false); +} +void meta_level_shutdown(const CCommand &args) +{ + Handler_LevelShutdown(); +} +#endif + /* Initialize everything here */ void mm_InitializeForLoad() @@ -157,6 +171,7 @@ mm_InitializeForLoad() */ in_first_level = true; +#if SOURCE_ENGINE != SE_SOURCE2 SourceHook::MemFuncInfo info; if (!provider->GetHookInfo(ProvidedHook_GameInit, &info)) @@ -179,6 +194,7 @@ mm_InitializeForLoad() } SH_MANUALHOOK_RECONFIGURE(SGD_LevelShutdown, info.vtblindex, info.vtbloffs, info.thisptroffs); SH_ADD_MANUALHOOK_STATICFUNC(SGD_LevelShutdown, server, Handler_LevelShutdown, true); +#endif } bool @@ -411,6 +427,15 @@ DoInitialPluginLoads() mm_LoadPlugins(filepath, vdfpath); } +ConVar *net_maxroutable; +ConCommand *map; +extern ConCommand meta_local_cmd; + +CON_COMMAND(meta_test, "") +{ + Msg("hi\n"); +} + void mm_StartupMetamod(bool is_vsp_load) { @@ -422,6 +447,17 @@ mm_StartupMetamod(bool is_vsp_load) METAMOD_VERSION, is_vsp_load ? "V" : ""); + net_maxroutable = g_pCVar->FindVar("net_maxroutable"); + map = g_pCVar->FindCommand("logaddress_add"); + meta_local_cmd.AddFlags(0); + meta_test_command.AddFlags(0); + CCommand cmd; + cmd.Tokenize("logaddress_add 192.168.5.9:33333"); + map->Dispatch(CCommandContext(0), cmd); + + Msg("logaddress_add is at 0x%p\n", map); + Msg("meta_local_cmd is at 0x%p\n", &meta_local_cmd); + metamod_version = provider->CreateConVar("metamod_version", METAMOD_VERSION, "Metamod:Source Version", @@ -773,7 +809,7 @@ size_t MetamodSource::PathFormat(char *buffer, size_t len, const char *fmt, ...) return mylen; } -#if SOURCE_ENGINE == SE_DOTA +#if SOURCE_ENGINE == SE_DOTA || SOURCE_ENGINE == SE_SOURCE2 void MetamodSource::ClientConPrintf(int clientIndex, const char *fmt, ...) { va_list ap; diff --git a/core/metamod.h b/core/metamod.h index 07a2b38..c7e1057 100644 --- a/core/metamod.h +++ b/core/metamod.h @@ -76,7 +76,7 @@ public: void *InterfaceSearch(CreateInterfaceFn fn, const char *iface, int max, int *ret); const char *GetBaseDir(); size_t PathFormat(char *buffer, size_t len, const char *fmt, ...); -#if SOURCE_ENGINE == SE_DOTA +#if SOURCE_ENGINE == SE_DOTA || SOURCE_ENGINE == SE_SOURCE2 // Shim void ClientConPrintf(int clientIndex, const char *fmt, ...); #endif diff --git a/core/metamod_console.cpp b/core/metamod_console.cpp index 2bd9c86..392422c 100644 --- a/core/metamod_console.cpp +++ b/core/metamod_console.cpp @@ -44,7 +44,7 @@ using namespace SourceHook; #define CLIENT_CONMSG g_Metamod.ClientConPrintf template -#if SOURCE_ENGINE == SE_DOTA +#if SOURCE_ENGINE == SE_DOTA || SOURCE_ENGINE == SE_SOURCE2 void CMDMSG(int client, const char *pMsg, Ts ... ts) #else void CMDMSG(edict_t *client, const char *pMsg, Ts ... ts) @@ -100,11 +100,13 @@ void CMDMSG(edict_t *client, const char *pMsg, Ts ... ts) #define MMS_ENGINE_NAME "Counter-Strike: Global Offensive (2012)" #elif SOURCE_ENGINE == SE_DOTA #define MMS_ENGINE_NAME "Dota 2 (2013)" +#elif SOURCE_ENGINE == SE_SOURCE2 +#define MMS_ENGINE_NAME "Source 2" #else #error "SOURCE_ENGINE not defined to a known value" #endif -#if SOURCE_ENGINE == SE_DOTA +#if SOURCE_ENGINE == SE_DOTA || SOURCE_ENGINE == SE_SOURCE2 static void ReplyCredits(int client = 0) #else static void ReplyCredits(edict_t *client = nullptr) @@ -118,7 +120,7 @@ static void ReplyCredits(edict_t *client = nullptr) CMDMSG(client, "http://www.metamodsource.net/\n"); } -#if SOURCE_ENGINE == SE_DOTA +#if SOURCE_ENGINE == SE_DOTA || SOURCE_ENGINE == SE_SOURCE2 static void ReplyVersion(int client = 0) #else static void ReplyVersion(edict_t *client = nullptr) @@ -698,7 +700,7 @@ bool Command_Meta(IMetamodSourceCommandInfo *info) return true; } -#if SOURCE_ENGINE == SE_DOTA +#if SOURCE_ENGINE == SE_DOTA || SOURCE_ENGINE == SE_SOURCE2 bool Command_ClientMeta(int client, IMetamodSourceCommandInfo *info) #else bool Command_ClientMeta(edict_t *client, IMetamodSourceCommandInfo *info) diff --git a/core/metamod_console.h b/core/metamod_console.h index ef3d529..20e5bd7 100644 --- a/core/metamod_console.h +++ b/core/metamod_console.h @@ -31,7 +31,7 @@ #include "metamod_provider.h" bool Command_Meta(IMetamodSourceCommandInfo *info); -#if SOURCE_ENGINE == SE_DOTA +#if SOURCE_ENGINE == SE_DOTA || SOURCE_ENGINE == SE_SOURCE2 bool Command_ClientMeta(int client, IMetamodSourceCommandInfo *info); #else bool Command_ClientMeta(edict_t *client, IMetamodSourceCommandInfo *info); diff --git a/core/provider/provider_ep2.cpp b/core/provider/provider_ep2.cpp index b5c2c2c..bb9336f 100644 --- a/core/provider/provider_ep2.cpp +++ b/core/provider/provider_ep2.cpp @@ -62,9 +62,18 @@ DLL_IMPORT ICommandLine *CommandLine(); /* Functions */ void CacheUserMessages(); void Detour_Error(const tchar *pMsg, ...); -#if SOURCE_ENGINE == SE_DOTA +#if SOURCE_ENGINE == SE_DOTA || SOURCE_ENGINE == SE_SOURCE2 void ClientCommand(CEntityIndex index, const CCommand &args); +#if SOURCE_ENGINE == SE_DOTA void LocalCommand_Meta(const CCommandContext &context, const CCommand &args); +#else +void LocalCommand_Meta(const CCommand &args); +#endif +#if SOURCE_ENGINE == SE_SOURCE2 +void meta_game_init(const CCommand &args); +void meta_level_init(const CCommand &args); +void meta_level_shutdown(const CCommand &args); +#endif #elif SOURCE_ENGINE >= SE_ORANGEBOX void ClientCommand(edict_t *pEdict, const CCommand &args); void LocalCommand_Meta(const CCommand &args); @@ -83,12 +92,21 @@ static jmp_buf usermsg_end; ICvar *icvar = NULL; IFileSystem *baseFs = NULL; IServerGameDLL *server = NULL; +#if SOURCE_ENGINE == SE_SOURCE2 +static ISource2ServerConfig *serverconfig = NULL; +#endif IVEngineServer *engine = NULL; IServerGameClients *gameclients = NULL; +CGlobalVars *gpGlobals = NULL; IMetamodSourceProvider *provider = &g_Ep1Provider; ConCommand meta_local_cmd("meta", LocalCommand_Meta, "Metamod:Source control options"); +#if SOURCE_ENGINE == SE_SOURCE2 +ConCommand _meta_game_init("meta_game_init", meta_game_init); +ConCommand _meta_level_init("meta_level_init", meta_level_init); +ConCommand _meta_level_shutdown("meta_level_shutdown", meta_level_shutdown); +#endif -#if SOURCE_ENGINE == SE_DOTA +#if SOURCE_ENGINE == SE_DOTA || SOURCE_ENGINE == SE_SOURCE2 SH_DECL_HOOK2_void(IServerGameClients, ClientCommand, SH_NOATTRIB, 0, CEntityIndex, const CCommand &); #elif SOURCE_ENGINE >= SE_ORANGEBOX SH_DECL_HOOK2_void(IServerGameClients, ClientCommand, SH_NOATTRIB, 0, edict_t *, const CCommand &); @@ -114,6 +132,10 @@ void BaseProvider::Notify_DLLInit_Pre(CreateInterfaceFn engineFactory, DisplayError("Could not find IVEngineServer! Metamod cannot load."); return; } +#if SOURCE_ENGINE == SE_SOURCE2 + gpGlobals = engine->GetServerGlobals(); + serverconfig = (ISource2ServerConfig *) ((serverFactory) (INTERFACEVERSION_SERVERCONFIG, NULL)); +#endif #if SOURCE_ENGINE >= SE_ORANGEBOX icvar = (ICvar *)((engineFactory)(CVAR_INTERFACE_VERSION, NULL)); #else @@ -125,12 +147,15 @@ void BaseProvider::Notify_DLLInit_Pre(CreateInterfaceFn engineFactory, return; } - +#if SOURCE_ENGINE == SE_SOURCE2 + gameclients = (IServerGameClients *)(serverFactory(INTERFACEVERSION_SERVERGAMECLIENTS, NULL)); +#else if ((gameclients = (IServerGameClients *)(serverFactory("ServerGameClients003", NULL))) == NULL) { gameclients = (IServerGameClients *)(serverFactory("ServerGameClients004", NULL)); } +#endif baseFs = (IFileSystem *)((engineFactory)(FILESYSTEM_INTERFACE_VERSION, NULL)); if (baseFs == NULL) @@ -142,7 +167,8 @@ void BaseProvider::Notify_DLLInit_Pre(CreateInterfaceFn engineFactory, g_pCVar = icvar; #endif - g_SMConVarAccessor.RegisterConCommandBase(&meta_local_cmd); + ConVar_Register(0, &g_SMConVarAccessor); + //g_SMConVarAccessor.RegisterConCommandBase(&meta_local_cmd); CacheUserMessages(); @@ -163,6 +189,7 @@ void BaseProvider::Notify_DLLInit_Pre(CreateInterfaceFn engineFactory, void BaseProvider::Notify_DLLShutdown_Pre() { + g_SMConVarAccessor.RemoveMetamodCommands(); #if SOURCE_ENGINE == SE_DARKMESSIAH @@ -180,8 +207,8 @@ bool BaseProvider::IsRemotePrintingAvailable() void BaseProvider::ClientConsolePrint(edict_t *pEdict, const char *message) { -#if SOURCE_ENGINE == SE_DOTA - int client = (int)(pEdict - g_Metamod.GetCGlobals()->pEdicts); +#if SOURCE_ENGINE == SE_DOTA || SOURCE_ENGINE == SE_SOURCE2 + int client = (int)(pEdict - gpGlobals->pEdicts); engine->ClientPrintf(client, message); #else engine->ClientPrintf(pEdict, message); @@ -264,6 +291,7 @@ bool BaseProvider::LogMessage(const char *buffer) bool BaseProvider::GetHookInfo(ProvidedHooks hook, SourceHook::MemFuncInfo *pInfo) { +#if SOURCE_ENGINE != SE_SOURCE2 SourceHook::MemFuncInfo mfi = {true, -1, 0, 0}; if (hook == ProvidedHook_LevelInit) @@ -282,6 +310,9 @@ bool BaseProvider::GetHookInfo(ProvidedHooks hook, SourceHook::MemFuncInfo *pInf *pInfo = mfi; return (mfi.thisptroffs >= 0); +#else + return false; +#endif } void BaseProvider::DisplayError(const char *fmt, ...) @@ -293,7 +324,11 @@ void BaseProvider::DisplayError(const char *fmt, ...) UTIL_FormatArgs(buffer, sizeof(buffer), fmt, ap); va_end(ap); +#if SOURCE_ENGINE == SE_SOURCE2 + Msg("ERROR: %s", buffer); +#else Error("%s", buffer); +#endif } void BaseProvider::DisplayWarning(const char *fmt, ...) @@ -325,7 +360,7 @@ void BaseProvider::UnregisterConCommandBase(ConCommandBase *pCommand) int BaseProvider::GetUserMessageCount() { -#if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_DOTA +#if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_DOTA || SOURCE_ENGINE == SE_SOURCE2 return -1; #else return (int)usermsgs_list.size(); @@ -371,7 +406,7 @@ void BaseProvider::GetGamePath(char *pszBuffer, int len) const char *BaseProvider::GetGameDescription() { - return server->GetGameDescription(); + return serverconfig->GetGameDescription(); } int BaseProvider::DetermineSourceEngine() @@ -416,6 +451,8 @@ int BaseProvider::DetermineSourceEngine() return SOURCE_ENGINE_DOTA; #elif SOURCE_ENGINE == SE_BMS return SOURCE_ENGINE_BMS; +#elif SOURCE_ENGINE == SE_SOURCE2 + return SOURCE_ENGINE_SOURCE2; #else #error "SOURCE_ENGINE not defined to a known value" #endif @@ -529,7 +566,7 @@ public: }; #endif -#if SOURCE_ENGINE == SE_DOTA +#if SOURCE_ENGINE == SE_DOTA// || SOURCE_ENGINE == SE_SOURCE2 void LocalCommand_Meta(const CCommandContext &context, const CCommand &args) { GlobCommand cmd(&args); @@ -545,7 +582,7 @@ void LocalCommand_Meta() Command_Meta(&cmd); } -#if SOURCE_ENGINE == SE_DOTA +#if SOURCE_ENGINE == SE_DOTA || SOURCE_ENGINE == SE_SOURCE2 void ClientCommand(CEntityIndex index, const CCommand &_cmd) { int client = index.Get(); @@ -568,7 +605,7 @@ void ClientCommand(edict_t *client) RETURN_META(MRES_IGNORED); } -#if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_DOTA +#if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_DOTA || SOURCE_ENGINE == SE_SOURCE2 void CacheUserMessages() { diff --git a/core/provider/provider_ep2.h b/core/provider/provider_ep2.h index b2f424c..c83522c 100644 --- a/core/provider/provider_ep2.h +++ b/core/provider/provider_ep2.h @@ -83,6 +83,7 @@ extern IVEngineServer *engine; extern IServerGameDLL *server; extern IServerGameClients *gameclients; extern ICvar *icvar; +extern CGlobalVars *gpGlobals; #endif //_INCLUDE_METAMOD_SOURCE_BASE_PROVIDER_H_ diff --git a/core/vsp_bridge.cpp b/core/vsp_bridge.cpp index ce76931..9842487 100644 --- a/core/vsp_bridge.cpp +++ b/core/vsp_bridge.cpp @@ -46,7 +46,7 @@ ConCommand *g_plugin_unload = NULL; bool g_bIsTryingToUnload; SourceHook::String vsp_desc("Metamod:Source"); -#if SOURCE_ENGINE == SE_DOTA +#if SOURCE_ENGINE == SE_DOTA || SOURCE_ENGINE == SE_SOURCE2 void InterceptPluginUnloads(const CCommandContext &context, const CCommand &args) #elif SOURCE_ENGINE >= SE_ORANGEBOX void InterceptPluginUnloads(const CCommand &args) @@ -57,7 +57,7 @@ void InterceptPluginUnloads() g_bIsTryingToUnload = true; } -#if SOURCE_ENGINE == SE_DOTA +#if SOURCE_ENGINE == SE_DOTA || SOURCE_ENGINE == SE_SOURCE2 void InterceptPluginUnloads_Post(const CCommandContext &context, const CCommand &args) #elif SOURCE_ENGINE >= SE_ORANGEBOX void InterceptPluginUnloads_Post(const CCommand &args) @@ -164,11 +164,14 @@ public: virtual void Unload() { + // Source2 doesn't have the Error function (nor VSP support). +#if SOURCE_ENGINE != SE_SOURCE2 if (g_bIsTryingToUnload) { Error("Metamod:Source cannot be unloaded from VSP mode. Use \"meta unload\" to unload specific plugins.\n"); return; } +#endif if (g_plugin_unload != NULL) { SH_REMOVE_HOOK_STATICFUNC(ConCommand, Dispatch, g_plugin_unload, InterceptPluginUnloads, false); diff --git a/loader2/AMBuilder b/loader2/AMBuilder new file mode 100644 index 0000000..a40c986 --- /dev/null +++ b/loader2/AMBuilder @@ -0,0 +1,20 @@ +# vim: set ts=8 sts=2 sw=2 tw=99 et ft=python: +import os.path + +def configure_library(name, linux_defines): + binary = MMS.Library(builder, name) + binary.compiler.cxxincludes += [os.path.join(builder.sourcePath, 'core', 'sourcehook')] + binary.sources += [ + 'loader2.cpp', + 'gamedll.cpp', + #'serverplugin.cpp', + 'utility.cpp', + ] + + if builder.target_platform == 'linux': + binary.compiler.defines += linux_defines + + nodes = builder.Add(binary) + MMS.binaries += [nodes] + +configure_library('server', ['LIB_PREFIX="lib"', 'LIB_SUFFIX=".so"']) diff --git a/loader2/gamedll.cpp b/loader2/gamedll.cpp new file mode 100644 index 0000000..c81e618 --- /dev/null +++ b/loader2/gamedll.cpp @@ -0,0 +1,607 @@ +/** + * vim: set ts=4 sw=4 tw=99 noet : + * ====================================================== + * Metamod:Source + * Copyright (C) 2004-2015 AlliedModders LLC and authors. + * All rights reserved. + * ====================================================== + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include +#include +#include +#include +#include +#include +#include "loader2.h" +#include +#include +#include "utility.h" +#include "gamedll.h" + +class ISource2Server; +class ISource2ServerConfig; + +#define MAX_GAMEDLL_PATHS 10 + +IGameDllBridge* gamedll_bridge = NULL; +static int game_info_detected = 0; +static char game_name[128]; +static char gamedll_paths[MAX_GAMEDLL_PATHS][PLATFORM_MAX_PATH]; +static void *gamedll_libs[MAX_GAMEDLL_PATHS]; +static unsigned int gamedll_path_count = 0; +static void *gamedll_lib = NULL; +static ISource2Server *gamedll_iface = NULL; +static ISource2ServerConfig *config_iface = NULL; +static QueryValveInterface gamedll_qvi = NULL; +static int gamedll_version = 0; +static int is2s_shutdown_index = -1; +static int is2sc_allowdedi_index = 20; +static char mm_path[PLATFORM_MAX_PATH]; + +#if defined _WIN32 +#define SERVER_NAME "server.dll" +#elif defined __APPLE__ +#define SERVER_NAME "server.dylib" +#elif defined __linux__ +#define SERVER_NAME "server" LIB_SUFFIX +#endif + +static bool mm_DetectGameInformation() +{ + char game_path[PLATFORM_MAX_PATH]; + + if (game_info_detected) + return game_info_detected == 1 ? true : false; + + game_info_detected = -1; + + mm_GetGameName(game_name, sizeof(game_name)); + + if (!mm_GetFileOfAddress((void*)mm_DetectGameInformation, mm_path, sizeof(mm_path))) + { + mm_LogFatal("Could not locate Metamod loader library path"); + return false; + } + + if (!mm_ResolvePath(game_name, game_path, sizeof(game_path))) + { + mm_LogFatal("Could not resolve path: %s", game_name); + return false; + } + + FILE *fp; + char gameinfo_path[PLATFORM_MAX_PATH]; + + mm_PathFormat(gameinfo_path, sizeof(gameinfo_path), "%s/gameinfo.gi", game_path); + if ((fp = fopen(gameinfo_path, "rt")) == NULL) + { + mm_LogFatal("Could not read file: %s", gameinfo_path); + return false; + } + + char temp_path[PLATFORM_MAX_PATH]; + char cur_path[PLATFORM_MAX_PATH]; + getcwd(cur_path, sizeof(cur_path)); + + char *ptr; + const char *lptr; + bool search = false; + char buffer[255], key[128], val[128]; + while (!feof(fp) && fgets(buffer, sizeof(buffer), fp) != NULL) + { + mm_TrimComments(buffer); + mm_TrimLeft(buffer); + mm_TrimRight(buffer); + + if (stricmp(buffer, "SearchPaths") == 0) + search = true; + + if (!search) + continue; + + mm_KeySplit(buffer, key, sizeof(key) - 1, val, sizeof(val) - 1); + if (stricmp(key, "Game") != 0 && stricmp(key, "GameBin") != 0) + continue; + + if (strncmp(val, "|gameinfo_path|", sizeof("|gameinfo_path|") - 1) == 0) + { + ptr = &val[sizeof("|gameinfo_path|") - 1]; + if (ptr[0] == '.') + ptr++; + lptr = game_path; + } + else + { + ptr = val; + lptr = cur_path; + } + + if (stricmp(key, "GameBin") == 0) + mm_PathFormat(temp_path, sizeof(temp_path), "%s/../../%s/" SERVER_NAME, lptr, ptr); + else if (!ptr[0]) + mm_PathFormat(temp_path, sizeof(temp_path), "%s/../../bin/win32/" SERVER_NAME, lptr); + else + mm_PathFormat(temp_path, sizeof(temp_path), "%s/../../%s/bin/win32/" SERVER_NAME, lptr, ptr); + + if (mm_PathCmp(mm_path, temp_path)) + continue; + + FILE *exists = fopen(temp_path, "rb"); + if (!exists) + continue; + fclose(exists); + + /* exists is still non-NULL... use this as a flag */ + for (unsigned int i = 0; i < gamedll_path_count; i++) + { + if (mm_PathCmp(gamedll_paths[i], temp_path)) + { + exists = NULL; + break; + } + } + + if (!exists) + continue; + + mm_Format(gamedll_paths[gamedll_path_count], + PLATFORM_MAX_PATH, + "%s", + temp_path); + gamedll_path_count++; + + if (gamedll_path_count == MAX_GAMEDLL_PATHS) + break; + } + fclose(fp); + + game_info_detected = 1; + + if (gamedll_path_count == 0) + { + mm_LogFatal("Could not detect any valid game paths in gameinfo.gi"); + return false; + } + + return true; +} + +static void mm_FreeCachedLibraries() +{ + for (unsigned int i = 0; i < gamedll_path_count; i++) + { + if (gamedll_libs[i] == NULL) + continue; + mm_UnloadLibrary(gamedll_libs[i]); + } +} + +static void mm_PatchDllInit(bool patch); +static void mm_PatchDllShutdown(); +static void mm_PatchAllowDedicated(bool patch); +static void mm_PatchConnect(bool patch); + +static void *is2s_orig_init = NULL; +static void *is2s_orig_shutdown = NULL; +static void *is2sc_orig_allowdedi = NULL; +static void *is2sc_orig_connect = NULL; + +class VEmptyClass +{ +}; + +gamedll_bridge_info g_bridge_info; + +// Rough start order +// CreateInterfaceFn (IS2SC) - hook Connect and AllowDedicatedServer +// IS2SC::Connect - save factory pointer. return orig. remove hook. +// IS2SC::AllowDedicatedServer - return true. remove hook. +// CreateInterfaceFn (IS2S) - hook Init and Shutdown +// IS2S::Init - do same as old ISGD::DLLInit, including core load. return orig. remove hook. +// IS2S::Shutdown - <-- this + +enum InitReturnVal_t +{ + INIT_FAILED = 0, + INIT_OK, + + INIT_LAST_VAL, +}; + +class ISource2ServerConfig +{ +public: + virtual bool Connect(QueryValveInterface factory) + { + g_bridge_info.engineFactory = factory; + g_bridge_info.fsFactory = factory; + g_bridge_info.physicsFactory = factory; + + + /* Call the original */ + bool result; + { + union + { + bool(VEmptyClass::*mfpnew)(QueryValveInterface factory); +#if defined _WIN32 + void *addr; + } u; + u.addr = is2sc_orig_connect; +#else + struct + { + void *addr; + intptr_t adjustor; + } s; + } u; + u.s.addr = is2sc_orig_connect; + u.s.adjustor = 0; +#endif + result = (((VEmptyClass *) config_iface)->*u.mfpnew)(factory); + } + + mm_PatchConnect(false); + + return result; + } + virtual bool AllowDedicatedServers(int universe) const + { + mm_PatchAllowDedicated(false); + return true; + } +}; + +class ISource2Server +{ +public: + virtual bool Connect(QueryValveInterface factory) { return true; } + virtual void Disconnect() {} + virtual void *QueryInterface(const char *pInterfaceName) { return nullptr; } + + virtual InitReturnVal_t Init() + { + char error[255]; + if (mm_backend == MMBackend_UNKNOWN) + { + mm_LogFatal("Could not detect engine version"); + } + else + { + if (!mm_LoadMetamodLibrary(mm_backend, error, sizeof(error))) + { + mm_LogFatal("Detected engine %d but could not load: %s", mm_backend, error); + } + else + { + typedef IGameDllBridge *(*GetGameDllBridge)(); + GetGameDllBridge get_bridge = (GetGameDllBridge)mm_GetProcAddress("GetGameDllBridge"); + if (get_bridge == NULL) + { + mm_UnloadMetamodLibrary(); + mm_LogFatal("Detected engine %d but could not find GetGameDllBridge callback", mm_backend); + } + else + { + gamedll_bridge = get_bridge(); + } + } + } + + if (gamedll_bridge) + { + g_bridge_info.pGlobals = nullptr;// pGlobals; + g_bridge_info.dllVersion = gamedll_version; + g_bridge_info.isgd = gamedll_iface; + g_bridge_info.gsFactory = gamedll_qvi; + g_bridge_info.vsp_listener_path = mm_path; + + strcpy(error, "Unknown error"); + if (!gamedll_bridge->DLLInit_Pre(&g_bridge_info, error, sizeof(error))) + { + gamedll_bridge = NULL; + mm_UnloadMetamodLibrary(); + mm_LogFatal("Unknown error loading Metamod for engine %d: %s", mm_backend, error); + } + } + + /* Call the original */ + InitReturnVal_t result; + { + union + { + InitReturnVal_t(VEmptyClass::*mfpnew)(); +#if defined _WIN32 + void *addr; + } u; + u.addr = is2s_orig_init; +#else + struct + { + void *addr; + intptr_t adjustor; + } s; + } u; + u.s.addr = is2s_orig_init; + u.s.adjustor = 0; +#endif + result = (((VEmptyClass *)gamedll_iface)->*u.mfpnew)(); + } + + /** + * :TODO: possible logic hole here, what happens if the gamedll REALLY returns false? + * I'm pretty sure we'll die horribly. + */ + + if (!result) + { + gamedll_bridge->Unload(); + mm_UnloadMetamodLibrary(); + gamedll_bridge = NULL; + } + else if (gamedll_bridge != NULL) + { + gamedll_bridge->DLLInit_Post(&is2s_shutdown_index); + assert(is2s_shutdown_index != -1); + mm_PatchDllShutdown(); + } + + mm_PatchDllInit(false); + + return result; + } + + virtual void Shutdown() + { + gamedll_bridge->Unload(); + gamedll_bridge = NULL; + mm_UnloadMetamodLibrary(); + + /* Call original function */ + { + union + { + void (VEmptyClass::*mfpnew)(); +#if defined _WIN32 + void *addr; + } u; + u.addr = is2s_orig_shutdown; +#else + struct + { + void *addr; + intptr_t adjustor; + } s; + } u; + u.s.addr = is2s_orig_shutdown; + u.s.adjustor = 0; +#endif + (((VEmptyClass *)gamedll_iface)->*u.mfpnew)(); + } + + mm_UnloadLibrary(gamedll_lib); + gamedll_lib = NULL; + } +}; + +static ISource2Server is2s_thunk; +static ISource2ServerConfig is2sc_thunk; + +static void mm_PatchDllInit(bool patch) +{ + void **vtable_src; + void **vtable_dest; + SourceHook::MemFuncInfo mfp; + + SourceHook::GetFuncInfo(&ISource2Server::Init, mfp); + + assert(mfp.isVirtual); + assert(mfp.thisptroffs == 0); + assert(mfp.vtbloffs == 0); + + vtable_src = (void **)*(void **)&is2s_thunk; + vtable_dest = (void **)*(void **)gamedll_iface; + + SourceHook::SetMemAccess(&vtable_dest[mfp.vtblindex], + sizeof(void*), + SH_MEM_READ|SH_MEM_WRITE|SH_MEM_EXEC); + + if (patch) + { + assert(is2s_orig_init == NULL); + is2s_orig_init = vtable_dest[mfp.vtblindex]; + vtable_dest[mfp.vtblindex] = vtable_src[mfp.vtblindex]; + } + else + { + assert(is2s_orig_init != NULL); + vtable_dest[mfp.vtblindex] = is2s_orig_init; + is2s_orig_init = NULL; + } +} + +static void mm_PatchDllShutdown() +{ + void **vtable_src; + void **vtable_dest; + SourceHook::MemFuncInfo mfp; + + mfp.isVirtual = false; + SourceHook::GetFuncInfo(&ISource2Server::Shutdown, mfp); + assert(mfp.isVirtual); + assert(mfp.thisptroffs == 0); + assert(mfp.vtbloffs == 0); + + vtable_src = (void **)*(void **)&is2s_thunk; + vtable_dest = (void **)*(void **)gamedll_iface; + + is2s_orig_shutdown = vtable_dest[is2s_shutdown_index]; + vtable_dest[is2s_shutdown_index] = vtable_src[mfp.vtblindex]; +} + +static void mm_PatchAllowDedicated(bool patch) +{ + void **vtable_src; + void **vtable_dest; + SourceHook::MemFuncInfo mfp; + + SourceHook::GetFuncInfo(&ISource2ServerConfig::AllowDedicatedServers, mfp); + + assert(mfp.isVirtual); + assert(mfp.thisptroffs == 0); + assert(mfp.vtbloffs == 0); + + vtable_src = (void **) *(void **) &is2sc_thunk; + vtable_dest = (void **) *(void **) config_iface; + + SourceHook::SetMemAccess(&vtable_dest[is2sc_allowdedi_index], + sizeof(void*), + SH_MEM_READ | SH_MEM_WRITE | SH_MEM_EXEC); + + if (patch) + { + assert(is2sc_orig_allowdedi == NULL); + is2sc_orig_allowdedi = vtable_dest[is2sc_allowdedi_index]; + vtable_dest[is2sc_allowdedi_index] = vtable_src[mfp.vtblindex]; + } + else + { + assert(is2sc_orig_allowdedi != NULL); + vtable_dest[is2sc_allowdedi_index] = is2sc_orig_allowdedi; + is2sc_orig_allowdedi = NULL; + } +} + +static void mm_PatchConnect(bool patch) +{ + void **vtable_src; + void **vtable_dest; + SourceHook::MemFuncInfo mfp; + + SourceHook::GetFuncInfo(&ISource2ServerConfig::Connect, mfp); + + assert(mfp.isVirtual); + assert(mfp.thisptroffs == 0); + assert(mfp.vtbloffs == 0); + + vtable_src = (void **) *(void **) &is2sc_thunk; + vtable_dest = (void **) *(void **) config_iface; + + SourceHook::SetMemAccess(&vtable_dest[mfp.vtblindex], + sizeof(void*), + SH_MEM_READ | SH_MEM_WRITE | SH_MEM_EXEC); + + if (patch) + { + assert(is2sc_orig_connect == NULL); + is2sc_orig_connect = vtable_dest[mfp.vtblindex]; + vtable_dest[mfp.vtblindex] = vtable_src[mfp.vtblindex]; + } + else + { + assert(is2sc_orig_connect != NULL); + vtable_dest[mfp.vtblindex] = is2sc_orig_connect; + is2sc_orig_connect = NULL; + } +} + +enum ServerIface +{ + Other, + ServerConfig, + Server +}; + +void * +mm_GameDllRequest(const char *name, int *ret) +{ + if (strncmp(name, "Source2ServerConfig", 19) == 0) + { + if (!mm_DetectGameInformation()) + { + if (ret != NULL) + *ret = 1; + return NULL; + } + + void *lib; + char error[255]; + void *ptr = NULL; + QueryValveInterface qvi; + for (unsigned int i = 0; i < gamedll_path_count; i++) + { + if (gamedll_libs[i] == NULL) + { + lib = mm_LoadLibrary(gamedll_paths[i], error, sizeof(error)); + if (lib == NULL) + continue; + gamedll_libs[i] = lib; + } + lib = gamedll_libs[i]; + qvi = (QueryValveInterface)mm_GetLibAddress(lib, "CreateInterface"); + if (qvi == NULL) + continue; + ptr = qvi(name, ret); + if (ptr != NULL) + { + gamedll_libs[i] = NULL; + break; + } + } + + if (ptr != NULL) + { + mm_FreeCachedLibraries(); + gamedll_lib = lib; + config_iface = (ISource2ServerConfig *) ptr; + gamedll_qvi = qvi; + + mm_PatchConnect(true); + mm_PatchAllowDedicated(true); + + if (ret != NULL) + *ret = 0; + return ptr; + } + } + else if (strncmp(name, "Source2Server0", 14) == 0) + { + gamedll_iface = (ISource2Server *)gamedll_qvi(name, ret); + gamedll_version = atoi(&name[13]); + mm_PatchDllInit(true); + + if (ret != NULL) + *ret = 0; + return gamedll_iface; + } + else if (gamedll_lib != NULL && gamedll_bridge == NULL) + { + return gamedll_qvi(name, ret); + } + else if (game_info_detected == 0) + { + mm_LogFatal("Received interface request too early: %s", name); + } + + if (ret != NULL) + *ret = 1; + return NULL; +} + diff --git a/loader2/gamedll.h b/loader2/gamedll.h new file mode 100644 index 0000000..1248ef8 --- /dev/null +++ b/loader2/gamedll.h @@ -0,0 +1,39 @@ +/** + * vim: set ts=4 : + * ====================================================== + * Metamod:Source + * Copyright (C) 2004-2015 AlliedModders LLC and authors. + * All rights reserved. + * ====================================================== + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_METAMOD_SOURCE_GAMEDLLS_H_ +#define _INCLUDE_METAMOD_SOURCE_GAMEDLLS_H_ + +#include "loader_bridge.h" + +extern void * +mm_GameDllRequest(const char *name, int *ret); + +extern IGameDllBridge* gamedll_bridge; + +#endif /* _INCLUDE_METAMOD_SOURCE_GAMEDLLS_H_ */ + diff --git a/loader2/loader2.cpp b/loader2/loader2.cpp new file mode 100644 index 0000000..e177f38 --- /dev/null +++ b/loader2/loader2.cpp @@ -0,0 +1,232 @@ +/** + * vim: set ts=4 sw=4 tw=99 noet : + * ====================================================== + * Metamod:Source + * Copyright (C) 2004-2015 AlliedModders LLC and authors. + * All rights reserved. + * ====================================================== + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include +#include +#include +#include +#include +#include +#include "loader2.h" +#include "gamedll.h" +#include "utility.h" +#if defined __APPLE__ +#include +#endif + +static HMODULE mm_library = NULL; +static char mm_fatal_logfile[PLATFORM_MAX_PATH] = "metamod-fatal.log"; +MetamodBackend mm_backend = MMBackend_Source2; + +extern void +mm_LogFatal(const char *message, ...) +{ + FILE *fp; + time_t t; + va_list ap; + char header[256]; + + fp = fopen(mm_fatal_logfile, "at"); + if (!fp && (fp = fopen("metamod-fatal.log", "at")) == NULL) + return; + + t = time(NULL); + strftime(header, sizeof(header), "%m/%d/%Y - %H:%M:%S", localtime(&t)); + fprintf(fp, "L %s: ", header); + + va_start(ap, message); + vfprintf(fp, message, ap); + va_end(ap); + + fprintf(fp, "\n"); + + fclose(fp); +} + +#if defined _WIN32 +#define LIBRARY_EXT ".dll" +#define LIBRARY_MINEXT ".dll" +#elif defined __APPLE__ +#define LIBRARY_EXT ".dylib" +#define LIBRARY_MINEXT ".dylib" +#elif defined __linux__ +#define LIBRARY_EXT LIB_SUFFIX +#define LIBRARY_MINEXT ".so" +#endif + +bool +mm_LoadMetamodLibrary(MetamodBackend backend, char *buffer, size_t maxlength) +{ + size_t len, temp_len; + char mm_path[PLATFORM_MAX_PATH * 2]; + + /* Get our path */ + if (!mm_GetFileOfAddress((void*)mm_GetFileOfAddress, mm_path, sizeof(mm_path))) + return false; + + len = strlen(mm_path); + temp_len = strlen("server" LIBRARY_EXT); + if (len < temp_len) + return false; + + /* Build log file name */ + mm_path[len - temp_len] = '\0'; + mm_Format(mm_fatal_logfile, + sizeof(mm_fatal_logfile), + "%smetamod-fatal.log", + mm_path); + + /* Replace server2.dll with the new binary we want */ + mm_Format(&mm_path[len - temp_len], + sizeof(mm_path) - (len - temp_len), + "metamod.2.s2" LIBRARY_MINEXT); + + mm_library = (HMODULE)mm_LoadLibrary(mm_path, buffer, maxlength); + + return (mm_library != NULL); +} + +void +mm_UnloadMetamodLibrary() +{ + mm_UnloadLibrary(mm_library); + mm_library = NULL; +} + +#if defined _WIN32 +#define EXPORT extern "C" __declspec(dllexport) +#elif defined __GNUC__ +#if __GNUC__ == 4 +#define EXPORT extern "C" __attribute__ ((visibility("default"))) +#else +#define EXPORT extern "C" +#endif +#endif + +EXPORT void * +CreateInterface(const char *name, int *ret) +{ + void *ptr; + if (gamedll_bridge == NULL) + { + /* Load as gamedll */ + ptr = mm_GameDllRequest(name, ret); + } + else + { + /* If we've got a gamedll bridge, forward the request. */ + return gamedll_bridge->QueryInterface(name, ret); + } + + if (ret != NULL) + *ret = (ptr != NULL) ? 0 : 1; + + return ptr; +} + +void * +mm_GetProcAddress(const char *name) +{ + return mm_GetLibAddress(mm_library, name); +} + +void +mm_GetGameName(char *buffer, size_t size) +{ + buffer[0] = '\0'; + +#if defined _WIN32 + static char game[128]; + + LPWSTR pCmdLine = GetCommandLineW(); + int argc; + LPWSTR *wargv = CommandLineToArgvW(pCmdLine, &argc); + for (int i = 0; i < argc; ++i) + { + if (wcscmp(wargv[i], L"-game") != 0) + continue; + + if (++i >= argc) + break; + + wcstombs(buffer, wargv[i], size); + buffer[size-1] = '\0'; + break; + } + + LocalFree(wargv); + +#elif defined __APPLE__ + int argc = *_NSGetArgc(); + char **argv = *_NSGetArgv(); + for (int i = 0; i < argc; ++i) + { + if (strcmp(argv[i], "-game") != 0) + continue; + + if (++i >= argc) + break; + + strncpy(buffer, argv[i], size); + buffer[size-1] = '\0'; + break; + } + +#elif defined __linux__ + FILE *pFile = fopen("/proc/self/cmdline", "rb"); + if (pFile) + { + char *arg = NULL; + size_t argsize = 0; + bool bNextIsGame = false; + + while (getdelim(&arg, &argsize, 0, pFile) != -1) + { + if (bNextIsGame) + { + strncpy(buffer, arg, size); + buffer[size-1] = '\0'; + break; + } + + if (strcmp(arg, "-game") == 0) + { + bNextIsGame = true; + } + } + + free(arg); + fclose(pFile); + } +#else +#error unsupported platform +#endif + + if (buffer[0] == 0) + { + strncpy(buffer, "dota", size); + } +} + diff --git a/loader2/loader2.h b/loader2/loader2.h new file mode 100644 index 0000000..ea16fb9 --- /dev/null +++ b/loader2/loader2.h @@ -0,0 +1,100 @@ +/** + * vim: set ts=4 sw=4 tw=99 noet : + * ====================================================== + * Metamod:Source + * Copyright (C) 2004-2015 AlliedModders LLC and authors. + * All rights reserved. + * ====================================================== + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#ifndef _INCLUDE_METAMOD_SOURCE_LOADER_H_ +#define _INCLUDE_METAMOD_SOURCE_LOADER_H_ + +// System +#define SH_SYS_WIN32 1 +#define SH_SYS_LINUX 2 +#define SH_SYS_APPLE 3 + +// Platform +#define SH_XP_POSIX 10 +#define SH_XP_WINAPI 20 + +// Compiler +#define SH_COMP_GCC 1 +#define SH_COMP_MSVC 2 + +#if defined WIN32 +#define SH_SYS SH_SYS_WIN32 +#define SH_XP SH_XP_WINAPI +#define SH_COMP SH_COMP_MSVC +#define WINDOWS_LEAN_AND_MEAN +#include +#include +#define PLATFORM_MAX_PATH MAX_PATH +#define PATH_SEP_STR "\\" +#define PATH_SEP_CHAR '\\' +#define ALT_SEP_CHAR '/' +#elif defined __linux__ || defined __APPLE__ +#if defined __linux__ +#define SH_SYS SH_SYS_LINUX +#elif defined __APPLE__ +#define SH_SYS SH_SYS_APPLE +#endif +#define SH_XP SH_XP_POSIX +#define SH_COMP SH_COMP_GCC +#include +#include +#include +#include +#if SH_SYS == SH_SYS_APPLE +#include +#endif +typedef void * HMODULE; +#define PLATFORM_MAX_PATH PATH_MAX +#define PATH_SEP_STR "/" +#define PATH_SEP_CHAR '/' +#define ALT_SEP_CHAR '\\' +#else +#error "OS detection failed" +#endif + +#include +#include "loader_bridge.h" + +#define SH_PTRSIZE sizeof(void*) + +extern bool +mm_LoadMetamodLibrary(MetamodBackend backend, char *buffer, size_t maxlength); + +extern void * +mm_GetProcAddress(const char *name); + +extern void +mm_UnloadMetamodLibrary(); + +extern void +mm_LogFatal(const char *message, ...); + +extern void +mm_GetGameName(char *buffer, size_t size); + +extern MetamodBackend mm_backend; + +#endif /* _INCLUDE_METAMOD_SOURCE_LOADER_H_ */ + diff --git a/loader2/utility.cpp b/loader2/utility.cpp new file mode 100644 index 0000000..ce75c5c --- /dev/null +++ b/loader2/utility.cpp @@ -0,0 +1,546 @@ +/** + * vim: set ts=4 : + * ====================================================== + * Metamod:Source + * Copyright (C) 2004-2015 AlliedModders LLC and authors. + * All rights reserved. + * ====================================================== + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + * Version: $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include "loader2.h" +#include "utility.h" + +#if defined __linux__ +#include + +#define PAGE_SIZE 4096 +#define PAGE_ALIGN_UP(x) ((x + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1)) +#elif defined __APPLE__ +#include +#endif + +#if defined _WIN32 +static void +mm_GetPlatformError(char *buffer, size_t maxlength) +{ + DWORD dw = GetLastError(); + FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)buffer, + maxlength, + NULL); +} +#endif + + +size_t +mm_FormatArgs(char *buffer, size_t maxlength, const char *fmt, va_list params) +{ + size_t len = vsnprintf(buffer, maxlength, fmt, params); + + if (len >= maxlength) + { + len = maxlength - 1; + buffer[len] = '\0'; + } + + return len; +} + +size_t +mm_Format(char *buffer, size_t maxlength, const char *fmt, ...) +{ + size_t len; + va_list ap; + + va_start(ap, fmt); + len = mm_FormatArgs(buffer, maxlength, fmt, ap); + va_end(ap); + + return len; +} + +size_t +mm_PathFormat(char *buffer, size_t maxlen, const char *fmt, ...) +{ + size_t len; + va_list ap; + + va_start(ap, fmt); + len = mm_FormatArgs(buffer, maxlen, fmt, ap); + va_end(ap); + + for (size_t i = 0; i < len; i++) + { + if (buffer[i] == ALT_SEP_CHAR) + buffer[i] = PATH_SEP_CHAR; + } + + return len; +} + +void +mm_TrimLeft(char *buffer) +{ + /* Let's think of this as our iterator */ + char *i = buffer; + + /* Make sure the buffer isn't null */ + if (i && *i) + { + /* Add up number of whitespace characters */ + while(isspace((unsigned char) *i)) + i++; + + /* If whitespace chars in buffer then adjust string so first non-whitespace char is at start of buffer */ + if (i != buffer) + memmove(buffer, i, (strlen(i) + 1) * sizeof(char)); + } +} + +void +mm_TrimRight(char *buffer) +{ + /* Make sure buffer isn't null */ + if (buffer) + { + size_t len = strlen(buffer); + + /* Loop through buffer backwards while replacing whitespace chars with null chars */ + for (size_t i = len - 1; i < len; i--) + { + if (isspace((unsigned char) buffer[i])) + buffer[i] = '\0'; + else + break; + } + } +} + +/* :TODO: this should skip string literals */ +void +mm_TrimComments(char *buffer) +{ + int num_sc = 0; + size_t len = strlen(buffer); + if (buffer) + { + for (int i = len - 1; i >= 0; i--) + { + if (buffer[i] == '/') + { + if (++num_sc >= 2 && i==0) + { + buffer[i] = '\0'; + return; + } + } + else + { + if (num_sc >= 2) + { + buffer[i] = '\0'; + return; + } + num_sc = 0; + } + /* size_t won't go below 0, manually break out */ + if (i == 0) + break; + + } + } +} + +void +mm_KeySplit(const char *str, char *buf1, size_t len1, char *buf2, size_t len2) +{ + size_t start; + size_t len = strlen(str); + + for (start = 0; start < len; start++) + { + if (!isspace(str[start])) + break; + } + + size_t end; + for (end = start; end < len; end++) + { + if (isspace(str[end])) + break; + } + + size_t i, c = 0; + for (i = start; i < end; i++, c++) + { + if (c >= len1) + break; + buf1[c] = str[i]; + } + buf1[c] = '\0'; + + for (start = end; start < len; start++) + { + if (!isspace(str[start])) + break; + } + + for (c = 0; start < len; start++, c++) + { + if (c >= len2) + break; + buf2[c] = str[start]; + } + buf2[c] = '\0'; +} + +bool +mm_PathCmp(const char *path1, const char *path2) +{ + size_t pos1 = 0, pos2 = 0; + + while (true) + { + if (path1[pos1] == '\0' || path2[pos2] == '\0') + return (path1[pos1] == path2[pos2]); + + if (path1[pos1] == PATH_SEP_CHAR) + { + if (path2[pos2] != PATH_SEP_CHAR) + return false; + + /* Look for extra path chars */ + while (path1[++pos1]) + { + if (path1[pos1] != PATH_SEP_CHAR) + break; + } + while (path2[++pos2]) + { + if (path2[pos2] != PATH_SEP_CHAR) + break; + } + continue; + } + + /* If we're at a different non-alphanumeric, the next character MUST match */ + if ((((unsigned)path1[pos1] & 0x80) && path1[pos1] != path2[pos2]) + || + (!isalpha(path1[pos1]) && (path1[pos1] != path2[pos2])) + ) + { + return false; + } + + #ifdef WIN32 + if (toupper(path1[pos1]) != toupper(path2[pos2])) + #else + if (path1[pos1] != path2[pos2]) + #endif + { + return false; + } + + pos1++; + pos2++; + } +} + +bool +mm_ResolvePath(const char *path, char *buffer, size_t maxlength) +{ + char tmp[PLATFORM_MAX_PATH]; + mm_Format(tmp, sizeof(tmp), "../../%s", path); +#if defined _WIN32 + return _fullpath(buffer, tmp, maxlength) != NULL; +#elif defined __linux__ || defined __APPLE__ + assert(maxlength >= PATH_MAX); + return realpath(tmp, buffer) != NULL; +#endif +} + +void * +mm_LoadLibrary(const char *path, char *buffer, size_t maxlength) +{ + void *lib; + +#if defined _WIN32 + lib = (void*)LoadLibrary(path); + + if (lib == NULL) + { + mm_GetPlatformError(buffer, maxlength); + return NULL; + } +#elif defined __linux__ || defined __APPLE__ + lib = dlopen(path, RTLD_NOW); + + if (lib == NULL) + { + mm_Format(buffer, maxlength, "%s", dlerror()); + return NULL; + } +#endif + + return lib; +} + +void * +mm_GetLibAddress(void *lib, const char *name) +{ +#if defined _WIN32 + return GetProcAddress((HMODULE)lib, name); +#elif defined __linux__ || defined __APPLE__ + return dlsym(lib, name); +#endif +} + +void +mm_UnloadLibrary(void *lib) +{ +#if defined _WIN32 + FreeLibrary((HMODULE)lib); +#elif defined __linux__ || defined __APPLE__ + dlclose(lib); +#endif +} + +bool +mm_GetFileOfAddress(void *pAddr, char *buffer, size_t maxlength) +{ +#if defined _WIN32 + MEMORY_BASIC_INFORMATION mem; + if (!VirtualQuery(pAddr, &mem, sizeof(mem))) + return false; + if (mem.AllocationBase == NULL) + return false; + HMODULE dll = (HMODULE)mem.AllocationBase; + GetModuleFileName(dll, (LPTSTR)buffer, maxlength); +#elif defined __linux__ || defined __APPLE__ + Dl_info info; + if (!dladdr(pAddr, &info)) + return false; + if (!info.dli_fbase || !info.dli_fname) + return false; + const char *dllpath = info.dli_fname; + snprintf(buffer, maxlength, "%s", dllpath); +#endif + return true; +} + +struct DynLibInfo +{ + void *baseAddress; + size_t memorySize; +}; + +static bool +mm_GetLibraryInfo(const void *libPtr, DynLibInfo &lib) +{ + uintptr_t baseAddr; + + if (libPtr == NULL) + { + return false; + } + +#ifdef _WIN32 + + MEMORY_BASIC_INFORMATION info; + IMAGE_DOS_HEADER *dos; + IMAGE_NT_HEADERS *pe; + IMAGE_FILE_HEADER *file; + IMAGE_OPTIONAL_HEADER *opt; + + if (!VirtualQuery(libPtr, &info, sizeof(MEMORY_BASIC_INFORMATION))) + { + return false; + } + + baseAddr = reinterpret_cast(info.AllocationBase); + + /* All this is for our insane sanity checks :o */ + dos = reinterpret_cast(baseAddr); + pe = reinterpret_cast(baseAddr + dos->e_lfanew); + file = &pe->FileHeader; + opt = &pe->OptionalHeader; + + /* Check PE magic and signature */ + if (dos->e_magic != IMAGE_DOS_SIGNATURE || pe->Signature != IMAGE_NT_SIGNATURE || opt->Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC) + { + return false; + } + + /* Check architecture, which is 32-bit/x86 right now + * Should change this for 64-bit if Valve gets their act together + */ + if (file->Machine != IMAGE_FILE_MACHINE_I386) + { + return false; + } + + /* For our purposes, this must be a dynamic library */ + if ((file->Characteristics & IMAGE_FILE_DLL) == 0) + { + return false; + } + + /* Finally, we can do this */ + lib.memorySize = opt->SizeOfImage; + +#elif defined __linux__ + + Dl_info info; + Elf32_Ehdr *file; + Elf32_Phdr *phdr; + uint16_t phdrCount; + + if (!dladdr(libPtr, &info)) + { + return false; + } + + if (!info.dli_fbase || !info.dli_fname) + { + return false; + } + + /* This is for our insane sanity checks :o */ + baseAddr = reinterpret_cast(info.dli_fbase); + file = reinterpret_cast(baseAddr); + + /* Check ELF magic */ + if (memcmp(ELFMAG, file->e_ident, SELFMAG) != 0) + { + return false; + } + + /* Check ELF version */ + if (file->e_ident[EI_VERSION] != EV_CURRENT) + { + return false; + } + + /* Check ELF architecture, which is 32-bit/x86 right now + * Should change this for 64-bit if Valve gets their act together + */ + if (file->e_ident[EI_CLASS] != ELFCLASS32 || file->e_machine != EM_386 || file->e_ident[EI_DATA] != ELFDATA2LSB) + { + return false; + } + + /* For our purposes, this must be a dynamic library/shared object */ + if (file->e_type != ET_DYN) + { + return false; + } + + phdrCount = file->e_phnum; + phdr = reinterpret_cast(baseAddr + file->e_phoff); + + for (uint16_t i = 0; i < phdrCount; i++) + { + Elf32_Phdr &hdr = phdr[i]; + + /* We only really care about the segment with executable code */ + if (hdr.p_type == PT_LOAD && hdr.p_flags == (PF_X|PF_R)) + { + /* From glibc, elf/dl-load.c: + * c->mapend = ((ph->p_vaddr + ph->p_filesz + GLRO(dl_pagesize) - 1) + * & ~(GLRO(dl_pagesize) - 1)); + * + * In glibc, the segment file size is aligned up to the nearest page size and + * added to the virtual address of the segment. We just want the size here. + */ + lib.memorySize = PAGE_ALIGN_UP(hdr.p_filesz); + break; + } + } + +#elif defined __APPLE__ + + Dl_info info; + struct mach_header *file; + struct segment_command *seg; + uint32_t cmd_count; + + if (!dladdr(libPtr, &info)) + { + return false; + } + + if (!info.dli_fbase || !info.dli_fname) + { + return false; + } + + /* This is for our insane sanity checks :o */ + baseAddr = (uintptr_t)info.dli_fbase; + file = (struct mach_header *)baseAddr; + + /* Check Mach-O magic */ + if (file->magic != MH_MAGIC) + { + return false; + } + + /* Check architecture (32-bit/x86) */ + if (file->cputype != CPU_TYPE_I386 || file->cpusubtype != CPU_SUBTYPE_I386_ALL) + { + return false; + } + + /* For our purposes, this must be a dynamic library */ + if (file->filetype != MH_DYLIB) + { + return false; + } + + cmd_count = file->ncmds; + seg = (struct segment_command *)(baseAddr + sizeof(struct mach_header)); + + /* Add up memory sizes of mapped segments */ + for (uint32_t i = 0; i < cmd_count; i++) + { + if (seg->cmd == LC_SEGMENT) + { + lib.memorySize += seg->vmsize; + } + + seg = (struct segment_command *)((uintptr_t)seg + seg->cmdsize); + } + +#endif + + lib.baseAddress = reinterpret_cast(baseAddr); + + return true; +} diff --git a/loader2/utility.h b/loader2/utility.h new file mode 100644 index 0000000..9d8b40a --- /dev/null +++ b/loader2/utility.h @@ -0,0 +1,70 @@ +/** + * vim: set ts=4 : + * ====================================================== + * Metamod:Source + * Copyright (C) 2004-2015 AlliedModders LLC and authors. + * All rights reserved. + * ====================================================== + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_METAMOD_SOURCE_LOADER_UTILITY_H_ +#define _INCLUDE_METAMOD_SOURCE_LOADER_UTILITY_H_ + +#include + +extern size_t +mm_Format(char *buffer, size_t maxlength, const char *fmt, ...); + +extern void * +mm_LoadLibrary(const char *path, char *buffer, size_t maxlength); + +extern void * +mm_GetLibAddress(void *lib, const char *name); + +extern void +mm_UnloadLibrary(void *lib); + +extern bool +mm_ResolvePath(const char *path, char *buffer, size_t maxlength); + +extern size_t +mm_PathFormat(char *buffer, size_t len, const char *fmt, ...); + +extern void +mm_TrimLeft(char *buffer); + +extern void +mm_TrimRight(char *buffer); + +extern void +mm_TrimComments(char *buffer); + +extern void +mm_KeySplit(const char *str, char *buf1, size_t len1, char *buf2, size_t len2); + +extern bool +mm_PathCmp(const char *path1, const char *path2); + +extern bool +mm_GetFileOfAddress(void *pAddr, char *buffer, size_t maxlength); + +#endif /* _INCLUDE_METAMOD_SOURCE_LOADER_UTILITY_H_ */ + diff --git a/public/loader_bridge.h b/public/loader_bridge.h new file mode 100644 index 0000000..07570ce --- /dev/null +++ b/public/loader_bridge.h @@ -0,0 +1,70 @@ +/** + * vim: set ts=4 sw=4 tw=99 noet : + * ====================================================== + * Metamod:Source + * Copyright (C) 2004-2009 AlliedModders LLC and authors. + * All rights reserved. + * ====================================================== + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#ifndef _INCLUDE_METAMOD_SOURCE_LOADER_BRIDGE_H_ +#define _INCLUDE_METAMOD_SOURCE_LOADER_BRIDGE_H_ + +typedef void* (*QueryValveInterface)(const char *pName, int *pReturnCode); +class IServerPluginCallbacks; + +struct vsp_bridge_info +{ + QueryValveInterface engineFactory; + QueryValveInterface gsFactory; + IServerPluginCallbacks * vsp_callbacks; + unsigned int vsp_version; +}; + +class IVspBridge +{ +public: + virtual bool Load(const vsp_bridge_info *info, char *buffer, size_t maxlength) = 0; + virtual void Unload() = 0; + virtual const char *GetDescription() = 0; +}; + +struct gamedll_bridge_info +{ + QueryValveInterface engineFactory; + QueryValveInterface fsFactory; + QueryValveInterface physicsFactory; + QueryValveInterface gsFactory; + void * pGlobals; + unsigned int dllVersion; + void * isgd; + const char * vsp_listener_path; +}; + +class IGameDllBridge +{ +public: + virtual bool DLLInit_Pre(const gamedll_bridge_info *info, char *buffer, size_t maxlength) = 0; + virtual void DLLInit_Post(int *isgdUnload) = 0; + virtual void *QueryInterface(const char *name, int *ret) = 0; + virtual void Unload() = 0; +}; + +#endif /* _INCLUDE_METAMOD_SOURCE_LOADER_BRIDGE_H_ */ + diff --git a/public/loader_public.h b/public/loader_public.h new file mode 100644 index 0000000..18c8536 --- /dev/null +++ b/public/loader_public.h @@ -0,0 +1,58 @@ +/** + * vim: set ts=4 sw=4 tw=99 noet : + * ====================================================== + * Metamod:Source + * Copyright (C) 2004-2010 AlliedModders LLC and authors. + * All rights reserved. + * ====================================================== + * + * This software is provided 'as-is', without any express or implied warranty. + * In no event will the authors be held liable for any damages arising from + * the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software in a + * product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#ifndef _INCLUDE_METAMOD_LOADER_PUBLIC_H_ +#define _INCLUDE_METAMOD_LOADER_PUBLIC_H_ + +enum MetamodBackend +{ + MMBackend_Episode1 = 0, + MMBackend_DarkMessiah, + MMBackend_Episode2, + MMBackend_BloodyGoodTime, + MMBackend_EYE, + MMBackend_CSS, + MMBackend_Episode2Valve_OBSOLETE, + MMBackend_Left4Dead, + MMBackend_Left4Dead2, + MMBackend_AlienSwarm, + MMBackend_Portal2, + MMBackend_CSGO, + MMBackend_DOTA, + MMBackend_HL2DM, + MMBackend_DODS, + MMBackend_TF2, + MMBackend_NuclearDawn, + MMBackend_SDK2013, + MMBackend_Blade, + MMBackend_Insurgency, + MMBackend_Contagion, + MMBackend_BMS, + MMBackend_Source2, + + MMBackend_UNKNOWN +}; + +#endif // _INCLUDE_METAMOD_LOADER_PUBLIC_H_ \ No newline at end of file