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