commit d306783285ccf355e37d791e69aff307584ab1ff Author: arthurdead Date: Fri Apr 29 01:23:17 2022 -0300 init commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/AMBuildScript b/AMBuildScript new file mode 100644 index 0000000..049ff38 --- /dev/null +++ b/AMBuildScript @@ -0,0 +1,449 @@ +# 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 = { + 'episode1': SDK('HL2SDK', '1.ep1', '1', 'EPISODEONE', WinLinux, 'episode1'), + 'ep2': SDK('HL2SDKOB', '2.ep2', '3', 'ORANGEBOX', WinLinux, 'orangebox'), + 'css': SDK('HL2SDKCSS', '2.css', '6', 'CSS', WinLinuxMac, 'css'), + 'hl2dm': SDK('HL2SDKHL2DM', '2.hl2dm', '7', 'HL2DM', WinLinuxMac, 'hl2dm'), + 'dods': SDK('HL2SDKDODS', '2.dods', '8', 'DODS', WinLinuxMac, 'dods'), + 'sdk2013': SDK('HL2SDK2013', '2.sdk2013', '9', 'SDK2013', WinLinuxMac, 'sdk2013'), + 'tf2': SDK('HL2SDKTF2', '2.tf2', '11', 'TF2', WinLinuxMac, 'tf2'), + 'l4d': SDK('HL2SDKL4D', '2.l4d', '12', 'LEFT4DEAD', WinLinuxMac, 'l4d'), + 'nucleardawn': SDK('HL2SDKND', '2.nd', '13', 'NUCLEARDAWN', WinLinuxMac, 'nucleardawn'), + 'l4d2': SDK('HL2SDKL4D2', '2.l4d2', '15', 'LEFT4DEAD2', WinLinuxMac, 'l4d2'), + 'darkm': SDK('HL2SDK-DARKM', '2.darkm', '2', 'DARKMESSIAH', WinOnly, 'darkm'), + 'swarm': SDK('HL2SDK-SWARM', '2.swarm', '16', 'ALIENSWARM', WinOnly, 'swarm'), + 'bgt': SDK('HL2SDK-BGT', '2.bgt', '4', 'BLOODYGOODTIME', WinOnly, 'bgt'), + 'eye': SDK('HL2SDK-EYE', '2.eye', '5', 'EYE', WinOnly, 'eye'), + 'csgo': SDK('HL2SDKCSGO', '2.csgo', '21', 'CSGO', WinLinuxMac, 'csgo'), + 'portal2': SDK('HL2SDKPORTAL2', '2.portal2', '17', 'PORTAL2', [], 'portal2'), + 'blade': SDK('HL2SDKBLADE', '2.blade', '18', 'BLADE', WinLinux, 'blade'), + 'insurgency': SDK('HL2SDKINSURGENCY', '2.insurgency', '19', 'INSURGENCY', WinLinuxMac, 'insurgency'), + 'contagion': SDK('HL2SDKCONTAGION', '2.contagion', '14', 'CONTAGION', WinOnly, 'contagion'), + 'bms': SDK('HL2SDKBMS', '2.bms', '10', 'BMS', WinLinux, 'bms'), + 'doi': SDK('HL2SDKDOI', '2.doi', '20', 'DOI', WinLinuxMac, 'doi'), +} + +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) + + # Optimization + 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', + '-Wno-register', + '-msse', + '-m32', + '-fvisibility=hidden', + ] + cxx.cxxflags += [ + '-std=c++17', + '-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'] + + 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..1a3d90f --- /dev/null +++ b/AMBuilder @@ -0,0 +1,40 @@ +# vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: +import os, sys + +projectName = 'proxysend' + +# smsdk_ext.cpp will be automatically added later +sourceFiles = [ + 'extension.cpp', + 'packed_entity.cpp', + os.path.join(Extension.sm_root, 'public', 'CDetour', 'detours.cpp'), + os.path.join(Extension.sm_root, 'public', 'asm', 'asm.c'), + os.path.join(Extension.sm_root, 'public', 'libudis86/decode.c'), + os.path.join(Extension.sm_root, 'public', 'libudis86/itab.c'), + os.path.join(Extension.sm_root, 'public', 'libudis86/syn-att.c'), + os.path.join(Extension.sm_root, 'public', 'libudis86/syn-intel.c'), + os.path.join(Extension.sm_root, 'public', 'libudis86/syn.c'), + os.path.join(Extension.sm_root, 'public', 'libudis86/udis86.c') +] + +############### +# Make sure to edit PackageScript, which copies your files to their appropriate locations +# Simple extensions do not need to modify past this point. + +project = Extension.HL2Project(builder, projectName + '.ext') + +if os.path.isfile(os.path.join(builder.currentSourcePath, 'sdk', 'smsdk_ext.cpp')): + # Use the copy included in the project + project.sources += [os.path.join('sdk', 'smsdk_ext.cpp')] +else: + # Use the copy included with SM 1.6 and newer + project.sources += [os.path.join(Extension.sm_root, 'public', 'smsdk_ext.cpp')] + +project.sources += sourceFiles + +for sdk_name in Extension.sdks: + sdk = Extension.sdks[sdk_name] + + binary = Extension.HL2Config(project, projectName + '.ext.' + sdk.ext, sdk) + +Extension.extensions = builder.Add(project) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..eedafa6 --- /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)/sourcepawn/include + +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 -std=c++11 + +################################################ +### 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..36ce6e4 --- /dev/null +++ b/PackageScript @@ -0,0 +1,52 @@ +# 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/scripting/include', + #'addons/sourcemod/gamedata', + #'addons/sourcemod/configs', +] + +# 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) + +# Include files +#CopyFiles('include', 'addons/sourcemod/scripting/include', +# [ 'sample.inc', ] +#) + +# GameData files +#CopyFiles('gamedata', 'addons/sourcemod/gamedata', +# [ 'myfile.txt', +# 'file2.txt' +# ] +#) + +# Config Files +#CopyFiles('configs', 'addons/sourcemod/configs', +# [ 'configfile.cfg', +# 'otherconfig.cfg, +# ] +#) + +# Copy binaries. +for cxx_task in Extension.extensions: + builder.AddCopy(cxx_task.binary, folder_map['addons/sourcemod/extensions']) diff --git a/changeframelist.h b/changeframelist.h new file mode 100644 index 0000000..b0bb53b --- /dev/null +++ b/changeframelist.h @@ -0,0 +1,56 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef CHANGEFRAMELIST_H +#define CHANGEFRAMELIST_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "bitbuf.h" + + +// This class holds the last tick (from host_tickcount) that each property in +// a datatable changed at. +// +// It provides fast access to a list of properties that changed within a certain frame range. +// +// These are created once per entity per frame. Since usually a very small percentage of an +// entity's properties actually change each frame, this allows you to get a small set of +// properties to delta for each client. +abstract_class IChangeFrameList +{ +public: + + // Call this to delete the object. + virtual void Release() = 0; + + // This just returns the value you passed into AllocChangeFrameList(). + virtual int GetNumProps() = 0; + + // Sets the change frames for the specified properties to iFrame. + virtual void SetChangeTick( const int *pPropIndices, int nPropIndices, const int iTick ) = 0; + + // Get a list of all properties with a change frame > iFrame. + virtual int GetPropsChangedAfterTick( int iTick, int *iOutProps, int nMaxOutProps ) = 0; + + virtual IChangeFrameList* Copy() = 0; // return a copy of itself + + +protected: + // Use Release to delete these. + virtual ~IChangeFrameList() {} +}; + + +// Call to initialize. Pass in the number of properties this CChangeFrameList will hold. +// All properties will be initialized to iCurFrame. +IChangeFrameList* AllocChangeFrameList( int nProperties, int iCurFrame ); + + +#endif // CHANGEFRAMELIST_H diff --git a/configure.py b/configure.py new file mode 100644 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..ddb52c7 --- /dev/null +++ b/extension.cpp @@ -0,0 +1,1126 @@ +/** + * 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 +#include "extension.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "packed_entity.h" +#include +#include +#include + +/** + * @file extension.cpp + * @brief Implement extension code here. + */ + +static Sample g_Sample; /**< Global singleton for extension's main interface */ + +SMEXT_LINK(&g_Sample); + +static IGameConfig *gameconf{nullptr}; + +static std::mutex mux; + +static ISDKTools *g_pSDKTools{nullptr}; +static ISDKHooks *g_pSDKHooks{nullptr}; + +class CFrameSnapshot; +class CClientFrame; + +class CBaseClient : public IGameEventListener2, public IClient, public IClientMessageHandler +{ +}; + +class CGameClient : public CBaseClient +{ +}; + +class CBaseEntity : public IServerEntity +{ +}; + +struct opaque_ptr final +{ + inline opaque_ptr(opaque_ptr &&other) noexcept + { operator=(std::move(other)); } + + template + static void del_hlpr(void *ptr_) noexcept + { delete[] static_cast(ptr_); } + + opaque_ptr() = default; + + template + void emplace(std::size_t num, Args &&...args) noexcept { + if(del_func && ptr) { + del_func(ptr); + } + ptr = static_cast(new T{std::forward(args)...}); + del_func = del_hlpr; + } + + template + T &get(std::size_t element) noexcept + { return static_cast(ptr)[element]; } + template + T *get() noexcept + { return static_cast(ptr); } + + template + const T &get(std::size_t element) const noexcept + { return static_cast(ptr)[element]; } + template + const T *get() const noexcept + { return static_cast(ptr); } + + ~opaque_ptr() noexcept { + if(del_func && ptr) { + del_func(ptr); + } + } + + opaque_ptr &operator=(opaque_ptr &&other) noexcept + { + ptr = other.ptr; + other.ptr = nullptr; + del_func = other.del_func; + other.del_func = nullptr; + return *this; + } + +private: + opaque_ptr(const opaque_ptr &) = delete; + opaque_ptr &operator=(const opaque_ptr &) = delete; + + void *ptr{nullptr}; + void (*del_func)(void *) {nullptr}; +}; + +enum prop_types : unsigned char +{ + int_, + short_, + char_, + unsigned_int, + unsigned_short, + unsigned_char, + float_, + vector, + qangle, + cstring, + unknown, +}; + +static void global_send_proxy(const SendProp *pProp, const void *pStructBase, const void *pData, DVariant *pOut, int iElement, int objectID); + +using tables_t = std::unordered_map; +static tables_t tables; + +struct proxyrestore_t final +{ + inline proxyrestore_t(proxyrestore_t &&other) noexcept + { operator=(std::move(other)); } + + proxyrestore_t(SendProp *pProp_) noexcept + : pProp{pProp_}, pRealProxy{pProp->GetProxyFn()} + { + #ifdef _DEBUG + printf("set %s proxy func\n", pProp->GetName()); + #endif + pProp->SetProxyFn(global_send_proxy); + } + + ~proxyrestore_t() noexcept { + if(pProp && pRealProxy) { + #ifdef _DEBUG + printf("reset %s proxy func\n", pProp->GetName()); + #endif + pProp->SetProxyFn(pRealProxy); + } + } + + proxyrestore_t &operator=(proxyrestore_t &&other) noexcept + { + pProp = other.pProp; + other.pProp = nullptr; + pRealProxy = other.pRealProxy; + other.pRealProxy = nullptr; + return *this; + } + + SendProp *pProp{nullptr}; + SendVarProxyFn pRealProxy{nullptr}; + std::size_t ref{0}; + +private: + proxyrestore_t(const proxyrestore_t &) = delete; + proxyrestore_t &operator=(const proxyrestore_t &) = delete; + proxyrestore_t() = delete; +}; + +static const CStandardSendProxies *std_proxies; + +static prop_types guess_prop_type(const SendProp *pProp, std::size_t &elements) noexcept +{ + elements = 1; + + switch(pProp->GetType()) { + case DPT_Int: { + SendVarProxyFn pRealProxy{pProp->GetProxyFn()}; + if(pProp->GetFlags() & SPROP_UNSIGNED) { + if(pRealProxy == std_proxies->m_UInt8ToInt32) { + return prop_types::unsigned_char; + } else if(pRealProxy == std_proxies->m_UInt16ToInt32) { + return prop_types::unsigned_short; + } else { + return prop_types::unsigned_int; + } + } else { + if(pRealProxy == std_proxies->m_UInt8ToInt32) { + return prop_types::char_; + } else if(pRealProxy == std_proxies->m_UInt16ToInt32) { + return prop_types::short_; + } else { + return prop_types::int_; + } + } + } + case DPT_Float: + { return prop_types::float_; } + case DPT_Vector: { + if(pProp->m_fLowValue == 0.0f && pProp->m_fHighValue == 360.0f) { + return prop_types::qangle; + } else { + return prop_types::vector; + } + } + case DPT_VectorXY: + { return prop_types::vector; } + case DPT_String: + { return prop_types::cstring; } + case DPT_Array: + { return prop_types::unknown; } + case DPT_DataTable: + { return prop_types::unknown; } + } + + return prop_types::unknown; +} + +struct packed_entity_data_t final +{ + packed_entity_data_t(packed_entity_data_t &&) noexcept = default; + packed_entity_data_t &operator=(packed_entity_data_t &&) noexcept = default; + + std::unique_ptr packedData{}; + std::unique_ptr writeBuf{}; + int objectID{-1}; + + packed_entity_data_t() noexcept = default; + ~packed_entity_data_t() noexcept = default; + + void allocate() noexcept { + packedData.reset(static_cast(aligned_alloc(4, MAX_PACKEDENTITY_DATA))); + writeBuf.reset(new bf_write{"SV_PackEntity->writeBuf", packedData.get(), MAX_PACKEDENTITY_DATA}); + } + +private: + packed_entity_data_t(const packed_entity_data_t &) = delete; + packed_entity_data_t &operator=(const packed_entity_data_t &) = delete; +}; + +struct pack_entity_params_t final +{ + std::vector> entity_data{}; + std::vector slots{}; + CBaseClient *client{nullptr}; + int current_slot{-1}; + bool in_compute_packs{false}; + std::vector entities{}; + + pack_entity_params_t(std::vector &&slots_, std::vector &&entities_) noexcept + : slots{std::move(slots_)}, entities{std::move(entities_)} + { + entity_data.resize(slots.size()); + for(auto &it : entity_data) { + it.reserve(entities_.size()); + } + } + ~pack_entity_params_t() noexcept = default; + +private: + pack_entity_params_t(const pack_entity_params_t &) = delete; + pack_entity_params_t &operator=(const pack_entity_params_t &) = delete; + pack_entity_params_t(pack_entity_params_t &&) = delete; + pack_entity_params_t &operator=(pack_entity_params_t &&) = delete; +}; + +static thread_local pack_entity_params_t *current_packentity_params{nullptr}; + +static void Host_Error(const char *error, ...) noexcept +{ + va_list argptr; + char string[1024]; + + va_start(argptr, error); + Q_vsnprintf(string, sizeof(string), error, argptr); + va_end(argptr); + + Error("Host_Error: %s", string); +} + +using restores_t = std::unordered_map>; +static restores_t restores; + +struct table_reference_t +{ + table_reference_t(SendTable *pTable) noexcept + { + tables_t::iterator it_table{tables.find(pTable)}; + if(it_table == tables.end()) { + it_table = tables.emplace(std::pair{pTable, new std::size_t{0}}).first; + } + table_ref = it_table->second; + ++(*table_ref); + } + + virtual ~table_reference_t() noexcept + { + if(table_ref) { + if(--(*table_ref) == 0) { + tables_t::iterator it_table{tables.begin()}; + while(it_table != tables.end()) { + if(it_table->second == table_ref) { + tables.erase(it_table); + break; + } + ++it_table; + } + } + } + } + + inline table_reference_t(table_reference_t &&other) noexcept + { operator=(std::move(other)); } + + table_reference_t &operator=(table_reference_t &&other) noexcept + { + table_ref = other.table_ref; + other.table_ref = nullptr; + return *this; + } + + std::size_t *table_ref{nullptr}; + +private: + table_reference_t(const table_reference_t &) = delete; + table_reference_t &operator=(const table_reference_t &) = delete; + table_reference_t() = delete; +}; + +struct prop_reference_t +{ + prop_reference_t(SendProp *pProp) noexcept + { + restores_t::iterator it_restore{restores.find(pProp)}; + if(it_restore == restores.end()) { + it_restore = restores.emplace(std::pair>{pProp, new proxyrestore_t{pProp}}).first; + } + restore = it_restore->second.get(); + ++restore->ref; + #ifdef _DEBUG + printf("added ref %zu for %s\n", restore->ref, pProp->GetName()); + #endif + } + + virtual ~prop_reference_t() noexcept + { + if(restore) { + #ifdef _DEBUG + printf("removed ref %zu for %s\n", restore->ref-1u, restore->pProp->GetName()); + #endif + if(--restore->ref == 0) { + restores_t::iterator it_restore{restores.begin()}; + while(it_restore != restores.end()) { + if(it_restore->second.get() == restore) { + restores.erase(it_restore); + break; + } + ++it_restore; + } + } + } + } + + inline prop_reference_t(prop_reference_t &&other) noexcept + { operator=(std::move(other)); } + + prop_reference_t &operator=(prop_reference_t &&other) noexcept + { + restore = other.restore; + other.restore = nullptr; + return *this; + } + + proxyrestore_t *restore{nullptr}; + +private: + prop_reference_t(const prop_reference_t &) = delete; + prop_reference_t &operator=(const prop_reference_t &) = delete; + prop_reference_t() = delete; +}; + +struct callback_t final : prop_reference_t +{ + callback_t(SendProp *pProp, prop_types type_, std::size_t elements_, std::size_t offset_) noexcept + : prop_reference_t{pProp}, offset{offset_}, elements{elements_}, type{type_}, prop{pProp} + { + if(type == prop_types::cstring) { + fwd = forwards->CreateForwardEx(nullptr, ET_Hook, 7, nullptr, Param_Cell, Param_String, Param_String, Param_Cell, Param_Cell, Param_Cell); + } else { + ParamType value_param_type; + + switch(type) { + case prop_types::int_: + case prop_types::short_: + case prop_types::char_: + case prop_types::unsigned_int: + case prop_types::unsigned_short: + case prop_types::unsigned_char: { + value_param_type = Param_CellByRef; + } break; + case prop_types::float_: { + value_param_type = Param_FloatByRef; + } break; + case prop_types::vector: + case prop_types::qangle: { + value_param_type = Param_Array; + } break; + } + + fwd = forwards->CreateForwardEx(nullptr, ET_Hook, 6, nullptr, Param_Cell, Param_String, value_param_type, Param_Cell, Param_Cell); + } + } + + ~callback_t() noexcept override final { + if(fwd) { + forwards->ReleaseForward(fwd); + } + } + + template + void fwd_call_int(const SendProp *pProp, const void *pStructBase, const void *pData, DVariant *pOut, int iElement, int objectID) const noexcept + { + fwd->PushCell(objectID); + fwd->PushString(pProp->GetName()); + cell_t sp_value{static_cast(*reinterpret_cast(pData))}; + fwd->PushCellByRef(&sp_value); + fwd->PushCell(iElement); + fwd->PushCell(current_packentity_params->current_slot+1); + cell_t res{Pl_Continue}; + fwd->Execute(&res); + if(res == Pl_Changed) { + const T new_value{static_cast(sp_value)}; + restore->pRealProxy(pProp, pStructBase, static_cast(&new_value), pOut, iElement, objectID); + } else { + restore->pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID); + } + } + + void fwd_call_float(const SendProp *pProp, const void *pStructBase, const void *pData, DVariant *pOut, int iElement, int objectID) const noexcept + { + fwd->PushCell(objectID); + fwd->PushString(pProp->GetName()); + float sp_value{static_cast(*reinterpret_cast(pData))}; + fwd->PushFloatByRef(&sp_value); + fwd->PushCell(iElement); + fwd->PushCell(current_packentity_params->current_slot+1); + cell_t res{Pl_Continue}; + fwd->Execute(&res); + if(res == Pl_Changed) { + restore->pRealProxy(pProp, pStructBase, static_cast(&sp_value), pOut, iElement, objectID); + } else { + restore->pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID); + } + } + + template + void fwd_call_vec(const SendProp *pProp, const void *pStructBase, const void *pData, DVariant *pOut, int iElement, int objectID) const noexcept + { + fwd->PushCell(objectID); + fwd->PushString(pProp->GetName()); + const T &vec{*reinterpret_cast(pData)}; + cell_t sp_value[3]{ + sp_ftoc(vec[0]), + sp_ftoc(vec[1]), + sp_ftoc(vec[2]) + }; + fwd->PushArray(sp_value, 3, SM_PARAM_COPYBACK); + fwd->PushCell(iElement); + fwd->PushCell(current_packentity_params->current_slot+1); + cell_t res{Pl_Continue}; + fwd->Execute(&res); + if(res == Pl_Changed) { + const Vector new_value{ + sp_ctof(sp_value[0]), + sp_ctof(sp_value[1]), + sp_ctof(sp_value[2]), + }; + restore->pRealProxy(pProp, pStructBase, static_cast(&new_value), pOut, iElement, objectID); + } else { + restore->pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID); + } + } + + void fwd_call_str(const SendProp *pProp, const void *pStructBase, const void *pData, DVariant *pOut, int iElement, int objectID) const noexcept + { + fwd->PushCell(objectID); + fwd->PushString(pProp->GetName()); + static char sp_value[4096]; + strcpy(sp_value, reinterpret_cast(pData)); + fwd->PushStringEx(sp_value, sizeof(sp_value), SM_PARAM_STRING_UTF8|SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK); + fwd->PushCell(sizeof(sp_value)); + fwd->PushCell(iElement); + fwd->PushCell(current_packentity_params->current_slot+1); + cell_t res{Pl_Continue}; + fwd->Execute(&res); + if(res == Pl_Changed) { + restore->pRealProxy(pProp, pStructBase, static_cast(sp_value), pOut, iElement, objectID); + } else { + restore->pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID); + } + } + + void proxy_call(const SendProp *pProp, const void *pStructBase, const void *pData, DVariant *pOut, int iElement, int objectID) const noexcept + { + if(!current_packentity_params || current_packentity_params->current_slot == -1) { + restore->pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID); + return; + } + + switch(type) { + case prop_types::int_: { + fwd_call_int(pProp, pStructBase, pData, pOut, iElement, objectID); + } break; + case prop_types::short_: { + fwd_call_int(pProp, pStructBase, pData, pOut, iElement, objectID); + } break; + case prop_types::char_: { + fwd_call_int(pProp, pStructBase, pData, pOut, iElement, objectID); + } break; + case prop_types::unsigned_int: { + fwd_call_int(pProp, pStructBase, pData, pOut, iElement, objectID); + } break; + case prop_types::unsigned_short: { + fwd_call_int(pProp, pStructBase, pData, pOut, iElement, objectID); + } break; + case prop_types::unsigned_char: { + fwd_call_int(pProp, pStructBase, pData, pOut, iElement, objectID); + } break; + case prop_types::float_: { + fwd_call_float(pProp, pStructBase, pData, pOut, iElement, objectID); + } break; + case prop_types::vector: { + fwd_call_vec(pProp, pStructBase, pData, pOut, iElement, objectID); + } break; + case prop_types::qangle: { + fwd_call_vec(pProp, pStructBase, pData, pOut, iElement, objectID); + } break; + case prop_types::cstring: { + fwd_call_str(pProp, pStructBase, pData, pOut, iElement, objectID); + } break; + } + } + + inline callback_t(callback_t &&other) noexcept + : prop_reference_t{std::move(other)} + { operator=(std::move(other)); } + + callback_t &operator=(callback_t &&other) noexcept + { + fwd = other.fwd; + other.fwd = nullptr; + prop = other.prop; + other.prop = nullptr; + offset = other.offset; + elements = other.elements; + type = other.type; + return *this; + } + + IChangeableForward *fwd{nullptr}; + std::size_t offset{-1}; + std::size_t elements{1}; + prop_types type{prop_types::unknown}; + SendProp *prop{nullptr}; + +private: + callback_t(const callback_t &) = delete; + callback_t &operator=(const callback_t &) = delete; + callback_t() = delete; +}; + +using callbacks_t = std::unordered_map; + +struct proxyhook_t final +{ + callbacks_t callbacks; + + inline proxyhook_t(const ServerClass *pServer) noexcept + { + } + + ~proxyhook_t() noexcept = default; + + inline proxyhook_t(proxyhook_t &&other) noexcept + { operator=(std::move(other)); } + + proxyhook_t &operator=(proxyhook_t &&other) noexcept + { + callbacks = std::move(other.callbacks); + return *this; + } + +private: + proxyhook_t(const proxyhook_t &) = delete; + proxyhook_t &operator=(const proxyhook_t &) = delete; + proxyhook_t() = delete; +}; + +using hooks_t = std::unordered_map; +static hooks_t hooks; + +DETOUR_DECL_STATIC6(SendTable_Encode, bool, const SendTable *, pTable, const void *, pStruct, bf_write *, pOut, int, objectID, CUtlMemory *, pRecipients, bool, bNonZeroOnly) +{ +#if 0 + const tables_t &ctables{tables}; + + if(ctables.find(pTable) == ctables.cend()) { + delete current_packentity_params; + current_packentity_params = nullptr; + } +#endif + + if(!current_packentity_params || !current_packentity_params->in_compute_packs) { + return DETOUR_STATIC_CALL(SendTable_Encode)(pTable, pStruct, pOut, objectID, pRecipients, bNonZeroOnly); + } + + { + bool encoded{DETOUR_STATIC_CALL(SendTable_Encode)(pTable, pStruct, pOut, objectID, pRecipients, bNonZeroOnly)}; + if(!encoded) { + Host_Error( "SV_PackEntity: SendTable_Encode returned false (ent %d).\n", objectID ); + return false; + } + } + + const std::vector &entities{current_packentity_params->entities}; + + if(std::find(entities.cbegin(), entities.cend(), objectID) != entities.cend()) { + for(int i{0}; i < current_packentity_params->slots.size(); ++i) { + packed_entity_data_t &packedData{current_packentity_params->entity_data[i].emplace_back()}; + + packedData.objectID = objectID; + packedData.allocate(); + + current_packentity_params->current_slot = current_packentity_params->slots[i]; + bool encoded{DETOUR_STATIC_CALL(SendTable_Encode)(pTable, pStruct, packedData.writeBuf.get(), objectID, pRecipients, bNonZeroOnly)}; + current_packentity_params->current_slot = -1; + if(!encoded) { + Host_Error( "SV_PackEntity: SendTable_Encode returned false (ent %d).\n", objectID ); + return false; + } + } + } + + return true; +} + +DETOUR_DECL_STATIC8(SendTable_CalcDelta, int, const SendTable *, pTable, const void *, pFromState, const int, nFromBits, const void *, pToState, const int, nToBits, int *, pDeltaProps, int, nMaxDeltaProps, const int, objectID) +{ + if(!current_packentity_params || !current_packentity_params->in_compute_packs) { + return DETOUR_STATIC_CALL(SendTable_CalcDelta)(pTable, pFromState, nFromBits, pToState, nToBits, pDeltaProps, nMaxDeltaProps, objectID); + } + + int global_nChanges{DETOUR_STATIC_CALL(SendTable_CalcDelta)(pTable, pFromState, nFromBits, pToState, nToBits, pDeltaProps, nMaxDeltaProps, objectID)}; + + const std::vector &entities{current_packentity_params->entities}; + + if(std::find(entities.cbegin(), entities.cend(), objectID) != entities.cend()) { + if(global_nChanges < nMaxDeltaProps) { + std::unique_ptr client_deltaProps{new int[nMaxDeltaProps]{static_cast(-1)}}; + + for(int i{0}; i < current_packentity_params->slots.size(); ++i) { + packed_entity_data_t &packedData{current_packentity_params->entity_data[i].back()}; + + const int client_nChanges{DETOUR_STATIC_CALL(SendTable_CalcDelta)(pTable, pFromState, nFromBits, packedData.packedData.get(), packedData.writeBuf->GetNumBitsWritten(), client_deltaProps.get(), nMaxDeltaProps, objectID)}; + + for(int j{0}; j < client_nChanges; ++j) { + bool found{false}; + for(int k{0}; k < global_nChanges; ++k) { + if(pDeltaProps[k] == client_deltaProps[j]) { + found = true; + break; + } + } + if(!found) { + pDeltaProps[global_nChanges++] = client_deltaProps[j]; + if(global_nChanges >= nMaxDeltaProps) { + return nMaxDeltaProps; + } + } + } + } + } + } + + return global_nChanges; +} + +DETOUR_DECL_MEMBER2(CFrameSnapshotManager_GetPackedEntity, PackedEntity *, CFrameSnapshot *, pSnapshot, int, entity) +{ + if(!current_packentity_params || !current_packentity_params->client) { + return DETOUR_MEMBER_CALL(CFrameSnapshotManager_GetPackedEntity)(pSnapshot, entity); + } + + PackedEntity *packed{DETOUR_MEMBER_CALL(CFrameSnapshotManager_GetPackedEntity)(pSnapshot, entity)}; + if(!packed) { + return nullptr; + } + + const std::vector &entities{current_packentity_params->entities}; + + if(std::find(entities.cbegin(), entities.cend(), entity) != entities.cend()) { + const int slot{current_packentity_params->client->GetPlayerSlot()}; + + const packed_entity_data_t *packedData{nullptr}; + for(int i{0}; i < current_packentity_params->slots.size(); ++i) { + if(current_packentity_params->slots[i] == slot) { + for(const packed_entity_data_t &it : current_packentity_params->entity_data[i]) { + if(it.objectID == entity) { + packedData = ⁢ + break; + } + } + break; + } + } + + if(packedData) { + packed->AllocAndCopyPadded(packedData->packedData.get(), packedData->writeBuf->GetNumBytesWritten()); + } + } + + return packed; +} + +DETOUR_DECL_MEMBER4(CBaseServer_WriteDeltaEntities, void, CBaseClient *, client, CClientFrame *, to, CClientFrame *, from, bf_write &, pBuf) +{ + if(!current_packentity_params) { + DETOUR_MEMBER_CALL(CBaseServer_WriteDeltaEntities)(client, to, from, pBuf); + return; + } + + current_packentity_params->client = client; + DETOUR_MEMBER_CALL(CBaseServer_WriteDeltaEntities)(client, to, from, pBuf); + current_packentity_params->client = nullptr; +} + +class CFrameSnapshot +{ + DECLARE_FIXEDSIZE_ALLOCATOR( CFrameSnapshot ); + +public: + + CFrameSnapshot(); + ~CFrameSnapshot(); + + // Reference-counting. + void AddReference(); + void ReleaseReference(); + + CFrameSnapshot* NextSnapshot() const; + + +public: + CInterlockedInt m_ListIndex; // Index info CFrameSnapshotManager::m_FrameSnapshots. + + // Associated frame. + int m_nTickCount; // = sv.tickcount + + // State information + class CFrameSnapshotEntry *m_pEntities; + int m_nNumEntities; // = sv.num_edicts + + // This list holds the entities that are in use and that also aren't entities for inactive clients. + unsigned short *m_pValidEntities; + int m_nValidEntities; + + // Additional HLTV info + class CHLTVEntityData *m_pHLTVEntityData; // is NULL if not in HLTV mode or array of m_pValidEntities entries + class CReplayEntityData *m_pReplayEntityData; // is NULL if not in replay mode or array of m_pValidEntities entries + + class CEventInfo **m_pTempEntities; // temp entities + int m_nTempEntities; + + CUtlVector m_iExplicitDeleteSlots; + +private: + + // Snapshots auto-delete themselves when their refcount goes to zero. + CInterlockedInt m_nReferences; +}; + +DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, clientCount, CGameClient **, clients, CFrameSnapshot *, snapshot) +{ + if(current_packentity_params) { + delete current_packentity_params; + current_packentity_params = nullptr; + } + + std::vector slots{}; + + for(int i{0}; i < clientCount; ++i) { + CGameClient *client{clients[i]}; + if(!client->IsConnected() || + !client->IsActive() || + !client->IsSpawned() || + client->IsFakeClient() || + client->IsHLTV() || + client->IsReplay()) { + continue; + } + slots.emplace_back(client->GetPlayerSlot()); + } + + std::vector entities{}; + + const hooks_t &chooks{hooks}; + + entities.reserve(snapshot->m_nValidEntities); + + for(int i{0}; i < snapshot->m_nValidEntities; ++i) { + hooks_t::const_iterator it_hook{chooks.find(snapshot->m_pValidEntities[i])}; + if(it_hook != chooks.cend()) { + edict_t *edict{gamehelpers->EdictOfIndex(snapshot->m_pValidEntities[i])}; + for(const auto &it_callback : it_hook->second.callbacks) { + gamehelpers->SetEdictStateChanged(edict, it_callback.second.offset); + } + entities.emplace_back(snapshot->m_pValidEntities[i]); + } + } + + if(!slots.empty() && !entities.empty()) { + current_packentity_params = new pack_entity_params_t{std::move(slots), std::move(entities)}; + } + + if(current_packentity_params) { + current_packentity_params->in_compute_packs = true; + } + DETOUR_STATIC_CALL(SV_ComputeClientPacks)(clientCount, clients, snapshot); + if(current_packentity_params) { + current_packentity_params->in_compute_packs = false; + } +} + +DETOUR_DECL_MEMBER1(CGameServer_SendClientMessages, void, bool, bSendSnapshots) +{ + DETOUR_MEMBER_CALL(CGameServer_SendClientMessages)(bSendSnapshots); + + if(current_packentity_params) { + delete current_packentity_params; + current_packentity_params = nullptr; + } +} + +static void global_send_proxy(const SendProp *pProp, const void *pStructBase, const void *pData, DVariant *pOut, int iElement, int objectID) +{ + const hooks_t &chooks{hooks}; + hooks_t::const_iterator it_hook{chooks.find(objectID)}; + if(it_hook != chooks.cend()) { + const char *name_ptr{pProp->GetName()}; + const std::string prop{name_ptr}; + callbacks_t::const_iterator it_callback{it_hook->second.callbacks.find(prop)}; + if(it_callback != it_hook->second.callbacks.cend()) { + it_callback->second.proxy_call(pProp, pStructBase, pData, pOut, iElement, objectID); + return; + } + } + + const restores_t &crestores{restores}; + restores_t::const_iterator it_restore{crestores.find(const_cast(pProp))}; + if(it_restore != crestores.cend()) { + it_restore->second->pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID); + return; + } +} + +static void OnGameFrame(bool simulating) noexcept +{ + if(!simulating) { + return; + } + + +} + +static cell_t proxysend_hook(IPluginContext *pContext, const cell_t *params) noexcept +{ + int idx{gamehelpers->ReferenceToBCompatRef(params[1])}; + CBaseEntity *pEntity{gamehelpers->ReferenceToEntity(params[1])}; + if(!pEntity) { + return pContext->ThrowNativeError("Invalid Entity Reference/Index %i", idx); + } + + char *name_ptr; + pContext->LocalToString(params[2], &name_ptr); + std::string name{name_ptr}; + + IServerNetworkable *pNetwork{pEntity->GetNetworkable()}; + ServerClass *pServer{pNetwork->GetServerClass()}; + + sm_sendprop_info_t info{}; + if(!gamehelpers->FindSendPropInfo(pServer->GetName(), name.c_str(), &info)) { + return pContext->ThrowNativeError("Could not find prop %s", name.c_str()); + } + + SendProp *pProp{info.prop}; + std::size_t elements{1}; + prop_types type{guess_prop_type(pProp, elements)}; + if(type == prop_types::unknown) { + return pContext->ThrowNativeError("Unsupported prop"); + } + +#ifdef _DEBUG + printf("added %s hook for %i\n", pProp->GetName(), idx); +#endif + + IPluginFunction *callback{pContext->GetFunctionById(params[3])}; + + hooks_t::iterator it_hook{hooks.find(idx)}; + if(it_hook == hooks.end()) { + it_hook = hooks.emplace(std::pair{idx, proxyhook_t{pServer}}).first; + } + + callbacks_t::iterator it_callback{it_hook->second.callbacks.find(name)}; + if(it_callback == it_hook->second.callbacks.end()) { + it_callback = it_hook->second.callbacks.emplace(std::pair{std::move(name), callback_t{pProp, type, elements, info.actual_offset}}).first; + } + + it_callback->second.fwd->RemoveFunction(callback); + it_callback->second.fwd->AddFunction(callback); + + return 0; +} + +static cell_t proxysend_unhook(IPluginContext *pContext, const cell_t *params) noexcept +{ + int idx{gamehelpers->ReferenceToBCompatRef(params[1])}; + CBaseEntity *pEntity{gamehelpers->ReferenceToEntity(params[1])}; + if(!pEntity) { + return pContext->ThrowNativeError("Invalid Entity Reference/Index %i", idx); + } + + IServerNetworkable *pNetwork{pEntity->GetNetworkable()}; + ServerClass *pServer{pNetwork->GetServerClass()}; + + char *name_ptr; + pContext->LocalToString(params[2], &name_ptr); + const std::string name{name_ptr}; + + IPluginFunction *callback{pContext->GetFunctionById(params[3])}; + + hooks_t::iterator it_hook{hooks.find(idx)}; + if(it_hook != hooks.end()) { + callbacks_t::iterator it_callback{it_hook->second.callbacks.find(name)}; + if(it_callback != it_hook->second.callbacks.end()) { + it_callback->second.fwd->RemoveFunction(callback); + #ifdef _DEBUG + printf("removed %s hook for %i\n", name.c_str(), idx); + #endif + if(it_callback->second.fwd->GetFunctionCount() == 0) { + it_hook->second.callbacks.erase(it_callback); + } + } + if(it_hook->second.callbacks.empty()) { + hooks.erase(it_hook); + } + } + + return 0; +} + +static constexpr const sp_nativeinfo_t natives[]{ + {"proxysend_hook", proxysend_hook}, + {"proxysend_unhook", proxysend_unhook}, + {nullptr, nullptr} +}; + +static CDetour *SendTable_CalcDelta_detour{nullptr}; +static CDetour *SendTable_Encode_detour{nullptr}; +static CDetour *SV_ComputeClientPacks_detour{nullptr}; + +static CDetour *CGameServer_SendClientMessages_detour{nullptr}; +static CDetour *CFrameSnapshotManager_GetPackedEntity_detour{nullptr}; +static CDetour *CBaseServer_WriteDeltaEntities_detour{nullptr}; + +bool Sample::SDK_OnLoad(char *error, size_t maxlen, bool late) noexcept +{ + gameconfs->LoadGameConfigFile("proxysend", &gameconf, nullptr, 0); + + CDetourManager::Init(smutils->GetScriptingEngine(), gameconf); + + SendTable_CalcDelta_detour = DETOUR_CREATE_STATIC(SendTable_CalcDelta, "SendTable_CalcDelta"); + SendTable_CalcDelta_detour->EnableDetour(); + + SendTable_Encode_detour = DETOUR_CREATE_STATIC(SendTable_Encode, "SendTable_Encode"); + SendTable_Encode_detour->EnableDetour(); + + SV_ComputeClientPacks_detour = DETOUR_CREATE_STATIC(SV_ComputeClientPacks, "SV_ComputeClientPacks"); + SV_ComputeClientPacks_detour->EnableDetour(); + + CGameServer_SendClientMessages_detour = DETOUR_CREATE_MEMBER(CGameServer_SendClientMessages, "CGameServer::SendClientMessages"); + CGameServer_SendClientMessages_detour->EnableDetour(); + + CFrameSnapshotManager_GetPackedEntity_detour = DETOUR_CREATE_MEMBER(CFrameSnapshotManager_GetPackedEntity, "CFrameSnapshotManager::GetPackedEntity"); + CFrameSnapshotManager_GetPackedEntity_detour->EnableDetour(); + + CBaseServer_WriteDeltaEntities_detour = DETOUR_CREATE_MEMBER(CBaseServer_WriteDeltaEntities, "CBaseServer::WriteDeltaEntities"); + CBaseServer_WriteDeltaEntities_detour->EnableDetour(); + + sharesys->AddDependency(myself, "sdktools.ext", true, true); + sharesys->AddDependency(myself, "sdkhooks.ext", true, true); + + sharesys->RegisterLibrary(myself, "proxysend"); + + smutils->AddGameFrameHook(OnGameFrame); + plsys->AddPluginsListener(this); + + sharesys->AddNatives(myself, natives); + + return true; +} + +bool Sample::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlen, bool late) noexcept +{ + GET_V_IFACE_ANY(GetEngineFactory, g_pCVar, ICvar, CVAR_INTERFACE_VERSION); + + std_proxies = gamedll->GetStandardSendProxies(); + + //TODO!!! make thread-safe code + ConVar *sv_parallel_packentities{g_pCVar->FindVar("sv_parallel_packentities")}; + sv_parallel_packentities->SetValue(false); + + ConVar *sv_parallel_sendsnapshot{g_pCVar->FindVar("sv_parallel_sendsnapshot")}; + sv_parallel_sendsnapshot->SetValue(false); + + return true; +} + +void Sample::OnCoreMapEnd() noexcept +{ + tables.clear(); + restores.clear(); + hooks.clear(); +} + +void Sample::SDK_OnUnload() noexcept +{ + SendTable_CalcDelta_detour->Destroy(); + SendTable_Encode_detour->Destroy(); + SV_ComputeClientPacks_detour->Destroy(); + CGameServer_SendClientMessages_detour->Destroy(); + CFrameSnapshotManager_GetPackedEntity_detour->Destroy(); + CBaseServer_WriteDeltaEntities_detour->Destroy(); + + tables.clear(); + restores.clear(); + hooks.clear(); + + gameconfs->CloseGameConfigFile(gameconf); + + smutils->RemoveGameFrameHook(OnGameFrame); + plsys->RemovePluginsListener(this); + g_pSDKHooks->RemoveEntityListener(this); +} + +void Sample::OnEntityDestroyed(CBaseEntity *pEntity) noexcept +{ + if(!pEntity) { + return; + } + + const int idx{gamehelpers->EntityToBCompatRef(pEntity)}; + + hooks_t::iterator it_hook{hooks.find(idx)}; + if(it_hook != hooks.end()) { + hooks.erase(it_hook); + } +} + +void Sample::OnPluginUnloaded(IPlugin *plugin) noexcept +{ + hooks_t::iterator it_hook{hooks.begin()}; + while(it_hook != hooks.end()) { + callbacks_t::iterator it_callback{it_hook->second.callbacks.begin()}; + while(it_callback != it_hook->second.callbacks.end()) { + it_callback->second.fwd->RemoveFunctionsOfPlugin(plugin); + if(it_callback->second.fwd->GetFunctionCount() == 0) { + it_callback = it_hook->second.callbacks.erase(it_callback); + continue; + } + ++it_callback; + } + if(it_hook->second.callbacks.empty()) { + it_hook = hooks.erase(it_hook); + continue; + } + ++it_hook; + } +} + +void Sample::SDK_OnAllLoaded() noexcept +{ + SM_GET_LATE_IFACE(SDKTOOLS, g_pSDKTools); + SM_GET_LATE_IFACE(SDKHOOKS, g_pSDKHooks); + + g_pSDKHooks->AddEntityListener(this); +} \ No newline at end of file diff --git a/extension.h b/extension.h new file mode 100644 index 0000000..acd485d --- /dev/null +++ b/extension.h @@ -0,0 +1,124 @@ +/** + * 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 +#include + +/** + * @brief Sample implementation of the SDK Extension. + * Note: Uncomment one of the pre-defined virtual functions in order to use it. + */ +class Sample final : public SDKExtension, public IPluginsListener, public ISMEntityListener +{ +public: + virtual void OnCoreMapEnd() noexcept override final; + virtual void OnPluginUnloaded(IPlugin *plugin) noexcept override final; + virtual void OnEntityDestroyed(CBaseEntity *pEntity) noexcept override final; + + /** + * @brief This is called after the initial loading sequence has been processed. + * + * @param error Error message buffer. + * @param maxlen Size of error message buffer. + * @param late Whether or not the module was loaded after map load. + * @return True to succeed loading, false to fail. + */ + virtual bool SDK_OnLoad(char *error, size_t maxlen, bool late) noexcept override final; + + /** + * @brief This is called right before the extension is unloaded. + */ + virtual void SDK_OnUnload() noexcept override final; + + /** + * @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() noexcept override final; + + /** + * @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 maxlen Size of error message buffer. + * @return True if working, false otherwise. + */ + //virtual bool QueryRunning(char *error, size_t maxlen); +public: +#if defined SMEXT_CONF_METAMOD + /** + * @brief Called when Metamod is attached, before the extension version is called. + * + * @param error Error buffer. + * @param maxlen Maximum size of error buffer. + * @param late Whether or not Metamod considers this a late load. + * @return True to succeed, false to fail. + */ + virtual bool SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlen, bool late) noexcept override final; + + /** + * @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 maxlen Maximum size of error buffer. + * @return True to succeed, false to fail. + */ + //virtual bool SDK_OnMetamodUnload(char *error, size_t maxlen); + + /** + * @brief Called when Metamod's pause state is changing. + * NOTE: By default this is blocked unless sent from SourceMod. + * + * @param paused Pause state being set. + * @param error Error buffer. + * @param maxlen Maximum size of error buffer. + * @return True to succeed, false to fail. + */ + //virtual bool SDK_OnMetamodPauseChange(bool paused, char *error, size_t maxlen); +#endif +}; + +#endif // _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ diff --git a/packed_entity.cpp b/packed_entity.cpp new file mode 100644 index 0000000..adf502f --- /dev/null +++ b/packed_entity.cpp @@ -0,0 +1,117 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include +#include +#include +#include "packed_entity.h" +#include "basetypes.h" +#include "changeframelist.h" +#include "dt_send.h" +//#include "dt_send_eng.h" +#include "server_class.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +// -------------------------------------------------------------------------------------------------- // +// PackedEntity. +// -------------------------------------------------------------------------------------------------- // + +PackedEntity::PackedEntity() +{ + m_pData = NULL; + m_pChangeFrameList = NULL; + m_nSnapshotCreationTick = 0; + m_nShouldCheckCreationTick = 0; +} + +PackedEntity::~PackedEntity() +{ + FreeData(); + + if ( m_pChangeFrameList ) + { + m_pChangeFrameList->Release(); + m_pChangeFrameList = NULL; + } +} + + +bool PackedEntity::AllocAndCopyPadded( const void *pData, unsigned long size ) +{ + FreeData(); + + unsigned long nBytes = PAD_NUMBER( size, 4 ); + + // allocate the memory + m_pData = malloc( nBytes ); + + if ( !m_pData ) + { + Assert( m_pData ); + return false; + } + + Q_memcpy( m_pData, pData, size ); + SetNumBits( nBytes * 8 ); + + return true; +} + + +int PackedEntity::GetPropsChangedAfterTick( int iTick, int *iOutProps, int nMaxOutProps ) +{ + if ( m_pChangeFrameList ) + { + return m_pChangeFrameList->GetPropsChangedAfterTick( iTick, iOutProps, nMaxOutProps ); + } + else + { + // signal that we don't have a changelist + return -1; + } +} + + +const CSendProxyRecipients* PackedEntity::GetRecipients() const +{ + return m_Recipients.Base(); +} + + +int PackedEntity::GetNumRecipients() const +{ + return m_Recipients.Count(); +} + + +void PackedEntity::SetRecipients( const CUtlMemory &recipients ) +{ + m_Recipients.CopyArray( recipients.Base(), recipients.Count() ); +} + + +bool PackedEntity::CompareRecipients( const CUtlMemory &recipients ) +{ + if ( recipients.Count() != m_Recipients.Count() ) + return false; + + return memcmp( recipients.Base(), m_Recipients.Base(), sizeof( CSendProxyRecipients ) * m_Recipients.Count() ) == 0; +} + +void PackedEntity::SetServerAndClientClass( ServerClass *pServerClass, ClientClass *pClientClass ) +{ + m_pServerClass = pServerClass; + m_pClientClass = pClientClass; + if ( pServerClass ) + { + Assert( pServerClass->m_pTable ); + SetShouldCheckCreationTick( pServerClass->m_pTable->HasPropsEncodedAgainstTickCount() ); + } +} \ No newline at end of file diff --git a/packed_entity.h b/packed_entity.h new file mode 100644 index 0000000..129c23d --- /dev/null +++ b/packed_entity.h @@ -0,0 +1,212 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#if !defined( PACKED_ENTITY_H ) +#define PACKED_ENTITY_H +#ifdef _WIN32 +#pragma once +#endif + +#include +#include +#include +#include +#include + +//#include "common.h" + +// Matched with the memdbgoff at end of header +#include "memdbgon.h" + +// This is extra spew to the files cltrace.txt + svtrace.txt +// #define DEBUG_NETWORKING 1 + +#if defined( DEBUG_NETWORKING ) +#include "convar.h" +void SpewToFile( PRINTF_FORMAT_STRING char const* pFmt, ... ); +extern ConVar sv_packettrace; +#define TRACE_PACKET( text ) if ( sv_packettrace.GetInt() ) { SpewToFile text ; }; +#else +#define TRACE_PACKET( text ) +#endif + +enum +{ + ENTITY_SENTINEL = 9999 // larger number than any real entity number +}; + +#define FLAG_IS_COMPRESSED (1<<31) + + +class CSendProxyRecipients; +class SendTable; +class RecvTable; +class ServerClass; +class ClientClass; +class IChangeFrameList; + + + +// Replaces entity_state_t. +// This is what we send to clients. + +class PackedEntity +{ +public: + + PackedEntity(); + ~PackedEntity(); + + void SetNumBits( int nBits ); + int GetNumBits() const; + int GetNumBytes() const; + void SetCompressed(); + bool IsCompressed() const; + + // Access the data in the entity. + void* GetData(); + void FreeData(); + + // Copy the data into the PackedEntity's data and make sure the # bytes allocated is + // an integer multiple of 4. + bool AllocAndCopyPadded( const void *pData, unsigned long size ); + + // These are like Get/Set, except SnagChangeFrameList clears out the + // PackedEntity's pointer since the usage model in sv_main is to keep + // the same CChangeFrameList in the most recent PackedEntity for the + // lifetime of an edict. + // + // When the PackedEntity is deleted, it deletes its current CChangeFrameList if it exists. + void SetChangeFrameList( IChangeFrameList *pList ); + IChangeFrameList* GetChangeFrameList(); + IChangeFrameList* SnagChangeFrameList(); + + // If this PackedEntity has a ChangeFrameList, then this calls through. If not, it returns all props + int GetPropsChangedAfterTick( int iTick, int *iOutProps, int nMaxOutProps ); + + // Access the recipients array. + const CSendProxyRecipients* GetRecipients() const; + int GetNumRecipients() const; + + void SetRecipients( const CUtlMemory &recipients ); + bool CompareRecipients( const CUtlMemory &recipients ); + + void SetSnapshotCreationTick( int nTick ); + int GetSnapshotCreationTick() const; + + void SetShouldCheckCreationTick( bool bState ); + bool ShouldCheckCreationTick() const; + + void SetServerAndClientClass( ServerClass *pServerClass, ClientClass *pClientClass ); + +public: + + ServerClass *m_pServerClass; // Valid on the server + ClientClass *m_pClientClass; // Valid on the client + + int m_nEntityIndex; // Entity index. + int m_ReferenceCount; // reference count; + +private: + + CUtlVector m_Recipients; + + void *m_pData; // Packed data. + int m_nBits; // Number of bits used to encode. + IChangeFrameList *m_pChangeFrameList; // Only the most current + + // This is the tick this PackedEntity was created on + unsigned int m_nSnapshotCreationTick : 31; + unsigned int m_nShouldCheckCreationTick : 1; +}; + + +inline void PackedEntity::SetNumBits( int nBits ) +{ + Assert( !( nBits & 31 ) ); + m_nBits = nBits; +} + +inline void PackedEntity::SetCompressed() +{ + m_nBits |= FLAG_IS_COMPRESSED; +} + +inline bool PackedEntity::IsCompressed() const +{ + return (m_nBits & FLAG_IS_COMPRESSED) != 0; +} + +inline int PackedEntity::GetNumBits() const +{ + Assert( !( m_nBits & 31 ) ); + return m_nBits & ~(FLAG_IS_COMPRESSED); +} + +inline int PackedEntity::GetNumBytes() const +{ + return 0; + //return Bits2Bytes( m_nBits ); +} + +inline void* PackedEntity::GetData() +{ + return m_pData; +} + +inline void PackedEntity::FreeData() +{ + if ( m_pData ) + { + free(m_pData); + m_pData = NULL; + } +} + +inline void PackedEntity::SetChangeFrameList( IChangeFrameList *pList ) +{ + Assert( !m_pChangeFrameList ); + m_pChangeFrameList = pList; +} + +inline IChangeFrameList* PackedEntity::GetChangeFrameList() +{ + return m_pChangeFrameList; +} + +inline IChangeFrameList* PackedEntity::SnagChangeFrameList() +{ + IChangeFrameList *pRet = m_pChangeFrameList; + m_pChangeFrameList = NULL; + return pRet; +} + +inline void PackedEntity::SetSnapshotCreationTick( int nTick ) +{ + m_nSnapshotCreationTick = (unsigned int)nTick; +} + +inline int PackedEntity::GetSnapshotCreationTick() const +{ + return (int)m_nSnapshotCreationTick; +} + +inline void PackedEntity::SetShouldCheckCreationTick( bool bState ) +{ + m_nShouldCheckCreationTick = bState ? 1 : 0; +} + +inline bool PackedEntity::ShouldCheckCreationTick() const +{ + return m_nShouldCheckCreationTick == 1 ? true : false; +} + +#include "memdbgoff.h" + +#endif // PACKED_ENTITY_H + diff --git a/smsdk_config.h b/smsdk_config.h new file mode 100644 index 0000000..3b6ba51 --- /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 "proxysend" +#define SMEXT_CONF_DESCRIPTION "proxysend" +#define SMEXT_CONF_VERSION "0.0.0.0" +#define SMEXT_CONF_AUTHOR "Arthurdead" +#define SMEXT_CONF_URL "http://www.sourcemod.net/" +#define SMEXT_CONF_LOGTAG "PROXYSEND" +#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_ diff --git a/sourcemod/gamedata/proxysend.txt b/sourcemod/gamedata/proxysend.txt new file mode 100644 index 0000000..1fda5e3 --- /dev/null +++ b/sourcemod/gamedata/proxysend.txt @@ -0,0 +1,39 @@ +"Games" +{ + "tf" + { + "Signatures" + { + "SendTable_CalcDelta" + { + "library" "engine" + "linux" "@_Z19SendTable_CalcDeltaPK9SendTablePKviS3_iPiii" + } + "SendTable_Encode" + { + "library" "engine" + "linux" "@_Z16SendTable_EncodePK9SendTablePKvP8bf_writeiP10CUtlMemoryI20CSendProxyRecipientsiEb" + } + "SV_ComputeClientPacks" + { + "library" "engine" + "linux" "@_Z21SV_ComputeClientPacksiPP11CGameClientP14CFrameSnapshot" + } + "CGameServer::SendClientMessages" + { + "library" "engine" + "linux" "@_ZN11CGameServer18SendClientMessagesEb" + } + "CFrameSnapshotManager::GetPackedEntity" + { + "library" "engine" + "linux" "@_ZN21CFrameSnapshotManager15GetPackedEntityEP14CFrameSnapshoti" + } + "CBaseServer::WriteDeltaEntities" + { + "library" "engine" + "linux" "@_ZN11CBaseServer18WriteDeltaEntitiesEP11CBaseClientP12CClientFrameS3_R8bf_write" + } + } + } +} \ No newline at end of file diff --git a/sourcemod/scripting/include/proxysend.inc b/sourcemod/scripting/include/proxysend.inc new file mode 100644 index 0000000..7ce1a1e --- /dev/null +++ b/sourcemod/scripting/include/proxysend.inc @@ -0,0 +1,38 @@ +#if defined __PROXYSEND_INC + #endinput +#endif +#define __PROXYSEND_INC + +typeset proxysend_callbacks +{ + function Action (int entity, const char[] prop, int &value, int element, int client); + function Action (int entity, const char[] prop, float &value, int element, int client); + function Action (int entity, const char[] prop, char[] value, int size, int element, int client); + function Action (int entity, const char[] prop, float value[3], int element, int client); +}; + +native void proxysend_hook(int entity, const char[] prop, proxysend_callbacks callback); +native void proxysend_unhook(int entity, const char[] prop, proxysend_callbacks callback); + +#if !defined REQUIRE_EXTENSIONS +public __ext_proxysend_SetNTVOptional() +{ + +} +#endif + +public Extension __ext_proxysend = +{ + name = "proxysend", + file = "proxysend.ext", +#if defined AUTOLOAD_EXTENSIONS + autoload = 1, +#else + autoload = 0, +#endif +#if defined REQUIRE_EXTENSIONS + required = 1, +#else + required = 0, +#endif +}; \ No newline at end of file