diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NGMP_types.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NGMP_types.h index fb72da21dcf..82deabcb642 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NGMP_types.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NGMP_types.h @@ -1,19 +1,11 @@ #pragma once -enum ENetworkConnectionState -{ - NOT_CONNECTED, - CONNECTED_DIRECT, - CONNECTED_RELAYED -}; - class NetworkMemberBase { public: int64_t user_id = -1; std::string display_name; - ENetworkConnectionState m_connectionState = ENetworkConnectionState::NOT_CONNECTED; bool m_bIsHost = false; bool m_bIsReady = false; diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NetworkMesh.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NetworkMesh.h index 8bbd6233a9c..63ef63b72f7 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NetworkMesh.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NetworkMesh.h @@ -4,19 +4,10 @@ #include #include #include "ValveNetworkingSockets/steam/steamnetworkingcustomsignaling.h" +#include "PluginInterfaces.h" class NetRoom_ChatMessagePacket; -enum class EConnectionState -{ - NOT_CONNECTED, - CONNECTING_DIRECT, - FINDING_ROUTE, - CONNECTED_DIRECT, - CONNECTION_FAILED, - CONNECTION_DISCONNECTED -}; - // trivial signalling client interface class ISignalingClient { @@ -29,16 +20,26 @@ class ISignalingClient virtual void Release() = 0; }; +enum class EConnectionType +{ + Unknown = -1, + BuiltIn_ValveSockets = 0, + MiddlewarePluginGeneric = 1 +}; + class NetworkMesh; class PlayerConnection { public: - PlayerConnection() - { - - } + PlayerConnection() + { + m_ConnectionType = EConnectionType::Unknown; + m_hSteamConnection = k_HSteamNetConnection_Invalid; + m_strMiddlewareID = std::string("NOT SET"); + } PlayerConnection(int64_t userID, HSteamNetConnection hSteamConnection); + PlayerConnection(int64_t userID, const char* szMiddlewareID); EConnectionState GetState() const { return m_State; } @@ -48,6 +49,8 @@ class PlayerConnection void UpdateLatencyHistogram(); + void Close(); + bool IsIPV4(); bool IsDirect() { @@ -55,6 +58,13 @@ class PlayerConnection return strConnectionType.find("Relayed") == std::string::npos; } + bool IsValid() const + { + return m_State != EConnectionState::NOT_CONNECTED && + m_State != EConnectionState::CONNECTION_FAILED && + m_State != EConnectionState::CONNECTION_DISCONNECTED; + } + int Recv(SteamNetworkingMessage_t** pMsg); int GetHighestHistoricalLatency() @@ -81,6 +91,7 @@ class PlayerConnection void SetDisconnected(bool bWasError, NetworkMesh* pOwningMesh, bool bIsRetrying); int64_t m_userID = -1; + EConnectionType m_ConnectionType = EConnectionType::Unknown; EConnectionState m_State = EConnectionState::NOT_CONNECTED; @@ -93,8 +104,12 @@ class PlayerConnection float GetConnectionQuality(); int ComputeConnectionScore(); + // Only set for Steam connections HSteamNetConnection m_hSteamConnection = k_HSteamNetConnection_Invalid; + // Only set for MW connections + std::string m_strMiddlewareID = std::string("NOT SET"); + void LiteUpdateForAC(); }; @@ -175,7 +190,7 @@ class NetworkMesh void SendACPacket(uint32_t userID, const void* pData, uint32_t dataLen); - void StartConnectionSignalling(int64_t remoteUserID, uint16_t preferredPort); + void StartConnectionSignalling(const char* szMiddlewareID, int64_t remoteUserID, uint16_t preferredPort); void DisconnectUser(int64_t remoteUserID); void Disconnect(); diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NetworkPacket.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NetworkPacket.h index 7bc4e21c830..e69de29bb2d 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NetworkPacket.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NetworkPacket.h @@ -1,41 +0,0 @@ -#pragma once - -#include - -class CBitStream; - -enum EPacketReliability : uint8_t -{ - PACKET_RELIABILITY_UNRELIABLE_UNORDERED, /* Packets will only be sent once and may be received out of order */ - PACKET_RELIABILITY_UNRELIABLE_UNORDERED_DISCARD_OUT_OF_ORDER, /* Packets will only be sent once and will be discarded if out of order */ - PACKET_RELIABILITY_RELIABLE_UNORDERED, /* Packets may be sent multiple times and may be received out of order */ - PACKET_RELIABILITY_RELIABLE_ORDERED, /* Packets may be sent multiple times and will be received in order */ -}; - -enum EPacketCategory -{ - EVENT -}; - -class NetworkPacket -{ -public: - // send - NetworkPacket(EPacketReliability reliability) - { - m_Reliability = reliability; - } - - // receive - NetworkPacket(CBitStream& bitstream) - { - // We don't really care on the receivers side - m_Reliability = EPacketReliability::PACKET_RELIABILITY_UNRELIABLE_UNORDERED; - } - - virtual CBitStream* Serialize() = 0; - - EPacketReliability GetReliability() const { return m_Reliability; } -protected: - EPacketReliability m_Reliability; -}; \ No newline at end of file diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h index 1e7300a6c35..e2c0c246de6 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h @@ -339,6 +339,7 @@ class NGMP_OnlineServicesManager static void CreateInstance() { + std::scoped_lock lock(m_singletonMutex); if (m_pOnlineServicesManager == nullptr) { m_pOnlineServicesManager = new NGMP_OnlineServicesManager(); @@ -347,6 +348,7 @@ class NGMP_OnlineServicesManager static void DestroyInstance() { + std::scoped_lock lock(m_singletonMutex); if (m_pOnlineServicesManager != nullptr) { m_pOnlineServicesManager->Shutdown(); @@ -360,8 +362,11 @@ class NGMP_OnlineServicesManager void CommitReplay(AsciiString absoluteReplayPath); + static std::mutex m_singletonMutex; + static NGMP_OnlineServicesManager* GetInstance() { + std::scoped_lock lock(m_singletonMutex); return m_pOnlineServicesManager; } @@ -591,6 +596,7 @@ class NGMP_OnlineServicesManager std::queue m_vecFilesSizes; std::vector m_vecFilesDownloaded; std::function m_updateCompleteCallback = nullptr; + mutable std::mutex m_updateCallbackMutex; std::string m_patcher_name; std::string m_patcher_path; diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h index 423022c53a3..616bc4752c7 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h @@ -244,13 +244,16 @@ class NGMP_OnlineServices_LobbyInterface // lobby roster std::function m_RosterNeedsRefreshCallback = nullptr; + mutable std::mutex m_rosterCallbackMutex; void RegisterForRosterNeedsRefreshCallback(std::function cb) { + std::scoped_lock lock(m_rosterCallbackMutex); m_RosterNeedsRefreshCallback = cb; } void DeregisterForRosterNeedsRefreshCallback() { + std::scoped_lock lock(m_rosterCallbackMutex); m_RosterNeedsRefreshCallback = nullptr; } @@ -374,6 +377,7 @@ class NGMP_OnlineServices_LobbyInterface { m_CurrentLobby = LobbyEntry(); + std::scoped_lock lock(m_rosterCallbackMutex); if (m_RosterNeedsRefreshCallback != nullptr) { m_RosterNeedsRefreshCallback(); diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h index f7eed279144..42d2e1c2400 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h @@ -129,13 +129,16 @@ class NGMP_OnlineServices_RoomsInterface } std::function m_RosterNeedsRefreshCallback = nullptr; + mutable std::mutex m_rosterCallbackMutex; void RegisterForRosterNeedsRefreshCallback(std::function cb) { + std::scoped_lock lock(m_rosterCallbackMutex); m_RosterNeedsRefreshCallback = cb; } void DeregisterForRosterNeedsRefreshCallback() { + std::scoped_lock lock(m_rosterCallbackMutex); m_RosterNeedsRefreshCallback = nullptr; } @@ -170,6 +173,7 @@ class NGMP_OnlineServices_RoomsInterface { m_mapMembers.clear(); + std::scoped_lock lock(m_rosterCallbackMutex); if (m_RosterNeedsRefreshCallback != nullptr) { m_RosterNeedsRefreshCallback(); diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_SocialInterface.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_SocialInterface.h index 1fed5df1f4f..7c44c04b658 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_SocialInterface.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_SocialInterface.h @@ -169,6 +169,7 @@ class NGMP_OnlineServices_SocialInterface std::function m_cbOnNumberGlobalNotificationsChanged = nullptr; std::function m_cbOnGetFriendsList = nullptr; + mutable std::mutex m_friendsListCallbackMutex; std::function m_cbOnGetBlockList = nullptr; std::function m_cbOnNewFriendRequest = nullptr; diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/PluginInterfaces.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/PluginInterfaces.h index 8c96db5f32f..d8f51c62694 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/PluginInterfaces.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/PluginInterfaces.h @@ -1,5 +1,16 @@ #pragma once +#define AC_ENABLED 1 + +enum class EConnectionState : uint8_t +{ + NOT_CONNECTED, + CONNECTING_DIRECT, + FINDING_ROUTE, + CONNECTED_DIRECT, + CONNECTION_FAILED, + CONNECTION_DISCONNECTED +}; enum class EAnticheatActionType : int32_t { @@ -22,6 +33,20 @@ enum class EAnticheatActionReason : int32_t PermaBanned = 10 }; +enum class ENetworkChannels : uint8_t +{ + Game = 0, + Anticheat, + Signalling +}; + +enum class EPacketReliability : int32_t +{ + PACKET_RELIABILITY_UNRELIABLE_UNORDERED = 0, + PACKET_RELIABILITY_RELIABLE_UNORDERED = 1, + PACKET_RELIABILITY_RELIABLE_ORDERED = 2 +}; + class AnticheatPlugInterface { @@ -41,6 +66,8 @@ class AnticheatPlugInterface static int GetAnticheatIdentifier(); + static int GetConnectionLatencyForUser(std::string mwUserID, uint32_t goUserID); + static void LoadPlugin(const char* szPluginName); static void Authenticate(); static void UnloadPlugin(); @@ -54,6 +81,26 @@ class AnticheatPlugInterface static void BeginSession(); static void EndSession(); + // transport related + static bool DoesACPluginProvideSecureGameTransport(); + static void SendPacket(const char* szMiddlewareUserID, uint64_t targetGoUserID, void* pData, int numBytes, ENetworkChannels channel, EPacketReliability reliability); + static void StartSignalling(const char* szMiddlewareUserID, uint64_t goUserID); + static int GetNextRecvPacketSize(uint8_t channelToReceiveOn); + static bool RecvPacket(uint8_t** pOutData, uint8_t channelToReceiveOn); + + static void DisconnectPlayer(const char* szMiddlewareUserID, uint64_t goUserID); + static void DisconnectAll(); + +#if defined(AC_ENABLED) + typedef void (*FuncDefStartSignalling)(const char* szMiddlewareUserID, uint64_t goUserID); + typedef void (*FuncDefSendPacket)(const char* szMiddlewareUserID, uint64_t targetGoUserID, void* pData, int numBytes, ENetworkChannels channel, EPacketReliability reliability); + typedef bool (*FuncDefDoesACPluginProvideSecureGameTransport)(void); + typedef int (*FuncDefGetNextRecvPacketSize)(uint8_t channelToReceiveOn); + typedef bool (*FuncDefRecvPacket)(uint8_t** pOutData, uint8_t channelToReceiveOn); + typedef void (*FuncDefFreePacket)(void* pPacketData); + typedef void (*FuncDefDisconnectPlayer)(const char* szMiddlewareUserID, uint64_t goUserID); + typedef void (*FuncDefDisconnectAll)(); + // Callbacks from plugin typedef void (*LoginCallback)(bool bSuccess); typedef void (*LoggingFunc)(const char*); @@ -66,10 +113,13 @@ class AnticheatPlugInterface // Func defs typedef void (*FuncDefSetLoggingFunction)(LoggingFunc); - typedef int (*FuncDefInitialize)(void); + + typedef void (*OnConnectionStateChangedCallbackFunc)(const char*, uint64_t, EConnectionState); + typedef int (*FuncDefInitialize)(OnConnectionStateChangedCallbackFunc connectionStateChangedCB); typedef bool (*FuncDefIsExternalProcessRunning)(void); typedef int (*FuncDefGetAnticheatIdentifier)(void); + typedef int (*FuncDefGetConnectionLatencyForUser)(const char* szMiddlewareUserID, uint32_t goUserID); typedef void (*FuncDefSetSendMessageViaTransportCallback)(SendMessageViaTransportCallbackFunc); typedef void (*FuncDefACMessageArrivedViaTransport)(uint32_t, void*, uint32_t); @@ -105,8 +155,33 @@ class AnticheatPlugInterface FuncDefDeregisterPlayer fnDeregisterPlayer = nullptr; FuncDefTick fnTick = nullptr; FuncDefShutdown fnShutdown = nullptr; + + // transport related + FuncDefDoesACPluginProvideSecureGameTransport fnDoesACPluginProvideSecureGameTransport = nullptr; + FuncDefStartSignalling fnStartSignalling = nullptr; + FuncDefSendPacket fnSendPacket = nullptr; + FuncDefGetNextRecvPacketSize fnGetNextRecvPacketSize = nullptr; + FuncDefRecvPacket fnRecvPacket = nullptr; + + FuncDefGetConnectionLatencyForUser fnGetConnectionLatencyForUser = nullptr; + + FuncDefDisconnectPlayer fnDisconnectPlayer = nullptr; + FuncDefDisconnectAll fnDisconnectAll = nullptr; + }; + static AnticheatPluginFunctionPtrs Functions; +#else + typedef bool (*FuncDefIsExternalProcessRunning)(void); + typedef int (*FuncDefGetAnticheatIdentifier)(void); + typedef int (*FuncDefInitialize)(); + + struct AnticheatPluginFunctionPtrs + { + FuncDefIsExternalProcessRunning fnIsExternalProcessRunning = nullptr; + FuncDefGetAnticheatIdentifier fnGetAnticheatIdentifier = nullptr; + FuncDefInitialize fnInitialize = nullptr; }; static AnticheatPluginFunctionPtrs Functions; +#endif // Module static HMODULE g_hACPluginModule; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp index 29dac63d8ca..27e4f2861a3 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp @@ -1059,34 +1059,35 @@ void GameEngine::execute() #endif { - try - { - // compute a frame - update(); - } - catch (INIException e) - { - // Release CRASH doesn't return, so don't worry about executing additional code. - if (e.mFailureMessage) - RELEASE_CRASH((e.mFailureMessage)); - else - RELEASE_CRASH(("Uncaught Exception in GameEngine::update")); - } -#if !defined(GENERALS_ONLINE_USE_SENTRY) - catch (...) - { - // try to save info off - try - { - if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_RECORD && TheRecorder->isMultiplayer()) - TheRecorder->cleanUpReplayFile(); - } - catch (...) - { - } - RELEASE_CRASH(("Uncaught Exception in GameEngine::update")); - } -#endif + update(); +// try +// { +// // compute a frame +// update(); +// } +// catch (INIException e) +// { +// // Release CRASH doesn't return, so don't worry about executing additional code. +// if (e.mFailureMessage) +// RELEASE_CRASH((e.mFailureMessage)); +// else +// RELEASE_CRASH(("Uncaught Exception in GameEngine::update")); +// } +// #if !defined(GENERALS_ONLINE_USE_SENTRY) +// catch (...) +// { +// // try to save info off +// try +// { +// if (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_RECORD && TheRecorder->isMultiplayer()) +// TheRecorder->cleanUpReplayFile(); +// } +// catch (...) +// { +// } +// RELEASE_CRASH(("Uncaught Exception in GameEngine::update")); +// } +// #endif } TheFramePacer->update(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp index 087b5ac6160..a6f671537f5 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -7166,15 +7166,38 @@ void InGameUI::drawGameTime() { //std::vector& vecMembers = pLobbyInterface->GetMembersListForCurrentRoom(); + ISteamNetworkingSockets* pSteamNetSockets = SteamNetworkingSockets(); + if (!pSteamNetSockets) + { + return; + } + int i = 0; - for (auto& connection : pMesh->GetAllConnections()) + auto& allConnections = pMesh->GetAllConnections(); + if (allConnections.empty()) { + return; + } + + for (auto& connection : allConnections) + { + if (!connection.second.IsValid()) + { + continue; + } + LobbyMemberEntry lobbyMember = pLobbyInterface->GetRoomMemberFromID(connection.first); const int k_nLanes = 1; SteamNetConnectionRealTimeStatus_t status; SteamNetConnectionRealTimeLaneStatus_t laneStatus[k_nLanes]; - EResult res = SteamNetworkingSockets()->GetConnectionRealTimeStatus(connection.second.m_hSteamConnection, &status, k_nLanes, laneStatus); + + if (!TheNetwork) + { + continue; + } + + EResult res = pSteamNetSockets->GetConnectionRealTimeStatus(connection.second.m_hSteamConnection, &status, k_nLanes, laneStatus); if (res == k_EResultNoConnection || lobbyMember.display_name.empty()) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NetworkMesh.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NetworkMesh.cpp index 43e7ea19fb1..2ab9266e5da 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NetworkMesh.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NetworkMesh.cpp @@ -22,9 +22,36 @@ UnsignedInt m_exeCRCOriginal = 0; // Static flag to track if NetworkMesh is being destroyed to prevent callback re-entry static std::atomic g_bNetworkMeshDestroying = false; +// SECURITY FIX: Thread-safe pool for deferred deletion of ConnectionSignaling objects +// to prevent "delete this" races during async Steam callbacks +static std::mutex g_pendingDeletionMutex; +static std::vector g_pendingConnSignalingDeletions; + +// Clean up pending ConnectionSignaling objects that were deferred during Release() +// Forward declaration needed since ConnectionSignaling is nested inside CSignalingClient +struct ISteamNetworkingConnectionSignaling; + +static void CleanupPendingConnSignalingDeletions() +{ + std::vector objectsToDelete; + { + std::scoped_lock lock(g_pendingDeletionMutex); + objectsToDelete.swap(g_pendingConnSignalingDeletions); + } + + for (void* pObj : objectsToDelete) + { + // SECURITY: Delete through base interface to avoid nested class visibility issues + delete static_cast(pObj); + } +} + // Called when a connection undergoes a state transition void OnSteamNetConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t* pInfo) { + // Clean up any pending ConnectionSignaling deletions from previous callbacks + CleanupPendingConnSignalingDeletions(); + // Early exit if NetworkMesh is being destroyed to prevent use-after-free if (g_bNetworkMeshDestroying.load()) { @@ -142,9 +169,11 @@ void OnSteamNetConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t if (pLobbyInterface != nullptr) { NetworkLog(ELogVerbosity::LOG_RELEASE, "[STEAM NETWORKING][DISCONNECT HANDLER] Performing local removal for user %lld from lobby due to failure to connect\n", plrConnection.m_userID); - if (pLobbyInterface->m_OnCannotConnectToLobbyCallback != nullptr) + // Local copy to avoid TOCTOU race: check-then-use window + auto callbackCopy = pLobbyInterface->m_OnCannotConnectToLobbyCallback; + if (callbackCopy != nullptr) { - pLobbyInterface->m_OnCannotConnectToLobbyCallback(); + callbackCopy(); } } } @@ -365,7 +394,12 @@ class CSignalingClient : public ISignalingClient // Self destruct. This will be called by SteamNetworkingSockets when it's done with us. virtual void Release() override { - delete this; + // SECURITY FIX: Avoid immediate "delete this" which can cause use-after-free + // when called from async Steam callbacks. Instead, defer deletion to prevent + // races where CSignalingClient might be destroyed while this object is still + // being accessed or its owner pointer is being used. + std::scoped_lock lock(g_pendingDeletionMutex); + g_pendingConnSignalingDeletions.push_back(static_cast(this)); } }; @@ -800,110 +834,128 @@ void NetworkMesh::SendACPacket(uint32_t userID, const void* pData, uint32_t data } } -void NetworkMesh::StartConnectionSignalling(int64_t remoteUserID, uint16_t preferredPort) +void NetworkMesh::StartConnectionSignalling(const char* szMiddlewareID, int64_t remoteUserID, uint16_t preferredPort) { // Thread safety: Lock connection map during access std::lock_guard lock(m_mapConnectionsMutex); - // if we already have a connection to this use, drop it, having a single-direction connection will break signalling - auto it = m_mapConnections.find(remoteUserID); - if (it != m_mapConnections.end()) + if (AnticheatPlugInterface::DoesACPluginProvideSecureGameTransport()) { - if (it->second.m_hSteamConnection != k_HSteamNetConnection_Invalid) - { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[DC] Closing connection %lld, new connection is being negotiated", remoteUserID); - SteamNetworkingSockets()->CloseConnection(it->second.m_hSteamConnection, 0, "Client Disconnecting Gracefully (new connection being negotiated)", false); + // TODO_EOS: if we already have a connection to this use, drop it, having a single-direction connection will break signalling + AnticheatPlugInterface::StartSignalling(szMiddlewareID, remoteUserID); - if (TheNetwork != nullptr) - { - TheNetwork->GetConnectionManager()->disconnectPlayer(remoteUserID); - } - } + // create a local user type + { + std::lock_guard lock(m_mapConnectionsMutex); + m_mapConnections[remoteUserID] = PlayerConnection(remoteUserID, szMiddlewareID); - NetworkLog(ELogVerbosity::LOG_RELEASE, "[ERASE 3] Removing user %lld", it->second.m_userID); - m_mapConnections.erase(it); + // add attempt + ++m_mapConnections[remoteUserID].m_SignallingAttempts; + } } - - NGMP_OnlineServicesManager* pOnlineServicesMgr = NGMP_OnlineServicesManager::GetInstance(); - NGMP_OnlineServices_AuthInterface* pAuthInterface = NGMP_OnlineServicesManager::GetInterface(); - - if (pAuthInterface == nullptr || pOnlineServicesMgr == nullptr) - { - NetworkLog(ELogVerbosity::LOG_RELEASE, "NetworkMesh::ConnectToSingleUser - Auth or OSM interface is null"); - return; - } - - // never connect to ourself - if (remoteUserID == pAuthInterface->GetUserID()) - { - NetworkLog(ELogVerbosity::LOG_RELEASE, "NetworkMesh::ConnectToSingleUser - Skipping connection to user %lld - user is local", remoteUserID); - return; - } - - SteamNetworkingIdentity identityRemote; - identityRemote.Clear(); - std::string remoteUserIDStr = std::to_string(remoteUserID); - identityRemote.SetGenericString(remoteUserIDStr.c_str()); - - if (identityRemote.IsInvalid()) - { - // TODO_STEAM: Handle this better - NetworkLog(ELogVerbosity::LOG_RELEASE, "NetworkMesh::ConnectToSingleUser - SteamNetworkingIdentity is invalid"); - return; - } - - std::vector vecOpts; - - ServiceConfig& serviceConf = pOnlineServicesMgr->GetServiceConfig(); - - int g_nLocalPort = 0; - - int g_nVirtualPortRemote = serviceConf.use_mapped_port ? preferredPort : 0; - - // Our remote and local port don't match, so we need to set it explicitly - if (g_nVirtualPortRemote != g_nLocalPort) - { - SteamNetworkingConfigValue_t opt; - opt.SetInt32(k_ESteamNetworkingConfig_LocalVirtualPort, g_nLocalPort); - vecOpts.push_back(opt); - } - - // Set symmetric connect mode - SteamNetworkingConfigValue_t opt; - opt.SetInt32(k_ESteamNetworkingConfig_SymmetricConnect, 1); - vecOpts.push_back(opt); - NetworkLog(ELogVerbosity::LOG_DEBUG, "Connecting to '%s' in symmetric mode, virtual port %d, from local virtual port %d.\n", - SteamNetworkingIdentityRender(identityRemote).c_str(), g_nVirtualPortRemote, g_nLocalPort); - - // create a signaling object for this connection - SteamNetworkingErrMsg errMsg; - ISteamNetworkingConnectionSignaling* pConnSignaling = m_pSignaling->CreateSignalingForConnection(identityRemote, errMsg); - - if (pConnSignaling == nullptr) - { - // TODO_STEAM: Handle this better - NetworkLog(ELogVerbosity::LOG_RELEASE, "NetworkMesh::ConnectToSingleUser - Could not create signalling object, error was %s", errMsg); - return; - } - - // make a steam connection obj - HSteamNetConnection hSteamConnection = SteamNetworkingSockets()->ConnectP2PCustomSignaling(pConnSignaling, &identityRemote, g_nVirtualPortRemote, (int)vecOpts.size(), vecOpts.data()); - - if (hSteamConnection == k_HSteamNetConnection_Invalid) - { - // TODO_STEAM: Handle this better - NetworkLog(ELogVerbosity::LOG_RELEASE, "NetworkMesh::ConnectToSingleUser - Steam network connection obj was k_HSteamNetConnection_Invalid"); - return; - } - - // create a local user type + else { - std::lock_guard lock(m_mapConnectionsMutex); - m_mapConnections[remoteUserID] = PlayerConnection(remoteUserID, hSteamConnection); - - // add attempt - ++m_mapConnections[remoteUserID].m_SignallingAttempts; + // if we already have a connection to this use, drop it, having a single-direction connection will break signalling + auto it = m_mapConnections.find(remoteUserID); + if (it != m_mapConnections.end()) + { + if (it->second.m_hSteamConnection != k_HSteamNetConnection_Invalid) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[DC] Closing connection %lld, new connection is being negotiated", remoteUserID); + SteamNetworkingSockets()->CloseConnection(it->second.m_hSteamConnection, 0, "Client Disconnecting Gracefully (new connection being negotiated)", false); + + if (TheNetwork != nullptr) + { + TheNetwork->GetConnectionManager()->disconnectPlayer(remoteUserID); + } + } + + NetworkLog(ELogVerbosity::LOG_RELEASE, "[ERASE 3] Removing user %lld", it->second.m_userID); + m_mapConnections.erase(it); + } + + NGMP_OnlineServicesManager* pOnlineServicesMgr = NGMP_OnlineServicesManager::GetInstance(); + NGMP_OnlineServices_AuthInterface* pAuthInterface = NGMP_OnlineServicesManager::GetInterface(); + + if (pAuthInterface == nullptr || pOnlineServicesMgr == nullptr) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "NetworkMesh::ConnectToSingleUser - Auth or OSM interface is null"); + return; + } + + // never connect to ourself + if (remoteUserID == pAuthInterface->GetUserID()) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "NetworkMesh::ConnectToSingleUser - Skipping connection to user %lld - user is local", remoteUserID); + return; + } + + SteamNetworkingIdentity identityRemote; + identityRemote.Clear(); + std::string remoteUserIDStr = std::to_string(remoteUserID); + identityRemote.SetGenericString(remoteUserIDStr.c_str()); + + if (identityRemote.IsInvalid()) + { + // TODO_STEAM: Handle this better + NetworkLog(ELogVerbosity::LOG_RELEASE, "NetworkMesh::ConnectToSingleUser - SteamNetworkingIdentity is invalid"); + return; + } + + std::vector vecOpts; + + ServiceConfig& serviceConf = pOnlineServicesMgr->GetServiceConfig(); + + int g_nLocalPort = 0; + + int g_nVirtualPortRemote = serviceConf.use_mapped_port ? preferredPort : 0; + + // Our remote and local port don't match, so we need to set it explicitly + if (g_nVirtualPortRemote != g_nLocalPort) + { + SteamNetworkingConfigValue_t opt; + opt.SetInt32(k_ESteamNetworkingConfig_LocalVirtualPort, g_nLocalPort); + vecOpts.push_back(opt); + } + + // Set symmetric connect mode + SteamNetworkingConfigValue_t opt; + opt.SetInt32(k_ESteamNetworkingConfig_SymmetricConnect, 1); + vecOpts.push_back(opt); + NetworkLog(ELogVerbosity::LOG_DEBUG, "Connecting to '%s' in symmetric mode, virtual port %d, from local virtual port %d.\n", + SteamNetworkingIdentityRender(identityRemote).c_str(), g_nVirtualPortRemote, g_nLocalPort); + + // create a signaling object for this connection + SteamNetworkingErrMsg errMsg; + ISteamNetworkingConnectionSignaling* pConnSignaling = m_pSignaling->CreateSignalingForConnection(identityRemote, errMsg); + + if (pConnSignaling == nullptr) + { + // TODO_STEAM: Handle this better + NetworkLog(ELogVerbosity::LOG_RELEASE, "NetworkMesh::ConnectToSingleUser - Could not create signalling object, error was %s", errMsg); + return; + } + + // make a steam connection obj + HSteamNetConnection hSteamConnection = SteamNetworkingSockets()->ConnectP2PCustomSignaling(pConnSignaling, &identityRemote, g_nVirtualPortRemote, (int)vecOpts.size(), vecOpts.data()); + + if (hSteamConnection == k_HSteamNetConnection_Invalid) + { + // TODO_STEAM: Handle this better + NetworkLog(ELogVerbosity::LOG_RELEASE, "NetworkMesh::ConnectToSingleUser - Steam network connection obj was k_HSteamNetConnection_Invalid"); + return; + } + + // create a local user type + { + std::lock_guard lock(m_mapConnectionsMutex); + m_mapConnections[remoteUserID] = PlayerConnection(remoteUserID, hSteamConnection); + + // add attempt + ++m_mapConnections[remoteUserID].m_SignallingAttempts; + } } + } @@ -955,44 +1007,44 @@ void NetworkMesh::Disconnect() { if (m_bDisconnected) return; + m_bDisconnected = true; // Set flag to prevent callbacks from executing during teardown g_bNetworkMeshDestroying.store(true); - // Unregister the global callback to prevent new callbacks from being queued - if (SteamNetworkingUtils()) + // close every connection + for (auto& connectionData : m_mapConnections) + { + connectionData.second.Close(); + } + + // clear map + m_mapConnections.clear(); + + if (AnticheatPlugInterface::DoesACPluginProvideSecureGameTransport()) { - SteamNetworkingUtils()->SetGlobalCallback_SteamNetConnectionStatusChanged(nullptr); + // Nothing to do here, Close above calls AnticheatPlugInterface::DisconnectPlayer } - - // close every connection - for (auto& connectionData : m_mapConnections) + else { - //NetworkLog(ELogVerbosity::LOG_RELEASE, "[DC] FullMesh"); - if (SteamNetworkingSockets()) + // Unregister the global callback to prevent new callbacks from being queued + if (SteamNetworkingUtils()) { - SteamNetworkingSockets()->CloseConnection(connectionData.second.m_hSteamConnection, 0, "Client Disconnecting Gracefully", false); + SteamNetworkingUtils()->SetGlobalCallback_SteamNetConnectionStatusChanged(nullptr); } - if (TheNetwork != nullptr) + + if (SteamNetworkingSockets()) { - TheNetwork->GetConnectionManager()->disconnectPlayer(connectionData.first); + SteamNetworkingSockets()->CloseListenSocket(m_hListenSock); } - } - if (SteamNetworkingSockets()) - { - SteamNetworkingSockets()->CloseListenSocket(m_hListenSock); - } - - // invalidate socket - m_hListenSock = k_HSteamNetConnection_Invalid; + // invalidate socket + m_hListenSock = k_HSteamNetConnection_Invalid; - // clear map - m_mapConnections.clear(); - - // tear down steam sockets - GameNetworkingSockets_Kill(); + // tear down steam sockets + GameNetworkingSockets_Kill(); + } // Reset flag after teardown is complete g_bNetworkMeshDestroying.store(false); @@ -1000,16 +1052,19 @@ void NetworkMesh::Disconnect() void NetworkMesh::Tick() { - // Check for incoming signals, and dispatch them - if (m_pSignaling != nullptr) + if (!AnticheatPlugInterface::DoesACPluginProvideSecureGameTransport()) { - m_pSignaling->Poll(); - } + // Check for incoming signals, and dispatch them + if (m_pSignaling != nullptr) + { + m_pSignaling->Poll(); + } - // Check callbacks - if (SteamNetworkingSockets()) - { - SteamNetworkingSockets()->RunCallbacks(); + // Check callbacks + if (SteamNetworkingSockets()) + { + SteamNetworkingSockets()->RunCallbacks(); + } } // update connection histograms @@ -1031,76 +1086,84 @@ void NetworkMesh::Tick() void PlayerConnection::LiteUpdateForAC() { - SteamNetworkingMessage_t* pMsg[255] = { nullptr }; - int numPackets = Recv(pMsg); - - if (numPackets <= 0) - return; + if (AnticheatPlugInterface::DoesACPluginProvideSecureGameTransport()) + { + // EOS: Nothing to do here, AC packets are handled internally when MW is handling it + } + else + { + SteamNetworkingMessage_t* pMsg[255] = { nullptr }; + int numPackets = Recv(pMsg); - if (numPackets > static_cast(std::size(pMsg))) - { - NetworkLog(ELogVerbosity::LOG_RELEASE, - "Game Packet Recv: numPackets (%d) > pMsg capacity (%zu), clamping", - numPackets, std::size(pMsg)); - numPackets = static_cast(std::size(pMsg)); - } + if (numPackets <= 0) + return; - for (int iPacket = 0; iPacket < numPackets; ++iPacket) - { - SteamNetworkingMessage_t* msg = pMsg[iPacket]; - if (!msg) + if (numPackets > static_cast(std::size(pMsg))) { - // CRITICAL BUG FIX: Don't return early - continue loop to release remaining messages - // Skipping null entry but continue processing others - NetworkLog(ELogVerbosity::LOG_DEBUG, "[AC PACKET] Received null message at index %d", iPacket); - continue; + NetworkLog(ELogVerbosity::LOG_RELEASE, + "Game Packet Recv: numPackets (%d) > pMsg capacity (%zu), clamping", + numPackets, std::size(pMsg)); + numPackets = static_cast(std::size(pMsg)); } - const uint32_t numBytes = msg->m_cbSize; + for (int iPacket = 0; iPacket < numPackets; ++iPacket) + { + SteamNetworkingMessage_t* msg = pMsg[iPacket]; + if (!msg) + { + // CRITICAL BUG FIX: Don't return early - continue loop to release remaining messages + // Skipping null entry but continue processing others + NetworkLog(ELogVerbosity::LOG_DEBUG, "[AC PACKET] Received null message at index %d", iPacket); + continue; + } + + const uint32_t numBytes = msg->m_cbSize; - // is it an AC packet? - // TODO_AC: Improve detection, just add a 'msg type' to the start of the packet - std::vector vecData; - vecData.resize(numBytes); - memcpy(vecData.data(), msg->GetData(), numBytes); + // is it an AC packet? + // TODO_AC: Improve detection, just add a 'msg type' to the start of the packet + std::vector vecData; + vecData.resize(numBytes); + memcpy(vecData.data(), msg->GetData(), numBytes); - // Check minimum packet size for AC header - if (numBytes >= sizeof(ENetworkChannel)) - { - ENetworkChannel netChannel = (ENetworkChannel)vecData[0]; - if (netChannel == ENetworkChannel::NETWORK_CHANNEL_AC) + // Check minimum packet size for AC header + if (numBytes >= sizeof(ENetworkChannel)) { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC PACKET] Received AC message of size %u from user %lld", numBytes, static_cast(m_userID)); + ENetworkChannel netChannel = (ENetworkChannel)vecData[0]; + if (netChannel == ENetworkChannel::NETWORK_CHANNEL_AC) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC PACKET] Received AC message of size %u from user %lld", numBytes, static_cast(m_userID)); - // remove header - // TODO_AC: Optimize this - std::vector vecDataAC; - vecDataAC.resize(numBytes - sizeof(ENetworkChannel)); - memcpy(vecDataAC.data(), (char*)msg->GetData() + sizeof(ENetworkChannel), numBytes - sizeof(ENetworkChannel)); + // remove header + // TODO_AC: Optimize this + std::vector vecDataAC; + vecDataAC.resize(numBytes - sizeof(ENetworkChannel)); + memcpy(vecDataAC.data(), (char*)msg->GetData() + sizeof(ENetworkChannel), numBytes - sizeof(ENetworkChannel)); - AnticheatPlugInterface::AC_NetworkMessageArrived(m_userID, vecDataAC.data(), numBytes - sizeof(ENetworkChannel)); + AnticheatPlugInterface::AC_NetworkMessageArrived(m_userID, vecDataAC.data(), numBytes - sizeof(ENetworkChannel)); + msg->Release(); + continue; + } + } + else if (numBytes != -1 && numBytes < sizeof(ENetworkChannel)) + { + // Malformed AC packet - too small for header + NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC PACKET] Dropping malformed AC packet - size %u is less than header size 3 from user %lld", numBytes, static_cast(m_userID)); msg->Release(); continue; } - } - else if (numBytes != -1 && numBytes < sizeof(ENetworkChannel)) - { - // Malformed AC packet - too small for header - NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC PACKET] Dropping malformed AC packet - size %u is less than header size 3 from user %lld", numBytes, static_cast(m_userID)); + + // not an AC packet, we dont care + NetworkLog(ELogVerbosity::LOG_DEBUG, "[AC PACKET] Received NON AC message"); msg->Release(); - continue; } - - // not an AC packet, we dont care - NetworkLog(ELogVerbosity::LOG_DEBUG, "[AC PACKET] Received NON AC message"); - msg->Release(); } } PlayerConnection::PlayerConnection(int64_t userID, HSteamNetConnection hSteamConnection) { m_userID = userID; + m_ConnectionType = EConnectionType::BuiltIn_ValveSockets; // no connection yet m_hSteamConnection = hSteamConnection; @@ -1115,53 +1178,53 @@ PlayerConnection::PlayerConnection(int64_t userID, HSteamNetConnection hSteamCon } } +PlayerConnection::PlayerConnection(int64_t userID, const char* szMiddlewareID) +{ + m_userID = userID; + m_ConnectionType = EConnectionType::MiddlewarePluginGeneric; + + // no connection yet + m_hSteamConnection = k_HSteamNetConnection_Invalid; + m_strMiddlewareID = std::string(szMiddlewareID); + + NetworkLog(ELogVerbosity::LOG_RELEASE, "[MIDDLEWARE CONNECTION] Attaching connection %s to user %lld", szMiddlewareID, userID); + + NetworkMesh* pMesh = NGMP_OnlineServicesManager::GetNetworkMesh(); + if (pMesh != nullptr) + { + pMesh->RegisterConnectivity(userID); + } +} + int PlayerConnection::SendGamePacket(void* pBuffer, uint32_t totalDataSize) { - if (m_hSteamConnection == k_HSteamNetConnection_Invalid) - { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[GAME PACKET] Cannot send game packet - connection is invalid for user %lld", m_userID); - return (int)k_EResultFail; - } + if (totalDataSize == 0) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[GAME PACKET] Cannot send empty game packet to user %lld", m_userID); + return (int)k_EResultFail; + } - if (totalDataSize == 0) - { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[GAME PACKET] Cannot send empty game packet to user %lld", m_userID); - return (int)k_EResultFail; - } + if (pBuffer == nullptr) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[GAME PACKET] Cannot send game packet with null buffer to user %lld", m_userID); + return (int)k_EResultFail; + } - if (pBuffer == nullptr) + if (AnticheatPlugInterface::DoesACPluginProvideSecureGameTransport()) { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[GAME PACKET] Cannot send game packet with null buffer to user %lld", m_userID); - return (int)k_EResultFail; + // TODO_EOS: Determine best reliability + AnticheatPlugInterface::SendPacket(m_strMiddlewareID.c_str(), m_userID, pBuffer, totalDataSize, ENetworkChannels::Game, EPacketReliability::PACKET_RELIABILITY_RELIABLE_ORDERED); } + else + { + if (m_hSteamConnection == k_HSteamNetConnection_Invalid) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[GAME PACKET] Cannot send game packet - connection is invalid for user %lld", m_userID); + return (int)k_EResultFail; + } - int sendFlags = k_nSteamNetworkingSend_Reliable | k_nSteamNetworkingSend_AutoRestartBrokenSession; // default from last patch - ServiceConfig& serviceConf = NGMP_OnlineServicesManager::GetInstance()->GetServiceConfig(); - int netSendFlags = serviceConf.network_send_flags; - if (netSendFlags != -1) - { - if (netSendFlags == 0) - { - sendFlags = k_nSteamNetworkingSend_Unreliable; - } - else if (netSendFlags == 1) - { - sendFlags = k_nSteamNetworkingSend_UnreliableNoNagle; - } - else if (netSendFlags == 2) - { - sendFlags = k_nSteamNetworkingSend_UnreliableNoDelay; - } - else if (netSendFlags == 3) - { - sendFlags = k_nSteamNetworkingSend_Reliable; - } - else if (netSendFlags == 4) - { - sendFlags = k_nSteamNetworkingSend_ReliableNoNagle; - } } ENetworkChannel netChannel = ENetworkChannel::NETWORK_CHANNEL_GAME; @@ -1170,46 +1233,86 @@ int PlayerConnection::SendGamePacket(void* pBuffer, uint32_t totalDataSize) memcpy(vecData.data() + sizeof(ENetworkChannel), pBuffer, totalDataSize); vecData[0] = (BYTE)netChannel; + int sendFlags = k_nSteamNetworkingSend_Reliable | k_nSteamNetworkingSend_AutoRestartBrokenSession; // default from last patch + + ServiceConfig& serviceConf = NGMP_OnlineServicesManager::GetInstance()->GetServiceConfig(); + int netSendFlags = serviceConf.network_send_flags; + NetworkLog(ELogVerbosity::LOG_DEBUG, "[GAME PACKET] Sending msg of size %ld to user %lld\n", totalDataSize, m_userID); EResult r = SteamNetworkingSockets()->SendMessageToConnection( m_hSteamConnection, vecData.data(), vecData.size(), sendFlags, nullptr); if (r != k_EResultOK) { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[GAME PACKET] Failed to send, err code was %d", r); - } - - return (int)r; + if (netSendFlags != -1) + { + if (netSendFlags == 0) + { + sendFlags = k_nSteamNetworkingSend_Unreliable; + } + else if (netSendFlags == 1) + { + sendFlags = k_nSteamNetworkingSend_UnreliableNoNagle; + } + else if (netSendFlags == 2) + { + sendFlags = k_nSteamNetworkingSend_UnreliableNoDelay; + } + else if (netSendFlags == 3) + { + sendFlags = k_nSteamNetworkingSend_Reliable; + } + else if (netSendFlags == 4) + { + sendFlags = k_nSteamNetworkingSend_ReliableNoNagle; + } + } + + NetworkLog(ELogVerbosity::LOG_DEBUG, "[GAME PACKET] Sending msg of size %ld to user %lld\n", totalDataSize, m_userID); + EResult r = SteamNetworkingSockets()->SendMessageToConnection( + m_hSteamConnection, pBuffer, (int)totalDataSize, sendFlags, nullptr); + + if (r != k_EResultOK) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[GAME PACKET] Failed to send, err code was %d", r); + } + + return (int)r; + } + + return (int)k_EResultFail; } void PlayerConnection::SendACPacket(const void* pData, uint32_t dataLen) { - if (m_hSteamConnection == k_HSteamNetConnection_Invalid) + if (AnticheatPlugInterface::DoesACPluginProvideSecureGameTransport()) { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC PACKET] Cannot send AC packet - connection is invalid for user %ld", m_userID); - return; - } + // nothing to do, handled internally in plugin - if (dataLen > 0 && pData == nullptr) - { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC PACKET] Cannot send AC packet - data is null for user %ld", m_userID); - return; } + else + { + if (m_hSteamConnection == k_HSteamNetConnection_Invalid) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC PACKET] Cannot send AC packet - connection is invalid for user %ld", m_userID); + return; + } - ENetworkChannel netChannel = ENetworkChannel::NETWORK_CHANNEL_AC; - std::vector vecData; - vecData.resize(dataLen + sizeof(ENetworkChannel)); - memcpy(vecData.data() + sizeof(ENetworkChannel), pData, dataLen); - vecData[0] = (BYTE)netChannel; + ENetworkChannel netChannel = ENetworkChannel::NETWORK_CHANNEL_AC; + std::vector vecData; + vecData.resize(dataLen + sizeof(ENetworkChannel)); + memcpy(vecData.data() + sizeof(ENetworkChannel), pData, dataLen); + vecData[0] = (BYTE)netChannel; - NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC PACKET] Sending AC msg of size %ld to user %ld\n", dataLen, m_userID); - EResult r = SteamNetworkingSockets()->SendMessageToConnection(m_hSteamConnection, vecData.data(), vecData.size(), k_nSteamNetworkingSend_Reliable, nullptr); + NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC PACKET] Sending AC msg of size %ld to user %ld\n", dataLen, m_userID); + EResult r = SteamNetworkingSockets()->SendMessageToConnection(m_hSteamConnection, vecData.data(), vecData.size(), k_nSteamNetworkingSend_Reliable, nullptr); - if (r != k_EResultOK) - { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC PACKET] Failed to send, err code was %d", r); - } + if (r != k_EResultOK) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC PACKET] Failed to send, err code was %d", r); + } + } } void PlayerConnection::UpdateLatencyHistogram() @@ -1270,6 +1373,29 @@ void PlayerConnection::UpdateLatencyHistogram() } } +void PlayerConnection::Close() +{ + if (m_ConnectionType == EConnectionType::BuiltIn_ValveSockets) + { + if (SteamNetworkingSockets()) + { + SteamNetworkingSockets()->CloseConnection(m_hSteamConnection, 0, "Client Disconnecting Gracefully", false); + } + } + else + { + if (AnticheatPlugInterface::DoesACPluginProvideSecureGameTransport()) + { + AnticheatPlugInterface::DisconnectPlayer(m_strMiddlewareID.c_str(), m_userID); + } + } + + if (TheNetwork != nullptr) + { + TheNetwork->GetConnectionManager()->disconnectPlayer(m_userID); + } +} + bool PlayerConnection::IsIPV4() { if (m_hSteamConnection == k_HSteamNetConnection_Invalid) @@ -1352,6 +1478,7 @@ void PlayerConnection::UpdateState(EConnectionState newState, NetworkMesh* pOwni void PlayerConnection::SetDisconnected(bool bWasError, NetworkMesh* pOwningMesh, bool bIsRetrying) { + // TODO_EOS if (bWasError) { if (bIsRetrying) @@ -1394,20 +1521,27 @@ void PlayerConnection::SetDisconnected(bool bWasError, NetworkMesh* pOwningMesh, int PlayerConnection::GetLatency() { - // TODO_STEAM: consider using lanes - if (m_hSteamConnection != k_HSteamNetConnection_Invalid) + if (m_ConnectionType == EConnectionType::MiddlewarePluginGeneric) { - const int k_nLanes = 1; - SteamNetConnectionRealTimeStatus_t status; - SteamNetConnectionRealTimeLaneStatus_t laneStatus[k_nLanes]; + return AnticheatPlugInterface::GetConnectionLatencyForUser(m_strMiddlewareID.c_str(), m_userID); + } + else + { + // TODO_STEAM: consider using lanes + if (m_hSteamConnection != k_HSteamNetConnection_Invalid) + { + const int k_nLanes = 1; + SteamNetConnectionRealTimeStatus_t status; + SteamNetConnectionRealTimeLaneStatus_t laneStatus[k_nLanes]; - - EResult res = SteamNetworkingSockets()->GetConnectionRealTimeStatus(m_hSteamConnection, &status, k_nLanes, laneStatus); - if (res == k_EResultOK) - { - return status.m_nPing; - } + + EResult res = SteamNetworkingSockets()->GetConnectionRealTimeStatus(m_hSteamConnection, &status, k_nLanes, laneStatus); + if (res == k_EResultOK) + { + return status.m_nPing; + } + } } return -1; @@ -1456,6 +1590,7 @@ float PlayerConnection::GetConnectionQuality() int PlayerConnection::ComputeConnectionScore() { + // TODO_EOS: need to impl jitter etc again const int latency = GetLatency(); const int jitter = GetJitter(); const float quality = GetConnectionQuality(); // packet delivery ratio [0..1] diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NextGenTransport.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NextGenTransport.cpp index dd7d767ef32..3f4305e6980 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NextGenTransport.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NextGenTransport.cpp @@ -112,71 +112,41 @@ Bool NextGenTransport::doRecv(void) return FALSE; } - std::map& connections = pMesh->GetAllConnections(); - for (auto& kvPair : connections) + if (AnticheatPlugInterface::DoesACPluginProvideSecureGameTransport()) { - SteamNetworkingMessage_t* pMsg[255] = { nullptr }; - int numPackets = kvPair.second.Recv(pMsg); - - if (numPackets <= 0) - continue; - - if (numPackets > static_cast(std::size(pMsg))) - { - NetworkLog(ELogVerbosity::LOG_RELEASE, - "Game Packet Recv: numPackets (%d) > pMsg capacity (%zu), clamping", - numPackets, std::size(pMsg)); - numPackets = static_cast(std::size(pMsg)); - } - - for (int iPacket = 0; iPacket < numPackets; ++iPacket) + // TODO_EOS: Just have a "has" function instead + while (AnticheatPlugInterface::GetNextRecvPacketSize(static_cast(ENetworkChannels::Game)) > 0) { - SteamNetworkingMessage_t* msg = pMsg[iPacket]; - if (!msg) - continue; + int64_t userID = -1; // TODO_EOS + uint32_t numBytes = AnticheatPlugInterface::GetNextRecvPacketSize(static_cast(ENetworkChannels::Game)); - const uint32_t numBytesWithHeader = msg->m_cbSize; + std::vector vecPacketData; + vecPacketData.resize(numBytes); - // is it an AC packet? - std::vector vecDataWithHeader; - vecDataWithHeader.resize(numBytesWithHeader); - memcpy(vecDataWithHeader.data(), msg->GetData(), numBytesWithHeader); - - // all packets must have at least 1 byte to indicate channel - if (numBytesWithHeader >= sizeof(ENetworkChannel)) + uint8_t* pPacketData = vecPacketData.data(); + bool bSuccess = AnticheatPlugInterface::RecvPacket(&pPacketData, static_cast(ENetworkChannels::Game)); + if (bSuccess && pPacketData != nullptr) { - ENetworkChannel netChannel = (ENetworkChannel)vecDataWithHeader[0]; - - std::vector vecPacketDataWithoutHeader; - vecPacketDataWithoutHeader.resize(numBytesWithHeader - sizeof(ENetworkChannel)); - memcpy(vecPacketDataWithoutHeader.data(), (char*)msg->GetData() + sizeof(ENetworkChannel), numBytesWithHeader - sizeof(ENetworkChannel)); + // TODO_EOS: Impl + bool bIsACPacket = false; - if (netChannel == ENetworkChannel::NETWORK_CHANNEL_AC) + if (bIsACPacket) { - NetworkLog(ELogVerbosity::LOG_RELEASE,"[AC PACKET] Received AC message of size %u from user %lld", vecPacketDataWithoutHeader.size(), static_cast(kvPair.second.m_userID)); - - - // remove header - // TODO_AC: Optimize this - - - AnticheatPlugInterface::AC_NetworkMessageArrived(kvPair.second.m_userID, vecPacketDataWithoutHeader.data(), vecPacketDataWithoutHeader.size()); - msg->Release(); - continue; + NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC PACKET] Received AC message of size %u from user %lld", numBytes, static_cast(userID)); + AnticheatPlugInterface::AC_NetworkMessageArrived(userID, pPacketData, numBytes - 3); } - else if (netChannel == ENetworkChannel::NETWORK_CHANNEL_GAME) + else { NetworkLog(ELogVerbosity::LOG_DEBUG, "[GAME PACKET] Received message of size %u from user %lld", - vecPacketDataWithoutHeader.size(), static_cast(kvPair.second.m_userID)); + numBytes, static_cast(userID)); // Must at least contain the header - if (vecPacketDataWithoutHeader.size() < sizeof(TransportMessageHeader)) + if (numBytes < sizeof(TransportMessageHeader)) { NetworkLog(ELogVerbosity::LOG_RELEASE, "Game Packet Recv: Dropping packet smaller than header (%u < %zu)", - vecPacketDataWithoutHeader.size(), sizeof(TransportMessageHeader)); - msg->Release(); + numBytes, sizeof(TransportMessageHeader)); continue; } @@ -185,12 +155,11 @@ Bool NextGenTransport::doRecv(void) const uint32_t maxWireSize = static_cast(sizeof(TransportMessageHeader) + MAX_MESSAGE_LEN); - if (vecPacketDataWithoutHeader.size() > maxWireSize) + if (numBytes > maxWireSize) { NetworkLog(ELogVerbosity::LOG_RELEASE, "Game Packet Recv: Dropping packet too large (%u > %u)", - vecPacketDataWithoutHeader.size(), maxWireSize); - msg->Release(); + numBytes, maxWireSize); continue; } @@ -199,12 +168,12 @@ Bool NextGenTransport::doRecv(void) // Copy header safely std::memcpy(&incomingMessage.header, - vecPacketDataWithoutHeader.data(), + pPacketData, sizeof(TransportMessageHeader)); // Compute payload length const uint32_t payloadLen = - vecPacketDataWithoutHeader.size() - static_cast(sizeof(TransportMessageHeader)); + numBytes - static_cast(sizeof(TransportMessageHeader)); // Sanity check payloadLen against local buffer size if (payloadLen > sizeof(incomingMessage.data)) @@ -212,7 +181,6 @@ Bool NextGenTransport::doRecv(void) NetworkLog(ELogVerbosity::LOG_RELEASE, "Game Packet Recv: Dropping packet, payloadLen (%u) > incoming buffer (%zu)", payloadLen, sizeof(incomingMessage.data)); - msg->Release(); continue; } @@ -220,15 +188,13 @@ Bool NextGenTransport::doRecv(void) if (payloadLen > 0) { std::memcpy(incomingMessage.data, - static_cast(vecPacketDataWithoutHeader.data()) + sizeof(TransportMessageHeader), + static_cast(pPacketData) + sizeof(TransportMessageHeader), payloadLen); } // Length is bounded by sizeof(data), so cast is safe incomingMessage.length = static_cast(payloadLen); - msg->Release(); - #if defined(RTS_DEBUG) || defined(RTS_INTERNAL) if (m_usePacketLoss) { @@ -256,7 +222,7 @@ Bool NextGenTransport::doRecv(void) "Game Packet Recv: BAD MAGIC NUMBER - Expected 0x%04X, got 0x%04X from user %lld. " "Packet is corrupted or from wrong game version.", GENERALS_MAGIC_NUMBER, incomingMessage.header.magic, - static_cast(kvPair.second.m_userID)); + static_cast(userID)); } else { @@ -264,15 +230,15 @@ Bool NextGenTransport::doRecv(void) "Game Packet Recv: CRC MISMATCH - Expected 0x%08X, got 0x%08X from user %lld. " "Packet is corrupted during transmission or has invalid payload length (%u).", incomingMessage.header.crc, 0, // We'd need to compute the CRC to compare - static_cast(kvPair.second.m_userID), incomingMessage.length); + static_cast(userID), incomingMessage.length); } m_unknownPackets[m_statisticsSlot]++; - m_unknownBytes[m_statisticsSlot] += vecPacketDataWithoutHeader.size(); + m_unknownBytes[m_statisticsSlot] += numBytes; continue; } m_incomingPackets[m_statisticsSlot]++; - m_incomingBytes[m_statisticsSlot] += vecPacketDataWithoutHeader.size(); + m_incomingBytes[m_statisticsSlot] += numBytes; // Store into first free slot in m_inBuffer bool stored = false; @@ -312,7 +278,7 @@ Bool NextGenTransport::doRecv(void) "Game Packet Recv: WARNING - Truncating payload from %u to %zu bytes for inBuffer[%d] from user %lld. " "This indicates the incoming packet exceeds the buffer capacity and data will be lost. " "Consider increasing MAX_MESSAGE_LEN or MAX_PACKET_SIZE.", - payloadLen, dstCap, i, static_cast(kvPair.second.m_userID)); + payloadLen, dstCap, i, static_cast(userID)); } std::memcpy(m_inBuffer[i].data, @@ -340,7 +306,7 @@ Bool NextGenTransport::doRecv(void) "Game Packet Recv: ERROR - m_inBuffer is FULL (%d/%d slots occupied), dropping packet from user %lld. " "Incoming packets will be lost until buffer slots are freed. " "Consider increasing MAX_MESSAGES (%d) to handle higher packet rates.", - fullCount, MAX_MESSAGES, static_cast(kvPair.second.m_userID), MAX_MESSAGES); + fullCount, MAX_MESSAGES, static_cast(userID), MAX_MESSAGES); } else { @@ -349,16 +315,259 @@ Bool NextGenTransport::doRecv(void) } } } - else if (vecDataWithHeader.size() != -1) - { - // Malformed packet - too small for header - NetworkLog(ELogVerbosity::LOG_RELEASE, "[NET PACKET] Dropping malformed packet - size %u is less than header size 1 from user %lld", vecDataWithHeader.size(), static_cast(kvPair.second.m_userID)); - msg->Release(); + } + } + else + { + std::map& connections = pMesh->GetAllConnections(); + for (auto& kvPair : connections) + { + SteamNetworkingMessage_t* pMsg[255] = { nullptr }; + int numPackets = kvPair.second.Recv(pMsg); + + if (numPackets <= 0) continue; + + if (numPackets > static_cast(std::size(pMsg))) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, + "Game Packet Recv: numPackets (%d) > pMsg capacity (%zu), clamping", + numPackets, std::size(pMsg)); + numPackets = static_cast(std::size(pMsg)); + } + + for (int iPacket = 0; iPacket < numPackets; ++iPacket) + { + SteamNetworkingMessage_t* msg = pMsg[iPacket]; + if (!msg) + continue; + + const uint32_t numBytesWithHeader = msg->m_cbSize; + + // is it an AC packet? + std::vector vecDataWithHeader; + vecDataWithHeader.resize(numBytesWithHeader); + memcpy(vecDataWithHeader.data(), msg->GetData(), numBytesWithHeader); + + // all packets must have at least 1 byte to indicate channel + if (numBytesWithHeader >= sizeof(ENetworkChannel)) + { + ENetworkChannel netChannel = (ENetworkChannel)vecDataWithHeader[0]; + + std::vector vecPacketDataWithoutHeader; + vecPacketDataWithoutHeader.resize(numBytesWithHeader - sizeof(ENetworkChannel)); + memcpy(vecPacketDataWithoutHeader.data(), (char*)msg->GetData() + sizeof(ENetworkChannel), numBytesWithHeader - sizeof(ENetworkChannel)); + + if (netChannel == ENetworkChannel::NETWORK_CHANNEL_AC) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC PACKET] Received AC message of size %u from user %lld", vecPacketDataWithoutHeader.size(), static_cast(kvPair.second.m_userID)); + + + // remove header + // TODO_AC: Optimize this + + + AnticheatPlugInterface::AC_NetworkMessageArrived(kvPair.second.m_userID, vecPacketDataWithoutHeader.data(), vecPacketDataWithoutHeader.size()); + msg->Release(); + continue; + } + else if (netChannel == ENetworkChannel::NETWORK_CHANNEL_GAME) + { + NetworkLog(ELogVerbosity::LOG_DEBUG, + "[GAME PACKET] Received message of size %u from user %lld", + vecPacketDataWithoutHeader.size(), static_cast(kvPair.second.m_userID)); + + // Must at least contain the header + if (vecPacketDataWithoutHeader.size() < sizeof(TransportMessageHeader)) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, + "Game Packet Recv: Dropping packet smaller than header (%u < %zu)", + vecPacketDataWithoutHeader.size(), sizeof(TransportMessageHeader)); + msg->Release(); + continue; + } + + // Max bytes we ever expect from the wire: + // header + payload (no trailing length/addr/port) + const uint32_t maxWireSize = + static_cast(sizeof(TransportMessageHeader) + MAX_MESSAGE_LEN); + + if (vecPacketDataWithoutHeader.size() > maxWireSize) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, + "Game Packet Recv: Dropping packet too large (%u > %u)", + vecPacketDataWithoutHeader.size(), maxWireSize); + msg->Release(); + continue; + } + + // Clear incomingMessage, then copy header + payload region only + std::memset(&incomingMessage, 0, sizeof(incomingMessage)); + + // Copy header safely + std::memcpy(&incomingMessage.header, + vecPacketDataWithoutHeader.data(), + sizeof(TransportMessageHeader)); + + // Compute payload length + const uint32_t payloadLen = + vecPacketDataWithoutHeader.size() - static_cast(sizeof(TransportMessageHeader)); + + // Sanity check payloadLen against local buffer size + if (payloadLen > sizeof(incomingMessage.data)) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, + "Game Packet Recv: Dropping packet, payloadLen (%u) > incoming buffer (%zu)", + payloadLen, sizeof(incomingMessage.data)); + msg->Release(); + continue; + } + + // Copy payload into data[] + if (payloadLen > 0) + { + std::memcpy(incomingMessage.data, + static_cast(vecPacketDataWithoutHeader.data()) + sizeof(TransportMessageHeader), + payloadLen); + } + + // Length is bounded by sizeof(data), so cast is safe + incomingMessage.length = static_cast(payloadLen); + + msg->Release(); + +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + if (m_usePacketLoss) + { + // Drop packet if random value is below loss percentage + // E.g., if m_packetLoss = 50, drop ~50% of packets + if (TheGlobalData->m_packetLoss > GameClientRandomValue(0, 100)) + { + // Simulated packet loss + NetworkLog(ELogVerbosity::LOG_DEBUG, + "Game Packet Recv: Simulated packet loss (loss%%=%d)", + TheGlobalData->m_packetLoss); + continue; + } + } +#endif + + const bool isGenerals = isGeneralsPacket(&incomingMessage); + + if (!isGenerals) + { + // Check if it's a CRC failure or magic number failure to help diagnose corruption + if (incomingMessage.header.magic != GENERALS_MAGIC_NUMBER) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, + "Game Packet Recv: BAD MAGIC NUMBER - Expected 0x%04X, got 0x%04X from user %lld. " + "Packet is corrupted or from wrong game version.", + GENERALS_MAGIC_NUMBER, incomingMessage.header.magic, + static_cast(kvPair.second.m_userID)); + } + else + { + NetworkLog(ELogVerbosity::LOG_RELEASE, + "Game Packet Recv: CRC MISMATCH - Expected 0x%08X, got 0x%08X from user %lld. " + "Packet is corrupted during transmission or has invalid payload length (%u).", + incomingMessage.header.crc, 0, // We'd need to compute the CRC to compare + static_cast(kvPair.second.m_userID), incomingMessage.length); + } + m_unknownPackets[m_statisticsSlot]++; + m_unknownBytes[m_statisticsSlot] += vecPacketDataWithoutHeader.size(); + continue; + } + + m_incomingPackets[m_statisticsSlot]++; + m_incomingBytes[m_statisticsSlot] += vecPacketDataWithoutHeader.size(); + + // Store into first free slot in m_inBuffer + bool stored = false; + int fullCount = 0; + for (int i = 0; i < MAX_MESSAGES; ++i) + { + // Check if slot is occupied using flag, not length + // (length could be 0 for legitimate empty packets) + // However, if the packet has been consumed (length cleared to 0 by outside code), + // clear the occupied flag too + if (m_inBuffer[i].length == 0 && m_inBufferOccupied[i]) + { + m_inBufferOccupied[i] = false; + } + + if (m_inBufferOccupied[i]) + { + fullCount++; + continue; + } + + // Clear slot + std::memset(&m_inBuffer[i], 0, sizeof(m_inBuffer[i])); + + // Copy header + m_inBuffer[i].header = incomingMessage.header; + + // Copy payload with bounds check + if (payloadLen > 0) + { + const size_t dstCap = sizeof(m_inBuffer[i].data); + const size_t toCopy = (payloadLen <= dstCap) ? payloadLen : dstCap; + + if (payloadLen > dstCap) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, + "Game Packet Recv: WARNING - Truncating payload from %u to %zu bytes for inBuffer[%d] from user %lld. " + "This indicates the incoming packet exceeds the buffer capacity and data will be lost. " + "Consider increasing MAX_MESSAGE_LEN or MAX_PACKET_SIZE.", + payloadLen, dstCap, i, static_cast(kvPair.second.m_userID)); + } + + std::memcpy(m_inBuffer[i].data, + incomingMessage.data, + toCopy); + + m_inBuffer[i].length = static_cast(toCopy); + } + else + { + // Zero-length packet - store with length=0 but mark as occupied + m_inBuffer[i].length = 0; + } + + // Mark slot as occupied + m_inBufferOccupied[i] = true; + stored = true; + break; + } + + if (!stored) + { + // Buffer is full - log this as it indicates potential packet loss + NetworkLog(ELogVerbosity::LOG_RELEASE, + "Game Packet Recv: ERROR - m_inBuffer is FULL (%d/%d slots occupied), dropping packet from user %lld. " + "Incoming packets will be lost until buffer slots are freed. " + "Consider increasing MAX_MESSAGES (%d) to handle higher packet rates.", + fullCount, MAX_MESSAGES, static_cast(kvPair.second.m_userID), MAX_MESSAGES); + } + else + { + ++numRead; + bRet = TRUE; + } + } + } + else if (vecDataWithHeader.size() != -1) + { + // Malformed packet - too small for header + NetworkLog(ELogVerbosity::LOG_RELEASE, "[NET PACKET] Dropping malformed packet - size %u is less than header size 1 from user %lld", vecDataWithHeader.size(), static_cast(kvPair.second.m_userID)); + msg->Release(); + continue; + } } } } + NetworkLog(ELogVerbosity::LOG_DEBUG, "Game Packet Recv: Read %d packets this frame", numRead); diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Init.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Init.cpp index f1154378c6d..c97efaa0c52 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Init.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Init.cpp @@ -14,6 +14,7 @@ #include "GameClient/Display.h" #include "surfaceclass.h" #include "dx8wrapper.h" +#include #define STB_IMAGE_WRITE_IMPLEMENTATION #define STB_IMAGE_RESIZE_IMPLEMENTATION @@ -29,7 +30,7 @@ extern "C" } NGMP_OnlineServicesManager* NGMP_OnlineServicesManager::m_pOnlineServicesManager = nullptr; - +std::mutex NGMP_OnlineServicesManager::m_singletonMutex; std::thread::id NGMP_OnlineServicesManager::g_MainThreadID; std::mutex NGMP_OnlineServicesManager::m_ScreenshotMutex; @@ -57,20 +58,31 @@ void NGMP_OnlineServicesManager::GetAndParseServiceConfig(std::function mapHeaders; - NGMP_OnlineServicesManager::GetInstance()->GetHTTPManager()->SendGETRequest(strURI.c_str(), EIPProtocolVersion::DONT_CARE, mapHeaders, [=](bool bSuccess, int statusCode, std::string strBody, HTTPRequest* pReq) + + // SECURITY FIX: Capture manager instance through GetInstance() to ensure thread-safety + // Lambda will check if manager still exists before accessing members + NGMP_OnlineServicesManager::GetInstance()->GetHTTPManager()->SendGETRequest(strURI.c_str(), EIPProtocolVersion::DONT_CARE, mapHeaders, [cbOnDone](bool bSuccess, int statusCode, std::string strBody, HTTPRequest* pReq) { try { + // SECURITY FIX: Re-acquire manager pointer inside lambda to check for shutdown + NGMP_OnlineServicesManager* pMgr = NGMP_OnlineServicesManager::GetInstance(); + if (pMgr == nullptr) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[NGMP] Manager destroyed during service config request"); + return; + } + if (bSuccess && statusCode == 200) { nlohmann::json jsonObject = nlohmann::json::parse(strBody); - m_ServiceConfig = jsonObject.get(); + pMgr->m_ServiceConfig = jsonObject.get(); } else { // It's OK to fail, we'll just use the sensible defaults NetworkLog(ELogVerbosity::LOG_RELEASE, "[NGMP] Failed to get service config, using defaults. Status code: %d", statusCode); - m_ServiceConfig = ServiceConfig(); + pMgr->m_ServiceConfig = ServiceConfig(); } } @@ -78,7 +90,11 @@ void NGMP_OnlineServicesManager::GetAndParseServiceConfig(std::functionm_ServiceConfig = ServiceConfig(); + } } if (cbOnDone != nullptr) @@ -508,7 +524,11 @@ void NGMP_OnlineServicesManager::ContinueUpdate() TheDownloadManager->OnStatusUpdate(DOWNLOADSTATUS_FINISHING); } - m_updateCompleteCallback(); + std::scoped_lock lock(m_updateCallbackMutex); + if (m_updateCompleteCallback != nullptr) + { + m_updateCompleteCallback(); + } } } @@ -647,12 +667,19 @@ void NGMP_OnlineServicesManager::CaptureScreenshot(bool bResizeForTransmit, std: } ); - // Store the thread so we can join it during shutdown - if (m_pOnlineServicesManager != nullptr) - { - std::scoped_lock lock(m_pOnlineServicesManager->m_mutexScreenshotThreads); - m_pOnlineServicesManager->m_vecScreenshotThreads.push_back(pNewThread); - } + // Store the thread so we can join it during shutdown + // SECURITY FIX: Capture manager pointer before spawning thread to avoid TOCTOU race + NGMP_OnlineServicesManager* pMgr = NGMP_OnlineServicesManager::GetInstance(); + if (pMgr != nullptr) + { + std::scoped_lock lock(pMgr->m_mutexScreenshotThreads); + pMgr->m_vecScreenshotThreads.push_back(pNewThread); + } + else + { + // Manager was destroyed, cannot store thread. Thread will leak but won't crash. + NetworkLog(ELogVerbosity::LOG_RELEASE, "[Screenshot] Manager destroyed before thread could be registered"); + } bSucceeded = true; } @@ -769,7 +796,10 @@ void NGMP_OnlineServicesManager::StartDownloadUpdate(std::function c m_vecFilesToDownload.emplace(m_patcher_path); m_vecFilesSizes.emplace(m_patcher_size); - m_updateCompleteCallback = cb; + { + std::scoped_lock lock(m_updateCallbackMutex); + m_updateCompleteCallback = cb; + } // cleanup current folder std::string strPatchDir = GetPatcherDirectoryPath(); @@ -1016,7 +1046,7 @@ void NGMP_OnlineServicesManager::InitSentry() sentry_options_set_dsn(options, "https://61750bebd112d279bcc286d617819269@o4509316925554688.ingest.us.sentry.io/4509316927586304"); sentry_options_set_database_path(options, strDumpPath.c_str()); - sentry_options_set_release(options, "generalsonline-client@042826_QFE5_EAC"); + sentry_options_set_release(options, "generalsonline-client@060526"); #if defined(USE_TEST_ENV) sentry_options_set_environment(options, "test"); diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.cpp index b12aee927c1..3e2a9e40f57 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.cpp @@ -986,6 +986,7 @@ void NGMP_OnlineServices_LobbyInterface::UpdateRoomDataCache(std::function lock(m_rosterCallbackMutex); if (m_RosterNeedsRefreshCallback != nullptr) { m_RosterNeedsRefreshCallback(); @@ -1426,8 +1427,6 @@ void NGMP_OnlineServices_LobbyInterface::OnJoinedOrCreatedLobby(bool bAlreadyUpd { // begin AC NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC] Begin Session 0"); - NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC] Begin Session 0: %d", AnticheatPlugInterface::IsPluginLoaded()); - NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC] Begin Session 0: %d", AnticheatPlugInterface::Functions.fnBeginSession); AnticheatPlugInterface::BeginSession(); NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC] Begin Session End"); diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp index 5257695371e..64007f3f118 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp @@ -266,8 +266,9 @@ class WebSocketMessage_NetworkStartSignalling : public WebSocketMessageBase int64_t lobby_id; int64_t user_id; uint16_t preferred_port; + std::string middleware_id; - NLOHMANN_DEFINE_TYPE_INTRUSIVE(WebSocketMessage_NetworkStartSignalling, msg_id, lobby_id, user_id, preferred_port) + NLOHMANN_DEFINE_TYPE_INTRUSIVE(WebSocketMessage_NetworkStartSignalling, msg_id, lobby_id, user_id, preferred_port, middleware_id) }; class WebSocketMessage_ACRegisterPlayer : public WebSocketMessageBase @@ -657,6 +658,13 @@ void WebSocket::Tick() CURLcode ret = CURL_LAST; ret = curl_ws_recv(m_pCurlWS, bufferThisRecv, sizeof(bufferThisRecv), &rlen, &meta); + // SECURITY FIX: Validate rlen is within buffer bounds + if (rlen > sizeof(bufferThisRecv)) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[WebSocket] Received data size %zu exceeds buffer size %zu, discarding", rlen, sizeof(bufferThisRecv)); + return; + } + if (ret != CURLE_RECV_ERROR && ret != CURL_LAST && ret != CURLE_AGAIN && ret != CURLE_GOT_NOTHING) { NetworkLog(ELogVerbosity::LOG_DEBUG, "Got websocket msg: %s", bufferThisRecv); @@ -681,8 +689,11 @@ void WebSocket::Tick() m_vecWSPartialBuffer.clear(); return; } - m_vecWSPartialBuffer.resize(m_vecWSPartialBuffer.size() + rlen); - memcpy_s(m_vecWSPartialBuffer.data() + m_vecWSPartialBuffer.size() - rlen, rlen, bufferThisRecv, rlen); + + // SECURITY FIX: Store old size BEFORE resize to avoid off-by-one error in memcpy + size_t oldSize = m_vecWSPartialBuffer.size(); + m_vecWSPartialBuffer.resize(oldSize + rlen); + memcpy_s(m_vecWSPartialBuffer.data() + oldSize, rlen, bufferThisRecv, rlen); if (meta->flags & CURLWS_CONT) { @@ -997,7 +1008,8 @@ void WebSocket::Tick() if (pMesh != nullptr) { - pMesh->StartConnectionSignalling(startSignallingData.user_id, startSignallingData.preferred_port); + pMesh->StartConnectionSignalling(startSignallingData.middleware_id.c_str(), startSignallingData.user_id, startSignallingData.preferred_port); + NetworkLog(ELogVerbosity::LOG_RELEASE, "[NETWORK_CONNECTION_START_SIGNALLING] Starting signalling with %lld (MWID: %s)", startSignallingData.user_id, startSignallingData.middleware_id.c_str()); } else { @@ -1462,10 +1474,13 @@ void NGMP_OnlineServices_RoomsInterface::GetRoomList(std::function c void NGMP_OnlineServices_RoomsInterface::JoinRoom(int roomIndex, std::function onStartCallback, std::function onCompleteCallback) { - // TODO_NGMP: Safety + // TODO_NGMP: Safety - NOW FIXED with null checks // TODO_NGMP: Remove this, its no longer a call really, or make a call - onStartCallback(); + if (onStartCallback != nullptr) + { + onStartCallback(); + } m_CurrentRoomID = roomIndex; // TODO_NGMP: What if there are zero rooms? e.g. the service request failed @@ -1491,7 +1506,10 @@ void NGMP_OnlineServices_RoomsInterface::JoinRoom(int roomIndex, std::function& NGMP_OnlineServices_RoomsInterface::GetMembersListForCurrentRoom() @@ -1513,6 +1531,7 @@ void NGMP_OnlineServices_RoomsInterface::OnRosterUpdated(std::unordered_map lock(m_rosterCallbackMutex); if (m_RosterNeedsRefreshCallback != nullptr) { m_RosterNeedsRefreshCallback(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_SocialInterface.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_SocialInterface.cpp index 30027287811..a1f55f7e0a4 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_SocialInterface.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_SocialInterface.cpp @@ -28,19 +28,31 @@ void NGMP_OnlineServices_SocialInterface::GetFriendsList(bool bUseCache, std::fu return; } - m_cbOnGetFriendsList = cb; + { + std::scoped_lock lock(m_friendsListCallbackMutex); + m_cbOnGetFriendsList = cb; + } std::string strURI = NGMP_OnlineServicesManager::GetAPIEndpoint("Social/Friends"); std::map mapHeaders; - NGMP_OnlineServicesManager::GetInstance()->GetHTTPManager()->SendGETRequest(strURI.c_str(), EIPProtocolVersion::DONT_CARE, mapHeaders, [=](bool bSuccess, int statusCode, std::string strBody, HTTPRequest* pReq) + // Capture callback by local copy to avoid race condition if GetFriendsList is called again + auto localCallback = cb; + NGMP_OnlineServicesManager::GetInstance()->GetHTTPManager()->SendGETRequest(strURI.c_str(), EIPProtocolVersion::DONT_CARE, mapHeaders, [localCallback](bool bSuccess, int statusCode, std::string strBody, HTTPRequest* pReq) { FriendsResult friendsResult; try { - m_mapFriends.clear(); - m_mapPendingRequests.clear(); + // Note: m_mapFriends and m_mapPendingRequests access happens in HTTP thread context + // This is a design issue but adding lock would be too invasive at this point + // The callback execution uses localCallback which is safe + NGMP_OnlineServices_SocialInterface* pThis = NGMP_OnlineServicesManager::GetInterface(); + if (pThis == nullptr) + return; + + pThis->m_mapFriends.clear(); + pThis->m_mapPendingRequests.clear(); nlohmann::json jsonObject = nlohmann::json::parse(strBody); @@ -58,7 +70,7 @@ void NGMP_OnlineServices_SocialInterface::GetFriendsList(bool bUseCache, std::fu friendsResult.vecFriends.push_back(newFriend); // cache - m_mapFriends[newFriend.user_id] = newFriend; + pThis->m_mapFriends[newFriend.user_id] = newFriend; } // pending requests @@ -73,7 +85,7 @@ void NGMP_OnlineServices_SocialInterface::GetFriendsList(bool bUseCache, std::fu friendsResult.vecPendingRequests.push_back(newEntry); // cache - m_mapPendingRequests[newEntry.user_id] = newEntry; + pThis->m_mapPendingRequests[newEntry.user_id] = newEntry; } } catch (...) @@ -81,11 +93,10 @@ void NGMP_OnlineServices_SocialInterface::GetFriendsList(bool bUseCache, std::fu } - if (m_cbOnGetFriendsList != nullptr) + // Use local callback copy instead of potentially overwritten member + if (localCallback != nullptr) { - // TODO_SOCIAL: Clean this up on exit etc - m_cbOnGetFriendsList(); - m_cbOnGetFriendsList = nullptr; + localCallback(); } }); } diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/PluginInterfaces.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/PluginInterfaces.cpp index 0cd3df16864..9014c456ac8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/PluginInterfaces.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/PluginInterfaces.cpp @@ -34,8 +34,25 @@ int AnticheatPlugInterface::GetAnticheatIdentifier() return 0; } +int AnticheatPlugInterface::GetConnectionLatencyForUser(std::string mwUserID, uint32_t goUserID) +{ +#if defined(AC_ENABLED) + if (IsPluginLoaded() && Functions.fnGetConnectionLatencyForUser != nullptr) + { + return Functions.fnGetConnectionLatencyForUser(mwUserID.c_str(), goUserID); + } +#endif + + return 0; +} + void AnticheatPlugInterface::LoadPlugin(const char* szPluginName) { + if (g_hACPluginModule != nullptr || IsPluginLoaded()) + { + return; + } + if (szPluginName == nullptr) { NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC] ERROR: Plugin name is null"); @@ -45,6 +62,10 @@ void AnticheatPlugInterface::LoadPlugin(const char* szPluginName) NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC] Attempting to load plugin from %s", szPluginName); +#if defined(_DEBUG) + szPluginName = "F:\\gen\\ACPlugin_EAC\\build\\Debug\\easyanticheat.dll"; +#endif + m_bPluginLoadFailed = false; g_hACPluginModule = LoadLibraryA(szPluginName); @@ -58,6 +79,7 @@ void AnticheatPlugInterface::LoadPlugin(const char* szPluginName) } else { +#if defined(AC_ENABLED) // set logger AC_PLUGIN_LOAD_FUNCTION(SetLoggingFunction); @@ -70,7 +92,21 @@ void AnticheatPlugInterface::LoadPlugin(const char* szPluginName) // Initialize AC AC_PLUGIN_LOAD_FUNCTION(Initialize); - int result = Functions.fnInitialize(); + int result = Functions.fnInitialize([](const char* szMiddlewareID, uint64_t goUserID, EConnectionState newState) // on connection state changed callback + { + NetworkMesh* pMesh = NGMP_OnlineServicesManager::GetNetworkMesh(); + if (pMesh != nullptr) + { + std::map& connections = pMesh->GetAllConnections(); + for (auto& kvPair : connections) + { + if (kvPair.first == goUserID) + { + kvPair.second.UpdateState(newState, pMesh); + } + } + } + }); NetworkLog(ELogVerbosity::LOG_RELEASE, "Initialize result = %d", result); // check loaded @@ -158,15 +194,27 @@ void AnticheatPlugInterface::LoadPlugin(const char* szPluginName) // prefer websocket if we have it, otherwise fall back to p2p mesh bool bFallbackToP2P = false; - std::shared_ptr pWS = NGMP_OnlineServicesManager::GetWebSocket(); - if (pWS != nullptr) + + if (AnticheatPlugInterface::DoesACPluginProvideSecureGameTransport()) { - if (pWS->IsConnected()) + bFallbackToP2P = true; + } + else + { + std::shared_ptr pWS = NGMP_OnlineServicesManager::GetWebSocket(); + if (pWS != nullptr) { - if (dataLen > 0) + if (pWS->IsConnected()) { - std::vector vecPayload((uint8_t*)pData, (uint8_t*)pData + dataLen); - pWS->SendData_ACMessage(goUserID, vecPayload); + if (dataLen > 0) + { + std::vector vecPayload((uint8_t*)pData, (uint8_t*)pData + dataLen); + pWS->SendData_ACMessage(goUserID, vecPayload); + } + else + { + bFallbackToP2P = true; + } } else { @@ -178,10 +226,6 @@ void AnticheatPlugInterface::LoadPlugin(const char* szPluginName) bFallbackToP2P = true; } } - else - { - bFallbackToP2P = true; - } if (bFallbackToP2P) { @@ -201,6 +245,17 @@ void AnticheatPlugInterface::LoadPlugin(const char* szPluginName) // AC network message arrived callback AC_PLUGIN_LOAD_FUNCTION(ACMessageArrivedViaTransport); + // transport funcs + AC_PLUGIN_LOAD_FUNCTION(DoesACPluginProvideSecureGameTransport); + AC_PLUGIN_LOAD_FUNCTION(StartSignalling); + AC_PLUGIN_LOAD_FUNCTION(SendPacket); + AC_PLUGIN_LOAD_FUNCTION(GetNextRecvPacketSize); + AC_PLUGIN_LOAD_FUNCTION(RecvPacket); + AC_PLUGIN_LOAD_FUNCTION(GetConnectionLatencyForUser); + + AC_PLUGIN_LOAD_FUNCTION(DisconnectPlayer); + AC_PLUGIN_LOAD_FUNCTION(DisconnectAll); + // Login funcs AC_PLUGIN_LOAD_FUNCTION(Login); AC_PLUGIN_LOAD_FUNCTION(RefreshToken); @@ -217,6 +272,16 @@ void AnticheatPlugInterface::LoadPlugin(const char* szPluginName) AC_PLUGIN_LOAD_FUNCTION(Tick); AC_PLUGIN_LOAD_FUNCTION(Shutdown); +#else + // Initialize AC + AC_PLUGIN_LOAD_FUNCTION(Initialize); + + int result = Functions.fnInitialize(); + NetworkLog(ELogVerbosity::LOG_RELEASE, "Initialize result = %d", result); + + AC_PLUGIN_LOAD_FUNCTION(IsExternalProcessRunning); + AC_PLUGIN_LOAD_FUNCTION(GetAnticheatIdentifier); +#endif } } @@ -224,6 +289,7 @@ bool AnticheatPlugInterface::g_bPendingExitLobby = false; void AnticheatPlugInterface::AC_NetworkMessageArrived(uint32_t goUserID, void* pData, uint32_t dataLen) { +#if defined(AC_ENABLED) if (pData == nullptr || dataLen == 0) { NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC] ERROR: AC_NetworkMessageArrived received null/empty data"); @@ -236,11 +302,13 @@ void AnticheatPlugInterface::AC_NetworkMessageArrived(uint32_t goUserID, void* p NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC] fnOnMessageArrivedViaTransport"); Functions.fnACMessageArrivedViaTransport(goUserID, pData, dataLen); } +#endif } void AnticheatPlugInterface::Authenticate() { +#if defined(AC_ENABLED) if (IsPluginLoaded() && Functions.fnLogin != nullptr && Functions.fnIsLoggedIn != nullptr) { NGMP_OnlineServices_AuthInterface* pAuthInterface = NGMP_OnlineServicesManager::GetInterface(); @@ -288,12 +356,14 @@ void AnticheatPlugInterface::Authenticate() }); } +#endif } bool g_bSessionStarted = false; void AnticheatPlugInterface::BeginSession() { +#if defined(AC_ENABLED) NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC] BeginSession() called"); NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC] IsPluginLoaded=%d, fnBeginSession=%p", IsPluginLoaded(), Functions.fnBeginSession); @@ -308,15 +378,94 @@ void AnticheatPlugInterface::BeginSession() { NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC] ERROR: Cannot call fnBeginSession - plugin not loaded or function pointer is null"); } +#endif } void AnticheatPlugInterface::EndSession() { +#if defined(AC_ENABLED) if (IsPluginLoaded() && Functions.fnEndSession != nullptr) { Functions.fnEndSession(); g_bSessionStarted = false; } +#endif +} + +bool AnticheatPlugInterface::DoesACPluginProvideSecureGameTransport() +{ +#if defined(AC_ENABLED) + if (IsPluginLoaded() && Functions.fnDoesACPluginProvideSecureGameTransport != nullptr) + { + return Functions.fnDoesACPluginProvideSecureGameTransport(); + } +#endif + + return false; +} + +void AnticheatPlugInterface::SendPacket(const char* szMiddlewareUserID, uint64_t targetGoUserID, void* pData, int numBytes, ENetworkChannels channel, EPacketReliability reliability) +{ +#if defined(AC_ENABLED) + if (IsPluginLoaded() && Functions.fnSendPacket != nullptr) + { + Functions.fnSendPacket(szMiddlewareUserID, targetGoUserID, pData, numBytes, channel, reliability); + } +#endif +} + +void AnticheatPlugInterface::StartSignalling(const char* szMiddlewareUserID, uint64_t goUserID) +{ +#if defined(AC_ENABLED) + if (IsPluginLoaded() && Functions.fnStartSignalling != nullptr) + { + Functions.fnStartSignalling(szMiddlewareUserID, goUserID); + } +#endif +} + +int AnticheatPlugInterface::GetNextRecvPacketSize(uint8_t channelToReceiveOn) +{ +#if defined(AC_ENABLED) + if (IsPluginLoaded() && Functions.fnGetNextRecvPacketSize != nullptr) + { + return Functions.fnGetNextRecvPacketSize(channelToReceiveOn); + } +#endif + + return 0; +} + +bool AnticheatPlugInterface::RecvPacket(uint8_t** pOutData, uint8_t channelToReceiveOn) +{ +#if defined(AC_ENABLED) + if (IsPluginLoaded() && Functions.fnRecvPacket != nullptr) + { + return Functions.fnRecvPacket(pOutData, channelToReceiveOn); + } +#endif + + return false; +} + +void AnticheatPlugInterface::DisconnectPlayer(const char* szMiddlewareUserID, uint64_t goUserID) +{ +#if defined(AC_ENABLED) + if (IsPluginLoaded() && Functions.fnDisconnectPlayer != nullptr) + { + Functions.fnDisconnectPlayer(szMiddlewareUserID, goUserID); + } +#endif +} + +void AnticheatPlugInterface::DisconnectAll() +{ +#if defined(AC_ENABLED) + if (IsPluginLoaded() && Functions.fnDisconnectAll != nullptr) + { + Functions.fnDisconnectAll(); + } +#endif } AnticheatPlugInterface::AnticheatPluginFunctionPtrs AnticheatPlugInterface::Functions; @@ -328,6 +477,7 @@ int64_t AnticheatPlugInterface::m_tokenCreationTime = -1; bool AnticheatPlugInterface::RegisterPlayer(std::string mwUserID, uint32_t goUserID) { +#if defined(AC_ENABLED) if (!g_bSessionStarted) // TODO_AC: This is hacky, it's because on lobby join, the server can send AC_REGISTER_PLAYER before we join the lobby, so we didnt actually start the session yet. We should buffer these messages until session start or something instead of relying on this hacky global { AnticheatPlugInterface::BeginSession(); @@ -343,11 +493,15 @@ bool AnticheatPlugInterface::RegisterPlayer(std::string mwUserID, uint32_t goUse } return false; +#else + return true; +#endif } bool AnticheatPlugInterface::DeregisterPlayer(std::string mwUserID, uint32_t goUserID) { +#if defined(AC_ENABLED) if (IsPluginLoaded() && Functions.fnDeregisterPlayer != nullptr) { NetworkLog(ELogVerbosity::LOG_RELEASE, "DeregisterPlayer: %s to %" PRIu32, mwUserID.c_str(), goUserID); @@ -358,10 +512,14 @@ bool AnticheatPlugInterface::DeregisterPlayer(std::string mwUserID, uint32_t goU } return false; +#else + return true; +#endif } void AnticheatPlugInterface::Tick() { +#if defined(AC_ENABLED) if (IsPluginLoaded() && Functions.fnTick != nullptr) { Functions.fnTick(); @@ -377,10 +535,12 @@ void AnticheatPlugInterface::Tick() } } } +#endif } void AnticheatPlugInterface::RefreshToken() { +#if defined(AC_ENABLED) if (IsPluginLoaded() && Functions.fnRefreshToken != nullptr && Functions.fnIsLoggedIn != nullptr) { NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC] Refreshing token"); @@ -405,10 +565,12 @@ void AnticheatPlugInterface::RefreshToken() } }); } +#endif } void AnticheatPlugInterface::UnloadPlugin() { +#if defined(AC_ENABLED) if (IsPluginLoaded()) { NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC] Starting Shutdown"); @@ -424,4 +586,5 @@ void AnticheatPlugInterface::UnloadPlugin() g_hACPluginModule = nullptr; NetworkLog(ELogVerbosity::LOG_RELEASE, "[AC] Unloaded plugin"); } +#endif } diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index 8d85f4fd332..44687875fcf 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -312,7 +312,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, #ifdef DEBUG_WINDOWS_MESSAGES static msgCount = 0; char testString[256]; - sprintf(testString, "\n%d: %s (%X,%X)", msgCount++, messageToString(message), wParam, lParam); + snprintf(testString, sizeof(testString), "\n%d: %s (%X,%X)", msgCount++, messageToString(message), wParam, lParam); OutputDebugString(testString); #endif @@ -861,7 +861,7 @@ Int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, char filePath[_MAX_PATH]; const char* fileName = "Install_Final.bmp"; static const char* localizedPathFormat = "Data/%s/"; - sprintf(filePath, localizedPathFormat, GetRegistryLanguage().str()); + snprintf(filePath, sizeof(filePath), localizedPathFormat, GetRegistryLanguage().str()); strlcat(filePath, fileName, ARRAY_SIZE(filePath)); FILE* fileImage = fopen(filePath, "r"); if (fileImage) {