From b86f66d459dc53a646b84d718672585048cd7b72 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Sat, 15 Nov 2008 18:29:24 -0600 Subject: [PATCH] Initial sketch of new unified loader (vsp only currently). --- loader/Makefile | 74 ++++++++++++ loader/loader.cpp | 221 +++++++++++++++++++++++++++++++++++ loader/loader.h | 48 ++++++++ loader/loader_bridge.h | 24 ++++ loader/serverplugin.cpp | 248 ++++++++++++++++++++++++++++++++++++++++ loader/serverplugin.h | 10 ++ 6 files changed, 625 insertions(+) create mode 100644 loader/Makefile create mode 100644 loader/loader.cpp create mode 100644 loader/loader.h create mode 100644 loader/loader_bridge.h create mode 100644 loader/serverplugin.cpp create mode 100644 loader/serverplugin.h diff --git a/loader/Makefile b/loader/Makefile new file mode 100644 index 0000000..77b71f0 --- /dev/null +++ b/loader/Makefile @@ -0,0 +1,74 @@ +# (C)2004-2008 SourceMod Development Team +# Makefile written by David "BAILOPAN" Anderson + +##################################### +### EDIT BELOW FOR OTHER PROJECTS ### +##################################### + +BINARY = server_i486.so + +OBJECTS = loader.cpp \ + serverplugin.cpp + +############################################## +### CONFIGURE ANY OTHER FLAGS/OPTIONS HERE ### +############################################## + +C_OPT_FLAGS = -DNDEBUG -O3 -funroll-loops -pipe -fno-strict-aliasing +C_DEBUG_FLAGS = -D_DEBUG -DDEBUG -g -ggdb3 +C_GCC4_FLAGS = -fvisibility=hidden +CPP_GCC4_FLAGS = -fvisibility-inlines-hidden +CPP = gcc-4.1 + +LINK += -static-libgcc + +INCLUDE += -I. -I../core/sourcehook + +CFLAGS += -D_LINUX -Dstricmp=strcasecmp -D_stricmp=strcasecmp -D_strnicmp=strncasecmp \ + -Dstrnicmp=strncasecmp -D_snprintf=snprintf -D_vsnprintf=vsnprintf -D_alloca=alloca \ + -Dstrcmpi=strcasecmp -Wall -Werror -Wno-uninitialized -mfpmath=sse -msse -DHAVE_STDINT_H -m32 +CPPFLAGS += -Wno-non-virtual-dtor -fno-exceptions -fno-rtti + +################################################ +### DO NOT EDIT BELOW HERE FOR MOST PROJECTS ### +################################################ + +ifeq "$(DEBUG)" "true" + BIN_DIR = Debug + CFLAGS += $(C_DEBUG_FLAGS) +else + BIN_DIR = Release + CFLAGS += $(C_OPT_FLAGS) +endif + +GCC_VERSION := $(shell $(CPP) -dumpversion >&1 | cut -b1) +ifeq "$(GCC_VERSION)" "4" + CFLAGS += $(C_GCC4_FLAGS) + CPPFLAGS += $(CPP_GCC4_FLAGS) +endif + +OBJ_LINUX := $(OBJECTS:%.cpp=$(BIN_DIR)/%.o) +OBJ_LINUX := $(OBJ_LINUX:%.c=$(BIN_DIR)/%.o) + +$(BIN_DIR)/%.o: %.cpp + $(CPP) $(INCLUDE) $(CFLAGS) $(CPPFLAGS) -o $@ -c $< + +$(BIN_DIR)/%.o: %.c + $(CPP) $(INCLUDE) $(CFLAGS) -o $@ -c $< + +all: + mkdir -p $(BIN_DIR) + $(MAKE) -f Makefile metamod + +metamod: $(OBJ_LINUX) + $(CPP) $(INCLUDE) $(OBJ_LINUX) $(LINK) -m32 -shared -ldl -lm -o$(BIN_DIR)/$(BINARY) + +debug: + $(MAKE) -f Makefile all DEBUG=true + +default: all + +clean: + rm -rf $(BIN_DIR)/*.o + rm -rf $(BIN_DIR)/$(BINARY) + diff --git a/loader/loader.cpp b/loader/loader.cpp new file mode 100644 index 0000000..b8ab5c9 --- /dev/null +++ b/loader/loader.cpp @@ -0,0 +1,221 @@ +#include +#include +#include +#include +#include +#include "loader.h" +#include "serverplugin.h" + +static HMODULE mm_library = NULL; +static char mm_fatal_logfile[PLATFORM_MAX_PATH] = "metamod-fatal.log"; + +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), "%d/%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); +} + +static 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; +} + +static 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; +} + +static 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); +#else + 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; +} + +static const char *backend_names[3] = +{ + "1.ep1", + "2.ep2", + "2.l4d" +}; + +#if defined _WIN32 +#define LIBRARY_EXT ".dll" +#define LIBRARY_MINEXT ".dll" +#elif defined __linux__ +#define LIBRARY_EXT "_i486.so" +#define LIBRARY_MINEXT ".so" +#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 + +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 server.dll with the new binary we want */ + mm_Format(&mm_path[len - temp_len], + sizeof(mm_path) - (len - temp_len), + "metamod.%s" LIBRARY_MINEXT, + backend_names[backend]); + +#if defined _WIN32 + mm_library = LoadLibrary(mm_path); + + if (mm_library == NULL) + { + mm_GetPlatformError(buffer, maxlength); + return false; + } +#elif defined __linux__ + mm_library = dlopen(mm_path, RTLD_NOW); + + if (mm_library == NULL) + { + mm_Format(buffer, maxlength, "%s", dlerror()); + return false; + } +#endif + + return true; +} + +void +mm_UnloadMetamodLibrary() +{ +#if defined _WIN32 + FreeLibrary(mm_library); +#else + dlclose(mm_library); +#endif +} + +#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) +{ + if (mm_library != NULL) + { + if (ret != NULL) + *ret = 1; + return NULL; + } + + void *ptr; + if (strncmp(name, "ISERVERPLUGINCALLBACKS", 22) == 0) + { + ptr = mm_GetVspCallbacks(atoi(&name[22])); + if (ret != NULL) + *ret = ptr != NULL ? 0 : 1; + return ptr; + } + + if (ret != NULL) + *ret = 1; + + return NULL; +} + +extern void * +mm_GetProcAddress(const char *name) +{ +#if defined _WIN32 + return GetProcAddress(mm_library, name); +#elif defined __linux__ + return dlsym(mm_library, name); +#endif +} + diff --git a/loader/loader.h b/loader/loader.h new file mode 100644 index 0000000..883e674 --- /dev/null +++ b/loader/loader.h @@ -0,0 +1,48 @@ +#ifndef _INCLUDE_METAMOD_SOURCE_LOADER_H_ +#define _INCLUDE_METAMOD_SOURCE_LOADER_H_ + +#define SH_COMP_GCC 1 +#define SH_COMP_MSVC 2 + +#if defined WIN32 +#define WINDOWS_LEAN_AND_MEAN +#include +#define PLATFORM_MAX_PATH MAX_PATH +typedef INTPTR intptr_t; +typedef UINTPTR uintptr_t; +#define SH_COMP SH_COMP_MSVC +#elif defined __linux__ +#include +#include +#include +typedef void * HMODULE; +#define PLATFORM_MAX_PATH PATH_MAX +#define SH_COMP SH_COMP_GCC +#else +#error "OS detection failed" +#endif + +#define SH_PTRSIZE sizeof(void*) + +enum MetamodBackend +{ + MMBackend_Episode1 = 0, + MMBackend_Episode2, + MMBackend_Left4Dead, + MMBackend_UNKNOWN +}; + +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, ...); + +#endif /* _INCLUDE_METAMOD_SOURCE_LOADER_H_ */ + diff --git a/loader/loader_bridge.h b/loader/loader_bridge.h new file mode 100644 index 0000000..4d2fb5b --- /dev/null +++ b/loader/loader_bridge.h @@ -0,0 +1,24 @@ +#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) = 0; + virtual void Unload() = 0; + virtual const char *GetDescription() = 0; +}; + +#endif /* _INCLUDE_METAMOD_SOURCE_LOADER_BRIDGE_H_ */ + diff --git a/loader/serverplugin.cpp b/loader/serverplugin.cpp new file mode 100644 index 0000000..d49309b --- /dev/null +++ b/loader/serverplugin.cpp @@ -0,0 +1,248 @@ +#include +#include +#include +#include "loader.h" +#include +#include +#include "serverplugin.h" + +typedef enum +{ + PLUGIN_CONTINUE = 0, + PLUGIN_OVERRIDE, + PLUGIN_STOP, +} PLUGIN_RESULT; + +typedef enum +{ + eQueryCvarValueStatus_ValueIntact=0, + eQueryCvarValueStatus_CvarNotFound=1, + eQueryCvarValueStatus_NotACvar=2, + eQueryCvarValueStatus_CvarProtected=3 +} EQueryCvarValueStatus; + +typedef int QueryCvarCookie_t; +class CCommand; +class IServerPluginCallbacks; +struct edict_t; + +#if defined WIN32 +#define LIBRARY_EXT ".dll" +#else +#define LIBRARY_EXT "_i486.so" +#endif + +class IRandomThings +{ +public: + virtual PLUGIN_RESULT ClientCommand(edict_t *pEntity, const CCommand& args) + { + return PLUGIN_CONTINUE; + } +}; + +/** + * The vtable must match the general layout for ISPC. We modify the vtable + * based on what we get back. + */ +class ServerPlugin +{ + IVspBridge* bridge; + unsigned int vsp_version; + bool load_allowed; +public: + ServerPlugin() + { + load_allowed = false; + } + virtual bool Load(QueryValveInterface engineFactory, QueryValveInterface gsFactory) + { + MetamodBackend backend = MMBackend_UNKNOWN; + + if (!load_allowed) + return false; + + load_allowed = false; + + /* Check for L4D */ + if (engineFactory("VEngineServer022", NULL) != NULL && + engineFactory("VEngineCvar007", NULL) != NULL) + { + backend = MMBackend_Left4Dead; + } + else if (engineFactory("VEngineServer021", NULL) != NULL) + { + /* Check for OB */ + if (engineFactory("VEngineCvar004", NULL) != NULL && + engineFactory("VModelInfoServer002", NULL) != NULL) + { + backend = MMBackend_Episode2; + } + /* Check for EP1 */ + else if (engineFactory("VModelInfoServer001", NULL) != NULL && + (engineFactory("VEngineCvar003", NULL) != NULL || + engineFactory("VEngineCvar002", NULL) != NULL)) + { + backend = MMBackend_Episode1; + } + } + + if (backend == MMBackend_UNKNOWN) + { + mm_LogFatal("Could not detect engine version, spewing stats:"); + return false; + } + else if (backend >= MMBackend_Episode2) + { + /* We need to insert the right type of call into this vtable */ + void **vtable_src; + void **vtable_dest; + IRandomThings sample; + SourceHook::MemFuncInfo mfp_dest, mfp_src; + + mfp_dest.isVirtual = false; + mfp_src.isVirtual = false; + + SourceHook::GetFuncInfo(&ServerPlugin::ClientCommand, mfp_dest); + SourceHook::GetFuncInfo(&IRandomThings::ClientCommand, mfp_src); + + assert(mfp_dest.isVirtual); + assert(mfp_dest.thisptroffs == 0); + assert(mfp_dest.vtbloffs == 0); + assert(mfp_src.isVirtual); + assert(mfp_src.thisptroffs == 0); + assert(mfp_src.vtbloffs == 0); + + vtable_src = (void **)*(void **)&sample; + vtable_dest = (void **)*(void **)this; + SourceHook::SetMemAccess(&vtable_dest[mfp_dest.vtblindex], + sizeof(void*), + SH_MEM_READ|SH_MEM_WRITE|SH_MEM_EXEC); + vtable_dest[mfp_dest.vtblindex] = vtable_src[mfp_src.vtblindex]; + } + + char error[255]; + if (!mm_LoadMetamodLibrary(backend, error, sizeof(error))) + { + mm_LogFatal("Detected engine %d but could not load: %s", backend, error); + return false; + } + + typedef IVspBridge *(*GetVspBridge)(); + GetVspBridge get_bridge = (GetVspBridge)mm_GetProcAddress("GetVspBridge"); + if (get_bridge == NULL) + { + mm_UnloadMetamodLibrary(); + mm_LogFatal("Detected engine %d but could not find GetVspBridge callback."); + return false; + } + + bridge = get_bridge(); + + vsp_bridge_info info; + + info.engineFactory = engineFactory; + info.gsFactory = gsFactory; + info.vsp_callbacks = (IServerPluginCallbacks*)this; + info.vsp_version = vsp_version; + + if (!bridge->Load(&info)) + { + mm_UnloadMetamodLibrary(); + return false; + } + + return true; + } + virtual void Unload() + { + if (bridge == NULL) + return; + bridge->Unload(); + } + virtual void Pause() + { + } + virtual void UnPause() + { + } + virtual const char *GetPluginDescription() + { + if (bridge == NULL) + return "Metamod:Source Loader Shim"; + return bridge->GetDescription(); + } + virtual void LevelInit(char const *pMapName) + { + } + virtual void ServerActivate(edict_t *pEdictList, int edictCount, int clientMax) + { + } + virtual void GameFrame(bool simulating) + { + } + virtual void LevelShutdown() + { + } + virtual void ClientActive(edict_t *pEntity) + { + } + virtual void ClientDisconnect(edict_t *pEntity) + { + } + virtual void ClientPutInServer(edict_t *pEntity, char const *playername) + { + } + virtual void SetCommandClient(int index) + { + } + virtual void ClientSettingsChanged(edict_t *pEdict) + { + } + virtual PLUGIN_RESULT ClientConnect(bool *bAllowConnect, + edict_t *pEntity, + const char *pszName, + const char *pszAddress, + char *reject, + int maxrejectlen) + { + return PLUGIN_CONTINUE; + } + virtual PLUGIN_RESULT ClientCommand(edict_t *pEntity) + { + return PLUGIN_CONTINUE; + } + virtual PLUGIN_RESULT NetworkIDValidated(const char *pszUserName, const char *pszNetworkID) + { + return PLUGIN_CONTINUE; + } + virtual void OnQueryCvarValueFinished(QueryCvarCookie_t iCookie, + edict_t *pPlayerEntity, + EQueryCvarValueStatus eStatus, + const char *pCvarName, + const char *pCvarValue) + { + } + void PrepForLoad(unsigned int version) + { + vsp_version = version; + load_allowed = true; + } + bool IsLoaded() + { + return bridge != NULL; + } +}; + +ServerPlugin mm_vsp_callbacks; + +void *mm_GetVspCallbacks(unsigned int version) +{ + if (mm_vsp_callbacks.IsLoaded()) + return NULL; + + mm_vsp_callbacks.PrepForLoad(version); + + return &mm_vsp_callbacks; +} + diff --git a/loader/serverplugin.h b/loader/serverplugin.h new file mode 100644 index 0000000..757ad60 --- /dev/null +++ b/loader/serverplugin.h @@ -0,0 +1,10 @@ +#ifndef _INCLUDE_METAMOD_SOURCE_SERVERPLUGINS_H_ +#define _INCLUDE_METAMOD_SOURCE_SERVERPLUGINS_H_ + +#include "loader_bridge.h" + +extern void * +mm_GetVspCallbacks(unsigned int version); + +#endif /* _INCLUDE_METAMOD_SOURCE_SERVERPLUGINS_H_ */ +