diff --git a/Core/GameEngine/Source/GameNetwork/GameSpy/MainMenuUtils.cpp b/Core/GameEngine/Source/GameNetwork/GameSpy/MainMenuUtils.cpp index f35a947ae0e..922e7ed3f5b 100644 --- a/Core/GameEngine/Source/GameNetwork/GameSpy/MainMenuUtils.cpp +++ b/Core/GameEngine/Source/GameNetwork/GameSpy/MainMenuUtils.cpp @@ -44,6 +44,8 @@ #include "GameClient/ShellHooks.h" #include "gamespy/ghttp/ghttp.h" +// Must follow ghttp.h: gsavailable.h uses gsi_char without including gsplatform.h. +#include "gamespy/gsavailable.h" #include "GameNetwork/DownloadManager.h" #include "GameNetwork/GameSpy/BuddyThread.h" @@ -59,6 +61,23 @@ static Bool checkingForPatchBeforeGameSpy = FALSE; static Int checksLeftBeforeOnline = 0; + +// Every GameSpy SDK entry point fails until this reaches GSIACAvailable. +static GSIACResult availableCheckResult = GSIACWaiting; +// Tracked separately from the result so a cancelled check cannot decrement +// checksLeftBeforeOnline after CancelPatchCheckCallback() has reset it. +static Bool availableCheckInProgress = FALSE; + +static const char *getGameSpyGameName() +{ +#if RTS_GENERALS + return "ccgenerals"; +#elif RTS_ZEROHOUR + return "ccgenzh"; +#else +#error "No GameSpy gamename defined for this build target" +#endif +} static Int timeThroughOnline = 0; // used to avoid having old callbacks cause problems static Bool mustDownloadPatch = FALSE; static Bool cantConnectBeforeOnline = FALSE; @@ -140,6 +159,13 @@ static void noPatchBeforeOnlineCallback() } } +// noPatchBeforeOnlineCallback() cannot be reused here: it calls startOnline(), which +// would fail the same check and reopen this box. +static void backendUnavailableCallback() +{ + HandleCanceledDownload(); +} + /////////////////////////////////////////////////////////////////////////////////////// static Bool hasWriteAccess() @@ -216,6 +242,15 @@ static void startOnline() TheScriptEngine->signalUIInteract(TheShellHookNames[SHELL_SCRIPT_HOOK_MAIN_MENU_ONLINE_SELECTED]); + if (availableCheckResult != GSIACAvailable) + { + // Backend reported the title disabled; the GameSpy threads would all fail to start. + MessageBoxOk(TheGameText->fetch("GUI:GSErrorTitle"), + TheGameText->fetch("GUI:GSDisconReason4"), + backendUnavailableCallback); + return; + } + DEBUG_ASSERTCRASH( !TheGameSpyBuddyMessageQueue, ("TheGameSpyBuddyMessageQueue exists!") ); DEBUG_ASSERTCRASH( !TheGameSpyPeerMessageQueue, ("TheGameSpyPeerMessageQueue exists!") ); DEBUG_ASSERTCRASH( !TheGameSpyInfo, ("TheGameSpyInfo exists!") ); @@ -555,6 +590,11 @@ void CancelPatchCheckCallbackAndReopenDropdown() void CancelPatchCheckCallback() { s_asyncDNSLookupInProgress = FALSE; + if (availableCheckInProgress) + { + GSICancelAvailableCheck(); + availableCheckInProgress = FALSE; + } HandleCanceledDownload(FALSE); // don't dropdown checkingForPatchBeforeGameSpy = FALSE; checksLeftBeforeOnline = 0; @@ -730,7 +770,7 @@ DWORD WINAPI asyncGethostbynameThreadFunc( void * szName ) /////////////////////////////////////////////////////////////////////////////////////// -int asyncGethostbyname(char * szName) +int asyncGethostbyname(const char * szName) { static int stat = 0; static unsigned long threadid; @@ -739,7 +779,8 @@ int asyncGethostbyname(char * szName) { /* Kick off gethostname thread */ s_asyncDNSThreadDone = FALSE; - s_asyncDNSThreadHandle = CreateThread( nullptr, 0, asyncGethostbynameThreadFunc, szName, 0, &threadid ); + s_asyncDNSThreadHandle = CreateThread( nullptr, 0, asyncGethostbynameThreadFunc, + const_cast(szName), 0, &threadid ); if( s_asyncDNSThreadHandle == nullptr ) { @@ -773,8 +814,7 @@ void HTTPThinkWrapper() { if (s_asyncDNSLookupInProgress) { - Char hostname[] = "servserv.generals.ea.com"; - Int ret = asyncGethostbyname(hostname); + Int ret = asyncGethostbyname("servserv.generals.ea.com"); switch(ret) { case LOOKUP_FAILED: @@ -787,6 +827,30 @@ void HTTPThinkWrapper() } } + // GSIAvailableCheckThink() is what advances the check; it has to be called until it + // stops returning GSIACWaiting. Completing counts as one of checksLeftBeforeOnline so + // startOnline() waits for it, the same way it waits for the HTTP fetches. + if (availableCheckInProgress) + { + availableCheckResult = GSIAvailableCheckThink(); + if (availableCheckResult != GSIACWaiting) + { + availableCheckInProgress = FALSE; + --checksLeftBeforeOnline; + DEBUG_ASSERTCRASH(checksLeftBeforeOnline>=0, ("Too many callbacks")); + if (onlineCancelWindow && checksLeftBeforeOnline == 0) + { + TheWindowManager->winDestroy(onlineCancelWindow); + onlineCancelWindow = nullptr; + } + + DEBUG_LOG(("Availability check returned %d", availableCheckResult)); + + if (checksLeftBeforeOnline == 0) + startOnline(); + } + } + if (isHttpOk) { try @@ -829,8 +893,7 @@ void StartPatchCheck() TheGameText->fetch("GUI:CheckingForPatches"), CancelPatchCheckCallbackAndReopenDropdown); s_asyncDNSLookupInProgress = TRUE; - Char hostname[] = "servserv.generals.ea.com"; - Int ret = asyncGethostbyname(hostname); + Int ret = asyncGethostbyname("servserv.generals.ea.com"); switch(ret) { case LOOKUP_FAILED: @@ -847,7 +910,12 @@ void StartPatchCheck() static void reallyStartPatchCheck() { - checksLeftBeforeOnline = 4; + checksLeftBeforeOnline = 5; // the four ghttp calls below, plus the availability check + + GSICancelAvailableCheck(); // going online is retryable; do not leak the old socket + availableCheckResult = GSIACWaiting; + availableCheckInProgress = TRUE; + GSIStartAvailableCheck(getGameSpyGameName()); std::string gameURL, mapURL; std::string configURL, motdURL; diff --git a/Core/GameEngine/Source/GameNetwork/GameSpy/Thread/PeerThread.cpp b/Core/GameEngine/Source/GameNetwork/GameSpy/Thread/PeerThread.cpp index 178a1b1ad3c..def21481106 100644 --- a/Core/GameEngine/Source/GameNetwork/GameSpy/Thread/PeerThread.cpp +++ b/Core/GameEngine/Source/GameNetwork/GameSpy/Thread/PeerThread.cpp @@ -1192,6 +1192,18 @@ void PeerThreadClass::Thread_Function() peer = peerInitialize( &callbacks ); DEBUG_ASSERTCRASH( peer != nullptr, ("null peer!") ); + if (peer == nullptr) + { + // The assert above is a no-op in release, where the null goes on to fault in + // peerSetRoomWatchKeys(). startThread() reuses this object, so the flags have + // to be cleared or they leak into the next attempt. + markAsDisconnected(); + PeerResponse resp; + resp.peerResponseType = PeerResponse::PEERRESPONSE_DISCONNECT; + resp.discon.reason = DISCONNECT_COULDNOTCONNECT; + TheGameSpyPeerMessageQueue->addResponse(resp); + return; + } m_isConnected = m_isConnecting = false; qr2_register_key(EXECRC_KEY, EXECRC_STR);