commit a67ef4d4828647992fdc0261f8c7bd28fb336469 Author: Asher Baker Date: Tue Jul 19 17:07:38 2011 +0100 Initial commit. diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..a158343 --- /dev/null +++ b/.hgignore @@ -0,0 +1,3 @@ +syntax: glob +build/* +msvc10/* diff --git a/AMBuildScript b/AMBuildScript new file mode 100644 index 0000000..dd64260 --- /dev/null +++ b/AMBuildScript @@ -0,0 +1,332 @@ +# vim: set ts=2 sw=2 tw=99 noet ft=python: +import os +import sys +import ambuild.command as command +from ambuild.command import SymlinkCommand + +vboxhack = 0 + +class SM: + def __init__(self): + self.compiler = Cpp.Compiler() + + #Build SDK info + self.sdkInfo = { } + self.sdkInfo['ep2v'] = {'sdk': 'HL2SDKOBVALVE', 'ext': '2.ep2v', 'def': '4', + 'name': 'ORANGEBOXVALVE', 'platform': ['windows', 'linux', 'darwin']} + + if AMBuild.mode == 'config': + #Detect compilers + self.compiler.DetectAll(AMBuild) + + #Detect variables + envvars = { 'MMSOURCE18': 'mmsource-1.8', + 'HL2SDKOBVALVE': 'hl2sdk-ob-valve', + 'SOURCEMOD13': 'sourcemod-1.3' + } + + #Must have a path for each envvar (file a bug if you don't like this) + for i in envvars: + if i in os.environ: + path = os.environ[i] + if not os.path.isdir(path): + raise Exception('Path for {0} was not found: {1}'.format(i, path)) + else: + head = os.getcwd() + oldhead = None + while head != None and head != oldhead: + path = os.path.join(head, envvars[i]) + if os.path.isdir(path): + break + oldhead = head + head, tail = os.path.split(head) + if head == None or head == oldhead: + raise Exception('Could not find a valid path for {0}'.format(i)) + AMBuild.cache.CacheVariable(i, path) + + #Set up defines + cxx = self.compiler.cxx + if isinstance(cxx, Cpp.GCC): + self.vendor = 'gcc' + self.compiler.AddToListVar('CDEFINES', 'stricmp=strcasecmp') + self.compiler.AddToListVar('CDEFINES', '_stricmp=strcasecmp') + self.compiler.AddToListVar('CDEFINES', '_snprintf=snprintf') + self.compiler.AddToListVar('CDEFINES', '_vsnprintf=vsnprintf') + self.compiler.AddToListVar('CFLAGS', '-pipe') + self.compiler.AddToListVar('CFLAGS', '-fno-strict-aliasing') + if cxx.majorVersion >= 4: + self.compiler.AddToListVar('CFLAGS', '-fvisibility=hidden') + self.compiler.AddToListVar('CXXFLAGS', '-fvisibility-inlines-hidden') + self.compiler.AddToListVar('CFLAGS', '-Wall') + self.compiler.AddToListVar('CFLAGS', '-Werror') + self.compiler.AddToListVar('CFLAGS', '-Wno-uninitialized') + self.compiler.AddToListVar('CFLAGS', '-Wno-unused') + self.compiler.AddToListVar('CFLAGS', '-Wno-switch') + self.compiler.AddToListVar('CFLAGS', '-mfpmath=sse') + self.compiler.AddToListVar('CFLAGS', '-msse') + self.compiler.AddToListVar('CFLAGS', '-m32') + self.compiler.AddToListVar('POSTLINKFLAGS', '-m32') + self.compiler.AddToListVar('CFLAGS', '-static-libgcc') + self.compiler.AddToListVar('CXXFLAGS', '-fno-exceptions') + self.compiler.AddToListVar('CXXFLAGS', '-fno-rtti') + self.compiler.AddToListVar('CXXFLAGS', '-fno-threadsafe-statics') + self.compiler.AddToListVar('CXXFLAGS', '-Wno-non-virtual-dtor') + self.compiler.AddToListVar('CDEFINES', 'HAVE_STDINT_H') + elif isinstance(cxx, Cpp.MSVC): + self.vendor = 'msvc' + if AMBuild.options.debug == '1': + self.compiler.AddToListVar('CFLAGS', '/MTd') + self.compiler.AddToListVar('POSTLINKFLAGS', '/NODEFAULTLIB:libcmt') + else: + self.compiler.AddToListVar('CFLAGS', '/MT') + self.compiler.AddToListVar('CDEFINES', '_CRT_SECURE_NO_DEPRECATE') + self.compiler.AddToListVar('CDEFINES', '_CRT_SECURE_NO_WARNINGS') + self.compiler.AddToListVar('CDEFINES', '_CRT_NONSTDC_NO_DEPRECATE') + self.compiler.AddToListVar('CXXFLAGS', '/EHsc') + self.compiler.AddToListVar('CXXFLAGS', '/GR-') + self.compiler.AddToListVar('CFLAGS', '/W3') + self.compiler.AddToListVar('CFLAGS', '/nologo') + self.compiler.AddToListVar('CFLAGS', '/Zi') + self.compiler.AddToListVar('CXXFLAGS', '/TP') + self.compiler.AddToListVar('POSTLINKFLAGS', '/DEBUG') + self.compiler.AddToListVar('POSTLINKFLAGS', '/MACHINE:X86') + self.compiler.AddToListVar('POSTLINKFLAGS', '/SUBSYSTEM:WINDOWS') + self.compiler.AddToListVar('POSTLINKFLAGS', 'kernel32.lib') + self.compiler.AddToListVar('POSTLINKFLAGS', 'user32.lib') + self.compiler.AddToListVar('POSTLINKFLAGS', 'gdi32.lib') + self.compiler.AddToListVar('POSTLINKFLAGS', 'winspool.lib') + self.compiler.AddToListVar('POSTLINKFLAGS', 'comdlg32.lib') + self.compiler.AddToListVar('POSTLINKFLAGS', 'advapi32.lib') + self.compiler.AddToListVar('POSTLINKFLAGS', 'shell32.lib') + self.compiler.AddToListVar('POSTLINKFLAGS', 'ole32.lib') + self.compiler.AddToListVar('POSTLINKFLAGS', 'oleaut32.lib') + self.compiler.AddToListVar('POSTLINKFLAGS', 'uuid.lib') + self.compiler.AddToListVar('POSTLINKFLAGS', 'odbc32.lib') + self.compiler.AddToListVar('POSTLINKFLAGS', 'odbccp32.lib') + + #Optimization + if AMBuild.options.opt == '1': + self.compiler.AddToListVar('CDEFINES', 'NDEBUG') + if self.vendor == 'gcc': + self.compiler.AddToListVar('CFLAGS', '-O3') + elif self.vendor == 'msvc': + self.compiler.AddToListVar('CFLAGS', '/Ox') + self.compiler.AddToListVar('POSTLINKFLAGS', '/OPT:ICF') + self.compiler.AddToListVar('POSTLINKFLAGS', '/OPT:REF') + + #Debugging + if AMBuild.options.debug == '1': + self.compiler.AddToListVar('CDEFINES', 'DEBUG') + self.compiler.AddToListVar('CDEFINES', '_DEBUG') + if self.vendor == 'gcc': + self.compiler.AddToListVar('CFLAGS', '-g3') + elif self.vendor == 'msvc': + self.compiler.AddToListVar('CFLAGS', '/Od') + self.compiler.AddToListVar('CFLAGS', '/RTC1') + + #Platform-specifics + if AMBuild.target['platform'] == 'linux': + self.compiler.AddToListVar('CDEFINES', '_LINUX') + elif AMBuild.target['platform'] == 'darwin': + self.compiler.AddToListVar('CFLAGS', ['-isysroot', + '/Developer/SDKs/MacOSX10.5.sdk']) + self.compiler.AddToListVar('POSTLINKFLAGS', '-mmacosx-version-min=10.5') + self.compiler.AddToListVar('POSTLINKFLAGS', ['-arch', 'i386']) + self.compiler.AddToListVar('POSTLINKFLAGS', '-lstdc++') + + # For OS X dylib versioning + import re + productFile = open(os.path.join(AMBuild.sourceFolder, 'buildbot', 'product.version'), 'r') + productContents = productFile.read() + productFile.close() + m = re.match('(\d+)\.(\d+)\.(\d+).*', productContents) + if m == None: + self.version = '1.0.0' + else: + major, minor, release = m.groups() + self.version = '{0}.{1}.{2}'.format(major, minor, release) + AMBuild.cache.CacheVariable('version', self.version) + elif AMBuild.target['platform'] == 'windows': + self.compiler.AddToListVar('CDEFINES', 'WIN32') + self.compiler.AddToListVar('CDEFINES', '_WINDOWS') + + #Finish up + self.compiler.AddToListVar('CDEFINES', 'SOURCEMOD_BUILD') + self.compiler.AddToListVar('CDEFINES', 'SM_GENERATED_BUILD') + self.compiler.AddToListVar('CINCLUDES', + os.path.join(AMBuild.outputFolder, 'includes')) + self.compiler.ToConfig(AMBuild, 'compiler') + AMBuild.cache.CacheVariable('vendor', self.vendor) + self.targetMap = { } + AMBuild.cache.CacheVariable('targetMap', self.targetMap) + else: + self.compiler.FromConfig(AMBuild, 'compiler') + self.targetMap = AMBuild.cache['targetMap'] + + if AMBuild.target['platform'] == 'windows': + self.compiler.AddToListVar('RCINCLUDES', os.path.join(AMBuild.sourceFolder, 'extension')) + + self.mmsPath = AMBuild.cache['MMSOURCE18'] + + def DefaultCompiler(self): + return self.compiler.Clone() + + def JobMatters(self, jobname): + file = sys._getframe().f_code.co_filename + if AMBuild.mode == 'config': + self.targetMap[jobname] = file + return True + if len(AMBuild.args) == 0: + return True + if not jobname in AMBuild.args: + return False + + def DefaultExtCompiler(self, path): + compiler = self.DefaultCompiler() + compiler['CXXINCLUDES'].append(os.path.join(AMBuild.sourceFolder, path)) + compiler['CXXINCLUDES'].append(os.path.join(AMBuild.sourceFolder, path, 'sdk')) + compiler['CXXINCLUDES'].append(os.path.join(AMBuild.sourceFolder, 'public')) + compiler['CXXINCLUDES'].append(os.path.join(AMBuild.sourceFolder, 'public', 'extensions')) + compiler['CXXINCLUDES'].append(os.path.join(AMBuild.sourceFolder, 'public', 'sourcepawn')) + return compiler + + def AutoVersion(self, folder, binary): + if AMBuild.target['platform'] == 'windows': + env = {'RCDEFINES': ['BINARY_NAME="' + binary.binaryFile + '"', 'SM_GENERATED_BUILD']} + binary.AddResourceFile(os.path.join(folder, 'version.rc' ), env) + elif AMBuild.target['platform'] == 'darwin' and isinstance(binary, Cpp.LibraryBuilder): + binary.compiler['POSTLINKFLAGS'].extend(['-compatibility_version', '1.0.0']) + binary.compiler['POSTLINKFLAGS'].extend(['-current_version', AMBuild.cache['version']]) + else: + return + + def PreSetupHL2Job(self, job, builder, sdk): + info = self.sdkInfo[sdk] + sdkPath = AMBuild.cache[info['sdk']] + if AMBuild.target['platform'] == 'linux': + if sdk == 'ep1': + staticLibs = os.path.join(sdkPath, 'linux_sdk') + else: + staticLibs = os.path.join(sdkPath, 'lib', 'linux') + workFolder = os.path.join(AMBuild.outputFolder, job.workFolder) + if sdk in ['ep2v', 'l4d', 'l4d2']: + for i in ['tier1_i486.a', 'mathlib_i486.a', 'libvstdlib.so', 'libtier0.so']: + link = os.path.join(workFolder, i) + target = os.path.join(staticLibs, i) + try: + os.lstat(link) + except: + if vboxhack == 1: + job.AddCommand(command.DirectCommand(['cp', '-f', target, link])) + else: + job.AddCommand(SymlinkCommand(link, target)) + else: + for i in ['tier1_i486.a', 'mathlib_i486.a', 'vstdlib_i486.so', 'tier0_i486.so']: + link = os.path.join(workFolder, i) + target = os.path.join(staticLibs, i) + try: + os.lstat(link) + except: + if vboxhack == 1: + job.AddCommand(command.DirectCommand(['cp', '-f', target, link])) + else: + job.AddCommand(SymlinkCommand(link, target)) + elif AMBuild.target['platform'] == 'darwin': + staticLibs = os.path.join(sdkPath, 'lib', 'mac') + workFolder = os.path.join(AMBuild.outputFolder, job.workFolder) + for i in ['tier1_i486.a', 'mathlib_i486.a', 'libvstdlib.dylib', 'libtier0.dylib']: + link = os.path.join(workFolder, i) + target = os.path.join(staticLibs, i) + try: + os.lstat(link) + except: + if vboxhack == 1: + job.AddCommand(command.DirectCommand(['cp', '-f', target, link])) + else: + job.AddCommand(SymlinkCommand(link, target)) + elif AMBuild.target['platform'] == 'windows': + libs = ['tier0', 'tier1', 'vstdlib', 'mathlib'] + if sdk == 'swarm': + libs.append('interfaces') + for lib in libs: + libPath = os.path.join(sdkPath, 'lib', 'public', lib) + '.lib' + builder.RebuildIfNewer(libPath) + builder['POSTLINKFLAGS'].append(libPath) + + def PostSetupHL2Job(self, job, builder, sdk): + if AMBuild.target['platform'] in ['linux', 'darwin']: + builder.AddObjectFiles(['tier1_i486.a', 'mathlib_i486.a']) + + def DefaultHL2Compiler(self, path, sdk, noLink = False, oldMms = '-legacy'): + compiler = self.DefaultExtCompiler(path) + + mms = 'core' + if sdk == 'ep1': + mms += oldMms + + compiler['CXXINCLUDES'].append(os.path.join(self.mmsPath, mms)) + compiler['CXXINCLUDES'].append(os.path.join(self.mmsPath, mms, 'sourcehook')) + + info = self.sdkInfo + compiler['CDEFINES'].extend(['SE_' + info[i]['name'] + '=' + info[i]['def'] for i in info]) + + paths = [['public'], ['public', 'engine'], ['public', 'mathlib'], ['public', 'vstdlib'], + ['public', 'tier0'], ['public', 'tier1']] + if sdk == 'ep1' or sdk == 'darkm': + paths.append(['public', 'dlls']) + paths.append(['game_shared']) + else: + paths.append(['public', 'game', 'server']) + paths.append(['game', 'shared']) + paths.append(['common']) + + info = self.sdkInfo[sdk] + sdkPath = AMBuild.cache[info['sdk']] + + compiler['CDEFINES'].append('SOURCE_ENGINE=' + info['def']) + + if sdk == 'swarm' and AMBuild.target['platform'] == 'windows': + compiler['CDEFINES'].extend(['COMPILER_MSVC', 'COMPILER_MSVC32']) + + if sdk == 'ep1': + if AMBuild.target['platform'] == 'linux': + staticLibs = os.path.join(sdkPath, 'linux_sdk') + else: + if AMBuild.target['platform'] == 'linux': + staticLibs = os.path.join(sdkPath, 'lib', 'linux') + elif AMBuild.target['platform'] == 'darwin': + staticLibs = os.path.join(sdkPath, 'lib', 'mac') + + for i in paths: + compiler['CXXINCLUDES'].append(os.path.join(sdkPath, *i)) + + if not noLink: + if AMBuild.target['platform'] == 'linux': + compiler['POSTLINKFLAGS'][0:0] = ['-lm'] + if sdk in ['ep2v', 'l4d', 'l4d2']: + compiler['POSTLINKFLAGS'][0:0] = ['libtier0.so'] + compiler['POSTLINKFLAGS'][0:0] = ['libvstdlib.so'] + else: + compiler['POSTLINKFLAGS'][0:0] = ['tier0_i486.so'] + compiler['POSTLINKFLAGS'][0:0] = ['vstdlib_i486.so'] + elif AMBuild.target['platform'] == 'darwin': + compiler['POSTLINKFLAGS'][0:0] = ['libtier0.dylib'] + compiler['POSTLINKFLAGS'][0:0] = ['libvstdlib.dylib'] + + return compiler + +sm = SM() +globals = { + 'SM': sm +} + +#AMBuild.Include(os.path.join('buildbot', 'Versioning'), globals) + +FileList = [ + ['AMBuilder'], + ['buildbot', 'PackageScript'] + ] + +for parts in FileList: + AMBuild.Include(os.path.join(*parts), globals) + diff --git a/AMBuilder b/AMBuilder new file mode 100644 index 0000000..ddc42d6 --- /dev/null +++ b/AMBuilder @@ -0,0 +1,34 @@ +# vim: set ts=2 sw=2 tw=99 noet ft=python: +import os + +for i in SM.sdkInfo: + sdk = SM.sdkInfo[i] + if AMBuild.target['platform'] not in sdk['platform']: + continue + + compiler = SM.DefaultHL2Compiler('', i) + compiler['CXXINCLUDES'].append(os.path.join(AMBuild.cache['SOURCEMOD13'], 'public')) + compiler['CXXINCLUDES'].append(os.path.join(AMBuild.cache['SOURCEMOD13'], 'public', 'sourcepawn')) + #compiler['CXXINCLUDES'].append(os.path.join(AMBuild.cache['SOURCEMOD13'], 'public', 'jit')) + #compiler['CXXINCLUDES'].append(os.path.join(AMBuild.cache['SOURCEMOD13'], 'public', 'jit', 'x86')) + + if compiler.cc.name == 'gcc': + compiler['CFLAGS'].append('-Wno-parentheses') + + if i != 'ep1': + compiler['CDEFINES'].append('HOOKING_ENABLED') + + name = 'connect.ext.' + sdk['ext'] + extension = AMBuild.AddJob(name) + binary = Cpp.LibraryBuilder(name, AMBuild, extension, compiler) + SM.PreSetupHL2Job(extension, binary, i) + binary.AddSourceFiles('', [ + 'extension.cpp', + 'asm/asm.c', + 'CDetour/detours.cpp', + 'sdk/smsdk_ext.cpp' + ]) + SM.PostSetupHL2Job(extension, binary, i) + #SM.AutoVersion('extension', binary) + binary.SendToJob() + diff --git a/CDetour/detourhelpers.h b/CDetour/detourhelpers.h new file mode 100644 index 0000000..b171074 --- /dev/null +++ b/CDetour/detourhelpers.h @@ -0,0 +1,98 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id: detourhelpers.h 248 2008-08-27 00:56:22Z pred $ + */ + +#ifndef _INCLUDE_SOURCEMOD_DETOURHELPERS_H_ +#define _INCLUDE_SOURCEMOD_DETOURHELPERS_H_ + +#if defined PLATFORM_LINUX +#include +#define PAGE_SIZE 4096 +#define ALIGN(ar) ((long)ar & ~(PAGE_SIZE-1)) +#define PAGE_EXECUTE_READWRITE PROT_READ|PROT_WRITE|PROT_EXEC +#endif + +struct patch_t +{ + patch_t() + { + patch[0] = 0; + bytes = 0; + } + unsigned char patch[20]; + size_t bytes; +}; + +inline void ProtectMemory(void *addr, int length, int prot) +{ +#if defined PLATFORM_LINUX + void *addr2 = (void *)ALIGN(addr); + mprotect(addr2, sysconf(_SC_PAGESIZE), prot); +#elif defined PLATFORM_WINDOWS + DWORD old_prot; + VirtualProtect(addr, length, prot, &old_prot); +#endif +} + +inline void SetMemPatchable(void *address, size_t size) +{ + ProtectMemory(address, (int)size, PAGE_EXECUTE_READWRITE); +} + +inline void DoGatePatch(unsigned char *target, void *callback) +{ + SetMemPatchable(target, 20); + + target[0] = 0xFF; /* JMP */ + target[1] = 0x25; /* MEM32 */ + *(void **)(&target[2]) = callback; +} + +inline void ApplyPatch(void *address, int offset, const patch_t *patch, patch_t *restore) +{ + ProtectMemory(address, 20, PAGE_EXECUTE_READWRITE); + + unsigned char *addr = (unsigned char *)address + offset; + if (restore) + { + for (size_t i=0; ibytes; i++) + { + restore->patch[i] = addr[i]; + } + restore->bytes = patch->bytes; + } + + for (size_t i=0; ibytes; i++) + { + addr[i] = patch->patch[i]; + } +} + +#endif //_INCLUDE_SOURCEMOD_DETOURHELPERS_H_ diff --git a/CDetour/detours.cpp b/CDetour/detours.cpp new file mode 100644 index 0000000..71aba44 --- /dev/null +++ b/CDetour/detours.cpp @@ -0,0 +1,192 @@ +/** +* vim: set ts=4 : +* ============================================================================= +* SourceMod +* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This program is free software; you can redistribute it and/or modify it under +* the terms of the GNU General Public License, version 3.0, as published by the +* Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +* details. +* +* You should have received a copy of the GNU General Public License along with +* this program. If not, see . +* +* As a special exception, AlliedModders LLC gives you permission to link the +* code of this program (as well as its derivative works) to "Half-Life 2," the +* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software +* by the Valve Corporation. You must obey the GNU General Public License in +* all respects for all other code used. Additionally, AlliedModders LLC grants +* this exception to all derivative works. AlliedModders LLC defines further +* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), +* or . +* +* Version: $Id: detours.cpp 248 2008-08-27 00:56:22Z pred $ +*/ + +#include "detours.h" +#include + +ISourcePawnEngine *CDetourManager::spengine = NULL; +IGameConfig *CDetourManager::gameconf = NULL; + +void CDetourManager::Init(ISourcePawnEngine *spengine, IGameConfig *gameconf) +{ + CDetourManager::spengine = spengine; + CDetourManager::gameconf = gameconf; +} + +CDetour *CDetourManager::CreateDetour(void *callbackfunction, void **trampoline, const char *signame) +{ + CDetour *detour = new CDetour(callbackfunction, trampoline, signame); + if (detour) + { + if (!detour->Init(spengine, gameconf)) + { + delete detour; + return NULL; + } + + return detour; + } + + return NULL; +} + +CDetour::CDetour(void *callbackfunction, void **trampoline, const char *signame) +{ + enabled = false; + detoured = false; + detour_address = NULL; + detour_trampoline = NULL; + this->signame = signame; + this->detour_callback = callbackfunction; + spengine = NULL; + gameconf = NULL; + this->trampoline = trampoline; +} + +bool CDetour::Init(ISourcePawnEngine *spengine, IGameConfig *gameconf) +{ + this->spengine = spengine; + this->gameconf = gameconf; + + if (!CreateDetour()) + { + enabled = false; + return enabled; + } + + enabled = true; + + return enabled; +} + +void CDetour::Destroy() +{ + DeleteDetour(); + delete this; +} + +bool CDetour::IsEnabled() +{ + return enabled; +} + +bool CDetour::CreateDetour() +{ + if (!gameconf->GetMemSig(signame, &detour_address)) + { + g_pSM->LogError(myself, "Could not locate %s - Disabling detour", signame); + return false; + } + + if (!detour_address) + { + g_pSM->LogError(myself, "Sigscan for %s failed - Disabling detour to prevent crashes", signame); + return false; + } + + detour_restore.bytes = copy_bytes((unsigned char *)detour_address, NULL, OP_JMP_SIZE+1); + + /* First, save restore bits */ + for (size_t i=0; iAllocatePageMemory(CodeSize); + spengine->SetReadWrite(wr.outbase); + wr.outptr = wr.outbase; + detour_trampoline = wr.outbase; + goto jit_rewind; + } + + spengine->SetReadExecute(wr.outbase); + + *trampoline = detour_trampoline; + + return true; +} + +void CDetour::DeleteDetour() +{ + if (detoured) + { + DisableDetour(); + } + + if (detour_trampoline) + { + /* Free the allocated trampoline memory */ + spengine->FreePageMemory(detour_trampoline); + detour_trampoline = NULL; + } +} + +void CDetour::EnableDetour() +{ + if (!detoured) + { + DoGatePatch((unsigned char *)detour_address, &detour_callback); + detoured = true; + } +} + +void CDetour::DisableDetour() +{ + if (detoured) + { + /* Remove the patch */ + ApplyPatch(detour_address, 0, &detour_restore, NULL); + detoured = false; + } +} diff --git a/CDetour/detours.h b/CDetour/detours.h new file mode 100644 index 0000000..289e2b3 --- /dev/null +++ b/CDetour/detours.h @@ -0,0 +1,300 @@ +/** +* vim: set ts=4 : +* ============================================================================= +* SourceMod +* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This program is free software; you can redistribute it and/or modify it under +* the terms of the GNU General Public License, version 3.0, as published by the +* Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +* details. +* +* You should have received a copy of the GNU General Public License along with +* this program. If not, see . +* +* As a special exception, AlliedModders LLC gives you permission to link the +* code of this program (as well as its derivative works) to "Half-Life 2," the +* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software +* by the Valve Corporation. You must obey the GNU General Public License in +* all respects for all other code used. Additionally, AlliedModders LLC grants +* this exception to all derivative works. AlliedModders LLC defines further +* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), +* or . +* +* Version: $Id: detours.h 257 2008-09-23 03:12:13Z pred $ +*/ + +#ifndef _INCLUDE_SOURCEMOD_DETOURS_H_ +#define _INCLUDE_SOURCEMOD_DETOURS_H_ + +#include "extension.hpp" +#include +#include +#include "detourhelpers.h" + +/** + * CDetours class for SourceMod Extensions by pRED* + * detourhelpers.h entirely stolen from CSS:DM and were written by BAILOPAN (I assume). + * asm.h/c from devmaster.net (thanks cybermind) edited by pRED* to handle gcc -fPIC thunks correctly + * Concept by Nephyrin Zey (http://www.doublezen.net/) and Windows Detour Library (http://research.microsoft.com/sn/detours/) + * Member function pointer ideas by Don Clugston (http://www.codeproject.com/cpp/FastDelegate.asp) + */ + +#define DETOUR_MEMBER_CALL(name) (this->*name##_Actual) +#define DETOUR_STATIC_CALL(name) (name##_Actual) + +#define DETOUR_DECL_STATIC0(name, ret) \ +ret (*name##_Actual)(void) = NULL; \ +ret name(void) + +#define DETOUR_DECL_STATIC1(name, ret, p1type, p1name) \ +ret (*name##_Actual)(p1type) = NULL; \ +ret name(p1type p1name) + +#define DETOUR_DECL_STATIC2(name, ret, p1type, p1name, p2type, p2name) \ + ret (*name##_Actual)(p1type, p2type) = NULL; \ + ret name(p1type p1name, p2type p2name) + +#define DETOUR_DECL_MEMBER0(name, ret) \ +class name##Class \ +{ \ +public: \ + ret name(); \ + static ret (name##Class::* name##_Actual)(void); \ +}; \ +ret (name##Class::* name##Class::name##_Actual)(void) = NULL; \ +ret name##Class::name() + +#define DETOUR_DECL_MEMBER1(name, ret, p1type, p1name) \ +class name##Class \ +{ \ +public: \ + ret name(p1type p1name); \ + static ret (name##Class::* name##_Actual)(p1type); \ +}; \ +ret (name##Class::* name##Class::name##_Actual)(p1type) = NULL; \ +ret name##Class::name(p1type p1name) + +#define DETOUR_DECL_MEMBER2(name, ret, p1type, p1name, p2type, p2name) \ +class name##Class \ +{ \ +public: \ + ret name(p1type p1name, p2type p2name); \ + static ret (name##Class::* name##_Actual)(p1type, p2type); \ +}; \ +ret (name##Class::* name##Class::name##_Actual)(p1type, p2type) = NULL; \ +ret name##Class::name(p1type p1name, p2type p2name) + +#define DETOUR_DECL_MEMBER3(name, ret, p1type, p1name, p2type, p2name, p3type, p3name) \ +class name##Class \ +{ \ +public: \ + ret name(p1type p1name, p2type p2name, p3type p3name); \ + static ret (name##Class::* name##_Actual)(p1type, p2type, p3type); \ +}; \ +ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type) = NULL; \ +ret name##Class::name(p1type p1name, p2type p2name, p3type p3name) + +#define DETOUR_DECL_MEMBER5(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name) \ +class name##Class \ +{ \ +public: \ + ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name); \ + static ret (name##Class::* name##_Actual)(p1type, p2type, p3type, p4type, p5type); \ +}; \ +ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type, p5type) = NULL; \ +ret name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name) + +#define DETOUR_DECL_MEMBER7(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name, p6type, p6name, p7type, p7name) \ +class name##Class \ +{ \ +public: \ + ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name); \ + static ret (name##Class::* name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type); \ +}; \ +ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type) = NULL; \ +ret name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name) + +#define DETOUR_DECL_MEMBER8(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name, p6type, p6name, p7type, p7name, p8type, p8name) \ +class name##Class \ +{ \ +public: \ + ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name, p8type p8name); \ + static ret (name##Class::* name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type, p8type); \ +}; \ +ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type, p8type) = NULL; \ +ret name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name, p8type p8name) + +#define DETOUR_DECL_MEMBER9(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name, p6type, p6name, p7type, p7name, p8type, p8name, p9type, p9name) \ +class name##Class \ +{ \ +public: \ + ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name, p8type p8name, p9type p9name); \ + static ret (name##Class::* name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type, p8type, p9type); \ +}; \ +ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type, p8type, p9type) = NULL; \ +ret name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name, p8type p8name, p9type p9name) + +#define DETOUR_DECL_MEMBER10(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name, p6type, p6name, p7type, p7name, p8type, p8name, p9type, p9name, p10type, p10name) \ +class name##Class \ +{ \ +public: \ + ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name, p8type p8name, p9type p9name, p10type p10name); \ + static ret (name##Class::* name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type, p8type, p9type, p10type); \ +}; \ +ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type, p8type, p9type, p10type) = NULL; \ +ret name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name, p8type p8name, p9type p9name, p10type p10name) + +#define GET_MEMBER_CALLBACK(name) (void *)GetCodeAddress(&name##Class::name) +#define GET_MEMBER_TRAMPOLINE(name) (void **)(&name##Class::name##_Actual) + +#define GET_STATIC_CALLBACK(name) (void *)&name +#define GET_STATIC_TRAMPOLINE(name) (void **)&name##_Actual + +#define DETOUR_CREATE_MEMBER(name, gamedata) CDetourManager::CreateDetour(GET_MEMBER_CALLBACK(name), GET_MEMBER_TRAMPOLINE(name), gamedata); +#define DETOUR_CREATE_STATIC(name, gamedata) CDetourManager::CreateDetour(GET_STATIC_CALLBACK(name), GET_STATIC_TRAMPOLINE(name), gamedata); + + +class GenericClass {}; +typedef void (GenericClass::*VoidFunc)(); + +inline void *GetCodeAddr(VoidFunc mfp) +{ + return *(void **)&mfp; +} + +/** + * Converts a member function pointer to a void pointer. + * This relies on the assumption that the code address lies at mfp+0 + * This is the case for both g++ and later MSVC versions on non virtual functions but may be different for other compilers + * Based on research by Don Clugston : http://www.codeproject.com/cpp/FastDelegate.asp + */ +#define GetCodeAddress(mfp) GetCodeAddr(reinterpret_cast(mfp)) + +class CDetourManager; + +class CDetour +{ +public: + + bool IsEnabled(); + + /** + * These would be somewhat self-explanatory I hope + */ + void EnableDetour(); + void DisableDetour(); + + void Destroy(); + + friend class CDetourManager; + +protected: + CDetour(void *callbackfunction, void **trampoline, const char *signame); + + bool Init(ISourcePawnEngine *spengine, IGameConfig *gameconf); +private: + + /* These create/delete the allocated memory */ + bool CreateDetour(); + void DeleteDetour(); + + bool enabled; + bool detoured; + + patch_t detour_restore; + /* Address of the detoured function */ + void *detour_address; + /* Address of the allocated trampoline function */ + void *detour_trampoline; + /* Address of the callback handler */ + void *detour_callback; + /* The function pointer used to call our trampoline */ + void **trampoline; + + const char *signame; + ISourcePawnEngine *spengine; + IGameConfig *gameconf; +}; + +class CDetourManager +{ +public: + + static void Init(ISourcePawnEngine *spengine, IGameConfig *gameconf); + + /** + * Creates a new detour + * + * @param callbackfunction Void pointer to your detour callback function. + * @param trampoline Address of the trampoline pointer + * @param signame Section name containing a signature to fetch from the gamedata file. + * @return A new CDetour pointer to control your detour. + * + * Example: + * + * CBaseServer::ConnectClient(netadr_s &, int, int, int, char const*, char const*, char const*, int) + * + * Define a new class with the required function and a member function pointer to the same type: + * + * class CBaseServerDetour + * { + * public: + * bool ConnectClient(void *netaddr_s, int, int, int, char const*, char const*, char const*, int); + * static bool (CBaseServerDetour::* ConnectClient_Actual)(void *netaddr_s, int, int, int, char const*, char const*, char const*, int); + * } + * + * void *callbackfunc = GetCodeAddress(&CBaseServerDetour::ConnectClient); + * void **trampoline = (void **)(&CBaseServerDetour::ConnectClient_Actual); + * + * Creation: + * CDetourManager::CreateDetour(callbackfunc, trampoline, "ConnectClient"); + * + * Usage: + * + * CBaseServerDetour::ConnectClient(void *netaddr_s, int, int, int, char const*, char const*, char const*, int) + * { + * //pre hook code + * bool result = (this->*ConnectClient_Actual)(netaddr_s, rest of params); + * //post hook code + * return result; + * } + * + * Note we changed the netadr_s reference into a void* to avoid needing to define the type + */ + static CDetour *CreateDetour(void *callbackfunction, void **trampoline, const char *signame); + + friend class CBlocker; + friend class CDetour; + +private: + static ISourcePawnEngine *spengine; + static IGameConfig *gameconf; +}; + +#define DECL_DETOUR(name) \ + CDetour *name##_Detour = NULL; + +#define CREATE_DETOUR(name) \ + name##_Detour = DETOUR_CREATE_MEMBER(name, #name); \ + if (name##_Detour != NULL) \ + { \ + name##_Detour->EnableDetour(); \ + } else { \ + snprintf(error, maxlen, "Failed to create " #name " detour, check error log.\n"); \ + return false; \ + } + +#define DESTROY_DETOUR(name) \ + if (name##_Detour != NULL) \ +{ \ + name##_Detour->Destroy(); \ + name##_Detour = NULL; \ +} + +#endif // _INCLUDE_SOURCEMOD_DETOURS_H_ diff --git a/asm/asm.c b/asm/asm.c new file mode 100644 index 0000000..b984fef --- /dev/null +++ b/asm/asm.c @@ -0,0 +1,421 @@ +#include "asm.h" + +#ifndef WIN32 +#define _GNU_SOURCE +#include +#include + +#define REG_EAX 0 +#define REG_ECX 1 +#define REG_EDX 2 +#define REG_EBX 3 + +#define IA32_MOV_REG_IMM 0xB8 // encoding is +r +#endif + +extern void Msg( const char *, ... ); + +/** +* Checks if a call to a fpic thunk has just been written into dest. +* If found replaces it with a direct mov that sets the required register to the value of pc. +* +* @param dest Destination buffer where a call opcode + addr (5 bytes) has just been written. +* @param pc The program counter value that needs to be set (usually the next address from the source). +* @noreturn +*/ +void check_thunks(unsigned char *dest, unsigned char *pc) +{ +#if defined WIN32 + return; +#else + /* Step write address back 4 to the start of the function address */ + unsigned char *writeaddr = dest - 4; + unsigned char *calloffset = *(unsigned char **)writeaddr; + unsigned char *calladdr = (unsigned char *)(dest + (unsigned int)calloffset); + + /* Lookup name of function being called */ + if ((*calladdr == 0x8B) && (*(calladdr+2) == 0x24) && (*(calladdr+3) == 0xC3)) + { + //a thunk maybe? + char movByte = IA32_MOV_REG_IMM; + + /* Calculate the correct mov opcode */ + switch (*(calladdr+1)) + { + case 0x04: + { + movByte += REG_EAX; + break; + } + case 0x1C: + { + movByte += REG_EBX; + break; + } + case 0x0C: + { + movByte += REG_ECX; + break; + } + case 0x14: + { + movByte += REG_EDX; + break; + } + default: + { + Msg("Unknown thunk: %c\n", *(calladdr+1)); + break; + } + } + + /* Move our write address back one to where the call opcode was */ + writeaddr--; + + + /* Write our mov */ + *writeaddr = movByte; + writeaddr++; + + /* Write the value - The provided program counter value */ + *(void **)writeaddr = (void *)pc; + writeaddr += 4; + } + + return; +#endif +} + +//if dest is NULL, returns minimum number of bytes needed to be copied +//if dest is not NULL, it will copy the bytes to dest as well as fix CALLs and JMPs +//http://www.devmaster.net/forums/showthread.php?t=2311 +int copy_bytes(unsigned char *func, unsigned char* dest, int required_len) { + int bytecount = 0; + + while(bytecount < required_len && *func != 0xCC) + { + // prefixes F0h, F2h, F3h, 66h, 67h, D8h-DFh, 2Eh, 36h, 3Eh, 26h, 64h and 65h + int operandSize = 4; + int FPU = 0; + int twoByte = 0; + unsigned char opcode = 0x90; + unsigned char modRM = 0xFF; + while(*func == 0xF0 || + *func == 0xF2 || + *func == 0xF3 || + (*func & 0xFC) == 0x64 || + (*func & 0xF8) == 0xD8 || + (*func & 0x7E) == 0x62) + { + if(*func == 0x66) + { + operandSize = 2; + } + else if((*func & 0xF8) == 0xD8) + { + FPU = *func; + if (dest) + *dest++ = *func++; + else + func++; + bytecount++; + break; + } + + if (dest) + *dest++ = *func++; + else + func++; + bytecount++; + } + + // two-byte opcode byte + if(*func == 0x0F) + { + twoByte = 1; + if (dest) + *dest++ = *func++; + else + func++; + bytecount++; + } + + // opcode byte + opcode = *func++; + if (dest) *dest++ = opcode; + bytecount++; + + // mod R/M byte + modRM = 0xFF; + if(FPU) + { + if((opcode & 0xC0) != 0xC0) + { + modRM = opcode; + } + } + else if(!twoByte) + { + if((opcode & 0xC4) == 0x00 || + (opcode & 0xF4) == 0x60 && ((opcode & 0x0A) == 0x02 || (opcode & 0x09) == 0x09) || + (opcode & 0xF0) == 0x80 || + (opcode & 0xF8) == 0xC0 && (opcode & 0x0E) != 0x02 || + (opcode & 0xFC) == 0xD0 || + (opcode & 0xF6) == 0xF6) + { + modRM = *func++; + if (dest) *dest++ = modRM; + bytecount++; + } + } + else + { + if((opcode & 0xF0) == 0x00 && (opcode & 0x0F) >= 0x04 && (opcode & 0x0D) != 0x0D || + (opcode & 0xF0) == 0x30 || + opcode == 0x77 || + (opcode & 0xF0) == 0x80 || + (opcode & 0xF0) == 0xA0 && (opcode & 0x07) <= 0x02 || + (opcode & 0xF8) == 0xC8) + { + // No mod R/M byte + } + else + { + modRM = *func++; + if (dest) *dest++ = modRM; + bytecount++; + } + } + + // SIB + if((modRM & 0x07) == 0x04 && + (modRM & 0xC0) != 0xC0) + { + if (dest) + *dest++ = *func++; //SIB + else + func++; + bytecount++; + } + + // mod R/M displacement + + // Dword displacement, no base + if((modRM & 0xC5) == 0x05) { + if (dest) { + *(unsigned int*)dest = *(unsigned int*)func; + dest += 4; + } + func += 4; + bytecount += 4; + } + + // Byte displacement + if((modRM & 0xC0) == 0x40) { + if (dest) + *dest++ = *func++; + else + func++; + bytecount++; + } + + // Dword displacement + if((modRM & 0xC0) == 0x80) { + if (dest) { + *(unsigned int*)dest = *(unsigned int*)func; + dest += 4; + } + func += 4; + bytecount += 4; + } + + // immediate + if(FPU) + { + // Can't have immediate operand + } + else if(!twoByte) + { + if((opcode & 0xC7) == 0x04 || + (opcode & 0xFE) == 0x6A || // PUSH/POP/IMUL + (opcode & 0xF0) == 0x70 || // Jcc + opcode == 0x80 || + opcode == 0x83 || + (opcode & 0xFD) == 0xA0 || // MOV + opcode == 0xA8 || // TEST + (opcode & 0xF8) == 0xB0 || // MOV + (opcode & 0xFE) == 0xC0 || // RCL + opcode == 0xC6 || // MOV + opcode == 0xCD || // INT + (opcode & 0xFE) == 0xD4 || // AAD/AAM + (opcode & 0xF8) == 0xE0 || // LOOP/JCXZ + opcode == 0xEB || + opcode == 0xF6 && (modRM & 0x30) == 0x00) // TEST + { + if (dest) + *dest++ = *func++; + else + func++; + bytecount++; + } + else if((opcode & 0xF7) == 0xC2) // RET + { + if (dest) { + *(unsigned short*)dest = *(unsigned short*)func; + dest += 2; + } + func += 2; + bytecount += 2; + } + else if((opcode & 0xFC) == 0x80 || + (opcode & 0xC7) == 0x05 || + (opcode & 0xF8) == 0xB8 || + (opcode & 0xFE) == 0xE8 || // CALL/Jcc + (opcode & 0xFE) == 0x68 || + (opcode & 0xFC) == 0xA0 || + (opcode & 0xEE) == 0xA8 || + opcode == 0xC7 || + opcode == 0xF7 && (modRM & 0x30) == 0x00) + { + if (dest) { + //Fix CALL/JMP offset + if ((opcode & 0xFE) == 0xE8) { + if (operandSize == 4) + { + *(long*)dest = ((func + *(long*)func) - dest); + + //pRED* edit. func is the current address of the call address, +4 is the next instruction, so the value of $pc + check_thunks(dest+4, func+4); + } + else + *(short*)dest = ((func + *(short*)func) - dest); + + } else { + if (operandSize == 4) + *(unsigned long*)dest = *(unsigned long*)func; + else + *(unsigned short*)dest = *(unsigned short*)func; + } + dest += operandSize; + } + func += operandSize; + bytecount += operandSize; + + } + } + else + { + if(opcode == 0xBA || // BT + opcode == 0x0F || // 3DNow! + (opcode & 0xFC) == 0x70 || // PSLLW + (opcode & 0xF7) == 0xA4 || // SHLD + opcode == 0xC2 || + opcode == 0xC4 || + opcode == 0xC5 || + opcode == 0xC6) + { + if (dest) + *dest++ = *func++; + else + func++; + } + else if((opcode & 0xF0) == 0x80) // Jcc -i + { + if (dest) { + if (operandSize == 4) + *(unsigned long*)dest = *(unsigned long*)func; + else + *(unsigned short*)dest = *(unsigned short*)func; + + dest += operandSize; + } + func += operandSize; + bytecount += operandSize; + } + } + } + + return bytecount; +} + +//insert a specific JMP instruction at the given location +void inject_jmp(void* src, void* dest) { + *(unsigned char*)src = OP_JMP; + *(long*)((unsigned char*)src+1) = (long)((unsigned char*)dest - ((unsigned char*)src + OP_JMP_SIZE)); +} + +//fill a given block with NOPs +void fill_nop(void* src, unsigned int len) { + unsigned char* src2 = (unsigned char*)src; + while (len) { + *src2++ = OP_NOP; + --len; + } +} + +void* eval_jump(void* src) { + unsigned char* addr = (unsigned char*)src; + + if (!addr) return 0; + + //import table jump + if (addr[0] == OP_PREFIX && addr[1] == OP_JMP_SEG) { + addr += 2; + addr = *(unsigned char**)addr; + //TODO: if addr points into the IAT + return *(void**)addr; + } + + //8bit offset + else if (addr[0] == OP_JMP_BYTE) { + addr = &addr[OP_JMP_BYTE_SIZE] + *(char*)&addr[1]; + //mangled 32bit jump? + if (addr[0] = OP_JMP) { + addr = addr + *(int*)&addr[1]; + } + return addr; + } + /* + //32bit offset + else if (addr[0] == OP_JMP) { + addr = &addr[OP_JMP_SIZE] + *(int*)&addr[1]; + } + */ + + return addr; +} +/* +from ms detours package +static bool detour_is_imported(PBYTE pbCode, PBYTE pbAddress) +{ + MEMORY_BASIC_INFORMATION mbi; + VirtualQuery((PVOID)pbCode, &mbi, sizeof(mbi)); + __try { + PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)mbi.AllocationBase; + if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { + return false; + } + + PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((PBYTE)pDosHeader + + pDosHeader->e_lfanew); + if (pNtHeader->Signature != IMAGE_NT_SIGNATURE) { + return false; + } + + if (pbAddress >= ((PBYTE)pDosHeader + + pNtHeader->OptionalHeader + .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress) && + pbAddress < ((PBYTE)pDosHeader + + pNtHeader->OptionalHeader + .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress + + pNtHeader->OptionalHeader + .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size)) { + return true; + } + return false; + } + __except(EXCEPTION_EXECUTE_HANDLER) { + return false; + } +} +*/ diff --git a/asm/asm.h b/asm/asm.h new file mode 100644 index 0000000..6086232 --- /dev/null +++ b/asm/asm.h @@ -0,0 +1,40 @@ +#ifndef __ASM_H__ +#define __ASM_H__ + +#define OP_JMP 0xE9 +#define OP_JMP_SIZE 5 + +#define OP_NOP 0x90 +#define OP_NOP_SIZE 1 + +#define OP_PREFIX 0xFF +#define OP_JMP_SEG 0x25 + +#define OP_JMP_BYTE 0xEB +#define OP_JMP_BYTE_SIZE 2 + +#ifdef __cplusplus +extern "C" { +#endif + +void check_thunks(unsigned char *dest, unsigned char *pc); + +//if dest is NULL, returns minimum number of bytes needed to be copied +//if dest is not NULL, it will copy the bytes to dest as well as fix CALLs and JMPs +//http://www.devmaster.net/forums/showthread.php?t=2311 +int copy_bytes(unsigned char *func, unsigned char* dest, int required_len); + +//insert a specific JMP instruction at the given location +void inject_jmp(void* src, void* dest); + +//fill a given block with NOPs +void fill_nop(void* src, unsigned int len); + +//evaluate a JMP at the target +void* eval_jump(void* src); + +#ifdef __cplusplus +} +#endif + +#endif //__ASM_H__ diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..b925533 --- /dev/null +++ b/build.bat @@ -0,0 +1,7 @@ +mkdir build +cd build +call "%VS100COMNTOOLS%\vsvars32.bat" +..\configure.py +build.py +pause +cd .. \ No newline at end of file diff --git a/build.py b/build.py new file mode 100644 index 0000000..6734248 --- /dev/null +++ b/build.py @@ -0,0 +1,10 @@ + +# vim: set ts=2 sw=2 tw=99 noet: +import sys +import ambuild.runner as runner + +run = runner.Runner() +run.options.usage = '%prog [options] [job list]' +run.options.add_option('-l', '--list-jobs', action='store_true', dest='list', help='print list of jobs') +run.Build() + \ No newline at end of file diff --git a/buildbot/PackageScript b/buildbot/PackageScript new file mode 100644 index 0000000..f1948c0 --- /dev/null +++ b/buildbot/PackageScript @@ -0,0 +1,109 @@ +# vim: set ts=2 sw=2 tw=99 noet ft=python: +import os +import shutil +import ambuild.osutil as osutil +from ambuild.command import Command + +job = AMBuild.AddJob('package') + +class DestroyPath(Command): + def __init__(self, folder): + Command.__init__(self) + self.folder = folder + + def destroy(self, path): + entries = os.listdir(path) + for entry in entries: + newpath = os.path.join(path, entry) + if os.path.isdir(newpath): + self.destroy(newpath) + os.rmdir(newpath) + elif os.path.isfile(newpath): + os.remove(newpath) + + def run(self, runner, job): + runner.PrintOut('rm -rf {0}/*'.format(self.folder)) + self.destroy(self.folder) + +class CreateFolders(Command): + def __init__(self, folders): + Command.__init__(self) + self.folders = folders + + def run(self, runner, job): + for folder in self.folders: + path = os.path.join(*folder) + runner.PrintOut('mkdir {0}'.format(path)) + os.makedirs(path) + +#Shallow folder copy +class CopyFolder(Command): + def __init__(self, fromList, toList, excludes = []): + Command.__init__(self) + self.fromPath = os.path.join(AMBuild.sourceFolder, *fromList) + self.toPath = os.path.join(*toList) + self.excludes = excludes + + def run(self, runner, job): + entries = os.listdir(self.fromPath) + for entry in entries: + if entry in self.excludes: + continue + path = os.path.join(self.fromPath, entry) + if not os.path.isfile(path): + continue + runner.PrintOut('copy {0} to {1}'.format(path, self.toPath)) + shutil.copy(path, self.toPath) + +#Single file copy +class CopyFile(Command): + def __init__(self, fromFile, toPath): + Command.__init__(self) + self.fromFile = fromFile + self.toPath = toPath + + def run(self, runner, job): + runner.PrintOut('copy {0} to {1}'.format(self.fromFile, self.toPath)) + shutil.copy(self.fromFile, self.toPath) + + +folders = [['addons', 'sourcemod', 'extensions'], ['addons', 'sourcemod', 'gamedata'], ['addons', 'sourcemod', 'scripting', 'include']] + +#Setup +job.AddCommand(DestroyPath(os.path.join(AMBuild.outputFolder, 'package'))) +job.AddCommand(CreateFolders(folders)) + +#Copy Files +job.AddCommand(CopyFile(os.path.join(AMBuild.sourceFolder, 'connect.games.txt'), + os.path.join('addons', 'sourcemod', 'gamedata'))) +job.AddCommand(CopyFile(os.path.join(AMBuild.sourceFolder, 'connect.inc'), + os.path.join('addons', 'sourcemod', 'scripting', 'include'))) +job.AddCommand(CopyFile(os.path.join(AMBuild.sourceFolder, 'connect.sp'), + os.path.join('addons', 'sourcemod', 'scripting'))) + +bincopies = [] + +def AddNormalLibrary(name, dest): + dest = os.path.join('addons', 'sourcemod', dest) + bincopies.append(CopyFile(os.path.join('..', name, name + osutil.SharedLibSuffix()), dest)) + #pdb_list.append(name + '\\' + name + '.pdb') + +def AddHL2Library(name, dest): + for i in SM.sdkInfo: + sdk = SM.sdkInfo[i] + if AMBuild.target['platform'] not in sdk['platform']: + continue + AddNormalLibrary(name + '.ext.' + sdk['ext'], dest) + +#pdb_list = [] + +AddHL2Library('connect', 'extensions') + +job.AddCommandGroup(bincopies) + +#if AMBuild.target['platform'] == 'windows': +# pdblog = open(os.path.join(AMBuild.outputFolder, 'pdblog.txt'), 'wt') +# for pdb in pdb_list: +# pdblog.write(pdb + '\n') +# pdblog.close() + diff --git a/buildbot/bootstrap.pl b/buildbot/bootstrap.pl new file mode 100644 index 0000000..298ed7d --- /dev/null +++ b/buildbot/bootstrap.pl @@ -0,0 +1,79 @@ +#!/usr/bin/perl +# vim: set ts=2 sw=2 tw=99 noet: + +use strict; +use Cwd; +use File::Basename; +use File::Path; + +my ($myself, $path) = fileparse($0); +chdir($path); + +require 'helpers.pm'; + +#Go back above build dir +chdir(Build::PathFormat('../..')); + +#Get the source path. +our ($root) = getcwd(); + +rmtree('OUTPUT'); +mkdir('OUTPUT') or die("Failed to create output folder: $!\n"); +chdir('OUTPUT'); +my ($result); +print "Attempting to reconfigure...\n"; + +#update and configure shiz +if ($^O eq "linux") { + my @sdks = ('sourcemod-1.3', 'mmsource-1.8', 'hl2sdk-ob-valve'); + my ($sdk); + foreach $sdk (@sdks) { + print "Updating checkout of ", $sdk, " on ", $^O, "\n"; + $result = `hg pull -u /home/builds/common/$sdk`; + print $result; + } + + $ENV{'SOURCEMOD13'} = '/home/builds/common/sourcemod-1.3'; + $ENV{'MMSOURCE18'} = '/home/builds/common/mmsource-1.8'; + + $ENV{'HL2SDKOBVALVE'} = '/home/builds/common/hl2sdk-ob-valve'; +} elsif ($^O eq "darwin") { + my @sdks = ('sourcemod-1.3', 'mmsource-1.8', 'hl2sdk-ob-valve'); + my ($sdk); + foreach $sdk (@sdks) { + print "Updating checkout of ", $sdk, " on ", $^O, "\n"; + $result = `hg pull -u /Users/builds/builds/common/$sdk`; + print $result; + } + + $ENV{'SOURCEMOD13'} = '/Users/builds/builds/common/sourcemod-1.3'; + $ENV{'MMSOURCE18'} = '/Users/builds/builds/common/mmsource-1.8'; + + $ENV{'HL2SDKOBVALVE'} = '/Users/builds/builds/common/hl2sdk-ob-valve'; +} else { + my @sdks = ('sourcemod-1.3', 'mmsource-1.8', 'hl2sdk-ob-valve'); + my ($sdk); + foreach $sdk (@sdks) { + print "Updating checkout of ", $sdk, " on ", $^O, "\n"; + $result = `hg pull -u C:/Scripts/common/$sdk`; + print $result; + } + + $ENV{'SOURCEMOD13'} = 'C:/Scripts/common/sourcemod-1.3'; + $ENV{'MMSOURCE18'} = 'C:/Scripts/common/mmsource-1.8'; + + $ENV{'HL2SDKOBVALVE'} = 'C:/Scripts/common/hl2sdk-ob-valve'; +} + +#configure AMBuild +if ($^O eq "linux") { + $result = `CC=gcc-4.1 CXX=gcc-4.1 python3.1 ../build/configure.py --enable-optimize`; +} elsif ($^O eq "darwin") { + $result = `CC=gcc-4.2 CXX=gcc-4.2 python3.1 ../build/configure.py --enable-optimize`; +} else { + $result = `C:\\Python31\\Python.exe ..\\build\\configure.py --enable-optimize`; +} +print "$result\n"; +if ($? != 0) { + die('Could not configure!'); +} diff --git a/buildbot/helpers.pm b/buildbot/helpers.pm new file mode 100644 index 0000000..7dcb24d --- /dev/null +++ b/buildbot/helpers.pm @@ -0,0 +1,94 @@ +#!/usr/bin/perl + +use strict; +use Cwd; + +package Build; + +sub HgRevNum +{ + my ($path) = (@_); + my ($cd, $text, $rev); + + $cd = Cwd::cwd(); + chdir($path); + $text = `hg identify -n`; + chdir($cd); + + chomp $text; + if ($text =~ /^(\d+)/) + { + return $1; + } + + return 0; +} + +sub ProductVersion +{ + my ($file) = (@_); + my ($version); + open(FILE, $file) or die "Could not open $file: $!\n"; + $version = ; + close(FILE); + chomp $version; + return $version; +} + +sub Delete +{ + my ($str)=(@_); + if ($^O =~ /MSWin/) + { + Command("del /S /F /Q \"$str\""); + Command("rmdir /S /Q \"$str\""); + } else { + Command("rm -rf $str"); + } + return !(-e $str); +} + +sub Copy +{ + my ($src,$dest)=(@_); + if ($^O =~ /MSWin/) + { + Command("copy \"$src\" \"$dest\" /y"); + } else { + Command("cp \"$src\" \"$dest\""); + } + return (-e $dest); +} + +sub Move +{ + my ($src,$dest)=(@_); + if ($^O =~ /MSWin/) + { + Command("move \"$src\" \"$dest\""); + } else { + Command("mv \"$src\" \"$dest\""); + } + return (-e $dest); +} + +sub Command +{ + my($cmd)=(@_); + print "$cmd\n"; + return `$cmd`; +} + +sub PathFormat +{ + my ($str)=(@_); + if ($^O =~ /MSWin/) + { + $str =~ s#/#\\#g; + } else { + $str =~ s#\\#/#g; + } + return $str; +} + +return 1; diff --git a/buildbot/package.pl b/buildbot/package.pl new file mode 100644 index 0000000..f7396c1 --- /dev/null +++ b/buildbot/package.pl @@ -0,0 +1,82 @@ +#!/usr/bin/perl + +use strict; +use Cwd; +use File::Basename; +use Net::FTP; + +my ($ftp_host, $ftp_user, $ftp_pass, $ftp_path); + +$ftp_host = $ARGV[0]; +$ftp_user = $ARGV[1]; +$ftp_pass = $ARGV[2]; +$ftp_path = $ARGV[3]; + +my ($myself, $path) = fileparse($0); +chdir($path); + +require 'helpers.pm'; + +my ($version); +$version = Build::ProductVersion(Build::PathFormat('product.version')); +$version .= '-hg' . Build::HgRevNum('.'); + +# Append OS to package version +if ($^O eq "darwin") +{ + $version .= '-mac'; +} +elsif ($^O =~ /MSWin/) +{ + $version .= '-windows'; +} +else +{ + $version .= '-' . $^O; +} + +#Switch to the output folder. +chdir(Build::PathFormat('../../OUTPUT/package')); + +my ($filename); +$filename = 'vanillaweps-' . $version; +if ($^O eq "linux") +{ + $filename .= '.tar.gz'; + print "tar zcvf $filename addons\n"; + system("tar zcvf $filename addons"); +} +else +{ + $filename .= '.zip'; + print "zip -r $filename addons\n"; + system("zip -r $filename addons"); +} + +#my ($major,$minor) = ($version =~ /^(\d+)\.(\d+)/); +#$ftp_path .= "/$major.$minor"; + +my ($ftp); + +$ftp = Net::FTP->new($ftp_host, Debug => 0) + or die "Cannot connect to host $ftp_host : $@"; + +$ftp->login($ftp_user, $ftp_pass) + or die "Cannot connect to host $ftp_host as $ftp_user : " . $ftp->message . "\n"; + +if ($ftp_path ne '') +{ + $ftp->cwd($ftp_path) + or die "Cannot change to folder $ftp_path : " . $ftp->message . "\n"; +} + +$ftp->binary(); +$ftp->put($filename) + or die "Cannot drop file $filename ($ftp_path) : " . $ftp->message . "\n"; + +$ftp->close(); + +print "File sent to drop site as $filename -- build succeeded.\n"; + +exit(0); + diff --git a/buildbot/product.version b/buildbot/product.version new file mode 100644 index 0000000..afaf360 --- /dev/null +++ b/buildbot/product.version @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/buildbot/pushbuild.txt b/buildbot/pushbuild.txt new file mode 100644 index 0000000..e69de29 diff --git a/buildbot/startbuild.pl b/buildbot/startbuild.pl new file mode 100644 index 0000000..c77515e --- /dev/null +++ b/buildbot/startbuild.pl @@ -0,0 +1,27 @@ +#!/usr/bin/perl +# vim: set ts=2 sw=2 tw=99 noet: + +use File::Basename; + +my ($myself, $path) = fileparse($0); +chdir($path); + +require 'helpers.pm'; + +chdir('../../OUTPUT'); + +if ($^O eq "linux" || $^O eq "darwin") { + system("python3.1 build.py 2>&1"); +} else { + system("C:\\Python31\\python.exe build.py 2>&1"); +} + +if ($? != 0) +{ + die "Build failed: $!\n"; +} +else +{ + exit(0); +} + diff --git a/configure.py b/configure.py new file mode 100644 index 0000000..f0f2aa0 --- /dev/null +++ b/configure.py @@ -0,0 +1,10 @@ +# vim: set ts=2 sw=2 tw=99 noet: +import sys +import ambuild.runner as runner + +run = runner.Runner() +run.options.add_option('--enable-debug', action='store_const', const='1', dest='debug', + help='Enable debugging symbols') +run.options.add_option('--enable-optimize', action='store_const', const='1', dest='opt', + help='Enable optimization') +run.Configure(sys.path[0]) diff --git a/connect.games.txt b/connect.games.txt new file mode 100644 index 0000000..4593364 --- /dev/null +++ b/connect.games.txt @@ -0,0 +1,68 @@ +"Games" +{ + "#default" + { + "#supported" + { + "engine" "orangebox_valve" + } + + "Offsets" + { + "ISteamGameServer__BeginAuthSession" + { + "linux" "20" + "windows" "20" + } + + "ISteamGameServer__EndAuthSession" + { + "linux" "21" + "windows" "21" + } + } + + "Signatures" + { + "CBaseServer__ConnectClient" + { + "library" "engine" + "linux" "@_ZN11CBaseServer13ConnectClientER8netadr_siiiiPKcS3_S3_i" + "windows" "\x81\xEC\x2A\x2A\x2A\x2A\x56\x68\x2A\x2A\x2A\x2A\x8B\xF1\xFF\x15\x2A\x2A\x2A\x2A\x8B\x06" + } + + "CBaseServer__CheckChallengeType" + { + "library" "engine" + "linux" "@_ZN11CBaseServer18CheckChallengeTypeEP11CBaseClientiR8netadr_siPKcii" + "windows" "\x8B\x44\x24\x10\x83\xEC\x14" + } + + "CBaseServer__RejectConnection" + { + "library" "engine" + "linux" "@_ZN11CBaseServer16RejectConnectionERK8netadr_siPc" + "windows" "\x81\xEC\x2A\x2A\x2A\x2A\x56\x6A\xFF" + } + + "CBaseClient__SetSteamID" + { + "library" "engine" + "linux" "@_ZN11CBaseClient10SetSteamIDERK8CSteamID" + "windows" "\x8B\x44\x24\x04\x8B\x10\x89\x51\x2A\x8B\x40\x2A\x89\x41\x2A\xC2\x04\x00" + } + + "CBaseServer__CheckMasterServerRequestRestart" + { + "library" "engine" + "windows" "\xE8\x2A\x2A\x2A\x2A\x83\x78\x08\x00\x74\x2A\xE8\x2A\x2A\x2A\x2A\x8B\x48\x08\x8B\x01\x8B\x50\x2A\xFF\xD2\x84\xC0" + } + + "Steam3Server" + { + "library" "engine" + "linux" "@_Z12Steam3Serverv" + } + } + } +} diff --git a/connect.inc b/connect.inc new file mode 100644 index 0000000..8562811 --- /dev/null +++ b/connect.inc @@ -0,0 +1,22 @@ +#if defined _connect_included +#endinput +#endif +#define _connect_included + +forward bool:OnClientPreConnect(const String:name[], String:password[255], const String:ip[], const String:steamID[], String:rejectReason[255]); + +public Extension:__ext_Connect = +{ + name = "Connect", + file = "connect.ext", +#if defined AUTOLOAD_EXTENSIONS + autoload = 1, +#else + autoload = 0, +#endif +#if defined REQUIRE_EXTENSIONS + required = 1, +#else + required = 0, +#endif +} \ No newline at end of file diff --git a/connect.sp b/connect.sp new file mode 100644 index 0000000..4d92548 --- /dev/null +++ b/connect.sp @@ -0,0 +1,24 @@ +#pragma semicolon 1 // Force strict semicolon mode. + +#include +#define REQUIRE_EXTENSIONS +#include + +public bool:OnClientPreConnect(const String:name[], String:password[255], const String:ip[], const String:steamID[], String:rejectReason[255]) +{ + PrintToServer("----------------\nName: %s\nPassword: %s\nIP: %s\nSteamID: %s\n----------------", name, password, ip, steamID); + + new AdminId:admin = FindAdminByIdentity(AUTHMETHOD_STEAM, steamID); + + if (admin == INVALID_ADMIN_ID) + { + return true; + } + + if (GetAdminFlag(admin, Admin_Root)) + { + GetConVarString(FindConVar("sv_password"), password, 255); + } + + return true; +} \ No newline at end of file diff --git a/extension.cpp b/extension.cpp new file mode 100644 index 0000000..72d49c3 --- /dev/null +++ b/extension.cpp @@ -0,0 +1,392 @@ +/* + * ============================================================================= + * Connect Extension + * Copyright (C) 2011 Asher Baker (asherkin). All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "extension.hpp" +#include "CDetour/detours.h" + +#include "steam/steamclientpublic.h" + +Connect g_connect; + +SMEXT_LINK(&g_connect); + +ICvar *icvar = NULL; + +ConVar connectVersion("connect_version", SMEXT_CONF_VERSION, FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY, SMEXT_CONF_DESCRIPTION " Version"); + +IGameConfig *g_pGameConf = NULL; + +IForward *g_pConnectForward = NULL; + +class IClient; +class CBaseClient; + +class CBaseServer; + +typedef enum EBeginAuthSessionResult +{ + k_EBeginAuthSessionResultOK = 0, // Ticket is valid for this game and this steamID. + k_EBeginAuthSessionResultInvalidTicket = 1, // Ticket is not valid. + k_EBeginAuthSessionResultDuplicateRequest = 2, // A ticket has already been submitted for this steamID + k_EBeginAuthSessionResultInvalidVersion = 3, // Ticket is from an incompatible interface version + k_EBeginAuthSessionResultGameMismatch = 4, // Ticket is not for this game + k_EBeginAuthSessionResultExpiredTicket = 5, // Ticket has expired +} EBeginAuthSessionResult; + +typedef struct netadr_s +{ +private: + typedef enum + { + NA_NULL = 0, + NA_LOOPBACK, + NA_BROADCAST, + NA_IP, + } netadrtype_t; + +public: + netadrtype_t type; + unsigned char ip[4]; + unsigned short port; +} netadr_t; + +char *CSteamID::Render() const +{ + static char szSteamID[64]; + V_snprintf(szSteamID, sizeof(szSteamID), "STEAM_0:%u:%u", (m_unAccountID % 2) ? 1 : 0, (int32)m_unAccountID/2); + return szSteamID; +} + +class CSteam3Server +{ +public: + void *m_pSteamGameServer; + void *m_pSteamGameServerUtils; + void *m_pSteamMasterServerUpdater; + void *m_pSteamGameServerNetworking; + void *m_pSteamGameServerStats; +} *g_pSteam3Server; + +CBaseServer *g_pBaseServer = NULL; + +typedef CSteam3Server *(*Steam3ServerFunc)(); + +#ifndef WIN32 +typedef void (*RejectConnectionFunc)(CBaseServer *, const netadr_t &address, int iClientChallenge, char *pchReason); +#else +typedef void (__fastcall *RejectConnectionFunc)(CBaseServer *, void *, const netadr_t &address, int iClientChallenge, char *pchReason); +#endif + +#ifndef WIN32 +typedef void (*SetSteamIDFunc)(CBaseClient *, const CSteamID &steamID); +#else +typedef void (__fastcall *SetSteamIDFunc)(CBaseClient *, void *, const CSteamID &steamID); +#endif + +Steam3ServerFunc g_pSteam3ServerFunc = NULL; +RejectConnectionFunc g_pRejectConnectionFunc = NULL; +SetSteamIDFunc g_pSetSteamIDFunc = NULL; + +CSteam3Server *Steam3Server() +{ + if (!g_pSteam3ServerFunc) + return NULL; + + return g_pSteam3ServerFunc(); +} + +void RejectConnection(const netadr_t &address, int iClientChallenge, char *pchReason) +{ + if (!g_pRejectConnectionFunc || !g_pBaseServer) + return; + +#ifndef WIN32 + g_pRejectConnectionFunc(g_pBaseServer, address, iClientChallenge, pchReason); +#else + g_pRejectConnectionFunc(g_pBaseServer, NULL, address, iClientChallenge, pchReason); +#endif +} + +void SetSteamID(CBaseClient *pClient, const CSteamID &steamID) +{ + if (!pClient || !g_pSetSteamIDFunc) + return; + +#ifndef WIN32 + g_pSetSteamIDFunc(pClient, steamID); +#else + g_pSetSteamIDFunc(pClient, NULL, steamID); +#endif +} + +class VFuncEmptyClass{}; + +int g_nBeginAuthSessionOffset = 0; +int g_nEndAuthSessionOffset = 0; + +EBeginAuthSessionResult BeginAuthSession(const void *pAuthTicket, int cbAuthTicket, CSteamID steamID) +{ + if (!g_pSteam3Server || !g_pSteam3Server->m_pSteamGameServer || g_nBeginAuthSessionOffset == 0) + return k_EBeginAuthSessionResultOK; + + void **this_ptr = *(void ***)&g_pSteam3Server->m_pSteamGameServer; + void **vtable = *(void ***)g_pSteam3Server->m_pSteamGameServer; + void *func = vtable[g_nBeginAuthSessionOffset]; + + union { + EBeginAuthSessionResult (VFuncEmptyClass::*mfpnew)(const void *, int, CSteamID); + +#ifndef WIN32 + struct { + void *addr; + intptr_t adjustor; + } s; + } u; + + u.s.addr = func; + u.s.adjustor = 0; +#else + void *addr; + } u; + + u.addr = func; +#endif + + return (EBeginAuthSessionResult)(reinterpret_cast(this_ptr)->*u.mfpnew)(pAuthTicket, cbAuthTicket, steamID); +} + +void EndAuthSession(CSteamID steamID) +{ + if (!g_pSteam3Server || !g_pSteam3Server->m_pSteamGameServer || g_nEndAuthSessionOffset == 0) + return; + + void **this_ptr = *(void ***)&g_pSteam3Server->m_pSteamGameServer; + void **vtable = *(void ***)g_pSteam3Server->m_pSteamGameServer; + void *func = vtable[g_nEndAuthSessionOffset]; + + union { + void (VFuncEmptyClass::*mfpnew)(CSteamID); + +#ifndef WIN32 + struct { + void *addr; + intptr_t adjustor; + } s; + } u; + + u.s.addr = func; + u.s.adjustor = 0; +#else + void *addr; + } u; + + u.addr = func; +#endif + + return (void)(reinterpret_cast(this_ptr)->*u.mfpnew)(steamID); +} + +DECL_DETOUR(CBaseServer__ConnectClient); +DECL_DETOUR(CBaseServer__CheckChallengeType); + +bool g_bSuppressCheckChallengeType = false; + +char passwordBuffer[255]; +DETOUR_DECL_MEMBER9(CBaseServer__ConnectClient, IClient *, netadr_t &, address, int, nProtocol, int, iChallenge, int, iClientChallenge, int, nAuthProtocol, const char *, pchName, const char *, pchPassword, const char *, pCookie, int, cbCookie) +{ + g_pBaseServer = (CBaseServer *)this; + + if (pCookie == NULL || cbCookie < sizeof(uint64)) + { + RejectConnection(address, iClientChallenge, "#GameUI_ServerRejectInvalidSteamCertLen"); + return NULL; + } + + char ipString[30]; + V_snprintf(ipString, sizeof(ipString), "%u.%u.%u.%u", address.ip[0], address.ip[1], address.ip[2], address.ip[3]); + V_strncpy(passwordBuffer, pchPassword, 255); + uint64 ullSteamID = *(uint64 *)pCookie; + + void *pvTicket = (void *)((intptr_t)pCookie + sizeof(uint64)); + int cbTicket = cbCookie - sizeof(uint64); + + EBeginAuthSessionResult result = BeginAuthSession(pvTicket, cbTicket, CSteamID(ullSteamID)); + if (result != k_EBeginAuthSessionResultOK) + { + EndAuthSession(CSteamID(ullSteamID)); + RejectConnection(address, iClientChallenge, "#GameUI_ServerRejectSteam"); + return NULL; + } + + char rejectReason[255]; + + g_pConnectForward->PushString(pchName); + g_pConnectForward->PushStringEx(passwordBuffer, 255, SM_PARAM_STRING_UTF8 | SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK); + g_pConnectForward->PushString(ipString); + g_pConnectForward->PushString(CSteamID(ullSteamID).Render()); + g_pConnectForward->PushStringEx(rejectReason, 255, SM_PARAM_STRING_UTF8 | SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK); + + cell_t retVal = 1; + g_pConnectForward->Execute(&retVal); + + if (retVal == 0) + { + EndAuthSession(CSteamID(ullSteamID)); + RejectConnection(address, iClientChallenge, rejectReason); + return NULL; + } + + pchPassword = passwordBuffer; + + g_bSuppressCheckChallengeType = true; + return DETOUR_MEMBER_CALL(CBaseServer__ConnectClient)(address, nProtocol, iChallenge, iClientChallenge, nAuthProtocol, pchName, pchPassword, pCookie, cbCookie); +} + +DETOUR_DECL_MEMBER7(CBaseServer__CheckChallengeType, bool, CBaseClient *, pClient, int, nUserID, netadr_t &, address, int, nAuthProtocol, const char *, pCookie, int, cbCookie, int, iClientChallenge) +{ + if (g_bSuppressCheckChallengeType) + { + uint64 ullSteamID = *(uint64 *)pCookie; + SetSteamID(pClient, CSteamID(ullSteamID)); + + g_bSuppressCheckChallengeType = false; + return true; + } else { + return DETOUR_MEMBER_CALL(CBaseServer__CheckChallengeType)(pClient, nUserID, address, nAuthProtocol, pCookie, cbCookie, iClientChallenge); + } +} + +bool Connect::SDK_OnLoad(char *error, size_t maxlen, bool late) +{ + char conf_error[255] = ""; + if (!gameconfs->LoadGameConfigFile("connect.games", &g_pGameConf, conf_error, sizeof(conf_error))) + { + if (conf_error[0]) + { + snprintf(error, maxlen, "Could not read connect.games.txt: %s\n", conf_error); + } + return false; + } + + if (!g_pGameConf->GetMemSig("CBaseServer__RejectConnection", (void **)(&g_pRejectConnectionFunc)) || !g_pRejectConnectionFunc) + { + snprintf(error, maxlen, "Failed to find CBaseServer__RejectConnection function.\n"); + return false; + } + + if (!g_pGameConf->GetMemSig("CBaseClient__SetSteamID", (void **)(&g_pSetSteamIDFunc)) || !g_pSetSteamIDFunc) + { + snprintf(error, maxlen, "Failed to find CBaseClient__SetSteamID function.\n"); + return false; + } + +#ifndef WIN32 + if (!g_pGameConf->GetMemSig("Steam3Server", (void **)(&g_pSteam3ServerFunc)) || !g_pSteam3ServerFunc) + { + snprintf(error, maxlen, "Failed to find Steam3Server function.\n"); + return false; + } +#else + void *address; + if (!g_pGameConf->GetMemSig("CBaseServer__CheckMasterServerRequestRestart", &address) || !address) + { + snprintf(error, maxlen, "Failed to find CBaseServer__CheckMasterServerRequestRestart function.\n"); + return false; + } + + //META_CONPRINTF("CheckMasterServerRequestRestart: %p\n", address); + address = (void *)((intptr_t)address + 1); // Skip CALL opcode + intptr_t offset = (intptr_t)(*(void **)address); // Get offset + + g_pSteam3ServerFunc = (Steam3ServerFunc)((intptr_t)address + offset + sizeof(intptr_t)); + //META_CONPRINTF("Steam3Server: %p\n", g_pSteam3ServerFunc); +#endif + + g_pSteam3Server = Steam3Server(); + if (!g_pSteam3Server) + { + snprintf(error, maxlen, "Unable to get Steam3Server singleton.\n"); + return false; + } + + /* + META_CONPRINTF("ISteamGameServer: %p\n", g_pSteam3Server->m_pSteamGameServer); + META_CONPRINTF("ISteamUtils: %p\n", g_pSteam3Server->m_pSteamGameServerUtils); + META_CONPRINTF("ISteamMasterServerUpdater: %p\n", g_pSteam3Server->m_pSteamMasterServerUpdater); + META_CONPRINTF("ISteamNetworking: %p\n", g_pSteam3Server->m_pSteamGameServerNetworking); + META_CONPRINTF("ISteamGameServerStats: %p\n", g_pSteam3Server->m_pSteamGameServerStats); + */ + + if (!g_pGameConf->GetOffset("ISteamGameServer__BeginAuthSession", &g_nBeginAuthSessionOffset) || g_nBeginAuthSessionOffset == 0) + { + snprintf(error, maxlen, "Failed to find ISteamGameServer__BeginAuthSession offset.\n"); + return false; + } + + if (!g_pGameConf->GetOffset("ISteamGameServer__EndAuthSession", &g_nEndAuthSessionOffset) || g_nEndAuthSessionOffset == 0) + { + snprintf(error, maxlen, "Failed to find ISteamGameServer__EndAuthSession offset.\n"); + return false; + } + + CDetourManager::Init(g_pSM->GetScriptingEngine(), g_pGameConf); + + CREATE_DETOUR(CBaseServer__ConnectClient); + CREATE_DETOUR(CBaseServer__CheckChallengeType); + + g_pConnectForward = g_pForwards->CreateForward("OnClientPreConnect", ET_LowEvent, 5, NULL, Param_String, Param_String, Param_String, Param_String, Param_String); + + return true; +} + +bool Connect::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlen, bool late) +{ + GET_V_IFACE_CURRENT(GetEngineFactory, icvar, ICvar, CVAR_INTERFACE_VERSION); + if (!icvar) + { + snprintf(error, maxlen, "Could not find interface %s", CVAR_INTERFACE_VERSION); + return false; + } + + g_pCVar = icvar; + + ConVar_Register(0, this); + + return true; +} + +void Connect::SDK_OnUnload() +{ + g_pForwards->ReleaseForward(g_pConnectForward); + + gameconfs->CloseGameConfigFile(g_pGameConf); +} + +bool Connect::SDK_OnMetamodUnload(char *error, size_t maxlen) +{ + DESTROY_DETOUR(CBaseServer__ConnectClient); + DESTROY_DETOUR(CBaseServer__CheckChallengeType); + + return true; +} + +bool Connect::RegisterConCommandBase(ConCommandBase *pCommand) { + META_REGCVAR(pCommand); + return true; +} diff --git a/extension.hpp b/extension.hpp new file mode 100644 index 0000000..72a84f5 --- /dev/null +++ b/extension.hpp @@ -0,0 +1,103 @@ +/** + * ============================================================================= + * TF2 Items Extension + * Copyright (C) 2009-2010 AzuiSleet, Asher Baker (asherkin). All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + */ + +#ifndef _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ +#define _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ + +/** + * @file extension.hpp + * @brief Connect extension code header. + */ + +#include "smsdk_ext.hpp" + +/** + * @brief Sample implementation of the SDK Extension. + * Note: Uncomment one of the pre-defined virtual functions in order to use it. + */ +class Connect : public SDKExtension, public IConCommandBaseAccessor +{ +public: + /** + * @brief This is called after the initial loading sequence has been processed. + * + * @param error Error message buffer. + * @param maxlength Size of error message buffer. + * @param late Whether or not the module was loaded after map load. + * @return True to succeed loading, false to fail. + */ + virtual bool SDK_OnLoad(char *error, size_t maxlen, bool late); + + /** + * @brief This is called right before the extension is unloaded. + */ + virtual void SDK_OnUnload(); + + /** + * @brief Called when the pause state is changed. + */ + //virtual void SDK_OnPauseChange(bool paused); + + /** + * @brief this is called when Core wants to know if your extension is working. + * + * @param error Error message buffer. + * @param maxlength Size of error message buffer. + * @return True if working, false otherwise. + */ + //virtual bool QueryRunning(char *error, size_t maxlen); +public: +#if defined SMEXT_CONF_METAMOD + /** + * @brief Called when Metamod is attached, before the extension version is called. + * + * @param error Error buffer. + * @param maxlength Maximum size of error buffer. + * @param late Whether or not Metamod considers this a late load. + * @return True to succeed, false to fail. + */ + virtual bool SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlen, bool late); + + /** + * @brief Called when Metamod is detaching, after the extension version is called. + * NOTE: By default this is blocked unless sent from SourceMod. + * + * @param error Error buffer. + * @param maxlength Maximum size of error buffer. + * @return True to succeed, false to fail. + */ + virtual bool SDK_OnMetamodUnload(char *error, size_t maxlen); + + /** + * @brief Called when Metamod's pause state is changing. + * NOTE: By default this is blocked unless sent from SourceMod. + * + * @param paused Pause state being set. + * @param error Error buffer. + * @param maxlength Maximum size of error buffer. + * @return True to succeed, false to fail. + */ + //virtual bool SDK_OnMetamodPauseChange(bool paused, char *error, size_t maxlen); +#endif +public: //IConCommandBaseAccessor + bool RegisterConCommandBase(ConCommandBase *pCommand); +}; + +#endif // _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ diff --git a/msvc10/connect.sln b/msvc10/connect.sln new file mode 100644 index 0000000..3a23d46 --- /dev/null +++ b/msvc10/connect.sln @@ -0,0 +1,23 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "connect", "connect.vcxproj", "{B3E797CF-4E77-4C9D-B8A8-7589B6902206}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug - Orange Box Valve|Win32 = Debug - Orange Box Valve|Win32 + Release - Orange Box Valve|Win32 = Release - Orange Box Valve|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B3E797CF-4E77-4C9D-B8A8-7589B6902206}.Debug - Orange Box Valve|Win32.ActiveCfg = Debug - Orange Box Valve|Win32 + {B3E797CF-4E77-4C9D-B8A8-7589B6902206}.Debug - Orange Box Valve|Win32.Build.0 = Debug - Orange Box Valve|Win32 + {B3E797CF-4E77-4C9D-B8A8-7589B6902206}.Release - Orange Box Valve|Win32.ActiveCfg = Release - Orange Box Valve|Win32 + {B3E797CF-4E77-4C9D-B8A8-7589B6902206}.Release - Orange Box Valve|Win32.Build.0 = Release - Orange Box Valve|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + VisualSVNWorkingCopyRoot = .. + EndGlobalSection +EndGlobal diff --git a/msvc10/connect.vcxproj b/msvc10/connect.vcxproj new file mode 100644 index 0000000..7ff27e3 --- /dev/null +++ b/msvc10/connect.vcxproj @@ -0,0 +1,133 @@ + + + + + Debug - Orange Box Valve + Win32 + + + Release - Orange Box Valve + Win32 + + + + {B3E797CF-4E77-4C9D-B8A8-7589B6902206} + tf2items + Win32Proj + connect + + + + DynamicLibrary + MultiByte + true + + + DynamicLibrary + MultiByte + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + AllRules.ruleset + + + AllRules.ruleset + + + false + $(ProjectName).ext.2.ep2v + $(ProjectName).ext.2.ep2v + + + + /D SE_EPISODEONE=1 /D SE_DARKMESSIAH=2 /D SE_ORANGEBOX=3 /D SE_ORANGEBOXVALVE=4 /D SE_LEFT4DEAD=5 /D SE_LEFT4DEAD2=6 %(AdditionalOptions) + Disabled + ..;..\sdk;$(SOURCEMOD13)\public\;$(SOURCEMOD13)\public\sourcepawn\;$(HL2SDKOBVALVE)\game\shared\;$(HL2SDKOBVALVE)\game\server\;$(HL2SDKOBVALVE)\public;$(HL2SDKOBVALVE)\public\engine;$(HL2SDKOBVALVE)\public\game\server;$(HL2SDKOBVALVE)\public\tier0;$(HL2SDKOBVALVE)\public\tier1;$(MMSOURCE18)\core;$(MMSOURCE18)\core\sourcehook;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;SDK_EXPORTS;_CRT_SECURE_NO_DEPRECATE;SOURCEMOD_BUILD;SOURCE_ENGINE=4;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + NotSet + false + + + Level3 + EditAndContinue + false + + + $(HL2SDKOBVALVE)\lib\public\tier0.lib;$(HL2SDKOBVALVE)\lib\public\tier1.lib;$(HL2SDKOBVALVE)\lib\public\vstdlib.lib;%(AdditionalDependencies) + $(OutDir)$(ProjectName).ext.2.ep2v.dll + LIBC;LIBCD;LIBCMT;%(IgnoreSpecificDefaultLibraries) + true + Windows + false + + + MachineX86 + + + + + /MP /D SE_EPISODEONE=1 /D SE_DARKMESSIAH=2 /D SE_ORANGEBOX=3 /D SE_ORANGEBOXVALVE=4 /D SE_LEFT4DEAD=5 /D SE_LEFT4DEAD2=6 %(AdditionalOptions) + Speed + ..;..\sdk;$(SOURCEMOD13)\public\;$(SOURCEMOD13)\public\sourcepawn\;$(HL2SDKOBVALVE)\game\shared\;$(HL2SDKOBVALVE)\game\server\;$(HL2SDKOBVALVE)\public;$(HL2SDKOBVALVE)\public\engine;$(HL2SDKOBVALVE)\public\game\server;$(HL2SDKOBVALVE)\public\tier0;$(HL2SDKOBVALVE)\public\tier1;$(MMSOURCE18)\core;$(MMSOURCE18)\core\sourcehook;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;SDK_EXPORTS;_CRT_SECURE_NO_DEPRECATE;SOURCEMOD_BUILD;SOURCE_ENGINE=4;%(PreprocessorDefinitions) + MultiThreaded + NotSet + false + + + Level3 + true + ProgramDatabase + + + $(HL2SDKOBVALVE)\lib\public\tier0.lib;$(HL2SDKOBVALVE)\lib\public\tier1.lib;$(HL2SDKOBVALVE)\lib\public\vstdlib.lib;%(AdditionalDependencies) + LinkVerbose + $(OutDir)$(ProjectName).ext.2.ep2v.dll + LIBC;LIBCD;LIBCMTD;%(IgnoreSpecificDefaultLibraries) + true + Windows + true + true + false + + + MachineX86 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/msvc10/connect.vcxproj.filters b/msvc10/connect.vcxproj.filters new file mode 100644 index 0000000..20ebd71 --- /dev/null +++ b/msvc10/connect.vcxproj.filters @@ -0,0 +1,50 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {31958233-BB2D-4e41-A8F9-CE8A4684F436} + + + + + Source Files + + + SourceMod SDK + + + Source Files + + + Source Files + + + + + Header Files + + + SourceMod SDK + + + SourceMod SDK + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/msvc10/connect.vcxproj.user b/msvc10/connect.vcxproj.user new file mode 100644 index 0000000..695b5c7 --- /dev/null +++ b/msvc10/connect.vcxproj.user @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/sdk/smsdk_config.hpp b/sdk/smsdk_config.hpp new file mode 100644 index 0000000..07cc74b --- /dev/null +++ b/sdk/smsdk_config.hpp @@ -0,0 +1,78 @@ +/** + * ============================================================================= + * connect Extension + * Copyright (C) 2011 Asher Baker (asherkin). All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + */ + +#ifndef _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ +#define _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ + +/** + * @file smsdk_config.hpp + * @brief Contains macros for configuring basic extension information. + */ + +/* Basic information exposed publicly */ +#define SMEXT_CONF_NAME "Connect" +#define SMEXT_CONF_DESCRIPTION "" +#define SMEXT_CONF_VERSION "1.0.0" +#define SMEXT_CONF_AUTHOR "Asher \"asherkin\" Baker" +#define SMEXT_CONF_URL "http://limetech.org/" +#define SMEXT_CONF_LOGTAG "CONNECT" +#define SMEXT_CONF_LICENSE "GPL" +#define SMEXT_CONF_DATESTRING __DATE__ + +/** + * @brief Exposes plugin's main interface. + */ +#define SMEXT_LINK(name) SDKExtension *g_pExtensionIface = name; + +/** + * @brief Sets whether or not this plugin required Metamod. + * NOTE: Uncomment to enable, comment to disable. + */ +#define SMEXT_CONF_METAMOD + +/** Enable interfaces you want to use here by uncommenting lines */ +#define SMEXT_ENABLE_FORWARDSYS +//#define SMEXT_ENABLE_HANDLESYS +//#define SMEXT_ENABLE_PLAYERHELPERS +//#define SMEXT_ENABLE_DBMANAGER +#define SMEXT_ENABLE_GAMECONF +//#define SMEXT_ENABLE_MEMUTILS +//#define SMEXT_ENABLE_GAMEHELPERS +//#define SMEXT_ENABLE_TIMERSYS +//#define SMEXT_ENABLE_THREADER +//#define SMEXT_ENABLE_LIBSYS +//#define SMEXT_ENABLE_MENUS +//#define SMEXT_ENABLE_ADTFACTORY +//#define SMEXT_ENABLE_PLUGINSYS +//#define SMEXT_ENABLE_ADMINSYS +//#define SMEXT_ENABLE_TEXTPARSERS +//#define SMEXT_ENABLE_USERMSGS +//#define SMEXT_ENABLE_TRANSLATOR +//#define SMEXT_ENABLE_NINVOKE + +#endif // _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ diff --git a/sdk/smsdk_ext.cpp b/sdk/smsdk_ext.cpp new file mode 100644 index 0000000..23cccb2 --- /dev/null +++ b/sdk/smsdk_ext.cpp @@ -0,0 +1,465 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod Base Extension Code + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#include +#include +#include "smsdk_ext.hpp" + +/** + * @file smsdk_ext.cpp + * @brief Contains wrappers for making Extensions easier to write. + */ + +IExtension *myself = NULL; /**< Ourself */ +IShareSys *g_pShareSys = NULL; /**< Share system */ +IShareSys *sharesys = NULL; /**< Share system */ +ISourceMod *g_pSM = NULL; /**< SourceMod helpers */ +ISourceMod *smutils = NULL; /**< SourceMod helpers */ + +#if defined SMEXT_ENABLE_FORWARDSYS +IForwardManager *g_pForwards = NULL; /**< Forward system */ +IForwardManager *forwards = NULL; /**< Forward system */ +#endif +#if defined SMEXT_ENABLE_HANDLESYS +IHandleSys *g_pHandleSys = NULL; /**< Handle system */ +IHandleSys *handlesys = NULL; /**< Handle system */ +#endif +#if defined SMEXT_ENABLE_PLAYERHELPERS +IPlayerManager *playerhelpers = NULL; /**< Player helpers */ +#endif //SMEXT_ENABLE_PLAYERHELPERS +#if defined SMEXT_ENABLE_DBMANAGER +IDBManager *dbi = NULL; /**< DB Manager */ +#endif //SMEXT_ENABLE_DBMANAGER +#if defined SMEXT_ENABLE_GAMECONF +IGameConfigManager *gameconfs = NULL; /**< Game config manager */ +#endif //SMEXT_ENABLE_DBMANAGER +#if defined SMEXT_ENABLE_MEMUTILS +IMemoryUtils *memutils = NULL; +#endif //SMEXT_ENABLE_DBMANAGER +#if defined SMEXT_ENABLE_GAMEHELPERS +IGameHelpers *gamehelpers = NULL; +#endif +#if defined SMEXT_ENABLE_TIMERSYS +ITimerSystem *timersys = NULL; +#endif +#if defined SMEXT_ENABLE_ADTFACTORY +IADTFactory *adtfactory = NULL; +#endif +#if defined SMEXT_ENABLE_THREADER +IThreader *threader = NULL; +#endif +#if defined SMEXT_ENABLE_LIBSYS +ILibrarySys *libsys = NULL; +#endif +#if defined SMEXT_ENABLE_PLUGINSYS +SourceMod::IPluginManager *plsys; +#endif +#if defined SMEXT_ENABLE_MENUS +IMenuManager *menus = NULL; +#endif +#if defined SMEXT_ENABLE_ADMINSYS +IAdminSystem *adminsys = NULL; +#endif +#if defined SMEXT_ENABLE_TEXTPARSERS +ITextParsers *textparsers = NULL; +#endif +#if defined SMEXT_ENABLE_USERMSGS +IUserMessages *usermsgs = NULL; +#endif +#if defined SMEXT_ENABLE_TRANSLATOR +ITranslator *translator = NULL; +#endif +#if defined SMEXT_ENABLE_NINVOKE +INativeInterface *ninvoke = NULL; +#endif + +/** Exports the main interface */ +PLATFORM_EXTERN_C IExtensionInterface *GetSMExtAPI() +{ + return g_pExtensionIface; +} + +SDKExtension::SDKExtension() +{ +#if defined SMEXT_CONF_METAMOD + m_SourceMMLoaded = false; + m_WeAreUnloaded = false; + m_WeGotPauseChange = false; +#endif +} + +bool SDKExtension::OnExtensionLoad(IExtension *me, IShareSys *sys, char *error, size_t maxlength, bool late) +{ + g_pShareSys = sharesys = sys; + myself = me; + +#if defined SMEXT_CONF_METAMOD + m_WeAreUnloaded = true; + + if (!m_SourceMMLoaded) + { + if (error) + { + snprintf(error, maxlength, "Metamod attach failed"); + } + return false; + } +#endif + SM_GET_IFACE(SOURCEMOD, g_pSM); + smutils = g_pSM; +#if defined SMEXT_ENABLE_HANDLESYS + SM_GET_IFACE(HANDLESYSTEM, g_pHandleSys); + handlesys = g_pHandleSys; +#endif +#if defined SMEXT_ENABLE_FORWARDSYS + SM_GET_IFACE(FORWARDMANAGER, g_pForwards); + forwards = g_pForwards; +#endif +#if defined SMEXT_ENABLE_PLAYERHELPERS + SM_GET_IFACE(PLAYERMANAGER, playerhelpers); +#endif +#if defined SMEXT_ENABLE_DBMANAGER + SM_GET_IFACE(DBI, dbi); +#endif +#if defined SMEXT_ENABLE_GAMECONF + SM_GET_IFACE(GAMECONFIG, gameconfs); +#endif +#if defined SMEXT_ENABLE_MEMUTILS + SM_GET_IFACE(MEMORYUTILS, memutils); +#endif +#if defined SMEXT_ENABLE_GAMEHELPERS + SM_GET_IFACE(GAMEHELPERS, gamehelpers); +#endif +#if defined SMEXT_ENABLE_TIMERSYS + SM_GET_IFACE(TIMERSYS, timersys); +#endif +#if defined SMEXT_ENABLE_ADTFACTORY + SM_GET_IFACE(ADTFACTORY, adtfactory); +#endif +#if defined SMEXT_ENABLE_THREADER + SM_GET_IFACE(THREADER, threader); +#endif +#if defined SMEXT_ENABLE_LIBSYS + SM_GET_IFACE(LIBRARYSYS, libsys); +#endif +#if defined SMEXT_ENABLE_PLUGINSYS + SM_GET_IFACE(PLUGINSYSTEM, plsys); +#endif +#if defined SMEXT_ENABLE_MENUS + SM_GET_IFACE(MENUMANAGER, menus); +#endif +#if defined SMEXT_ENABLE_ADMINSYS + SM_GET_IFACE(ADMINSYS, adminsys); +#endif +#if defined SMEXT_ENABLE_TEXTPARSERS + SM_GET_IFACE(TEXTPARSERS, textparsers); +#endif +#if defined SMEXT_ENABLE_USERMSGS + SM_GET_IFACE(USERMSGS, usermsgs); +#endif +#if defined SMEXT_ENABLE_TRANSLATOR + SM_GET_IFACE(TRANSLATOR, translator); +#endif + + if (SDK_OnLoad(error, maxlength, late)) + { +#if defined SMEXT_CONF_METAMOD + m_WeAreUnloaded = true; +#endif + return true; + } + + return false; +} + +bool SDKExtension::IsMetamodExtension() +{ +#if defined SMEXT_CONF_METAMOD + return true; +#else + return false; +#endif +} + +void SDKExtension::OnExtensionPauseChange(bool state) +{ +#if defined SMEXT_CONF_METAMOD + m_WeGotPauseChange = true; +#endif + SDK_OnPauseChange(state); +} + +void SDKExtension::OnExtensionsAllLoaded() +{ + SDK_OnAllLoaded(); +} + +void SDKExtension::OnExtensionUnload() +{ +#if defined SMEXT_CONF_METAMOD + m_WeAreUnloaded = true; +#endif + SDK_OnUnload(); +} + +const char *SDKExtension::GetExtensionAuthor() +{ + return SMEXT_CONF_AUTHOR; +} + +const char *SDKExtension::GetExtensionDateString() +{ + return SMEXT_CONF_DATESTRING; +} + +const char *SDKExtension::GetExtensionDescription() +{ + return SMEXT_CONF_DESCRIPTION; +} + +const char *SDKExtension::GetExtensionVerString() +{ + return SMEXT_CONF_VERSION; +} + +const char *SDKExtension::GetExtensionName() +{ + return SMEXT_CONF_NAME; +} + +const char *SDKExtension::GetExtensionTag() +{ + return SMEXT_CONF_LOGTAG; +} + +const char *SDKExtension::GetExtensionURL() +{ + return SMEXT_CONF_URL; +} + +bool SDKExtension::SDK_OnLoad(char *error, size_t maxlength, bool late) +{ + return true; +} + +void SDKExtension::SDK_OnUnload() +{ +} + +void SDKExtension::SDK_OnPauseChange(bool paused) +{ +} + +void SDKExtension::SDK_OnAllLoaded() +{ +} + +#if defined SMEXT_CONF_METAMOD + +PluginId g_PLID = 0; /**< Metamod plugin ID */ +ISmmPlugin *g_PLAPI = NULL; /**< Metamod plugin API */ +SourceHook::ISourceHook *g_SHPtr = NULL; /**< SourceHook pointer */ +ISmmAPI *g_SMAPI = NULL; /**< SourceMM API pointer */ + +IVEngineServer *engine = NULL; /**< IVEngineServer pointer */ +IServerGameDLL *gamedll = NULL; /**< IServerGameDLL pointer */ + +/** Exposes the extension to Metamod */ +SMM_API void *PL_EXPOSURE(const char *name, int *code) +{ +#if defined METAMOD_PLAPI_VERSION + if (name && !strcmp(name, METAMOD_PLAPI_NAME)) +#else + if (name && !strcmp(name, PLAPI_NAME)) +#endif + { + if (code) + { + *code = IFACE_OK; + } + return static_cast(g_pExtensionIface); + } + + if (code) + { + *code = IFACE_FAILED; + } + + return NULL; +} + +bool SDKExtension::Load(PluginId id, ISmmAPI *ismm, char *error, size_t maxlen, bool late) +{ + PLUGIN_SAVEVARS(); + +#if !defined METAMOD_PLAPI_VERSION + GET_V_IFACE_ANY(serverFactory, gamedll, IServerGameDLL, INTERFACEVERSION_SERVERGAMEDLL); + GET_V_IFACE_CURRENT(engineFactory, engine, IVEngineServer, INTERFACEVERSION_VENGINESERVER); +#else + GET_V_IFACE_ANY(GetServerFactory, gamedll, IServerGameDLL, INTERFACEVERSION_SERVERGAMEDLL); + GET_V_IFACE_CURRENT(GetEngineFactory, engine, IVEngineServer, INTERFACEVERSION_VENGINESERVER); +#endif + + m_SourceMMLoaded = true; + + return SDK_OnMetamodLoad(ismm, error, maxlen, late); +} + +bool SDKExtension::Unload(char *error, size_t maxlen) +{ + if (!m_WeAreUnloaded) + { + if (error) + { + snprintf(error, maxlen, "This extension must be unloaded by SourceMod."); + } + return false; + } + + return SDK_OnMetamodUnload(error, maxlen); +} + +bool SDKExtension::Pause(char *error, size_t maxlen) +{ + if (!m_WeGotPauseChange) + { + if (error) + { + snprintf(error, maxlen, "This extension must be paused by SourceMod."); + } + return false; + } + + m_WeGotPauseChange = false; + + return SDK_OnMetamodPauseChange(true, error, maxlen); +} + +bool SDKExtension::Unpause(char *error, size_t maxlen) +{ + if (!m_WeGotPauseChange) + { + if (error) + { + snprintf(error, maxlen, "This extension must be unpaused by SourceMod."); + } + return false; + } + + m_WeGotPauseChange = false; + + return SDK_OnMetamodPauseChange(false, error, maxlen); +} + +const char *SDKExtension::GetAuthor() +{ + return GetExtensionAuthor(); +} + +const char *SDKExtension::GetDate() +{ + return GetExtensionDateString(); +} + +const char *SDKExtension::GetDescription() +{ + return GetExtensionDescription(); +} + +const char *SDKExtension::GetLicense() +{ + return SMEXT_CONF_LICENSE; +} + +const char *SDKExtension::GetLogTag() +{ + return GetExtensionTag(); +} + +const char *SDKExtension::GetName() +{ + return GetExtensionName(); +} + +const char *SDKExtension::GetURL() +{ + return GetExtensionURL(); +} + +const char *SDKExtension::GetVersion() +{ + return GetExtensionVerString(); +} + +bool SDKExtension::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlength, bool late) +{ + return true; +} + +bool SDKExtension::SDK_OnMetamodUnload(char *error, size_t maxlength) +{ + return true; +} + +bool SDKExtension::SDK_OnMetamodPauseChange(bool paused, char *error, size_t maxlength) +{ + return true; +} + +#endif + +/* Overload a few things to prevent libstdc++ linking */ +#if defined __linux__ || defined __APPLE__ +extern "C" void __cxa_pure_virtual(void) +{ +} + +void *operator new(size_t size) +{ + return malloc(size); +} + +void *operator new[](size_t size) +{ + return malloc(size); +} + +void operator delete(void *ptr) +{ + free(ptr); +} + +void operator delete[](void * ptr) +{ + free(ptr); +} +#endif + diff --git a/sdk/smsdk_ext.hpp b/sdk/smsdk_ext.hpp new file mode 100644 index 0000000..a3de9b3 --- /dev/null +++ b/sdk/smsdk_ext.hpp @@ -0,0 +1,339 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod Base Extension Code + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_SOURCEMOD_EXTENSION_BASESDK_H_ +#define _INCLUDE_SOURCEMOD_EXTENSION_BASESDK_H_ + +/** + * @file smsdk_ext.hpp + * @brief Contains wrappers for making Extensions easier to write. + */ + +#include "smsdk_config.hpp" +#include +#include +#include +#include +#include +#if defined SMEXT_ENABLE_FORWARDSYS +#include +#endif //SMEXT_ENABLE_FORWARDSYS +#if defined SMEXT_ENABLE_PLAYERHELPERS +#include +#endif //SMEXT_ENABLE_PlAYERHELPERS +#if defined SMEXT_ENABLE_DBMANAGER +#include +#endif //SMEXT_ENABLE_DBMANAGER +#if defined SMEXT_ENABLE_GAMECONF +#include +#endif +#if defined SMEXT_ENABLE_MEMUTILS +#include +#endif +#if defined SMEXT_ENABLE_GAMEHELPERS +#include +#endif +#if defined SMEXT_ENABLE_TIMERSYS +#include +#endif +#if defined SMEXT_ENABLE_ADTFACTORY +#include +#endif +#if defined SMEXT_ENABLE_THREADER +#include +#endif +#if defined SMEXT_ENABLE_LIBSYS +#include +#endif +#if defined SMEXT_ENABLE_PLUGINSYS +#include +#endif +#if defined SMEXT_ENABLE_MENUS +#include +#endif +#if defined SMEXT_ENABLE_ADMINSYS +#include +#endif +#if defined SMEXT_ENABLE_TEXTPARSERS +#include +#endif +#if defined SMEXT_ENABLE_USERMSGS +#include +#endif +#if defined SMEXT_ENABLE_TRANSLATOR +#include +#endif +#if defined SMEXT_ENABLE_NINVOKE +#include +#endif + +#if defined SMEXT_CONF_METAMOD +#include +#include +#endif + +using namespace SourceMod; +using namespace SourcePawn; + +class SDKExtension : +#if defined SMEXT_CONF_METAMOD + public ISmmPlugin, +#endif + public IExtensionInterface +{ +public: + /** Constructor */ + SDKExtension(); +public: + /** + * @brief This is called after the initial loading sequence has been processed. + * + * @param error Error message buffer. + * @param maxlength Size of error message buffer. + * @param late Whether or not the module was loaded after map load. + * @return True to succeed loading, false to fail. + */ + virtual bool SDK_OnLoad(char *error, size_t maxlength, bool late); + + /** + * @brief This is called right before the extension is unloaded. + */ + virtual void SDK_OnUnload(); + + /** + * @brief This is called once all known extensions have been loaded. + */ + virtual void SDK_OnAllLoaded(); + + /** + * @brief Called when the pause state is changed. + */ + virtual void SDK_OnPauseChange(bool paused); + +#if defined SMEXT_CONF_METAMOD + /** + * @brief Called when Metamod is attached, before the extension version is called. + * + * @param error Error buffer. + * @param maxlength Maximum size of error buffer. + * @param late Whether or not Metamod considers this a late load. + * @return True to succeed, false to fail. + */ + virtual bool SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlength, bool late); + + /** + * @brief Called when Metamod is detaching, after the extension version is called. + * NOTE: By default this is blocked unless sent from SourceMod. + * + * @param error Error buffer. + * @param maxlength Maximum size of error buffer. + * @return True to succeed, false to fail. + */ + virtual bool SDK_OnMetamodUnload(char *error, size_t maxlength); + + /** + * @brief Called when Metamod's pause state is changing. + * NOTE: By default this is blocked unless sent from SourceMod. + * + * @param paused Pause state being set. + * @param error Error buffer. + * @param maxlength Maximum size of error buffer. + * @return True to succeed, false to fail. + */ + virtual bool SDK_OnMetamodPauseChange(bool paused, char *error, size_t maxlength); +#endif + +public: //IExtensionInterface + virtual bool OnExtensionLoad(IExtension *me, IShareSys *sys, char *error, size_t maxlength, bool late); + virtual void OnExtensionUnload(); + virtual void OnExtensionsAllLoaded(); + + /** Returns whether or not this is a Metamod-based extension */ + virtual bool IsMetamodExtension(); + + /** + * @brief Called when the pause state changes. + * + * @param state True if being paused, false if being unpaused. + */ + virtual void OnExtensionPauseChange(bool state); + + /** Returns name */ + virtual const char *GetExtensionName(); + /** Returns URL */ + virtual const char *GetExtensionURL(); + /** Returns log tag */ + virtual const char *GetExtensionTag(); + /** Returns author */ + virtual const char *GetExtensionAuthor(); + /** Returns version string */ + virtual const char *GetExtensionVerString(); + /** Returns description string */ + virtual const char *GetExtensionDescription(); + /** Returns date string */ + virtual const char *GetExtensionDateString(); +#if defined SMEXT_CONF_METAMOD +public: //ISmmPlugin + /** Called when the extension is attached to Metamod. */ + virtual bool Load(PluginId id, ISmmAPI *ismm, char *error, size_t maxlength, bool late); + /** Returns the author to MM */ + virtual const char *GetAuthor(); + /** Returns the name to MM */ + virtual const char *GetName(); + /** Returns the description to MM */ + virtual const char *GetDescription(); + /** Returns the URL to MM */ + virtual const char *GetURL(); + /** Returns the license to MM */ + virtual const char *GetLicense(); + /** Returns the version string to MM */ + virtual const char *GetVersion(); + /** Returns the date string to MM */ + virtual const char *GetDate(); + /** Returns the logtag to MM */ + virtual const char *GetLogTag(); + /** Called on unload */ + virtual bool Unload(char *error, size_t maxlength); + /** Called on pause */ + virtual bool Pause(char *error, size_t maxlength); + /** Called on unpause */ + virtual bool Unpause(char *error, size_t maxlength); +private: + bool m_SourceMMLoaded; + bool m_WeAreUnloaded; + bool m_WeGotPauseChange; +#endif +}; + +extern SDKExtension *g_pExtensionIface; +extern IExtension *myself; + +extern IShareSys *g_pShareSys; +extern IShareSys *sharesys; /* Note: Newer name */ +extern ISourceMod *g_pSM; +extern ISourceMod *smutils; /* Note: Newer name */ + +/* Optional interfaces are below */ +#if defined SMEXT_ENABLE_FORWARDSYS +extern IForwardManager *g_pForwards; +extern IForwardManager *forwards; /* Note: Newer name */ +#endif //SMEXT_ENABLE_FORWARDSYS +#if defined SMEXT_ENABLE_HANDLESYS +extern IHandleSys *g_pHandleSys; +extern IHandleSys *handlesys; /* Note: Newer name */ +#endif //SMEXT_ENABLE_HANDLESYS +#if defined SMEXT_ENABLE_PLAYERHELPERS +extern IPlayerManager *playerhelpers; +#endif //SMEXT_ENABLE_PLAYERHELPERS +#if defined SMEXT_ENABLE_DBMANAGER +extern IDBManager *dbi; +#endif //SMEXT_ENABLE_DBMANAGER +#if defined SMEXT_ENABLE_GAMECONF +extern IGameConfigManager *gameconfs; +#endif //SMEXT_ENABLE_DBMANAGER +#if defined SMEXT_ENABLE_MEMUTILS +extern IMemoryUtils *memutils; +#endif +#if defined SMEXT_ENABLE_GAMEHELPERS +extern IGameHelpers *gamehelpers; +#endif +#if defined SMEXT_ENABLE_TIMERSYS +extern ITimerSystem *timersys; +#endif +#if defined SMEXT_ENABLE_ADTFACTORY +extern IADTFactory *adtfactory; +#endif +#if defined SMEXT_ENABLE_THREADER +extern IThreader *threader; +#endif +#if defined SMEXT_ENABLE_LIBSYS +extern ILibrarySys *libsys; +#endif +#if defined SMEXT_ENABLE_PLUGINSYS +extern SourceMod::IPluginManager *plsys; +#endif +#if defined SMEXT_ENABLE_MENUS +extern IMenuManager *menus; +#endif +#if defined SMEXT_ENABLE_ADMINSYS +extern IAdminSystem *adminsys; +#endif +#if defined SMEXT_ENABLE_USERMSGS +extern IUserMessages *usermsgs; +#endif +#if defined SMEXT_ENABLE_TRANSLATOR +extern ITranslator *translator; +#endif +#if defined SMEXT_ENABLE_NINVOKE +extern INativeInterface *ninvoke; +#endif + +#if defined SMEXT_CONF_METAMOD +PLUGIN_GLOBALVARS(); +extern IVEngineServer *engine; +extern IServerGameDLL *gamedll; +#endif + +/** Creates a SourceMod interface macro pair */ +#define SM_MKIFACE(name) SMINTERFACE_##name##_NAME, SMINTERFACE_##name##_VERSION +/** Automates retrieving SourceMod interfaces */ +#define SM_GET_IFACE(prefix, addr) \ + if (!g_pShareSys->RequestInterface(SM_MKIFACE(prefix), myself, (SMInterface **)&addr)) \ + { \ + if (error != NULL && maxlength) \ + { \ + size_t len = snprintf(error, maxlength, "Could not find interface: %s", SMINTERFACE_##prefix##_NAME); \ + if (len >= maxlength) \ + { \ + error[maxlength - 1] = '\0'; \ + } \ + } \ + return false; \ + } +/** Automates retrieving SourceMod interfaces when needed outside of SDK_OnLoad() */ +#define SM_GET_LATE_IFACE(prefix, addr) \ + g_pShareSys->RequestInterface(SM_MKIFACE(prefix), myself, (SMInterface **)&addr) +/** Validates a SourceMod interface pointer */ +#define SM_CHECK_IFACE(prefix, addr) \ + if (!addr) \ + { \ + if (error != NULL && maxlength) \ + { \ + size_t len = snprintf(error, maxlength, "Could not find interface: %s", SMINTERFACE_##prefix##_NAME); \ + if (len >= maxlength) \ + { \ + error[maxlength - 1] = '\0'; \ + } \ + } \ + return false; \ + } + +#endif // _INCLUDE_SOURCEMOD_EXTENSION_BASESDK_H_