From 7b493af742d53aa1c2516621f426f27652108a42 Mon Sep 17 00:00:00 2001 From: arthurdead Date: Sat, 16 Sep 2023 17:30:33 -0300 Subject: [PATCH] css --- PackageScript | 2 +- extension.cpp | 4796 ++++++++--------- sourcemod/gamedata/proxysend.txt | 160 +- sourcemod/scripting/include/proxysend.inc | 59 +- sourcemod/scripting/include/proxysend_tf2.inc | 63 + 5 files changed, 2546 insertions(+), 2534 deletions(-) create mode 100644 sourcemod/scripting/include/proxysend_tf2.inc diff --git a/PackageScript b/PackageScript index b30ab78..52ece56 100644 --- a/PackageScript +++ b/PackageScript @@ -30,7 +30,7 @@ def CopyFiles(src, dest, files): # Include files CopyFiles('sourcemod/scripting/include', 'addons/sourcemod/scripting/include', - [ 'proxysend.inc', ] + [ 'proxysend.inc', 'proxysend_tf2.inc' ] ) # GameData files diff --git a/extension.cpp b/extension.cpp index 3d98f43..e5ba7ca 100644 --- a/extension.cpp +++ b/extension.cpp @@ -1,2398 +1,2398 @@ -/** - * 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 -#include "packed_entity.h" -#include -#include -#include -#include -#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 ISDKHooks *g_pSDKHooks{nullptr}; -ISDKTools *g_pSDKTools = nullptr; - -IServer *server = nullptr; -CBaseEntityList *g_pEntityList = nullptr; - -static void *CGameClient_GetSendFrame_ptr{nullptr}; - -static int CBaseClient_UpdateSendState_idx{-1}; -static int CBaseClient_SendSnapshot_idx{-1}; - -template -int vfunc_index(T func) -{ - SourceHook::MemFuncInfo info{}; - SourceHook::GetFuncInfo(func, info); - return info.vtblindex; -} - -template -R call_mfunc(T *pThisPtr, void *offset, Args ...args) -{ - class VEmptyClass {}; - - void **this_ptr = *reinterpret_cast(&pThisPtr); - - union - { - R (VEmptyClass::*mfpnew)(Args...); -#ifndef PLATFORM_POSIX - void *addr; - } u; - u.addr = offset; -#else - struct - { - void *addr; - intptr_t adjustor; - } s; - } u; - u.s.addr = offset; - u.s.adjustor = 0; -#endif - - return (R)(reinterpret_cast(this_ptr)->*u.mfpnew)(args...); -} - -template -R call_vfunc(T *pThisPtr, size_t offset, Args ...args) -{ - void **vtable = *reinterpret_cast(pThisPtr); - void *vfunc = vtable[offset]; - - return call_mfunc(pThisPtr, vfunc, args...); -} - -class CFrameSnapshot; -class CClientFrame; - -class CBaseClient : public IGameEventListener2, public IClient, public IClientMessageHandler -{ -}; - -class CGameClient : public CBaseClient -{ -public: - inline void SendSnapshot(CClientFrame *pFrame) - { call_vfunc(this, CBaseClient_SendSnapshot_idx, pFrame); } - - inline void UpdateSendState() - { call_vfunc(this, CBaseClient_UpdateSendState_idx); } - - inline CClientFrame *GetSendFrame() - { return call_mfunc(this, CGameClient_GetSendFrame_ptr); } -}; - -class CBaseEntity : public IServerEntity -{ -}; - -using prop_types = proxysend::prop_types; - -static void global_send_proxy(const SendProp *pProp, const void *pStructBase, const void *pData, DVariant *pOut, int iElement, int objectID); - -static const CStandardSendProxies *std_proxies; - -#if SOURCE_ENGINE == SE_TF2 -static const SendProp *m_nPlayerCond{nullptr}; -static const SendProp *_condition_bits{nullptr}; -static const SendProp *m_nPlayerCondEx{nullptr}; -static const SendProp *m_nPlayerCondEx2{nullptr}; -static const SendProp *m_nPlayerCondEx3{nullptr}; -static const SendProp *m_nPlayerCondEx4{nullptr}; - -static bool is_prop_cond(const SendProp *pProp) -{ - return (pProp == m_nPlayerCond || - pProp == _condition_bits || - pProp == m_nPlayerCondEx || - pProp == m_nPlayerCondEx2 || - pProp == m_nPlayerCondEx3 || - pProp == m_nPlayerCondEx4); -} -#endif - -struct proxyrestore_t final -{ - inline proxyrestore_t(proxyrestore_t &&other) noexcept - { operator=(std::move(other)); } - - proxyrestore_t(SendProp *pProp_, prop_types type_) noexcept - : pProp{pProp_}, pRealProxy{pProp->GetProxyFn()}, type{type_} - { - #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; - type = other.type; - return *this; - } - - SendProp *pProp{nullptr}; - SendVarProxyFn pRealProxy{nullptr}; - std::size_t ref{0}; - prop_types type{prop_types::unknown}; - -private: - proxyrestore_t(const proxyrestore_t &) = delete; - proxyrestore_t &operator=(const proxyrestore_t &) = delete; - proxyrestore_t() = delete; -}; - -using restores_t = std::unordered_map>; -static restores_t restores; - -static SendVarProxyFn SendProxy_StringT_To_String_ptr{nullptr}; -static SendVarProxyFn SendProxy_Color32ToInt_ptr{nullptr}; -static SendVarProxyFn SendProxy_EHandleToInt_ptr{nullptr}; - -static prop_types guess_prop_type(const SendProp *pProp, const SendTable *pTable) noexcept -{ -#if defined _DEBUG - printf("%s type is ", pProp->GetName()); -#endif - - SendVarProxyFn pRealProxy{pProp->GetProxyFn()}; - if(pRealProxy == global_send_proxy) { - restores_t::const_iterator it_restore{restores.find(const_cast(pProp))}; - if(it_restore == restores.cend()) { - #if defined _DEBUG - printf("invalid (global send proxy)\n"); - #endif - return prop_types::unknown; - } - - #if defined _DEBUG - printf("from restore (global send proxy)\n"); - #endif - return it_restore->second->type; - } - -#if SOURCE_ENGINE == SE_TF2 - if(is_prop_cond(pProp)) { - #if defined _DEBUG - printf("unsigned int (is cond)\n"); - #endif - return prop_types::unsigned_int; - } -#endif - - switch(pProp->GetType()) { - case DPT_Int: { - if(pProp->GetFlags() & SPROP_UNSIGNED) { - if(pRealProxy == std_proxies->m_UInt8ToInt32) { - if(pProp->m_nBits == 1) { - #if defined _DEBUG - printf("bool (bits == 1)\n"); - #endif - return prop_types::bool_; - } - - #if defined _DEBUG - printf("unsigned char (std proxy)\n"); - #endif - return prop_types::unsigned_char; - } else if(pRealProxy == std_proxies->m_UInt16ToInt32) { - #if defined _DEBUG - printf("unsigned short (std proxy)\n"); - #endif - return prop_types::unsigned_short; - } else if(pRealProxy == std_proxies->m_UInt32ToInt32) { - if(pTable && strcmp(pTable->GetName(), "DT_BaseEntity") == 0 && strcmp(pProp->GetName(), "m_clrRender") == 0) { - #if defined _DEBUG - printf("color32 (hardcode)\n"); - #endif - return prop_types::color32_; - } - - #if defined _DEBUG - printf("unsigned int (std proxy)\n"); - #endif - return prop_types::unsigned_int; - } else { - if(SendProxy_Color32ToInt_ptr && pRealProxy == SendProxy_Color32ToInt_ptr) { - return prop_types::color32_; - } else if(SendProxy_EHandleToInt_ptr && pRealProxy == SendProxy_EHandleToInt_ptr) { - return prop_types::ehandle; - } - - { - if(pProp->m_nBits == 32) { - struct dummy_t { - unsigned int val{256}; - } dummy; - - DVariant out{}; - pRealProxy(pProp, static_cast(&dummy), static_cast(&dummy.val), &out, 0, -1); - if(out.m_Int == 65536) { - #if defined _DEBUG - printf("color32 (proxy)\n"); - #endif - return prop_types::color32_; - } - } - } - - { - if(pProp->m_nBits == NUM_NETWORKED_EHANDLE_BITS) { - struct dummy_t { - EHANDLE val{}; - } dummy; - - DVariant out{}; - pRealProxy(pProp, static_cast(&dummy), static_cast(&dummy.val), &out, 0, -1); - if(out.m_Int == INVALID_NETWORKED_EHANDLE_VALUE) { - #if defined _DEBUG - printf("ehandle (proxy)\n"); - #endif - return prop_types::ehandle; - } - } - } - - #if defined _DEBUG - printf("unsigned int (flag)\n"); - #endif - return prop_types::unsigned_int; - } - } else { - if(pRealProxy == std_proxies->m_Int8ToInt32) { - #if defined _DEBUG - printf("char (std proxy)\n"); - #endif - return prop_types::char_; - } else if(pRealProxy == std_proxies->m_Int16ToInt32) { - #if defined _DEBUG - printf("short (std proxy)\n"); - #endif - return prop_types::short_; - } else if(pRealProxy == std_proxies->m_Int32ToInt32) { - #if defined _DEBUG - printf("int (std proxy)\n"); - #endif - return prop_types::int_; - } else { - { - struct dummy_t { - short val{SHRT_MAX-1}; - } dummy; - - DVariant out{}; - pRealProxy(pProp, static_cast(&dummy), static_cast(&dummy.val), &out, 0, -1); - if(out.m_Int == dummy.val+1) { - #if defined _DEBUG - printf("short (proxy)\n"); - #endif - return prop_types::short_; - } - } - - #if defined _DEBUG - printf("int (type)\n"); - #endif - 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: { - if(SendProxy_StringT_To_String_ptr && pRealProxy == SendProxy_StringT_To_String_ptr) { - return prop_types::tstring; - } else { - return prop_types::cstring; - } - } - case DPT_Array: - return prop_types::unknown; - case DPT_DataTable: - return prop_types::unknown; - } - - return prop_types::unknown; -} - -proxysend::prop_types Sample::guess_prop_type(const SendProp *prop, const SendTable *table) const noexcept -{ - return ::guess_prop_type(prop, table); -} - -template -class thread_var_base -{ -protected: - using ptr_ret_t = std::conditional_t::value, T, T *>; - -public: - inline void reset(std::nullptr_t) noexcept - { reset_ptr(nullptr); } - - thread_var_base &operator=(std::nullptr_t) noexcept - { - reset_ptr(nullptr); - return *this; - } - - inline thread_var_base(std::nullptr_t) noexcept - : thread_var_base{} - { - } - - inline thread_var_base() noexcept - { - } - - inline bool operator!() const noexcept; - - inline ptr_ret_t operator->() noexcept - { return get_ptr(); } - - inline ~thread_var_base() noexcept - { - if(allocated_) { - unallocate(); - } - } - -protected: - static constexpr const pthread_key_t invalid_key{PTHREAD_KEYS_MAX+1}; - - pthread_key_t key{invalid_key}; - bool allocated_{false}; - - static void dtor(void *ptr) noexcept - { delete reinterpret_cast(ptr); } - - T *get_ptr_raw() const noexcept - { - if(!allocated_) { - return nullptr; - } - return reinterpret_cast(pthread_getspecific(key)); - } - - ptr_ret_t get_ptr() const noexcept - { - T *ptr{get_ptr_raw()}; - if(!ptr) { - return nullptr; - } - #if 0 - if constexpr(std::is_pointer_v) { - return *ptr; - } else - #endif - { - return ptr; - } - } - - T *get_or_allocate_ptr() noexcept - { - if(!allocated_ && !allocate()) { - return nullptr; - } - T *ptr{reinterpret_cast(pthread_getspecific(key))}; - if(!ptr) { - ptr = new T{}; - pthread_setspecific(key, ptr); - } - return ptr; - } - - bool allocate() noexcept - { - if(!__sync_bool_compare_and_swap(&allocated_, 0, 1)) { - return true; - } - if(pthread_key_create(&key, dtor) != 0) { - return false; - } - pthread_setspecific(key, new T{}); - return true; - } - - bool unallocate() noexcept - { - if(!__sync_bool_compare_and_swap(&allocated_, 1, 0)) { - return true; - } - T *old_ptr{reinterpret_cast(pthread_getspecific(key))}; - if(old_ptr) { - delete old_ptr; - } - return (pthread_key_delete(key) == 0); - } - - void reset_ptr(T *ptr) noexcept - { - T *old_ptr{get_ptr_raw()}; - if(old_ptr) { - delete old_ptr; - } - if(!allocated_ && !allocate()) { - return; - } - pthread_setspecific(key, ptr); - } - -private: - thread_var_base(const thread_var_base &) = delete; - thread_var_base &operator=(const thread_var_base &) = delete; - thread_var_base(thread_var_base &&) = delete; - thread_var_base &operator=(thread_var_base &&) = delete; -}; - -template <> -inline bool thread_var_base::operator!() const noexcept -{ - const bool *ptr{get_ptr_raw()}; - return (!ptr || !*ptr); -} - -template -inline bool thread_var_base::operator!() const noexcept -{ return get_ptr() == nullptr; } - -template -class thread_var final : public thread_var_base -{ -public: - using thread_var_base::thread_var_base; - using thread_var_base::reset; - - template - T &reset(Args &&...args) noexcept - { - T *ptr{this->get_or_allocate_ptr()}; - ptr->~T(); - new (ptr) T{std::forward(args)...}; - return *ptr; - } - - inline thread_var() noexcept - : thread_var_base{} - { - } - - inline thread_var(const T &val) noexcept - { *this->get_or_allocate_ptr() = val; } - - inline thread_var(T &&val) noexcept - { *this->get_or_allocate_ptr() = std::move(val); } - - template - inline thread_var(Args &&...args) noexcept - { - T *ptr{this->get_or_allocate_ptr()}; - ptr->~T(); - new (ptr) T{std::forward(args)...}; - } - - thread_var &operator=(std::nullptr_t) noexcept - { - this->reset_ptr(nullptr); - return *this; - } - - thread_var &operator=(const T &val) noexcept - { - *this->get_or_allocate_ptr() = val; - return *this; - } - - thread_var &operator=(T &&val) noexcept - { - *this->get_or_allocate_ptr() = std::move(val); - return *this; - } - - inline T &operator*() noexcept - { return *this->get_ptr_raw(); } - - inline T &get() noexcept - { return *this->get_ptr_raw(); } - - inline operator T &() noexcept - { return *this->get_ptr_raw(); } - - inline explicit operator bool() const noexcept - { return this->get_ptr() != nullptr; } -}; - -template <> -class thread_var final : public thread_var_base -{ -public: - using thread_var_base::thread_var_base; - using thread_var_base::reset; - - inline thread_var() - : thread_var_base{} - { - } - - inline bool operator*() const noexcept - { return get(); } - - inline bool get() const noexcept - { - const bool *ptr{get_ptr_raw()}; - return (ptr && *ptr); - } - - inline operator bool() const noexcept - { return get(); } - - bool reset(bool val) noexcept - { - set_value(val); - return val; - } - - thread_var &operator=(std::nullptr_t) noexcept - { - set_value(false); - return *this; - } - - inline thread_var(bool val) noexcept - { set_value(val); } - - thread_var &operator=(bool val) noexcept - { - set_value(val); - return *this; - } - -private: - void set_value(bool val) noexcept - { - *this->get_or_allocate_ptr() = val; - } -}; - -CBaseEntity *ReferenceToEntity(unsigned long ref) -{ - union { - cell_t sp_val; - unsigned long e_val; - } u; - - u.e_val = ref; - - return gamehelpers->ReferenceToEntity(u.sp_val); -} - -unsigned long EntityToReference(CBaseEntity *ent) -{ - union { - cell_t sp_val; - unsigned long e_val; - } u; - - u.sp_val = gamehelpers->EntityToReference(ent); - - return u.e_val; -} - -unsigned long IndexToReference(int objectID) -{ - union { - cell_t sp_val; - unsigned long e_val; - } u; - - u.sp_val = gamehelpers->IndexToReference(objectID); - - return u.e_val; -} - -struct packed_entity_data_t final -{ - packed_entity_data_t(packed_entity_data_t &&other) noexcept - { operator=(std::move(other)); } - packed_entity_data_t &operator=(packed_entity_data_t &&other) noexcept { - packedData = other.packedData; - other.packedData = nullptr; - writeBuf = other.writeBuf; - other.writeBuf = nullptr; - ref = other.ref; - other.ref = INVALID_EHANDLE_INDEX; - return *this; - } - - char *packedData{nullptr}; - bf_write *writeBuf{nullptr}; - unsigned long ref{INVALID_EHANDLE_INDEX}; - - bool allocated() const noexcept - { return (packedData && writeBuf); } - - bool written() const noexcept - { return allocated() && (writeBuf->GetNumBitsWritten() > 0); } - - packed_entity_data_t() noexcept = default; - ~packed_entity_data_t() noexcept { - reset(); - } - - void reset() noexcept { - if(writeBuf) { - delete writeBuf; - writeBuf = nullptr; - } - if(packedData) { - free(packedData); - packedData = nullptr; - } - } - - void allocate() noexcept { - reset(); - - packedData = static_cast(aligned_alloc(4, MAX_PACKEDENTITY_DATA)); - memset(packedData, 0x0, MAX_PACKEDENTITY_DATA); - writeBuf = new bf_write{"SV_PackEntity->writeBuf", packedData, 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{}; - std::vector entities{}; - int snapshot_index{-1}; - - pack_entity_params_t(std::vector &&slots_, std::vector &&entities_, int snapshot_index_) noexcept - : slots{std::move(slots_)}, entities{std::move(entities_)}, snapshot_index{snapshot_index_} - { - entity_data.resize(slots.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_var in_compute_packs{}; -static thread_var do_calc_delta{}; -static thread_var do_writedelta_entities{}; -static thread_var writedeltaentities_client{}; -static thread_var sendproxy_client_slot{}; - -static std::unique_ptr packentity_params{}; - -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); -} - -struct prop_reference_t -{ - prop_reference_t(SendProp *pProp, prop_types type) noexcept - { - restores_t::iterator it_restore{restores.find(pProp)}; - if(it_restore == restores.end()) { - std::unique_ptr ptr{new proxyrestore_t{pProp, type}}; - it_restore = restores.emplace(std::pair>{pProp, std::move(ptr)}).first; - } - restore = it_restore->second.get(); - ++restore->ref; - #ifdef _DEBUG - printf("added ref %zu for %s %p\n", restore->ref, pProp->GetName(), pProp); - #endif - } - - virtual ~prop_reference_t() noexcept - { - if(restore) { - #ifdef _DEBUG - printf("removed ref %zu for %s %p\n", restore->ref-1u, restore->pProp->GetName(), restore->pProp); - #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; - -struct opaque_ptr final -{ - inline opaque_ptr(opaque_ptr &&other) noexcept - { operator=(std::move(other)); } - - template - static void del_hlpr_arr(void *ptr_) noexcept - { delete[] static_cast(ptr_); } - 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); - } - if(num > 1) { - ptr = static_cast(new T[num]); - for(size_t i = 0; i < num; ++i) { - new (&static_cast(ptr)[i]) T{std::forward(args)...}; - } - del_func = del_hlpr_arr; - } else { - ptr = static_cast(new T{std::forward(args)...}); - del_func = del_hlpr; - } - } - - void clear() noexcept { - if(del_func && ptr) { - del_func(ptr); - } - del_func = nullptr; - ptr = nullptr; - } - - 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}; -}; - -struct callback_t final : prop_reference_t -{ - callback_t(unsigned long ref_, SendProp *pProp, std::string &&name_, int element_, prop_types type_, std::size_t offset_) noexcept - : prop_reference_t{pProp, type_}, offset{offset_}, type{type_}, element{element_}, name{std::move(name_)}, prop{pProp}, ref{ref_} - { - if(type == prop_types::cstring || type == prop_types::tstring) { - fwd = forwards->CreateForwardEx(nullptr, ET_Hook, 6, nullptr, Param_Cell, Param_String, Param_String, Param_Cell, Param_Cell, Param_Cell); - } else if(type == prop_types::color32_) { - fwd = forwards->CreateForwardEx(nullptr, ET_Hook, 8, nullptr, Param_Cell, Param_String, Param_CellByRef, Param_CellByRef, Param_CellByRef, Param_CellByRef, 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: - case prop_types::bool_: - case prop_types::ehandle: { - 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, 5, nullptr, Param_Cell, Param_String, value_param_type, Param_Cell, Param_Cell); - } - } - - inline bool has_any_per_client_func() const noexcept - { return !per_client_funcs.empty(); } - - void change_edict_state() noexcept - { - if(ref != INVALID_EHANDLE_INDEX) { - CBaseEntity *pEntity{::ReferenceToEntity(ref)}; - if(pEntity) { - edict_t *edict{pEntity->GetNetworkable()->GetEdict()}; - if(edict) { - gamehelpers->SetEdictStateChanged(edict, offset); - } - } - } - } - - void add_function(IPluginFunction *func, bool per_client) noexcept - { - fwd->RemoveFunction(func); - fwd->AddFunction(func); - - if(per_client) { - bool found{false}; - - per_client_funcs_t::const_iterator it_func{per_client_funcs.cbegin()}; - while(it_func != per_client_funcs.cend()) { - if(it_func->func == func) { - found = true; - break; - } - ++it_func; - } - - if(!found) { - per_client_funcs.emplace_back(per_client_func_t{func}); - } - } - } - - void remove_function(IPluginFunction *func) noexcept - { - fwd->RemoveFunction(func); - - per_client_funcs_t::const_iterator it_func{per_client_funcs.cbegin()}; - while(it_func != per_client_funcs.cend()) { - if(it_func->func == func) { - per_client_funcs.erase(it_func); - break; - } - ++it_func; - } - } - - void remove_functions_of_plugin(IPlugin *plugin) noexcept - { - per_client_funcs_t::const_iterator it_func{per_client_funcs.cbegin()}; - while(it_func != per_client_funcs.cend()) { - if((*it_func).func->GetParentContext() == plugin->GetBaseContext()) { - it_func = per_client_funcs.erase(it_func); - continue; - } - ++it_func; - } - - fwd->RemoveFunctionsOfPlugin(plugin); - } - - ~callback_t() noexcept override final { - if(fwd) { - forwards->ReleaseForward(fwd); - } - } - - static int get_current_client_slot() noexcept - { - if(!sendproxy_client_slot) { - return -1; - } - - return sendproxy_client_slot; - } - - static int get_current_client_entity() noexcept - { - int slot{get_current_client_slot()}; - if(slot == -1) { - return -1; - } - - return slot+1; - } - - bool fwd_call_ehandle(int client, const SendProp *pProp, const void *old_pData, opaque_ptr &new_pData, int objectID) const noexcept - { - fwd->PushCell(objectID); - fwd->PushStringEx((char *)name.c_str(), name.size()+1, SM_PARAM_STRING_COPY|SM_PARAM_STRING_UTF8, 0); - const EHANDLE &hndl{*reinterpret_cast(old_pData)}; - CBaseEntity *pEntity = hndl.Get(); - cell_t sp_value{pEntity ? gamehelpers->EntityToBCompatRef(pEntity) : -1}; - fwd->PushCellByRef(&sp_value); - fwd->PushCell(element); - fwd->PushCell(client); - cell_t res{Pl_Continue}; - fwd->Execute(&res); - if(res == Pl_Changed) { - new_pData.emplace(1); - EHANDLE &new_value{new_pData.get(0)}; - pEntity = gamehelpers->ReferenceToEntity(sp_value); - if(pEntity) { - new_value = pEntity->GetRefEHandle(); - } else { - new_value.Term(); - } - return true; - } - return false; - } - - bool fwd_call_color32(int client, const SendProp *pProp, const void *old_pData, opaque_ptr &new_pData, int objectID) const noexcept - { - fwd->PushCell(objectID); - fwd->PushStringEx((char *)name.c_str(), name.size()+1, SM_PARAM_STRING_COPY|SM_PARAM_STRING_UTF8, 0); - const color32 &clr{*reinterpret_cast(old_pData)}; - cell_t sp_r{static_cast(clr.r)}; - cell_t sp_g{static_cast(clr.g)}; - cell_t sp_b{static_cast(clr.b)}; - cell_t sp_a{static_cast(clr.a)}; - fwd->PushCellByRef(&sp_r); - fwd->PushCellByRef(&sp_g); - fwd->PushCellByRef(&sp_b); - fwd->PushCellByRef(&sp_a); - fwd->PushCell(element); - fwd->PushCell(client); - cell_t res{Pl_Continue}; - fwd->Execute(&res); - if(res == Pl_Changed) { - new_pData.emplace(1); - color32 &new_value{new_pData.get(0)}; - new_value.r = static_cast(sp_r); - new_value.g = static_cast(sp_g); - new_value.b = static_cast(sp_b); - new_value.a = static_cast(sp_a); - return true; - } - return false; - } - - template - bool fwd_call_int(int client, const SendProp *pProp, const void *old_pData, opaque_ptr &new_pData, int objectID) const noexcept - { - fwd->PushCell(objectID); - fwd->PushStringEx((char *)name.c_str(), name.size()+1, SM_PARAM_STRING_COPY|SM_PARAM_STRING_UTF8, 0); - cell_t sp_value{static_cast(*reinterpret_cast(old_pData))}; - fwd->PushCellByRef(&sp_value); - fwd->PushCell(element); - fwd->PushCell(client); - cell_t res{Pl_Continue}; - fwd->Execute(&res); - if(res == Pl_Changed) { - new_pData.emplace(1); - T &new_value{new_pData.get(0)}; - new_value = static_cast(sp_value); - return true; - } - return false; - } - - bool fwd_call_float(int client, const SendProp *pProp, const void *old_pData, opaque_ptr &new_pData, int objectID) const noexcept - { - fwd->PushCell(objectID); - fwd->PushStringEx((char *)name.c_str(), name.size()+1, SM_PARAM_STRING_COPY|SM_PARAM_STRING_UTF8, 0); - float sp_value{static_cast(*reinterpret_cast(old_pData))}; - fwd->PushFloatByRef(&sp_value); - fwd->PushCell(element); - fwd->PushCell(client); - cell_t res{Pl_Continue}; - fwd->Execute(&res); - if(res == Pl_Changed) { - new_pData.emplace(1); - float &new_value{new_pData.get(0)}; - new_value = sp_value; - return true; - } - return false; - } - - template - bool fwd_call_vec(int client, const SendProp *pProp, const void *old_pData, opaque_ptr &new_pData, int objectID) const noexcept - { - fwd->PushCell(objectID); - fwd->PushStringEx((char *)name.c_str(), name.size()+1, SM_PARAM_STRING_COPY|SM_PARAM_STRING_UTF8, 0); - const T &vec{*reinterpret_cast(old_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(element); - fwd->PushCell(client); - cell_t res{Pl_Continue}; - fwd->Execute(&res); - if(res == Pl_Changed) { - new_pData.emplace(1); - T &new_value{new_pData.get(0)}; - new_value.x = sp_ctof(sp_value[0]); - new_value.y = sp_ctof(sp_value[1]); - new_value.z = sp_ctof(sp_value[2]); - return true; - } - return false; - } - - bool fwd_call_str(int client, const SendProp *pProp, const void *old_pData, opaque_ptr &new_pData, int objectID) const noexcept - { - fwd->PushCell(objectID); - fwd->PushStringEx((char *)name.c_str(), name.size()+1, SM_PARAM_STRING_COPY|SM_PARAM_STRING_UTF8, 0); - static char sp_value[4096]; - const char *str{reinterpret_cast(old_pData)}; - size_t len{strlen(str)}; - strcpy(sp_value, str); - fwd->PushStringEx(sp_value, len, SM_PARAM_STRING_UTF8|SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK); - fwd->PushCell(sizeof(sp_value)); - fwd->PushCell(element); - fwd->PushCell(client); - cell_t res{Pl_Continue}; - fwd->Execute(&res); - if(res == Pl_Changed) { - new_pData.emplace(strlen(sp_value)+1); - char *new_value{new_pData.get()}; - strcpy(new_value, new_value); - return true; - } - return false; - } - - bool fwd_call_tstr(int client, const SendProp *pProp, const void *old_pData, opaque_ptr &new_pData, int objectID) const noexcept - { - fwd->PushCell(objectID); - fwd->PushStringEx((char *)name.c_str(), name.size()+1, SM_PARAM_STRING_COPY|SM_PARAM_STRING_UTF8, 0); - static char sp_value[4096]; - const char *str{STRING(*reinterpret_cast(old_pData))}; - size_t len{strlen(str)}; - strcpy(sp_value, str); - fwd->PushStringEx(sp_value, len, SM_PARAM_STRING_UTF8|SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK); - fwd->PushCell(sizeof(sp_value)); - fwd->PushCell(element); - fwd->PushCell(client); - cell_t res{Pl_Continue}; - fwd->Execute(&res); - if(res == Pl_Changed) { - new_pData.emplace(1); - string_t &new_value{new_pData.get(0)}; - new_value = MAKE_STRING(sp_value); - return true; - } - return false; - } - - bool can_call_fwd(int client) const noexcept - { - if(!fwd || (has_any_per_client_func() && client == -1)) { - return false; - } - return true; - } - - bool fwd_call(int client, const SendProp *pProp, const void *old_pData, opaque_ptr &new_pData, int objectID) const noexcept - { - switch(type) { - case prop_types::int_: - return fwd_call_int(client, pProp, old_pData, new_pData, objectID); - case prop_types::bool_: - return fwd_call_int(client, pProp, old_pData, new_pData, objectID); - case prop_types::short_: - return fwd_call_int(client, pProp, old_pData, new_pData, objectID); - case prop_types::char_: - return fwd_call_int(client, pProp, old_pData, new_pData, objectID); - case prop_types::unsigned_int: - return fwd_call_int(client, pProp, old_pData, new_pData, objectID); - case prop_types::unsigned_short: - return fwd_call_int(client, pProp, old_pData, new_pData, objectID); - case prop_types::unsigned_char: - return fwd_call_int(client, pProp, old_pData, new_pData, objectID); - case prop_types::float_: - return fwd_call_float(client, pProp, old_pData, new_pData, objectID); - case prop_types::vector: - return fwd_call_vec(client, pProp, old_pData, new_pData, objectID); - case prop_types::qangle: - return fwd_call_vec(client, pProp, old_pData, new_pData, objectID); - case prop_types::color32_: - return fwd_call_color32(client, pProp, old_pData, new_pData, objectID); - case prop_types::ehandle: - return fwd_call_ehandle(client, pProp, old_pData, new_pData, objectID); - case prop_types::cstring: - return fwd_call_str(client, pProp, old_pData, new_pData, objectID); - case prop_types::tstring: - return fwd_call_tstr(client, pProp, old_pData, new_pData, objectID); - } - return false; - } - - void proxy_call(const SendProp *pProp, const void *pStructBase, const void *pOldData, const void *pNewData, DVariant *pOut, int iElement, int objectID) const noexcept - { - #if SOURCE_ENGINE == SE_TF2 - if(is_prop_cond(pProp)) { - DVariant ignore{}; - restore->pRealProxy(nullptr, nullptr, pOldData, &ignore, -1, -1); - std_proxies->m_UInt32ToInt32(pProp, pStructBase, pNewData, pOut, iElement, objectID); - } else - #endif - { - restore->pRealProxy(pProp, pStructBase, pNewData, pOut, iElement, objectID); - } - } - - 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; - type = other.type; - ref = other.ref; - other.ref = INVALID_EHANDLE_INDEX; - name = std::move(other.name); - element = other.element; - other.element = 0; - per_client_funcs = std::move(other.per_client_funcs); - return *this; - } - - IChangeableForward *fwd{nullptr}; - std::size_t offset{-1}; - prop_types type{prop_types::unknown}; - int element{0}; - std::string name{}; - SendProp *prop{nullptr}; - unsigned long ref{INVALID_EHANDLE_INDEX}; - - struct per_client_func_t - { - IPluginFunction *func{nullptr}; - - inline per_client_func_t(IPluginFunction *func_) noexcept - : func{func_} - { - } - - inline per_client_func_t(per_client_func_t &&other) noexcept - { operator=(std::move(other)); } - - per_client_func_t &operator=(per_client_func_t &&other) noexcept - { - func = other.func; - other.func = nullptr; - return *this; - } - - private: - per_client_func_t(const per_client_func_t &) = delete; - per_client_func_t &operator=(const per_client_func_t &) = delete; - per_client_func_t() = delete; - }; - - using per_client_funcs_t = std::vector; - per_client_funcs_t per_client_funcs{}; - -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; - unsigned long ref{INVALID_EHANDLE_INDEX}; - - inline proxyhook_t(unsigned long ref_) noexcept - : ref{ref_} - { - } - - inline ~proxyhook_t() noexcept - { - } - - void add_callback(SendProp *pProp, std::string &&name, int element, prop_types type, int offset, IPluginFunction *func, bool per_client) noexcept - { - callbacks_t::iterator it_callback{callbacks.find(pProp)}; - if(it_callback == callbacks.end()) { - it_callback = callbacks.emplace(std::pair{pProp, callback_t{ref, pProp, std::move(name), element, type, offset}}).first; - } - - it_callback->second.add_function(func, per_client); - } - - inline proxyhook_t(proxyhook_t &&other) noexcept - { operator=(std::move(other)); } - - proxyhook_t &operator=(proxyhook_t &&other) noexcept - { - callbacks = std::move(other.callbacks); - ref = other.ref; - other.ref = INVALID_EHANDLE_INDEX; - 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) -{ - do_calc_delta = false; - - if(!packentity_params || !in_compute_packs) { - return DETOUR_STATIC_CALL(SendTable_Encode)(pTable, pStruct, pOut, objectID, pRecipients, bNonZeroOnly); - } - - { - sendproxy_client_slot = -1; - if(!DETOUR_STATIC_CALL(SendTable_Encode)(pTable, pStruct, pOut, objectID, pRecipients, bNonZeroOnly)) { - Host_Error( "SV_PackEntity: SendTable_Encode returned false (ent %d).\n", objectID ); - return false; - } - } - - unsigned long ref{::IndexToReference(objectID)}; - - const std::vector &entities{packentity_params->entities}; - if(std::find(entities.cbegin(), entities.cend(), ref) != entities.cend()) { - const std::size_t slots_size{packentity_params->slots.size()}; - for(std::size_t i{0}; i < slots_size; ++i) { - std::vector &vec{packentity_params->entity_data[i]}; - vec.emplace_back(); - packed_entity_data_t &packedData{vec.back()}; - - packedData.ref = ref; - packedData.allocate(); - - sendproxy_client_slot = packentity_params->slots[i]; - const bool encoded{DETOUR_STATIC_CALL(SendTable_Encode)(pTable, pStruct, packedData.writeBuf, objectID, pRecipients, bNonZeroOnly)}; - sendproxy_client_slot = -1; - if(!encoded) { - Host_Error( "SV_PackEntity: SendTable_Encode returned false (ent %d).\n", objectID ); - return false; - } - } - do_calc_delta = true; - } - - 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(!packentity_params || !in_compute_packs || !do_calc_delta) { - return DETOUR_STATIC_CALL(SendTable_CalcDelta)(pTable, pFromState, nFromBits, pToState, nToBits, pDeltaProps, nMaxDeltaProps, objectID); - } - - do_calc_delta = false; - - int global_nChanges{DETOUR_STATIC_CALL(SendTable_CalcDelta)(pTable, pFromState, nFromBits, pToState, nToBits, pDeltaProps, nMaxDeltaProps, objectID)}; - int total_nChanges{global_nChanges}; - - if(total_nChanges < nMaxDeltaProps) { - int *client_deltaProps{new int[nMaxDeltaProps]{}}; - - int new_nChanges{total_nChanges}; - - unsigned long ref = ::IndexToReference(objectID); - - const std::size_t slots_size{packentity_params->slots.size()}; - for(std::size_t i{0}; i < slots_size; ++i) { - using entity_data_t = std::vector; - entity_data_t &entity_data{packentity_params->entity_data[i]}; - - packed_entity_data_t *packedData{nullptr}; - for(entity_data_t::reverse_iterator it{entity_data.rbegin()}; it != entity_data.rend(); ++it) { - if(it->ref == ref) { - packedData = &(*it); - break; - } - } - - if(!packedData || !packedData->written()) { - continue; - } - - const int client_nChanges{DETOUR_STATIC_CALL(SendTable_CalcDelta)(pTable, pFromState, nFromBits, packedData->packedData, packedData->writeBuf->GetNumBitsWritten(), client_deltaProps, nMaxDeltaProps, objectID)}; - int client_nChanges_new{0}; - - bool done{false}; - - for(int j{0}; j < client_nChanges; ++j) { - bool found{false}; - for(int k{0}; k < new_nChanges; ++k) { - if(pDeltaProps[k] == client_deltaProps[j]) { - found = true; - break; - } - } - if(!found) { - ++client_nChanges_new; - pDeltaProps[total_nChanges++] = client_deltaProps[j]; - if(total_nChanges >= nMaxDeltaProps) { - done = true; - break; - } - } - } - - if(done) { - break; - } - - new_nChanges += client_nChanges_new; - } - - delete[] client_deltaProps; - } - - if(total_nChanges > nMaxDeltaProps) { - total_nChanges = nMaxDeltaProps; - } - - return total_nChanges; -} - -class CFrameSnapshot -{ -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_MEMBER2(CFrameSnapshotManager_GetPackedEntity, PackedEntity *, CFrameSnapshot *, pSnapshot, int, entity) -{ - if(!pSnapshot || !packentity_params || !writedeltaentities_client || packentity_params->snapshot_index != pSnapshot->m_ListIndex) { - return DETOUR_MEMBER_CALL(CFrameSnapshotManager_GetPackedEntity)(pSnapshot, entity); - } - - PackedEntity *packed{DETOUR_MEMBER_CALL(CFrameSnapshotManager_GetPackedEntity)(pSnapshot, entity)}; - if(!packed) { - return nullptr; - } - - const int slot{writedeltaentities_client}; - - unsigned long ref{::IndexToReference(entity)}; - - const packed_entity_data_t *packedData{nullptr}; - const std::size_t slots_size{packentity_params->slots.size()}; - for(std::size_t i{0}; i < slots_size; ++i) { - if(packentity_params->slots[i] == slot) { - const std::vector &entity_data{packentity_params->entity_data[i]}; - for(const packed_entity_data_t &it : entity_data) { - if(it.ref == ref) { - if(it.written()) { - packedData = ⁢ - } - break; - } - } - break; - } - } - - if(packedData) { - if(packed->GetData()) { - packed->FreeData(); - } - - packed->AllocAndCopyPadded(packedData->packedData, packedData->writeBuf->GetNumBytesWritten()); - } - - return packed; -} - -static ConVar *sv_stressbots{nullptr}; - -static bool is_client_valid(CBaseClient *client) noexcept -{ - if(client->IsHLTV() || - client->IsReplay()) { - return false; - } - - if(!sv_stressbots->GetBool()) { - if(client->IsFakeClient()) { - return false; - } - } - - if(!client->IsConnected() || - !client->IsSpawned() || - !client->IsActive()) { - return false; - } - - return true; -} - -#include - -class CBaseServer : public IServer -{ -public: - virtual float GetCPUUsage( void ) = 0; - virtual void BroadcastPrintf ( PRINTF_FORMAT_STRING const char *fmt, ...) = 0; - virtual void SetMaxClients( int number ) = 0; - virtual void WriteDeltaEntities( CBaseClient *client, CClientFrame *to, CClientFrame *from, bf_write &pBuf ) = 0; -}; - -void PreWriteDeltaEntities(CBaseClient *client) -{ - if(do_writedelta_entities) { - if(!is_client_valid(client)) { - writedeltaentities_client = -1; - } else { - writedeltaentities_client = client->GetPlayerSlot(); - } - } - - for(auto it : g_Sample.pack_ent_listeners) { - it->pre_write_deltas(); - } -} - -void PostWriteDeltaEntities() -{ - for(auto it : g_Sample.pack_ent_listeners) { - it->post_write_deltas(); - } - - if(do_writedelta_entities) { - writedeltaentities_client = -1; - } -} - -DETOUR_DECL_MEMBER4(CBaseServer_WriteDeltaEntities, void, CBaseClient *, client, CClientFrame *, to, CClientFrame *, from, bf_write &, pBuf) -{ - CBaseServer *pthis = (CBaseServer *)this; - - if(pthis->IsHLTV() || - pthis->IsReplay()) { - DETOUR_MEMBER_CALL(CBaseServer_WriteDeltaEntities)(client, to, from, pBuf); - } else { - PreWriteDeltaEntities(client); - DETOUR_MEMBER_CALL(CBaseServer_WriteDeltaEntities)(client, to, from, pBuf); - PostWriteDeltaEntities(); - } -} - -static CDetour *CBaseServer_WriteDeltaEntities_detour{nullptr}; - -static ConVar *sv_parallel_packentities{nullptr}; -static ConVar *sv_parallel_sendsnapshot{nullptr}; - -DETOUR_DECL_STATIC0(InvalidateSharedEdictChangeInfos, void) -{ - DETOUR_STATIC_CALL(InvalidateSharedEdictChangeInfos)(); -} - -static std::thread::id main_thread_id; - -struct PackWork_t; - -DETOUR_DECL_STATIC1(PackWork_tProcess, void, PackWork_t &, item) -{ - DETOUR_STATIC_CALL(PackWork_tProcess)(item); -} - -DETOUR_DECL_STATIC4(SV_PackEntity, void, int, edictIdx, edict_t *, edict, ServerClass *, pServerClass, CFrameSnapshot *, pSnapshot) -{ - DETOUR_STATIC_CALL(SV_PackEntity)(edictIdx, edict, pServerClass, pSnapshot); -} - -static CDetour *SendTable_CalcDelta_detour{nullptr}; -static CDetour *SendTable_Encode_detour{nullptr}; -static CDetour *CFrameSnapshotManager_GetPackedEntity_detour{nullptr}; - -DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, clientCount, CGameClient **, clients, CFrameSnapshot *, snapshot) -{ - packentity_params.reset(nullptr); - - std::vector slots{}; - - for(int i{0}; i < clientCount; ++i) { - CGameClient *client{clients[i]}; - if(!is_client_valid(client)) { - continue; - } - slots.emplace_back(client->GetPlayerSlot()); - } - const std::size_t slots_size{slots.size()}; - - std::vector entities{}; - - bool any_hook{false}; - - const hooks_t &chooks{hooks}; - - for(int i{0}; i < snapshot->m_nValidEntities; ++i) { - int idx{snapshot->m_pValidEntities[i]}; - unsigned long ref{::IndexToReference(idx)}; - - for(auto it : g_Sample.pack_ent_listeners) { - CBaseEntity *pEntity{::ReferenceToEntity(ref)}; - if(pEntity) { - it->pre_pack_entity(pEntity); - } - } - - hooks_t::const_iterator it_hook{chooks.find(ref)}; - if(it_hook != chooks.cend()) { - if(!it_hook->second.callbacks.empty()) { - any_hook = true; - } - if(slots_size > 0) { - bool any_per_client_func{false}; - for(const auto &it_callback : it_hook->second.callbacks) { - if(it_callback.second.has_any_per_client_func()) { - any_per_client_func = true; - break; - } - } - if(any_per_client_func) { - entities.emplace_back(ref); - } - } - } - } - - const bool any_per_client_hook{slots_size > 0 && entities.size() > 0}; - -#if defined _DEBUG && 0 - printf("slots = %i, entities = %i\n", slots.size(), entities.size()); - printf("any_hook = %i, any_per_client_hook = %i, is_parallel_pack_allowed = %i\n", any_hook, any_per_client_hook, g_Sample.is_parallel_pack_allowed()); -#endif - - if(any_per_client_hook) { - packentity_params.reset(new pack_entity_params_t{std::move(slots), std::move(entities), snapshot->m_ListIndex}); - SendTable_Encode_detour->EnableDetour(); - SendTable_CalcDelta_detour->EnableDetour(); - CFrameSnapshotManager_GetPackedEntity_detour->EnableDetour(); - do_writedelta_entities = true; - } else { - do_writedelta_entities = false; - CFrameSnapshotManager_GetPackedEntity_detour->DisableDetour(); - SendTable_Encode_detour->DisableDetour(); - SendTable_CalcDelta_detour->DisableDetour(); - } - - const bool parallel_pack{ - !any_hook && - g_Sample.is_parallel_pack_allowed() - }; - - //sv_parallel_sendsnapshot->SetValue(false); - sv_parallel_packentities->SetValue(parallel_pack); - - in_compute_packs = true; - DETOUR_STATIC_CALL(SV_ComputeClientPacks)(clientCount, clients, snapshot); - in_compute_packs = false; - - if(!sv_parallel_packentities->GetBool() && any_per_client_hook) { - SendTable_Encode_detour->DisableDetour(); - SendTable_CalcDelta_detour->DisableDetour(); - } -} - -bool Sample::is_parallel_pack_allowed() const noexcept -{ - return !std::any_of(pack_ent_listeners.cbegin(), pack_ent_listeners.cend(), - [](const parallel_pack_listener *listener) noexcept -> bool { - return !listener->is_allowed(); - } - ); -} - -bool Sample::add_listener(const parallel_pack_listener *ptr) noexcept -{ - if(std::find(pack_ent_listeners.cbegin(), pack_ent_listeners.cend(), ptr) != pack_ent_listeners.cend()) { - return false; - } - - pack_ent_listeners.emplace_back(ptr); - return true; -} - -bool Sample::remove_listener(const parallel_pack_listener *ptr) noexcept -{ - pack_ent_listeners_t::const_iterator it{std::find(pack_ent_listeners.cbegin(), pack_ent_listeners.cend(), ptr)}; - if(it == pack_ent_listeners.cend()) { - return false; - } - - pack_ent_listeners.erase(it); - return true; -} - -static void global_send_proxy(const SendProp *pProp, const void *pStructBase, const void *pData, DVariant *pOut, int iElement, int objectID) -{ - proxyrestore_t *restore{nullptr}; - - if(objectID != -1) { - const hooks_t &chooks{hooks}; - unsigned long ref{::IndexToReference(objectID)}; - hooks_t::const_iterator it_hook{chooks.find(ref)}; - if(it_hook != chooks.cend()) { - callbacks_t::const_iterator it_callback{it_hook->second.callbacks.find(pProp)}; - if(it_callback != it_hook->second.callbacks.cend()) { - restore = it_callback->second.restore; - const int client{callback_t::get_current_client_entity()}; - if(it_callback->second.can_call_fwd(client)) { - if(std::this_thread::get_id() == main_thread_id) { - opaque_ptr new_data{}; - if(it_callback->second.fwd_call(client, pProp, pData, new_data, objectID)) { - it_callback->second.proxy_call(pProp, pStructBase, pData, new_data.get(), pOut, iElement, objectID); - return; - } - } - } - } - } - } - - if(!restore) { - const restores_t &crestores{restores}; - restores_t::const_iterator it_restore{crestores.find(const_cast(pProp))}; - if(it_restore != crestores.cend()) { - restore = it_restore->second.get(); - } - } - - if(restore) { - restore->pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID); - } -} - -static void game_frame(bool simulating) noexcept -{ - if(!simulating) { - return; - } - - // dumb nonsense so clients are fully aware that our hooked edicts are changing. - // this looks hacky, and it is, but there is not a better way i could find - // after tearing my hair out for 3 days of research - // sappho.io - for (auto& hook : hooks) - { - unsigned long ref = hook.first; // hook.second.ref; - //for (auto& cb : hook.second.callbacks) - //{ - // cb.second.change_edict_state(); - //} - CBaseEntity* pEntity = ::ReferenceToEntity(ref); - if (!pEntity) - { - continue; - } - edict_t* edict = pEntity->GetNetworkable()->GetEdict(); - if (!edict) - { - continue; - } - gamehelpers->SetEdictStateChanged(edict, 0); - } -} - -DETOUR_DECL_MEMBER1(CGameServer_SendClientMessages, void, bool, bSendSnapshots) -{ - DETOUR_MEMBER_CALL(CGameServer_SendClientMessages)(bSendSnapshots); - - if(!sv_parallel_sendsnapshot->GetBool()) { - do_writedelta_entities = false; - CFrameSnapshotManager_GetPackedEntity_detour->DisableDetour(); - } - - packentity_params.reset(nullptr); -} - -struct sm_sendprop_info_ex_t final : sm_sendprop_info_t -{ - SendTable *table; -}; - -static int utlVecOffsetOffset{-1}; - -static bool UTIL_FindInSendTable(SendTable *pTable, - const char *name, - sm_sendprop_info_ex_t *info, - unsigned int offset) noexcept -{ - int props = pTable->GetNumProps(); - for (int i = 0; i < props; ++i) - { - SendProp *prop = pTable->GetProp(i); - - // Skip InsideArray props (SendPropArray / SendPropArray2), - // we'll find them later by their containing array. - if (prop->IsInsideArray()) { - continue; - } - - const char *pname = prop->GetName(); - SendTable *pInnerTable = prop->GetDataTable(); - - if (pname && strcmp(name, pname) == 0) - { - // get true offset of CUtlVector - if (utlVecOffsetOffset != -1 && prop->GetOffset() == 0 && pInnerTable && pInnerTable->GetNumProps()) - { - SendProp *pLengthProxy = pInnerTable->GetProp(0); - const char *ipname = pLengthProxy->GetName(); - if (ipname && strcmp(ipname, "lengthproxy") == 0 && pLengthProxy->GetExtraData()) - { - info->table = pTable; - info->prop = prop; - info->actual_offset = offset + *reinterpret_cast(reinterpret_cast(pLengthProxy->GetExtraData()) + utlVecOffsetOffset); - return true; - } - } - info->table = pTable; - info->prop = prop; - info->actual_offset = offset + info->prop->GetOffset(); - return true; - } - if (pInnerTable) - { - if (UTIL_FindInSendTable(pInnerTable, - name, - info, - offset + prop->GetOffset()) - ) - { - return true; - } - } - } - - return false; -} - -using propinfo_t = std::unordered_map; -using propinfos_t = std::unordered_map; -static propinfos_t propinfos; - -bool Sample::remove_serverclass_from_cache(ServerClass *pClass) noexcept -{ - propinfos_t::iterator it_props{propinfos.find(pClass)}; - if(it_props == propinfos.cend()) { - return false; - } - - propinfos.erase(it_props); - return true; -} - -static bool FindSendPropInfo(ServerClass *pClass, std::string &&name, sm_sendprop_info_ex_t *info) noexcept -{ - propinfos_t::iterator it_props{propinfos.find(pClass)}; - if(it_props == propinfos.cend()) { - it_props = propinfos.emplace(std::pair{pClass, propinfo_t{}}).first; - } - if(it_props != propinfos.cend()) { - propinfo_t::iterator it_prop{it_props->second.find(name)}; - if(it_prop == it_props->second.cend()) { - if(UTIL_FindInSendTable(pClass->m_pTable, name.c_str(), info, 0)) { - it_prop = it_props->second.emplace(std::pair{std::move(name), std::move(*info)}).first; - } - } - if(it_prop != it_props->second.cend()) { - *info = it_prop->second; - return true; - } - } - return false; -} - -static cell_t proxysend_handle_hook(IPluginContext *pContext, hooks_t::iterator it_hook, unsigned long ref, int offset, SendProp *pProp, std::string &&prop_name, int element, SendTable *pTable, IPluginFunction *callback, bool per_client) -{ - prop_types type{prop_types::unknown}; - restores_t::const_iterator it_restore{restores.find(pProp)}; - if(it_restore != restores.cend()) { - type = it_restore->second->type; - } else { - type = guess_prop_type(pProp, pTable); - } - if(type == prop_types::unknown) { - return pContext->ThrowNativeError("Unsupported prop"); - } - -#ifdef _DEBUG - printf("added %s %p hook for %i\n", pProp->GetName(), pProp, ref); -#endif - - it_hook->second.add_callback(pProp, std::move(prop_name), element, type, offset, callback, per_client); - - return 0; -} - -static cell_t proxysend_hook(IPluginContext *pContext, const cell_t *params) noexcept -{ - CBaseEntity *pEntity{gamehelpers->ReferenceToEntity(params[1])}; - if(!pEntity) { - return pContext->ThrowNativeError("Invalid Entity Reference/Index %i", params[1]); - } - - char *name_ptr; - pContext->LocalToString(params[2], &name_ptr); - std::string name{name_ptr}; - - IPluginFunction *callback{pContext->GetFunctionById(params[3])}; - - bool per_client = static_cast(params[4]); - - IServerNetworkable *pNetwork{pEntity->GetNetworkable()}; - ServerClass *pServer{pNetwork->GetServerClass()}; - - sm_sendprop_info_ex_t info{}; - if(!FindSendPropInfo(pServer, std::move(name), &info)) { - return pContext->ThrowNativeError("Could not find prop %s", name.c_str()); - } - SendTable *pTable{info.table}; - - SendProp *pProp{info.prop}; - std::string prop_name{pProp->GetName()}; - - unsigned long ref = ::EntityToReference(pEntity); - - hooks_t::iterator it_hook{hooks.find(ref)}; - if(it_hook == hooks.end()) { - it_hook = hooks.emplace(std::pair{ref, proxyhook_t{ref}}).first; - } - - edict_t *edict{pEntity->GetNetworkable()->GetEdict()}; - - if(pProp->GetType() == DPT_DataTable) { - SendTable *pPropTable{pProp->GetDataTable()}; - int NumProps{pPropTable->GetNumProps()}; - for(int i = 0; i < NumProps; ++i) { - SendProp *pChildProp{pPropTable->GetProp(i)}; - std::string tmp_name{prop_name}; - int offset{info.actual_offset + pChildProp->GetOffset()}; - cell_t ret{proxysend_handle_hook(pContext, it_hook, ref, offset, pChildProp, std::move(tmp_name), i, pTable, callback, per_client)}; - if(ret != 0) { - return ret; - } - if(edict) { - gamehelpers->SetEdictStateChanged(edict, offset); - } - } - return 0; - } - - cell_t ret{proxysend_handle_hook(pContext, it_hook, ref, info.actual_offset, pProp, std::move(prop_name), 0, pTable, callback, per_client)}; - if(ret == 0) { - if(edict) { - gamehelpers->SetEdictStateChanged(edict, info.actual_offset); - } - } - - return ret; -} - -static void proxysend_handle_unhook(hooks_t::iterator it_hook, unsigned long ref, const SendProp *pProp, const char *name, IPluginFunction *callback) -{ - callbacks_t::iterator it_callback{it_hook->second.callbacks.find(pProp)}; - if(it_callback != it_hook->second.callbacks.end()) { - it_callback->second.remove_function(callback); - #ifdef _DEBUG - printf("removed func from %s %p callback for %i\n", name, pProp, ref); - #endif - if(it_callback->second.fwd->GetFunctionCount() == 0) { - #ifdef _DEBUG - printf("removed callback %s %p for %i\n", name, pProp, ref); - #endif - it_hook->second.callbacks.erase(it_callback); - } - } -} - -static cell_t proxysend_unhook(IPluginContext *pContext, const cell_t *params) noexcept -{ - CBaseEntity *pEntity{gamehelpers->ReferenceToEntity(params[1])}; - if(!pEntity) { - return pContext->ThrowNativeError("Invalid Entity Reference/Index %i", params[1]); - } - - IServerNetworkable *pNetwork{pEntity->GetNetworkable()}; - ServerClass *pServer{pNetwork->GetServerClass()}; - - char *name_ptr; - pContext->LocalToString(params[2], &name_ptr); - - sm_sendprop_info_t info{}; - if(!gamehelpers->FindSendPropInfo(pServer->GetName(), name_ptr, &info)) { - return pContext->ThrowNativeError("Could not find prop %s", name_ptr); - } - - const SendProp *pProp{info.prop}; - - IPluginFunction *callback{pContext->GetFunctionById(params[3])}; - - unsigned long ref = gamehelpers->EntityToReference(pEntity); - - hooks_t::iterator it_hook{hooks.find(ref)}; - if(it_hook != hooks.end()) { - if(pProp->GetType() == DPT_DataTable) { - SendTable *pPropTable{pProp->GetDataTable()}; - int NumProps{pPropTable->GetNumProps()}; - for(int i = 0; i < NumProps; ++i) { - SendProp *pChildProp{pPropTable->GetProp(i)}; - proxysend_handle_unhook(it_hook, ref, pChildProp, name_ptr, callback); - } - } else { - proxysend_handle_unhook(it_hook, ref, pProp, name_ptr, callback); - } - if(it_hook->second.callbacks.empty()) { - hooks.erase(it_hook); - } - } - - edict_t *edict{pEntity->GetNetworkable()->GetEdict()}; - if(edict) { - gamehelpers->SetEdictStateChanged(edict, 0); - } - - return 0; -} - -static constexpr const sp_nativeinfo_t natives[]{ - {"proxysend_hook", proxysend_hook}, - {"proxysend_unhook", proxysend_unhook}, - {nullptr, nullptr} -}; - -static CDetour *SV_ComputeClientPacks_detour{nullptr}; -static CDetour *SV_PackEntity_detour{nullptr}; -static CDetour *PackWork_tProcess_detour{nullptr}; -static CDetour *InvalidateSharedEdictChangeInfos_detour{nullptr}; -static CDetour *CGameServer_SendClientMessages_detour{nullptr}; - -bool Sample::SDK_OnLoad(char *error, size_t maxlen, bool late) noexcept -{ - if(!gameconfs->LoadGameConfigFile("proxysend", &gameconf, error, maxlen)) { - return false; - } - - IGameConfig *coregameconf{nullptr}; - if(!gameconfs->LoadGameConfigFile("core.games/common.games", &coregameconf, error, maxlen)) { - return false; - } - - coregameconf->GetOffset("CSendPropExtra_UtlVector::m_Offset", &utlVecOffsetOffset); - - gameconfs->CloseGameConfigFile(coregameconf); - - gameconf->GetOffset("CBaseClient::UpdateSendState", &CBaseClient_UpdateSendState_idx); - if(CBaseClient_UpdateSendState_idx == -1) { - snprintf(error, maxlen, "could not get CBaseClient::UpdateSendState offset"); - return false; - } - - gameconf->GetOffset("CBaseClient::SendSnapshot", &CBaseClient_SendSnapshot_idx); - if(CBaseClient_SendSnapshot_idx == -1) { - snprintf(error, maxlen, "could not get CBaseClient::SendSnapshot offset"); - return false; - } - - gameconf->GetMemSig("CGameClient::GetSendFrame", &CGameClient_GetSendFrame_ptr); - if(CGameClient_GetSendFrame_ptr == nullptr) { - snprintf(error, maxlen, "could not get CGameClient::GetSendFrame address"); - return false; - } - - gameconf->GetMemSig("SendProxy_StringT_To_String", (void **)&SendProxy_StringT_To_String_ptr); - if(SendProxy_StringT_To_String_ptr == nullptr) { - snprintf(error, maxlen, "could not get SendProxy_StringT_To_String address"); - return false; - } - - gameconf->GetMemSig("SendProxy_Color32ToInt", (void **)&SendProxy_Color32ToInt_ptr); - gameconf->GetMemSig("SendProxy_EHandleToInt", (void **)&SendProxy_EHandleToInt_ptr); - - CDetourManager::Init(smutils->GetScriptingEngine(), gameconf); - - SendTable_CalcDelta_detour = DETOUR_CREATE_STATIC(SendTable_CalcDelta, "SendTable_CalcDelta"); - if(!SendTable_CalcDelta_detour) { - snprintf(error, maxlen, "could not create SendTable_CalcDelta detour"); - return false; - } - - SendTable_Encode_detour = DETOUR_CREATE_STATIC(SendTable_Encode, "SendTable_Encode"); - if(!SendTable_Encode_detour) { - snprintf(error, maxlen, "could not create SendTable_Encode detour"); - return false; - } - - SV_ComputeClientPacks_detour = DETOUR_CREATE_STATIC(SV_ComputeClientPacks, "SV_ComputeClientPacks"); - if(!SV_ComputeClientPacks_detour) { - snprintf(error, maxlen, "could not create SV_ComputeClientPacks detour"); - return false; - } - - CGameServer_SendClientMessages_detour = DETOUR_CREATE_MEMBER(CGameServer_SendClientMessages, "CGameServer::SendClientMessages"); - if(!CGameServer_SendClientMessages_detour) { - snprintf(error, maxlen, "could not create CGameServer::SendClientMessages detour"); - return false; - } - - CFrameSnapshotManager_GetPackedEntity_detour = DETOUR_CREATE_MEMBER(CFrameSnapshotManager_GetPackedEntity, "CFrameSnapshotManager::GetPackedEntity"); - if(!CFrameSnapshotManager_GetPackedEntity_detour) { - snprintf(error, maxlen, "could not create CFrameSnapshotManager::GetPackedEntity detour"); - return false; - } - -#if SOURCE_ENGINE == SE_TF2 - { - void **vtable = *(void ***)server; - int index = vfunc_index(&CBaseServer::WriteDeltaEntities); - CBaseServer_WriteDeltaEntities_detour = DETOUR_CREATE_MEMBER(CBaseServer_WriteDeltaEntities, vtable[index]); - CBaseServer_WriteDeltaEntities_detour->EnableDetour(); - } -#endif - -#if 0 - //SV_PackEntity_detour = DETOUR_CREATE_STATIC(SV_PackEntity, "SV_PackEntity"); - //SV_PackEntity_detour->EnableDetour(); - - //PackWork_tProcess_detour = DETOUR_CREATE_STATIC(PackWork_tProcess, "PackWork_t::Process"); - //PackWork_tProcess_detour->EnableDetour(); - - //InvalidateSharedEdictChangeInfos_detour = DETOUR_CREATE_STATIC(InvalidateSharedEdictChangeInfos, "InvalidateSharedEdictChangeInfos"); - //InvalidateSharedEdictChangeInfos_detour->EnableDetour(); -#endif - - CGameServer_SendClientMessages_detour->EnableDetour(); - SV_ComputeClientPacks_detour->EnableDetour(); - - g_pEntityList = reinterpret_cast(gamehelpers->GetGlobalEntityList()); - - sharesys->AddDependency(myself, "sdkhooks.ext", true, true); - - sharesys->AddInterface(myself, this); - sharesys->RegisterLibrary(myself, "proxysend"); - - smutils->AddGameFrameHook(game_frame); - plsys->AddPluginsListener(this); - - sharesys->AddNatives(myself, natives); - -#if SOURCE_ENGINE == SE_TF2 - sm_sendprop_info_t info{}; - gamehelpers->FindSendPropInfo("CTFPlayer", "m_nPlayerCond", &info); - m_nPlayerCond = info.prop; - gamehelpers->FindSendPropInfo("CTFPlayer", "_condition_bits", &info); - _condition_bits = info.prop; - gamehelpers->FindSendPropInfo("CTFPlayer", "m_nPlayerCondEx", &info); - m_nPlayerCondEx = info.prop; - gamehelpers->FindSendPropInfo("CTFPlayer", "m_nPlayerCondEx2", &info); - m_nPlayerCondEx2 = info.prop; - gamehelpers->FindSendPropInfo("CTFPlayer", "m_nPlayerCondEx3", &info); - m_nPlayerCondEx3 = info.prop; - gamehelpers->FindSendPropInfo("CTFPlayer", "m_nPlayerCondEx4", &info); - m_nPlayerCondEx4 = info.prop; -#endif - - return true; -} - -bool Sample::RegisterConCommandBase(ConCommandBase *pCommand) -{ - META_REGCVAR(pCommand); - 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); - ConVar_Register(0, this); - -#if SOURCE_ENGINE == SE_TF2 - server = engine->GetIServer(); -#endif - - std_proxies = gamedll->GetStandardSendProxies(); - - main_thread_id = std::this_thread::get_id(); - - sv_parallel_packentities = g_pCVar->FindVar("sv_parallel_packentities"); - sv_parallel_sendsnapshot = g_pCVar->FindVar("sv_parallel_sendsnapshot"); - sv_stressbots = g_pCVar->FindVar("sv_stressbots"); - - return true; -} - -void Sample::OnCoreMapEnd() noexcept -{ - hooks.clear(); - restores.clear(); -} - -void Sample::SDK_OnUnload() noexcept -{ - OnCoreMapEnd(); - - SendTable_CalcDelta_detour->Destroy(); - SendTable_Encode_detour->Destroy(); - SV_ComputeClientPacks_detour->Destroy(); - //SV_PackEntity_detour->Destroy(); - //PackWork_tProcess_detour->Destroy(); - //InvalidateSharedEdictChangeInfos_detour->Destroy(); - CGameServer_SendClientMessages_detour->Destroy(); - CFrameSnapshotManager_GetPackedEntity_detour->Destroy(); - CBaseServer_WriteDeltaEntities_detour->Destroy(); - - gameconfs->CloseGameConfigFile(gameconf); - - smutils->RemoveGameFrameHook(game_frame); - plsys->RemovePluginsListener(this); - if(g_pSDKHooks) { - g_pSDKHooks->RemoveEntityListener(this); - } -} - -void Sample::OnEntityDestroyed(CBaseEntity *pEntity) noexcept -{ - if(!pEntity) { - return; - } - - const unsigned long ref{::EntityToReference(pEntity)}; - - hooks_t::iterator it_hook{hooks.find(ref)}; - 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.remove_functions_of_plugin(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; - } -} - -bool Sample::QueryRunning(char *error, size_t maxlength) -{ - SM_CHECK_IFACE(SDKHOOKS, g_pSDKHooks); - SM_CHECK_IFACE(SDKTOOLS, g_pSDKTools); - return true; -} - -bool Sample::QueryInterfaceDrop(SMInterface *pInterface) -{ - if(pInterface == g_pSDKHooks) - return false; - else if(pInterface == g_pSDKTools) - return false; - return IExtensionInterface::QueryInterfaceDrop(pInterface); -} - -void Sample::NotifyInterfaceDrop(SMInterface *pInterface) -{ - if(strcmp(pInterface->GetInterfaceName(), SMINTERFACE_SDKHOOKS_NAME) == 0) { - g_pSDKHooks->RemoveEntityListener(this); - g_pSDKHooks = NULL; - } else if(strcmp(pInterface->GetInterfaceName(), SMINTERFACE_SDKTOOLS_NAME) == 0) { - g_pSDKTools = NULL; - } -} - -void Sample::SDK_OnAllLoaded() noexcept -{ - SM_GET_LATE_IFACE(SDKHOOKS, g_pSDKHooks); - SM_GET_LATE_IFACE(SDKTOOLS, g_pSDKTools); - - g_pSDKHooks->AddEntityListener(this); - -#if SOURCE_ENGINE == SE_LEFT4DEAD2 - server = g_pSDKTools->GetIServer(); - - { - void **vtable = *(void ***)server; - int index = vfunc_index(&CBaseServer::WriteDeltaEntities); - CBaseServer_WriteDeltaEntities_detour = DETOUR_CREATE_MEMBER(CBaseServer_WriteDeltaEntities, vtable[index]); - CBaseServer_WriteDeltaEntities_detour->EnableDetour(); - } -#endif -} \ No newline at end of file +/** + * 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 +#include "packed_entity.h" +#include +#include +#include +#include +#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 ISDKHooks *g_pSDKHooks{nullptr}; +ISDKTools *g_pSDKTools = nullptr; + +IServer *server = nullptr; +CBaseEntityList *g_pEntityList = nullptr; + +static void *CGameClient_GetSendFrame_ptr{nullptr}; + +static int CBaseClient_UpdateSendState_idx{-1}; +static int CBaseClient_SendSnapshot_idx{-1}; + +template +int vfunc_index(T func) +{ + SourceHook::MemFuncInfo info{}; + SourceHook::GetFuncInfo(func, info); + return info.vtblindex; +} + +template +R call_mfunc(T *pThisPtr, void *offset, Args ...args) +{ + class VEmptyClass {}; + + void **this_ptr = *reinterpret_cast(&pThisPtr); + + union + { + R (VEmptyClass::*mfpnew)(Args...); +#ifndef PLATFORM_POSIX + void *addr; + } u; + u.addr = offset; +#else + struct + { + void *addr; + intptr_t adjustor; + } s; + } u; + u.s.addr = offset; + u.s.adjustor = 0; +#endif + + return (R)(reinterpret_cast(this_ptr)->*u.mfpnew)(args...); +} + +template +R call_vfunc(T *pThisPtr, size_t offset, Args ...args) +{ + void **vtable = *reinterpret_cast(pThisPtr); + void *vfunc = vtable[offset]; + + return call_mfunc(pThisPtr, vfunc, args...); +} + +class CFrameSnapshot; +class CClientFrame; + +class CBaseClient : public IGameEventListener2, public IClient, public IClientMessageHandler +{ +}; + +class CGameClient : public CBaseClient +{ +public: + inline void SendSnapshot(CClientFrame *pFrame) + { call_vfunc(this, CBaseClient_SendSnapshot_idx, pFrame); } + + inline void UpdateSendState() + { call_vfunc(this, CBaseClient_UpdateSendState_idx); } + + inline CClientFrame *GetSendFrame() + { return call_mfunc(this, CGameClient_GetSendFrame_ptr); } +}; + +class CBaseEntity : public IServerEntity +{ +}; + +using prop_types = proxysend::prop_types; + +static void global_send_proxy(const SendProp *pProp, const void *pStructBase, const void *pData, DVariant *pOut, int iElement, int objectID); + +static const CStandardSendProxies *std_proxies; + +#if SOURCE_ENGINE == SE_TF2 +static const SendProp *m_nPlayerCond{nullptr}; +static const SendProp *_condition_bits{nullptr}; +static const SendProp *m_nPlayerCondEx{nullptr}; +static const SendProp *m_nPlayerCondEx2{nullptr}; +static const SendProp *m_nPlayerCondEx3{nullptr}; +static const SendProp *m_nPlayerCondEx4{nullptr}; + +static bool is_prop_cond(const SendProp *pProp) +{ + return (pProp == m_nPlayerCond || + pProp == _condition_bits || + pProp == m_nPlayerCondEx || + pProp == m_nPlayerCondEx2 || + pProp == m_nPlayerCondEx3 || + pProp == m_nPlayerCondEx4); +} +#endif + +struct proxyrestore_t final +{ + inline proxyrestore_t(proxyrestore_t &&other) noexcept + { operator=(std::move(other)); } + + proxyrestore_t(SendProp *pProp_, prop_types type_) noexcept + : pProp{pProp_}, pRealProxy{pProp->GetProxyFn()}, type{type_} + { + #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; + type = other.type; + return *this; + } + + SendProp *pProp{nullptr}; + SendVarProxyFn pRealProxy{nullptr}; + std::size_t ref{0}; + prop_types type{prop_types::unknown}; + +private: + proxyrestore_t(const proxyrestore_t &) = delete; + proxyrestore_t &operator=(const proxyrestore_t &) = delete; + proxyrestore_t() = delete; +}; + +using restores_t = std::unordered_map>; +static restores_t restores; + +static SendVarProxyFn SendProxy_StringT_To_String_ptr{nullptr}; +static SendVarProxyFn SendProxy_Color32ToInt_ptr{nullptr}; +static SendVarProxyFn SendProxy_EHandleToInt_ptr{nullptr}; + +static prop_types guess_prop_type(const SendProp *pProp, const SendTable *pTable) noexcept +{ +#if defined _DEBUG + printf("%s type is ", pProp->GetName()); +#endif + + SendVarProxyFn pRealProxy{pProp->GetProxyFn()}; + if(pRealProxy == global_send_proxy) { + restores_t::const_iterator it_restore{restores.find(const_cast(pProp))}; + if(it_restore == restores.cend()) { + #if defined _DEBUG + printf("invalid (global send proxy)\n"); + #endif + return prop_types::unknown; + } + + #if defined _DEBUG + printf("from restore (global send proxy)\n"); + #endif + return it_restore->second->type; + } + +#if SOURCE_ENGINE == SE_TF2 + if(is_prop_cond(pProp)) { + #if defined _DEBUG + printf("unsigned int (is cond)\n"); + #endif + return prop_types::unsigned_int; + } +#endif + + switch(pProp->GetType()) { + case DPT_Int: { + if(pProp->GetFlags() & SPROP_UNSIGNED) { + if(pRealProxy == std_proxies->m_UInt8ToInt32) { + if(pProp->m_nBits == 1) { + #if defined _DEBUG + printf("bool (bits == 1)\n"); + #endif + return prop_types::bool_; + } + + #if defined _DEBUG + printf("unsigned char (std proxy)\n"); + #endif + return prop_types::unsigned_char; + } else if(pRealProxy == std_proxies->m_UInt16ToInt32) { + #if defined _DEBUG + printf("unsigned short (std proxy)\n"); + #endif + return prop_types::unsigned_short; + } else if(pRealProxy == std_proxies->m_UInt32ToInt32) { + if(pTable && strcmp(pTable->GetName(), "DT_BaseEntity") == 0 && strcmp(pProp->GetName(), "m_clrRender") == 0) { + #if defined _DEBUG + printf("color32 (hardcode)\n"); + #endif + return prop_types::color32_; + } + + #if defined _DEBUG + printf("unsigned int (std proxy)\n"); + #endif + return prop_types::unsigned_int; + } else { + if(SendProxy_Color32ToInt_ptr && pRealProxy == SendProxy_Color32ToInt_ptr) { + return prop_types::color32_; + } else if(SendProxy_EHandleToInt_ptr && pRealProxy == SendProxy_EHandleToInt_ptr) { + return prop_types::ehandle; + } + + { + if(pProp->m_nBits == 32) { + struct dummy_t { + unsigned int val{256}; + } dummy; + + DVariant out{}; + pRealProxy(pProp, static_cast(&dummy), static_cast(&dummy.val), &out, 0, -1); + if(out.m_Int == 65536) { + #if defined _DEBUG + printf("color32 (proxy)\n"); + #endif + return prop_types::color32_; + } + } + } + + { + if(pProp->m_nBits == NUM_NETWORKED_EHANDLE_BITS) { + struct dummy_t { + EHANDLE val{}; + } dummy; + + DVariant out{}; + pRealProxy(pProp, static_cast(&dummy), static_cast(&dummy.val), &out, 0, -1); + if(out.m_Int == INVALID_NETWORKED_EHANDLE_VALUE) { + #if defined _DEBUG + printf("ehandle (proxy)\n"); + #endif + return prop_types::ehandle; + } + } + } + + #if defined _DEBUG + printf("unsigned int (flag)\n"); + #endif + return prop_types::unsigned_int; + } + } else { + if(pRealProxy == std_proxies->m_Int8ToInt32) { + #if defined _DEBUG + printf("char (std proxy)\n"); + #endif + return prop_types::char_; + } else if(pRealProxy == std_proxies->m_Int16ToInt32) { + #if defined _DEBUG + printf("short (std proxy)\n"); + #endif + return prop_types::short_; + } else if(pRealProxy == std_proxies->m_Int32ToInt32) { + #if defined _DEBUG + printf("int (std proxy)\n"); + #endif + return prop_types::int_; + } else { + { + struct dummy_t { + short val{SHRT_MAX-1}; + } dummy; + + DVariant out{}; + pRealProxy(pProp, static_cast(&dummy), static_cast(&dummy.val), &out, 0, -1); + if(out.m_Int == dummy.val+1) { + #if defined _DEBUG + printf("short (proxy)\n"); + #endif + return prop_types::short_; + } + } + + #if defined _DEBUG + printf("int (type)\n"); + #endif + 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: { + if(SendProxy_StringT_To_String_ptr && pRealProxy == SendProxy_StringT_To_String_ptr) { + return prop_types::tstring; + } else { + return prop_types::cstring; + } + } + case DPT_Array: + return prop_types::unknown; + case DPT_DataTable: + return prop_types::unknown; + } + + return prop_types::unknown; +} + +proxysend::prop_types Sample::guess_prop_type(const SendProp *prop, const SendTable *table) const noexcept +{ + return ::guess_prop_type(prop, table); +} + +template +class thread_var_base +{ +protected: + using ptr_ret_t = std::conditional_t::value, T, T *>; + +public: + inline void reset(std::nullptr_t) noexcept + { reset_ptr(nullptr); } + + thread_var_base &operator=(std::nullptr_t) noexcept + { + reset_ptr(nullptr); + return *this; + } + + inline thread_var_base(std::nullptr_t) noexcept + : thread_var_base{} + { + } + + inline thread_var_base() noexcept + { + } + + inline bool operator!() const noexcept; + + inline ptr_ret_t operator->() noexcept + { return get_ptr(); } + + inline ~thread_var_base() noexcept + { + if(allocated_) { + unallocate(); + } + } + +protected: + static constexpr const pthread_key_t invalid_key{PTHREAD_KEYS_MAX+1}; + + pthread_key_t key{invalid_key}; + bool allocated_{false}; + + static void dtor(void *ptr) noexcept + { delete reinterpret_cast(ptr); } + + T *get_ptr_raw() const noexcept + { + if(!allocated_) { + return nullptr; + } + return reinterpret_cast(pthread_getspecific(key)); + } + + ptr_ret_t get_ptr() const noexcept + { + T *ptr{get_ptr_raw()}; + if(!ptr) { + return nullptr; + } + #if 0 + if constexpr(std::is_pointer_v) { + return *ptr; + } else + #endif + { + return ptr; + } + } + + T *get_or_allocate_ptr() noexcept + { + if(!allocated_ && !allocate()) { + return nullptr; + } + T *ptr{reinterpret_cast(pthread_getspecific(key))}; + if(!ptr) { + ptr = new T{}; + pthread_setspecific(key, ptr); + } + return ptr; + } + + bool allocate() noexcept + { + if(!__sync_bool_compare_and_swap(&allocated_, 0, 1)) { + return true; + } + if(pthread_key_create(&key, dtor) != 0) { + return false; + } + pthread_setspecific(key, new T{}); + return true; + } + + bool unallocate() noexcept + { + if(!__sync_bool_compare_and_swap(&allocated_, 1, 0)) { + return true; + } + T *old_ptr{reinterpret_cast(pthread_getspecific(key))}; + if(old_ptr) { + delete old_ptr; + } + return (pthread_key_delete(key) == 0); + } + + void reset_ptr(T *ptr) noexcept + { + T *old_ptr{get_ptr_raw()}; + if(old_ptr) { + delete old_ptr; + } + if(!allocated_ && !allocate()) { + return; + } + pthread_setspecific(key, ptr); + } + +private: + thread_var_base(const thread_var_base &) = delete; + thread_var_base &operator=(const thread_var_base &) = delete; + thread_var_base(thread_var_base &&) = delete; + thread_var_base &operator=(thread_var_base &&) = delete; +}; + +template <> +inline bool thread_var_base::operator!() const noexcept +{ + const bool *ptr{get_ptr_raw()}; + return (!ptr || !*ptr); +} + +template +inline bool thread_var_base::operator!() const noexcept +{ return get_ptr() == nullptr; } + +template +class thread_var final : public thread_var_base +{ +public: + using thread_var_base::thread_var_base; + using thread_var_base::reset; + + template + T &reset(Args &&...args) noexcept + { + T *ptr{this->get_or_allocate_ptr()}; + ptr->~T(); + new (ptr) T{std::forward(args)...}; + return *ptr; + } + + inline thread_var() noexcept + : thread_var_base{} + { + } + + inline thread_var(const T &val) noexcept + { *this->get_or_allocate_ptr() = val; } + + inline thread_var(T &&val) noexcept + { *this->get_or_allocate_ptr() = std::move(val); } + + template + inline thread_var(Args &&...args) noexcept + { + T *ptr{this->get_or_allocate_ptr()}; + ptr->~T(); + new (ptr) T{std::forward(args)...}; + } + + thread_var &operator=(std::nullptr_t) noexcept + { + this->reset_ptr(nullptr); + return *this; + } + + thread_var &operator=(const T &val) noexcept + { + *this->get_or_allocate_ptr() = val; + return *this; + } + + thread_var &operator=(T &&val) noexcept + { + *this->get_or_allocate_ptr() = std::move(val); + return *this; + } + + inline T &operator*() noexcept + { return *this->get_ptr_raw(); } + + inline T &get() noexcept + { return *this->get_ptr_raw(); } + + inline operator T &() noexcept + { return *this->get_ptr_raw(); } + + inline explicit operator bool() const noexcept + { return this->get_ptr() != nullptr; } +}; + +template <> +class thread_var final : public thread_var_base +{ +public: + using thread_var_base::thread_var_base; + using thread_var_base::reset; + + inline thread_var() + : thread_var_base{} + { + } + + inline bool operator*() const noexcept + { return get(); } + + inline bool get() const noexcept + { + const bool *ptr{get_ptr_raw()}; + return (ptr && *ptr); + } + + inline operator bool() const noexcept + { return get(); } + + bool reset(bool val) noexcept + { + set_value(val); + return val; + } + + thread_var &operator=(std::nullptr_t) noexcept + { + set_value(false); + return *this; + } + + inline thread_var(bool val) noexcept + { set_value(val); } + + thread_var &operator=(bool val) noexcept + { + set_value(val); + return *this; + } + +private: + void set_value(bool val) noexcept + { + *this->get_or_allocate_ptr() = val; + } +}; + +CBaseEntity *ReferenceToEntity(unsigned long ref) +{ + union { + cell_t sp_val; + unsigned long e_val; + } u; + + u.e_val = ref; + + return gamehelpers->ReferenceToEntity(u.sp_val); +} + +unsigned long EntityToReference(CBaseEntity *ent) +{ + union { + cell_t sp_val; + unsigned long e_val; + } u; + + u.sp_val = gamehelpers->EntityToReference(ent); + + return u.e_val; +} + +unsigned long IndexToReference(int objectID) +{ + union { + cell_t sp_val; + unsigned long e_val; + } u; + + u.sp_val = gamehelpers->IndexToReference(objectID); + + return u.e_val; +} + +struct packed_entity_data_t final +{ + packed_entity_data_t(packed_entity_data_t &&other) noexcept + { operator=(std::move(other)); } + packed_entity_data_t &operator=(packed_entity_data_t &&other) noexcept { + packedData = other.packedData; + other.packedData = nullptr; + writeBuf = other.writeBuf; + other.writeBuf = nullptr; + ref = other.ref; + other.ref = INVALID_EHANDLE_INDEX; + return *this; + } + + char *packedData{nullptr}; + bf_write *writeBuf{nullptr}; + unsigned long ref{INVALID_EHANDLE_INDEX}; + + bool allocated() const noexcept + { return (packedData && writeBuf); } + + bool written() const noexcept + { return allocated() && (writeBuf->GetNumBitsWritten() > 0); } + + packed_entity_data_t() noexcept = default; + ~packed_entity_data_t() noexcept { + reset(); + } + + void reset() noexcept { + if(writeBuf) { + delete writeBuf; + writeBuf = nullptr; + } + if(packedData) { + free(packedData); + packedData = nullptr; + } + } + + void allocate() noexcept { + reset(); + + packedData = static_cast(aligned_alloc(4, MAX_PACKEDENTITY_DATA)); + memset(packedData, 0x0, MAX_PACKEDENTITY_DATA); + writeBuf = new bf_write{"SV_PackEntity->writeBuf", packedData, 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{}; + std::vector entities{}; + int snapshot_index{-1}; + + pack_entity_params_t(std::vector &&slots_, std::vector &&entities_, int snapshot_index_) noexcept + : slots{std::move(slots_)}, entities{std::move(entities_)}, snapshot_index{snapshot_index_} + { + entity_data.resize(slots.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_var in_compute_packs{}; +static thread_var do_calc_delta{}; +static thread_var do_writedelta_entities{}; +static thread_var writedeltaentities_client{}; +static thread_var sendproxy_client_slot{}; + +static std::unique_ptr packentity_params{}; + +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); +} + +struct prop_reference_t +{ + prop_reference_t(SendProp *pProp, prop_types type) noexcept + { + restores_t::iterator it_restore{restores.find(pProp)}; + if(it_restore == restores.end()) { + std::unique_ptr ptr{new proxyrestore_t{pProp, type}}; + it_restore = restores.emplace(std::pair>{pProp, std::move(ptr)}).first; + } + restore = it_restore->second.get(); + ++restore->ref; + #ifdef _DEBUG + printf("added ref %zu for %s %p\n", restore->ref, pProp->GetName(), pProp); + #endif + } + + virtual ~prop_reference_t() noexcept + { + if(restore) { + #ifdef _DEBUG + printf("removed ref %zu for %s %p\n", restore->ref-1u, restore->pProp->GetName(), restore->pProp); + #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; + +struct opaque_ptr final +{ + inline opaque_ptr(opaque_ptr &&other) noexcept + { operator=(std::move(other)); } + + template + static void del_hlpr_arr(void *ptr_) noexcept + { delete[] static_cast(ptr_); } + 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); + } + if(num > 1) { + ptr = static_cast(new T[num]); + for(size_t i = 0; i < num; ++i) { + new (&static_cast(ptr)[i]) T{std::forward(args)...}; + } + del_func = del_hlpr_arr; + } else { + ptr = static_cast(new T{std::forward(args)...}); + del_func = del_hlpr; + } + } + + void clear() noexcept { + if(del_func && ptr) { + del_func(ptr); + } + del_func = nullptr; + ptr = nullptr; + } + + 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}; +}; + +struct callback_t final : prop_reference_t +{ + callback_t(unsigned long ref_, SendProp *pProp, std::string &&name_, int element_, prop_types type_, std::size_t offset_) noexcept + : prop_reference_t{pProp, type_}, offset{offset_}, type{type_}, element{element_}, name{std::move(name_)}, prop{pProp}, ref{ref_} + { + if(type == prop_types::cstring || type == prop_types::tstring) { + fwd = forwards->CreateForwardEx(nullptr, ET_Hook, 6, nullptr, Param_Cell, Param_String, Param_String, Param_Cell, Param_Cell, Param_Cell); + } else if(type == prop_types::color32_) { + fwd = forwards->CreateForwardEx(nullptr, ET_Hook, 8, nullptr, Param_Cell, Param_String, Param_CellByRef, Param_CellByRef, Param_CellByRef, Param_CellByRef, 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: + case prop_types::bool_: + case prop_types::ehandle: { + 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, 5, nullptr, Param_Cell, Param_String, value_param_type, Param_Cell, Param_Cell); + } + } + + inline bool has_any_per_client_func() const noexcept + { return !per_client_funcs.empty(); } + + void change_edict_state() noexcept + { + if(ref != INVALID_EHANDLE_INDEX) { + CBaseEntity *pEntity{::ReferenceToEntity(ref)}; + if(pEntity) { + edict_t *edict{pEntity->GetNetworkable()->GetEdict()}; + if(edict) { + gamehelpers->SetEdictStateChanged(edict, offset); + } + } + } + } + + void add_function(IPluginFunction *func, bool per_client) noexcept + { + fwd->RemoveFunction(func); + fwd->AddFunction(func); + + if(per_client) { + bool found{false}; + + per_client_funcs_t::const_iterator it_func{per_client_funcs.cbegin()}; + while(it_func != per_client_funcs.cend()) { + if(it_func->func == func) { + found = true; + break; + } + ++it_func; + } + + if(!found) { + per_client_funcs.emplace_back(per_client_func_t{func}); + } + } + } + + void remove_function(IPluginFunction *func) noexcept + { + fwd->RemoveFunction(func); + + per_client_funcs_t::const_iterator it_func{per_client_funcs.cbegin()}; + while(it_func != per_client_funcs.cend()) { + if(it_func->func == func) { + per_client_funcs.erase(it_func); + break; + } + ++it_func; + } + } + + void remove_functions_of_plugin(IPlugin *plugin) noexcept + { + per_client_funcs_t::const_iterator it_func{per_client_funcs.cbegin()}; + while(it_func != per_client_funcs.cend()) { + if((*it_func).func->GetParentContext() == plugin->GetBaseContext()) { + it_func = per_client_funcs.erase(it_func); + continue; + } + ++it_func; + } + + fwd->RemoveFunctionsOfPlugin(plugin); + } + + ~callback_t() noexcept override final { + if(fwd) { + forwards->ReleaseForward(fwd); + } + } + + static int get_current_client_slot() noexcept + { + if(!sendproxy_client_slot) { + return -1; + } + + return sendproxy_client_slot; + } + + static int get_current_client_entity() noexcept + { + int slot{get_current_client_slot()}; + if(slot == -1) { + return -1; + } + + return slot+1; + } + + bool fwd_call_ehandle(int client, const SendProp *pProp, const void *old_pData, opaque_ptr &new_pData, int objectID) const noexcept + { + fwd->PushCell(objectID); + fwd->PushStringEx((char *)name.c_str(), name.size()+1, SM_PARAM_STRING_COPY|SM_PARAM_STRING_UTF8, 0); + const EHANDLE &hndl{*reinterpret_cast(old_pData)}; + CBaseEntity *pEntity = hndl.Get(); + cell_t sp_value{pEntity ? gamehelpers->EntityToBCompatRef(pEntity) : -1}; + fwd->PushCellByRef(&sp_value); + fwd->PushCell(element); + fwd->PushCell(client); + cell_t res{Pl_Continue}; + fwd->Execute(&res); + if(res == Pl_Changed) { + new_pData.emplace(1); + EHANDLE &new_value{new_pData.get(0)}; + pEntity = gamehelpers->ReferenceToEntity(sp_value); + if(pEntity) { + new_value = pEntity->GetRefEHandle(); + } else { + new_value.Term(); + } + return true; + } + return false; + } + + bool fwd_call_color32(int client, const SendProp *pProp, const void *old_pData, opaque_ptr &new_pData, int objectID) const noexcept + { + fwd->PushCell(objectID); + fwd->PushStringEx((char *)name.c_str(), name.size()+1, SM_PARAM_STRING_COPY|SM_PARAM_STRING_UTF8, 0); + const color32 &clr{*reinterpret_cast(old_pData)}; + cell_t sp_r{static_cast(clr.r)}; + cell_t sp_g{static_cast(clr.g)}; + cell_t sp_b{static_cast(clr.b)}; + cell_t sp_a{static_cast(clr.a)}; + fwd->PushCellByRef(&sp_r); + fwd->PushCellByRef(&sp_g); + fwd->PushCellByRef(&sp_b); + fwd->PushCellByRef(&sp_a); + fwd->PushCell(element); + fwd->PushCell(client); + cell_t res{Pl_Continue}; + fwd->Execute(&res); + if(res == Pl_Changed) { + new_pData.emplace(1); + color32 &new_value{new_pData.get(0)}; + new_value.r = static_cast(sp_r); + new_value.g = static_cast(sp_g); + new_value.b = static_cast(sp_b); + new_value.a = static_cast(sp_a); + return true; + } + return false; + } + + template + bool fwd_call_int(int client, const SendProp *pProp, const void *old_pData, opaque_ptr &new_pData, int objectID) const noexcept + { + fwd->PushCell(objectID); + fwd->PushStringEx((char *)name.c_str(), name.size()+1, SM_PARAM_STRING_COPY|SM_PARAM_STRING_UTF8, 0); + cell_t sp_value{static_cast(*reinterpret_cast(old_pData))}; + fwd->PushCellByRef(&sp_value); + fwd->PushCell(element); + fwd->PushCell(client); + cell_t res{Pl_Continue}; + fwd->Execute(&res); + if(res == Pl_Changed) { + new_pData.emplace(1); + T &new_value{new_pData.get(0)}; + new_value = static_cast(sp_value); + return true; + } + return false; + } + + bool fwd_call_float(int client, const SendProp *pProp, const void *old_pData, opaque_ptr &new_pData, int objectID) const noexcept + { + fwd->PushCell(objectID); + fwd->PushStringEx((char *)name.c_str(), name.size()+1, SM_PARAM_STRING_COPY|SM_PARAM_STRING_UTF8, 0); + float sp_value{static_cast(*reinterpret_cast(old_pData))}; + fwd->PushFloatByRef(&sp_value); + fwd->PushCell(element); + fwd->PushCell(client); + cell_t res{Pl_Continue}; + fwd->Execute(&res); + if(res == Pl_Changed) { + new_pData.emplace(1); + float &new_value{new_pData.get(0)}; + new_value = sp_value; + return true; + } + return false; + } + + template + bool fwd_call_vec(int client, const SendProp *pProp, const void *old_pData, opaque_ptr &new_pData, int objectID) const noexcept + { + fwd->PushCell(objectID); + fwd->PushStringEx((char *)name.c_str(), name.size()+1, SM_PARAM_STRING_COPY|SM_PARAM_STRING_UTF8, 0); + const T &vec{*reinterpret_cast(old_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(element); + fwd->PushCell(client); + cell_t res{Pl_Continue}; + fwd->Execute(&res); + if(res == Pl_Changed) { + new_pData.emplace(1); + T &new_value{new_pData.get(0)}; + new_value.x = sp_ctof(sp_value[0]); + new_value.y = sp_ctof(sp_value[1]); + new_value.z = sp_ctof(sp_value[2]); + return true; + } + return false; + } + + bool fwd_call_str(int client, const SendProp *pProp, const void *old_pData, opaque_ptr &new_pData, int objectID) const noexcept + { + fwd->PushCell(objectID); + fwd->PushStringEx((char *)name.c_str(), name.size()+1, SM_PARAM_STRING_COPY|SM_PARAM_STRING_UTF8, 0); + static char sp_value[4096]; + const char *str{reinterpret_cast(old_pData)}; + size_t len{strlen(str)}; + strcpy(sp_value, str); + fwd->PushStringEx(sp_value, len, SM_PARAM_STRING_UTF8|SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK); + fwd->PushCell(sizeof(sp_value)); + fwd->PushCell(element); + fwd->PushCell(client); + cell_t res{Pl_Continue}; + fwd->Execute(&res); + if(res == Pl_Changed) { + new_pData.emplace(strlen(sp_value)+1); + char *new_value{new_pData.get()}; + strcpy(new_value, new_value); + return true; + } + return false; + } + + bool fwd_call_tstr(int client, const SendProp *pProp, const void *old_pData, opaque_ptr &new_pData, int objectID) const noexcept + { + fwd->PushCell(objectID); + fwd->PushStringEx((char *)name.c_str(), name.size()+1, SM_PARAM_STRING_COPY|SM_PARAM_STRING_UTF8, 0); + static char sp_value[4096]; + const char *str{STRING(*reinterpret_cast(old_pData))}; + size_t len{strlen(str)}; + strcpy(sp_value, str); + fwd->PushStringEx(sp_value, len, SM_PARAM_STRING_UTF8|SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK); + fwd->PushCell(sizeof(sp_value)); + fwd->PushCell(element); + fwd->PushCell(client); + cell_t res{Pl_Continue}; + fwd->Execute(&res); + if(res == Pl_Changed) { + new_pData.emplace(1); + string_t &new_value{new_pData.get(0)}; + new_value = MAKE_STRING(sp_value); + return true; + } + return false; + } + + bool can_call_fwd(int client) const noexcept + { + if(!fwd || (has_any_per_client_func() && client == -1)) { + return false; + } + return true; + } + + bool fwd_call(int client, const SendProp *pProp, const void *old_pData, opaque_ptr &new_pData, int objectID) const noexcept + { + switch(type) { + case prop_types::int_: + return fwd_call_int(client, pProp, old_pData, new_pData, objectID); + case prop_types::bool_: + return fwd_call_int(client, pProp, old_pData, new_pData, objectID); + case prop_types::short_: + return fwd_call_int(client, pProp, old_pData, new_pData, objectID); + case prop_types::char_: + return fwd_call_int(client, pProp, old_pData, new_pData, objectID); + case prop_types::unsigned_int: + return fwd_call_int(client, pProp, old_pData, new_pData, objectID); + case prop_types::unsigned_short: + return fwd_call_int(client, pProp, old_pData, new_pData, objectID); + case prop_types::unsigned_char: + return fwd_call_int(client, pProp, old_pData, new_pData, objectID); + case prop_types::float_: + return fwd_call_float(client, pProp, old_pData, new_pData, objectID); + case prop_types::vector: + return fwd_call_vec(client, pProp, old_pData, new_pData, objectID); + case prop_types::qangle: + return fwd_call_vec(client, pProp, old_pData, new_pData, objectID); + case prop_types::color32_: + return fwd_call_color32(client, pProp, old_pData, new_pData, objectID); + case prop_types::ehandle: + return fwd_call_ehandle(client, pProp, old_pData, new_pData, objectID); + case prop_types::cstring: + return fwd_call_str(client, pProp, old_pData, new_pData, objectID); + case prop_types::tstring: + return fwd_call_tstr(client, pProp, old_pData, new_pData, objectID); + } + return false; + } + + void proxy_call(const SendProp *pProp, const void *pStructBase, const void *pOldData, const void *pNewData, DVariant *pOut, int iElement, int objectID) const noexcept + { + #if SOURCE_ENGINE == SE_TF2 + if(is_prop_cond(pProp)) { + DVariant ignore{}; + restore->pRealProxy(nullptr, nullptr, pOldData, &ignore, -1, -1); + std_proxies->m_UInt32ToInt32(pProp, pStructBase, pNewData, pOut, iElement, objectID); + } else + #endif + { + restore->pRealProxy(pProp, pStructBase, pNewData, pOut, iElement, objectID); + } + } + + 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; + type = other.type; + ref = other.ref; + other.ref = INVALID_EHANDLE_INDEX; + name = std::move(other.name); + element = other.element; + other.element = 0; + per_client_funcs = std::move(other.per_client_funcs); + return *this; + } + + IChangeableForward *fwd{nullptr}; + std::size_t offset{-1}; + prop_types type{prop_types::unknown}; + int element{0}; + std::string name{}; + SendProp *prop{nullptr}; + unsigned long ref{INVALID_EHANDLE_INDEX}; + + struct per_client_func_t + { + IPluginFunction *func{nullptr}; + + inline per_client_func_t(IPluginFunction *func_) noexcept + : func{func_} + { + } + + inline per_client_func_t(per_client_func_t &&other) noexcept + { operator=(std::move(other)); } + + per_client_func_t &operator=(per_client_func_t &&other) noexcept + { + func = other.func; + other.func = nullptr; + return *this; + } + + private: + per_client_func_t(const per_client_func_t &) = delete; + per_client_func_t &operator=(const per_client_func_t &) = delete; + per_client_func_t() = delete; + }; + + using per_client_funcs_t = std::vector; + per_client_funcs_t per_client_funcs{}; + +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; + unsigned long ref{INVALID_EHANDLE_INDEX}; + + inline proxyhook_t(unsigned long ref_) noexcept + : ref{ref_} + { + } + + inline ~proxyhook_t() noexcept + { + } + + void add_callback(SendProp *pProp, std::string &&name, int element, prop_types type, int offset, IPluginFunction *func, bool per_client) noexcept + { + callbacks_t::iterator it_callback{callbacks.find(pProp)}; + if(it_callback == callbacks.end()) { + it_callback = callbacks.emplace(std::pair{pProp, callback_t{ref, pProp, std::move(name), element, type, offset}}).first; + } + + it_callback->second.add_function(func, per_client); + } + + inline proxyhook_t(proxyhook_t &&other) noexcept + { operator=(std::move(other)); } + + proxyhook_t &operator=(proxyhook_t &&other) noexcept + { + callbacks = std::move(other.callbacks); + ref = other.ref; + other.ref = INVALID_EHANDLE_INDEX; + 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) +{ + do_calc_delta = false; + + if(!packentity_params || !in_compute_packs) { + return DETOUR_STATIC_CALL(SendTable_Encode)(pTable, pStruct, pOut, objectID, pRecipients, bNonZeroOnly); + } + + { + sendproxy_client_slot = -1; + if(!DETOUR_STATIC_CALL(SendTable_Encode)(pTable, pStruct, pOut, objectID, pRecipients, bNonZeroOnly)) { + Host_Error( "SV_PackEntity: SendTable_Encode returned false (ent %d).\n", objectID ); + return false; + } + } + + unsigned long ref{::IndexToReference(objectID)}; + + const std::vector &entities{packentity_params->entities}; + if(std::find(entities.cbegin(), entities.cend(), ref) != entities.cend()) { + const std::size_t slots_size{packentity_params->slots.size()}; + for(std::size_t i{0}; i < slots_size; ++i) { + std::vector &vec{packentity_params->entity_data[i]}; + vec.emplace_back(); + packed_entity_data_t &packedData{vec.back()}; + + packedData.ref = ref; + packedData.allocate(); + + sendproxy_client_slot = packentity_params->slots[i]; + const bool encoded{DETOUR_STATIC_CALL(SendTable_Encode)(pTable, pStruct, packedData.writeBuf, objectID, pRecipients, bNonZeroOnly)}; + sendproxy_client_slot = -1; + if(!encoded) { + Host_Error( "SV_PackEntity: SendTable_Encode returned false (ent %d).\n", objectID ); + return false; + } + } + do_calc_delta = true; + } + + 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(!packentity_params || !in_compute_packs || !do_calc_delta) { + return DETOUR_STATIC_CALL(SendTable_CalcDelta)(pTable, pFromState, nFromBits, pToState, nToBits, pDeltaProps, nMaxDeltaProps, objectID); + } + + do_calc_delta = false; + + int global_nChanges{DETOUR_STATIC_CALL(SendTable_CalcDelta)(pTable, pFromState, nFromBits, pToState, nToBits, pDeltaProps, nMaxDeltaProps, objectID)}; + int total_nChanges{global_nChanges}; + + if(total_nChanges < nMaxDeltaProps) { + int *client_deltaProps{new int[nMaxDeltaProps]{}}; + + int new_nChanges{total_nChanges}; + + unsigned long ref = ::IndexToReference(objectID); + + const std::size_t slots_size{packentity_params->slots.size()}; + for(std::size_t i{0}; i < slots_size; ++i) { + using entity_data_t = std::vector; + entity_data_t &entity_data{packentity_params->entity_data[i]}; + + packed_entity_data_t *packedData{nullptr}; + for(entity_data_t::reverse_iterator it{entity_data.rbegin()}; it != entity_data.rend(); ++it) { + if(it->ref == ref) { + packedData = &(*it); + break; + } + } + + if(!packedData || !packedData->written()) { + continue; + } + + const int client_nChanges{DETOUR_STATIC_CALL(SendTable_CalcDelta)(pTable, pFromState, nFromBits, packedData->packedData, packedData->writeBuf->GetNumBitsWritten(), client_deltaProps, nMaxDeltaProps, objectID)}; + int client_nChanges_new{0}; + + bool done{false}; + + for(int j{0}; j < client_nChanges; ++j) { + bool found{false}; + for(int k{0}; k < new_nChanges; ++k) { + if(pDeltaProps[k] == client_deltaProps[j]) { + found = true; + break; + } + } + if(!found) { + ++client_nChanges_new; + pDeltaProps[total_nChanges++] = client_deltaProps[j]; + if(total_nChanges >= nMaxDeltaProps) { + done = true; + break; + } + } + } + + if(done) { + break; + } + + new_nChanges += client_nChanges_new; + } + + delete[] client_deltaProps; + } + + if(total_nChanges > nMaxDeltaProps) { + total_nChanges = nMaxDeltaProps; + } + + return total_nChanges; +} + +class CFrameSnapshot +{ +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_MEMBER2(CFrameSnapshotManager_GetPackedEntity, PackedEntity *, CFrameSnapshot *, pSnapshot, int, entity) +{ + if(!pSnapshot || !packentity_params || !writedeltaentities_client || packentity_params->snapshot_index != pSnapshot->m_ListIndex) { + return DETOUR_MEMBER_CALL(CFrameSnapshotManager_GetPackedEntity)(pSnapshot, entity); + } + + PackedEntity *packed{DETOUR_MEMBER_CALL(CFrameSnapshotManager_GetPackedEntity)(pSnapshot, entity)}; + if(!packed) { + return nullptr; + } + + const int slot{writedeltaentities_client}; + + unsigned long ref{::IndexToReference(entity)}; + + const packed_entity_data_t *packedData{nullptr}; + const std::size_t slots_size{packentity_params->slots.size()}; + for(std::size_t i{0}; i < slots_size; ++i) { + if(packentity_params->slots[i] == slot) { + const std::vector &entity_data{packentity_params->entity_data[i]}; + for(const packed_entity_data_t &it : entity_data) { + if(it.ref == ref) { + if(it.written()) { + packedData = ⁢ + } + break; + } + } + break; + } + } + + if(packedData) { + if(packed->GetData()) { + packed->FreeData(); + } + + packed->AllocAndCopyPadded(packedData->packedData, packedData->writeBuf->GetNumBytesWritten()); + } + + return packed; +} + +static ConVar *sv_stressbots{nullptr}; + +static bool is_client_valid(CBaseClient *client) noexcept +{ + if(client->IsHLTV() || + client->IsReplay()) { + return false; + } + + if(!sv_stressbots->GetBool()) { + if(client->IsFakeClient()) { + return false; + } + } + + if(!client->IsConnected() || + !client->IsSpawned() || + !client->IsActive()) { + return false; + } + + return true; +} + +#include + +class CBaseServer : public IServer +{ +public: + virtual float GetCPUUsage( void ) = 0; + virtual void BroadcastPrintf ( PRINTF_FORMAT_STRING const char *fmt, ...) = 0; + virtual void SetMaxClients( int number ) = 0; + virtual void WriteDeltaEntities( CBaseClient *client, CClientFrame *to, CClientFrame *from, bf_write &pBuf ) = 0; +}; + +void PreWriteDeltaEntities(CBaseClient *client) +{ + if(do_writedelta_entities) { + if(!is_client_valid(client)) { + writedeltaentities_client = -1; + } else { + writedeltaentities_client = client->GetPlayerSlot(); + } + } + + for(auto it : g_Sample.pack_ent_listeners) { + it->pre_write_deltas(); + } +} + +void PostWriteDeltaEntities() +{ + for(auto it : g_Sample.pack_ent_listeners) { + it->post_write_deltas(); + } + + if(do_writedelta_entities) { + writedeltaentities_client = -1; + } +} + +DETOUR_DECL_MEMBER4(CBaseServer_WriteDeltaEntities, void, CBaseClient *, client, CClientFrame *, to, CClientFrame *, from, bf_write &, pBuf) +{ + CBaseServer *pthis = (CBaseServer *)this; + + if(pthis->IsHLTV() || + pthis->IsReplay()) { + DETOUR_MEMBER_CALL(CBaseServer_WriteDeltaEntities)(client, to, from, pBuf); + } else { + PreWriteDeltaEntities(client); + DETOUR_MEMBER_CALL(CBaseServer_WriteDeltaEntities)(client, to, from, pBuf); + PostWriteDeltaEntities(); + } +} + +static CDetour *CBaseServer_WriteDeltaEntities_detour{nullptr}; + +static ConVar *sv_parallel_packentities{nullptr}; +static ConVar *sv_parallel_sendsnapshot{nullptr}; + +DETOUR_DECL_STATIC0(InvalidateSharedEdictChangeInfos, void) +{ + DETOUR_STATIC_CALL(InvalidateSharedEdictChangeInfos)(); +} + +static std::thread::id main_thread_id; + +struct PackWork_t; + +DETOUR_DECL_STATIC1(PackWork_tProcess, void, PackWork_t &, item) +{ + DETOUR_STATIC_CALL(PackWork_tProcess)(item); +} + +DETOUR_DECL_STATIC4(SV_PackEntity, void, int, edictIdx, edict_t *, edict, ServerClass *, pServerClass, CFrameSnapshot *, pSnapshot) +{ + DETOUR_STATIC_CALL(SV_PackEntity)(edictIdx, edict, pServerClass, pSnapshot); +} + +static CDetour *SendTable_CalcDelta_detour{nullptr}; +static CDetour *SendTable_Encode_detour{nullptr}; +static CDetour *CFrameSnapshotManager_GetPackedEntity_detour{nullptr}; + +DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, clientCount, CGameClient **, clients, CFrameSnapshot *, snapshot) +{ + packentity_params.reset(nullptr); + + std::vector slots{}; + + for(int i{0}; i < clientCount; ++i) { + CGameClient *client{clients[i]}; + if(!is_client_valid(client)) { + continue; + } + slots.emplace_back(client->GetPlayerSlot()); + } + const std::size_t slots_size{slots.size()}; + + std::vector entities{}; + + bool any_hook{false}; + + const hooks_t &chooks{hooks}; + + for(int i{0}; i < snapshot->m_nValidEntities; ++i) { + int idx{snapshot->m_pValidEntities[i]}; + unsigned long ref{::IndexToReference(idx)}; + + for(auto it : g_Sample.pack_ent_listeners) { + CBaseEntity *pEntity{::ReferenceToEntity(ref)}; + if(pEntity) { + it->pre_pack_entity(pEntity); + } + } + + hooks_t::const_iterator it_hook{chooks.find(ref)}; + if(it_hook != chooks.cend()) { + if(!it_hook->second.callbacks.empty()) { + any_hook = true; + } + if(slots_size > 0) { + bool any_per_client_func{false}; + for(const auto &it_callback : it_hook->second.callbacks) { + if(it_callback.second.has_any_per_client_func()) { + any_per_client_func = true; + break; + } + } + if(any_per_client_func) { + entities.emplace_back(ref); + } + } + } + } + + const bool any_per_client_hook{slots_size > 0 && entities.size() > 0}; + +#if defined _DEBUG && 0 + printf("slots = %i, entities = %i\n", slots.size(), entities.size()); + printf("any_hook = %i, any_per_client_hook = %i, is_parallel_pack_allowed = %i\n", any_hook, any_per_client_hook, g_Sample.is_parallel_pack_allowed()); +#endif + + if(any_per_client_hook) { + packentity_params.reset(new pack_entity_params_t{std::move(slots), std::move(entities), snapshot->m_ListIndex}); + SendTable_Encode_detour->EnableDetour(); + SendTable_CalcDelta_detour->EnableDetour(); + CFrameSnapshotManager_GetPackedEntity_detour->EnableDetour(); + do_writedelta_entities = true; + } else { + do_writedelta_entities = false; + CFrameSnapshotManager_GetPackedEntity_detour->DisableDetour(); + SendTable_Encode_detour->DisableDetour(); + SendTable_CalcDelta_detour->DisableDetour(); + } + + const bool parallel_pack{ + !any_hook && + g_Sample.is_parallel_pack_allowed() + }; + + //sv_parallel_sendsnapshot->SetValue(false); + sv_parallel_packentities->SetValue(parallel_pack); + + in_compute_packs = true; + DETOUR_STATIC_CALL(SV_ComputeClientPacks)(clientCount, clients, snapshot); + in_compute_packs = false; + + if(!sv_parallel_packentities->GetBool() && any_per_client_hook) { + SendTable_Encode_detour->DisableDetour(); + SendTable_CalcDelta_detour->DisableDetour(); + } +} + +bool Sample::is_parallel_pack_allowed() const noexcept +{ + return !std::any_of(pack_ent_listeners.cbegin(), pack_ent_listeners.cend(), + [](const parallel_pack_listener *listener) noexcept -> bool { + return !listener->is_allowed(); + } + ); +} + +bool Sample::add_listener(const parallel_pack_listener *ptr) noexcept +{ + if(std::find(pack_ent_listeners.cbegin(), pack_ent_listeners.cend(), ptr) != pack_ent_listeners.cend()) { + return false; + } + + pack_ent_listeners.emplace_back(ptr); + return true; +} + +bool Sample::remove_listener(const parallel_pack_listener *ptr) noexcept +{ + pack_ent_listeners_t::const_iterator it{std::find(pack_ent_listeners.cbegin(), pack_ent_listeners.cend(), ptr)}; + if(it == pack_ent_listeners.cend()) { + return false; + } + + pack_ent_listeners.erase(it); + return true; +} + +static void global_send_proxy(const SendProp *pProp, const void *pStructBase, const void *pData, DVariant *pOut, int iElement, int objectID) +{ + proxyrestore_t *restore{nullptr}; + + if(objectID != -1) { + const hooks_t &chooks{hooks}; + unsigned long ref{::IndexToReference(objectID)}; + hooks_t::const_iterator it_hook{chooks.find(ref)}; + if(it_hook != chooks.cend()) { + callbacks_t::const_iterator it_callback{it_hook->second.callbacks.find(pProp)}; + if(it_callback != it_hook->second.callbacks.cend()) { + restore = it_callback->second.restore; + const int client{callback_t::get_current_client_entity()}; + if(it_callback->second.can_call_fwd(client)) { + if(std::this_thread::get_id() == main_thread_id) { + opaque_ptr new_data{}; + if(it_callback->second.fwd_call(client, pProp, pData, new_data, objectID)) { + it_callback->second.proxy_call(pProp, pStructBase, pData, new_data.get(), pOut, iElement, objectID); + return; + } + } + } + } + } + } + + if(!restore) { + const restores_t &crestores{restores}; + restores_t::const_iterator it_restore{crestores.find(const_cast(pProp))}; + if(it_restore != crestores.cend()) { + restore = it_restore->second.get(); + } + } + + if(restore) { + restore->pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID); + } +} + +static void game_frame(bool simulating) noexcept +{ + if(!simulating) { + return; + } + + // dumb nonsense so clients are fully aware that our hooked edicts are changing. + // this looks hacky, and it is, but there is not a better way i could find + // after tearing my hair out for 3 days of research + // sappho.io + for (auto& hook : hooks) + { + unsigned long ref = hook.first; // hook.second.ref; + //for (auto& cb : hook.second.callbacks) + //{ + // cb.second.change_edict_state(); + //} + CBaseEntity* pEntity = ::ReferenceToEntity(ref); + if (!pEntity) + { + continue; + } + edict_t* edict = pEntity->GetNetworkable()->GetEdict(); + if (!edict) + { + continue; + } + gamehelpers->SetEdictStateChanged(edict, 0); + } +} + +DETOUR_DECL_MEMBER1(CGameServer_SendClientMessages, void, bool, bSendSnapshots) +{ + DETOUR_MEMBER_CALL(CGameServer_SendClientMessages)(bSendSnapshots); + + if(!sv_parallel_sendsnapshot->GetBool()) { + do_writedelta_entities = false; + CFrameSnapshotManager_GetPackedEntity_detour->DisableDetour(); + } + + packentity_params.reset(nullptr); +} + +struct sm_sendprop_info_ex_t final : sm_sendprop_info_t +{ + SendTable *table; +}; + +static int utlVecOffsetOffset{-1}; + +static bool UTIL_FindInSendTable(SendTable *pTable, + const char *name, + sm_sendprop_info_ex_t *info, + unsigned int offset) noexcept +{ + int props = pTable->GetNumProps(); + for (int i = 0; i < props; ++i) + { + SendProp *prop = pTable->GetProp(i); + + // Skip InsideArray props (SendPropArray / SendPropArray2), + // we'll find them later by their containing array. + if (prop->IsInsideArray()) { + continue; + } + + const char *pname = prop->GetName(); + SendTable *pInnerTable = prop->GetDataTable(); + + if (pname && strcmp(name, pname) == 0) + { + // get true offset of CUtlVector + if (utlVecOffsetOffset != -1 && prop->GetOffset() == 0 && pInnerTable && pInnerTable->GetNumProps()) + { + SendProp *pLengthProxy = pInnerTable->GetProp(0); + const char *ipname = pLengthProxy->GetName(); + if (ipname && strcmp(ipname, "lengthproxy") == 0 && pLengthProxy->GetExtraData()) + { + info->table = pTable; + info->prop = prop; + info->actual_offset = offset + *reinterpret_cast(reinterpret_cast(pLengthProxy->GetExtraData()) + utlVecOffsetOffset); + return true; + } + } + info->table = pTable; + info->prop = prop; + info->actual_offset = offset + info->prop->GetOffset(); + return true; + } + if (pInnerTable) + { + if (UTIL_FindInSendTable(pInnerTable, + name, + info, + offset + prop->GetOffset()) + ) + { + return true; + } + } + } + + return false; +} + +using propinfo_t = std::unordered_map; +using propinfos_t = std::unordered_map; +static propinfos_t propinfos; + +bool Sample::remove_serverclass_from_cache(ServerClass *pClass) noexcept +{ + propinfos_t::iterator it_props{propinfos.find(pClass)}; + if(it_props == propinfos.cend()) { + return false; + } + + propinfos.erase(it_props); + return true; +} + +static bool FindSendPropInfo(ServerClass *pClass, std::string &&name, sm_sendprop_info_ex_t *info) noexcept +{ + propinfos_t::iterator it_props{propinfos.find(pClass)}; + if(it_props == propinfos.cend()) { + it_props = propinfos.emplace(std::pair{pClass, propinfo_t{}}).first; + } + if(it_props != propinfos.cend()) { + propinfo_t::iterator it_prop{it_props->second.find(name)}; + if(it_prop == it_props->second.cend()) { + if(UTIL_FindInSendTable(pClass->m_pTable, name.c_str(), info, 0)) { + it_prop = it_props->second.emplace(std::pair{std::move(name), std::move(*info)}).first; + } + } + if(it_prop != it_props->second.cend()) { + *info = it_prop->second; + return true; + } + } + return false; +} + +static cell_t proxysend_handle_hook(IPluginContext *pContext, hooks_t::iterator it_hook, unsigned long ref, int offset, SendProp *pProp, std::string &&prop_name, int element, SendTable *pTable, IPluginFunction *callback, bool per_client) +{ + prop_types type{prop_types::unknown}; + restores_t::const_iterator it_restore{restores.find(pProp)}; + if(it_restore != restores.cend()) { + type = it_restore->second->type; + } else { + type = guess_prop_type(pProp, pTable); + } + if(type == prop_types::unknown) { + return pContext->ThrowNativeError("Unsupported prop"); + } + +#ifdef _DEBUG + printf("added %s %p hook for %i\n", pProp->GetName(), pProp, ref); +#endif + + it_hook->second.add_callback(pProp, std::move(prop_name), element, type, offset, callback, per_client); + + return 0; +} + +static cell_t proxysend_hook(IPluginContext *pContext, const cell_t *params) noexcept +{ + CBaseEntity *pEntity{gamehelpers->ReferenceToEntity(params[1])}; + if(!pEntity) { + return pContext->ThrowNativeError("Invalid Entity Reference/Index %i", params[1]); + } + + char *name_ptr; + pContext->LocalToString(params[2], &name_ptr); + std::string name{name_ptr}; + + IPluginFunction *callback{pContext->GetFunctionById(params[3])}; + + bool per_client = static_cast(params[4]); + + IServerNetworkable *pNetwork{pEntity->GetNetworkable()}; + ServerClass *pServer{pNetwork->GetServerClass()}; + + sm_sendprop_info_ex_t info{}; + if(!FindSendPropInfo(pServer, std::move(name), &info)) { + return pContext->ThrowNativeError("Could not find prop %s", name.c_str()); + } + SendTable *pTable{info.table}; + + SendProp *pProp{info.prop}; + std::string prop_name{pProp->GetName()}; + + unsigned long ref = ::EntityToReference(pEntity); + + hooks_t::iterator it_hook{hooks.find(ref)}; + if(it_hook == hooks.end()) { + it_hook = hooks.emplace(std::pair{ref, proxyhook_t{ref}}).first; + } + + edict_t *edict{pEntity->GetNetworkable()->GetEdict()}; + + if(pProp->GetType() == DPT_DataTable) { + SendTable *pPropTable{pProp->GetDataTable()}; + int NumProps{pPropTable->GetNumProps()}; + for(int i = 0; i < NumProps; ++i) { + SendProp *pChildProp{pPropTable->GetProp(i)}; + std::string tmp_name{prop_name}; + int offset{info.actual_offset + pChildProp->GetOffset()}; + cell_t ret{proxysend_handle_hook(pContext, it_hook, ref, offset, pChildProp, std::move(tmp_name), i, pTable, callback, per_client)}; + if(ret != 0) { + return ret; + } + if(edict) { + gamehelpers->SetEdictStateChanged(edict, offset); + } + } + return 0; + } + + cell_t ret{proxysend_handle_hook(pContext, it_hook, ref, info.actual_offset, pProp, std::move(prop_name), 0, pTable, callback, per_client)}; + if(ret == 0) { + if(edict) { + gamehelpers->SetEdictStateChanged(edict, info.actual_offset); + } + } + + return ret; +} + +static void proxysend_handle_unhook(hooks_t::iterator it_hook, unsigned long ref, const SendProp *pProp, const char *name, IPluginFunction *callback) +{ + callbacks_t::iterator it_callback{it_hook->second.callbacks.find(pProp)}; + if(it_callback != it_hook->second.callbacks.end()) { + it_callback->second.remove_function(callback); + #ifdef _DEBUG + printf("removed func from %s %p callback for %i\n", name, pProp, ref); + #endif + if(it_callback->second.fwd->GetFunctionCount() == 0) { + #ifdef _DEBUG + printf("removed callback %s %p for %i\n", name, pProp, ref); + #endif + it_hook->second.callbacks.erase(it_callback); + } + } +} + +static cell_t proxysend_unhook(IPluginContext *pContext, const cell_t *params) noexcept +{ + CBaseEntity *pEntity{gamehelpers->ReferenceToEntity(params[1])}; + if(!pEntity) { + return pContext->ThrowNativeError("Invalid Entity Reference/Index %i", params[1]); + } + + IServerNetworkable *pNetwork{pEntity->GetNetworkable()}; + ServerClass *pServer{pNetwork->GetServerClass()}; + + char *name_ptr; + pContext->LocalToString(params[2], &name_ptr); + + sm_sendprop_info_t info{}; + if(!gamehelpers->FindSendPropInfo(pServer->GetName(), name_ptr, &info)) { + return pContext->ThrowNativeError("Could not find prop %s", name_ptr); + } + + const SendProp *pProp{info.prop}; + + IPluginFunction *callback{pContext->GetFunctionById(params[3])}; + + unsigned long ref = gamehelpers->EntityToReference(pEntity); + + hooks_t::iterator it_hook{hooks.find(ref)}; + if(it_hook != hooks.end()) { + if(pProp->GetType() == DPT_DataTable) { + SendTable *pPropTable{pProp->GetDataTable()}; + int NumProps{pPropTable->GetNumProps()}; + for(int i = 0; i < NumProps; ++i) { + SendProp *pChildProp{pPropTable->GetProp(i)}; + proxysend_handle_unhook(it_hook, ref, pChildProp, name_ptr, callback); + } + } else { + proxysend_handle_unhook(it_hook, ref, pProp, name_ptr, callback); + } + if(it_hook->second.callbacks.empty()) { + hooks.erase(it_hook); + } + } + + edict_t *edict{pEntity->GetNetworkable()->GetEdict()}; + if(edict) { + gamehelpers->SetEdictStateChanged(edict, 0); + } + + return 0; +} + +static constexpr const sp_nativeinfo_t natives[]{ + {"proxysend_hook", proxysend_hook}, + {"proxysend_unhook", proxysend_unhook}, + {nullptr, nullptr} +}; + +static CDetour *SV_ComputeClientPacks_detour{nullptr}; +static CDetour *SV_PackEntity_detour{nullptr}; +static CDetour *PackWork_tProcess_detour{nullptr}; +static CDetour *InvalidateSharedEdictChangeInfos_detour{nullptr}; +static CDetour *CGameServer_SendClientMessages_detour{nullptr}; + +bool Sample::SDK_OnLoad(char *error, size_t maxlen, bool late) noexcept +{ + if(!gameconfs->LoadGameConfigFile("proxysend", &gameconf, error, maxlen)) { + return false; + } + + IGameConfig *coregameconf{nullptr}; + if(!gameconfs->LoadGameConfigFile("core.games/common.games", &coregameconf, error, maxlen)) { + return false; + } + + coregameconf->GetOffset("CSendPropExtra_UtlVector::m_Offset", &utlVecOffsetOffset); + + gameconfs->CloseGameConfigFile(coregameconf); + + gameconf->GetOffset("CBaseClient::UpdateSendState", &CBaseClient_UpdateSendState_idx); + if(CBaseClient_UpdateSendState_idx == -1) { + snprintf(error, maxlen, "could not get CBaseClient::UpdateSendState offset"); + return false; + } + + gameconf->GetOffset("CBaseClient::SendSnapshot", &CBaseClient_SendSnapshot_idx); + if(CBaseClient_SendSnapshot_idx == -1) { + snprintf(error, maxlen, "could not get CBaseClient::SendSnapshot offset"); + return false; + } + + gameconf->GetMemSig("CGameClient::GetSendFrame", &CGameClient_GetSendFrame_ptr); + if(CGameClient_GetSendFrame_ptr == nullptr) { + snprintf(error, maxlen, "could not get CGameClient::GetSendFrame address"); + return false; + } + + gameconf->GetMemSig("SendProxy_StringT_To_String", (void **)&SendProxy_StringT_To_String_ptr); + if(SendProxy_StringT_To_String_ptr == nullptr) { + snprintf(error, maxlen, "could not get SendProxy_StringT_To_String address"); + return false; + } + + gameconf->GetMemSig("SendProxy_Color32ToInt", (void **)&SendProxy_Color32ToInt_ptr); + gameconf->GetMemSig("SendProxy_EHandleToInt", (void **)&SendProxy_EHandleToInt_ptr); + + CDetourManager::Init(smutils->GetScriptingEngine(), gameconf); + + SendTable_CalcDelta_detour = DETOUR_CREATE_STATIC(SendTable_CalcDelta, "SendTable_CalcDelta"); + if(!SendTable_CalcDelta_detour) { + snprintf(error, maxlen, "could not create SendTable_CalcDelta detour"); + return false; + } + + SendTable_Encode_detour = DETOUR_CREATE_STATIC(SendTable_Encode, "SendTable_Encode"); + if(!SendTable_Encode_detour) { + snprintf(error, maxlen, "could not create SendTable_Encode detour"); + return false; + } + + SV_ComputeClientPacks_detour = DETOUR_CREATE_STATIC(SV_ComputeClientPacks, "SV_ComputeClientPacks"); + if(!SV_ComputeClientPacks_detour) { + snprintf(error, maxlen, "could not create SV_ComputeClientPacks detour"); + return false; + } + + CGameServer_SendClientMessages_detour = DETOUR_CREATE_MEMBER(CGameServer_SendClientMessages, "CGameServer::SendClientMessages"); + if(!CGameServer_SendClientMessages_detour) { + snprintf(error, maxlen, "could not create CGameServer::SendClientMessages detour"); + return false; + } + + CFrameSnapshotManager_GetPackedEntity_detour = DETOUR_CREATE_MEMBER(CFrameSnapshotManager_GetPackedEntity, "CFrameSnapshotManager::GetPackedEntity"); + if(!CFrameSnapshotManager_GetPackedEntity_detour) { + snprintf(error, maxlen, "could not create CFrameSnapshotManager::GetPackedEntity detour"); + return false; + } + +#if SOURCE_ENGINE == SE_TF2 || SOURCE_ENGINE == SE_CSS + { + void **vtable = *(void ***)server; + int index = vfunc_index(&CBaseServer::WriteDeltaEntities); + CBaseServer_WriteDeltaEntities_detour = DETOUR_CREATE_MEMBER(CBaseServer_WriteDeltaEntities, vtable[index]); + CBaseServer_WriteDeltaEntities_detour->EnableDetour(); + } +#endif + +#if 0 + //SV_PackEntity_detour = DETOUR_CREATE_STATIC(SV_PackEntity, "SV_PackEntity"); + //SV_PackEntity_detour->EnableDetour(); + + //PackWork_tProcess_detour = DETOUR_CREATE_STATIC(PackWork_tProcess, "PackWork_t::Process"); + //PackWork_tProcess_detour->EnableDetour(); + + //InvalidateSharedEdictChangeInfos_detour = DETOUR_CREATE_STATIC(InvalidateSharedEdictChangeInfos, "InvalidateSharedEdictChangeInfos"); + //InvalidateSharedEdictChangeInfos_detour->EnableDetour(); +#endif + + CGameServer_SendClientMessages_detour->EnableDetour(); + SV_ComputeClientPacks_detour->EnableDetour(); + + g_pEntityList = reinterpret_cast(gamehelpers->GetGlobalEntityList()); + + sharesys->AddDependency(myself, "sdkhooks.ext", true, true); + + sharesys->AddInterface(myself, this); + sharesys->RegisterLibrary(myself, "proxysend"); + + smutils->AddGameFrameHook(game_frame); + plsys->AddPluginsListener(this); + + sharesys->AddNatives(myself, natives); + +#if SOURCE_ENGINE == SE_TF2 + sm_sendprop_info_t info{}; + gamehelpers->FindSendPropInfo("CTFPlayer", "m_nPlayerCond", &info); + m_nPlayerCond = info.prop; + gamehelpers->FindSendPropInfo("CTFPlayer", "_condition_bits", &info); + _condition_bits = info.prop; + gamehelpers->FindSendPropInfo("CTFPlayer", "m_nPlayerCondEx", &info); + m_nPlayerCondEx = info.prop; + gamehelpers->FindSendPropInfo("CTFPlayer", "m_nPlayerCondEx2", &info); + m_nPlayerCondEx2 = info.prop; + gamehelpers->FindSendPropInfo("CTFPlayer", "m_nPlayerCondEx3", &info); + m_nPlayerCondEx3 = info.prop; + gamehelpers->FindSendPropInfo("CTFPlayer", "m_nPlayerCondEx4", &info); + m_nPlayerCondEx4 = info.prop; +#endif + + return true; +} + +bool Sample::RegisterConCommandBase(ConCommandBase *pCommand) +{ + META_REGCVAR(pCommand); + 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); + ConVar_Register(0, this); + +#if SOURCE_ENGINE == SE_TF2 || SOURCE_ENGINE == SE_CSS + server = engine->GetIServer(); +#endif + + std_proxies = gamedll->GetStandardSendProxies(); + + main_thread_id = std::this_thread::get_id(); + + sv_parallel_packentities = g_pCVar->FindVar("sv_parallel_packentities"); + sv_parallel_sendsnapshot = g_pCVar->FindVar("sv_parallel_sendsnapshot"); + sv_stressbots = g_pCVar->FindVar("sv_stressbots"); + + return true; +} + +void Sample::OnCoreMapEnd() noexcept +{ + hooks.clear(); + restores.clear(); +} + +void Sample::SDK_OnUnload() noexcept +{ + OnCoreMapEnd(); + + SendTable_CalcDelta_detour->Destroy(); + SendTable_Encode_detour->Destroy(); + SV_ComputeClientPacks_detour->Destroy(); + //SV_PackEntity_detour->Destroy(); + //PackWork_tProcess_detour->Destroy(); + //InvalidateSharedEdictChangeInfos_detour->Destroy(); + CGameServer_SendClientMessages_detour->Destroy(); + CFrameSnapshotManager_GetPackedEntity_detour->Destroy(); + CBaseServer_WriteDeltaEntities_detour->Destroy(); + + gameconfs->CloseGameConfigFile(gameconf); + + smutils->RemoveGameFrameHook(game_frame); + plsys->RemovePluginsListener(this); + if(g_pSDKHooks) { + g_pSDKHooks->RemoveEntityListener(this); + } +} + +void Sample::OnEntityDestroyed(CBaseEntity *pEntity) noexcept +{ + if(!pEntity) { + return; + } + + const unsigned long ref{::EntityToReference(pEntity)}; + + hooks_t::iterator it_hook{hooks.find(ref)}; + 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.remove_functions_of_plugin(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; + } +} + +bool Sample::QueryRunning(char *error, size_t maxlength) +{ + SM_CHECK_IFACE(SDKHOOKS, g_pSDKHooks); + SM_CHECK_IFACE(SDKTOOLS, g_pSDKTools); + return true; +} + +bool Sample::QueryInterfaceDrop(SMInterface *pInterface) +{ + if(pInterface == g_pSDKHooks) + return false; + else if(pInterface == g_pSDKTools) + return false; + return IExtensionInterface::QueryInterfaceDrop(pInterface); +} + +void Sample::NotifyInterfaceDrop(SMInterface *pInterface) +{ + if(strcmp(pInterface->GetInterfaceName(), SMINTERFACE_SDKHOOKS_NAME) == 0) { + g_pSDKHooks->RemoveEntityListener(this); + g_pSDKHooks = NULL; + } else if(strcmp(pInterface->GetInterfaceName(), SMINTERFACE_SDKTOOLS_NAME) == 0) { + g_pSDKTools = NULL; + } +} + +void Sample::SDK_OnAllLoaded() noexcept +{ + SM_GET_LATE_IFACE(SDKHOOKS, g_pSDKHooks); + SM_GET_LATE_IFACE(SDKTOOLS, g_pSDKTools); + + g_pSDKHooks->AddEntityListener(this); + +#if SOURCE_ENGINE == SE_LEFT4DEAD2 + server = g_pSDKTools->GetIServer(); + + { + void **vtable = *(void ***)server; + int index = vfunc_index(&CBaseServer::WriteDeltaEntities); + CBaseServer_WriteDeltaEntities_detour = DETOUR_CREATE_MEMBER(CBaseServer_WriteDeltaEntities, vtable[index]); + CBaseServer_WriteDeltaEntities_detour->EnableDetour(); + } +#endif +} diff --git a/sourcemod/gamedata/proxysend.txt b/sourcemod/gamedata/proxysend.txt index bfe70ee..e08cf90 100644 --- a/sourcemod/gamedata/proxysend.txt +++ b/sourcemod/gamedata/proxysend.txt @@ -1,80 +1,80 @@ -"Games" -{ - "tf" - { - "Offsets" - { - "CBaseClient::UpdateSendState" - { - "linux" "46" - } - "CBaseClient::SendSnapshot" - { - "linux" "52" - } - } - "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" - } - "CGameClient::GetSendFrame" - { - "library" "engine" - "linux" "@_ZN11CGameClient12GetSendFrameEv" - } - "SV_PackEntity" - { - "library" "engine" - "linux" "@_ZL13SV_PackEntityiP7edict_tP11ServerClassP14CFrameSnapshot" - } - "InvalidateSharedEdictChangeInfos" - { - "library" "engine" - "linux" "@_Z32InvalidateSharedEdictChangeInfosv" - } - "PackWork_t::Process" - { - "library" "engine" - "linux" "@_ZN10PackWork_t7ProcessERS_" - } - "SendProxy_StringT_To_String" - { - "library" "server" - "linux" "@_Z27SendProxy_StringT_To_StringPK8SendPropPKvS3_P8DVariantii" - } - "SendProxy_Color32ToInt" - { - "library" "server" - "linux" "@_Z22SendProxy_Color32ToIntPK8SendPropPKvS3_P8DVariantii" - } - "SendProxy_EHandleToInt" - { - "library" "server" - "linux" "@_Z22SendProxy_EHandleToIntPK8SendPropPKvS3_P8DVariantii" - } - } - } -} \ No newline at end of file +"Games" +{ + "#default" + { + "Offsets" + { + "CBaseClient::UpdateSendState" + { + "linux" "46" + } + "CBaseClient::SendSnapshot" + { + "linux" "52" + } + } + "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" + } + "CGameClient::GetSendFrame" + { + "library" "engine" + "linux" "@_ZN11CGameClient12GetSendFrameEv" + } + "SV_PackEntity" + { + "library" "engine" + "linux" "@_ZL13SV_PackEntityiP7edict_tP11ServerClassP14CFrameSnapshot" + } + "InvalidateSharedEdictChangeInfos" + { + "library" "engine" + "linux" "@_Z32InvalidateSharedEdictChangeInfosv" + } + "PackWork_t::Process" + { + "library" "engine" + "linux" "@_ZN10PackWork_t7ProcessERS_" + } + "SendProxy_StringT_To_String" + { + "library" "server" + "linux" "@_Z27SendProxy_StringT_To_StringPK8SendPropPKvS3_P8DVariantii" + } + "SendProxy_Color32ToInt" + { + "library" "server" + "linux" "@_Z22SendProxy_Color32ToIntPK8SendPropPKvS3_P8DVariantii" + } + "SendProxy_EHandleToInt" + { + "library" "server" + "linux" "@_Z22SendProxy_EHandleToIntPK8SendPropPKvS3_P8DVariantii" + } + } + } +} diff --git a/sourcemod/scripting/include/proxysend.inc b/sourcemod/scripting/include/proxysend.inc index 837bd3b..dda083d 100644 --- a/sourcemod/scripting/include/proxysend.inc +++ b/sourcemod/scripting/include/proxysend.inc @@ -3,7 +3,6 @@ #endif #define __PROXYSEND_INC -#include #include typeset proxysend_callbacks @@ -12,7 +11,6 @@ typeset proxysend_callbacks function Action (int entity, const char[] prop, RoundState &value, int element, int client); function Action (int entity, const char[] prop, RenderMode &value, int element, int client); function Action (int entity, const char[] prop, RenderFx &value, int element, int client); - function Action (int entity, const char[] prop, TFClassType &value, int element, int client); function Action (int entity, const char[] prop, bool &value, int element, int client); @@ -28,58 +26,9 @@ typeset proxysend_callbacks native void proxysend_hook(int entity, const char[] prop, proxysend_callbacks callback, bool per_client); native void proxysend_unhook(int entity, const char[] prop, proxysend_callbacks callback); -stock int get_bit_for_cond(TFCond cond) -{ - int icond = view_as(cond); - switch(icond / 32) { - case 0: return (1 << icond); - case 1: return (1 << (icond - 32)); - case 2: return (1 << (icond - 64)); - case 3: return (1 << (icond - 96)); - case 4: return (1 << (icond - 128)); - default: ThrowError("Invalid TFCond value %d", icond); - } - return 0; -} - -stock void get_prop_name_for_cond(TFCond cond, char[] name, int len) -{ - int icond = view_as(cond); - switch(icond / 32) { - case 0: strcopy(name, len, "m_nPlayerCond"); - case 1: strcopy(name, len, "m_nPlayerCondEx"); - case 2: strcopy(name, len, "m_nPlayerCondEx2"); - case 3: strcopy(name, len, "m_nPlayerCondEx3"); - case 4: strcopy(name, len, "m_nPlayerCondEx4"); - default: ThrowError("Invalid TFCond value %d", icond); - } -} - -stock void proxysend_hook_cond(int entity, TFCond cond, proxysend_callbacks callback, bool per_client) -{ - int icond = view_as(cond); - switch(icond / 32) { - case 0: proxysend_hook(entity, "m_nPlayerCond", callback, per_client); - case 1: proxysend_hook(entity, "m_nPlayerCondEx", callback, per_client); - case 2: proxysend_hook(entity, "m_nPlayerCondEx2", callback, per_client); - case 3: proxysend_hook(entity, "m_nPlayerCondEx3", callback, per_client); - case 4: proxysend_hook(entity, "m_nPlayerCondEx4", callback, per_client); - default: ThrowError("Invalid TFCond value %d", icond); - } -} - -stock void proxysend_unhook_cond(int entity, TFCond cond, proxysend_callbacks callback) -{ - int icond = view_as(cond); - switch(icond / 32) { - case 0: proxysend_unhook(entity, "m_nPlayerCond", callback); - case 1: proxysend_unhook(entity, "m_nPlayerCondEx", callback); - case 2: proxysend_unhook(entity, "m_nPlayerCondEx2", callback); - case 3: proxysend_unhook(entity, "m_nPlayerCondEx3", callback); - case 4: proxysend_unhook(entity, "m_nPlayerCondEx4", callback); - default: ThrowError("Invalid TFCond value %d", icond); - } -} +#if defined _tf2_included || defined _tf2_stocks_included + #include +#endif #if !defined REQUIRE_EXTENSIONS public __ext_proxysend_SetNTVOptional() @@ -103,4 +52,4 @@ public Extension __ext_proxysend = #else required = 0, #endif -}; \ No newline at end of file +}; diff --git a/sourcemod/scripting/include/proxysend_tf2.inc b/sourcemod/scripting/include/proxysend_tf2.inc new file mode 100644 index 0000000..642611b --- /dev/null +++ b/sourcemod/scripting/include/proxysend_tf2.inc @@ -0,0 +1,63 @@ +#if defined __PROXYSEND_TF2_INC + #endinput +#endif +#define __PROXYSEND_TF2_INC + +#include + +#if !defined __PROXYSEND_INC + #include +#endif + +stock int get_bit_for_cond(TFCond cond) +{ + int icond = view_as(cond); + switch(icond / 32) { + case 0: return (1 << icond); + case 1: return (1 << (icond - 32)); + case 2: return (1 << (icond - 64)); + case 3: return (1 << (icond - 96)); + case 4: return (1 << (icond - 128)); + default: ThrowError("Invalid TFCond value %d", icond); + } + return 0; +} + +stock void get_prop_name_for_cond(TFCond cond, char[] name, int len) +{ + int icond = view_as(cond); + switch(icond / 32) { + case 0: strcopy(name, len, "m_nPlayerCond"); + case 1: strcopy(name, len, "m_nPlayerCondEx"); + case 2: strcopy(name, len, "m_nPlayerCondEx2"); + case 3: strcopy(name, len, "m_nPlayerCondEx3"); + case 4: strcopy(name, len, "m_nPlayerCondEx4"); + default: ThrowError("Invalid TFCond value %d", icond); + } +} + +stock void proxysend_hook_cond(int entity, TFCond cond, proxysend_callbacks callback, bool per_client) +{ + int icond = view_as(cond); + switch(icond / 32) { + case 0: proxysend_hook(entity, "m_nPlayerCond", callback, per_client); + case 1: proxysend_hook(entity, "m_nPlayerCondEx", callback, per_client); + case 2: proxysend_hook(entity, "m_nPlayerCondEx2", callback, per_client); + case 3: proxysend_hook(entity, "m_nPlayerCondEx3", callback, per_client); + case 4: proxysend_hook(entity, "m_nPlayerCondEx4", callback, per_client); + default: ThrowError("Invalid TFCond value %d", icond); + } +} + +stock void proxysend_unhook_cond(int entity, TFCond cond, proxysend_callbacks callback) +{ + int icond = view_as(cond); + switch(icond / 32) { + case 0: proxysend_unhook(entity, "m_nPlayerCond", callback); + case 1: proxysend_unhook(entity, "m_nPlayerCondEx", callback); + case 2: proxysend_unhook(entity, "m_nPlayerCondEx2", callback); + case 3: proxysend_unhook(entity, "m_nPlayerCondEx3", callback); + case 4: proxysend_unhook(entity, "m_nPlayerCondEx4", callback); + default: ThrowError("Invalid TFCond value %d", icond); + } +}