// // Created by Michal Přikryl on 26.06.2025. // Copyright (c) 2025 slynxcz. All rights reserved. // #pragma once #include "playerslot.h" #include "steam/steamclientpublic.h" #include "utlstring.h" #include "inetchannel.h" #include "networkbasetypes.pb.h" #include #include #include "circularbuffer.h" #include "networksystem/inetworksystem.h" #include "threadtools.h" #include "tier1/netadr.h" #include #include #include #include #include #include namespace RayTracePlugin { class CHLTVServer; class INetMessage; class CNetworkGameServerBase; class CNetworkGameServer; class CFrameSnapshot; class CMsg_CVars; class CNETMsg_StringCmd_t; class CNETMsg_Tick_t; class CNETMsg_SpawnGroup_LoadCompleted_t; class CCLCMsg_ClientInfo_t; class CCLCMsg_BaselineAck_t; class CCLCMsg_LoadingProgress_t; class CCLCMsg_SplitPlayerConnect_t; class CCLCMsg_SplitPlayerDisconnect_t; class CCLCMsg_CmdKeyValues_t; class CCLCMsg_Move_t; class CCLCMsg_VoiceData_t; class CCLCMsg_FileCRCCheck_t; class CCLCMsg_RespondCvarValue_t; class NetMessagePacketStart_t; class NetMessagePacketEnd_t; class NetMessageConnectionClosed_t; class NetMessageConnectionCrashed_t; class NetMessageSplitscreenUserChanged_t; struct HltvReplayStats_t { enum FailEnum_t { FAILURE_ALREADY_IN_REPLAY, FAILURE_TOO_FREQUENT, FAILURE_NO_FRAME, FAILURE_NO_FRAME2, FAILURE_CANNOT_MATCH_DELAY, FAILURE_FRAME_NOT_READY, NUM_FAILURES }; uint nClients; uint nStartRequests; uint nSuccessfulStarts; uint nStopRequests; uint nAbortStopRequests; uint nUserCancels; uint nFullReplays; uint nNetAbortReplays; uint nFailedReplays[NUM_FAILURES]; }; // sizeof 56 COMPILE_TIME_ASSERT(sizeof(HltvReplayStats_t) == 56); struct Spike_t { public: CUtlString m_szDesc; int m_nBits; }; COMPILE_TIME_ASSERT(sizeof(Spike_t) == 16); class CNetworkStatTrace { public: CUtlVector m_Records; int m_nMinWarningBytes; int m_nStartBit; int m_nCurBit; }; COMPILE_TIME_ASSERT(sizeof(CNetworkStatTrace) == 40); enum CopiedLockState_t : int32 { CLS_NOCOPY = 0, CLS_UNLOCKED = 1, CLS_LOCKED_BY_COPYING_THREAD = 2, }; template class CCopyableLock : public MUTEX { typedef MUTEX BaseClass; public: // ... }; class CUtlSignaller_Base { public: using Delegate_t = CUtlDelegate; CUtlSignaller_Base(const Delegate_t& other) : m_SlotDeletionDelegate(other) { } CUtlSignaller_Base(Delegate_t&& other) : m_SlotDeletionDelegate(Move(other)) { } private: Delegate_t m_SlotDeletionDelegate; }; class CUtlSlot { public: using MTElement_t = CUtlSignaller_Base*; CUtlSlot() : m_ConnectedSignallers(0, 1) { } private: CCopyableLock m_Mutex; CUtlVector m_ConnectedSignallers; }; class CServerSideClientBase : public CUtlSlot, public INetworkChannelNotify, public INetworkMessageProcessingPreFilter { public: virtual ~CServerSideClientBase() = 0; public: CPlayerSlot GetPlayerSlot() const { return m_nClientSlot; } CPlayerUserId GetUserID() const { return m_UserID; } CEntityIndex GetEntityIndex() const { return m_nEntityIndex; } CSteamID GetClientSteamID() const { return m_SteamID; } const char* GetClientName() const { return m_Name; } INetChannel* GetNetChannel() const { return m_NetChannel; } const netadr_t* GetRemoteAddress() const { return &m_nAddr.GetAddress(); } CNetworkGameServerBase* GetServer() const { return m_Server; } virtual void Connect(int socket, const char* pszName, int nUserID, INetChannel* pNetChannel, uint8 nConnectionTypeFlags, uint32 uChallengeNumber) = 0; // bool bFakePlayer = !nConnectionTypeFlags || (nConnectionTypeFlags & 8) != 0; virtual void Inactivate(const char* pszAddons) = 0; virtual void Reactivate(CPlayerSlot nSlot) = 0; virtual void SetServer(CNetworkGameServer* pNetServer) = 0; virtual void Reconnect() = 0; virtual void Disconnect(ENetworkDisconnectionReason reason, const char* pszInternalReason) = 0; virtual bool CheckConnect() = 0; virtual void Create(CPlayerSlot& nSlot, CSteamID nSteamID, const char* pszName) = 0; virtual void SetRate(int nRate) = 0; virtual void SetUpdateRate(float fUpdateRate) = 0; virtual int GetRate() = 0; virtual void Clear() = 0; virtual bool ExecuteStringCommand(const CNETMsg_StringCmd_t& msg) = 0; // "false" trigger an anti spam counter to kick a client. virtual bool SendNetMessage(const CNetMessage* pData, NetChannelBufType_t bufType = BUF_DEFAULT) = 0; // "Client %d(%s) tried to send a RebroadcastSourceId msg.\n" virtual bool FilterMessage(const CNetMessage* pData, INetChannel* pChannel) = 0; // On Windows, this function is in a separate virtual table public: virtual void ClientPrintf(PRINTF_FORMAT_STRING const char*, ...) = 0; bool IsConnected() const { return m_nSignonState >= SIGNONSTATE_CONNECTED; } bool IsInGame() const { return m_nSignonState == SIGNONSTATE_FULL; } bool IsSpawned() const { return m_nSignonState >= SIGNONSTATE_NEW; } bool IsActive() const { return m_nSignonState == SIGNONSTATE_FULL; } virtual bool IsFakeClient() const { return m_bFakePlayer; } bool IsHLTV() const { return m_bIsHLTV; } virtual bool IsHumanPlayer() const { return false; } // Is an actual human player or splitscreen player (not a bot and not a HLTV slot) virtual bool IsHearingClient(CPlayerSlot nSlot) const { return false; } virtual bool IsProximityHearingClient() const = 0; virtual bool IsLowViolenceClient() const { return m_bLowViolence; } virtual bool IsSplitScreenUser() const { return m_bSplitScreenUser; } public: // Message Handlers virtual bool ProcessTick(const CNETMsg_Tick_t& msg) = 0; virtual bool ProcessStringCmd(const CNETMsg_StringCmd_t& msg) = 0; public: virtual bool ApplyConVars(const CMsg_CVars& list) = 0; private: virtual bool unk_28() = 0; public: virtual bool ProcessSpawnGroup_LoadCompleted(const CNETMsg_SpawnGroup_LoadCompleted_t& msg) = 0; virtual bool ProcessClientInfo(const CCLCMsg_ClientInfo_t& msg) = 0; virtual bool ProcessBaselineAck(const CCLCMsg_BaselineAck_t& msg) = 0; virtual bool ProcessLoadingProgress(const CCLCMsg_LoadingProgress_t& msg) = 0; virtual bool ProcessSplitPlayerConnect(const CCLCMsg_SplitPlayerConnect_t& msg) = 0; virtual bool ProcessSplitPlayerDisconnect(const CCLCMsg_SplitPlayerDisconnect_t& msg) = 0; virtual bool ProcessCmdKeyValues(const CCLCMsg_CmdKeyValues_t& msg) = 0; private: virtual bool unk_36() = 0; virtual bool unk_37() = 0; public: virtual bool ProcessMove(const CCLCMsg_Move_t& msg) = 0; virtual bool ProcessVoiceData(const CCLCMsg_VoiceData_t& msg) = 0; virtual bool ProcessRespondCvarValue(const CCLCMsg_RespondCvarValue_t& msg) = 0; virtual bool ProcessPacketStart(const NetMessagePacketStart_t& msg) = 0; virtual bool ProcessPacketEnd(const NetMessagePacketEnd_t& msg) = 0; virtual bool ProcessConnectionClosed(const NetMessageConnectionClosed_t& msg) = 0; virtual bool ProcessConnectionCrashed(const NetMessageConnectionCrashed_t& msg) = 0; public: virtual bool ProcessChangeSplitscreenUser(const NetMessageSplitscreenUserChanged_t& msg) = 0; private: virtual bool unk_47() = 0; virtual bool unk_48() = 0; virtual bool unk_49() = 0; public: virtual void ConnectionStart(INetChannel* pNetChannel) = 0; private: // SpawnGroup something. virtual void unk_51() = 0; virtual void unk_52() = 0; public: virtual void ExecuteDelayedCall(void*) = 0; virtual bool UpdateAcknowledgedFramecount(int tick) = 0; void ForceFullUpdate() { // For some reason, it doesn't work. // UpdateAcknowledgedFramecount(-1); m_nDeltaTick = -1; } virtual bool ShouldSendMessages() = 0; virtual void UpdateSendState() = 0; virtual const CMsgPlayerInfo& GetPlayerInfo() const { return m_playerInfo; } virtual void UpdateUserSettings() = 0; virtual void ResetUserSettings() = 0; private: virtual void unk_60() = 0; public: virtual void SendSignonData() = 0; virtual void SpawnPlayer() = 0; virtual void ActivatePlayer() = 0; virtual void SetName(const char* name) = 0; virtual void SetUserCVar(const char* cvar, const char* value) = 0; SignonState_t GetSignonState() const { return m_nSignonState; } virtual void FreeBaselines() = 0; bool IsFullyAuthenticated(void) { return m_bFullyAuthenticated; } void SetFullyAuthenticated(void) { m_bFullyAuthenticated = true; } virtual CServerSideClientBase* GetSplitScreenOwner() { return m_pAttachedTo; } virtual int GetNumPlayers() = 0; virtual void ShouldReceiveStringTableUserData() = 0; private: virtual void unk_70(CPlayerSlot nSlot) = 0; virtual void unk_71() = 0; virtual void unk_72() = 0; public: virtual int GetHltvLastSendTick() = 0; private: virtual void unk_74() = 0; virtual void unk_75() = 0; virtual void unk_76() = 0; public: virtual void Await() = 0; virtual void MarkToKick() = 0; virtual void UnmarkToKick() = 0; virtual bool ProcessSignonStateMsg(int state) = 0; virtual void PerformDisconnection(ENetworkDisconnectionReason reason) = 0; public: CUtlString m_UserIDString; CUtlString m_Name; CPlayerSlot m_nClientSlot; CEntityIndex m_nEntityIndex; CNetworkGameServerBase* m_Server; INetChannel* m_NetChannel; // CServerSideClientBase::Connect( name='%s', userid=%d, fake=%d, connectiontypeflags=%d, chan->addr=%s ) uint8 m_nConnectionTypeFlags; uint8 m_nAsyncDisconnectFlags; // check in Disconnect function, 1 add to queue, 2 disconnect now bool m_bMarkedToKick; SignonState_t m_nSignonState; bool m_bSplitScreenUser; bool m_bSplitAllowFastDisconnect; int m_nSplitScreenPlayerSlot; CServerSideClientBase* m_SplitScreenUsers[4]; CServerSideClientBase* m_pAttachedTo; bool m_bSplitPlayerDisconnecting; int m_nDisconnectionTypeFlags; bool m_bFakePlayer; bool m_bSendingSnapshot; private: [[maybe_unused]] char pad162[0x6]; public: CPlayerUserId m_UserID = -1; bool m_bReceivedPacket; // true, if client received a packet after the last send packet CSteamID m_SteamID; CSteamID m_DisconnectedSteamID; CSteamID m_AuthTicketSteamID; // Auth ticket CSteamID m_nFriendsID; ns_address m_nAddr; ns_address m_nAddr2; KeyValues* m_ConVars; bool m_bUnk0; private: [[maybe_unused]] char pad281[0x28]; public: bool m_bConVarsChanged; bool m_bIsHLTV; private: [[maybe_unused]] char pad323[0xD]; public: uint32 m_nSendtableCRC; uint32 m_uChallengeNumber; int m_nSignonTick; int m_nDeltaTick; int m_UnkVariable3; int m_nStringTableAckTick; int m_UnkVariable4; CFrameSnapshot* m_pLastSnapshot; // last send snapshot CUtlVector m_vecLoadedSpawnGroups; CMsgPlayerInfo m_playerInfo; CFrameSnapshot* m_pBaseline; int m_nBaselineUpdateTick; CBitVec m_BaselinesSent; int m_nBaselineUsed; // 0/1 toggling flag, singaling client what baseline to use int m_nLoadingProgress; // 0..100 progress, only valid during loading // This is used when we send out a nodelta packet to put the client in a state where we wait // until we get an ack from them on this packet. // This is for 3 reasons: // 1. A client requesting a nodelta packet means they're screwed so no point in deluging them with data. // Better to send the uncompressed data at a slow rate until we hear back from them (if at all). // 2. Since the nodelta packet deletes all client entities, we can't ever delta from a packet previous to it. // 3. It can eat up a lot of CPU on the server to keep building nodelta packets while waiting for // a client to get back on its feet. int m_nForceWaitForTick = -1; CCircularBuffer m_UnkBuffer = {1024}; bool m_bLowViolence = false; // true if client is in low-violence mode (L4D server needs to know) bool m_bSomethingWithAddressType = true; bool m_bFullyAuthenticated = false; bool m_bUnk1 = false; int m_nUnk; // The datagram is written to after every frame, but only cleared // when it is sent out to the client. overflow is tolerated. // Time when we should send next world state update ( datagram ) float m_fNextMessageTime = 0.0f; float m_fAuthenticatedTime = -1.0f; // Default time to wait for next message float m_fSnapshotInterval = 0.0f; private: [[maybe_unused]] char pad2572[0x8]; [[maybe_unused]] char m_packetmsg[0x15C]; // CSVCMsg_PacketEntities_t #ifdef __linux__ [[maybe_unused]] char pad2928[0x8]; #endif public: CNetworkStatTrace m_Trace; private: [[maybe_unused]] char pad2976[0x8]; public: // SV: Player %s kicked for too many failed console commands int m_spamCommandsCount = 0; // if the value is greater than 16, the player will be kicked with reason 39 int m_unknown = 0; double m_lastExecutedCommand = 0.0; // if command executed more than once per second, ++m_spamCommandCount private: [[maybe_unused]] char pad3000[0x8]; public: CCommand* m_pCommand; }; #ifdef __linux__ COMPILE_TIME_ASSERT(sizeof(CServerSideClientBase) == 3016); #endif class CServerSideClient : public CServerSideClientBase { }; }