From cd542100ede578de82ea05f74d477ab840bfbebe Mon Sep 17 00:00:00 2001 From: BotoX Date: Wed, 27 Jan 2021 15:07:12 +0100 Subject: [PATCH] initial commit --- AMBuildScript | 430 ++++++++++++++++++++ AMBuilder | 29 ++ Makefile | 233 +++++++++++ PackageScript | 36 ++ configure.py | 23 ++ extension.cpp | 756 +++++++++++++++++++++++++++++++++++ extension.h | 147 +++++++ gamedata/a2sqcache.games.txt | 171 ++++++++ smsdk_config.h | 81 ++++ 9 files changed, 1906 insertions(+) create mode 100644 AMBuildScript create mode 100644 AMBuilder create mode 100644 Makefile create mode 100644 PackageScript create mode 100755 configure.py create mode 100644 extension.cpp create mode 100644 extension.h create mode 100644 gamedata/a2sqcache.games.txt create mode 100644 smsdk_config.h diff --git a/AMBuildScript b/AMBuildScript new file mode 100644 index 0000000..5932d89 --- /dev/null +++ b/AMBuildScript @@ -0,0 +1,430 @@ +# vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: +import os, sys + +# Simple extensions do not need to modify this file. + +class SDK(object): + def __init__(self, sdk, ext, aDef, name, platform, dir): + self.folder = 'hl2sdk-' + dir + self.envvar = sdk + self.ext = ext + self.code = aDef + self.define = name + self.platform = platform + self.name = dir + self.path = None # Actual path + +WinOnly = ['windows'] +WinLinux = ['windows', 'linux'] +WinLinuxMac = ['windows', 'linux', 'mac'] + +PossibleSDKs = { + 'sdk2013': SDK('HL2SDK2013', '2.sdk2013', '9', 'SDK2013', WinLinuxMac, 'sdk2013'), + 'csgo': SDK('HL2SDKCSGO', '2.csgo', '21', 'CSGO', WinLinuxMac, 'csgo'), +} + +def ResolveEnvPath(env, folder): + if env in os.environ: + path = os.environ[env] + if os.path.isdir(path): + return path + return None + + head = os.getcwd() + oldhead = None + while head != None and head != oldhead: + path = os.path.join(head, folder) + if os.path.isdir(path): + return path + oldhead = head + head, tail = os.path.split(head) + + return None + +def Normalize(path): + return os.path.abspath(os.path.normpath(path)) + +class ExtensionConfig(object): + def __init__(self): + self.sdks = {} + self.binaries = [] + self.extensions = [] + self.generated_headers = None + self.mms_root = None + self.sm_root = None + + @property + def tag(self): + if builder.options.debug == '1': + return 'Debug' + return 'Release' + + def detectSDKs(self): + sdk_list = builder.options.sdks.split(',') + use_all = sdk_list[0] == 'all' + use_present = sdk_list[0] == 'present' + + for sdk_name in PossibleSDKs: + sdk = PossibleSDKs[sdk_name] + if builder.target_platform in sdk.platform: + if builder.options.hl2sdk_root: + sdk_path = os.path.join(builder.options.hl2sdk_root, sdk.folder) + else: + sdk_path = ResolveEnvPath(sdk.envvar, sdk.folder) + if sdk_path is None or not os.path.isdir(sdk_path): + if use_all or sdk_name in sdk_list: + raise Exception('Could not find a valid path for {0}'.format(sdk.envvar)) + continue + if use_all or use_present or sdk_name in sdk_list: + sdk.path = Normalize(sdk_path) + self.sdks[sdk_name] = sdk + + if len(self.sdks) < 1: + raise Exception('At least one SDK must be available.') + + if builder.options.sm_path: + self.sm_root = builder.options.sm_path + else: + self.sm_root = ResolveEnvPath('SOURCEMOD18', 'sourcemod-1.8') + if not self.sm_root: + self.sm_root = ResolveEnvPath('SOURCEMOD', 'sourcemod') + if not self.sm_root: + self.sm_root = ResolveEnvPath('SOURCEMOD_DEV', 'sourcemod-central') + + if not self.sm_root or not os.path.isdir(self.sm_root): + raise Exception('Could not find a source copy of SourceMod') + self.sm_root = Normalize(self.sm_root) + + if builder.options.mms_path: + self.mms_root = builder.options.mms_path + else: + self.mms_root = ResolveEnvPath('MMSOURCE110', 'mmsource-1.10') + if not self.mms_root: + self.mms_root = ResolveEnvPath('MMSOURCE', 'metamod-source') + if not self.mms_root: + self.mms_root = ResolveEnvPath('MMSOURCE_DEV', 'mmsource-central') + + if not self.mms_root or not os.path.isdir(self.mms_root): + raise Exception('Could not find a source copy of Metamod:Source') + self.mms_root = Normalize(self.mms_root) + + def configure(self): + cxx = builder.DetectCompilers() + + if cxx.like('gcc'): + self.configure_gcc(cxx) + elif cxx.vendor == 'msvc': + self.configure_msvc(cxx) + + # Optimizaiton + if builder.options.opt == '1': + cxx.defines += ['NDEBUG'] + + # Debugging + if builder.options.debug == '1': + cxx.defines += ['DEBUG', '_DEBUG'] + + # Platform-specifics + if builder.target_platform == 'linux': + self.configure_linux(cxx) + elif builder.target_platform == 'mac': + self.configure_mac(cxx) + elif builder.target_platform == 'windows': + self.configure_windows(cxx) + + # Finish up. + cxx.includes += [ + os.path.join(self.sm_root, 'public'), + ] + + def configure_gcc(self, cxx): + cxx.defines += [ + 'stricmp=strcasecmp', + '_stricmp=strcasecmp', + '_snprintf=snprintf', + '_vsnprintf=vsnprintf', + 'HAVE_STDINT_H', + 'GNUC', + ] + cxx.cflags += [ + '-pipe', + '-fno-strict-aliasing', +# '-Wall', +# '-Werror', + '-Wno-unused', + '-Wno-switch', + '-Wno-array-bounds', + '-msse', + '-m32', + '-fvisibility=hidden', + ] + cxx.cxxflags += [ + '-std=c++14', + '-fno-exceptions', + '-fno-threadsafe-statics', + '-Wno-non-virtual-dtor', + '-Wno-overloaded-virtual', + '-fvisibility-inlines-hidden', + ] + cxx.linkflags += ['-m32'] + + have_gcc = cxx.vendor == 'gcc' + have_clang = cxx.vendor == 'clang' + if cxx.version >= 'clang-3.6': + cxx.cxxflags += ['-Wno-inconsistent-missing-override'] + if have_clang or (cxx.version >= 'gcc-4.6'): + cxx.cflags += ['-Wno-narrowing'] + if have_clang or (cxx.version >= 'gcc-4.7'): + cxx.cxxflags += ['-Wno-delete-non-virtual-dtor'] + if cxx.version >= 'gcc-4.8': + cxx.cflags += ['-Wno-unused-result'] + + if have_clang: + cxx.cxxflags += ['-Wno-implicit-exception-spec-mismatch'] + if cxx.version >= 'apple-clang-5.1' or cxx.version >= 'clang-3.4': + cxx.cxxflags += ['-Wno-deprecated-register'] + else: + cxx.cxxflags += ['-Wno-deprecated'] + cxx.cflags += ['-Wno-sometimes-uninitialized'] + + if have_gcc: + cxx.cflags += ['-mfpmath=sse'] + + if builder.options.opt == '1': + cxx.cflags += ['-O3'] + + def configure_msvc(self, cxx): + if builder.options.debug == '1': + cxx.cflags += ['/MTd'] + cxx.linkflags += ['/NODEFAULTLIB:libcmt'] + else: + cxx.cflags += ['/MT'] + cxx.defines += [ + '_CRT_SECURE_NO_DEPRECATE', + '_CRT_SECURE_NO_WARNINGS', + '_CRT_NONSTDC_NO_DEPRECATE', + '_ITERATOR_DEBUG_LEVEL=0', + ] + cxx.cflags += [ + '/W3', + ] + cxx.cxxflags += [ + '/EHsc', + '/GR-', + '/TP', + ] + cxx.linkflags += [ + '/MACHINE:X86', + 'kernel32.lib', + 'user32.lib', + 'gdi32.lib', + 'winspool.lib', + 'comdlg32.lib', + 'advapi32.lib', + 'shell32.lib', + 'ole32.lib', + 'oleaut32.lib', + 'uuid.lib', + 'odbc32.lib', + 'odbccp32.lib', + ] + + if builder.options.opt == '1': + cxx.cflags += ['/Ox', '/Zo'] + cxx.linkflags += ['/OPT:ICF', '/OPT:REF'] + + if builder.options.debug == '1': + cxx.cflags += ['/Od', '/RTC1'] + + # This needs to be after our optimization flags which could otherwise disable it. + # Don't omit the frame pointer. + cxx.cflags += ['/Oy-'] + + def configure_linux(self, cxx): + cxx.defines += ['_LINUX', 'POSIX'] + cxx.linkflags += ['-Wl,--exclude-libs,ALL', '-lm'] + if cxx.vendor == 'gcc': + cxx.linkflags += ['-static-libgcc'] + elif cxx.vendor == 'clang': + cxx.linkflags += ['-lgcc_eh'] + cxx.linkflags += ['-static-libstdc++'] + + def configure_mac(self, cxx): + cxx.defines += ['OSX', '_OSX', 'POSIX'] + cxx.cflags += ['-mmacosx-version-min=10.5'] + cxx.linkflags += [ + '-mmacosx-version-min=10.5', + '-arch', 'i386', + '-lstdc++', + '-stdlib=libstdc++', + ] + cxx.cxxflags += ['-stdlib=libstdc++'] + + def configure_windows(self, cxx): + cxx.defines += ['WIN32', '_WINDOWS'] + + def ConfigureForExtension(self, context, compiler): + compiler.cxxincludes += [ + os.path.join(context.currentSourcePath), + os.path.join(context.currentSourcePath, 'sdk'), + os.path.join(self.sm_root, 'public'), + os.path.join(self.sm_root, 'public', 'extensions'), + os.path.join(self.sm_root, 'sourcepawn', 'include'), + os.path.join(self.sm_root, 'public', 'amtl', 'amtl'), + os.path.join(self.sm_root, 'public', 'amtl'), + ] + return compiler + + def ConfigureForHL2(self, binary, sdk): + compiler = binary.compiler + + if sdk.name == 'episode1': + mms_path = os.path.join(self.mms_root, 'core-legacy') + else: + mms_path = os.path.join(self.mms_root, 'core') + + compiler.cxxincludes += [ + os.path.join(mms_path), + os.path.join(mms_path, 'sourcehook'), + ] + + defines = ['SE_' + PossibleSDKs[i].define + '=' + PossibleSDKs[i].code for i in PossibleSDKs] + compiler.defines += defines + + paths = [ + ['public'], + ['public', 'engine'], + ['public', 'mathlib'], + ['public', 'vstdlib'], + ['public', 'tier0'], + ['public', 'tier1'] + ] + if sdk.name == 'episode1' or sdk.name == 'darkm': + paths.append(['public', 'dlls']) + paths.append(['game_shared']) + else: + paths.append(['public', 'game', 'server']) + paths.append(['public', 'toolframework']) + paths.append(['game', 'shared']) + paths.append(['common']) + + compiler.defines += ['SOURCE_ENGINE=' + sdk.code] + + if sdk.name in ['sdk2013', 'bms'] and compiler.like('gcc'): + # The 2013 SDK already has these in public/tier0/basetypes.h + compiler.defines.remove('stricmp=strcasecmp') + compiler.defines.remove('_stricmp=strcasecmp') + compiler.defines.remove('_snprintf=snprintf') + compiler.defines.remove('_vsnprintf=vsnprintf') + + if compiler.like('msvc'): + compiler.defines += ['COMPILER_MSVC', 'COMPILER_MSVC32'] + else: + compiler.defines += ['COMPILER_GCC'] + + # For everything after Swarm, this needs to be defined for entity networking + # to work properly with sendprop value changes. + if sdk.name in ['blade', 'insurgency', 'doi', 'csgo']: + compiler.defines += ['NETWORK_VARS_ENABLED'] + + if sdk.name in ['css', 'hl2dm', 'dods', 'sdk2013', 'bms', 'tf2', 'l4d', 'nucleardawn', 'l4d2']: + if builder.target_platform in ['linux', 'mac']: + compiler.defines += ['NO_HOOK_MALLOC', 'NO_MALLOC_OVERRIDE'] + + if sdk.name == 'csgo' and builder.target_platform == 'linux': + compiler.linkflags += ['-lstdc++'] + + for path in paths: + compiler.cxxincludes += [os.path.join(sdk.path, *path)] + + if builder.target_platform == 'linux': + if sdk.name == 'episode1': + lib_folder = os.path.join(sdk.path, 'linux_sdk') + elif sdk.name in ['sdk2013', 'bms']: + lib_folder = os.path.join(sdk.path, 'lib', 'public', 'linux32') + else: + lib_folder = os.path.join(sdk.path, 'lib', 'linux') + elif builder.target_platform == 'mac': + if sdk.name in ['sdk2013', 'bms']: + lib_folder = os.path.join(sdk.path, 'lib', 'public', 'osx32') + else: + lib_folder = os.path.join(sdk.path, 'lib', 'mac') + + if builder.target_platform in ['linux', 'mac']: + if sdk.name in ['sdk2013', 'bms']: + compiler.postlink += [ + compiler.Dep(os.path.join(lib_folder, 'tier1.a')), + compiler.Dep(os.path.join(lib_folder, 'mathlib.a')) + ] + else: + compiler.postlink += [ + compiler.Dep(os.path.join(lib_folder, 'tier1_i486.a')), + compiler.Dep(os.path.join(lib_folder, 'mathlib_i486.a')) + ] + + if sdk.name in ['blade', 'insurgency', 'doi', 'csgo']: + compiler.postlink += [compiler.Dep(os.path.join(lib_folder, 'interfaces_i486.a'))] + + dynamic_libs = [] + if builder.target_platform == 'linux': + if sdk.name in ['css', 'hl2dm', 'dods', 'tf2', 'sdk2013', 'bms', 'nucleardawn', 'l4d2', 'insurgency', 'doi']: + dynamic_libs = ['libtier0_srv.so', 'libvstdlib_srv.so'] + elif sdk.name in ['l4d', 'blade', 'insurgency', 'doi', 'csgo']: + dynamic_libs = ['libtier0.so', 'libvstdlib.so'] + else: + dynamic_libs = ['tier0_i486.so', 'vstdlib_i486.so'] + elif builder.target_platform == 'mac': + compiler.linkflags.append('-liconv') + dynamic_libs = ['libtier0.dylib', 'libvstdlib.dylib'] + elif builder.target_platform == 'windows': + libs = ['tier0', 'tier1', 'vstdlib', 'mathlib'] + if sdk.name in ['swarm', 'blade', 'insurgency', 'doi', 'csgo']: + libs.append('interfaces') + for lib in libs: + lib_path = os.path.join(sdk.path, 'lib', 'public', lib) + '.lib' + compiler.linkflags.append(compiler.Dep(lib_path)) + + for library in dynamic_libs: + source_path = os.path.join(lib_folder, library) + output_path = os.path.join(binary.localFolder, library) + + def make_linker(source_path, output_path): + def link(context, binary): + cmd_node, (output,) = context.AddSymlink(source_path, output_path) + return output + return link + + linker = make_linker(source_path, output_path) + compiler.linkflags[0:0] = [compiler.Dep(library, linker)] + + return binary + + def HL2Library(self, context, name, sdk): + binary = context.compiler.Library(name) + self.ConfigureForExtension(context, binary.compiler) + return self.ConfigureForHL2(binary, sdk) + + def HL2Project(self, context, name): + project = context.compiler.LibraryProject(name) + self.ConfigureForExtension(context, project.compiler) + return project + + def HL2Config(self, project, name, sdk): + binary = project.Configure(name, '{0} - {1}'.format(self.tag, sdk.name)) + return self.ConfigureForHL2(binary, sdk) + +Extension = ExtensionConfig() +Extension.detectSDKs() +Extension.configure() + +# Add additional buildscripts here +BuildScripts = [ + 'AMBuilder', +] + +if builder.backend == 'amb2': + BuildScripts += [ + 'PackageScript', + ] + +builder.RunBuildScripts(BuildScripts, { 'Extension': Extension}) diff --git a/AMBuilder b/AMBuilder new file mode 100644 index 0000000..556ddb0 --- /dev/null +++ b/AMBuilder @@ -0,0 +1,29 @@ +# vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: +import os + +if not "SM" in globals(): + SM = Extension + +projectName = 'a2sqcache' + +project = SM.HL2Project(builder, projectName + '.ext') +project.sources += [ + 'extension.cpp', + '../../public/smsdk_ext.cpp', + '../../public/CDetour/detours.cpp', + '../../public/asm/asm.c', + '../../public/libudis86/decode.c', + '../../public/libudis86/itab.c', + '../../public/libudis86/syn-att.c', + '../../public/libudis86/syn-intel.c', + '../../public/libudis86/syn.c', + '../../public/libudis86/udis86.c', +] +project.compiler.defines += ['HAVE_STRING_H']; + +for sdk_name in SM.sdks: + sdk = SM.sdks[sdk_name] + + binary = SM.HL2Config(project, projectName + '.ext', sdk) + +SM.extensions += builder.Add(project) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..956b14d --- /dev/null +++ b/Makefile @@ -0,0 +1,233 @@ +# (C)2004-2010 SourceMod Development Team +# Makefile written by David "BAILOPAN" Anderson + +########################################### +### EDIT THESE PATHS FOR YOUR OWN SETUP ### +########################################### + +SMSDK = ../.. +HL2SDK_ORIG = ../../../hl2sdk +HL2SDK_OB = ../../../hl2sdk-ob +HL2SDK_CSS = ../../../hl2sdk-css +HL2SDK_OB_VALVE = ../../../hl2sdk-ob-valve +HL2SDK_L4D = ../../../hl2sdk-l4d +HL2SDK_L4D2 = ../../../hl2sdk-l4d2 +HL2SDK_CSGO = ../../../hl2sdk-csgo +MMSOURCE19 = ../../../mmsource-1.9 + +##################################### +### EDIT BELOW FOR OTHER PROJECTS ### +##################################### + +PROJECT = sample + +#Uncomment for Metamod: Source enabled extension +#USEMETA = true + +OBJECTS = smsdk_ext.cpp extension.cpp + +############################################## +### CONFIGURE ANY OTHER FLAGS/OPTIONS HERE ### +############################################## + +C_OPT_FLAGS = -DNDEBUG -O3 -funroll-loops -pipe -fno-strict-aliasing +C_DEBUG_FLAGS = -D_DEBUG -DDEBUG -g -ggdb3 +C_GCC4_FLAGS = -fvisibility=hidden +CPP_GCC4_FLAGS = -fvisibility-inlines-hidden +CPP = gcc +CPP_OSX = clang + +########################## +### SDK CONFIGURATIONS ### +########################## + +override ENGSET = false + +# Check for valid list of engines +ifneq (,$(filter original orangebox orangeboxvalve css left4dead left4dead2 csgo,$(ENGINE))) + override ENGSET = true +endif + +ifeq "$(ENGINE)" "original" + HL2SDK = $(HL2SDK_ORIG) + CFLAGS += -DSOURCE_ENGINE=1 +endif +ifeq "$(ENGINE)" "orangebox" + HL2SDK = $(HL2SDK_OB) + CFLAGS += -DSOURCE_ENGINE=3 +endif +ifeq "$(ENGINE)" "css" + HL2SDK = $(HL2SDK_CSS) + CFLAGS += -DSOURCE_ENGINE=6 +endif +ifeq "$(ENGINE)" "orangeboxvalve" + HL2SDK = $(HL2SDK_OB_VALVE) + CFLAGS += -DSOURCE_ENGINE=7 +endif +ifeq "$(ENGINE)" "left4dead" + HL2SDK = $(HL2SDK_L4D) + CFLAGS += -DSOURCE_ENGINE=8 +endif +ifeq "$(ENGINE)" "left4dead2" + HL2SDK = $(HL2SDK_L4D2) + CFLAGS += -DSOURCE_ENGINE=9 +endif +ifeq "$(ENGINE)" "csgo" + HL2SDK = $(HL2SDK_CSGO) + CFLAGS += -DSOURCE_ENGINE=12 +endif + +HL2PUB = $(HL2SDK)/public + +ifeq "$(ENGINE)" "original" + INCLUDE += -I$(HL2SDK)/public/dlls + METAMOD = $(MMSOURCE19)/core-legacy +else + INCLUDE += -I$(HL2SDK)/public/game/server + METAMOD = $(MMSOURCE19)/core +endif + +OS := $(shell uname -s) + +ifeq "$(OS)" "Darwin" + LIB_EXT = dylib + HL2LIB = $(HL2SDK)/lib/mac +else + LIB_EXT = so + ifeq "$(ENGINE)" "original" + HL2LIB = $(HL2SDK)/linux_sdk + else + HL2LIB = $(HL2SDK)/lib/linux + endif +endif + +# if ENGINE is original or OB +ifneq (,$(filter original orangebox,$(ENGINE))) + LIB_SUFFIX = _i486.$(LIB_EXT) +else + LIB_PREFIX = lib + LIB_SUFFIX = .$(LIB_EXT) +endif + +INCLUDE += -I. -I.. -Isdk -I$(SMSDK)/public -I$(SMSDK)/public/sourcepawn + +ifeq "$(USEMETA)" "true" + LINK_HL2 = $(HL2LIB)/tier1_i486.a $(LIB_PREFIX)vstdlib$(LIB_SUFFIX) $(LIB_PREFIX)tier0$(LIB_SUFFIX) + ifeq "$(ENGINE)" "csgo" + LINK_HL2 += $(HL2LIB)/interfaces_i486.a + endif + + LINK += $(LINK_HL2) + + INCLUDE += -I$(HL2PUB) -I$(HL2PUB)/engine -I$(HL2PUB)/tier0 -I$(HL2PUB)/tier1 -I$(METAMOD) \ + -I$(METAMOD)/sourcehook + CFLAGS += -DSE_EPISODEONE=1 -DSE_DARKMESSIAH=2 -DSE_ORANGEBOX=3 -DSE_BLOODYGOODTIME=4 -DSE_EYE=5 \ + -DSE_CSS=6 -DSE_ORANGEBOXVALVE=7 -DSE_LEFT4DEAD=8 -DSE_LEFT4DEAD2=9 -DSE_ALIENSWARM=10 \ + -DSE_PORTAL2=11 -DSE_CSGO=12 +endif + +LINK += -m32 -lm -ldl + +CFLAGS += -DPOSIX -Dstricmp=strcasecmp -D_stricmp=strcasecmp -D_strnicmp=strncasecmp -Dstrnicmp=strncasecmp \ + -D_snprintf=snprintf -D_vsnprintf=vsnprintf -D_alloca=alloca -Dstrcmpi=strcasecmp -DCOMPILER_GCC -Wall -Werror \ + -Wno-overloaded-virtual -Wno-switch -Wno-unused -msse -DSOURCEMOD_BUILD -DHAVE_STDINT_H -m32 +CPPFLAGS += -Wno-non-virtual-dtor -fno-exceptions -fno-rtti + +################################################ +### DO NOT EDIT BELOW HERE FOR MOST PROJECTS ### +################################################ + +BINARY = $(PROJECT).ext.$(LIB_EXT) + +ifeq "$(DEBUG)" "true" + BIN_DIR = Debug + CFLAGS += $(C_DEBUG_FLAGS) +else + BIN_DIR = Release + CFLAGS += $(C_OPT_FLAGS) +endif + +ifeq "$(USEMETA)" "true" + BIN_DIR := $(BIN_DIR).$(ENGINE) +endif + +ifeq "$(OS)" "Darwin" + CPP = $(CPP_OSX) + LIB_EXT = dylib + CFLAGS += -DOSX -D_OSX + LINK += -dynamiclib -lstdc++ -mmacosx-version-min=10.5 +else + LIB_EXT = so + CFLAGS += -D_LINUX + LINK += -shared +endif + +IS_CLANG := $(shell $(CPP) --version | head -1 | grep clang > /dev/null && echo "1" || echo "0") + +ifeq "$(IS_CLANG)" "1" + CPP_MAJOR := $(shell $(CPP) --version | grep clang | sed "s/.*version \([0-9]\)*\.[0-9]*.*/\1/") + CPP_MINOR := $(shell $(CPP) --version | grep clang | sed "s/.*version [0-9]*\.\([0-9]\)*.*/\1/") +else + CPP_MAJOR := $(shell $(CPP) -dumpversion >&1 | cut -b1) + CPP_MINOR := $(shell $(CPP) -dumpversion >&1 | cut -b3) +endif + +# If not clang +ifeq "$(IS_CLANG)" "0" + CFLAGS += -mfpmath=sse +endif + +# Clang || GCC >= 4 +ifeq "$(shell expr $(IS_CLANG) \| $(CPP_MAJOR) \>= 4)" "1" + CFLAGS += $(C_GCC4_FLAGS) + CPPFLAGS += $(CPP_GCC4_FLAGS) +endif + +# Clang >= 3 || GCC >= 4.7 +ifeq "$(shell expr $(IS_CLANG) \& $(CPP_MAJOR) \>= 3 \| $(CPP_MAJOR) \>= 4 \& $(CPP_MINOR) \>= 7)" "1" + CFLAGS += -Wno-delete-non-virtual-dtor +endif + +# OS is Linux and not using clang +ifeq "$(shell expr $(OS) \= Linux \& $(IS_CLANG) \= 0)" "1" + LINK += -static-libgcc +endif + +OBJ_BIN := $(OBJECTS:%.cpp=$(BIN_DIR)/%.o) + +# This will break if we include other Makefiles, but is fine for now. It allows +# us to make a copy of this file that uses altered paths (ie. Makefile.mine) +# or other changes without mucking up the original. +MAKEFILE_NAME := $(CURDIR)/$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)) + +$(BIN_DIR)/%.o: %.cpp + $(CPP) $(INCLUDE) $(CFLAGS) $(CPPFLAGS) -o $@ -c $< + +all: check + mkdir -p $(BIN_DIR) + ln -sf ../smsdk_ext.cpp + if [ "$(USEMETA)" = "true" ]; then \ + ln -sf $(HL2LIB)/$(LIB_PREFIX)vstdlib$(LIB_SUFFIX); \ + ln -sf $(HL2LIB)/$(LIB_PREFIX)tier0$(LIB_SUFFIX); \ + fi + $(MAKE) -f $(MAKEFILE_NAME) extension + +check: + if [ "$(USEMETA)" = "true" ] && [ "$(ENGSET)" = "false" ]; then \ + echo "You must supply one of the following values for ENGINE:"; \ + echo "csgo, left4dead2, left4dead, css, orangeboxvalve, orangebox, or original"; \ + exit 1; \ + fi + +extension: check $(OBJ_BIN) + $(CPP) $(INCLUDE) $(OBJ_BIN) $(LINK) -o $(BIN_DIR)/$(BINARY) + +debug: + $(MAKE) -f $(MAKEFILE_NAME) all DEBUG=true + +default: all + +clean: check + rm -rf $(BIN_DIR)/*.o + rm -rf $(BIN_DIR)/$(BINARY) + diff --git a/PackageScript b/PackageScript new file mode 100644 index 0000000..dc2df3a --- /dev/null +++ b/PackageScript @@ -0,0 +1,36 @@ +# vim: set ts=8 sts=2 sw=2 tw=99 et ft=python: +import os + +# This is where the files will be output to +# package is the default +builder.SetBuildFolder('package') + +# Add any folders you need to this list +folder_list = [ + 'addons/sourcemod/extensions', + 'addons/sourcemod/gamedata' +] + +# Create the distribution folder hierarchy. +folder_map = {} +for folder in folder_list: + norm_folder = os.path.normpath(folder) + folder_map[folder] = builder.AddFolder(norm_folder) + +# Do all straight-up file copies from the source tree. +def CopyFiles(src, dest, files): + if not dest: + dest = src + dest_entry = folder_map[dest] + for source_file in files: + source_path = os.path.join(builder.sourcePath, src, source_file) + builder.AddCopy(source_path, dest_entry) + +# Gamedata (custom so updater doesn't replace it) +CopyFiles('gamedata', 'addons/sourcemod/gamedata', + [ 'a2sqcache.games.txt' ] +) + +# Copy binaries. +for cxx_task in Extension.extensions: + builder.AddCopy(cxx_task.binary, folder_map['addons/sourcemod/extensions']) diff --git a/configure.py b/configure.py new file mode 100755 index 0000000..57910e8 --- /dev/null +++ b/configure.py @@ -0,0 +1,23 @@ +# vim: set sts=2 ts=8 sw=2 tw=99 et: +import sys +from ambuild2 import run + +# Simple extensions do not need to modify this file. + +builder = run.PrepareBuild(sourcePath = sys.path[0]) + +builder.options.add_option('--hl2sdk-root', type=str, dest='hl2sdk_root', default=None, + help='Root search folder for HL2SDKs') +builder.options.add_option('--mms-path', type=str, dest='mms_path', default=None, + help='Path to Metamod:Source') +builder.options.add_option('--sm-path', type=str, dest='sm_path', default=None, + help='Path to SourceMod') +builder.options.add_option('--enable-debug', action='store_const', const='1', dest='debug', + help='Enable debugging symbols') +builder.options.add_option('--enable-optimize', action='store_const', const='1', dest='opt', + help='Enable optimization') +builder.options.add_option('-s', '--sdks', default='all', dest='sdks', + help='Build against specified SDKs; valid args are "all", "present", or ' + 'comma-delimited list of engine names (default: %default)') + +builder.Configure() diff --git a/extension.cpp b/extension.cpp new file mode 100644 index 0000000..fbf6300 --- /dev/null +++ b/extension.cpp @@ -0,0 +1,756 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod Sample Extension + * 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 "extension.h" +#include "CDetour/detours.h" +#include "steam/steam_gameserver.h" +#include "sm_namehashset.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +size_t +strlcpy(char *dst, const char *src, size_t dsize) +{ + const char *osrc = src; + size_t nleft = dsize; + + /* Copy as many bytes as will fit. */ + if (nleft != 0) { + while (--nleft != 0) { + if ((*dst++ = *src++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src. */ + if (nleft == 0) { + if (dsize != 0) + *dst = '\0'; /* NUL-terminate dst */ + while (*src++) + ; + } + + return(src - osrc - 1); /* count does not include NUL */ +} + +/** + * @file extension.cpp + * @brief Implement extension code here. + */ + +A2SQCache g_A2SQCache; /**< Global singleton for extension's main interface */ +A2SQCacheEvents g_A2SQCacheEvents; +A2SQCacheTimer g_A2SQCacheTimer; + +SMEXT_LINK(&g_A2SQCache); + +ConVar g_SvLogging("sv_qcache_logging", "0", FCVAR_NOTIFY, "Log connection checks."); +ConVar g_SvGameDesc("sv_gamedesc_override", "default", FCVAR_NOTIFY, "Overwrite the default game description. Set to 'default' to keep default description."); +ConVar g_SvMapName("sv_mapname_override", "default", FCVAR_NOTIFY, "Overwrite the map name. Set to 'default' to keep default name."); +ConVar *g_pSvVisibleMaxPlayers; +ConVar *g_pSvTags; + +IGameConfig *g_pGameConf = NULL; +IGameEventManager2 *g_pGameEvents = NULL; +ITimer *g_pA2SQCacheTimer = NULL; +ISDKTools *g_pSDKTools = NULL; +IServer *iserver = NULL; +CGlobalVars *gpGlobals = NULL; +IHLTVDirector *hltvdirector = NULL; +IHLTVServer *hltv = NULL; +double *net_time = NULL; +#if SOURCE_ENGINE == SE_CSGO +const char **g_sVersionString = NULL; +#endif + +uint8_t g_UserIDtoClientMap[USHRT_MAX + 1]; + +typedef struct netpacket_s +{ + netadr_t from; // sender IP +#if SOURCE_ENGINE >= SE_CSGO + // ns_address hack + struct // CPeerToPeerAddress + { + CSteamID m_steamID; + int m_steamChannel; + int m_AddrType; + } m_steamID; // SteamID destination + int m_AddrType; // NetworkSystemAddressType_t +#endif + int source; // received source + double received; // received time + unsigned char *data; // pointer to raw packet data + bf_read message; // easy bitbuf data access + int size; // size in bytes + int wiresize; // size in bytes before decompression + bool stream; // was send as stream + struct netpacket_s *pNext; // for internal use, should be NULL in public +} netpacket_t; + +typedef struct +{ + int nPort; // UDP/TCP use same port number +#if SOURCE_ENGINE >= SE_CSGO + int hUDP; + char pad[4*15]; +#else + bool bListening; // true if TCP port is listening + int hUDP; // handle to UDP socket from socket() + int hTCP; // handle to TCP socket from socket() +#endif +} netsocket_t; + +CUtlVector *net_sockets; +int g_ServerUDPSocket = 0; + +CDetour *g_Detour_CBaseServer__InactivateClients = NULL; +SH_DECL_MANUALHOOK1(ProcessConnectionlessPacket, 0, 0, 0, bool, netpacket_t *); // virtual bool IServer::ProcessConnectionlessPacket( netpacket_t *packet ) = 0; + +void *s_queryRateChecker = NULL; +bool (*CIPRateLimit__CheckIP)(void *pThis, netadr_t adr); +//bool (*CBaseServer__ValidChallenge)(void *pThis, netadr_t adr, int challengeNr); + +struct CQueryCache +{ + struct CPlayer + { + bool active; + bool fake; + int userid; + IClient *pClient; + char name[MAX_PLAYER_NAME_LENGTH]; + unsigned nameLen; + int32_t score; + double time; + } players[SM_MAXPLAYERS + 1]; + + struct CInfo + { + uint8_t nProtocol = 17; // Protocol | byte | Protocol version used by the server. + char aHostName[255]; // Name | string | Name of the server. + uint8_t aHostNameLen; + char aMapName[255]; // Map | string | Map the server has currently loaded. + uint8_t aMapNameLen; + char aGameDir[255]; // Folder | string | Name of the folder containing the game files. + uint8_t aGameDirLen; + char aGameDescription[255]; // Game | string | Full name of the game. + uint8_t aGameDescriptionLen; + uint16_t iSteamAppID; // ID | short | Steam Application ID of game. + uint8_t nNumClients = 0; // Players | byte | Number of players on the server. + uint8_t nMaxClients; // Max. Players | byte | Maximum number of players the server reports it can hold. + uint8_t nFakeClients = 0; // Bots | byte | Number of bots on the server. + uint8_t nServerType = 'd'; // Server type | byte | Indicates the type of server: 'd' for a dedicated server, 'l' for a non-dedicated server, 'p' for a SourceTV relay (proxy) + uint8_t nEnvironment = 'l'; // Environment | byte | Indicates the operating system of the server: 'l' for Linux, 'w' for Windows, 'm' or 'o' for Mac (the code changed after L4D1) + uint8_t nPassword; // Visibility | byte | Indicates whether the server requires a password: 0 for public, 1 for private + uint8_t bIsSecure; // VAC | byte | Specifies whether the server uses VAC: 0 for unsecured, 1 for secured + char aVersion[40]; // Version | string | Version of the game installed on the server. + uint8_t aVersionLen; + uint8_t nNewFlags = 0; // Extra Data Flag (EDF) | byte | If present, this specifies which additional data fields will be included. + uint16_t iUDPPort; // EDF & 0x80 -> Port | short | The server's game port number. + uint64_t iSteamID; // EDF & 0x10 -> SteamID | long long | Server's SteamID. + uint16_t iHLTVUDPPort; // EDF & 0x40 -> Port | short | Spectator port number for SourceTV. + char aHLTVName[255]; // EDF & 0x40 -> Name | string | Name of the spectator server for SourceTV. + uint8_t aHLTVNameLen; + char aKeywords[255]; // EDF & 0x20 -> Keywords | string | Tags that describe the game according to the server (for future use.) (sv_tags) + uint8_t aKeywordsLen; + uint64_t iGameID; // EDF & 0x01 -> GameID | long long | The server's 64-bit GameID. If this is present, a more accurate AppID is present in the low 24 bits. The earlier AppID could have been truncated as it was forced into 16-bit storage. + } info; + + uint8_t info_cache[sizeof(CInfo)] = {0xFF, 0xFF, 0xFF, 0xFF, 'I'}; + uint16_t info_cache_len; + + uint8_t players_cache[4+1+1+SM_MAXPLAYERS*(1+MAX_PLAYER_NAME_LENGTH+4+4)] = {0xFF, 0xFF, 0xFF, 0xFF, 'D', 0}; + uint16_t players_cache_len; +} g_QueryCache; + +class CBaseClient; +class CBaseServer; + + +void UpdateQueryCache() +{ + // A2S_INFO + CQueryCache::CInfo &info = g_QueryCache.info; + info.aHostNameLen = strlcpy(info.aHostName, iserver->GetName(), sizeof(info.aHostName)); + + if(strcmp(g_SvMapName.GetString(), "default") == 0) + info.aMapNameLen = strlcpy(info.aMapName, iserver->GetMapName(), sizeof(info.aMapName)); + else + info.aMapNameLen = strlcpy(info.aMapName, g_SvMapName.GetString(), sizeof(info.aMapName)); + + if(strcmp(g_SvGameDesc.GetString(), "default") == 0) + info.aGameDescriptionLen = strlcpy(info.aGameDescription, gamedll->GetGameDescription(), sizeof(info.aGameDescription)); + else + info.aGameDescriptionLen = strlcpy(info.aGameDescription, g_SvGameDesc.GetString(), sizeof(info.aGameDescription)); + + if(g_pSvVisibleMaxPlayers->GetInt() >= 0) + info.nMaxClients = g_pSvVisibleMaxPlayers->GetInt(); + else + info.nMaxClients = iserver->GetMaxClients(); + info.nPassword = iserver->GetPassword() ? 1 : 0; + info.bIsSecure = true; + + if(!(info.nNewFlags & 0x10) && engine->GetGameServerSteamID()) + { + info.iSteamID = engine->GetGameServerSteamID()->ConvertToUint64(); + info.nNewFlags |= 0x10; + } + + if(!(info.nNewFlags & 0x40) && hltvdirector->IsActive()) // tv_name can't change anymore + { +#if SOURCE_ENGINE >= SE_CSGO + hltv = hltvdirector->GetHLTVServer(0); +#else + hltv = hltvdirector->GetHLTVServer(); +#endif + if(hltv) + { + IServer *ihltvserver = hltv->GetBaseServer(); + if(ihltvserver) + { + info.iHLTVUDPPort = ihltvserver->GetUDPPort(); + info.aHLTVNameLen = strlcpy(info.aHLTVName, ihltvserver->GetName(), sizeof(info.aHLTVName)); + info.nNewFlags |= 0x40; + } + } + } + + info.aKeywordsLen = strlcpy(info.aKeywords, g_pSvTags->GetString(), sizeof(info.aKeywords)); + if(info.aKeywordsLen) + info.nNewFlags |= 0x20; + else + info.nNewFlags &= ~0x20; + + + uint8_t *info_cache = g_QueryCache.info_cache; + uint16_t pos = 5; // header: FF FF FF FF I + + info_cache[pos++] = info.nProtocol; + + memcpy(&info_cache[pos], info.aHostName, info.aHostNameLen + 1); + pos += info.aHostNameLen + 1; + + memcpy(&info_cache[pos], info.aMapName, info.aMapNameLen + 1); + pos += info.aMapNameLen + 1; + + memcpy(&info_cache[pos], info.aGameDir, info.aGameDirLen + 1); + pos += info.aGameDirLen + 1; + + memcpy(&info_cache[pos], info.aGameDescription, info.aGameDescriptionLen + 1); + pos += info.aGameDescriptionLen + 1; + + *(uint16_t *)&info_cache[pos] = info.iSteamAppID; + pos += 2; + + info_cache[pos++] = info.nNumClients; + + info_cache[pos++] = info.nMaxClients; + + info_cache[pos++] = 0;//info.nFakeClients; + + info_cache[pos++] = info.nServerType; + + info_cache[pos++] = info.nEnvironment; + + info_cache[pos++] = info.nPassword; + + info_cache[pos++] = info.bIsSecure; + + memcpy(&info_cache[pos], info.aVersion, info.aVersionLen + 1); + pos += info.aVersionLen + 1; + + info_cache[pos++] = info.nNewFlags; + + if(info.nNewFlags & 0x80) { + *(uint16_t *)&info_cache[pos] = info.iUDPPort; + pos += 2; + } + + if(info.nNewFlags & 0x10) { + *(uint64_t *)&info_cache[pos] = info.iSteamID; + pos += 8; + } + + if(info.nNewFlags & 0x40) { + *(uint16_t *)&info_cache[pos] = info.iHLTVUDPPort; + pos += 2; + + memcpy(&info_cache[pos], info.aHLTVName, info.aHLTVNameLen + 1); + pos += info.aHLTVNameLen + 1; + } + + if(info.nNewFlags & 0x20) { + memcpy(&info_cache[pos], info.aKeywords, info.aKeywordsLen + 1); + pos += info.aKeywordsLen + 1; + } + + if(info.nNewFlags & 0x01) { + *(uint64_t *)&info_cache[pos] = info.iGameID; + pos += 8; + } + + g_QueryCache.info_cache_len = pos; + + + // A2S_PLAYER + uint8_t *players_cache = g_QueryCache.players_cache; + pos = 6; // header: FF FF FF FF D 0[numplayers] + for(int i = 1; i <= SM_MAXPLAYERS; i++) + { + const CQueryCache::CPlayer &player = g_QueryCache.players[i]; + if(!player.active) + continue; + + players_cache[pos++] = players_cache[5]; // Index | byte | Index of player chunk starting from 0. + players_cache[5]++; // Players | byte | Number of players whose information was gathered. + memcpy(&players_cache[pos], player.name, player.nameLen + 1); // Name | string | Name of the player. + pos += player.nameLen + 1; + *(int32_t *)&players_cache[pos] = player.score; // Score | long | Player's score (usually "frags" or "kills".) + pos += 4; + *(float *)&players_cache[pos] = *net_time - player.time; // Duration | float | Time (in seconds) player has been connected to the server. + pos += 4; + } + + g_QueryCache.players_cache_len = pos; +} + +bool Hook_ProcessConnectionlessPacket(netpacket_t * packet) +{ + if(packet->size >= 25 && packet->data[4] == 'T') + { + if(!CIPRateLimit__CheckIP(s_queryRateChecker, packet->from)) + { + RETURN_META_VALUE(MRES_SUPERCEDE, false); + } + + sockaddr_in to; + to.sin_family = AF_INET; + to.sin_port = packet->from.port; + to.sin_addr.s_addr = *(int32_t *)&packet->from.ip; + + sendto(g_ServerUDPSocket, g_QueryCache.info_cache, g_QueryCache.info_cache_len, 0, (sockaddr *)&to, sizeof(to)); + + RETURN_META_VALUE(MRES_SUPERCEDE, true); + } + + if((packet->size == 5 || packet->size == 9) && packet->data[4] == 'U') + { + if(!CIPRateLimit__CheckIP(s_queryRateChecker, packet->from)) + { + RETURN_META_VALUE(MRES_SUPERCEDE, false); + } + + sockaddr_in to; + to.sin_family = AF_INET; + to.sin_port = packet->from.port; + to.sin_addr.s_addr = *(int32_t *)&packet->from.ip; + + int32_t challengeNr = -1; + if(packet->size == 9) + challengeNr = *(int32_t *)&packet->data[5]; + + /* TODO + * This is a complete nonsense challenge as it doesn't offer any protection at all. + * The point of this challenge is to stop spoofed source UDP DDoS reflection attacks, + * so it doesn't really matter if one single server out of thousands doesn't + * implement this correctly. If you do happen to use this on thousands of servers + * though then please do implement it correctly. + */ + int32_t realChallengeNr = *(int32_t *)&packet->from.ip ^ 0x55AADD88; + if(challengeNr != realChallengeNr) + { + uint8_t response[9] = {0xFF, 0xFF, 0xFF, 0xFF, 'A'}; + *(int32_t *)&response[5] = realChallengeNr; + sendto(g_ServerUDPSocket, response, sizeof(response), 0, (sockaddr *)&to, sizeof(to)); + RETURN_META_VALUE(MRES_SUPERCEDE, true); + } + + sendto(g_ServerUDPSocket, g_QueryCache.players_cache, g_QueryCache.players_cache_len, 0, (sockaddr *)&to, sizeof(to)); + + RETURN_META_VALUE(MRES_SUPERCEDE, true); + } + + RETURN_META_VALUE(MRES_IGNORED, false); +} + +DETOUR_DECL_MEMBER0(CBaseServer__InactivateClients, void) +{ + for(int slot = 0; slot < iserver->GetClientCount(); slot++) + { + int client = slot + 1; + IClient *pClient = iserver->GetClient(slot); + if(!pClient) + continue; + + // Disconnect all fake clients manually before the engine just nukes them. + if(pClient->IsFakeClient() && !pClient->IsHLTV()) + { + pClient->Disconnect(""); + } + } + + return DETOUR_MEMBER_CALL(CBaseServer__InactivateClients)(); +} + +bool A2SQCache::SDK_OnLoad(char *error, size_t maxlen, bool late) +{ + char conf_error[255] = ""; + if(!gameconfs->LoadGameConfigFile("a2sqcache.games", &g_pGameConf, conf_error, sizeof(conf_error))) + { + if(conf_error[0]) + { + snprintf(error, maxlen, "Could not read a2sqcache.games.txt: %s\n", conf_error); + } + return false; + } + + if(!g_pGameConf->GetMemSig("CIPRateLimit__CheckIP", (void **)&CIPRateLimit__CheckIP) || !CIPRateLimit__CheckIP) + { + snprintf(error, maxlen, "Failed to find CIPRateLimit::CheckIP address.\n"); + return false; + } + + if(!g_pGameConf->GetAddress("s_queryRateChecker", &s_queryRateChecker) || !s_queryRateChecker) + { + snprintf(error, maxlen, "Failed to find s_queryRateChecker address.\n"); + return false; + } + + if(!g_pGameConf->GetAddress("net_sockets", (void **)&net_sockets) || !net_sockets) + { + snprintf(error, maxlen, "Failed to find net_sockets address.\n"); + return false; + } + + if(!g_pGameConf->GetAddress("net_time", (void **)&net_time) || !net_time) + { + snprintf(error, maxlen, "Failed to find net_time address.\n"); + return false; + } + +#if SOURCE_ENGINE == SE_CSGO + if(!g_pGameConf->GetAddress("g_sVersionString", (void **)&g_sVersionString) || !g_sVersionString) + { + snprintf(error, maxlen, "Failed to find g_sVersionString address.\n"); + return false; + } +#endif + + CDetourManager::Init(g_pSM->GetScriptingEngine(), g_pGameConf); + + g_Detour_CBaseServer__InactivateClients = DETOUR_CREATE_MEMBER(CBaseServer__InactivateClients, "CBaseServer__InactivateClients"); + if(!g_Detour_CBaseServer__InactivateClients) + { + snprintf(error, maxlen, "Failed to detour CBaseServer__InactivateClients.\n"); + return false; + } + g_Detour_CBaseServer__InactivateClients->EnableDetour(); + + g_pGameEvents->AddListener(&g_A2SQCacheEvents, "player_connect", true); + g_pGameEvents->AddListener(&g_A2SQCacheEvents, "player_disconnect", true); + g_pGameEvents->AddListener(&g_A2SQCacheEvents, "player_changename", true); + + playerhelpers->AddClientListener(this); + + return true; +} + +bool A2SQCache::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlen, bool late) +{ + GET_V_IFACE_CURRENT(GetEngineFactory, engine, IVEngineServer, INTERFACEVERSION_VENGINESERVER); + GET_V_IFACE_ANY(GetServerFactory, gamedll, IServerGameDLL, INTERFACEVERSION_SERVERGAMEDLL); + GET_V_IFACE_CURRENT(GetEngineFactory, g_pGameEvents, IGameEventManager2, INTERFACEVERSION_GAMEEVENTSMANAGER2); + GET_V_IFACE_CURRENT(GetEngineFactory, g_pCVar, ICvar, CVAR_INTERFACE_VERSION); + GET_V_IFACE_CURRENT(GetServerFactory, hltvdirector, IHLTVDirector, INTERFACEVERSION_HLTVDIRECTOR); + + gpGlobals = ismm->GetCGlobals(); + ConVar_Register(0, this); + + g_pSvVisibleMaxPlayers = g_pCVar->FindVar("sv_visiblemaxplayers"); + g_pSvTags = g_pCVar->FindVar("sv_tags"); + + return true; +} + +void A2SQCache::SDK_OnUnload() +{ + if(g_Detour_CBaseServer__InactivateClients) + { + g_Detour_CBaseServer__InactivateClients->Destroy(); + g_Detour_CBaseServer__InactivateClients = NULL; + } + + g_pGameEvents->RemoveListener(&g_A2SQCacheEvents); + + playerhelpers->RemoveClientListener(this); + + if(g_pA2SQCacheTimer) + timersys->KillTimer(g_pA2SQCacheTimer); + + gameconfs->CloseGameConfigFile(g_pGameConf); +} + +bool A2SQCache::RegisterConCommandBase(ConCommandBase *pVar) +{ + /* Always call META_REGCVAR instead of going through the engine. */ + return META_REGCVAR(pVar); +} + +void A2SQCache::SDK_OnAllLoaded() +{ + SM_GET_LATE_IFACE(SDKTOOLS, g_pSDKTools); + + iserver = g_pSDKTools->GetIServer(); + if (!iserver) { + smutils->LogError(myself, "Failed to get IServer interface from SDKTools!"); + return; + } + + int socknum = 1; // NS_SERVER + g_ServerUDPSocket = (*net_sockets)[socknum].hUDP; + + if(!g_ServerUDPSocket) + { + smutils->LogError(myself, "Failed to find server UDP socket."); + return; + } + + int offset; + if (g_pGameConf->GetOffset("IServer__ProcessConnectionlessPacket", &offset)) + { + SH_MANUALHOOK_RECONFIGURE(ProcessConnectionlessPacket, offset, 0, 0); + SH_ADD_MANUALHOOK(ProcessConnectionlessPacket, iserver, SH_STATIC(Hook_ProcessConnectionlessPacket), false); + } + else + { + smutils->LogError(myself, "Failed to find IServer::ProcessConnectionlessPacket offset."); + return; + } + + g_pA2SQCacheTimer = timersys->CreateTimer(&g_A2SQCacheTimer, 1.0, NULL, TIMER_FLAG_REPEAT); + + // A2S_INFO + CQueryCache::CInfo &info = g_QueryCache.info; + info.aGameDirLen = strlcpy(info.aGameDir, smutils->GetGameFolderName(), sizeof(info.aGameDir)); + +#if SOURCE_ENGINE == SE_CSGO + info.iSteamAppID = 730; // wtf valve + info.aVersionLen = snprintf(info.aVersion, sizeof(info.aVersion), "%s", *g_sVersionString); +#else + info.iSteamAppID = engine->GetAppID(); + info.aVersionLen = snprintf(info.aVersion, sizeof(info.aVersion), "%d", engine->GetServerVersion()); +#endif + + info.iUDPPort = iserver->GetUDPPort(); + info.nNewFlags |= 0x80; + + info.iGameID = info.iSteamAppID; + info.nNewFlags |= 0x01; + + UpdateQueryCache(); + + // A2S_PLAYER + for(int slot = 0; slot < iserver->GetClientCount(); slot++) + { + int client = slot + 1; + IClient *pClient = iserver->GetClient(slot); + if(!pClient) + continue; + + CQueryCache::CPlayer &player = g_QueryCache.players[client]; + IGamePlayer *gplayer = playerhelpers->GetGamePlayer(client); + + if(!player.active) + { + g_QueryCache.info.nNumClients++; + if(pClient->IsFakeClient() && !pClient->IsHLTV() && (!gplayer || (gplayer->IsConnected() && !gplayer->IsSourceTV()))) + { + g_QueryCache.info.nFakeClients++; + player.fake = true; + } + } + + player.active = true; + player.pClient = pClient; + player.nameLen = strlcpy(player.name, pClient->GetClientName(), sizeof(player.name)); + + INetChannelInfo *netinfo = (INetChannelInfo *)player.pClient->GetNetChannel(); + if(netinfo) + player.time = *net_time - netinfo->GetTimeConnected(); + else + player.time = 0; + + if(gplayer && gplayer->IsConnected()) + { + IPlayerInfo *info = gplayer->GetPlayerInfo(); + if(info) + player.score = info->GetFragCount(); + else + player.score = 0; + } + + g_UserIDtoClientMap[pClient->GetUserID()] = client; + } +} + +void A2SQCache::OnClientSettingsChanged(int client) +{ + if(client >= 1 && client <= SM_MAXPLAYERS) + { + CQueryCache::CPlayer &player = g_QueryCache.players[client]; + if(player.active && player.pClient) + player.nameLen = strlcpy(player.name, player.pClient->GetClientName(), sizeof(player.name)); + } +} + +void A2SQCache::OnClientPutInServer(int client) +{ + if(client >= 1 && client <= SM_MAXPLAYERS) + { + CQueryCache::CPlayer &player = g_QueryCache.players[client]; + IGamePlayer *gplayer = playerhelpers->GetGamePlayer(client); + if(player.active && player.fake && gplayer->IsSourceTV()) + { + player.fake = false; + g_QueryCache.info.nFakeClients--; + } + } +} + +void A2SQCache::OnTimer() +{ + for(int client = 1; client <= SM_MAXPLAYERS; client++) + { + CQueryCache::CPlayer &player = g_QueryCache.players[client]; + if(!player.active) + continue; + + IGamePlayer *gplayer = playerhelpers->GetGamePlayer(client); + if(!gplayer || !gplayer->IsConnected()) + continue; + + IPlayerInfo *info = gplayer->GetPlayerInfo(); + if(info) + player.score = info->GetFragCount(); + } + + UpdateQueryCache(); +} + +void A2SQCacheEvents::FireGameEvent(IGameEvent *event) +{ + const char *name = event->GetName(); + + if(strcmp(name, "player_connect") == 0) + { + const int client = event->GetInt("index") + 1; + const int userid = event->GetInt("userid"); + const bool bot = event->GetBool("bot"); + const char *name = event->GetString("name"); + + if (g_SvLogging.GetInt()) + g_pSM->LogMessage(myself, "player_connect(client=%d, userid=%d, bot=%d, name=%s)", client, userid, bot, name); + + if(client >= 1 && client <= SM_MAXPLAYERS) + { + CQueryCache::CPlayer &player = g_QueryCache.players[client]; + + player.active = true; + player.fake = false; + player.pClient = iserver->GetClient(client - 1); + g_QueryCache.info.nNumClients++; + if(bot) + { + player.fake = true; + g_QueryCache.info.nFakeClients++; + } + player.time = *net_time; + player.score = 0; + player.nameLen = strlcpy(player.name, player.pClient->GetClientName(), sizeof(player.name)); + + g_UserIDtoClientMap[userid] = client; + + if (g_SvLogging.GetInt()) + g_pSM->LogMessage(myself, "\tCPlayer(active=%d, fake=%d, pClient=%p, name=%s)", player.active, player.fake, player.pClient, player.name); + } + + } + else if(strcmp(name, "player_disconnect") == 0) + { + const int userid = event->GetInt("userid"); + const int client = g_UserIDtoClientMap[userid]; + g_UserIDtoClientMap[userid] = 0; + + if (g_SvLogging.GetInt()) + g_pSM->LogMessage(myself, "player_disconnect(userid=%d, client=%d)", userid, client); + + if(client >= 1 && client <= SM_MAXPLAYERS) + { + CQueryCache::CPlayer &player = g_QueryCache.players[client]; + if (g_SvLogging.GetInt()) + g_pSM->LogMessage(myself, "\tCPlayer(active=%d, fake=%d, pClient=%p, name=%s)", player.active, player.fake, player.pClient, player.name); + + if(player.active) + { + g_QueryCache.info.nNumClients--; + if(player.fake) + g_QueryCache.info.nFakeClients--; + } + player.active = false; + player.pClient = NULL; + } + } + else if(strcmp(name, "player_changename") == 0) + { + const int userid = event->GetInt("userid"); + const int client = g_UserIDtoClientMap[userid]; + + g_A2SQCache.OnClientSettingsChanged(client); + } +} + +ResultType A2SQCacheTimer::OnTimer(ITimer *pTimer, void *pData) +{ + g_A2SQCache.OnTimer(); + return Pl_Continue; +} +void A2SQCacheTimer::OnTimerEnd(ITimer *pTimer, void *pData) {} diff --git a/extension.h b/extension.h new file mode 100644 index 0000000..67411ef --- /dev/null +++ b/extension.h @@ -0,0 +1,147 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod Sample Extension + * 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_PROPER_H_ +#define _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ + +/** + * @file extension.h + * @brief Sample extension code header. + */ + +#include "smsdk_ext.h" +#include + +/** + * @brief Sample implementation of the SDK Extension. + * Note: Uncomment one of the pre-defined virtual functions in order to use it. + */ +class A2SQCache : + public SDKExtension, + public IConCommandBaseAccessor, + public IClientListener +{ +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. + * Note: It is is a good idea to add natives here, if any are provided. + */ + virtual void SDK_OnAllLoaded(); + + /** + * @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 maxlength); +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 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: // IConCommandBaseAccessor + virtual bool RegisterConCommandBase(ConCommandBase *pVar); + +public: // IClientListener + virtual void OnClientSettingsChanged(int client); + virtual void OnClientPutInServer(int client); +public: + void OnTimer(); +}; + +class A2SQCacheEvents : public IGameEventListener2 +{ +public: + virtual void FireGameEvent( IGameEvent *event ); +#if SOURCE_ENGINE >= SE_ALIENSWARM + virtual int GetEventDebugID( void ) { return 42; } +#endif +}; + +class A2SQCacheTimer : public ITimedEvent +{ +public: + virtual ResultType OnTimer(ITimer *pTimer, void *pData); + virtual void OnTimerEnd(ITimer *pTimer, void *pData); +}; + +#endif // _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ diff --git a/gamedata/a2sqcache.games.txt b/gamedata/a2sqcache.games.txt new file mode 100644 index 0000000..8045001 --- /dev/null +++ b/gamedata/a2sqcache.games.txt @@ -0,0 +1,171 @@ +"Games" +{ + "#default" + { + "#supported" + { + "engine" "orangebox_valve" + "engine" "css" + } + + "Addresses" + { + "net_sockets" + { + "linux" + { + "signature" "net_sockets" + } + } + + "net_time" + { + "linux" + { + "signature" "net_time" + } + } + + "s_queryRateChecker" + { + "linux" + { + "signature" "s_queryRateChecker" + } + } + } + + "Signatures" + { + "net_sockets" + { + "library" "engine" + "linux" "@_ZL11net_sockets" + } + + "net_time" + { + "library" "engine" + "linux" "@net_time" + } + + "s_queryRateChecker" + { + "library" "engine" + "linux" "@_ZL18s_queryRateChecker" + } + + "CIPRateLimit__CheckIP" + { + "library" "engine" + "linux" "@_ZN12CIPRateLimit7CheckIPE8netadr_s" + } + + "CBaseServer__InactivateClients" + { + "library" "engine" + "linux" "@_ZN11CBaseServer17InactivateClientsEv" + } + } + + "Offsets" + { + "IServer__ProcessConnectionlessPacket" + { + "windows" "1" + "linux" "2" + } + } + } + + + "csgo" + { + "Addresses" + { + "net_sockets" + { + "linux" + { + "signature" "NET_ProcessSocket" + "read" "31" + } + } + + "net_time" + { + "linux" + { + "signature" "CServerMsg_Ping__SendMsg" + "read" "21" + } + } + + "s_queryRateChecker" + { + "linux" + { + "signature" "CheckConnectionLessRateLimits" + "read" "69" + } + } + + "g_sVersionString" + { + "linux" + { + "signature" "Sys_Version" + "read" "26" + } + } + } + + "Signatures" + { + "NET_ProcessSocket" + { + "library" "engine" + "linux" "\x55\x89\xE5\x57\x56\x53\x81\xEC\x8C\x00\x00\x00\x8B\x75\x08\x89\xF0\x89\x74\x24\x04\xC1\xE0\x06\x8D\x04\xB0\x89\x45\x90\xA1\x2A\x2A\x2A\x2A" + } + + "CServerMsg_Ping__SendMsg" + { + "library" "engine" + "linux" "\x55\x89\xE5\x56\x53\x8D\x5D\xE0\x81\xEC\xC0\x00\x00\x00\x8B\x45\x08\xF2\x0F\x10\x05\x2A\x2A\x2A\x2A\x8B\x75\x0C" + } + + "CheckConnectionLessRateLimits" + { + "library" "engine" + "linux" "\x55\x89\xE5\x83\xEC\x68\x89\x75\xF8\x8B\x75\x08\x89\x5D\xF4\x89\x7D\xFC\x8B\x46\x1C\x85\xC0\x75\x5F\x80\x3D\x2A\x2A\x2A\x2A\x00\x0F\x84\x2A\x2A\x2A\x2A" + } + + "CIPRateLimit__CheckIP" + { + "library" "engine" + "linux" "\x55\x89\xE5\x57\x56\x53\x81\xEC\xCC\x00\x00\x00\x8B\x5D\x08\xE8\x2A\x2A\x2A\x2A" + } + + "Sys_Version" + { + "library" "engine" + "linux" "\x55\x89\xE5\x56\x53\x81\xEC\x20\x01\x00\x00\x0F\xB6\x5D\x08" + } + + "CBaseServer__InactivateClients" + { + "library" "engine" + "linux" "\x55\x89\xE5\x57\x56\x53\x83\xEC\x1C\x8B\x7D\x08\x8B\xB7\x80\x02\x00\x00" + } + } + + "Offsets" + { + "IServer__ProcessConnectionlessPacket" + { + "windows" "1" + "linux" "2" + } + } + } +} diff --git a/smsdk_config.h b/smsdk_config.h new file mode 100644 index 0000000..0a77048 --- /dev/null +++ b/smsdk_config.h @@ -0,0 +1,81 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod Sample Extension + * 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_CONFIG_H_ +#define _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ + +/** + * @file smsdk_config.h + * @brief Contains macros for configuring basic extension information. + */ + +/* Basic information exposed publicly */ +#define SMEXT_CONF_NAME "A2SQCache" +#define SMEXT_CONF_DESCRIPTION "A2S_INFO+PLAYER cache" +#define SMEXT_CONF_VERSION "1.0" +#define SMEXT_CONF_AUTHOR "BotoX" +#define SMEXT_CONF_URL "https://git.botox.bz/CSSZombieEscape/sm-ext-A2SQCache" +#define SMEXT_CONF_LOGTAG "A2SQCACHE" +#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_ROOTCONSOLEMENU + +#endif // _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_