From 42a5c400fb148198f90c8aa6e3073f8251bed944 Mon Sep 17 00:00:00 2001 From: x64-dev <202863051+x64-dev@users.noreply.github.com> Date: Mon, 1 Sep 2025 16:23:06 -0400 Subject: [PATCH 1/9] Fix custom camera height not being applied --- .../Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 5cfc1e2f2ef..1e7c857c071 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -2097,7 +2097,7 @@ void GameLogic::startNewGame( Bool loadingSaveGame ) // update the loadscreen updateLoadProgress(LOAD_PROGRESS_POST_PRELOAD_ASSETS); - TheTacticalView->setDefaultView(0.0f, 0.0f, 1.0f); + TheTacticalView->setDefaultView(0.0f, 0.0f, 1.0f, false); TheTacticalView->setAngleAndPitchToDefault(); TheTacticalView->setZoomToDefault(); From 70585f7f00923783cfe481b4a5fdbb75cd125068 Mon Sep 17 00:00:00 2001 From: x64-dev <202863051+x64-dev@users.noreply.github.com> Date: Tue, 2 Sep 2025 16:25:25 -0400 Subject: [PATCH 2/9] WIP MM --- .../GeneralsOnline/OnlineServices_Init.h | 3 + .../OnlineServices_LobbyInterface.h | 27 ++ .../GUICallbacks/Menus/WOLQuickMatchMenu.cpp | 234 +++++++++++++++++- .../GeneralsOnline/OnlineServices_Init.cpp | 1 + .../OnlineServices_RoomsInterface.cpp | 62 +++++ 5 files changed, 323 insertions(+), 4 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h index db8b9884b72..7844eba4455 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h @@ -48,6 +48,9 @@ enum EWebSocketMessageID NETWORK_CONNECTION_START_SIGNALLING = 17, NETWORK_CONNECTION_DISCONNECT_PLAYER = 18, NETWORK_CONNECTION_CLIENT_REQUEST_SIGNALLING = 19, + MATCHMAKING_ACTION_JOIN_PREARRANGED_LOBBY = 20, + MATCHMAKING_ACTION_START_GAME = 21, + MATCHMAKING_MESSAGE = 22 }; enum class EQoSRegions diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h index 5aea588c6fe..0f0e92b3409 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h @@ -79,6 +79,33 @@ class NGMP_OnlineServices_LobbyInterface void SearchForLobbies(std::function onStartCallback, std::function)> onCompleteCallback); + std::function m_fnCallbackMatchmakingMessage = nullptr; + void RegisterForMatchmakingMessageCallback(std::function cb) + { + m_fnCallbackMatchmakingMessage = cb; + } + void InvokeMatchmakingMessageCallback(std::string str) + { + if (m_fnCallbackMatchmakingMessage != nullptr) + { + m_fnCallbackMatchmakingMessage(str); + } + } + + void InvokeMatchmakingStartGameCallback() + { + if (m_fnCallbackMatchmakingStartGame != nullptr) + { + m_fnCallbackMatchmakingStartGame(); + } + } + + std::function m_fnCallbackMatchmakingStartGame = nullptr; + void RegisterForMatchmakingStartGameCallback(std::function cb) + { + m_fnCallbackMatchmakingStartGame = cb; + } + // updates void UpdateCurrentLobby_Map(AsciiString strMap, AsciiString strMapPath, bool bIsOfficial, int newMaxPlayers); void UpdateCurrentLobby_LimitSuperweapons(bool bLimitSuperweapons); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp index 3b6bb9e7f15..4b9799684fd 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp @@ -84,6 +84,7 @@ static Bool s_inQM = FALSE; extern NGMPGame* TheNGMPGame; #endif #include "../OnlineServices_MatchmakingInterface.h" +#include "../OnlineServices_LobbyInterface.h" // PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// // window ids ------------------------------------------------------------------------------ @@ -636,6 +637,11 @@ static void populateQuickMatchMapSelectListbox( QuickMatchPreferences& pref ) static void saveQuickMatchOptions( void ) { + // TODO_QUICKMATCH +#if defined(GENERALS_ONLINE) + return; +#endif + if(isInInit) return; QuickMatchPreferences pref; @@ -718,6 +724,224 @@ static void saveQuickMatchOptions( void ) //------------------------------------------------------------------------------------------------- void WOLQuickMatchMenuInit( WindowLayout *layout, void *userData ) { +#if defined(GENERALS_ONLINE) + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); + + // cannot connect to the lobby we joined + pLobbyInterface->RegisterForCannotConnectToLobbyCallback([](void) + { + // TODO_QUICKMATCH: Show error message + stop matchmaking + enable buttons again + }); +#endif + + + if (pLobbyInterface != nullptr) + { + // TODO_QUICKMATCH: Deregister when leaving QM + pLobbyInterface->RegisterForMatchmakingMessageCallback([](std::string strMsg) + { + UnicodeString uMsg; + uMsg.format(L"%hs", strMsg.c_str()); + + Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, uMsg, GameSpyColor[GSCOLOR_DEFAULT], -1, -1); + GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index); + }); + + pLobbyInterface->RegisterForMatchmakingStartGameCallback([]() + { + buttonWiden->winEnable(FALSE); + + ////TheNGMPGame->enterGame(); + //TheNGMPGame->setSeed(12356); + + // TODO_QUICKMATCH + //TheGameSpyGame->markGameAsQM(); + + // TODO_QUICKMATCH + //const LadderInfo* info = getLadderInfo(); + + Int i; + Int numPlayers = 0; + for (i = 0; i < MAX_SLOTS; ++i) + { + //TheNGMPGame->getSlot(i); + // TODO_QUICKMATCH + //if (!resp.stagingRoomPlayerNames[i].empty()) + // ++numPlayers; + } + + // TODO_QUICKMATCH + // TODO_QUICKMATCH + //std::list maps = TheGameSpyConfig->getQMMaps(); + std::list maps; + maps.push_back(AsciiString("Maps\\Homeland Alliance\\Homeland Alliance.map")); + + for (std::list::const_iterator it = maps.begin(); it != maps.end(); ++it) + { + AsciiString theMap = *it; + theMap.toLower(); + const MapMetaData* md = TheMapCache->findMap(theMap); + if (md && md->m_numPlayers >= numPlayers) + { + TheNGMPGame->setMap(*it); + + // TODO_QUICKMATCH + //if (resp.qmStatus.mapIdx-- == 0) + // break; + } + } + + // TODO_QUICKMATCH: Create and join a lobby instead + // create our mesh + //if (pLobbyInterface != nullptr) + { + //pLobbyInterface->CreateMesh(); + } + + + Int numPlayersPerTeam = numPlayers / 2; + DEBUG_ASSERTCRASH(numPlayersPerTeam, ("0 players per team???")); + if (!numPlayersPerTeam) + numPlayersPerTeam = 1; + + for (i = 0; i < MAX_SLOTS; ++i) + { + NGMPGameSlot* slot = (NGMPGameSlot*)TheNGMPGame->getSlot(i); + + ////slot->setMapAvailability(TRUE); + + // TODO_QUICKMATCH + //if (resp.stagingRoomPlayerNames[i].empty()) + if (false) + { + //slot->setState(SLOT_CLOSED); + } + else if (slot->getState() == SLOT_PLAYER) + { + // TODO_QUICKMATCH + //AsciiString aName = resp.stagingRoomPlayerNames[i].c_str(); + //AsciiString aName; + //aName.format("QM User %d", i); + //UnicodeString uName; + //uName.translate(aName); + //slot->setState(SLOT_PLAYER, uName, 0); + + // TODO_QUICKMATCH + slot->setColor(i); + + // TODO_QUICKMATCH + slot->setStartPos(i); + slot->setPlayerTemplate(5+i); + //slot->setProfileID(0); + slot->setNATBehavior((FirewallHelperClass::FirewallBehaviorType)0); + //slot->setLocale(""); + slot->setTeamNumber(i / numPlayersPerTeam); + + slot->setMapAvailability(true); + + // TODO_QUICKMATCH + if (i == 0) + TheNGMPGame->setGameName(UnicodeString(L"Quickmatch")); + } + } + + //DEBUG_LOG(("Starting a QM game: options=[%s]", GameInfoToAsciiString(TheGameSpyGame).str())); + //SendStatsToOtherPlayers(TheNGMPGame); + TheNGMPGame->startGame(0); + GameWindow* buttonBuddies = TheWindowManager->winGetWindowFromId(NULL, buttonBuddiesID); + if (buttonBuddies) + buttonBuddies->winEnable(FALSE); + GameSpyCloseOverlay(GSOVERLAY_BUDDY); + }); + + pLobbyInterface->RegisterForJoinLobbyCallback([](EJoinLobbyResult result) + { + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); + + if (!pLobbyInterface->IsInLobby()) + { + return; + } + + if (TheNGMPGame == nullptr) + { + TheNGMPGame = new NGMPGame(); + } + pLobbyInterface->UpdateRoomDataCache([]() + { + + }); + + // connection events (for debug really) + NetworkMesh* pMesh = NGMP_OnlineServicesManager::GetNetworkMesh(); + if (pMesh != nullptr) + { + pMesh->RegisterForConnectionEvents([](int64_t userID, std::wstring strDisplayName, PlayerConnection* connection) + { + std::string strState = "Unknown"; + + EConnectionState connState = connection->GetState(); + std::string strConnectionType = connection->GetConnectionType(); + + switch (connState) + { + case EConnectionState::NOT_CONNECTED: + strState = "Not Connected"; + break; + + case EConnectionState::CONNECTING_DIRECT: + strState = "Connecting"; + break; + case EConnectionState::FINDING_ROUTE: + strState = "Connecting (Finding Route)"; + break; + + case EConnectionState::CONNECTED_DIRECT: + strState = "Connected"; + break; + + case EConnectionState::CONNECTION_FAILED: + strState = "Connection Failed"; + break; + + case EConnectionState::CONNECTION_DISCONNECTED: + strState = "Disconnected (Was Connected Previously)"; + break; + + default: + strState = "Unknown"; + break; + } + + UnicodeString strConnectionMessage; + if (connState == EConnectionState::CONNECTING_DIRECT || connState == EConnectionState::FINDING_ROUTE) + { + strConnectionMessage.format(L"Connecting to %s", strDisplayName.c_str()); + + Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, strConnectionMessage, GameMakeColor(255, 194, 15, 255), -1, -1); + GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index); + } + else if (connState == EConnectionState::CONNECTED_DIRECT) + { + strConnectionMessage.format(L"Connected to %s", strDisplayName.c_str()); + + Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, strConnectionMessage, GameMakeColor(255, 194, 15, 255), -1, -1); + GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index); + } + else + { + if (connState == EConnectionState::CONNECTION_FAILED || connState == EConnectionState::CONNECTION_DISCONNECTED) + { + strConnectionMessage.format(L"Connection failed to %s", strDisplayName.c_str()); + Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, strConnectionMessage, GameMakeColor(255, 194, 15, 255), -1, -1); + GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index); + } + } + }); + } + + }); + } isInInit = TRUE; if (TheGameSpyGame && TheGameSpyGame->isGameInProgress()) { @@ -972,7 +1196,9 @@ static void shutdownComplete( WindowLayout *layout ) //------------------------------------------------------------------------------------------------- void WOLQuickMatchMenuShutdown( WindowLayout *layout, void *userData ) { +#if !defined(GENERALS_ONLINE) TheGameSpyInfo->unregisterTextWindow(quickmatchTextWindow); +#endif if (!TheGameEngine->getQuitting()) saveQuickMatchOptions(); @@ -1068,7 +1294,7 @@ void WOLQuickMatchMenuUpdate( WindowLayout * layout, void *userData) RaiseGSMessageBox(); raiseMessageBoxes = false; } - + /// @todo: MDC handle disconnects in-game the same way as Custom Match! if (TheShell->isAnimFinished() && !buttonPushed && TheGameSpyPeerMessageQueue) @@ -1734,8 +1960,6 @@ WindowMsgHandledType WOLQuickMatchMenuSystem( GameWindow *window, UnsignedInt ms // TODO_QUICKMATCH: Chat has a sound effect in TheGameSpyInfo, re-eanble it if (bSuccess) { - Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, UnicodeString(L"Started matchmaking... searching for players"), GameSpyColor[GSCOLOR_DEFAULT], -1, -1); - GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index); // buttons buttonWiden->winEnable(FALSE); @@ -1938,8 +2162,10 @@ WindowMsgHandledType WOLQuickMatchMenuSystem( GameWindow *window, UnsignedInt ms } else if ( controlID == buttonBackID ) { - buttonPushed = true; +#if !defined(GENERALS_ONLINE) TheGameSpyInfo->leaveGroupRoom(); +#endif + buttonPushed = true; nextScreen = "Menus/WOLWelcomeMenu.wnd"; TheShell->pop(); } //if ( controlID == buttonBack ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Init.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Init.cpp index 97a575f1881..3f3bd8ecfc1 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Init.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_Init.cpp @@ -365,6 +365,7 @@ void NGMP_OnlineServicesManager::Init() m_pLobbyInterface = new NGMP_OnlineServices_LobbyInterface(); m_pRoomInterface = new NGMP_OnlineServices_RoomsInterface(); m_pStatsInterface = new NGMP_OnlineServices_StatsInterface(); + m_pMatchmakingInterface = new NGMP_OnlineServices_MatchmakingInterface(); m_pHTTPManager = new HTTPManager(); m_pHTTPManager->Initialize(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp index ae18ae999dd..008a0d8922a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp @@ -208,6 +208,14 @@ class WebSocketMessage_NetworkDisconnectPlayer : public WebSocketMessageBase NLOHMANN_DEFINE_TYPE_INTRUSIVE(WebSocketMessage_NetworkDisconnectPlayer, msg_id, lobby_id, user_id) }; +class WebSocketMessage_MatchmakingAction_JoinPrearrangedLobby : public WebSocketMessageBase +{ +public: + int64_t lobby_id; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE(WebSocketMessage_MatchmakingAction_JoinPrearrangedLobby, msg_id, lobby_id) +}; + class WebSocketMessage_RoomChatIncoming : public WebSocketMessageBase { @@ -247,6 +255,14 @@ class WebSocketMessage_RelayUpgrade : public WebSocketMessageBase NLOHMANN_DEFINE_TYPE_INTRUSIVE(WebSocketMessage_RelayUpgrade, msg_id, target_user_id) }; +class WebSocketMessage_MatchmakingMessage : public WebSocketMessageBase +{ +public: + std::string message; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE(WebSocketMessage_MatchmakingMessage, msg_id, message) +}; + class WebSocketMessage_NetworkRoomMemberListUpdate : public WebSocketMessageBase { public: @@ -546,6 +562,52 @@ void WebSocket::Tick() } break; + case EWebSocketMessageID::MATCHMAKING_ACTION_JOIN_PREARRANGED_LOBBY: + { + WebSocketMessage_MatchmakingAction_JoinPrearrangedLobby mmEvent = jsonObject.get(); + + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pLobbyInterface != nullptr) + { + // TODO_QUICKMATCH: Only if really in quickmatch + + // basic info needed to join + LobbyEntry lobbyEntry; + lobbyEntry.lobbyID = mmEvent.lobby_id; + lobbyEntry.map_path = "Maps\\Homeland Alliance\\Homeland Alliance.map"; + + pLobbyInterface->JoinLobby(lobbyEntry, std::string()); + } + else + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[NETWORK_CONNECTION_DISCONNECT_PLAYER] Lobby interface is null"); + break; + } + } + break; + + case EWebSocketMessageID::MATCHMAKING_ACTION_START_GAME: + { + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pLobbyInterface != nullptr) + { + pLobbyInterface->InvokeMatchmakingStartGameCallback(); + } + } + break; + + case EWebSocketMessageID::MATCHMAKING_MESSAGE: + { + WebSocketMessage_MatchmakingMessage matchmakingMsg = jsonObject.get(); + + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pLobbyInterface != nullptr) + { + pLobbyInterface->InvokeMatchmakingMessageCallback(matchmakingMsg.message); + } + } + break; + default: NetworkLog(ELogVerbosity::LOG_RELEASE, "Unhandled WebSocketMessage: %d", (int)msgID); break; From eb1f2dc8b414404ce14c6b99883b515c98a60c49 Mon Sep 17 00:00:00 2001 From: x64-dev <202863051+x64-dev@users.noreply.github.com> Date: Tue, 2 Sep 2025 21:03:10 -0400 Subject: [PATCH 3/9] - Removed enet (unused now) --- .../GeneralsOnline/NextGenTransport.h | 3 +- .../GeneralsOnline/Vendor/ENet/callbacks.h | 27 - .../GeneralsOnline/Vendor/ENet/enet.h | 616 ------------------ .../GeneralsOnline/Vendor/ENet/list.h | 43 -- .../GeneralsOnline/Vendor/ENet/protocol.h | 198 ------ .../GeneralsOnline/Vendor/ENet/time.h | 18 - .../GeneralsOnline/Vendor/ENet/types.h | 13 - .../GeneralsOnline/Vendor/ENet/unix.h | 48 -- .../GeneralsOnline/Vendor/ENet/utility.h | 13 - .../GeneralsOnline/Vendor/ENet/win32.h | 62 -- .../GeneralsOnline/Vendor/ENet/enet.lib | Bin 58202 -> 0 bytes .../GeneralsOnline/Vendor/ENet/enet64.lib | Bin 56018 -> 0 bytes 12 files changed, 1 insertion(+), 1040 deletions(-) delete mode 100644 GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/callbacks.h delete mode 100644 GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/enet.h delete mode 100644 GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/list.h delete mode 100644 GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/protocol.h delete mode 100644 GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/time.h delete mode 100644 GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/types.h delete mode 100644 GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/unix.h delete mode 100644 GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/utility.h delete mode 100644 GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/win32.h delete mode 100644 GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Vendor/ENet/enet.lib delete mode 100644 GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Vendor/ENet/enet64.lib diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NextGenTransport.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NextGenTransport.h index 35998abb8ce..ecebdd581d0 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NextGenTransport.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NextGenTransport.h @@ -7,9 +7,8 @@ #include "../../Transport.h" #include "../NGMP_include.h" - -#pragma comment(lib, "ENet/enet.lib") #include "GameNetwork/GeneralsOnline/Vendor/ValveNetworkingSockets/steam/isteamnetworkingmessages.h" + #pragma comment(lib, "ValveNetworkingSockets/GameNetworkingSockets.lib") #pragma comment(lib, "ValveNetworkingSockets/abseil_dll.lib") #pragma comment(lib, "ValveNetworkingSockets/libcrypto.lib") diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/callbacks.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/callbacks.h deleted file mode 100644 index 340a4a9896b..00000000000 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/callbacks.h +++ /dev/null @@ -1,27 +0,0 @@ -/** - @file callbacks.h - @brief ENet callbacks -*/ -#ifndef __ENET_CALLBACKS_H__ -#define __ENET_CALLBACKS_H__ - -#include - -typedef struct _ENetCallbacks -{ - void * (ENET_CALLBACK * malloc) (size_t size); - void (ENET_CALLBACK * free) (void * memory); - void (ENET_CALLBACK * no_memory) (void); -} ENetCallbacks; - -/** @defgroup callbacks ENet internal callbacks - @{ - @ingroup private -*/ -extern void * enet_malloc (size_t); -extern void enet_free (void *); - -/** @} */ - -#endif /* __ENET_CALLBACKS_H__ */ - diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/enet.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/enet.h deleted file mode 100644 index 30010187633..00000000000 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/enet.h +++ /dev/null @@ -1,616 +0,0 @@ -/** - @file enet.h - @brief ENet public header file -*/ -#ifndef __ENET_ENET_H__ -#define __ENET_ENET_H__ - -#ifdef __cplusplus -extern "C" -{ -#endif - -#include - -#ifdef _WIN32 -#include "enet/win32.h" -#else -#include "enet/unix.h" -#endif - -#include "enet/types.h" -#include "enet/protocol.h" -#include "enet/list.h" -#include "enet/callbacks.h" - -#define ENET_VERSION_MAJOR 1 -#define ENET_VERSION_MINOR 3 -#define ENET_VERSION_PATCH 18 -#define ENET_VERSION_CREATE(major, minor, patch) (((major)<<16) | ((minor)<<8) | (patch)) -#define ENET_VERSION_GET_MAJOR(version) (((version)>>16)&0xFF) -#define ENET_VERSION_GET_MINOR(version) (((version)>>8)&0xFF) -#define ENET_VERSION_GET_PATCH(version) ((version)&0xFF) -#define ENET_VERSION ENET_VERSION_CREATE(ENET_VERSION_MAJOR, ENET_VERSION_MINOR, ENET_VERSION_PATCH) - -typedef enet_uint32 ENetVersion; - -struct _ENetHost; -struct _ENetEvent; -struct _ENetPacket; - -typedef enum _ENetSocketType -{ - ENET_SOCKET_TYPE_STREAM = 1, - ENET_SOCKET_TYPE_DATAGRAM = 2 -} ENetSocketType; - -typedef enum _ENetSocketWait -{ - ENET_SOCKET_WAIT_NONE = 0, - ENET_SOCKET_WAIT_SEND = (1 << 0), - ENET_SOCKET_WAIT_RECEIVE = (1 << 1), - ENET_SOCKET_WAIT_INTERRUPT = (1 << 2) -} ENetSocketWait; - -typedef enum _ENetSocketOption -{ - ENET_SOCKOPT_NONBLOCK = 1, - ENET_SOCKOPT_BROADCAST = 2, - ENET_SOCKOPT_RCVBUF = 3, - ENET_SOCKOPT_SNDBUF = 4, - ENET_SOCKOPT_REUSEADDR = 5, - ENET_SOCKOPT_RCVTIMEO = 6, - ENET_SOCKOPT_SNDTIMEO = 7, - ENET_SOCKOPT_ERROR = 8, - ENET_SOCKOPT_NODELAY = 9, - ENET_SOCKOPT_TTL = 10 -} ENetSocketOption; - -typedef enum _ENetSocketShutdown -{ - ENET_SOCKET_SHUTDOWN_READ = 0, - ENET_SOCKET_SHUTDOWN_WRITE = 1, - ENET_SOCKET_SHUTDOWN_READ_WRITE = 2 -} ENetSocketShutdown; - -#define ENET_HOST_ANY 0 -#define ENET_HOST_BROADCAST 0xFFFFFFFFU -#define ENET_PORT_ANY 0 - -/** - * Portable internet address structure. - * - * The host must be specified in network byte-order, and the port must be in host - * byte-order. The constant ENET_HOST_ANY may be used to specify the default - * server host. The constant ENET_HOST_BROADCAST may be used to specify the - * broadcast address (255.255.255.255). This makes sense for enet_host_connect, - * but not for enet_host_create. Once a server responds to a broadcast, the - * address is updated from ENET_HOST_BROADCAST to the server's actual IP address. - */ -typedef struct _ENetAddress -{ - enet_uint32 host; - enet_uint16 port; -} ENetAddress; - -/** - * Packet flag bit constants. - * - * The host must be specified in network byte-order, and the port must be in - * host byte-order. The constant ENET_HOST_ANY may be used to specify the - * default server host. - - @sa ENetPacket -*/ -typedef enum _ENetPacketFlag -{ - /** packet must be received by the target peer and resend attempts should be - * made until the packet is delivered */ - ENET_PACKET_FLAG_RELIABLE = (1 << 0), - /** packet will not be sequenced with other packets - * not supported for reliable packets - */ - ENET_PACKET_FLAG_UNSEQUENCED = (1 << 1), - /** packet will not allocate data, and user must supply it instead */ - ENET_PACKET_FLAG_NO_ALLOCATE = (1 << 2), - /** packet will be fragmented using unreliable (instead of reliable) sends - * if it exceeds the MTU */ - ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT = (1 << 3), - - /** whether the packet has been sent from all queues it has been entered into */ - ENET_PACKET_FLAG_SENT = (1<<8) -} ENetPacketFlag; - -typedef void (ENET_CALLBACK * ENetPacketFreeCallback) (struct _ENetPacket *); - -/** - * ENet packet structure. - * - * An ENet data packet that may be sent to or received from a peer. The shown - * fields should only be read and never modified. The data field contains the - * allocated data for the packet. The dataLength fields specifies the length - * of the allocated data. The flags field is either 0 (specifying no flags), - * or a bitwise-or of any combination of the following flags: - * - * ENET_PACKET_FLAG_RELIABLE - packet must be received by the target peer - * and resend attempts should be made until the packet is delivered - * - * ENET_PACKET_FLAG_UNSEQUENCED - packet will not be sequenced with other packets - * (not supported for reliable packets) - * - * ENET_PACKET_FLAG_NO_ALLOCATE - packet will not allocate data, and user must supply it instead - * - * ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT - packet will be fragmented using unreliable - * (instead of reliable) sends if it exceeds the MTU - * - * ENET_PACKET_FLAG_SENT - whether the packet has been sent from all queues it has been entered into - @sa ENetPacketFlag - */ -typedef struct _ENetPacket -{ - size_t referenceCount; /**< internal use only */ - enet_uint32 flags; /**< bitwise-or of ENetPacketFlag constants */ - enet_uint8 * data; /**< allocated data for packet */ - size_t dataLength; /**< length of data */ - ENetPacketFreeCallback freeCallback; /**< function to be called when the packet is no longer in use */ - void * userData; /**< application private data, may be freely modified */ -} ENetPacket; - -typedef struct _ENetAcknowledgement -{ - ENetListNode acknowledgementList; - enet_uint32 sentTime; - ENetProtocol command; -} ENetAcknowledgement; - -typedef struct _ENetOutgoingCommand -{ - ENetListNode outgoingCommandList; - enet_uint16 reliableSequenceNumber; - enet_uint16 unreliableSequenceNumber; - enet_uint32 sentTime; - enet_uint32 roundTripTimeout; - enet_uint32 queueTime; - enet_uint32 fragmentOffset; - enet_uint16 fragmentLength; - enet_uint16 sendAttempts; - ENetProtocol command; - ENetPacket * packet; -} ENetOutgoingCommand; - -typedef struct _ENetIncomingCommand -{ - ENetListNode incomingCommandList; - enet_uint16 reliableSequenceNumber; - enet_uint16 unreliableSequenceNumber; - ENetProtocol command; - enet_uint32 fragmentCount; - enet_uint32 fragmentsRemaining; - enet_uint32 * fragments; - ENetPacket * packet; -} ENetIncomingCommand; - -typedef enum _ENetPeerState -{ - ENET_PEER_STATE_DISCONNECTED = 0, - ENET_PEER_STATE_CONNECTING = 1, - ENET_PEER_STATE_ACKNOWLEDGING_CONNECT = 2, - ENET_PEER_STATE_CONNECTION_PENDING = 3, - ENET_PEER_STATE_CONNECTION_SUCCEEDED = 4, - ENET_PEER_STATE_CONNECTED = 5, - ENET_PEER_STATE_DISCONNECT_LATER = 6, - ENET_PEER_STATE_DISCONNECTING = 7, - ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT = 8, - ENET_PEER_STATE_ZOMBIE = 9 -} ENetPeerState; - -#ifndef ENET_BUFFER_MAXIMUM -#define ENET_BUFFER_MAXIMUM (1 + 2 * ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS) -#endif - -enum -{ - ENET_HOST_RECEIVE_BUFFER_SIZE = 256 * 1024, - ENET_HOST_SEND_BUFFER_SIZE = 256 * 1024, - ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL = 1000, - ENET_HOST_DEFAULT_MTU = 1392, - ENET_HOST_DEFAULT_MAXIMUM_PACKET_SIZE = 32 * 1024 * 1024, - ENET_HOST_DEFAULT_MAXIMUM_WAITING_DATA = 32 * 1024 * 1024, - - ENET_PEER_DEFAULT_ROUND_TRIP_TIME = 500, - ENET_PEER_DEFAULT_PACKET_THROTTLE = 32, - ENET_PEER_PACKET_THROTTLE_SCALE = 32, - ENET_PEER_PACKET_THROTTLE_COUNTER = 7, - ENET_PEER_PACKET_THROTTLE_ACCELERATION = 2, - ENET_PEER_PACKET_THROTTLE_DECELERATION = 2, - ENET_PEER_PACKET_THROTTLE_INTERVAL = 5000, - ENET_PEER_PACKET_LOSS_SCALE = (1 << 16), - ENET_PEER_PACKET_LOSS_INTERVAL = 10000, - ENET_PEER_WINDOW_SIZE_SCALE = 64 * 1024, - ENET_PEER_TIMEOUT_LIMIT = 32, - ENET_PEER_TIMEOUT_MINIMUM = 5000, - ENET_PEER_TIMEOUT_MAXIMUM = 30000, - ENET_PEER_PING_INTERVAL = 500, - ENET_PEER_UNSEQUENCED_WINDOWS = 64, - ENET_PEER_UNSEQUENCED_WINDOW_SIZE = 1024, - ENET_PEER_FREE_UNSEQUENCED_WINDOWS = 32, - ENET_PEER_RELIABLE_WINDOWS = 16, - ENET_PEER_RELIABLE_WINDOW_SIZE = 0x1000, - ENET_PEER_FREE_RELIABLE_WINDOWS = 8 -}; - -typedef struct _ENetChannel -{ - enet_uint16 outgoingReliableSequenceNumber; - enet_uint16 outgoingUnreliableSequenceNumber; - enet_uint16 usedReliableWindows; - enet_uint16 reliableWindows [ENET_PEER_RELIABLE_WINDOWS]; - enet_uint16 incomingReliableSequenceNumber; - enet_uint16 incomingUnreliableSequenceNumber; - ENetList incomingReliableCommands; - ENetList incomingUnreliableCommands; -} ENetChannel; - -typedef enum _ENetPeerFlag -{ - ENET_PEER_FLAG_NEEDS_DISPATCH = (1 << 0), - ENET_PEER_FLAG_CONTINUE_SENDING = (1 << 1) -} ENetPeerFlag; - -/** - * An ENet peer which data packets may be sent or received from. - * - * No fields should be modified unless otherwise specified. - */ -typedef struct _ENetPeer -{ - ENetListNode dispatchList; - struct _ENetHost * host; - enet_uint16 outgoingPeerID; - enet_uint16 incomingPeerID; - enet_uint32 connectID; - enet_uint8 outgoingSessionID; - enet_uint8 incomingSessionID; - ENetAddress address; /**< Internet address of the peer */ - void * data; /**< Application private data, may be freely modified */ - ENetPeerState state; - ENetChannel * channels; - size_t channelCount; /**< Number of channels allocated for communication with peer */ - enet_uint32 incomingBandwidth; /**< Downstream bandwidth of the client in bytes/second */ - enet_uint32 outgoingBandwidth; /**< Upstream bandwidth of the client in bytes/second */ - enet_uint32 incomingBandwidthThrottleEpoch; - enet_uint32 outgoingBandwidthThrottleEpoch; - enet_uint32 incomingDataTotal; - enet_uint32 outgoingDataTotal; - enet_uint32 lastSendTime; - enet_uint32 lastReceiveTime; - enet_uint32 nextTimeout; - enet_uint32 earliestTimeout; - enet_uint32 packetLossEpoch; - enet_uint32 packetsSent; - enet_uint32 packetsLost; - enet_uint32 packetLoss; /**< mean packet loss of reliable packets as a ratio with respect to the constant ENET_PEER_PACKET_LOSS_SCALE */ - enet_uint32 packetLossVariance; - enet_uint32 packetThrottle; - enet_uint32 packetThrottleLimit; - enet_uint32 packetThrottleCounter; - enet_uint32 packetThrottleEpoch; - enet_uint32 packetThrottleAcceleration; - enet_uint32 packetThrottleDeceleration; - enet_uint32 packetThrottleInterval; - enet_uint32 pingInterval; - enet_uint32 timeoutLimit; - enet_uint32 timeoutMinimum; - enet_uint32 timeoutMaximum; - enet_uint32 lastRoundTripTime; - enet_uint32 lowestRoundTripTime; - enet_uint32 lastRoundTripTimeVariance; - enet_uint32 highestRoundTripTimeVariance; - enet_uint32 roundTripTime; /**< mean round trip time (RTT), in milliseconds, between sending a reliable packet and receiving its acknowledgement */ - enet_uint32 roundTripTimeVariance; - enet_uint32 mtu; - enet_uint32 windowSize; - enet_uint32 reliableDataInTransit; - enet_uint16 outgoingReliableSequenceNumber; - ENetList acknowledgements; - ENetList sentReliableCommands; - ENetList outgoingSendReliableCommands; - ENetList outgoingCommands; - ENetList dispatchedCommands; - enet_uint16 flags; - enet_uint16 reserved; - enet_uint16 incomingUnsequencedGroup; - enet_uint16 outgoingUnsequencedGroup; - enet_uint32 unsequencedWindow [ENET_PEER_UNSEQUENCED_WINDOW_SIZE / 32]; - enet_uint32 eventData; - size_t totalWaitingData; -} ENetPeer; - -/** An ENet packet compressor for compressing UDP packets before socket sends or receives. - */ -typedef struct _ENetCompressor -{ - /** Context data for the compressor. Must be non-NULL. */ - void * context; - /** Compresses from inBuffers[0:inBufferCount-1], containing inLimit bytes, to outData, outputting at most outLimit bytes. Should return 0 on failure. */ - size_t (ENET_CALLBACK * compress) (void * context, const ENetBuffer * inBuffers, size_t inBufferCount, size_t inLimit, enet_uint8 * outData, size_t outLimit); - /** Decompresses from inData, containing inLimit bytes, to outData, outputting at most outLimit bytes. Should return 0 on failure. */ - size_t (ENET_CALLBACK * decompress) (void * context, const enet_uint8 * inData, size_t inLimit, enet_uint8 * outData, size_t outLimit); - /** Destroys the context when compression is disabled or the host is destroyed. May be NULL. */ - void (ENET_CALLBACK * destroy) (void * context); -} ENetCompressor; - -/** Callback that computes the checksum of the data held in buffers[0:bufferCount-1] */ -typedef enet_uint32 (ENET_CALLBACK * ENetChecksumCallback) (const ENetBuffer * buffers, size_t bufferCount); - -/** Callback for intercepting received raw UDP packets. Should return 1 to intercept, 0 to ignore, or -1 to propagate an error. */ -typedef int (ENET_CALLBACK * ENetInterceptCallback) (struct _ENetHost * host, struct _ENetEvent * event); - -/** An ENet host for communicating with peers. - * - * No fields should be modified unless otherwise stated. - - @sa enet_host_create() - @sa enet_host_destroy() - @sa enet_host_connect() - @sa enet_host_service() - @sa enet_host_flush() - @sa enet_host_broadcast() - @sa enet_host_compress() - @sa enet_host_compress_with_range_coder() - @sa enet_host_channel_limit() - @sa enet_host_bandwidth_limit() - @sa enet_host_bandwidth_throttle() - */ -typedef struct _ENetHost -{ - ENetSocket socket; - ENetAddress address; /**< Internet address of the host */ - enet_uint32 incomingBandwidth; /**< downstream bandwidth of the host */ - enet_uint32 outgoingBandwidth; /**< upstream bandwidth of the host */ - enet_uint32 bandwidthThrottleEpoch; - enet_uint32 mtu; - enet_uint32 randomSeed; - int recalculateBandwidthLimits; - ENetPeer * peers; /**< array of peers allocated for this host */ - size_t peerCount; /**< number of peers allocated for this host */ - size_t channelLimit; /**< maximum number of channels allowed for connected peers */ - enet_uint32 serviceTime; - ENetList dispatchQueue; - enet_uint32 totalQueued; - size_t packetSize; - enet_uint16 headerFlags; - ENetProtocol commands [ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS]; - size_t commandCount; - ENetBuffer buffers [ENET_BUFFER_MAXIMUM]; - size_t bufferCount; - ENetChecksumCallback checksum; /**< callback the user can set to enable packet checksums for this host */ - ENetCompressor compressor; - enet_uint8 packetData [2][ENET_PROTOCOL_MAXIMUM_MTU]; - ENetAddress receivedAddress; - enet_uint8 * receivedData; - size_t receivedDataLength; - enet_uint32 totalSentData; /**< total data sent, user should reset to 0 as needed to prevent overflow */ - enet_uint32 totalSentPackets; /**< total UDP packets sent, user should reset to 0 as needed to prevent overflow */ - enet_uint32 totalReceivedData; /**< total data received, user should reset to 0 as needed to prevent overflow */ - enet_uint32 totalReceivedPackets; /**< total UDP packets received, user should reset to 0 as needed to prevent overflow */ - ENetInterceptCallback intercept; /**< callback the user can set to intercept received raw UDP packets */ - size_t connectedPeers; - size_t bandwidthLimitedPeers; - size_t duplicatePeers; /**< optional number of allowed peers from duplicate IPs, defaults to ENET_PROTOCOL_MAXIMUM_PEER_ID */ - size_t maximumPacketSize; /**< the maximum allowable packet size that may be sent or received on a peer */ - size_t maximumWaitingData; /**< the maximum aggregate amount of buffer space a peer may use waiting for packets to be delivered */ -} ENetHost; - -/** - * An ENet event type, as specified in @ref ENetEvent. - */ -typedef enum _ENetEventType -{ - /** no event occurred within the specified time limit */ - ENET_EVENT_TYPE_NONE = 0, - - /** a connection request initiated by enet_host_connect has completed. - * The peer field contains the peer which successfully connected. - */ - ENET_EVENT_TYPE_CONNECT = 1, - - /** a peer has disconnected. This event is generated on a successful - * completion of a disconnect initiated by enet_peer_disconnect, if - * a peer has timed out, or if a connection request intialized by - * enet_host_connect has timed out. The peer field contains the peer - * which disconnected. The data field contains user supplied data - * describing the disconnection, or 0, if none is available. - */ - ENET_EVENT_TYPE_DISCONNECT = 2, - - /** a packet has been received from a peer. The peer field specifies the - * peer which sent the packet. The channelID field specifies the channel - * number upon which the packet was received. The packet field contains - * the packet that was received; this packet must be destroyed with - * enet_packet_destroy after use. - */ - ENET_EVENT_TYPE_RECEIVE = 3 -} ENetEventType; - -/** - * An ENet event as returned by enet_host_service(). - - @sa enet_host_service - */ -typedef struct _ENetEvent -{ - ENetEventType type; /**< type of the event */ - ENetPeer * peer; /**< peer that generated a connect, disconnect or receive event */ - enet_uint8 channelID; /**< channel on the peer that generated the event, if appropriate */ - enet_uint32 data; /**< data associated with the event, if appropriate */ - ENetPacket * packet; /**< packet associated with the event, if appropriate */ -} ENetEvent; - -/** @defgroup global ENet global functions - @{ -*/ - -/** - Initializes ENet globally. Must be called prior to using any functions in - ENet. - @returns 0 on success, < 0 on failure -*/ -ENET_API int enet_initialize (void); - -/** - Initializes ENet globally and supplies user-overridden callbacks. Must be called prior to using any functions in ENet. Do not use enet_initialize() if you use this variant. Make sure the ENetCallbacks structure is zeroed out so that any additional callbacks added in future versions will be properly ignored. - - @param version the constant ENET_VERSION should be supplied so ENet knows which version of ENetCallbacks struct to use - @param inits user-overridden callbacks where any NULL callbacks will use ENet's defaults - @returns 0 on success, < 0 on failure -*/ -ENET_API int enet_initialize_with_callbacks (ENetVersion version, const ENetCallbacks * inits); - -/** - Shuts down ENet globally. Should be called when a program that has - initialized ENet exits. -*/ -ENET_API void enet_deinitialize (void); - -/** - Gives the linked version of the ENet library. - @returns the version number -*/ -ENET_API ENetVersion enet_linked_version (void); - -/** @} */ - -/** @defgroup private ENet private implementation functions */ - -/** - Returns the wall-time in milliseconds. Its initial value is unspecified - unless otherwise set. - */ -ENET_API enet_uint32 enet_time_get (void); -/** - Sets the current wall-time in milliseconds. - */ -ENET_API void enet_time_set (enet_uint32); - -/** @defgroup socket ENet socket functions - @{ -*/ -ENET_API ENetSocket enet_socket_create (ENetSocketType); -ENET_API int enet_socket_bind (ENetSocket, const ENetAddress *); -ENET_API int enet_socket_get_address (ENetSocket, ENetAddress *); -ENET_API int enet_socket_listen (ENetSocket, int); -ENET_API ENetSocket enet_socket_accept (ENetSocket, ENetAddress *); -ENET_API int enet_socket_connect (ENetSocket, const ENetAddress *); -ENET_API int enet_socket_send (ENetSocket, const ENetAddress *, const ENetBuffer *, size_t); -ENET_API int enet_socket_receive (ENetSocket, ENetAddress *, ENetBuffer *, size_t); -ENET_API int enet_socket_wait (ENetSocket, enet_uint32 *, enet_uint32); -ENET_API int enet_socket_set_option (ENetSocket, ENetSocketOption, int); -ENET_API int enet_socket_get_option (ENetSocket, ENetSocketOption, int *); -ENET_API int enet_socket_shutdown (ENetSocket, ENetSocketShutdown); -ENET_API void enet_socket_destroy (ENetSocket); -ENET_API int enet_socketset_select (ENetSocket, ENetSocketSet *, ENetSocketSet *, enet_uint32); - -/** @} */ - -/** @defgroup Address ENet address functions - @{ -*/ - -/** Attempts to parse the printable form of the IP address in the parameter hostName - and sets the host field in the address parameter if successful. - @param address destination to store the parsed IP address - @param hostName IP address to parse - @retval 0 on success - @retval < 0 on failure - @returns the address of the given hostName in address on success -*/ -ENET_API int enet_address_set_host_ip (ENetAddress * address, const char * hostName); - -/** Attempts to resolve the host named by the parameter hostName and sets - the host field in the address parameter if successful. - @param address destination to store resolved address - @param hostName host name to lookup - @retval 0 on success - @retval < 0 on failure - @returns the address of the given hostName in address on success -*/ -ENET_API int enet_address_set_host (ENetAddress * address, const char * hostName); - -/** Gives the printable form of the IP address specified in the address parameter. - @param address address printed - @param hostName destination for name, must not be NULL - @param nameLength maximum length of hostName. - @returns the null-terminated name of the host in hostName on success - @retval 0 on success - @retval < 0 on failure -*/ -ENET_API int enet_address_get_host_ip (const ENetAddress * address, char * hostName, size_t nameLength); - -/** Attempts to do a reverse lookup of the host field in the address parameter. - @param address address used for reverse lookup - @param hostName destination for name, must not be NULL - @param nameLength maximum length of hostName. - @returns the null-terminated name of the host in hostName on success - @retval 0 on success - @retval < 0 on failure -*/ -ENET_API int enet_address_get_host (const ENetAddress * address, char * hostName, size_t nameLength); - -/** @} */ - -ENET_API ENetPacket * enet_packet_create (const void *, size_t, enet_uint32); -ENET_API void enet_packet_destroy (ENetPacket *); -ENET_API int enet_packet_resize (ENetPacket *, size_t); -ENET_API enet_uint32 enet_crc32 (const ENetBuffer *, size_t); - -ENET_API ENetHost * enet_host_create (const ENetAddress *, size_t, size_t, enet_uint32, enet_uint32); -ENET_API void enet_host_destroy (ENetHost *); -ENET_API ENetPeer * enet_host_connect (ENetHost *, const ENetAddress *, size_t, enet_uint32); -ENET_API int enet_host_check_events (ENetHost *, ENetEvent *); -ENET_API int enet_host_service (ENetHost *, ENetEvent *, enet_uint32); -ENET_API void enet_host_flush (ENetHost *); -ENET_API void enet_host_broadcast (ENetHost *, enet_uint8, ENetPacket *); -ENET_API void enet_host_compress (ENetHost *, const ENetCompressor *); -ENET_API int enet_host_compress_with_range_coder (ENetHost * host); -ENET_API void enet_host_channel_limit (ENetHost *, size_t); -ENET_API void enet_host_bandwidth_limit (ENetHost *, enet_uint32, enet_uint32); -extern void enet_host_bandwidth_throttle (ENetHost *); -extern enet_uint32 enet_host_random_seed (void); -extern enet_uint32 enet_host_random (ENetHost *); - -ENET_API int enet_peer_send (ENetPeer *, enet_uint8, ENetPacket *); -ENET_API ENetPacket * enet_peer_receive (ENetPeer *, enet_uint8 * channelID); -ENET_API void enet_peer_ping (ENetPeer *); -ENET_API void enet_peer_ping_interval (ENetPeer *, enet_uint32); -ENET_API void enet_peer_timeout (ENetPeer *, enet_uint32, enet_uint32, enet_uint32); -ENET_API void enet_peer_reset (ENetPeer *); -ENET_API void enet_peer_disconnect (ENetPeer *, enet_uint32); -ENET_API void enet_peer_disconnect_now (ENetPeer *, enet_uint32); -ENET_API void enet_peer_disconnect_later (ENetPeer *, enet_uint32); -ENET_API void enet_peer_throttle_configure (ENetPeer *, enet_uint32, enet_uint32, enet_uint32); -extern int enet_peer_throttle (ENetPeer *, enet_uint32); -extern void enet_peer_reset_queues (ENetPeer *); -extern int enet_peer_has_outgoing_commands (ENetPeer *); -extern void enet_peer_setup_outgoing_command (ENetPeer *, ENetOutgoingCommand *); -extern ENetOutgoingCommand * enet_peer_queue_outgoing_command (ENetPeer *, const ENetProtocol *, ENetPacket *, enet_uint32, enet_uint16); -extern ENetIncomingCommand * enet_peer_queue_incoming_command (ENetPeer *, const ENetProtocol *, const void *, size_t, enet_uint32, enet_uint32); -extern ENetAcknowledgement * enet_peer_queue_acknowledgement (ENetPeer *, const ENetProtocol *, enet_uint16); -extern void enet_peer_dispatch_incoming_unreliable_commands (ENetPeer *, ENetChannel *, ENetIncomingCommand *); -extern void enet_peer_dispatch_incoming_reliable_commands (ENetPeer *, ENetChannel *, ENetIncomingCommand *); -extern void enet_peer_on_connect (ENetPeer *); -extern void enet_peer_on_disconnect (ENetPeer *); - -ENET_API void * enet_range_coder_create (void); -ENET_API void enet_range_coder_destroy (void *); -ENET_API size_t enet_range_coder_compress (void *, const ENetBuffer *, size_t, size_t, enet_uint8 *, size_t); -ENET_API size_t enet_range_coder_decompress (void *, const enet_uint8 *, size_t, enet_uint8 *, size_t); - -extern size_t enet_protocol_command_size (enet_uint8); - -#ifdef __cplusplus -} -#endif - -#endif /* __ENET_ENET_H__ */ - diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/list.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/list.h deleted file mode 100644 index d7b2600848f..00000000000 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/list.h +++ /dev/null @@ -1,43 +0,0 @@ -/** - @file list.h - @brief ENet list management -*/ -#ifndef __ENET_LIST_H__ -#define __ENET_LIST_H__ - -#include - -typedef struct _ENetListNode -{ - struct _ENetListNode * next; - struct _ENetListNode * previous; -} ENetListNode; - -typedef ENetListNode * ENetListIterator; - -typedef struct _ENetList -{ - ENetListNode sentinel; -} ENetList; - -extern void enet_list_clear (ENetList *); - -extern ENetListIterator enet_list_insert (ENetListIterator, void *); -extern void * enet_list_remove (ENetListIterator); -extern ENetListIterator enet_list_move (ENetListIterator, void *, void *); - -extern size_t enet_list_size (ENetList *); - -#define enet_list_begin(list) ((list) -> sentinel.next) -#define enet_list_end(list) (& (list) -> sentinel) - -#define enet_list_empty(list) (enet_list_begin (list) == enet_list_end (list)) - -#define enet_list_next(iterator) ((iterator) -> next) -#define enet_list_previous(iterator) ((iterator) -> previous) - -#define enet_list_front(list) ((void *) (list) -> sentinel.next) -#define enet_list_back(list) ((void *) (list) -> sentinel.previous) - -#endif /* __ENET_LIST_H__ */ - diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/protocol.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/protocol.h deleted file mode 100644 index f8c73d8a668..00000000000 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/protocol.h +++ /dev/null @@ -1,198 +0,0 @@ -/** - @file protocol.h - @brief ENet protocol -*/ -#ifndef __ENET_PROTOCOL_H__ -#define __ENET_PROTOCOL_H__ - -#include "enet/types.h" - -enum -{ - ENET_PROTOCOL_MINIMUM_MTU = 576, - ENET_PROTOCOL_MAXIMUM_MTU = 4096, - ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS = 32, - ENET_PROTOCOL_MINIMUM_WINDOW_SIZE = 4096, - ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE = 65536, - ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT = 1, - ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT = 255, - ENET_PROTOCOL_MAXIMUM_PEER_ID = 0xFFF, - ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT = 1024 * 1024 -}; - -typedef enum _ENetProtocolCommand -{ - ENET_PROTOCOL_COMMAND_NONE = 0, - ENET_PROTOCOL_COMMAND_ACKNOWLEDGE = 1, - ENET_PROTOCOL_COMMAND_CONNECT = 2, - ENET_PROTOCOL_COMMAND_VERIFY_CONNECT = 3, - ENET_PROTOCOL_COMMAND_DISCONNECT = 4, - ENET_PROTOCOL_COMMAND_PING = 5, - ENET_PROTOCOL_COMMAND_SEND_RELIABLE = 6, - ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE = 7, - ENET_PROTOCOL_COMMAND_SEND_FRAGMENT = 8, - ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED = 9, - ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT = 10, - ENET_PROTOCOL_COMMAND_THROTTLE_CONFIGURE = 11, - ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT = 12, - ENET_PROTOCOL_COMMAND_COUNT = 13, - - ENET_PROTOCOL_COMMAND_MASK = 0x0F -} ENetProtocolCommand; - -typedef enum _ENetProtocolFlag -{ - ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE = (1 << 7), - ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED = (1 << 6), - - ENET_PROTOCOL_HEADER_FLAG_COMPRESSED = (1 << 14), - ENET_PROTOCOL_HEADER_FLAG_SENT_TIME = (1 << 15), - ENET_PROTOCOL_HEADER_FLAG_MASK = ENET_PROTOCOL_HEADER_FLAG_COMPRESSED | ENET_PROTOCOL_HEADER_FLAG_SENT_TIME, - - ENET_PROTOCOL_HEADER_SESSION_MASK = (3 << 12), - ENET_PROTOCOL_HEADER_SESSION_SHIFT = 12 -} ENetProtocolFlag; - -#ifdef _MSC_VER -#pragma pack(push, 1) -#define ENET_PACKED -#elif defined(__GNUC__) || defined(__clang__) -#define ENET_PACKED __attribute__ ((packed)) -#else -#define ENET_PACKED -#endif - -typedef struct _ENetProtocolHeader -{ - enet_uint16 peerID; - enet_uint16 sentTime; -} ENET_PACKED ENetProtocolHeader; - -typedef struct _ENetProtocolCommandHeader -{ - enet_uint8 command; - enet_uint8 channelID; - enet_uint16 reliableSequenceNumber; -} ENET_PACKED ENetProtocolCommandHeader; - -typedef struct _ENetProtocolAcknowledge -{ - ENetProtocolCommandHeader header; - enet_uint16 receivedReliableSequenceNumber; - enet_uint16 receivedSentTime; -} ENET_PACKED ENetProtocolAcknowledge; - -typedef struct _ENetProtocolConnect -{ - ENetProtocolCommandHeader header; - enet_uint16 outgoingPeerID; - enet_uint8 incomingSessionID; - enet_uint8 outgoingSessionID; - enet_uint32 mtu; - enet_uint32 windowSize; - enet_uint32 channelCount; - enet_uint32 incomingBandwidth; - enet_uint32 outgoingBandwidth; - enet_uint32 packetThrottleInterval; - enet_uint32 packetThrottleAcceleration; - enet_uint32 packetThrottleDeceleration; - enet_uint32 connectID; - enet_uint32 data; -} ENET_PACKED ENetProtocolConnect; - -typedef struct _ENetProtocolVerifyConnect -{ - ENetProtocolCommandHeader header; - enet_uint16 outgoingPeerID; - enet_uint8 incomingSessionID; - enet_uint8 outgoingSessionID; - enet_uint32 mtu; - enet_uint32 windowSize; - enet_uint32 channelCount; - enet_uint32 incomingBandwidth; - enet_uint32 outgoingBandwidth; - enet_uint32 packetThrottleInterval; - enet_uint32 packetThrottleAcceleration; - enet_uint32 packetThrottleDeceleration; - enet_uint32 connectID; -} ENET_PACKED ENetProtocolVerifyConnect; - -typedef struct _ENetProtocolBandwidthLimit -{ - ENetProtocolCommandHeader header; - enet_uint32 incomingBandwidth; - enet_uint32 outgoingBandwidth; -} ENET_PACKED ENetProtocolBandwidthLimit; - -typedef struct _ENetProtocolThrottleConfigure -{ - ENetProtocolCommandHeader header; - enet_uint32 packetThrottleInterval; - enet_uint32 packetThrottleAcceleration; - enet_uint32 packetThrottleDeceleration; -} ENET_PACKED ENetProtocolThrottleConfigure; - -typedef struct _ENetProtocolDisconnect -{ - ENetProtocolCommandHeader header; - enet_uint32 data; -} ENET_PACKED ENetProtocolDisconnect; - -typedef struct _ENetProtocolPing -{ - ENetProtocolCommandHeader header; -} ENET_PACKED ENetProtocolPing; - -typedef struct _ENetProtocolSendReliable -{ - ENetProtocolCommandHeader header; - enet_uint16 dataLength; -} ENET_PACKED ENetProtocolSendReliable; - -typedef struct _ENetProtocolSendUnreliable -{ - ENetProtocolCommandHeader header; - enet_uint16 unreliableSequenceNumber; - enet_uint16 dataLength; -} ENET_PACKED ENetProtocolSendUnreliable; - -typedef struct _ENetProtocolSendUnsequenced -{ - ENetProtocolCommandHeader header; - enet_uint16 unsequencedGroup; - enet_uint16 dataLength; -} ENET_PACKED ENetProtocolSendUnsequenced; - -typedef struct _ENetProtocolSendFragment -{ - ENetProtocolCommandHeader header; - enet_uint16 startSequenceNumber; - enet_uint16 dataLength; - enet_uint32 fragmentCount; - enet_uint32 fragmentNumber; - enet_uint32 totalLength; - enet_uint32 fragmentOffset; -} ENET_PACKED ENetProtocolSendFragment; - -typedef union _ENetProtocol -{ - ENetProtocolCommandHeader header; - ENetProtocolAcknowledge acknowledge; - ENetProtocolConnect connect; - ENetProtocolVerifyConnect verifyConnect; - ENetProtocolDisconnect disconnect; - ENetProtocolPing ping; - ENetProtocolSendReliable sendReliable; - ENetProtocolSendUnreliable sendUnreliable; - ENetProtocolSendUnsequenced sendUnsequenced; - ENetProtocolSendFragment sendFragment; - ENetProtocolBandwidthLimit bandwidthLimit; - ENetProtocolThrottleConfigure throttleConfigure; -} ENET_PACKED ENetProtocol; - -#ifdef _MSC_VER -#pragma pack(pop) -#endif - -#endif /* __ENET_PROTOCOL_H__ */ - diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/time.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/time.h deleted file mode 100644 index c82a5460351..00000000000 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/time.h +++ /dev/null @@ -1,18 +0,0 @@ -/** - @file time.h - @brief ENet time constants and macros -*/ -#ifndef __ENET_TIME_H__ -#define __ENET_TIME_H__ - -#define ENET_TIME_OVERFLOW 86400000 - -#define ENET_TIME_LESS(a, b) ((a) - (b) >= ENET_TIME_OVERFLOW) -#define ENET_TIME_GREATER(a, b) ((b) - (a) >= ENET_TIME_OVERFLOW) -#define ENET_TIME_LESS_EQUAL(a, b) (! ENET_TIME_GREATER (a, b)) -#define ENET_TIME_GREATER_EQUAL(a, b) (! ENET_TIME_LESS (a, b)) - -#define ENET_TIME_DIFFERENCE(a, b) ((a) - (b) >= ENET_TIME_OVERFLOW ? (b) - (a) : (a) - (b)) - -#endif /* __ENET_TIME_H__ */ - diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/types.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/types.h deleted file mode 100644 index ab010a4b13d..00000000000 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/types.h +++ /dev/null @@ -1,13 +0,0 @@ -/** - @file types.h - @brief type definitions for ENet -*/ -#ifndef __ENET_TYPES_H__ -#define __ENET_TYPES_H__ - -typedef unsigned char enet_uint8; /**< unsigned 8-bit type */ -typedef unsigned short enet_uint16; /**< unsigned 16-bit type */ -typedef unsigned int enet_uint32; /**< unsigned 32-bit type */ - -#endif /* __ENET_TYPES_H__ */ - diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/unix.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/unix.h deleted file mode 100644 index b55be33103d..00000000000 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/unix.h +++ /dev/null @@ -1,48 +0,0 @@ -/** - @file unix.h - @brief ENet Unix header -*/ -#ifndef __ENET_UNIX_H__ -#define __ENET_UNIX_H__ - -#include -#include -#include -#include -#include -#include -#include - -#ifdef MSG_MAXIOVLEN -#define ENET_BUFFER_MAXIMUM MSG_MAXIOVLEN -#endif - -typedef int ENetSocket; - -#define ENET_SOCKET_NULL -1 - -#define ENET_HOST_TO_NET_16(value) (htons (value)) /**< macro that converts host to net byte-order of a 16-bit value */ -#define ENET_HOST_TO_NET_32(value) (htonl (value)) /**< macro that converts host to net byte-order of a 32-bit value */ - -#define ENET_NET_TO_HOST_16(value) (ntohs (value)) /**< macro that converts net to host byte-order of a 16-bit value */ -#define ENET_NET_TO_HOST_32(value) (ntohl (value)) /**< macro that converts net to host byte-order of a 32-bit value */ - -typedef struct -{ - void * data; - size_t dataLength; -} ENetBuffer; - -#define ENET_CALLBACK - -#define ENET_API extern - -typedef fd_set ENetSocketSet; - -#define ENET_SOCKETSET_EMPTY(sockset) FD_ZERO (& (sockset)) -#define ENET_SOCKETSET_ADD(sockset, socket) FD_SET (socket, & (sockset)) -#define ENET_SOCKETSET_REMOVE(sockset, socket) FD_CLR (socket, & (sockset)) -#define ENET_SOCKETSET_CHECK(sockset, socket) FD_ISSET (socket, & (sockset)) - -#endif /* __ENET_UNIX_H__ */ - diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/utility.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/utility.h deleted file mode 100644 index b04bb7a5b35..00000000000 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/utility.h +++ /dev/null @@ -1,13 +0,0 @@ -/** - @file utility.h - @brief ENet utility header -*/ -#ifndef __ENET_UTILITY_H__ -#define __ENET_UTILITY_H__ - -#define ENET_MAX(x, y) ((x) > (y) ? (x) : (y)) -#define ENET_MIN(x, y) ((x) < (y) ? (x) : (y)) -#define ENET_DIFFERENCE(x, y) ((x) < (y) ? (y) - (x) : (x) - (y)) - -#endif /* __ENET_UTILITY_H__ */ - diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/win32.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/win32.h deleted file mode 100644 index 6fcf4c811c2..00000000000 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/Vendor/ENet/win32.h +++ /dev/null @@ -1,62 +0,0 @@ -/** - @file win32.h - @brief ENet Win32 header -*/ -#ifndef __ENET_WIN32_H__ -#define __ENET_WIN32_H__ - -#ifdef _MSC_VER -#ifdef ENET_BUILDING_LIB -#pragma warning (disable: 4267) // size_t to int conversion -#pragma warning (disable: 4244) // 64bit to 32bit int -#pragma warning (disable: 4018) // signed/unsigned mismatch -#pragma warning (disable: 4146) // unary minus operator applied to unsigned type -#define _CRT_SECURE_NO_DEPRECATE -#define _CRT_SECURE_NO_WARNINGS -#endif -#endif - -#include - -#if !(defined(_WINSOCKAPI_) || defined(_WINSOCK_H)) -#include -#endif - -typedef SOCKET ENetSocket; - -#define ENET_SOCKET_NULL INVALID_SOCKET - -#define ENET_HOST_TO_NET_16(value) (htons (value)) -#define ENET_HOST_TO_NET_32(value) (htonl (value)) - -#define ENET_NET_TO_HOST_16(value) (ntohs (value)) -#define ENET_NET_TO_HOST_32(value) (ntohl (value)) - -typedef struct -{ - size_t dataLength; - void * data; -} ENetBuffer; - -#define ENET_CALLBACK __cdecl - -#ifdef ENET_DLL -#ifdef ENET_BUILDING_LIB -#define ENET_API __declspec( dllexport ) -#else -#define ENET_API __declspec( dllimport ) -#endif /* ENET_BUILDING_LIB */ -#else /* !ENET_DLL */ -#define ENET_API extern -#endif /* ENET_DLL */ - -typedef fd_set ENetSocketSet; - -#define ENET_SOCKETSET_EMPTY(sockset) FD_ZERO (& (sockset)) -#define ENET_SOCKETSET_ADD(sockset, socket) FD_SET (socket, & (sockset)) -#define ENET_SOCKETSET_REMOVE(sockset, socket) FD_CLR (socket, & (sockset)) -#define ENET_SOCKETSET_CHECK(sockset, socket) FD_ISSET (socket, & (sockset)) - -#endif /* __ENET_WIN32_H__ */ - - diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Vendor/ENet/enet.lib b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Vendor/ENet/enet.lib deleted file mode 100644 index 3d133bb184e88b99428421b77898b5bb3fde8b63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58202 zcmeFadw5jU)jvKbGlYQ*Iirp?)>NZTX;Pvlm}*9Xon&$oqJ)N96}e|55+EiMK&6`G z%!KS7M^h_G`>M6AZE5SPv|@sI%Ot=6UI?IE#0#R<3_-;k1W@z)ti8`UGm`|h@7wo} z-}8LW^UR!e*4fv!*Is+=wb$O~!o0b)^A}z{^0Kt}D=X$vz_I^nX=Ag-=7=D3Mw(2f zi6)aZGyXR!*JK*_`*E?!^liWVlP1%^UvZwv6#JWeoyqi{{x$kdrr2Lss>$?!^P63{ zq|!ILYDtx^YHoGaZI!c^SNRssoc&WD^tN}}j732C{CK!c*i7mWu(+yzS)$39wdQGdVsHWAGb8EFMIHGDvU1hDF zHpkRfE?&AUF4Dl~I<&HuMh%)6tsh5H4Hr)x4T<)OMxI|g|MIbVLX~Li0VS>>Ty5q2 z$||Eg8mbO?-cny#U#T}f$1h!imW-vgj$KeyH^d8+rBbgs4_Q;S<#<8i@RW4Z&`y3Op8vR4(5>6RW_<{8s5W8ls zZ~j8i3$-UA)Gw*6tgf0nubO)DV0s|C0TI42xF~^iW8cT42$c7go++G`n&c+CXn5jz?!$RyALzYgDLdaTin$LdA6Z*>#nbI!TE|X5VD+ z7k`Yh+uQ{UNOB>9Z#GY$sz%FU;F|$xqim_u zWYz-5^=l1BWnNn2i%RjBgonb%_dG4|-1+k>YjpP46JA*FTd;KbfFi_;5j|*>cQs7B zm=H)RXU9il1wJA7S4=kw<)XR(s>x# zzU68_xXW_l13B=x2I<9KNY8y)_D^C_CMxS=2D}^$@MB(3#qQHYJN-sWES~B>I0y*|!=OrJ-LX9?w;2 z=_S`q%}C20d&%;uB^Y0b9&|~!{mdk-v`XjMES473+YTI*A`_)H0YAD3mxbRH{1Wk_ zi^3G*w*bGf_?bM(SyM(PTdql(oJ?^k4!wI6KRwP50Fxq_Xu8;dE;FEN18O#)KNwIB zpfm9km26U;n3OazezV0`fZjz#$KVQSs z?OY9(kTRNo$jhZ+iDigS1!c-{=dn2E=lGu!hwY-x$2fYE84SC(7VX3XmlA!9qfGafw^=h-^!#{hMq)VS zsUU~VIR}Peg#ozx1S@=C63pZ&@b~;(8(<=53ne@Msg?FXIrY2vHVBT)ys67eHh2nl3?e6k7ww=62gG+8&5ZJs`@BKv@HBu{ReDzWbFo{Ubpy~xrs zRg&9wdD+wiZ=j<-1*k-6H$d6H8oU}hXpuJezU*!FvYF1&c*@?zs-5XU-!KzQfe zSg1Rau~lyCYbloG_F{{d-N6&O01t`MS3IE~YYA=XeOZ%?-c}^#MM}=>{-7<9`J8F& zYxbhtR$-LPV`-@nC9B{Go-en15~V#n!FKdCDp{A2ph(Z>JU@F^Fwcy#rm)@Ygxr=d zvCi(p8QoOCd=xMrx!$U$m!Bw|;^`G>>4oCbV`WZ9c0W}-HJB-}kJwjo+boIg1LeA? zsx7lnz5`x1OHU6Kev_xS8;wNei&hx)t9G7A^gDpc+JbqB>@B%%5=u1P!nPvCPzxT~ zwt3kkq_(R*O>QqqlwRSf&DT;38>Pe8#8x`3p+lBz#PB)o(n-0k0EyjZVO`z58T;h6 zo|Xd1%Wg(u{q^U`?FETageP_k{6aSG28Y(!eDMfdVn$a`SJ4L4qEJ6rnQy$xQ!>rji>Y51nVe0g0*~coBBW0k# zLR7Vy_~L4mctK#7d|yYPU%s!)x=#+=1y)BH=L8PO{sw{zoi*k{r|)^vgR=Xf(xj;Q zmM}Cb8l+H?8ldcE3IC2@z7@B$U;zb7Ma($}myTejF#VarZax-;-5BB}JlQ0qK}2(v zIqkJS29z4uCi`bXW+8H#QteEY)EO3~!-_s;H_zz9%|SWftzB+2wGtVN) zK>husCG6(9ao9bAO25S|o5mn48q7*;6zYSlNI;Tvq6 zqMBxe7`l!iw3WDk*Rjm$WSuCB^%;tbBvEfWWtF{4ZZEa5qsKpy+sm9bwngM$K2q8) zKe0!C;xlP42$j<7AbHfMY?ea*vW9w6LLXRLiu!|=E3^B?qXMR7XEmbpp#H0#MF#Ba z<8R6B$6T+~zC;2jyF=MjCU}|a;Mu!S6)&XHXbDmb9kx6n7X_3R6=hR8(G|+k^~x=r zsz{Xh%Nm?4h2F6s5;fc$G`kHYY(ung0dMP;kt8Q4jAA=8Lg515=%EvqaDiy|0vQA= zwNS;!AUYU>>OaM4qv{3=?a9>s)b^2nbYOi1lTgH~c*)$n>B=G4cGcJXr;3vpsRr$0 zsPYL zggG7xpv$K3AbY4&BEAA~B1P&>L2^P~Nxe^Ky@iG3HVm4R6LLw=D6>!=(h!tRRBAGY zEJ&3e;E<^`+QnEW?~&Alz+KeES`sLqf>EqHW2??xeev9tZ(-eIP$YK0>(P?Z~abiD#u1iBGr*e~!J3~`B`+z~`At~%-*N@u5 z`*40@WHq=p(C^DYo29T$*XCL|@>>wV+v++j`&$8tdg(%{zK8XoU1}oB@d{iag(4B~ zx!fMoSfppu=ZF)ETt!%DlUy=+Ga%uEpnMlSVu(Up1wCf2V9svDS?1c=X>;&pvd_Y%n~%q!5%SRb?_YsS{&$Gondx*O#7&Coznv_$4 zof;Ke+1BIx`In4IQ+G-mwN9Qug*nkQ`&rTjOcyS=P-&0+E>{ zRWa_c!|W}P%?@E>&A>=p?aV<|T&iKvDYlaGPnxP5l}9+YbTs=I0_Cxq6;kp&mj3CF$`3YEA=OwHV+?-&Q&h0|OvL8p)OK*DO0J-f9r311e^iGOvpR8O(8kL_1K&O-2 z+|UQ?BSNm%WhH`U(q2JWQ{WGW85q?hZ-rJNVk#XLCZNbJaJxom6o6M%>Wm~4=>+$B zTZukIFrp#rLJ|#BSD`TM15q8vKk*_=rrxd@`!udN4ibS$phR`cO@Be9jo2BxF|Od< zi-?!Q={E!VBzm&5WhVL?SLrl9Y)Q$+#B0VVEzQ&GI-*?Ygp?)HO(8`98Y49#q@azO zpSJ_}E=O)hv!X*wM~Ms3+SYjPlh8>}97pf@sDIb-19JO;f&BNb6)YHZq-1yM{MQJ{ z0tVcHq)<1cj;t|@KtpRR3Dzds@u9TX=B1(3;@{@W)@VvJ;l#+qP=nC{>f?4W14Rk? z?P3g_j4?Ey)D^pHbFk1k3t3oYKh0{8g}&gkZ2*R`NqVzBTk(uz^BB51NjBZslKm5$+Fh9}g6 z^xt|E{+Jwi4tXL_RR1j+DIKM7a{JNevXv8oA*YDO_>#fL3q=D)E4XP$; ze;GOibaEuSt^V4QHTYz2hsL7)hiGa=jb+v$T~DpPC*C zPC@4&j&TxgXaTld$|G!nV8m6+`nu-@%Jo^OHp0u11Y4y z0WEQrZq~9%8hh)bkueZa^9((c6x!UZ?2gEy6PhZgC%pV07=$qICfQFXO{8fJnsA4WErGolI%FNOo9&PE9EWThMv}HiO7R-4 ziGk{BKmUz-opngU?f?tzfA< z`H3!F>dQ~O$X?Wj;oj63`Tq&&A7>~b(mzfw|B*cx`zcHt$zcqHNZl!o#Ta6pXP@+6$uMvw(}w0vldgF|{y;IjUtYks9}u$YD@OZY!18 z7A`K8Ey?Ufr7NLjhLq8bVHX5pAV%mEXqW=#_01wVPK6=?-gx2^rtDXd?tU5mKn-wR zr>2`CUx)les27tSp@%{7I%Eyn60&zTB2y?{9}30mZRC=c(HA*>3~h2MvYVHPDu=-y zT@7=;1dM!SC`5C(y<@<0M3M<7(W5%#2STVv4%-*4AX>CcucTFeVxLjhaWpNykLy5@ zGruq-r>-w5M^eKUC)uTXNWdtYQVru7mw#C6)80c8(Lx#N$y_DIo!jMcrv)|glCI0E)HpenO%IrvGH6OpR0Ldk_+{aTDw*~`Gce^R zTLTH6WZNopezLvUlAr8Qh7~23n>U%09iFaad;XSWTYfm%n!lN^1ycMP{46N=AE1Sa z`1wYB55@N+&oQr07@6$IACN#vk{eOh;kO{W`2@s3~&_BVOnHBcLJipJkfNI0sYp1eg}xw zAssvn<>N4=;h$oN6-fd44BUDH>N6la$|J%Ju}Z10NzF%a+p^mIeHCVw~W5iq3^nG`MeeZ#Xjx2 zGCZM!(6@TqE9%%)VuD1qZdgCx;0Q`o9!x;a#0p59aw=SLsFC4hGV7XFX z)QkBRajEiBM>tS4qrjhihuY{)I`nJ{3E3&e3 zDO_6GoRMXcXn|TR;b(BF4)zS`z+$!qZB=F$WWN-*p*%yx+xY)~XEjt%A{rl)R?{Z# z!V$&MD(6rhBrgpWZh!|#HQ4DO!ljVA0xs&wlpa=1V1r2|r3PbuF6L^arLMXFeG#L< zqv^V|4`Vj(A3<1*nS`$DT>-M-iFlqQeGk89YWET+Md(JZG>aS<3`rAvizvgDzJA&N zTOcr|X2IO!-$8I0_d9_CXyL(-T+2U~&I=>UbOIPxc(;1vHnWvf1gO4m(?($x3K(C@)+>g4)DN zNcVGxOVY%Xo%HxsHR+hYmgIy>QfeR)!mCO(aS&cL#Y*7_H^YFc5nAAU2DHL}RvJ)) z0r?H6MTbgg%tNxRI%3s*2DAncwS9Qig94WjUiF9p&9w5|@pg^@Eij;k26R`t2&cD6 zRY^Lri8|dWb-OxcwQxo-UtBOUt%yLTmTwQfl#w>dPskC6zsQJ2T52?+>2vGaZRPUxnhsI1K z>ffnOb25k4^^uvf$#KK12oak@hN6j8ve$bzYMF6s0O(~=OH+ft`r_6xi&=Rp>kjqE z+1<)^Nq(Y}b+S8?qW(t%pchunc-q?Sr!~noT3(`vT3QoR$jSoHZ}DdE7#3BLx)j0 zb*h~e+ts2J)=UcK2GVnA3AGgkf>=yv&7_|NElFs(Lt#gP&C9cG?nq%B+oRr05U;~<}v!k}2}@P<0Lh4BiNSBFCG z#(TufRe4QfFnKBwI~u~R7TIDR3)Lcf%wwtA>_=uXm@>6^Wz1u_+T0KyV2WC7N6s_Q zWVFH%6LuzMGFdE#W-NTH$DJJ6c+aIfVu_Im)@*iE$O^1J9=5aYjnqP+gEkm+OFB1_ zmm#2n&d8*w{R)w?kKMqI#!3sahU?>RHN*zSU%7212I8sd>W$8-PU2LypLftJj9J|6 z&SCZE(4-ZHO$&M}=SKBfCnAX$LefcF(OA3GMD$g7Y^ECKcf`!no_YTi@?j=C4ty|k zDO&fI)nD`Oy~{U@D6|X0<5CC@{#q@zFoi||6>9_v4FYPhjVTmf>+DpoFb_*~i<1DY zDu(Z1c|UB!c(qZCpPd-@Evx`;rOQlsZ^5ILmD9}L7IvpmFJ$F*%|lhtYo)05dcK^) zHjgPLr-_{LAAtGg=D!lzj;n1n%BjDj5soc&px${f!}`g4S6%K@OYG{M=~a7)#6jCR z)RAwaH&b8(f1-2a?rZ1Sj~@yhPJ{&_%k_$^EJMmL<`#BUu!_>lt47caf2k z?N%ojhI$iGrBKf>NQ=qr?F}fEDfGcG*WOwzHnF|P`8dn38JAyOXJ+}d7}tsb@Jz{- zGG;on@T0P(Q!G}M7tVJBFop9A>87qo7X@w%>BZhDH~k#rDe*Fqo6$5CV}uA38vkKQ4AH%=MkG zmgm6P+DhWvwY^RTeVtOqo)?gfi7m_({Ijoi8{RuHs9NB&_7K$>&mKo&xxy3v-W z6{k!WU(=lGb?K26RAxR*8L9McAYMSeCgO$1VYQBoN4X4#^49+Trv6_-jQl&+q$x;_ z3|Um4-85cu%Td(w!!1X!xR}jm7;Xldj!KhYh~ZdIj6rE4>=U_2%7@>3NRt#@O1C)y z=_+Q}brO6dVSnm^{b@_Gr6|c0^MuGmqjmWCQ7B4>t~K~=q91VUX|x@>oUkzcQc7NV z4NOfpCa)xm(L6#0t-NK|rgKKLtm7_Rwz`cGLRT==FrfgXW zbB3^e6(yAnwy2r7Z#*sXTdXG2LM_c0`b~_XSaH&|QDPG9jv$^7@woPZXTp&Qz}*3ec+X+_6aJ~R4(>a6K8N1Hzkp5|(AlV&z>NlU0S{LUD2+p;wqD4gWq@dM zpJ@84fg?9z@piKT?KYry4CrG(7x7ds2Jcc`9HvVFIXQHN0j&fi%5uK}tuvtQ2J{)A zi+SAdgU%x5>i~)LaxmW&w0a+qDBn>78V6le)ZKX&&LC~3p;Q_A3e*@|h#ZWKp zh{I4;4X9}}4As#D3Pv$^0Z|i&`4cd$ahMKZ))|<=_SQC3oor>%>px|0jkT1aYOtk( zIfl62(r{p`)7oUY3XJ!Zf>|OP*$6FG^!8a4)NHb`ErKYx&M?}D?6$KPS1zXggW}RH zx#!1)p?6~kVG0LfW)8yC55ka6D=s~+hX!FDAA})y;rLju48jmM$A{5K0HmJR*@6Tb zhU|LZVAU1Y+yCG0&K|mEv#S=@%$~i_w{%IJJI6FT`dCeme9L{D30vFR7-uNgaD(@N ze$y7t_`RW=wqjFz?C#b?UQg)jU2$W1B6N=-;u~ zXJ?IdrSXew_SBzErmMkcBT$iTBz**G^7G{TkHj`KZ-mRxm&!o?l;kIpOXRryDuGVQ4 z2yMp^r&j9@p+(o<+fNbF`(CD&HpC_ZO_dpREJkF)MB z(y;#>otpG5U8knqlyng;3qJ>b=io;d;R^A~!4KAEex>1`bTh)I;}@ehFVOU59&aUH zkyNF8=t{#6LlDxZ)C5#&Kr;=f!GH#jznmBUN8|60kt)?DI&S9SiRv50;II(1Y%FFL zo@xwCEawvAm?h$k`0GD4Zc?gCw4}wjN$oQDx>ZAoag)j;dYm3o0Wh>2i6CNwTC3gz`c@3kw_oHO{30%i`t#S@kKv7 z3=B>(E?_l|2+>0!$P(6!JG^_gQ0V#`m?GSKLlaCXP&e_a&cbfDg+0Xlrm%-|k+R9o zH)QFO3ob8kGVOgux0YyYCcwd5bw0!sY%jibbcbz)mJncQ6V}z<-|AKYNVx0+y?-Lf zghdSU#Y+nu@a3o;@;e!ZQ2pJ@ET{XC#%#hPYO&xfn{>vd^VyjI9z78+a7ABw=Ss^fB7fYW`e zYj^$o$`-5gu~hf+@owa5*h#b^>vjebJ~f!)fqHPvTztX;I=wiENdI2nj{Vh% z^XHG3Pku4V8r+(;WZW!!RoQ#_b6%^x<)%OEe9xR$@~UOxSAkKx7CklcPbb$t^^@u~ z|18+E?U@7a6VE>OZuP#g^)uft+A(U+8$Tbv^Y4#k?H_%W>8;5PllN{tXKwckPkp}r z!izUOIqf&AH|~)C^xTGrHzcH6TZYa4=YHE3XC&z#t5z<(?2NL7GyeXAxuNlwc-va@ z-&mG-%K_=xv|Vd{Y=7-z7Wl_47d+LL_^05s&Owa!D zP}VcA=KtyK342%X8^3td&PPvuzGwH%kt)xZ`sAT$6vguvf-91s?Yr(anX`x=e_#%!7;np^|!rle`&&-H!Xep z=@TbDe*SLH^X=bz?e2ARdj7a)&mF(s`TI?s`*MHw>D!Y&x#^jmH%))`&u=C^_0x%0 z{Im4+F{AdCoj3A{V-w6*%(=~y-}UjFx2L{+)7p>kUXp$5^Hs%nJvsNq?)m8$E35^W!#dPEUI7pZ8wBevIYjCujbpe{W~@j_#+_ zhxcdx;PJO^Sl7AWzeayL|Hy-Xyt#hluNNo$HF3m)KfNNkH*nMN+OwvchBZxiVYTD7 z4Tl~d^N-te&ug>&*WJ7B9rgTc@3(*a#>xxdK9Kb4uHDTAhhF`p@5wh8E>T{;;N^3k zZ@gjj)5qRfzHaIAlJ=B;U2kovxmoVtJ}%KaGf6t5|KZAesve)$x4VDQ+@c-T=d3t1 z@{x~rjoLd!v0T^rq}erh^i6*`e$JfkYfGxGIk0@m)e~;s`S=^x@7eE7dfR<;+`cO- z9)I?)n;(AWtvh%8vn100R95Pz&pr3c&W+o%e*I*{pZ~btIqZt=jlWLZ`*Ozgx2C*s z)BcMuz0LdNh6!^wC7ieL#{0%B{qdF0C$0be-L|gt-X3;+@Z*F_{vFMYbSKiNNZLP! zA8B8!$ZFY4esqe3OqbV`0HN(QAP9}AT-h3&#|_8=v5YJVY0S;VZv%cl#9N5_P-UBuoQh06@v#wiKgv<931+W5jPEx6+Z{p zWCi3maL*dhU=38$Wd`V=-^BuS2~u5$UsT^Lz!M&0Fw=mjiNnmJFu16_&8EQ`=stud z+DEVdlm;5B?eN?sUWjN$8dR;_g$7EL9(*m=P^1KzCDL;!#rUf+=20kBtp-N7)6B2Y z^!nfa#*AQa_F53woCYQ#JM8FE6GilZm+Kf1?r4p+p8;4W>CA zu{=Vro%rRzC+)?#DsL1P&ZgORLp24G6}zXT0uu@o7MC1lx(w zT&ugGOD(NY%U7Dqt6kgW5{?J)OokHLs%eDqmEl@=^Fh1~-cSFoZr=n$q3ps|&O~fy z8`Uygy}eZR6e^cVP#>cjr@n$#V#{bIDT&>kn)WKp1bBjL2)Q;H`Y+c=#X5Q>)PMJ} z<{o4#$$_otBx-X99)b_kziXfGB2A5LHAr;C3NQKpv zc=6U1_Kh}D=u+L~sKc8|x>UbU$6~6$%cVB&2>q;=zr++ z_?y1(oZiC+DZR((-}R<%icTMOoKZqgDQY(YwUhl@5DJZk=L__T>cLze`@Y9c^@jBs zlGc!}kLx4qv~eb>*L|g*LBl4ZAyD?ah6TB@G^Nv)q{aohyO7=HVSp?l~G%?Nh zQZ0%W;5jNl5f$J%qX1ek*C}5yd$V_oMqkZ)D_qPwMz}Z=G2y$B$yevG+fQrv$R${$ zrJ0eDO8rI^MN-pUo9oB)&N1l0GmY}8UN!`Eb@^QEO?6;_UnYLos^`=DyjCtf-z!AE zi;6SzK%d~GJSA+$X7*GfPFPm*@S5$2ypP3UK7-O{ZeHcj#NA&{XiU@1tB1}gFt6JbxCs;Lq4epn+ zfm|%J`@;oxA}{vc*un)Q;!NR!EOFy&|)+^W-X4;R#kn@=EDh}%kt08_ZB0sjK>18O;oa4mp7hlB|?-32HOzi?A4 z{>9t-0A-;|F>s{$&=qbX*(e}NTR^(4 z_Orj!gno9UC%Q+yRV^ULn&HY{MdSXJH!k-*5PTVx-^v}%VLp4j)b+N)rZNS&Q1xI- zK~mW9JS-ItXnao!VZH43RQTzog+1l;hzJ4Pcm!|NR0usIX#jnkGYa4w2E9%$rLaXy{R686~W zQM25jr}ghur@=MPa}oSC)Pg((8>$QPn8$%^(^#i+NNzYeT=q92PQ%F&_1}}*c{V&E zVmT=oV){F7%~w4n(ywaV(v83{z*zMD7kQU@B%PwsOsxyo!*cAayPsg}rHV|YUQOj~ z+_G;#%y^2@fepO@O}g;(=*$zoUpImNai&pcWH_a^el!v?skdV@WN8A7SHrO{4ZC`= zyUm1i2;S2K1Z0+VBe4_=UWLK|jdQr(;io;HhEytGBBu@V3suy`|9vB1X&3wyY$3|I_O5hzH`HTe z+k*@3-q2y2*EKOgF4!!$?+Q*x#Qri@NWP2O7u=VJn_$ADS*jWYXnm@g+ndtgPyhRs$7c4)aoJ^3LpFmDz)cy+<6iSvEJ0l^S zgyj?mI>E#3h(#+QAE^`0?N>f@g<&%_GK_zlrhXZ+v_Sdz>`CiI;rZc*Fhp$zV-JdykW< zREi;5=^3sZ{7McyM%?YOX7_L8n>d7Vo&;kBEcH$#3bCIhZaLzXD{j>7@y;!7h2mB! zZe`-e8zStX@eCnnh}%qYn%{X*FwV5GaAL(ngjBL>dUFBBhQ(-l@IZMwor zjDEq}-*Ln2i2qYD;1R#908G7v=Ah&;&%y(G%BAQjX?T~tjgfn53WcM-g5k{Q9~$qy zK^v5#D-mi1n8YePl;u_bBF3x`>TR618hJ_UPVmD;>GZ)5{3-f>9(w&0aNG=b6dT4< z(ErPo70zLD;8wI9J1V!)?&I=tGcsj`_GaNE6>N2yY;`r!9wND^0=Pv7^3;22ACvj# z=_(_F2dfT}*iPjgvvMLq_Wv30ltWT|l2^GspGH7xh^j`h-IdTS_m)sRB5w$cJ-n*bL}iHKW;z za%DK3>__YD<;bDuK5WlcHsxW?A2a-b9>K!&ez;11fv^rJRd(!=BS##y#G)J=q4Zhn z(N;L*kv0uEp+`||EY9CkgCRz0e>a+!%KQB-h0b$ry+6cwWB#^aEpVSrr!rA0>U0|s zZ7uSIj&}@>#`Xw_G+q9&8gToYHVUXsl z&yg%O3Q5#=L|)eUohx=oPm(DcsCebBKG~l@+997$BfiKxmButn&q|1GUQTl0-dcY! zvX@fAJQ(?0GdQ10yv^R$`;b<;} z>7tDVtztM^b`;~$5t1EwI6LM4Xc+qy4l0Nn#wHFijHP}eeGK)XUHBc;@*qD;hn^szm~lSX?}FaTp? z3K$u?Qoz1&Rrh46*dXG`QHxWUCs!>_Wu82>7z@;JWOp)8A+|R$PbthTY*9J$l%bi+ zn1`RGLKC!99M@%0ieMn4S&_yTn$lTf%+Sa-sw>n*AYqc7itBp?Qv`m;MA1zhQ2-N1gs{b35NHnohck zrjMvw|DL#coy=}Ti*UVE_WulEW0>0cG^OSJMs8n4qpUX1dw^h?nO#FIi|Rn`VIH@d z^iQ6rHf4=uL|Z;y|0Io*8r_|vY{{W6>^ds@X%7fGu$y%?h7MAJa7v8EUuXjxpSX^Y z{cZ}j(g}TYA`b4DJ{qT%%KpoNWG}P5-q43OZ|Gyph<1vD50%Y!C7kGWeJm>rC}I*) zbzmce|5kdcu#gx4YozeS|ENTAA8Q~^gt6~8xM7ym<@!tx)DzuSI!mdvPKrNGkME}V z!+5EON`&+Xuuj>)bWuC6uqd0?_K^(7Cx)aoO&?VNA;U@ zJ(-u@O{lu3L^|r$5xEnvCcmk*czqqGsjoN)Z#}mbz`ptI;VUU|J-jZTW^(>? zlTcD8W(IH1be)j>q$(0mvaOiHrSf{HrEWSK>2ACMX>qP`sWn(Dd0n5>-vQIiXtgvC z+kWP$CTTl*_h^iAub_r0V14XMR^4#Z13Ddrv<90GiqvUL=G>s!?|o(1;_33_z_>~h z5(Pu%ZhhYL7`Dz(K|r%U-=m^5{#wvd&&Cz1e?Vsz8rOWFU$HfYc3$Of&L<+5`rqO> zFF_1mzgT);$_7~}k+G^MUHOGG5#@IHZlpt{%*sJIZ-nm_x&3w+Cw?}XonU)H5p=|l zY;^h*PD89ns2ih%?EHy+qJ$E?;=6K=WqpUa?~QL+VDs^#@zsQI9YiUF%Y1fD2#gY`0DTxX6YMsGZeGG| zCMDcvf&&6zGb!OVlM-$-Dd9Gg5^ggo;Wm>JZZnxw%1UyP@|18%A@aa!0(q>&P4mA^ zD7w%^n@JaCa7;oc(97jeu~H=aKg1i*CmULAEI}ZL51A;;)30&2lMSmRG!uW!=DVNK~f5k=7=m3 z`zZ3G*#3z@hsysx^e`0Bq27|Egfd;9_%3jT>&EojTWQU}s%7ztZv-89iSrn1duYlG zev7KzzIe_^Evd#R0)@h9~?y?cb536@_#;X{Xqg$^Y^Pl9w8nhEtQa0WC4KOH0t z{!GPIHVbb-?(SyQ8!APSBsJeAaaN>Vy-;OzHKWbU{o^v7=r`yAYrmq#K=W99$vyk~ zpjy$P#8l3nML6dyG8|9LhAoIZ?Z}8IMMTr`^G0jZ<)(DJiEJgAAo_4C2^buh44O5) zO}p$!UQqM!-X}M`NVqll(>=_(vBl{qHu2E?;JMgkqWuUL=vVs-|IIylP7o#)KO)dV zA`U0(6njyOZwaF1;YU`6&++>L zvr9@RKwdAgr$>2`-R6siI{nF$WJi>jD85&Kyw52}zR&V9Ju_AelKx(*Xjk1$>%vx^GU- zL(5*n|4aCP!ZkzeQoIIiGw9r}QY5U&(xlk~e3Xa> z?ckqBZP4S9ra=R~U)0=zO^9N{C|HHQU_^dS33oRX=ho8&T2 za(M!1IA}NP#H0i_f4e}0spPlG{J%W8?3(2AYdCzJcwL7iiU%BpR+=w$+G40Y%!Na54V{8C-az~uSe-22RK`b9f`Nz0U6VA_ zE?MpYHxud7e#t^~VxsA3+Sr{C9HM5$N#Yg5P?`C1K}&4AFT zS~w6*gHR0(O4A?*Kar7AaqyjW0_ry2zGpyv29$z+F2a!wQ$T(LA|tlIk;Wq+I_pG0 z3k-<-(FD$cJ}96m26V)L@*sD`+eZxOpaJC$)87u^drMxE^HJ=$i_)Tt`_-a^LtWHw zYw(Nu-n#L0G!8R^Feqg-%o1SIP_igy6)@#-m^Hv;L6$_rkS=o+k|c@=0b@s#Mlr7f zGbax71u)Sa+ITRXgStEdnG=nb3yd8yC5o8_Oh+7sw8%%mG|@246bq6gin$+{Iq0iV z%s+u?MNf@lUL_3rWE4XuNM(WHqL>_D+#r7xGYuHJKt}vxy;6}JJb%;Ct>#exR zM;g!OF{*D6wLV zGO%zD62ekZx$=Pw>m!VjzVBfv6W=nMn3{3-2E5}g>b!&^iip77`6&W6B8*VW?dEbD zn^qogJ;wQAJJ^2p;eALY_&EK$dVN=u?X!0zUu8HC&p&RsJ44<%yHkMkTOPF9zC6b8x0f;0aV}AD9?hlUd^IoZ-g_#)M~`Sa!?8Dyf>Y+KmEW zX->Hp91g4_ILV4zE{abpS<3B%M!A7YQ}11e!0MfDd_?es>l3+Y7QLKUPRG%aStjEo zR&`RP<6hTExv2o}D8EBgj*1j~81P)4PJs`f$59SmzzTe>f@-PRY^{H3&o z<@Zr0w1Pm+Mxeg3IZJWRq>s!{BLr>--vFJC?_ObfXf{!V*j7+IRiiqiGxul}8f3(0 zJu5E6cX{}Vm(N)+ZaSQ&-T;;0wae&hl6LcL=`i}|_^!u8A+2c*M{IB7JN>dZY2x|y z9(+48I88?L;WH7_6Z>U()g>tV#(w1ev>h#!@d?|zyPtis`_PD!$Pj0+PO=2HBN>F! z`w_Joj`W0E6o~_MTDAPd{p%6e%mstmMlFps4cDh~!5-{jY9bRY*tryFQ{-7%b7&(F zEzIa!BO)8m`H)NJ;e$W*=aAGY;Qr3L4|zvS$H$5aCnZWs2Sk?Ve7=Lf{v1xx*2j>H z+OQV?K}#$1(D5bq;IFpg*~R~H8aQCE2)a*R4?yC_JoE(%)0`_gZrGqWBuq*&+QjME?DI*BHlr7v#*~-0?v1XUW{hV{SpK~tv zbI#*_&Tj7KT*&>LOSzwO8T_0{BCW6*d|Zg*B9T*MtDdhe2_g!JMXQzIV>61JUmm$A zDsix?MIK+^pNIoXEh3!VtK37?PC#R&v7X?xVthLCev&8n#7$Ml-~aZvzp-W#OjI}e zP>xx-6DzJxxJ3Q{FR91tE}Lnad;x$X!HTMYaQkptk0zM8*;tEgLyXJco3s=h2cb?|p$L z5UuxZy|fVDt@O?B;4gCMklReSkkE?YeN^Q=Op_~K<%2Iwx--!A#TEL<-dL1T1r-||KdV)M2 zNCG}6qA~@RpkCP>z3W?pzdnf2F8&XGPxZYOgfA0#Unc?1g~m9(HJpgfP8}f?DUaj9 zeSh^TD%C~ea#JI0@2_=o@ct^sGxi0(x(MgOa%52kN1Q5MD;(=i-@EC)F2Qy6dp-lG_?PCQv&r8G^7j6xJrTUwPDD0ZUke+KP^Q*2w_ z$6b1gJ`8t@MX)k)-un5Vpxpcc{L(LsF6y^uZ@g`a5gMr21Vk6_M5el9V1;1#)v zh9fw4=1J&^c{r5UEeH2Q(BZ3Oe&oXQXQ%Kk3qB;7>{AGI^4UxGDwEO@GF}V@xa>M* z$>^*)z@-<-EXG@$J;ViB38_grs3mzheP108j}?bnpT#~MGra`|N0WZjypcv)6fD@x zwa*f}?3a-{TF41s)NqIWV;eEPL`hF|2e1D!xVkz8q3H|Wr0LPF zq1D94XjbL01e`UvnzJyDy6N93w@)oZHPcD=cn)dM5yDU5-tdYWA3KLX#VT@*^^X5q zUJjMvo-#ay-m#5F6?Sg@74nw+dPH{%)b)w)vU<;FKsw|E<0pu~_= zqIyp$Ao|i}vR*GPRcdr+s5|O&d*n)x;js=S33>d=oM6^Y{XSp zt)&JM{UG}Gn9R<@TkaUFe{Zxk*h~txtHde?4&fzOA@Btyac!15-OsjWbZ2ZA`f^F< zxRMUJX&=2T=^0bxR{G3x^EP?}lQHcWAkJBzg16rL4DJwr$6HOhhCwr;0ySzRpT_w0 zqv<&qAePvOX4PaGK`cNENknFFJ)j7=!f9S>+cW61nViF05j11#c=<<2ws{tnWcGb@ zjCKVt;~H%4TmB&UHe5m@8fGE(aLF2iaFF6Q^rwgU#JION)JZ9#NY|<%xxI5<9w#C=Mpw@9Iw5tjVDEMcHk-Y^W1v< zBSl0LwEHRQ`&lE#uS zFBl)MLKv=oFH+{iRj|mY4~0~=VN9Hq7P&(Np+Qi!1l&los}*{h6_UixB>?t0?1JyM zvdId6=tw>s@U^ccWTO7$73>atDEmi|m@3W*-jNe7qJlw(Ae8_&nhU|(QTG1|cXm33 zZ{_l7s2%eg~AtxeqYS>y`^YF z=zDEL>3c=-`d*RH_lo!e2wF82DSxxV$0cB7I}3lu+j1%?y~ z0J<}6F^=x~$?flETWIf0r?>H6rVel8A&C8=GDP`K1SoCX@-<+PBafX5^GqND~3TIy;dgmhB9 zigsNj7}1bl z*agJ+I32GTb$#4~5t zdoU0}dXx$dqM_}5tP2Dkx!K3EJJhQp6QcSY*I)8*p2%NObFHCaBPFYY-q`XgvgM$` z$yrx$%Af{Ela)prU2d982{iGB56Mj)Jeiw#tAh)FLaBkJ_Ea>W7sOx{T_TId~x;qXq`{~Gad8ERir z$hO0@8FYx=u=^kjT|80_M392A)GblDMZp1>et-(gO|(aDV5QH6 zHwCYB{cf=>j`MX4_YI@^cq}o^%bF>9+V(5%6KI>bSA19R2MIXUaw2R(LscIWr zuw{^#Q`<#BU5jKrz7NExPg1wNW;EL_j@)=zR(?*BW`$kpMBl{f$Vw6fL-bAbK5i(| zx@uW`-$Yk6`X;tolkpDSS?`^nL!UjIn+5}Zu^ z^hxn9PhF7}EX{Q7@Zks%t)EW8IMizgkIKq&YOwg;_yP90U_7UdE`?4XRiV(if{q6% zbb&yEv2vsFg!bKs2c(i@e`SlUi8iy_hW4;_*Rx zMDL0j9jVvPh>-#|u3ezh#Y=$Q*e^jnSUrsd_y9d>padu%ECI^DT>?CWl(IXh_X|ap zFIV)S;HC)KsH5^umjfd>X;Z-@MA{woA4L8N4x#yirdr6&*C1lVjw*2ZA#@K}>8EYU zMxa$k@i+wE$LJs^QQVO9JSAZ8`#L;D`TY+fH4<_Cd%@l$NkS~lHe)S{tD7J6n<4wq z3+_IGx_ODI?toB%SyeJMhD;6U$Uxl#SrKkNO4Y}O=|(fTl+l&Z7kr+E055B%1n6i6 z9xkI#>1iNCMJT)Dv%jNtOVPRxib1WOfIF%IoroB1pibmJLfe$;bIGWulw-DFuC~)bZC+TAL6d_ih7+>IRSg`d#H$kD|S+_EeJ+T4svk!t)H=G zS`vtm%rGR;V#+?+BD3&FErLj>2h>STQVgXP%1v1mS9VN6B>5!nMl0wsP?ftS$Wa|el5pr!uJ8l>H9$F?5^^Z`ZqMK-RmmHs%VxKZ5x() zw}380N1FL#<}CZ`0SqQiw>~H>%wj$4V{djx;|Ih`Bf1+8ah}>6gzP}sK9&nY8R5o$ zF#$~{@pWBUpr1FqT-Zl87Z+MFSR%3K*P_xpdsy>hxU0?dG_ILMNgbBTxJY@P-ngEB zRN(ksy>11R=M;7rh9_>6TV~0KuG3LbD!R1Y6Mme3N6;0?KLBJ@9E5q7lakvCDFX-?%N%=k1^D|8mu zGqAAGu{MFLAPLAu8TbmONPP$vD!M7GeKax0i31}Lvxl-nm`%8ai^XErUE)dDHt?kg z&dDs_7I_Dq9!Hu%>)%VFfsZ(`TojJuq68$51D~KP(brM?UqjKyaWw?fL==_QS9Nsp zN;$Bb49s$%aB9N}3MOd#z zpgTq}TK1qCK)-@^lY$uhiSETYT2p9Epb?#z{t)IjkSCF)5bW9j;BC!57F||o{S@R5 z(!ML2PdDL7TTs9}p5z&nO}oHIWZ1>x0x4_U$ZHidZxn;S!s-IoL^{z_B!I6#TFNPl ztW1Vv#@vur-raJWsfAmok9C2aUP89g>%iwQo~*D)FmBl(rED%{b5ft0%Q~|=NWz?l z=g7JE6Z0#tveH>>l35MrgFNwecXR?x5*2G*Cq^t!(L4eg4+YR1a^Ma~80Cc3hqF^U zG_BpL%iPFZyu{AWRIe*Y1^_wW{TO-Pj6b>UEWX7bOJ{iq)EnwS!;n#mriiG_ybt+m zl!K0_SU;NH30h?zi)=!4Fl#Bz{<=_nt+~c+SVLJT>j>q}gq~Bp8B()6=Ci+Ig%qFt zrN(TbaL`Wp2Y>l^gzxogs&p%rR&C&Btl|_63)2P8BN99}h>5{dAkb;pig?PCsxz6c z`fkM@C-PzbxdlNoC zyiFJtvBa4kEP={4X*7z(TPIjz7=dv@;iQq_5}NO0o)eWyByerK`8P=bZBAUsoJt9S%)a8F=YT)gvoI^Q4a>5J|!jV-F6;K5YweVu06UzPv@gi~Eai90tRA}aW=FCMABs$Om;Eu2l}}Xkv&*9K+lEl zfJEf`a4G`t`AQfgx6??dktDF-O|tzBmC!5-5;HKf%|DAgf$0x7mb9W#XrQ z>wb;Old%StopGf$Iq(rZqou}`A$wX3M6)wvIZhlZ5-Gqty)_^^ql`D13jQJ(Rex*6 z+ekgBG$)|`{+wimAf~2sMgG6it_C=&>pH((EgKohE5_N_*p{1hqT0e1j=OfF3v}Fbl3MT!APfa)@SY|-41>0%x9>j~HhadZZ?8H7G z!rxPJBr?Gi=7nWUe;>{R=vOSU={Imig+}t5U6qquuOKi3y1C402k%dD9)w~>shpx! z1|~37L8oM1Y16Ub*@&Vp23G431CDDoIW-ll!sRsl1MHU?=k+(*CD#r&!IV$nX z?^`QDgLx%g?qf2oHCBp=(-D>{hwvQ2@3Q3zt`rg8HbJ>Chnw@84}$LizUa<{zZ92C z^wJy~3zZCnVy}jph`vX(2&f0w23*5mnm{-23cmNSer-+Q_>XwD-D7l`gO8PetQTQTU-8k;GSP$;~Qp69RFv7#10ie_l zBVnDnch$1g+<&s@y29KqH&^$*B;~3J?(k)t^uf6EBmCYX=mq!-nibFv!+YR3XffEs ztMSr6^whZXDb!P?Z39x;b!c2v$^{?|{lL<=z^qDc1WJgMKLNT`5SL6;j(332;319K zsEZ#Lv-YX1WC`$GE$-WQbjB_Pc~44((srbV4vi&g;LCLG&HQGJc;g8a{J&R)FK?L5fU>>b< zl!0)U#Jdyd6N0t_X}lZICMlYaLMZww6tBG3Lfy(c6R1{rKZ`G4r2kn(;wlyU{< zmcI=vc2bxhmfZsHd_^f~XaHf?+He7L`|I{Un^)cJI@$DA?X&mi%wL8}=vo`sO+KCUnqq zPY8L*iN1;i-%3O)onH3f6W$xD*jpwx%km|oDB4}?`ubcFo70KnFdTn4Wf6m_EnBuYgPH8zvppugnnmko&ApqJSBvsZ0RNNlpP*4ta9xJ;c-7!o+$Ztx zgU9_i|3B+8s1J!K`!h7bx(s|Py@3B_Z{U9ok20LJ=tP#Ua@8j>O}trrOD`OaNVpF1 z>hPb42Z}86TDOgJk*N`MNno|!5Foy-N8EWWr5H|n|L_rf8?2gEAL_pYJ0z82=Y~*! zf{`J@jTqT>lwvUb!0ghb0pG>9;mhaVb%*g_JYRmivY0i0wDL0lz{q$%gS0b}<7&st zRn(n)v%?(Ha=Z1$cZ6WWk;ShTM;UN9p|xs{=z^p3Mp{Ed_j4vqWyOwbkl~^G?yA`F zX@HT%Ve~mv4s8k5_CGK*7gP?t8F?toxI<5bYAbeZgLH|%!XLk4$7P0aHf|k4@Ike9 zxHf;ZiaTQ*8F?fG-Eu+OQZ^b??y#BaHIHl+yXal`09(lwy5DCM?30+_pS!~N6uB+K{YFwMxp;tf%5NMA*M>_Q@jR>RYT z{p-baPZyhRACw~I{!@l{&3%F)bdQc=o>CCR+${ipbB|t8j}^NzmQ*zhM{&(39BfdW4K1xN<&a3t?5Y@J=s_a zV!B&_k#1ezJ^pk(efRmJ)!5y(#KRxqPA>gp?4Z?r{B*Q+)|mVi(rnB@;{0yIOk8iS zeDvT|3^6A zR=B$&c3~ zsmA>KQ!92og9?Tv@M%nIZcXYV zxv6=5%%L68HelY}`1D)xH0mC<8pUfzp%U@=IR{V3M#!V1NhQa$v6c}$g^&g0vJ^0Z zt9|P$A3g%N&$WQ1Y8ZQ8`m1>}t9kzD9B#dHWE3K$R^nQ00u|H$mb?VI45-f{7SVA{ zAO~(M7H9?JxuEeA%#RGuai0LJ>ug8Se&)y5pw&tt2ldb$hwUvjMgeI7Ni@nbV~b`I z5U^MSR{E-~7-GSmk`XTNfM(=$JNaip9*!z-ZXD9VDag05p32A=D->+mvgf)PH^r}|08_pAoGpd=r0&{qnSou z%5@Y)T5rlTe0W|RJTiAHMkHTqCOSoAy>!jRlcxNypgFjF?yzm2QU720*QM*YxX}`n zfFIJGi|-)coJAs)|Ija@Cfi)Qq&jor4E|Y~0wiY~$3r3Wx{%=9Sd_$duXrAn9`k%f zI`+WaHeQcs2!>Uy;pJe}N?ed&wWw}8Ci4*>WM9>T2)X#qo+uMWP0<8r95<;fsj7M| zl)G+tUsWwwjwvUZp9{ld*Gl76&3lv~G4d@D>=ukg8o{P&BMl)8A1{?rG_Gp%1Su^0Dx_!}_-IPZ+DgGHphU ztwldUJ5Kf&jNv?mlb^#AI2oSAc^+v`^aa8Fe;(Sh!U;WD8oIeW64{L+)Id6sNpBfxZHCR+ED`PviUtngaRS8OFE-?jBGZ9>C@S z7`{+C|21x%Q8F_WE62k9a?Ah7x>oT4=>ES0yh~#>knoth`v3$qmW6vg@)q!ado|8E z;5`s=oYlap7Zh~46toA*z&iKXxBx*lINK37g1@_4J#lL{#Hx^#8+U%$1Cq4JWQp7sqM`C48fBEsB*kA_3-u>qtkSNN+xpnb;f*cX>Eg-x5nUX=Z4PGltW$L}Js3&Z)kd;sCJJHyvw{rp%$?M51V7~VqoOOS)`WOy<2lw3BIJC&p*%|8=L z_{VZ#+1G++FMRJCz{1lYLD+R7vaD<&eixSY3kAVRoS#Mf#%mnsi@<8vg~qO`fK;ri zDOS7KW1%MO4^iIe8h9bt-h_4l1x4-ifQnf^@ZrX#9x|G5V0%$ zdoSg}fqw4e`;;)GVryGs?Jb6_p$z+N$aozLC6auwCu}cT+CL^Q?X*%fEQVYO(s_hm zK=`kX>>bFDZ}*qx*qf2?U&UU<$fq|WD<>F#(^$47_5%rRHP`8obvw?eGatY7%&7AS zerE`J62H`7)Y)xme*tu-(4MvEn?N5G+V?H(hn7ZN+$y1i@W3Qc+&N|uM;j@v&7$Xl z=!H?|6p-?!PN3 zR;V28Jyx{WqA`ocE#fXf8n4Epc8f+W;+o|eF9DBLl(eYbqGv36%c2Vw1^Wr~TYD!; z;5}|MHz5c8t;p*{#~mQ{;FLHLfquaDwm@M^v)@N?Q$m&;o{qB#=NQfc7h8%`KFSwx zw;viP#QA$t;35UeF;E=TuL9*~APo5^=NShzst|`C?Am>lTR_?Gqs#*3O&?_uC>*6( zkkbK55;dwo;aan&e3Z|C(t|owh_fG*{XPocWyX9Iwr=}Tw+dWbFLe}Ep+MmRwC61) z*wgb%)T;Ygc6f@mP$I^07k zGXoT^GwdE3XH9^zDL~m8ps>|&50&%v0Oh*@3g0o@L%B`|D3^Scw!U0%uDi=HaV!TZ zUFocsGT|tZGV6Nex*2!gzQgp!w)MRoo7}GYOg59tbaY{;h3PR)r2{1CJnu#4dG{ua z>`12qJ@f3NU(nI#ijXe*zO>tA-H}S6r@IfVxi%4%=`j+DLyE{7CS`C#41?*vUzf?c zc{3EoGl}@4a)bg@=^ImEmb&-RnCI@ET&BAyE(K3S_m7n@x#;NZO!v6jFtomn{kc^4 zV3F8V8aD2J!k;hC;0VL)AN`<2=Cm)}RT#cu;>w_gi5)<9C0N}$zcts#Ydo$gs!CAlS?TZu~{F7!qhSocI0E?5wo z$s+d{7SmB6*_gCna@K4&H4cyQ44$FQJ)z)YxkY+`r)a>98oy9l)s#mHbV(WvoSbco znd$D#bp@iD1pQHs7kDH>Z!PKEl8(MyQ*Uo~Z$TmI8=jc1V-&L2+11^bHVJzT(TV|1 oE4!0--{YzViL6L>4m9_6Z}L#pDYPJMh|=7+sIN7hgZ!!g2T>4?L;wH) diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Vendor/ENet/enet64.lib b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/Vendor/ENet/enet64.lib deleted file mode 100644 index 0c73c3d1bcfd742853ac5eadf7c8c5e052ae55dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56018 zcmeFa33yaR);E4T=`29x21G?cX|-jDpeBk=T43x>y2)+mK#+Bmfv}`aFhEQ?fQXpn zc86TAjhS&7-x)`7bY_@Maa00|+8vM%xP^c$;u3L76Bpbtpwi#(RNZ^KvoOv(?>F!N z`9IHhpLFiIr>ahEr%s)!I#qSy_yv`VN`Kb>3QPAVJK>YViT^E@9NXAj?xncDAPAEM zA+5dp?+m9P^!#1AUl4xmXaA!h^!&Nz2}0tpXt5yt-~O%nS`ZR{+wuhAKl3YDR^ppq zwyexowxGOhb;MEmQ-oPy9YVF+oH;n1wJjB(&be? z#KOt&BqxyJF>*gW19eLj^MZhjmoH5WvwYdIl107*%)-j$3l=Y0P?d<&V^8q5sB{5B zmd`IQTUwUrbs=I{S+*GEdU_XMY2|XCuUyMtc^T!eyktQoC(pjimQ|HhYIGT*vSjJ< z6^S8wd{&`8dH4#{C8{~G2sKo<^3ZUo8zbbR%0*XQsSznbrFKte1)(ZS7L}Cg>CiA$ zi0zi@lIjwzk{NpWGSpc@+3Lu}WmUcLNJNR2S>~^zY}ri->7Y-rt*@kV#e(t#ysvC& z$@1#NfTatn<^yxnateW3TZ-C9OchJ!d=${K3?NtnjtB3!8=9@=vkk;sF(SwthFIrx%$1*?8O{~Fr%d4wOxmVss zOG_5rJilZGie0N0h6b~*C|jhJWn4h*7z>uU|8EEbGp5uFA3L{rckiRCMiva56~{hBC-3T<$AbS12jQl1W%G;VW@@ zk~p}?|TAsQ@(xL5f|H4qT)DP{@Dw zP!X{oD#^T%!{i?d8+(fIe-?gb{EG1l;>RMu``7qU0t)i;ueFRSm@+fVl5^!{E6bLl zdrEvp;)9exvv7?8c@5|e1G>k6#sQ)lq*w)kvTtIsp3NYN^Be}@AHydzpOmg-_C1h6 zRF@n_Avi=-xhruj#|<|ii{WZDaH9-pjNy8@w+wm#e@?{&;U-hO9WB!E^b$4L1pWb= z^!Z$_VTrkj>jfbfKPqoI)ThOQZTpHc^>&e-c|6F@Lft8~>?3x<6nz_E1XuqC<{LnGgDhG88l& zSHls8X}aC~ELgZ4N}2wUpWE?qc_p=c>+033xsZ?dRy`GLqb|9}e@WFoJ$0c!x?-kW zYa-V2V>0%qSLbM6iu*6FPh%O`aGoIG4^M9yTa3mq1~pZDF7v?n+tP=O<*wcTLx%m^ zGIlOV-9=24@bo2PCAfFM-zQr72IHIgV>J>FOszCq^P^))#`YzP?S1$okvo8iRWFmJ z+r4o)N;1A(=lV_>{-(xS4(p~m6X@S&GSsk!Pbtf?PAkJbHhB7xcRVGnU~}Z zgsLw9D%JEhK+!LO*QIc~Su*YHe3{eFIm{5s)7DK$t|vG)Re48JA7o!Haq}#bQ)zAO zaAY+*h_tI=mI>H4uS=Q5s9Ok~>NM?V^j}1KBXVxgZ|3ybr}B1AO!=xXXiZf<^C;hd zg3}7ZAtD?C1*a91fr3K_q@7kq+sa%(Ji>@yYY^Yj`896_0h2 za?Bk*VwRM53Qb2`Ifs?)pgoC5=YcX$xT}E>EX|XMZm62dBM*swnUQ@OjfE$=xicK+ z5sxy%n(b!gu?um{wMvDiR?wM4)Qb|*O$e6kIXu-DfVv&jd7`ro>Y>gs@tueIYDzSc zTWD%V*gO+LTn~!88eI)}M3L$*4`tj|eZH35?JT)3YGvv+HB%WQ=|n2zF|`pTCEOa# zY7v_znL&k!FdDn6&vlqWlxdLgl~8Ua1b>a`4&nC@wbMFRGVP&k&Y_GKQQ>+dK=zKk z-^XH-3djl7Vy;t#g9o2Q-sHR_r-}d|P0?uNs~_R~$`b8$!4u1f%fyl&a6q~7kdDGi z3GP&C%A6#vyaKJK$Q?!R6q``yIu)upWfFX`ItzHuOoRa`J9;QP)>Rs%3Ani%{SnnB2#$VSH6-|E)&S7HyOeN0)@+w zz?Scjj}EA7r8mx-{|m=l$1fc79P^n@*F!$70=EQ0Zsip(GPg{;H&htdCEgoOZxI8P zXcvWn4@7?%0C&)iyi7{>sDnMh8!|~Vv^bsz6ABPeTS#dz;lmAdx)gL;mlc|JOX@aj zJ3P1}W>R#77f1p9N?gkoRbp2UazH+$h|@ZUym*qIwszoMXxmeHp(L-dS_IL55&S-`ZccN^;S_kz zK;XjukX3HwRd4M{A-3Wo1Vvif#DEz-k+A+s*t-W!%&WNin_BTdgA-Dj^U{F{SqzLt zIl7cq4;l$_Ng1Se!+0y*A#QGAdAu0X*d274B=sy(Kq;%Nx~S%|DaXzR!LfxZ5SdP% ztjrjbWMtcw^C?nXYGl_z)v7cj%jZBym0L5EV>xYN(=Nxi-)N1azo|uh0`269W2Wt$ zXH)xE4oY&w6#ghZ+%Yu#etJVeENH$e=imgSU088uEgBl~z3u{JPkHCtcf_XS9^1E- zyS#k;_Q~-O3(!R@farVaBIq_GBGl)sD=vx}{2v4numm{4wou z<+h4)A*+`n2)?#zUym3eHhV^jIE+Z+k>w4f7*jj%g1i-*b~_tpP*+KE(__t+f}S&k zj=z5!iy_BwAPJeRG)G^DRMF(KWNM9mij+}#g9YEDYH@t~71hZTUv!?s^pk&D4(5`zw2>A?*(OGa-}&gaVF-^{D06?5G3|^aR&fmqmAzI7Om8rgm}j zDB>wcZigsmA(-5p+>Nie#F)t>25EHYNO%<|dDIkYUtq7~sS4zTTWL+G>5!X=izQUi zcIXqWyvebas0kBP>7z&pDp@jV?Cc0mOLAuIYA7;6EG6)xC%9@LRRt3b{u_`^)K0U} z7>~iNziGD<`sNd@)D-u2p4s^C)IXAA=x>ls8Nnj7GDPdBJ@FkHtsT|lQKHey@kZyK z`;sVQNrf6EH3u;uv)0E8>z@@B+KEwAD4#sE#8!6}FESKYYV;K_V&JfEB#DO1=n+Kc zZFJjCiT(oswT$V~rTQaD>5@kKS%(s(4v;h$Y9SVCAx$mR3rN(us86taOz%-Ik=x<< znkxnnqJ)M_&5b@qoQ@1$axxYJC!lKVCXWKWS)7X(qW5 z-H$-txW3n|zpeeG0c8X6icy4HIe<);Th~g;>#W}md6bvMdz;f+#lR9&ZM@k`4Gu|@ zB0!sNCrm}ELga$kaQh5A4} ztW!B$sO%M+I;dljOg~7r&qe<(3LQ(1QnguU?#rVVjJy}S zaz5~wI;fiFIFwf@yG3r+J57W#h4I(KV1@+=^;oTyqY)Z96R84!M_Hg$YIjh@^yqbJ zfD+^o>0*0GCA3n^c5(CetY+IDF&M#w0tr4lqzlD?;TK5yo}>iP0L#zhFUw%Cz@+RC ze>Bu%L)Xy+eFoS9lRG#&DcqjfZ>PuBCd$PSBko{9GV@bQe$+ISj9F|^c1F+SB3bJP zUCJwnAZkIeG&+8LDf2du&V z3_6pwx9R21OSN&FLwU>b?OWiT4-!~HWGwNT zBm4pJL|!oC;+#X61SO_8zhGOMZet|Mdo*bMz7J?lONp|&5DO7gKp){rfqO)5u3c0vF9U?y_2A)8S@Ymm= zZ1+0$bUL#RlD_CPb&+pssG6E6*}f6|tC2;mm5BbCC_G|JC{W_&OndGDQ9irH>QO#ciH~SV>%?dfeGKOxk{xIzXkIk6 zj@BVbwiZ!dMqQU2GLdeTYicOMh!5^7rjhrxRD}T2n2NR5U3-eDSk8D~Aw}3!o}?6{ zbJc1sq+6tCe-KTEge46t*QC){xy?h|&LW9^h^9s{v2he+Kw1OzosNq>rG9j3e>UzQ zl@#3}npgCGv`271O2ujQQl|^ZgJu%6p5sKHTTG2`4%onh!7~Ljxq{|?V1o=RDZE9> zZl@{igv^0(J|@;Zg0e*YdobRnfz;2yl0j#ZNmH|&80i!{vyP#!MX6}VcP@*PNBDrH2<>SE%Gi7pz|p+2b4C{u3K z0@64e`dlP@B)#AMXk!-*(j(CZyqQK`ggouU2#D<#a zs7Q2PNEVc7sv5UxU$mS!v}uq2`3roK5Eh$WB?Wm7c}33m5i8go@{S}hZm}2Hv3i}O zQ8apQhnc(0XPa7F4PFzmHNrfBS*ItCU;sE0?XFL`P92)5-%dlWn=jT!gm3C%J>o!* z>#bd5wSFg{pj;U%G1RW@f*`z>+DH-ON$`!e<5x#XbHjLqn98cqOd?4}h>|@W&MYQT zFf1j>kl|5|d8iSh#`*XiMIJSw4KUS?2|$;!-63yJayHB{L59Z#+At|NXCS0+$hQ#m zF%W?&3_*<~kt$pa8qt~fCpLv0%3+$ACGCo-J0j*FxvtZm?m#!@fNtY7l$)bmxj_(W z{aFX2t1!|lRE|ZzraHs?YY}G?&hbbEt|hGe>a{=6a-{&uSqk zoAL+gMmQ9TLkgyx3yMF+Ph%N>?dS4XJij3?qAWbC_R+gYHm-0p3Cq@`oN#rUpUh=Na*Rp zXr(3#sdY)&reaFBAPnL!!GiZTbPM!Oe&B9aA89DR^$Y~Cp1I3R+$c6^}Y=90O$3h}^?Z9Z7 zL#y%P(9f|f#-SvnZ77Sw35XUh%xrBAAAt5F_1qPoLO=ZYY9@z}0D)E!liBJS$7LG+ zXyuK&5(Vx`CB-3&;c7K-qYP+_;hJsWsAPHAT*Gy|fwLQs({Sx$B~)MSS_DtVL#d?a zpRI&a+ANwMUVjufaX@_Ca~3ef2XRa#FvJaU%$>jxPjF1{wNc^~S_6(hz1K$LZaVbu zt&PsrgB$POYoij}XiY5s@YO{A&DTb$&U-(PYba7IPPcvvL7_dg5=zf@?OQ-;bIj}A zFh}s!b3HS`man@NVb!fm7s9XzW;=@(=(ci+p7q5~zLuKkvbf~*Zi#j_+#>0;mW4f2 z8n=8*w1@0QM4&HyHP(XX$5v#ozWU0m$e+a$Uy=O{s$?`Pw?0;6AtPu-HpHeoQJV1g zzp)~_3h~^9){*$w`>=W`hX*s#c9+&HhvA#~>#-tBlA%J&gHH2wE3zB<@RzXKI!dCE z|6Y$0!4O%(GypZt3kF=}De zff_1Kj!-1ApC#d>6L=Ql384wEqom-hbOsag6CkOqKqp-glO|wrENr2Fl9=Dcyd+Pw zP>1eRyqVz;)^673O2H`-tclK`7OzaSU>d0gP-G&>a3F?Y$WM@@%*&RPi9<;%uHSYk z5Rv){k`?3@sc&HVH3J(pFjBt--Zb}69Bk3R$iz9CV>;*SB+z+43k+~m9BkKuh=b}| z0a6YCb^sIyt?y#5`VIZ{LMoe>O;stWvqoVUCfS-rc>`h+n=Z591*VW*?DyHlTkdZE zS)IOuj2T3wDpLxo?c^h?)vMfWfqIxMMe>O!QGQQEyh{FgyvT#4-7~9Uuq0uwZda2%OcQOvN?G$C| z^3++mSR9JNk0>7lAFwv*wuRi{#1L{WDG^ptlIZO61joEcbF2x1$1W+`nc#T_LES2Z z%vvcjk!EP9vaPDi$fPpUf_$DnldVc~_$V@|&K#ml9Im>CEA_3Q0%>9rXx4}N5T$+} zP?Lh@(Nu|((AJI{g*nN^$7R-VRtr=(8A{CWq~ zH0)hcED2ap3*}jhS*-^1m5F; zE6fC7CtwHwC;57e`;rKLki#B=H*mNpw%02zn=;&qQIfbUBoQMUU zFd4rKa|Kcm(-~-FpgWi4s8P(MdS{KTLT{apdDK3`M>CbyXQ}kU?HL%^7c@Uh7K(uK znxjv#>7N>+oO8VMS(@?IEP=4v?ar3epO12vg^5qx%A4RLbXDT!FSW*V4V&STZ_7=o zKFh6iVfdsRVyW?{GZDNnTMMcQ77xj}?Uk=f@^u2U+GJv6yQG}ZMG9*;di#A@QWcW4 zlCqzSm^!qp-V^=2g%HrIx!Q%bf~1r8(E-xm}e%dPzp}ai7=0p zRURKTNx}#7dh|lAk}{1fO8w+lP^M)%6~~Z>!%ED&y|LR02`r|t9>Ow>nQiCjn}%Xs zUiz0h`VeLE978CCUy1; z;EF-{4o}&^y@d-i-QmuWZklWx2)Z66x-vS50>+#~fQh5UiKt zv~^Xc(}K&l4kheVlt!SPl@)vYA6$qaWy;>_$I&|u@~bdxNAeK zPHiof+tmUqrD_$TD&$qoDt>upq`*pkVFE>|zaJneZLm}*q_r2e_^UL-=p&)ND|3TW zzLQUwR*taUFvoYEI%x))hZ##+qH@C=2g)w%RmXEw=u&MnB>>AcmAhSmPkkw_z~Sl* z%1fQ&sGU=MB}fk?gyqr==A(SelYmX&k?6IQYD&&llntgJucjn>)JyOmtg_P+#8PAD zMLNG2jh=Nc9#e?*C~FZ^otP_kq{v;Sm4`9Khxsja>JUe4AB0%0ZQdN;7$u4?F5o=M zQHVEP=)uQWO|TR|uuRSF{Ea$g20VA}K~rGWb^-eS=>3SK(Ug%UWaOo1rxmAGq!gJ$ zNf6p5npMf;vno_FCYpgMO%(>V_0R5CoL-Sulo~R(CbgT=Q-t(9b9QoZQiZ9gk8l+B zG_yTa8uQRT=_$rL#mw!sGx2ufX92zl?+2lfS^;ms+k!zVjpyloCw?Bljd*Xwj}*^M zc)y4rjhI7t&mREVfa$4VDldm=;GjL|$ps-6tBc)9I<@xv*o+V9w!`r=3sdl)Iy*D7 zaOb!R1GgH`1AkqjhDlwqp$$ILqFrYjG znrT2Q0C6h61;i=80f^Ik9ren~suHzwF^l641KI$H=i>=LWU*`(HUpv&rdgNkqa*1A>KDqFdQMWSP1-M^J09)ECXNM{-0)3_>39H zWg7ly#*DiTHE@(0?$2VlQm#3UQpuq)hHJKgqZu>qnrpa@H*j_XavH9Zf%6y;nxB@c z83vBp1gAXLa4j})iw&sMaP2)8$L2W}!9%x4Z$Ksi^FQFlz8(vZ#VB#+hk3XX|EYb) zG5NqyE01Gl0z+**jwu6%T6`R{1{iAham>BIQ0tFlXkLqC0LS#6!6Vr~b1L!YKR1K7 zDjtaj^`5~ihYRs*{OLV|N3Ey#^Rk9QW=}hVw?82m8Z(|Fy)d5{7@E^tTC$Y()M<}C z%Xd;oP`xww45RjS49P40=$NZ}VG4U;7WKkZ^}@)#Fu(4Fd88MnxfforvN3;t+0u&nrM~6Ms)RVKZL>|B=+rj%B~GAfs9yUX|H;n9ZrdSQ z>G3_;3HvyEZir@kU=y|oYq56Qv>2b8{nvKPCTzat+oiivGj@{pr?mWc=5up$;-?en zyBPn1CNYGuQSbTO4ZvfAT(`&azp%rG((_o4c>9{qr5=X{4nugeW@|#^udkgmU-zO{ zWT!Pj48O}US72iETuQ5?PD|%#xb3D@LU9nrr%7bwhH-3*q`K23HB(XsNy?=3#911Q z!^s>S7L{%_)2$5hz@4Z`N>RF7!7Ir_G!mwT8ll*#CYV_G25aL~730=s!c>0^I@>A9 zJ%3ag`jj*E@6;!wGh**NywmZcewD-^^`kZTZNQKG{+NykFD(53_P#>O1Eo8et#NXQ z^ooHD*SmjbeE)Ct@1)OCNyPhHikp6Mj00aq-7xf3V_=_>eqGtRZqEej5va-zTJx`RlO`MGbp# zFM7p&%D2TDEj?~Kb`a|Ge3;eSN^wfc0jILFgS_y$T;J{X<2%jOKa~Qp>O3i8&ty(~ zdR3`{%(`tTmL4n|n@`z8Ky0Jn%L9;yrJ0?r@p%i(^J&EM*`DvSu=M2Hgn2`(Jn1W0 zog4HiMC`0M+_@=H9@6$i3yeHyGVm%%by#RR9y5W*^V7}Mm>rx9s=M@s2+ZlrJ1jRw zDN*|R2_X`8k@F3Yj3Qs5a(J)V%aUHOXxXd2FGo zVU1)|r|iB`iiMA+W8a?Rsv!z?e%M0QAGzh`Ot;)=s(R(yJ=oh$dBj!|4(N6%jg7tL zGE$O+lq*fyrZlsVc4cyQQn9H5&44|RY41&VAIFbXH5|S84aGNk+>f7C^LKxC+R8Bt z27dPJw0{3}?vABtcYnR4;_5#viavVZ;)kwmI6UyNEpOd=LG+FGhC^+?9_`uO@A)-P zu1&ja%g4Vw|Ha0|nN8<^Y1yJuY-@fn7^Ey9xd+D@i_I(`*?3HIdP=01; zIb2Zu@!dPA#g_9&Cm4z!^w@$TwVUaKl1kPdhW3O#J0b^Uw-h)>bdW_UO!|1TR)$$=dXXu zJ~Z-L;hkwU(+;$py`c5QXTE;=!i#r2HS53bd-iql56^FTXiL(l^oErA|2UMf(;6M{ zuNdSho%7f87lbEV=H1-rcxy%KEr(6pENu^5n)$}(O5n*`Mm)1Q^$#Ds`0!Qx zwsib{SK6wvCj@c*#(wt?ePG~w&z-U4j>{%2P5*WI;$NEPF8b)}X*b`za6$R-gV`m4 zDZ;`pKfN!t{I)-shK0XQubaI?{ATB&O(jLqrol(Iyr8aW*!umu=fAzsec795eYIxq zT_v7<-_2R@kHXicJ#(#3*!Jj=?C175{_yUk1NR-Auyn_s$G-b||K26hvi$E4EgL(r z;ikX0ZYh4V>N2zQ!}F7`n(yiVmtAYlczgUUDMf#AB>f~abHEGrmW*AOJX1RRslake zzkS}V4}Y-WwSgzznfJi1Ls=I+(7NgAjRzvOzHX}3G?5s$S!n!c{|OVb6Xnp4YuF?->+FW*#BbIUd5=YE`e^RgAg z_MK`Uy;r$m^_!WmOnUpKcv#SS#*yx#hZ`p}^<=Rf|=jhmVm|90e;i#~bq(IwUW?^&Ak=hS`=UU$vF z&cID+m1oWtQtBqXc;ArKTaG+F`pMgJhi%UI?fSM~o$A_oN`(t;gH!%BZ~@95>1{`7?bPd~CgWBagoQ*H=; zo^;ud>4{{)#jUtV%j8faAd(|y;WzkyE`tsl&{#+o?$4vi8?!JI5clT?M6*+7VHY4i z7uIG#9~x8u{C}dK_SQf(4W@?6eTSk+Hj(5eEhql`=QL2J2E-!*l&IgN;JdGLFSz>0gK-U%w42_~3=Q?q zW`=Z4mb~(z-^~Ugn!mw3(4vZ4b(B7%vrf=bG?UG3Dd#7P7V)olQ-;V|g$ zaX485y?;7c!R7<{(_BDY2mWzutel*ywBdVP3!VqnfbkCsv!@ok3Fk1_8}>}7kcdT8gh%-V(r(VT6s`Ireh$V+qBxMP>^u!K#m)o(C=ov-P$A9rU` z7J^Oe1#;)oGA8oGO%4#9=I}4m6F5Eg>WdzC_~Kus_ppW0dxE`eZ&#n!>4}$*mPp%Y zzMtZt1~NHPuhPn4@*JwP{*-5!Cq`jLDF`l5?qluu-h@*128j(*AFmUk3&%i&8^10>~*=*1X88RZ)( zu!A5>JPIWCrlB4?Nn$%oSM<+V80LJ$zq1fH@%xtU-(LAo#Q3M8>10tBZ=Q%xb!cnY zD~7=m0>jWujOWrNxrS^zunDS$tUYkZhd!!{`%htVq+6hkTA+Nx?}J?o2gL zi$|_V;TBrurHw*)kc*HtgYr-?&)ShtfW?yhaTnedu!%{rPzWV&s5q#u1|Xrb z@s8x>;y--XkxU}2oqul7K8yJ0ChfDBe}?c<9m8qw&Yf=oR_a>me=9bpMC$edBwZxf zKy$4~Tpek1^n|0d)2NOLGE&DZIDkZmkme3BHx6#nz^|mgO3I%3Q6DxmQF%DkNkurK zf{OUhT50{6l~y!Lb*jrt`PSSNnEd%NbpfZB|JGu}sxc=SsQGYqLrMj3FdWb(FSZgh zBB|4{VK#phrZnc@TMvU%IP$Hq&@@e4EN;%v8V|GCC*djuht%ME6x+0&Jj?EfRqK1e zZ?ubkR$p@cHOP@ECzP`bXHSekb>wGaQbCGViE!)y{Ei`af9TeHNxp3klC&Q6Ao#A9 zV5!9$0Gs~cd@eTCQ{Cn40&y6}5-c{gM#%t#TgL>~Gx^ygY@^sTd2l?k&R>d6bRq@Y zJ~t_e(ZXpKtZldz93WDBpkVXK5V6S;CK zT4S0#aWVEezshQ#+$qHb$MI2PbjbRce+YEUlH>cZL?|2xe z+$2oJC5SLXzrZ11lSaYLSW878eEtQMOD{fObs2a(kb*KiZB=(#5=3U7{PI6<4J%LE z8qV+58pIy00ma%EpBraOo~E5l*7>_fd$`-%t)a7CDWSBzjS2dcdKn573d_q*C8FSv zsn?WyXx`1K$PcoYx&g1)8_Ee-65{oR5{bRR#FsHE+e`v%0t8qkd_p^LDhtObtFf;& zZ&Xf5sT>>0v%rBwzg3FlF)^k|0vxIoHIa2x{2SeoQxG(MKsTlA4;Gz3>g1D2V*R5a zLk4TV#k->e8|IWRxG5}8P(;no4Hm%`F6rFkjDh5j?D9@0ohgA7j>_3(Z>SiBt)AGO zerTsXE2gX(6R}%B3mt%+i`Oc2t)N1)rraoO#So1Z`PA*a`O#xtb>+0%-H@}X>e-1RKm zfiWn*7>IltaF-7O(LE}wam>`AI8Tj2A9=mOSRMaB4{gkOtGA&h^D~39 z$v#qC_aIo!3AIt{xx4E*QL+Wp`)8iCxcu&6T`N(?PTOZO8gbBp2HV9z5e6gT=4NS} zLlWhDHZqx==~ktolI>2~eI(XhPDouVPA;%Z^0SSwo`MZ?SY1b@jg8ptqJJp7xoxkB zb$0`$=0o=GqTV){T7Z8M95;7cPLR_YL4gYAq|wBtSNZZMMD$HPxSu- z{+tb^5UnSi4Xd)eO2rtjQhK?@{wTs?PjE4daxW`xR;m4>Coi5Glf$qlc0}eyKX5FFwsm8d*L(m|JuLFt5iB5B1jH0@7lJURT>e?XT1346i2 z%W_G}zah)5UvYgN3FrD3dx?Pps-K{I3<;NP>?H=S0xmig|7g7RPvw8~Ecoi)-^R}} z9pzO2X00K@%4>{2Fl^*o?X<-44J-czACT29$Zh*50EMJ#pPwL(41lvX>ZLbpERdoBp*(vBzImD*H5%W9%K?yI~H*}^zpv* z@Bg_zwfuDYl&F+Z710Po^v@%Zk29cG^_fDgy_`mmn$Bt!?WTtw^=rL9_)&c-@w8ft zM_poQbdn8u@JS0kI!lc9=1_LAGZ(^9NOmadUYaqv z0)B8ZMy5j!;e1TmXh8;k9u>Qb_KJQtgY&WA&1cjc$LpyV#%#@XcuDG+v|G>iozIKm z*bup09N*7}6XqsoLN1czvxTegRP(8Cy^4{$^t9xt{{}g?@^4v?^8zRmV64n3gLH?NUwZ~L$pOrJ07 zHt+Oz;#fY<6JJ8-^=tdK`bABeXh(C>ye5h%EY=-@C^HJJ?qz&Q%Da@nz2KsE8K2mIwGM&LmbSTWO>J=E@k5nZmrY$frE0}hBmFYMXE>yi6dv|i$JgOas zj^0i628U4|h40ZlzK-SFS11MZ?UF6u?z_s3{am!|>V#JQ(Nj3PhUXV_LDBa(PY2sr z;`CWb9WQE@jR0h9FGackIho@t#;^R z*xQF)%liwJmkKfCvP|5Z#F$?6H=-~wAYP3D|Ll=2?1S6mpk{`jOWBQa@PVovS8!UU z9LAAEI5Bms+>tsyb>(G}W(To3BrP)6V$Wr)Jzif3wyWyZ_&2uxK0s^~WP>T{;lHF$ zc6uz^PiCARtCdr5z8!~zVbf`*q}I?TUda{+q3E%jg^~+;lx(n|uJYg%Hm33X258o8 zFjjM8)Q={-5*z=LTx@&ZV=N(#@Ozo+y&UWZbHR!o?JlJ-6Wzx^hd8K^#_qEq2?oui z!TUT*p;A4^9dQ+bHa2(-`o&nUL)`CjV#34!AQDx(jZtFSUj5I3FcKyI4B|IT=^jx- zfpI8bG?kiF(-bq;A!sUMByB=d!Y*5v=>I)DOX_?{3eE;Qy(#*;sO4-<0TsuY&)P}F)}x%gG6;(^l73&?TvZ=V{h+#0-Jdk z{q^Kqi{Q2BDtw_mYi>*MrF~Q%jm{;XL>C7{5^4Vs<~$!ezo%wPSZlZl$*Q`vb1+__ zd;kT&!@lh6$0KO1{1DX~rC=Hz%2#V;KNjGRMu0=rA9n61`6aGxcLs|`IKrQ#xWga7 z{PI1mo?S(>L7sHpJ3y?E4yxlWa;gc^V<>eiCdNXNI)J&LP_fmXS{c;Yb`fm7@dw|) zj%hdnr!T=nqiXV-gTk&3Zh9J}cZp8Z@B#c%JH{iV;2l_lle(-)2 zvzwa$Z^C;Z3>$U;&cxdcV~B$U<4xhxKRO(UsWB(%vD))vRv$De4&jRWcmw~(Fz9Uqav%oo zO3D;>^%+pWa3vW?g5AvSsp0Z9vluh%7I8ybT5}Y(Vb;;%WK9fJ|l`HwchcR)8*M`Ry>EQwDSf z_>{X2H=vII(Jmje@Qr~x55vAI815E8KVi@s@G1A#1c*4@EF|^Q{oMhG`&$bB9mQP# z1c-;&42Z`&W`ORR4~V-40p+j|BQkV#LNB!pfSwfrD&JD zG=2_(S`&NraD0C6f?0dWr7XW+g!pn6CV9>*tuc!+vP3trC80y>|iU?8N& zSO!@E@e+Cz5HFXVfG%aO8IVHUbtIsx8O{!fQ(ggxr{x{+AIF`SrbC~A*ElW-e8nLU zVAfrHF9AdvtozgPe*}ZTB${ic0ijqlS0qYv9d1At!_{iwsLnWL$_=MXv2z^dnnSsU zEA;^!XEz|H;VKz8j{y}Kt}_hW90QtbxE33@#RgPrxRx8Z3Ip;PuB!~(8Uv~^T>S>F z!GP{ETpJDCy$1Av;rgI~+h9PC8Lk@*+}{jnli~Wbf!kt0+YHx`f!kp~Er#nW2CmhB z_8G1R4cuD>)NZ(bVBk6o=o7>Bn1TD+fVvD<0IYt^kQX|XZa^6ZlxaXi3}~nU^R% zfFV66j`=k(r0v8pPf)0Cm@qJ;`NaLa2Mp;yaf}J6BP}S7xd<52h2j{R7$J=)j+p@r z=|yqOQea3sieqS~K{`?#L%ShKQ;K6~Zj;|95|^u(*2Z(p&R+ z30k;WT$v?I$FX!rOuHl!R$L*0rP#kkv`|NFx+z17d z?aIsMEqDQ5{}X#;PNn<*nm+DCrG9c7skRA^y^mda#c+2*y`MrIG#=6in7zxWKXacQC?SCF_|Z+4* z@RjH%-OFps&#e9|cXAe;;`WZ{-v}JLx`pmh^NcHys%$v(zlN-m_`&SEB`l7^ybM!^ zPF#lK!UX3z4!MKQ1=qegVa%6d-2jJAFVYsh)X6w#=mU`qSBGF1Uw_QOS+mw1q-4U3 z)wepn%CL?n`k1euS~N7dXoxbq0(U>e*AuXoP=jEY&U={D9#4>&@p^18SD(>&BgLx@ z!-ub^=C)D7tzUZO+eQk$eD649^uueB(RNIOUceUt{6t2mb*H1>*OmvW;ld6U+!dsK6<%fv3v&I@j&t6!#5%^zQNt&dmNK>G}lP?TJ@YSUj^eh zNv)+rCq3$~h_s~sg)#1)$W)Hgv zPr|h>@)-1>^d-52Q?StB!HrP3Y(zc_XntH?A)Ut#qgGGWAzC4?`wrbUR+`A@S@iD* zac9<>;J+KKE>kDwI^V>i5`0k1VK`!;l+hw=L$Kl*p)lB1e-|)f9gAOI)8dPi zSuBRTv~tx}Db?w73b9aMj9K{nQEp{zdZBW8p($F36vNmj4d}_a;-INCaX(!W2hGOr zQq0MlK?+HblJl_Pa;L?uOza|7cbmchkFmh4VC9KxVEY?k1sr%7{Gk`H=>I87!lQgw zs9vX)s6zaMHKR#X^Ej1>J>rR5qvoXZJH4`O;`BKNkudOpufgj5uYjIf--7=^bEA?!)DwJ&go($-UZVe(_<*2c zmz;C}23z6g1~Ryy4+kQ)V~;NEXopap;Rt*W8c&U>o^lZVTuUeF`^GpwloKu6Dy(%nWTXfz_otSwZ^)g zmoIN76)GR%y1ONXSmDcxNL&8;7yd}$m>K7|cDaKAYWCXoQu5l|RJZaxNtHq+KypL$ z{}s7F7Rw<@yI()Y`vsB|s245%Q&@c9cofB8G`E}4R&ig4gnL@4fF-0dTiMxpd83|3 z<8AxFcL&<#(#&eB*H%5a`T|KUL?JU7!S-pq%8I*lr`{hCSzMGzWTW*)y!zrf@vjR- zLsvmD!Dkahc0-_gKQmj=7e_9HACyt_07{iLCQ70~7@|uI{1JuPc{pM5lK8G${|Q5S zi428;%O#1da~?#RSZclUhHRA1DQq}V?YVMS3hs%5+e&nP$#gzlnVx;v)L;-TsLbLn zl1e3tLx$6F{xU@?Z%^^D{G{^XTa${8%Xux`wc zh3@=SLK$_lujBW7(FK?lu#s_L;ZwQS%6f6A9q3B5-9~gqu{tFirx1dxJctoo1$7OA z0qoO`x5SSK)ekZrjoh-UVx{13sKsInlObPJcBUb%uC*eWC@s-XSA~&qlfavH(m~QU z!v5|_%DsBqG1zUQOAY>-;(?iq-K$I~?R~5>Ji`KVBF&1RGe02ue*i`bE?YuuZr@61 zq{0*2+`u~1`i68u;QA1^0<$O_K!|IE3zbuy*I;!HJ5I8(^CXj1l)hT$w!!lMVekSm zBlB@J`fwS05En$ z?8|oGE~)jcmr{qY>>$pcg<oN+9zDGVhpm&wQ~v>SF9p|;as`_Ui|8MRq_9qlt>=ExG3+=F77gLNTH7$up9@Dx zeu1Ka2>t{aQu7-?lBC){;HFG&@B|lIOTF^iQeg`aAcaYJyp%uIAduKFgyK`z^Mh?! z>X{;RH4#?X`QT{TqMw$vP{zd`m7Q8nmjUK_g6p?no{H`Rw0MH6zjs4ZmxA{bM;9t5 zJYaMfebU{kxv+DEokA(yjq86OlnQMpMEOksHLsxkOe__fRN@J5?TJ(3O6Vc?WaBG* zB%>eB6rWhyFHBJ&x)-Q$>E6^BY*v6F^UzYHl5A;7{!EnA`^gI~iZ$)?);>oxK`>QS z#{j}%MW_-4b0`^B4{TTz<{4qAdMKsL&1|J)2Arq>kGh`K+(cCTz(QPH@+#2c zO%W_nK`c=OD+jmj^Xdzup*WvV%Dyb{6hU93*J>bm^y) zP)&Y_jzztv1=Xfwsi7zbfM#~0eP6$)=}*CROex1uObk4YBoN;OS^$7e)O*$-GfGQV zYu0YAaThd?%iki_k=ZB7kkJ#1Aw-hJ`d?E>BTJCP8aD=EMj2fVN4-5$J!8xf%>pk{ z|A!{QnPffjg@YaZFDdnn_<+=;;sqNBQ8K4u;~-^sRINlfcySuW&PFu(& z=Hb4q-QlB2%Jc_dxBC(Wz=l~9&iGDNrr#TV6@^OaW*n~b8d#WkZOImr+sup6f7G7j z$-58XqH|H2hCR%#=p{&HH_;Id8Ttq8v@@fRgPi6c%CIZzWk>CIeBcv-i$iH~D!W}- zEm^y9T@vA)I4s*aE+1AfZ&1i4X(Ury<)~@zk#W)UIZvA=GSQP%4^Ul0lU+lU{2H>?gR$GqNH*9-QaaS4 zA(SkPoh5aY19#BDrV5%oPo7Jh*y4gA)ypuo_p^JPM-qEY_tVhjW`NNv@sGEUcoLXN zGSVindn$QLnsmyQ4?LYJhn9ZtOk>6TC|>9!7PTY2f)dnkL8y7@amES^5pO=?3c^E7o6S_;?f z{yXX>mpA_{ee=ZxeRC051N!FQ{uBBpx2B(h7`o4o)PB;1Y>R_5tx0b_V;e zLl}q#z6HX;bR3UzgfzP2I@P~J!Z4~2!iyWS69N)Su0%4tF!(rK4#Lz!Dd@LuK-%fU z?nGDXk!TR;#`P!cd~+CukXiLqCGkFNt8pip2&$!7|S}LTItuOIIYF8)hXDTjWNMcF}ou-VNWsCAWBw8wKBjwSZj8TX3hI&12 zr4aotILIe4SQrmLooRL0?x(uJIr=DQ8H55C!@*m7`%ow#gXji{3~c1NcIJg*pg$ar z@33QA0aC$YPlHP4PJW0=LLEmJOeAaN!lb(tyvw=?)kZTi=yv^967TRpg-@U4=7`-- zMMan06~S6D@JC9pR@gWu5>ukX9T?Cego3n+P50AfG$vOuuBAq%nziUW!SUEQ(n^C_ zIRg}krHbiBVB7@!sZ)6a7w(!RoV#B4F-0kEzE1vFwAaKk#C7|DAdMh+z8w%|eI#}G zUMX;xPuF@(-$BnNp#flsUZBT$kUcm$tF%haUUe%gMM#7WV(7q{WzzhD*0A4I>8G$0I= zFTs%&O3*6w-4mGUy6iajWETCqv0A{&MQO$z%^-b}t7a>o#26_CmCf78MeA1tGyRQm z_UsJCp~TRP!T$t0l4(1(X@%5r(et4e8Rd%xj6E>e92s3hMW9!*D9a!juV`^y6@8zwc&uXwR;EDH`60*%=;qMeF)@9{NgQ^jOT?oPhl z9@~%RQw&a;3bW-941a0gG;W4yp$*iSa@sA*JE(PsGih4}c4gFEfu^j7FLvR z!3Tt4q3wR2vzdU`6pgmbESwfa*)k66w0G8c__qLvP1?~jyGY$DonKZA^8=B z3a&f|p$SoIN(Uu0C1i%L%V6BcMvqOXTJSnBz3zaFU{-$}Y*a8y9nAy-9UO^E1WpxC$}}7SPDJlq_j8 zBtYXf*;^$+46FxDBoCrVZ{eM5IiDK2I3GC{>vBjj?UC!RQQ2Cqt6QUVl``51d>XW?*>7) z@^17V?88iqXeICtwXlfJop8&KvvVgXMlEtZVpkDG1I3VB17DAJqtn| z*BaoE;Q}ysChp&vz^{1wfTl-!t8GS#k8-N#a8AviR z0M*MR)HVw>EHCm#v~AoE;toD<2Mu>5kV|464^4r6+y%rY4S{|pK=C-0j;t>Ih!7kM zf^Es@5z={2KpkzV-cb-+qm?5BmW_-)!cSK`g9S^8ON&`I4VFfdX({7g7JqcGE^(;S z66L25BC$>%rEgw(ViQ(zLQvw0A<=M8xqltp49T{dKDeGlOj5Umz7vY^21bZFNA-3) zQ8ask!)x5(_st&DyR2uNI#R)=z!0Pu7F%ey8SR2$^6X*=YG=?+TVNo(NJIgTeN?j` zI}!vNNwNw}l4*j9e-43Qt3fCdT^vJ_40AH<)2T@thc3>Z=t4!=AhJaHF@UJ(Lfb=J znBB{q&=OSukZPqQ)$RbC)k;#C+UPC#f=+G-FY| z9U0?owHwb7yrLw5-m{V$-5 z{uKatT*|5d9P^s?R?_rwSHzG0l7q2$1|J!dsV{5I?!{2}~!~_h&Mm zW!;59Y(8crd@>Qp^fojyQjemUgq3g4g?^I)FwRg|Ibc1X0?AD5LKBA*=qv|B9h*q8$7!efQOih;=n^N|byR^qnC2it(+>X%gSm zTu~lE^irY;-Xta_dBT=;>HugFVX#1|GHJD8CE~^XR<-X@XVPyse`AlI6UGda?5--5{Z9Cax7y#m0Y2I<=|F1QUZ zs&)pr)Bw45|uz3mP&RwMCTrO!My+rU#7bOu)i-rW5+;<<$=n=3|{g9C5TRx zg{wTaAEr44CX$U!tsTN!RE#ro<&#*SnTuS?C)03B1p_dC%Yne7$>G4kFX2yPS_CD& z4bXnE4x&ul3XTyR#{=2mR>*=AnAC!GhJ-l^<^#*^LFN>F7cL3%h@ZnE*0%s1twJQ= z$DB{ye6@lPhiOG&D_p=P8zGpOw)toFeLvHx(5YGqgD=n&bRi!y7X~waDw(iqb1c@% z#>-N$2$SL`p(HuM~vigx=B-9*$uz^Jc8l-$8-8UL~7U|l$K!|REgV0m>>^HxomIGsQwo~-nSXqtwt`hGq$`dHL>p{EAQ<+*uHkkrZiy+7GpL)c z;ix*2AMk8Nqa3aY)7X9{8%ao#EFKCW9~YA7AAp;|a25ohw!uEE_|Oz?b?O*=Y~U`u zLn0}ZIzCpN0>EW~d57Gd+ky-7(6xYPcA99sNu6PPEDR`ULsMHMlK~2rG0d;pi4L zSL&Z?puE%Yd?S5lwIch0Z$Qqew6WPK3`bxXwYS}IvHw0A5kYjb)jIg5Qzq(e0ie%h zln|1J1c5PV_JE*2)m=!kWHR#(t>7R9o`5eV3JZhlLlCNN`8}}zIhYG35sPyZRL?nG zZUz5HOl9qsvJRNOl#c*~gP79Ax>qsg!)FeLAuX^;n5>RZo~(>FVbSQbC>tMgAswC4 z8H-I|?6fWkL(%AB#G6=bH1{DzLQCN$?d_sP7}|WR%S93|WQGv$bLK+=_TA^?!UYBV z`@{mKozFR3bp=VgcyEMNpd3kdD7WXvsa^rO7R{mYS6uZ|nxkhTIGa_V)lrBJ2Q!yQ z7XwcqSETTJ#?2Xu`A>Yg#!YjI{+Fm|e}zxPrmZ1?BZ>8Bsw62Z>`(*5n3$NYyAk=! ziYO<;pC@A#)6`|!jrK^6q|s=qN3;fAbI|b}O$@HvhyXO}0NP@Mf)AFL-bFMXN>lV8 zMg*w+kXSe3vxKx~h=H$;>S@PfV&Fr3(IC$;&Vlq>;5XU^l-z~hW)}cm9bh_a8&Zej zgt`hTK4E;sm0L87|M_jC5W9TZzSU4~SRz-)xx^i&r_YQFE_t9)LKczYAqyyJlA5F$-EJ<0dPE)vB z`LbGxTA-1@{0ND2DZit1oTf|%Qo&@RGPwHCXQdNcfw?f}vVl#XJFZbQ?RPe7e| zh(v;>PDOu-kN6!LxJ9GqUU=G@ex&!_^j`kxhEdZXp{GxzT`;9+H*xf&JuE{QRyq*j zM)V4YV8Rm=7!S>^#}b+brzHe`F4j zgmSbn=(lJn$S826J9B^20iiGm2kn&OT!1)k10YVtkF0Rly8vB9MiU>$fFz7&6q9hPfP{MnNK$mnpmC^uQ&>v`NaUJ;B=6He)7bSj zAklXcNOGtNwN>=>0Z9(M03=$jfK8zIJq%RMT5=ca9aAF+K*CsK(EC7NX1Qgk`&ThK z0W_Ua1uC5AtAa@|Dn3#^2iYh-o&<_8cK~Pxqc5RVh};%~#-Y`Sk57Tb^%7*faD70c zBKtR)oImfN2LlJrJ^#P7`pZ9=joU#P{3YaIy5-4-CpmmfgItN_LS0Fb1p2AOva zb9q!MsaI3Un=_r%@dwwAXo_y4`Wtxm64EmL|!{4^Crzk<-n z6hgIEh@P6_N;O1K*dVGu!ciUzsy2vfrf_8}o|V~|*XkLM13rR*Fl~TmP0d^kGa#we8FDL?)%Ig|%q1)k@ypk}cS)vgyn!Ez}WE z(oNVPM_IsX>(Z7~x+9mcldUc345U21#5h~)rd@E$sP+n}fLffbcuH+xWfNqMr?nbM zQt@HB!^*EtCoOkY_|1F8jMs18OS=-QorwQM^Io6bE?Zx>COS@=TryZk#wD|vjFq%q zCfnHz8Eco2uI-3fbKXi?>D88aE42w`38*PmuQoDv?L|zH$f3YQkJ*Np3q(nKLSoH# z6s&^fD{E1~Wv~LZsxzIkTT3kRmB!9Q+MQm1?I~nR>!_}AD$`18+nJ;}<61b%ZY7e2 zMDq4bwzJJjt+Lutk0c2mOKl2JWJ_Cw2y=#LBl|!hlU}23t;0CZW^X-fMh%JKAbaZ& zKei6zcMPMEzWtw=F8K-n4|Z=Zw-3`ZCE-S~U(?zCzxm%mIERvMRsR0m?JD@c)t{3# z{k}uFU2d%ndUtj`M_sTpow<6Zt9Z{=y0d3rJIg6u>eI3E8^(SYG4fl}AH<6rS<~0j zOCgRZ&i|xzl^RlIj;=D+^l!uURLRQ-Tgm5A2|HoD1};S0j^H2V^LQ7dVVd7m4tP>11>MLyTTN;5vy34%ciWx zKgo-ZX(K7!B=6pBglDH0_lwBXBSkHQjcZw-?A8$Jp?`0IuDKCfm=>$f(gMVo1B4;? z&mg{CbFQk~Kx^v7dcTUT$uX~@>&{@L(9|^-JgvF|FW@2HbyqM_xnU#eB0p@<=JtbC z?>QWdb6o>}*kfWgVjRJ|OUjZ4(sV0KkPt#WO`OKpL7B#0H;kLgjhyKiG?>Ec9)HLJ zuf?Xq2nV0tzWWaDiyv;qC&|V$GITg-RX~XMb$)@2w6W zvp*F1Y$@`2I`UNCPg~YwC=gTDrzoyPr>uu?AKkR~_!2IVDD<4EoPSHcO*wC4T~0Y~ z!F}-))SIKuu0v-mr@W4rjj=%;r?t&gSN^Q)%3hwE18b4f$E)*76{k1tFxE4m@`iUR z@3%(pih`M5 zE*M?qkdB_Q?kE;^OSr!`&$F-71FMEZieX&tG6aTl=gO@oiqPa?T1>&d2-_p6rHYBW ztY-v%tN5W@znz}lFcFu~XbSe`ew5GOXz}}+Z+;7jmikY@MbUJiyqdkBK4PCc+!>$t z#TX}c0US0{V4J$i%{ws` z*}XHU9{1{b*gX_$K(gZ9C&yH7T#Yu-*gKMnpm=C;GSt@?>>nPa*$9Y#nCM27RJ@+e z+-4k-;Q4vw&4HLnKav)w}1JI4x_GXok6PNH!Y74mP`1V)bem z)QOYwR2Yoy-GyR?H7hk{7IWf9?N7wLd5l&k{dV`?hYMqz_b;cht*6X!tn+d->71TB zFm7lpzJe4rlRG_k=5cOQ7L#dqoKm}j+$bOUu9fa|df?bIqOtf698>D4bP^cZ(OA`7 zk5#_mJxU(%v?5$1xLGEz(v4^;?nGuaQ?ZOivU6gpvP;}b%R{@aWb4En=AO{o0V-v@ z5#|TQeK72y;Flq|WvFR^#h4d!Q)R=;xL-~o;wq^{M5mCySJpfgT%M6i=!N=6N2&TJq|h8Sj82y2qg-$13vj77ga5?r{|-RImF2hNXk&Vyq)r69`7| z4LcMJgyz$?Y!}ue%e0#M(h-r0creGS#k=v$GCZy7zM>)$j6)ac$Oj`n{r9SH=<3!_ ze(zO&-EpGP?L)4W=}GS)rI&P!9TgZG>BjOdg;1VPwa2x&Lh~vB)CyhUJ;iSJP zG^){u^Jh3G;M|6DKh7}DgE;#tWUPp;_adWb5r^whoKfIGTyMc2sz%^AuKRFO^Ers? z1?Wstz+qe)aJB(2!Ijq3vVn8BR$%;L4e)qeH{k37?!$E=Mqf7phjINL&dtDEaNU7( z3vfTKzr(o|co5eo@lWXq;BB~W9E<;Q9&@egK$`Q|I8nGQvNFGYs5- zlh$#480S8mlMsiGaor8~vwcOOJe-OXW7_z=lF?H9PGfXCeybUE8m=1%gCg)jV=#gq z2Eu?PxMvMFV7NDdCbMe?s^)w~y9~M*r6XLEK|ckO@mE^K={fpe=27O}C0+gk= z{sd?`hw(Jf0!A+aH88>^2Wo_M0+6^?1Bq(`kobKNXa@WEC6J874gp=wTm^ELYEvi> z0g{o^l|V8Mx&df1yFLXZW1h!=D2GFVF(?_~t~BTdgYpLb(4d_Ly>Ad1m(O8+YYo~B zBys*D5RI$J3h&o3nva@R%cvPhV&7@Fp99sg>+3-C7*(S_ioVrAUt#V6AkjAwwKB?F z9gyf-3M8&P^PgSsLd~sb^b4ScjGi?Jw}FW`W$acEA|JSvIfyCO@diu8pGXVP?vFCZ@5hc-Dh0;47b^!hm7mPhTCG$qsDcs;hr#Pn{j={aN7-f-njM~ zZnr`EjOziz9W>~$aXn(Vw+$LJuEz}bonXzpg1V&9QK|Ah*KlNm zE%ji$aSbWIGUiUMA?}p~jW@0r8Z^P6Nd|=tsxoMbLDdHNzjDi$;v^*8hyL;$3}D<3 zaV>Yy6FQEQWmte(cbA8`9(R3)p+TGZi4F?{TBw|uT?Vubr=y2rbI`-wgFEVX9hnD+ zLFPHk<6s6o%m5hbi5;0kV5mQKnD@a@uk0|Nf}y_IVW_mIhjy4Nz)(N!Ff?jSy|u&8 z!j{x$I}D9cQ_t-%cY&e)+hOhpL%q1e{1^=NJFw~hGgSmvj7k z;}po=lEXjmfLfRL@XtG-@ALHRb`Jl%g9sdvq>|$;_@o0}SJ^QUa{H5#A&g3c!x^8~ z%z5I#t^&W{V`li61wQ5$AG6BG-0fpF`WRgz+`Uwq6z ze9U>&K-D3Rm-(3KK4zYeapnT(bj5u#c@NWJ=k08p4m*qz1k+|^+)Viy4Yd4;9OUah z+_{Ftc%V3)N!#f}8zu(Gm}tr>Wq4FLk5S=lJLVu*YRZ9kxS8)dEHwz5NTo1x+W~32 zg?*=UqP*0>%dR?|xONb6H4wcbt!Fv`bT*|}h%IQd5*d%!!c58|L|(7A>}9wpZ^#uY z*A;hW5{64>5I@Y#NR-M@nJA8?Xy{eix%?!uP)CVzDRa;9P~|70l4zw4IYRd+A4R0I zNxRJ>qEtH~y5vkYA!Ut;4trrfpViS+C8C We74=hlS{3o1x||^7I!qEq5KoBp@tv; From 73959329f7504420398f3ebcc3ea4382bce138af Mon Sep 17 00:00:00 2001 From: x64-dev <202863051+x64-dev@users.noreply.github.com> Date: Wed, 3 Sep 2025 13:57:10 -0400 Subject: [PATCH 4/9] On mismatch UI, only show players who mismatched, since the UI isnt big enough to show every user --- .../Source/GameLogic/System/GameLogic.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 1e7c857c071..5bc52ea2c8c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -2682,7 +2682,7 @@ void GameLogic::processCommandList( CommandList *list ) #if defined(GENERALS_ONLINE) // provide more details UnicodeString strMismatchDetails; - strMismatchDetails.format(L"GameLogic frame %d, latest frame %d, GetGameLogicRandomSeedCRC was %d\nHad %d CRCs from %d players\nNum RNG Calls %llu\nAll Player CRCs:\n", + strMismatchDetails.format(L"GameLogic frame %d, latest frame %d, GetGameLogicRandomSeedCRC was %d\nHad %d CRCs from %d players\nNum RNG Calls %llu\nMismatched Players:\n", TheGameLogic->getFrame(), TheGameLogic->getFrame() - TheNetwork->getRunAhead() - 1, GetGameLogicRandomSeedCRC(), @@ -2721,12 +2721,15 @@ void GameLogic::processCommandList( CommandList *list ) // show all players for (std::map::const_iterator crcIt = m_cachedCRCs.begin(); crcIt != m_cachedCRCs.end(); ++crcIt) { - Player* player = ThePlayerList->getNthPlayer(crcIt->first); - UnicodeString strPlayerInfo; - strPlayerInfo.format(L"player %d (%s) = %X [%s]\n", crcIt->first, player ? player->getPlayerDisplayName().str() : L"", crcIt->second, - crcIt->second == biggestCRC ? L"OK" : L"MISMATCH"); + // only show users who arent OK, UI isn't huge + if (crcIt->second != biggestCRC) + { + Player* player = ThePlayerList->getNthPlayer(crcIt->first); + UnicodeString strPlayerInfo; + strPlayerInfo.format(L"player %d (%s) = %X [MISMATCH]\n", crcIt->first, player ? player->getPlayerDisplayName().str() : L"", crcIt->second); - strMismatchDetails.concat(strPlayerInfo); + strMismatchDetails.concat(strPlayerInfo); + } } // TODO_NGMP: Handle missing CRCs, although that doesnt seem common From c7c885d9fb70560b0317e6c395497a3c57160505 Mon Sep 17 00:00:00 2001 From: x64-dev <202863051+x64-dev@users.noreply.github.com> Date: Thu, 4 Sep 2025 18:14:47 -0400 Subject: [PATCH 5/9] - Improved handling of websocket JSON data --- .../GeneralsOnline/OnlineServices_Init.h | 3 +- .../OnlineServices_RoomsInterface.cpp | 515 ++++++++++-------- 2 files changed, 288 insertions(+), 230 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h index 7844eba4455..e14b10f687b 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h @@ -36,7 +36,7 @@ enum EWebSocketMessageID NETWORK_ROOM_MARK_READY = 5, LOBBY_CURRENT_LOBBY_UPDATE = 6, NETWORK_ROOM_LOBBY_LIST_UPDATE = 7, - PLAYER_CONNECTION_RELAY_UPGRADE = 8, + UNUSED_PLACEHOLDER = 8, // this was relay upgrade, was removed. We can re-use it later, but service needs this placeholder PLAYER_NAME_CHANGE = 9, LOBBY_ROOM_CHAT_FROM_CLIENT = 10, LOBBY_CHAT_FROM_SERVER = 11, @@ -162,7 +162,6 @@ class WebSocket void SendData_JoinNetworkRoom(int roomID); void SendData_LeaveNetworkRoom(); void SendData_MarkReady(bool bReady); - void SendData_ConnectionRelayUpgrade(int64_t userID); void SendData_RequestSignalling(int64_t targetUserID); void SendData_Signalling(int64_t targetUserID, std::vector vecPayload); diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp index 008a0d8922a..891cb47f739 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp @@ -22,7 +22,7 @@ int WebSocket::Ping() { size_t sent; CURLcode result = curl_ws_send(m_pCurl, "wsping", strlen("wsping"), &sent, 0, - CURLWS_PING); + CURLWS_PING); nlohmann::json j; j["msg_id"] = EWebSocketMessageID::PING; @@ -109,17 +109,6 @@ void WebSocket::SendData_RoomChatMessage(UnicodeString& msg, bool bIsAction) Send(strBody.c_str()); } -void WebSocket::SendData_ConnectionRelayUpgrade(int64_t userID) -{ - nlohmann::json j; - j["msg_id"] = EWebSocketMessageID::PLAYER_CONNECTION_RELAY_UPGRADE; - j["target_user_id"] = userID; - - std::string strBody = j.dump(); - - Send(strBody.c_str()); -} - void WebSocket::SendData_MarkReady(bool bReady) { nlohmann::json j; @@ -173,7 +162,7 @@ void WebSocket::Send(const char* send_payload) size_t sent; CURLcode result = curl_ws_send(m_pCurl, send_payload, strlen(send_payload), &sent, 0, - CURLWS_BINARY); + CURLWS_BINARY); if (result != CURLE_OK) { @@ -247,14 +236,6 @@ class WebSocketMessage_LobbyChatIncoming : public WebSocketMessageBase NLOHMANN_DEFINE_TYPE_INTRUSIVE(WebSocketMessage_LobbyChatIncoming, msg_id, message, action, announcement, show_announcement_to_host, user_id) }; -class WebSocketMessage_RelayUpgrade : public WebSocketMessageBase -{ -public: - int64_t target_user_id = -1; - - NLOHMANN_DEFINE_TYPE_INTRUSIVE(WebSocketMessage_RelayUpgrade, msg_id, target_user_id) -}; - class WebSocketMessage_MatchmakingMessage : public WebSocketMessageBase { public: @@ -272,6 +253,52 @@ class WebSocketMessage_NetworkRoomMemberListUpdate : public WebSocketMessageBase NLOHMANN_DEFINE_TYPE_INTRUSIVE(WebSocketMessage_NetworkRoomMemberListUpdate, names, ids) }; +static bool JSONDeserialize(const char* szBuffer, nlohmann::json* jsonObject) +{ + try + { + *jsonObject = nlohmann::json::parse(szBuffer); + return true; + } + catch (nlohmann::json::exception& jsonException) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "JSONDeserialize: Unparsable JSON: %s (%s)", szBuffer, jsonException.what()); + return false; + } + catch (...) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "JSONDeserialize: Unparsable JSON: %s", szBuffer); + return false; + } + + return false; +} + +template +static bool JSONGetAsObject(nlohmann::json& jsonObject, T* outMsg) +{ + try + { + *outMsg = jsonObject.get(); + + return true; + } + catch (nlohmann::json::exception& jsonException) + { + std::string targetTypeName = typeid(T).name(); + NetworkLog(ELogVerbosity::LOG_RELEASE, "JSONGetAsObject: Unparsable JSON: Target Type is %s (%s)", targetTypeName.c_str(), jsonException.what()); + return false; + } + catch (...) + { + std::string targetTypeName = typeid(T).name(); + NetworkLog(ELogVerbosity::LOG_RELEASE, "JSONGetAsObject: Unparsable JSON: Target Type is %s", targetTypeName.c_str()); + return false; + } + + return false; +} + //static std::string strSignal = "str:1 "; void WebSocket::Tick() { @@ -316,9 +343,8 @@ void WebSocket::Tick() char bufferThisRecv[8196 * 4] = { 0 }; CURLcode ret = CURL_LAST; - ret = curl_ws_recv(m_pCurl, bufferThisRecv, sizeof(bufferThisRecv) - 1, &rlen, &meta); - bufferThisRecv[rlen] = 0; // Ensure null-termination - + ret = curl_ws_recv(m_pCurl, bufferThisRecv, sizeof(bufferThisRecv), &rlen, &meta); + if (ret != CURLE_RECV_ERROR && ret != CURL_LAST && ret != CURLE_AGAIN && ret != CURLE_GOT_NOTHING) { NetworkLog(ELogVerbosity::LOG_DEBUG, "Got websocket msg: %s", bufferThisRecv); @@ -334,8 +360,6 @@ void WebSocket::Tick() } else if (meta->flags & CURLWS_TEXT) { - NetworkLog(ELogVerbosity::LOG_DEBUG, "websocket recv buffer is: %s", bufferThisRecv); - bool bMessageComplete = false; m_vecWSPartialBuffer.resize(m_vecWSPartialBuffer.size() + rlen); @@ -362,256 +386,291 @@ void WebSocket::Tick() { try { + // null terminate buffer + m_vecWSPartialBuffer[m_vecWSPartialBuffer.size()] = '\0'; + // process it - nlohmann::json jsonObject = nlohmann::json::parse(m_vecWSPartialBuffer); + nlohmann::json jsonObject; + bool bDeserializedOK = JSONDeserialize(m_vecWSPartialBuffer.data(), &jsonObject); // clear buffer and resize m_vecWSPartialBuffer.clear(); m_vecWSPartialBuffer.resize(0); - - if (jsonObject.contains("msg_id")) + if (bDeserializedOK) { - WebSocketMessageBase msgDetails = jsonObject.get(); - EWebSocketMessageID msgID = msgDetails.msg_id; - - switch (msgID) - { - - case EWebSocketMessageID::PONG: - { - int64_t currTime = std::chrono::duration_cast(std::chrono::utc_clock::now().time_since_epoch()).count(); - m_lastPong = currTime; - } - break; - - case EWebSocketMessageID::NETWORK_ROOM_CHAT_FROM_SERVER: + if (jsonObject.contains("msg_id")) { - WebSocketMessage_RoomChatIncoming chatData = jsonObject.get(); + WebSocketMessageBase msgDetails; + bool bParsedBase = JSONGetAsObject(jsonObject, &msgDetails); - UnicodeString unicodeStr(from_utf8(chatData.message).c_str()); - - Color color = DetermineColorForChatMessage(EChatMessageType::CHAT_MESSAGE_TYPE_NETWORK_ROOM, true, chatData.action); - - NGMP_OnlineServices_RoomsInterface* pRoomsInterface = NGMP_OnlineServicesManager::GetInterface(); - if (pRoomsInterface != nullptr && pRoomsInterface->m_OnChatCallback != nullptr) + if (bParsedBase) { - pRoomsInterface->m_OnChatCallback(unicodeStr, color); - } - } - break; + EWebSocketMessageID msgID = msgDetails.msg_id; - case EWebSocketMessageID::START_GAME: - { - NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); - if (pLobbyInterface != nullptr && pLobbyInterface->m_callbackStartGamePacket != nullptr) - { - pLobbyInterface->m_callbackStartGamePacket(); - } - } - break; + switch (msgID) + { + + case EWebSocketMessageID::PONG: + { + int64_t currTime = std::chrono::duration_cast(std::chrono::utc_clock::now().time_since_epoch()).count(); + m_lastPong = currTime; + } + break; + case EWebSocketMessageID::NETWORK_ROOM_CHAT_FROM_SERVER: + { + WebSocketMessage_RoomChatIncoming chatData; + bool bParsed = JSONGetAsObject(jsonObject, &chatData); - case EWebSocketMessageID::NETWORK_CONNECTION_START_SIGNALLING: - { - WebSocketMessage_NetworkStartSignalling startSignallingData = jsonObject.get(); + if (bParsed) + { + UnicodeString unicodeStr(from_utf8(chatData.message).c_str()); - NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); - if (pLobbyInterface != nullptr) - { - NetworkMesh* pMesh = pLobbyInterface->GetNetworkMeshForLobby(); + Color color = DetermineColorForChatMessage(EChatMessageType::CHAT_MESSAGE_TYPE_NETWORK_ROOM, true, chatData.action); - if (pMesh != nullptr) - { - pMesh->StartConnectionSignalling(startSignallingData.user_id, startSignallingData.preferred_port); + NGMP_OnlineServices_RoomsInterface* pRoomsInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pRoomsInterface != nullptr && pRoomsInterface->m_OnChatCallback != nullptr) + { + pRoomsInterface->m_OnChatCallback(unicodeStr, color); + } + } } - else + break; + + case EWebSocketMessageID::START_GAME: { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[NETWORK_CONNECTION_START_SIGNALLING] Network mesh is null"); - break; + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pLobbyInterface != nullptr && pLobbyInterface->m_callbackStartGamePacket != nullptr) + { + pLobbyInterface->m_callbackStartGamePacket(); + } } - } - else - { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[NETWORK_CONNECTION_START_SIGNALLING] Lobby interface is null"); break; - } - } - break; - case EWebSocketMessageID::NETWORK_CONNECTION_DISCONNECT_PLAYER: - { - WebSocketMessage_NetworkDisconnectPlayer disconnectPlayerData = jsonObject.get(); - NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); - if (pLobbyInterface != nullptr) - { - int64_t currentLobbyID = pLobbyInterface->GetCurrentLobby().lobbyID; - - if (currentLobbyID == -1 || currentLobbyID != disconnectPlayerData.lobby_id) + case EWebSocketMessageID::NETWORK_CONNECTION_START_SIGNALLING: { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[NETWORK_CONNECTION_DISCONNECT_PLAYER] Lobby ID mismatch! Expected %lld, got %lld", currentLobbyID, disconnectPlayerData.lobby_id); - break; - } + WebSocketMessage_NetworkStartSignalling startSignallingData; + bool bParsed = JSONGetAsObject(jsonObject, &startSignallingData); - NetworkMesh* pMesh = pLobbyInterface->GetNetworkMeshForLobby(); + if (bParsed) + { + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pLobbyInterface != nullptr) + { + NetworkMesh* pMesh = pLobbyInterface->GetNetworkMeshForLobby(); + + if (pMesh != nullptr) + { + pMesh->StartConnectionSignalling(startSignallingData.user_id, startSignallingData.preferred_port); + } + else + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[NETWORK_CONNECTION_START_SIGNALLING] Network mesh is null"); + break; + } + } + else + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[NETWORK_CONNECTION_START_SIGNALLING] Lobby interface is null"); + break; + } + } + } + break; - if (pMesh != nullptr) + case EWebSocketMessageID::NETWORK_CONNECTION_DISCONNECT_PLAYER: { - pMesh->DisconnectUser(disconnectPlayerData.user_id); + WebSocketMessage_NetworkDisconnectPlayer disconnectPlayerData; + bool bParsed = JSONGetAsObject(jsonObject, &disconnectPlayerData); + + if (bParsed) + { + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pLobbyInterface != nullptr) + { + int64_t currentLobbyID = pLobbyInterface->GetCurrentLobby().lobbyID; + + if (currentLobbyID == -1 || currentLobbyID != disconnectPlayerData.lobby_id) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[NETWORK_CONNECTION_DISCONNECT_PLAYER] Lobby ID mismatch! Expected %lld, got %lld", currentLobbyID, disconnectPlayerData.lobby_id); + break; + } + + NetworkMesh* pMesh = pLobbyInterface->GetNetworkMeshForLobby(); + + if (pMesh != nullptr) + { + pMesh->DisconnectUser(disconnectPlayerData.user_id); + } + else + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[NETWORK_CONNECTION_DISCONNECT_PLAYER] Network mesh is null"); + break; + } + } + else + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[NETWORK_CONNECTION_DISCONNECT_PLAYER] Lobby interface is null"); + break; + } + } } - else + break; + + case EWebSocketMessageID::NETWORK_SIGNAL: { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[NETWORK_CONNECTION_DISCONNECT_PLAYER] Network mesh is null"); - break; + NetworkLog(ELogVerbosity::LOG_RELEASE, "[SIGNAL] GOT SIGNAL!"); + + WebSocketMessage_NetworkSignal signalData; + bool bParsed = JSONGetAsObject(jsonObject, &signalData); + + if (bParsed) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[SIGNAL] Signal User: %lld!", signalData.target_user_id); + NetworkLog(ELogVerbosity::LOG_RELEASE, "[SIGNAL] Signal Payload Size: %d!", (int)signalData.payload.size()); + m_pendingSignals.push(signalData.payload); + } } - } - else - { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[NETWORK_CONNECTION_DISCONNECT_PLAYER] Lobby interface is null"); break; - } - } - break; - case EWebSocketMessageID::NETWORK_SIGNAL: - { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[SIGNAL] GOT SIGNAL!"); - WebSocketMessage_NetworkSignal signalData = jsonObject.get(); + case EWebSocketMessageID::LOBBY_CHAT_FROM_SERVER: + { + WebSocketMessage_LobbyChatIncoming chatData; + bool bParsed = JSONGetAsObject(jsonObject, &chatData); - NetworkLog(ELogVerbosity::LOG_RELEASE, "[SIGNAL] Signal User: %lld!", signalData.target_user_id); - NetworkLog(ELogVerbosity::LOG_RELEASE, "[SIGNAL] Signal Payload Size: %d!", (int)signalData.payload.size()); - m_pendingSignals.push(signalData.payload); - } - break; + if (bParsed) + { + UnicodeString unicodeStr(from_utf8(chatData.message).c_str()); + + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pLobbyInterface != nullptr) + { + int lobbySlot = -1; + auto lobbyMembers = pLobbyInterface->GetMembersListForCurrentRoom(); + for (const auto& lobbyMember : lobbyMembers) + { + if (lobbyMember.user_id == chatData.user_id) + { + lobbySlot = lobbyMember.m_SlotIndex; + break; + } + } + + Color color = DetermineColorForChatMessage(EChatMessageType::CHAT_MESSAGE_TYPE_LOBBY, true, chatData.action, lobbySlot); + + if (pLobbyInterface->m_OnChatCallback != nullptr) + { + pLobbyInterface->m_OnChatCallback(unicodeStr, color); + } + } + } + } + break; - case EWebSocketMessageID::LOBBY_CHAT_FROM_SERVER: - { - WebSocketMessage_LobbyChatIncoming chatData = jsonObject.get(); + case EWebSocketMessageID::NETWORK_ROOM_MEMBER_LIST_UPDATE: + { + WebSocketMessage_NetworkRoomMemberListUpdate memberList; + bool bParsed = JSONGetAsObject(jsonObject, &memberList); - UnicodeString unicodeStr(from_utf8(chatData.message).c_str()); + if (bParsed) + { + NGMP_OnlineServices_RoomsInterface* pRoomsInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pRoomsInterface != nullptr) + { + pRoomsInterface->OnRosterUpdated(memberList.names, memberList.ids); + } + } + } + break; - NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); - if (pLobbyInterface != nullptr) - { - int lobbySlot = -1; - auto lobbyMembers = pLobbyInterface->GetMembersListForCurrentRoom(); - for (const auto& lobbyMember : lobbyMembers) + case EWebSocketMessageID::LOBBY_CURRENT_LOBBY_UPDATE: { - if (lobbyMember.user_id == chatData.user_id) + // re-get the room info as it is stale + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pLobbyInterface != nullptr) { - lobbySlot = lobbyMember.m_SlotIndex; - break; + pLobbyInterface->UpdateRoomDataCache(nullptr); } } + break; - Color color = DetermineColorForChatMessage(EChatMessageType::CHAT_MESSAGE_TYPE_LOBBY, true, chatData.action, lobbySlot); - - if (pLobbyInterface->m_OnChatCallback != nullptr) + case EWebSocketMessageID::NETWORK_ROOM_LOBBY_LIST_UPDATE: { - pLobbyInterface->m_OnChatCallback(unicodeStr, color); + // re-get the room info as it is stale + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pLobbyInterface != nullptr) + { + pLobbyInterface->SetLobbyListDirty(); + } } - } - } - break; - - // TODO_STEAM: remove relay upgrade path from everything - case EWebSocketMessageID::PLAYER_CONNECTION_RELAY_UPGRADE: - { - WebSocketMessage_RelayUpgrade relayUpgrade = jsonObject.get(); - NetworkLog(ELogVerbosity::LOG_RELEASE, "Got relay upgrade for user %lld", relayUpgrade.target_user_id); - NetworkMesh* pMesh = NGMP_OnlineServicesManager::GetNetworkMesh(); - if (pMesh != nullptr) - { - // TODO_STEAM - //pMesh->OnRelayUpgrade(relayUpgrade.target_user_id); - } - } - break; - - case EWebSocketMessageID::NETWORK_ROOM_MEMBER_LIST_UPDATE: - { - WebSocketMessage_NetworkRoomMemberListUpdate memberList = jsonObject.get(); - NGMP_OnlineServices_RoomsInterface* pRoomsInterface = NGMP_OnlineServicesManager::GetInterface(); - if (pRoomsInterface != nullptr) - { - pRoomsInterface->OnRosterUpdated(memberList.names, memberList.ids); - } - } - break; - - case EWebSocketMessageID::LOBBY_CURRENT_LOBBY_UPDATE: - { - // re-get the room info as it is stale - NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); - if (pLobbyInterface != nullptr) - { - pLobbyInterface->UpdateRoomDataCache(nullptr); - } - } - break; + break; - case EWebSocketMessageID::NETWORK_ROOM_LOBBY_LIST_UPDATE: - { - // re-get the room info as it is stale - NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); - if (pLobbyInterface != nullptr) - { - pLobbyInterface->SetLobbyListDirty(); - } - } - break; + case EWebSocketMessageID::MATCHMAKING_ACTION_JOIN_PREARRANGED_LOBBY: + { + WebSocketMessage_MatchmakingAction_JoinPrearrangedLobby mmEvent; + bool bParsed = JSONGetAsObject(jsonObject, &mmEvent); - case EWebSocketMessageID::MATCHMAKING_ACTION_JOIN_PREARRANGED_LOBBY: - { - WebSocketMessage_MatchmakingAction_JoinPrearrangedLobby mmEvent = jsonObject.get(); + if (bParsed) + { + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pLobbyInterface != nullptr) + { + // TODO_QUICKMATCH: Only if really in quickmatch + + // basic info needed to join + LobbyEntry lobbyEntry; + lobbyEntry.lobbyID = mmEvent.lobby_id; + lobbyEntry.map_path = "Maps\\Homeland Alliance\\Homeland Alliance.map"; + + pLobbyInterface->JoinLobby(lobbyEntry, std::string()); + } + else + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "[NETWORK_CONNECTION_DISCONNECT_PLAYER] Lobby interface is null"); + break; + } + } + } + break; - NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); - if (pLobbyInterface != nullptr) - { - // TODO_QUICKMATCH: Only if really in quickmatch + case EWebSocketMessageID::MATCHMAKING_ACTION_START_GAME: + { + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pLobbyInterface != nullptr) + { + pLobbyInterface->InvokeMatchmakingStartGameCallback(); + } + } + break; - // basic info needed to join - LobbyEntry lobbyEntry; - lobbyEntry.lobbyID = mmEvent.lobby_id; - lobbyEntry.map_path = "Maps\\Homeland Alliance\\Homeland Alliance.map"; + case EWebSocketMessageID::MATCHMAKING_MESSAGE: + { + WebSocketMessage_MatchmakingMessage matchmakingMsg; + bool bParsed = JSONGetAsObject(jsonObject, &matchmakingMsg); - pLobbyInterface->JoinLobby(lobbyEntry, std::string()); - } - else - { - NetworkLog(ELogVerbosity::LOG_RELEASE, "[NETWORK_CONNECTION_DISCONNECT_PLAYER] Lobby interface is null"); + if (bParsed) + { + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pLobbyInterface != nullptr) + { + pLobbyInterface->InvokeMatchmakingMessageCallback(matchmakingMsg.message); + } + } + } break; - } - } - break; - case EWebSocketMessageID::MATCHMAKING_ACTION_START_GAME: - { - NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); - if (pLobbyInterface != nullptr) - { - pLobbyInterface->InvokeMatchmakingStartGameCallback(); + default: + NetworkLog(ELogVerbosity::LOG_RELEASE, "Unhandled WebSocketMessage: %d", (int)msgID); + break; + } } - } - break; - - case EWebSocketMessageID::MATCHMAKING_MESSAGE: - { - WebSocketMessage_MatchmakingMessage matchmakingMsg = jsonObject.get(); - - NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); - if (pLobbyInterface != nullptr) + else { - pLobbyInterface->InvokeMatchmakingMessageCallback(matchmakingMsg.message); + NetworkLog(ELogVerbosity::LOG_RELEASE, "Malformed WebSocketMessage: couldn't parse as WebSocketMessageBase"); } } - break; - - default: - NetworkLog(ELogVerbosity::LOG_RELEASE, "Unhandled WebSocketMessage: %d", (int)msgID); - break; - } } else { @@ -693,7 +752,7 @@ void WebSocket::Tick() NGMP_OnlineServices_RoomsInterface::NGMP_OnlineServices_RoomsInterface() { - + } void NGMP_OnlineServices_RoomsInterface::GetRoomList(std::function cb) @@ -715,7 +774,7 @@ void NGMP_OnlineServices_RoomsInterface::GetRoomList(std::function c std::string strName; ERoomFlags flags; - + roomEntryIter["id"].get_to(id); roomEntryIter["name"].get_to(strName); roomEntryIter["flags"].get_to(flags); @@ -768,7 +827,7 @@ void NGMP_OnlineServices_RoomsInterface::JoinRoom(int roomIndex, std::function Date: Thu, 4 Sep 2025 18:46:30 -0400 Subject: [PATCH 6/9] - Fixed a bug where stats could be interpreted correctly resulting in high ranks being granted early --- .../OnlineServices_StatsInterface.h | 1 + .../GUICallbacks/Menus/PopupPlayerInfo.cpp | 16 --------- .../OnlineServices_RoomsInterface.cpp | 2 +- .../OnlineServices_StatsInterface.cpp | 35 ++++++++++++++++++- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_StatsInterface.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_StatsInterface.h index 56e0a99054d..e26e98f282e 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_StatsInterface.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_StatsInterface.h @@ -438,6 +438,7 @@ class NGMP_OnlineServices_StatsInterface { public: NGMP_OnlineServices_StatsInterface(); + ~NGMP_OnlineServices_StatsInterface(); void GetGlobalStats(std::function cb); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupPlayerInfo.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupPlayerInfo.cpp index c63806e5678..617d031f8c7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupPlayerInfo.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/PopupPlayerInfo.cpp @@ -246,22 +246,6 @@ void GetAdditionalDisconnectsFromUserFile(PSPlayerStats *stats) // default values RankPoints::RankPoints(void) { - NGMP_OnlineServices_StatsInterface* statsInterface = NGMP_OnlineServicesManager::GetInterface(); - - if (statsInterface != nullptr) - { - m_ranks[RANK_PRIVATE] = 0; - m_ranks[RANK_CORPORAL] = statsInterface->getPointsForRank(RANK_CORPORAL); // 5 - m_ranks[RANK_SERGEANT] = statsInterface->getPointsForRank(RANK_SERGEANT); // 10 - m_ranks[RANK_LIEUTENANT] = statsInterface->getPointsForRank(RANK_LIEUTENANT); // 20 - m_ranks[RANK_CAPTAIN] = statsInterface->getPointsForRank(RANK_CAPTAIN); // 50 - m_ranks[RANK_MAJOR] = statsInterface->getPointsForRank(RANK_MAJOR); // 100 - m_ranks[RANK_COLONEL] = statsInterface->getPointsForRank(RANK_COLONEL); // 200 - m_ranks[RANK_BRIGADIER_GENERAL] = statsInterface->getPointsForRank(RANK_BRIGADIER_GENERAL); // 500 - m_ranks[RANK_GENERAL] = statsInterface->getPointsForRank(RANK_GENERAL); // 1000 - m_ranks[RANK_COMMANDER_IN_CHIEF] = statsInterface->getPointsForRank(RANK_COMMANDER_IN_CHIEF); // 2000 - } - m_winMultiplier = 3.0f; m_lostMultiplier = 0.0f; m_hourSpentOnlineMultiplier = 1.0f; diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp index 891cb47f739..17ff4a84df4 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp @@ -387,7 +387,7 @@ void WebSocket::Tick() try { // null terminate buffer - m_vecWSPartialBuffer[m_vecWSPartialBuffer.size()] = '\0'; + m_vecWSPartialBuffer.push_back('\0'); // process it nlohmann::json jsonObject; diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_StatsInterface.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_StatsInterface.cpp index bf96f27effa..b0d3319899d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_StatsInterface.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_StatsInterface.cpp @@ -12,10 +12,38 @@ NGMP_OnlineServices_StatsInterface::NGMP_OnlineServices_StatsInterface() { TheRankPointValues = NEW RankPoints; + // populate ranks + // TODO_NGMP: Perhaps get this from the service? + TheRankPointValues->m_ranks[RANK_PRIVATE] = 0; + TheRankPointValues->m_ranks[RANK_CORPORAL] = getPointsForRank(RANK_CORPORAL); // 5 + TheRankPointValues->m_ranks[RANK_SERGEANT] = getPointsForRank(RANK_SERGEANT); // 10 + TheRankPointValues->m_ranks[RANK_LIEUTENANT] = getPointsForRank(RANK_LIEUTENANT); // 20 + TheRankPointValues->m_ranks[RANK_CAPTAIN] = getPointsForRank(RANK_CAPTAIN); // 50 + TheRankPointValues->m_ranks[RANK_MAJOR] = getPointsForRank(RANK_MAJOR); // 100 + TheRankPointValues->m_ranks[RANK_COLONEL] = getPointsForRank(RANK_COLONEL); // 200 + TheRankPointValues->m_ranks[RANK_BRIGADIER_GENERAL] = getPointsForRank(RANK_BRIGADIER_GENERAL); // 500 + TheRankPointValues->m_ranks[RANK_GENERAL] = getPointsForRank(RANK_GENERAL); // 1000 + TheRankPointValues->m_ranks[RANK_COMMANDER_IN_CHIEF] = getPointsForRank(RANK_COMMANDER_IN_CHIEF); // 2000 + // TODO_NGMP: Better location TheLadderList = NEW LadderList; } +NGMP_OnlineServices_StatsInterface::~NGMP_OnlineServices_StatsInterface() +{ + if (TheRankPointValues != nullptr) + { + delete TheRankPointValues; + TheRankPointValues = nullptr; + } + + if (TheLadderList != nullptr) + { + delete TheLadderList; + TheLadderList = nullptr; + } +} + void NGMP_OnlineServices_StatsInterface::GetGlobalStats(std::function cb) { std::string strURI = NGMP_OnlineServicesManager::GetAPIEndpoint("GlobalStats"); @@ -169,9 +197,14 @@ void NGMP_OnlineServices_StatsInterface::findPlayerStatsByID(int64_t userID, std // cb cb(true, stats); } + catch (nlohmann::json::exception& jsonException) + { + NetworkLog(ELogVerbosity::LOG_RELEASE, "Stats: Unparsable JSON 1: %s (%s)", strBody.c_str(), jsonException.what()); + cb(false, stats); + } catch (...) { - // cb + NetworkLog(ELogVerbosity::LOG_RELEASE, "Stats: Unparsable JSON 2: %s", strBody.c_str()); cb(false, stats); } }); From c1d6aeb8070989118baea74f0bc719b3a517e9c9 Mon Sep 17 00:00:00 2001 From: x64-dev <202863051+x64-dev@users.noreply.github.com> Date: Thu, 4 Sep 2025 19:23:49 -0400 Subject: [PATCH 7/9] - Fix bug where player tool tips could show wrong user - Also show more info on player tooltip --- .../OnlineServices_RoomsInterface.h | 17 ++- .../GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp | 133 ++++++++++++++++-- 2 files changed, 141 insertions(+), 9 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h index 9983d0a6137..3dee20766ba 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.h @@ -83,7 +83,8 @@ struct NGMP_RoomInfo class NetworkRoomMember : public NetworkMemberBase { - +public: + bool IsValid() const { return user_id != -1; } }; class NGMP_OnlineServices_RoomsInterface @@ -149,6 +150,20 @@ class NGMP_OnlineServices_RoomsInterface return nullptr; } + NetworkRoomMember GetRoomMemberFromName(const char* szTargetName) + { + // TODO_NGMP: Migrate away from this, it's slow. This game relies on names too much. + for (auto kvPair : m_mapMembers) + { + if (strcmp(kvPair.second.display_name.c_str(), szTargetName) == 0) + { + return kvPair.second; + } + } + + return NetworkRoomMember(); + } + std::map& GetMembersListForCurrentRoom(); // Chat diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp index 7ddf8a36233..dc4c7579472 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp @@ -296,22 +296,139 @@ static void playerTooltip(GameWindow *window, } UnicodeString uName = GadgetListBoxGetText(window, row, COLUMN_PLAYERNAME); - AsciiString aName; - aName.translate(uName); + // TODO_NGMP: This causes issues with duplicate names. We should have better ways of looking this up + perhaps only allow unique names NGMP_OnlineServices_RoomsInterface* pRoomsInterface = NGMP_OnlineServicesManager::GetInterface(); - if (pRoomsInterface != nullptr) + NGMP_OnlineServices_AuthInterface* pAuthInterface = NGMP_OnlineServicesManager::GetInterface(); + NGMP_OnlineServices_StatsInterface* pStatsInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pRoomsInterface != nullptr && pAuthInterface != nullptr && pStatsInterface != nullptr) { - NetworkRoomMember* pRoomMember = pRoomsInterface->GetRoomMemberFromIndex(row); + NetworkRoomMember roomMember = pRoomsInterface->GetRoomMemberFromName(to_utf8(uName.str()).c_str()); + // TODO_NGMP: This is an async call, we should block future popups until it returns to avoid weirdness if (col > 0) { - if (pRoomMember != nullptr) + if (roomMember.IsValid()) { - UnicodeString ucTooltip; - ucTooltip.format(L"Display Name: %s\nUser ID: %lld", from_utf8(pRoomMember->display_name).c_str(), pRoomMember->user_id); + // new + pStatsInterface->findPlayerStatsByID(roomMember.user_id, [=](bool bSuccess, PSPlayerStats stats) + { + if (!bSuccess) + { + TheMouse->setCursorTooltip(UnicodeString(L"Error: 1"), -1, NULL, 1.5f); + } + else + { + UnicodeString tooltip = UnicodeString::TheEmptyString; + if (roomMember.user_id == pAuthInterface->GetUserID()) + { + tooltip.format(TheGameText->fetch("TOOLTIP:LocalPlayer"), uName.str()); } + else + { + // not us + // TODO_SOCIAL + bool bIsFriend = false; + if (bIsFriend) + { + // buddy + tooltip.format(TheGameText->fetch("TOOLTIP:BuddyPlayer"), uName.str()); + } + else + { + // non-buddy profiled player + tooltip.format(TheGameText->fetch("TOOLTIP:ProfiledPlayer"), uName.str()); + + // NOTE: Removed non-profiled generic player, this doesn't exist on Generals Online, everyone has a profile + } + } + + // add user ID + UnicodeString tmp; + tmp.format(L"\nUser ID: %lld", roomMember.user_id); + tooltip.concat(tmp); - TheMouse->setCursorTooltip(ucTooltip, -1, NULL, 1.5f); // the text and width are the only params used. the others are the default values. + // TODO_SOCIAL + bool bIgnored = false; + if (bIgnored) + { + tooltip.concat(TheGameText->fetch("TOOLTIP:IgnoredModifier")); + } + + Int rankPoints = CalculateRank(stats); + Int rank = 0; + Int i = 0; + while (rankPoints >= TheRankPointValues->m_ranks[i + 1]) + ++i; + rank = i; + + // determine favorite side + Int mostGames = 0; + Int favorite = 0; + for (auto it = stats.games.begin(); it != stats.games.end(); ++it) + { + if (it->second >= mostGames) + { + mostGames = it->second; + favorite = it->first; + } + } + + AsciiString sideName = "GUI:RandomSide"; + if (mostGames > 0) + { + const PlayerTemplate* fac = ThePlayerTemplateStore->getNthPlayerTemplate(favorite); + if (fac) + { + sideName.format("SIDE:%s", fac->getSide().str()); + } + } + AsciiString rankName; + rankName.format("GUI:GSRank%d", rank); + + tmp.clear(); + tmp.format(L"\n\nFavorite Side: %ls\nRank: %ls", TheGameText->fetch(sideName).str(), TheGameText->fetch(rankName).str()); + tooltip.concat(tmp); + + int totalWins = 0; + int totalLosses = 0; + int totalDC = 0; + int totalWinsInRow = 0; + int totalLossesInRow = 0; + int totalDCInRow = 0; + int maxWinsInRow = 0; + int maxLossesInRow = 0; + int maxDCInRow = 0; + + for (int i = 0; i < stats.wins.size(); ++i) + { + totalWins += stats.wins[i]; + totalLosses += stats.losses[i]; + totalDC += stats.discons[i]; + totalWinsInRow = stats.winsInARow; + totalLossesInRow = stats.lossesInARow; + totalDCInRow = stats.disconsInARow; + + maxWinsInRow = stats.maxWinsInARow; + maxLossesInRow = stats.maxLossesInARow; + maxDCInRow = stats.maxDisconsInARow; + } + + tmp.clear(); + tmp.format(L"\n\nTotal Wins: %d\nTotal Losses: %d\nTotal Disconnects: %d\n\nCurrent Win Streak: %d\nCurrent Loss Streak: %d\nCurrent Disconnect Streak: %d\n\nLongest Win Streak: %d\nLongest Loss Streak: %d\nLongest Disconnect Streak: %d", + totalWins, + totalLosses, + totalDC, + totalWinsInRow, + totalLossesInRow, + totalDCInRow, + maxWinsInRow, + maxLossesInRow, + maxDCInRow); + tooltip.concat(tmp); + + TheMouse->setCursorTooltip(tooltip, -1, NULL, 1.5f); // the text and width are the only params used. the others are the default values. + } + }, EStatsRequestPolicy::RESPECT_CACHE_ALLOW_REQUEST); } else { From 251901c252776cd8ae557d50c69e8fba6b5aeb0a Mon Sep 17 00:00:00 2001 From: x64-dev <202863051+x64-dev@users.noreply.github.com> Date: Thu, 4 Sep 2025 19:43:47 -0400 Subject: [PATCH 8/9] - Tooltip for server list - Fixed boxes in server name --- .../OnlineServices_LobbyInterface.h | 1 + .../Source/GameNetwork/GameSpy/LobbyUtils.cpp | 92 +++++++++++++++++-- .../OnlineServices_LobbyInterface.cpp | 15 +++ 3 files changed, 100 insertions(+), 8 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h index 0f0e92b3409..d5dea07171a 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h @@ -400,6 +400,7 @@ class NGMP_OnlineServices_LobbyInterface void LeaveCurrentLobby(); + LobbyEntry GetLobbyFromID(int64_t lobbyID); LobbyEntry GetLobbyFromIndex(int index); std::vector m_vecLobbies; diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp index 2f587a22a6d..1344fa9872c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp @@ -212,11 +212,6 @@ static void gameTooltip(GameWindow* window, WinInstanceData* instData, UnsignedInt mouse) { - // TODO_NGMP -#if defined(GENERALS_ONLINE) - return; -#endif - Int x, y, row, col; x = LOLONGTOSHORT(mouse); y = HILONGTOSHORT(mouse); @@ -230,12 +225,26 @@ static void gameTooltip(GameWindow* window, } Int gameID = (Int)GadgetListBoxGetItemData(window, row, 0); +#if defined(GENERALS_ONLINE) + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pLobbyInterface == nullptr) + { + return; + } + + LobbyEntry lobbyEntry = pLobbyInterface->GetLobbyFromID(gameID); + if (lobbyEntry.lobbyID == -1) + { + return; + } +#else GameSpyStagingRoom *room = TheGameSpyInfo->findStagingRoomByID(gameID); if (!room) { TheMouse->setCursorTooltip( TheGameText->fetch("TOOLTIP:UnknownGame") ); return; } +#endif if (col == COLUMN_PING) { @@ -258,7 +267,11 @@ static void gameTooltip(GameWindow* window, } if (col == COLUMN_PASSWORD) { +#if defined(GENERALS_ONLINE) + if (lobbyEntry.passworded) +#else if (room->getHasPassword()) +#endif { UnicodeString checkTooltip =TheGameText->fetch("TOOTIP:Password"); if(!checkTooltip.compare(L"Password required to joing game")) @@ -271,7 +284,11 @@ static void gameTooltip(GameWindow* window, } if (col == COLUMN_USE_STATS) { - if ( room->getUseStats() ) +#if defined(GENERALS_ONLINE) + if (lobbyEntry.track_stats) +#else + if (room->getUseStats()) +#endif { TheMouse->setCursorTooltip( TheGameText->fetch("TOOLTIP:UseStatsOn") ); } @@ -285,6 +302,11 @@ static void gameTooltip(GameWindow* window, UnicodeString tooltip; UnicodeString mapName; + +#if defined(GENERALS_ONLINE) + // GO already has the full map info, don't need the cache + mapName.translate(lobbyEntry.map_name.c_str()); +#else const MapMetaData *md = TheMapCache->findMap(room->getMap()); if (md) { @@ -303,9 +325,23 @@ static void gameTooltip(GameWindow* window, } mapName.translate( start ); } +#endif UnicodeString tmp; + +#if defined(GENERALS_ONLINE) + UnicodeString gameName; + gameName.format(L"%s", from_utf8(lobbyEntry.name).c_str()); + tooltip.format(TheGameText->fetch("TOOLTIP:GameInfoGameName"), gameName.str()); +#else tooltip.format(TheGameText->fetch("TOOLTIP:GameInfoGameName"), room->getGameName().str()); +#endif + +#if defined(GENERALS_ONLINE) + // TODO_QUICKMATCH + if (false) +#else if (room->getLadderPort() != 0) + { const LadderInfo *linfo = TheLadderList->findLadder(room->getLadderIP(), room->getLadderPort()); if (linfo) @@ -314,7 +350,12 @@ static void gameTooltip(GameWindow* window, tooltip.concat(tmp); } } +#endif +#if defined(GENERALS_ONLINE) + if (lobbyEntry.exe_crc != TheGlobalData->m_exeCRC || lobbyEntry.ini_crc != TheGlobalData->m_iniCRC) +#else if (room->getExeCRC() != TheGlobalData->m_exeCRC || room->getIniCRC() != TheGlobalData->m_iniCRC) +#endif { tmp.format(TheGameText->fetch("TOOLTIP:InvalidGameVersion"), mapName.str()); tooltip.concat(tmp); @@ -322,6 +363,39 @@ static void gameTooltip(GameWindow* window, tmp.format(TheGameText->fetch("TOOLTIP:GameInfoMap"), mapName.str()); tooltip.concat(tmp); +#if defined(GENERALS_ONLINE) + for (LobbyMemberEntry& member : lobbyEntry.members) + { + if (member.IsHuman()) + { + UnicodeString plrName; + plrName.format(L"%s", from_utf8(member.display_name).c_str()); + + // TODO_NGMP: We don't have stats info + //tmp.format(TheGameText->fetch("TOOLTIP:GameInfoPlayer"), plrName.str(), slot->getWins(), slot->getLosses()); + tooltip.concat(L'\n'); + tooltip.concat(plrName); + } + else + { + switch (member.m_SlotState) + { + case SLOT_EASY_AI: + tooltip.concat(L'\n'); + tooltip.concat(TheGameText->fetch("GUI:EasyAI")); + break; + case SLOT_MED_AI: + tooltip.concat(L'\n'); + tooltip.concat(TheGameText->fetch("GUI:MediumAI")); + break; + case SLOT_BRUTAL_AI: + tooltip.concat(L'\n'); + tooltip.concat(TheGameText->fetch("GUI:HardAI")); + break; + } + } + } +#else AsciiString aPlayer; UnicodeString player; Int numPlayers = 0; @@ -359,6 +433,7 @@ static void gameTooltip(GameWindow* window, } } DEBUG_ASSERTCRASH(numPlayers, ("Tooltipping a 0-player game!")); +#endif TheMouse->setCursorTooltip( tooltip, 10, NULL, 2.0f ); // the text and width are the only params used. the others are the default values. } @@ -570,7 +645,7 @@ static Int insertGame(GameWindow* win, LobbyEntry& lobbyInfo, Bool showMap) } UnicodeString gameName; - gameName.format(L"%s (%s)", UnicodeString(from_utf8(lobbyInfo.name).c_str()), strOwnerName.c_str()); + gameName.format(L"%s (%s)", from_utf8(lobbyInfo.name).c_str(), strOwnerName.c_str()); int numPlayers = lobbyInfo.current_players; int maxPlayers = lobbyInfo.max_players; @@ -578,7 +653,7 @@ static Int insertGame(GameWindow* win, LobbyEntry& lobbyInfo, Bool showMap) AsciiString lobbyMapName = AsciiString(lobbyInfo.map_name.c_str()); AsciiString ladder = AsciiString("TODO_NGMP"); USHORT ladderPort = 1; - int gameID = 0; + int gameID = lobbyInfo.lobbyID; // TODO_NGMP: Downcast. We should use int64 everywhere, although its unlikely we actually need int64 for lobby since its reset regularly. bool bHasPassword = lobbyInfo.passworded; @@ -932,6 +1007,7 @@ void RefreshGameListBox( GameWindow *win, Bool showMap ) { win->winEnable(false); GadgetListBoxAddEntryText(win, UnicodeString(L"No lobbies were found"), GameMakeColor(255, 194, 15, 255), -1, -1); + } else { diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.cpp index c9d787b074e..95216c1dbd4 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.cpp @@ -1024,6 +1024,21 @@ void NGMP_OnlineServices_LobbyInterface::LeaveCurrentLobby() ResetCachedRoomData(); } + +LobbyEntry NGMP_OnlineServices_LobbyInterface::GetLobbyFromID(int64_t lobbyID) +{ + // TODO_NGMP: Optimize for lookup + for (LobbyEntry& lobbyEntry : m_vecLobbies) + { + if (lobbyEntry.lobbyID == lobbyID) + { + return lobbyEntry; + } + } + + return LobbyEntry(); +} + LobbyEntry NGMP_OnlineServices_LobbyInterface::GetLobbyFromIndex(int index) { // TODO_NGMP: safety From b2d4e0b253c61c05a7314eb94fc2a0e37630d373 Mon Sep 17 00:00:00 2001 From: x64-dev <202863051+x64-dev@users.noreply.github.com> Date: Thu, 4 Sep 2025 19:51:09 -0400 Subject: [PATCH 9/9] Lookup users by ID instead of name in network room --- .../GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp index dc4c7579472..3b30a398c5f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLLobbyMenu.cpp @@ -303,15 +303,16 @@ static void playerTooltip(GameWindow *window, NGMP_OnlineServices_StatsInterface* pStatsInterface = NGMP_OnlineServicesManager::GetInterface(); if (pRoomsInterface != nullptr && pAuthInterface != nullptr && pStatsInterface != nullptr) { - NetworkRoomMember roomMember = pRoomsInterface->GetRoomMemberFromName(to_utf8(uName.str()).c_str()); + int profileID = (int)GadgetListBoxGetItemData(listboxLobbyPlayers, row, 0); + NetworkRoomMember* roomMember = pRoomsInterface->GetRoomMemberFromID(profileID); // TODO_NGMP: This is an async call, we should block future popups until it returns to avoid weirdness if (col > 0) { - if (roomMember.IsValid()) + if (roomMember != nullptr) { // new - pStatsInterface->findPlayerStatsByID(roomMember.user_id, [=](bool bSuccess, PSPlayerStats stats) + pStatsInterface->findPlayerStatsByID(roomMember->user_id, [=](bool bSuccess, PSPlayerStats stats) { if (!bSuccess) { @@ -320,7 +321,7 @@ static void playerTooltip(GameWindow *window, else { UnicodeString tooltip = UnicodeString::TheEmptyString; - if (roomMember.user_id == pAuthInterface->GetUserID()) + if (roomMember->user_id == pAuthInterface->GetUserID()) { tooltip.format(TheGameText->fetch("TOOLTIP:LocalPlayer"), uName.str()); } else @@ -344,7 +345,7 @@ static void playerTooltip(GameWindow *window, // add user ID UnicodeString tmp; - tmp.format(L"\nUser ID: %lld", roomMember.user_id); + tmp.format(L"\nUser ID: %lld", roomMember->user_id); tooltip.concat(tmp); // TODO_SOCIAL @@ -711,6 +712,9 @@ static Int insertPlayerInListbox(const PlayerInfo& info, Color color) Int index = GadgetListBoxAddEntryImage(listboxLobbyPlayers, rankImg, -1, 0, w, h); GadgetListBoxAddEntryText(listboxLobbyPlayers, uStr, color, index, 1); #endif + + // attach data + GadgetListBoxSetItemData(listboxLobbyPlayers, (void*)info.m_profileID, index); return index; }