From d306783285ccf355e37d791e69aff307584ab1ff Mon Sep 17 00:00:00 2001 From: arthurdead Date: Fri, 29 Apr 2022 01:23:17 -0300 Subject: [PATCH] init commit --- .gitignore | 1 + AMBuildScript | 449 ++++++++ AMBuilder | 40 + Makefile | 233 +++++ PackageScript | 52 + changeframelist.h | 56 + configure.py | 23 + extension.cpp | 1126 +++++++++++++++++++++ extension.h | 124 +++ packed_entity.cpp | 117 +++ packed_entity.h | 212 ++++ smsdk_config.h | 81 ++ sourcemod/gamedata/proxysend.txt | 39 + sourcemod/scripting/include/proxysend.inc | 38 + 14 files changed, 2591 insertions(+) create mode 100644 .gitignore create mode 100644 AMBuildScript create mode 100644 AMBuilder create mode 100644 Makefile create mode 100644 PackageScript create mode 100644 changeframelist.h create mode 100644 configure.py create mode 100644 extension.cpp create mode 100644 extension.h create mode 100644 packed_entity.cpp create mode 100644 packed_entity.h create mode 100644 smsdk_config.h create mode 100644 sourcemod/gamedata/proxysend.txt create mode 100644 sourcemod/scripting/include/proxysend.inc 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