commit cd542100ede578de82ea05f74d477ab840bfbebe Author: BotoX Date: Wed Jan 27 15:07:12 2021 +0100 initial commit 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_