diff --git a/lib/netplay/netplay.cpp b/lib/netplay/netplay.cpp index f14fb719a99..cbde1bc076c 100644 --- a/lib/netplay/netplay.cpp +++ b/lib/netplay/netplay.cpp @@ -231,8 +231,10 @@ class PlayerManagementRecord // Variables NETPLAY NetPlay; -static bool allow_joining = false; -static bool server_not_there = false; +static bool allow_joining = false; +static bool server_not_there = false; +static bool lobby_disabled = false; +static std::string lobby_disabled_info_link_url; static GAMESTRUCT gamestruct; static std::vector DownloadingWzFiles; @@ -417,6 +419,22 @@ bool NETGameIsLocked() return NetPlay.GamePassworded; } +bool NET_getLobbyDisabled() +{ + return lobby_disabled; +} + +const std::string& NET_getLobbyDisabledInfoLinkURL() +{ + return lobby_disabled_info_link_url; +} + +void NET_setLobbyDisabled(const std::string& infoLinkURL) +{ + lobby_disabled = true; + lobby_disabled_info_link_url = infoLinkURL; +} + // Sets if the game is password protected or not void NETGameLocked(bool flag) { @@ -3409,6 +3427,14 @@ bool LobbyServerConnectionHandler::connect() { return false; } + if (lobby_disabled) + { + debug(LOG_ERROR, "Multiplayer lobby support unavailable. Please update your client."); + wz_command_interface_output("WZEVENT: lobbyerror: Client support disabled / unavailable\n"); + gamestruct.gameId = 0; + server_not_there = true; + return true; // return true once, so that NETallowJoining processes the "first time connect" branch + } if (currentState == LobbyConnectionState::Connecting_WaitingForResponse || currentState == LobbyConnectionState::Connected) { return false; // already connecting or connected @@ -3519,14 +3545,20 @@ bool LobbyServerConnectionHandler::disconnect() queuedServerUpdate = false; + ActivityManager::instance().hostGameLobbyServerDisconnect(); + currentState = LobbyConnectionState::Disconnected; return true; } void LobbyServerConnectionHandler::sendUpdate() { - if (server_not_there) + if (lobby_disabled || server_not_there) { + if (currentState != LobbyConnectionState::Disconnected) + { + disconnect(); + } return; } @@ -3545,10 +3577,18 @@ void LobbyServerConnectionHandler::sendUpdate() void LobbyServerConnectionHandler::sendUpdateNow() { ASSERT_OR_RETURN(, rs_socket != nullptr, "Null socket"); + if (lobby_disabled) + { + if (currentState != LobbyConnectionState::Disconnected) + { + disconnect(); + } + return; + } + if (!NETsendGAMESTRUCT(rs_socket, &gamestruct)) { disconnect(); - ActivityManager::instance().hostGameLobbyServerDisconnect(); } lastServerUpdate = realTime; queuedServerUpdate = false; @@ -3556,7 +3596,6 @@ void LobbyServerConnectionHandler::sendUpdateNow() if (rs_socket && readLobbyResponse(rs_socket, NET_TIMEOUT_DELAY) == SOCKET_ERROR) { disconnect(); - ActivityManager::instance().hostGameLobbyServerDisconnect(); } } @@ -3567,7 +3606,6 @@ void LobbyServerConnectionHandler::sendKeepAlive() { // The socket has been invalidated, so get rid of it. (using them now may cause SIGPIPE). disconnect(); - ActivityManager::instance().hostGameLobbyServerDisconnect(); } lastServerUpdate = realTime; } @@ -4585,6 +4623,12 @@ bool NETfindGames(std::vector& results, size_t startingIndex, size_t { size_t gamecount = 0; results.clear(); + + if (lobby_disabled) + { + return true; + } + bool success = NETenumerateGames([&results, &gamecount, startingIndex, resultsLimit, onlyMatchingLocalVersion](const GAMESTRUCT &lobbyGame) -> bool { if (gamecount++ < startingIndex) { diff --git a/lib/netplay/netplay.h b/lib/netplay/netplay.h index 1c029eb3b01..a0c2c2cf179 100644 --- a/lib/netplay/netplay.h +++ b/lib/netplay/netplay.h @@ -490,6 +490,10 @@ const std::vector& NET_getDownloadingWzFiles(); void NET_addDownloadingWZFile(WZFile&& newFile); void NET_clearDownloadingWZFiles(); +bool NET_getLobbyDisabled(); +const std::string& NET_getLobbyDisabledInfoLinkURL(); +void NET_setLobbyDisabled(const std::string& infoLinkURL); + bool NETGameIsLocked(); void NETGameLocked(bool flag); void NETresetGamePassword(); diff --git a/src/clparse.cpp b/src/clparse.cpp index 8e223b6bd99..10e90f0d559 100644 --- a/src/clparse.cpp +++ b/src/clparse.cpp @@ -575,8 +575,8 @@ static std::string specialGetBaseDir(const std::string& platformSpecificPath, st * set up first. * \param argc number of arguments given * \param argv string array of the arguments - * \return Returns true on success, false on error */ -bool ParseCommandLineEarly(int argc, const char * const *argv) + * \return See ParseCLIEarlyResult enum */ +ParseCLIEarlyResult ParseCommandLineEarly(int argc, const char * const *argv) { poptContext poptCon = poptGetContext(nullptr, argc, argv, getOptionsTable(), 0); int iOption; @@ -615,11 +615,11 @@ bool ParseCommandLineEarly(int argc, const char * const *argv) case CLI_HELP: poptPrintHelp(poptCon, stdout); - return false; + return ParseCLIEarlyResult::HANDLED_QUIT_EARLY_COMMAND; case CLI_VERSION: printf("Warzone 2100 - %s\n", version_getFormattedVersionString()); - return false; + return ParseCLIEarlyResult::HANDLED_QUIT_EARLY_COMMAND; #if defined(WZ_OS_WIN) case CLI_WIN_ENABLE_CONSOLE: @@ -693,7 +693,7 @@ bool ParseCommandLineEarly(int argc, const char * const *argv) }; } - return true; + return ParseCLIEarlyResult::OK_CONTINUE; } //! second half of parsing the commandline diff --git a/src/clparse.h b/src/clparse.h index 298affabdea..a8f5175284f 100644 --- a/src/clparse.h +++ b/src/clparse.h @@ -26,7 +26,13 @@ // parse the commandline bool ParseCommandLine(int argc, const char * const *argv); -bool ParseCommandLineEarly(int argc, const char * const *argv); + +enum class ParseCLIEarlyResult +{ + OK_CONTINUE, + HANDLED_QUIT_EARLY_COMMAND +}; +ParseCLIEarlyResult ParseCommandLineEarly(int argc, const char * const *argv); bool ParseCommandLineDebugFlags(int argc, const char * const *argv); bool autogame_enabled(); diff --git a/src/main.cpp b/src/main.cpp index 5c401cd8df7..efb60314245 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1753,6 +1753,38 @@ static void cleanupOldLogFiles() }); } +static void mainProcessCompatCheckResults(CompatCheckResults results) +{ + // Since this may be called from any thread, use wzAsyncExecOnMainThread + wzAsyncExecOnMainThread([results]() { + if (!results.successfulCheck) + { + return; + } + if (!results.hasIssue()) + { + return; + } + + // supported_terrain + auto& configFlags = results.issue.value().configFlags; + if (configFlags.supportedTerrain.count(getTerrainShaderQuality()) == 0) + { + // current terrain mode is not in supported list + // if not in a game, change the terrain shader quality back to default + if (GetGameMode() != GS_NORMAL) + { + setTerrainShaderQuality(TerrainShaderQuality::MEDIUM); + } + } + // multilobby + if (!configFlags.multilobby) + { + NET_setLobbyDisabled(results.issue.value().infoLink); + } + }); +} + // for backend detection extern const char *BACKEND; @@ -1861,9 +1893,10 @@ int realmain(int argc, char *argv[]) urlRequestInit(); // find early boot info - if (!ParseCommandLineEarly(utfargc, utfargv)) + ParseCLIEarlyResult earlyCommandLineParsingResult = ParseCommandLineEarly(utfargc, utfargv); + if (earlyCommandLineParsingResult != ParseCLIEarlyResult::OK_CONTINUE) { - return EXIT_FAILURE; + return (earlyCommandLineParsingResult == ParseCLIEarlyResult::HANDLED_QUIT_EARLY_COMMAND) ? 0 : EXIT_FAILURE; } /* Initialize the write/config directory for PhysicsFS. @@ -2157,6 +2190,7 @@ int realmain(int argc, char *argv[]) break; } + asyncGetCompatCheckResults(mainProcessCompatCheckResults); WzInfoManager::initialize(); #if defined(ENABLE_DISCORD) discordRPCInitialize(); diff --git a/src/multiint.cpp b/src/multiint.cpp index 4733dd728d4..bf30a301f48 100644 --- a/src/multiint.cpp +++ b/src/multiint.cpp @@ -114,6 +114,7 @@ #include "faction.h" #include "multilobbycommands.h" #include "stdinreader.h" +#include "urlhelpers.h" #include "hci/quickchat.h" #include "activity.h" @@ -121,6 +122,7 @@ #include "3rdparty/gsl_finally.h" #define MAP_PREVIEW_DISPLAY_TIME 2500 // number of milliseconds to show map in preview +#define LOBBY_DISABLED_TAG "lobbyDisabled" #define VOTE_TAG "voting" #define KICK_REASON_TAG "kickReason" #define SLOTTYPE_TAG_PREFIX "slotType" @@ -6034,6 +6036,34 @@ static void randomizeOptions() } } +void displayLobbyDisabledNotification() +{ + if (!hasNotificationsWithTag(LOBBY_DISABLED_TAG)) + { + WZ_Notification notification; + notification.duration = 0; + notification.contentTitle = _("Multiplayer Lobby Support Unavailable"); + + notification.contentText = _("Your client cannot connect to the mutiplayer lobby."); + notification.contentText += "\n\n"; + notification.contentText += _("Please click the button below for more information on how to fix it."); + + std::string infoLink = NET_getLobbyDisabledInfoLinkURL(); + notification.action = WZ_Notification_Action(_("More Information"), [infoLink](const WZ_Notification&) { + // Open the infoLink url + wzAsyncExecOnMainThread([infoLink]{ + if (!openURLInBrowser(infoLink.c_str())) + { + debug(LOG_ERROR, "Failed to open url in browser: \"%s\"", infoLink.c_str()); + } + }); + }); + notification.tag = LOBBY_DISABLED_TAG; + + addNotification(notification, WZ_Notification_Trigger::Immediate()); + } +} + bool WzMultiplayerOptionsTitleUI::startHost() { resetReadyStatus(false); @@ -6055,6 +6085,11 @@ bool WzMultiplayerOptionsTitleUI::startHost() return false; } + if (NET_getLobbyDisabled()) + { + displayLobbyDisabledNotification(); + } + bInActualHostedLobby = true; widgDelete(psWScreen, MULTIOP_REFRESH); @@ -6372,6 +6407,7 @@ void startMultiplayerGame() wz_command_interface_output("WZEVENT: startMultiplayerGame\n"); debug(LOG_INFO, "startMultiplayerGame"); + cancelOrDismissNotificationsWithTag(LOBBY_DISABLED_TAG); cancelOrDismissNotificationIfTag([](const std::string& tag) { return (tag.rfind(SLOTTYPE_TAG_PREFIX, 0) == 0); }); @@ -6900,6 +6936,7 @@ void WzMultiplayerOptionsTitleUI::frontendMultiMessages(bool running) } case NET_FIREUP: // campaign game started.. can fire the whole shebang up... cancelOrDismissNotificationsWithTag(VOTE_TAG); // don't need vote notifications anymore + cancelOrDismissNotificationsWithTag(LOBBY_DISABLED_TAG); cancelOrDismissNotificationIfTag([](const std::string& tag) { return (tag.rfind(SLOTTYPE_TAG_PREFIX, 0) == 0); }); @@ -7326,6 +7363,7 @@ TITLECODE WzMultiplayerOptionsTitleUI::run() if (!NetPlay.isHostAlive && ingame.side == InGameSide::MULTIPLAYER_CLIENT) { cancelOrDismissNotificationsWithTag(VOTE_TAG); + cancelOrDismissNotificationsWithTag(LOBBY_DISABLED_TAG); cancelOrDismissNotificationIfTag([](const std::string& tag) { return (tag.rfind(SLOTTYPE_TAG_PREFIX, 0) == 0); }); diff --git a/src/multiint.h b/src/multiint.h index 07b72be8ce0..4bca5acd811 100644 --- a/src/multiint.h +++ b/src/multiint.h @@ -102,6 +102,7 @@ void sendRoomNotifyMessage(char const *text); void sendRoomSystemMessageToSingleReceiver(char const *text, uint32_t receiver); void displayRoomSystemMessage(char const *text); void displayRoomNotifyMessage(char const *text); +void displayLobbyDisabledNotification(); void handleAutoReadyRequest(); diff --git a/src/titleui/gamefind.cpp b/src/titleui/gamefind.cpp index d91a948e491..ed3f252fde7 100644 --- a/src/titleui/gamefind.cpp +++ b/src/titleui/gamefind.cpp @@ -103,6 +103,11 @@ void WzGameFindTitleUI::start() pie_LoadBackDrop(SCREEN_RANDOMBDROP); } + if (NET_getLobbyDisabled()) + { + displayLobbyDisabledNotification(); + } + addGames(); // now add games. displayConsoleMessages(); } @@ -224,7 +229,14 @@ void WzGameFindTitleUI::addConsoleBox() setConsolePermanence(true, true); setConsoleLineInfo(3); // use x lines on chat window - addConsoleMessage(_("Connecting to the lobby server..."), DEFAULT_JUSTIFY, NOTIFY_MESSAGE); + if (!NET_getLobbyDisabled()) + { + addConsoleMessage(_("Connecting to the lobby server..."), DEFAULT_JUSTIFY, NOTIFY_MESSAGE); + } + else + { + addConsoleMessage(_("Multiplayer Lobby Support Unavailable"), DEFAULT_JUSTIFY, SYSTEM_MESSAGE, false, MAX_CONSOLE_MESSAGE_DURATION); + } displayConsoleMessages(); return; } @@ -675,7 +687,7 @@ void WzGameFindTitleUI::addGames() switch (getLobbyError()) { case ERROR_NOERROR: - if (NetPlay.HaveUpgrade) + if (NetPlay.HaveUpgrade || NET_getLobbyDisabled()) { txt = _("There appears to be a game update available!"); } diff --git a/src/updatemanager.cpp b/src/updatemanager.cpp index b42e5163c5e..c0e94723a2d 100644 --- a/src/updatemanager.cpp +++ b/src/updatemanager.cpp @@ -76,8 +76,9 @@ struct CachePaths { static std::string configureLinkURL(const std::string& url, BuildPropertyProvider& propProvider); static bool isValidExpiry(const json& updateData); -static void initProcessData(const std::vector &updateDataUrls, ProcessJSONDataFileFunc processDataFunc, CachePaths outputPaths); -static void fetchLatestData(const std::vector &updateDataUrls, ProcessJSONDataFileFunc processDataFunc, CachePaths outputPaths); +typedef std::function ProcessDataCompletionHandler; +static void initProcessData(const std::vector &updateDataUrls, ProcessJSONDataFileFunc processDataFunc, CachePaths outputPaths, ProcessDataCompletionHandler completionHandler); +static void fetchLatestData(const std::vector &updateDataUrls, ProcessJSONDataFileFunc processDataFunc, CachePaths outputPaths, ProcessDataCompletionHandler completionHandler); class WzUpdateManager { public: @@ -103,8 +104,45 @@ const std::string WZ_DEFAULT_COMPATINFO_LINK = "https://warzone2100.github.io/up static const char updatesCacheDataPath[] = WZ_UPDATES_CACHE_DIR "/wz2100_updates.json"; static const char cacheInfoPath[] = WZ_UPDATES_CACHE_DIR "/cache_info.json"; static const char compatDataPath[] = WZ_UPDATES_CACHE_DIR "/wz2100_compat.json"; +static const char compatCacheInfoPath[] = WZ_UPDATES_CACHE_DIR "/cache_info_compat.json"; static CachePaths updatesCachePaths = CachePaths{updatesCacheDataPath, cacheInfoPath}; -static CachePaths compatCachePaths = CachePaths{compatDataPath, nullptr}; +static CachePaths compatCachePaths = CachePaths{compatDataPath, compatCacheInfoPath}; + +static std::mutex compatCheckResultsMutex; +static optional compatCheckResults = nullopt; +static std::vector registeredCompatCheckResultsHandlers; + +// May be called from any thread +void setCompatCheckResults(CompatCheckResults results, bool onlyIfUnset = false) +{ + std::vector handlersToConsume; + + // lock scope + { + std::lock_guard guard(compatCheckResultsMutex); + if (compatCheckResults.has_value()) + { + if (onlyIfUnset) + { + // expected possibility - just silently return + return; + } + else + { + // not expected possibility - log a warning and proceed + wzAsyncExecOnMainThread([]{ debug(LOG_WARNING, "Overwriting already-set results"); }); + } + } + compatCheckResults = results; + handlersToConsume = std::move(registeredCompatCheckResultsHandlers); + } + + for (auto& handler : handlersToConsume) + { + if (!handler) { continue; } + handler(results); + } +} template date::sys_time parse_ISO_8601(const std::string& timeStr) @@ -260,6 +298,89 @@ static void applyBaseNotificationInfo(WZ_Notification& notification, const json& } } +inline void from_json(const nlohmann::json& j, TerrainShaderQuality& p) { + uint32_t val = j.get(); + if (val > static_cast(TerrainShaderQuality_MAX)) + { + throw nlohmann::json::type_error::create(302, "type must be an valid integer, but is " + std::to_string(val), &j); + } + p = static_cast(val); +} + +inline void from_json(const nlohmann::json& j, CompatCheckIssue::ConfigFlags& p) { + if (j.contains("terrain")) + { + p.supportedTerrain = j["terrain"].get>(); + } + if (j.contains("multilobby")) + { + p.multilobby = j["multilobby"].get(); + } +} + +static CompatCheckResults createCompatCheckResults(const std::string& compatNoticeIdStr, const std::string& infoLink, const json& compatNotice, const json& notificationInfo) +{ + CompatCheckIssue issue; + issue.identifier = compatNoticeIdStr; + issue.infoLink = infoLink; + + if (notificationInfo.is_object()) + { + try + { + if (notificationInfo.contains("severity")) + { + auto severityStr = notificationInfo["severity"].get(); + if (severityStr == "warning") + { + issue.severity = CompatCheckIssue::Severity::Warning; + } + else if (severityStr == "critical") + { + issue.severity = CompatCheckIssue::Severity::Critical; + } + } + } + catch (const std::exception&) + { + // Parsing notificationInfo "severity" failed + // no-op - just ignore + } + + try + { + if (notificationInfo.contains("unsupported")) + { + issue.unsupported = notificationInfo["unsupported"].get(); + } + } + catch (const std::exception&) + { + // Parsing notificationInfo "unsupported" failed + // no-op - just ignore + } + } + + if (compatNotice.contains("config")) + { + auto& compatConfig = compatNotice["config"]; + if (compatConfig.is_object()) + { + try + { + issue.configFlags = compatConfig.get(); + } + catch (const std::exception&) + { + // Parsing "config" failed + // no-op - just ignore + } + } + } + + return CompatCheckResults(true, issue); +} + // May be called from a background thread ProcessResult WzUpdateManager::processUpdateJSONFile(const json& updateData, bool validSignature, bool validExpiry) { @@ -408,7 +529,7 @@ ProcessResult WzUpdateManager::processUpdateJSONFile(const json& updateData, boo void WzUpdateManager::initUpdateCheck() { std::vector updateDataUrls = {"https://data.wz2100.net/wz2100.json", "https://warzone2100.github.io/update-data/wz2100.json"}; - initProcessData(updateDataUrls, WzUpdateManager::processUpdateJSONFile, updatesCachePaths); + initProcessData(updateDataUrls, WzUpdateManager::processUpdateJSONFile, updatesCachePaths, nullptr); } // May be called from a background thread @@ -519,6 +640,8 @@ ProcessResult WzCompatCheckManager::processCompatCheckJSONFile(const json& updat } addNotification(notification, WZ_Notification_Trigger::Immediate()); }); + + setCompatCheckResults(createCompatCheckResults(compatNoticeIdStr, infoLink, compatNotice, notificationInfo)); return ProcessResult::UPDATE_FOUND; } catch (const std::exception&) @@ -527,6 +650,8 @@ ProcessResult WzCompatCheckManager::processCompatCheckJSONFile(const json& updat continue; } } + + setCompatCheckResults(CompatCheckResults(true)); return ProcessResult::MATCHED_CHANNEL_NO_UPDATE; } catch (const std::exception&) @@ -535,6 +660,8 @@ ProcessResult WzCompatCheckManager::processCompatCheckJSONFile(const json& updat continue; } } + + setCompatCheckResults(CompatCheckResults(true)); return ProcessResult::NO_MATCHING_CHANNEL; } @@ -542,7 +669,10 @@ ProcessResult WzCompatCheckManager::processCompatCheckJSONFile(const json& updat void WzCompatCheckManager::initCompatCheck() { std::vector updateDataUrls = {"https://data.wz2100.net/wz2100_compat.json", "https://warzone2100.github.io/update-data/wz2100_compat.json"}; - initProcessData(updateDataUrls, WzCompatCheckManager::processCompatCheckJSONFile, compatCachePaths); + initProcessData(updateDataUrls, WzCompatCheckManager::processCompatCheckJSONFile, compatCachePaths, []() { + // set an unsuccessful result (if no prior result set) + setCompatCheckResults(CompatCheckResults(false), true); + }); } template @@ -627,8 +757,10 @@ static bool cacheInfoIsUsable(CachePaths& paths) } // May be called from a background thread -static void initProcessData(const std::vector &updateDataUrls, ProcessJSONDataFileFunc processDataFunc, CachePaths outputPaths) +static void initProcessData(const std::vector &updateDataUrls, ProcessJSONDataFileFunc processDataFunc, CachePaths outputPaths, ProcessDataCompletionHandler completionHandler) { + bool handledWithCachedData = false; + if (PHYSFS_exists(outputPaths.cache_data_path) && cacheInfoIsUsable(outputPaths)) { try { @@ -680,7 +812,7 @@ static void initProcessData(const std::vector &updateDataUrls, Proc } // handled with cached data - return; + handledWithCachedData = true; } catch (const std::exception &e) { std::string errorStr = e.what(); @@ -688,15 +820,25 @@ static void initProcessData(const std::vector &updateDataUrls, Proc debug(LOG_WZ, "Cached updates file: %s", errorStr.c_str()); }); // continue on to fetch a fresh copy + handledWithCachedData = false; + } + + if (handledWithCachedData) + { + if (completionHandler) + { + completionHandler(); + } + return; } } // Fall-back to URL request for the latest data - fetchLatestData(updateDataUrls, processDataFunc, outputPaths); + fetchLatestData(updateDataUrls, processDataFunc, outputPaths, completionHandler); } // May be called from a background thread -static void fetchLatestData(const std::vector &updateDataUrls, ProcessJSONDataFileFunc processDataFunc, CachePaths outputPaths) +static void fetchLatestData(const std::vector &updateDataUrls, ProcessJSONDataFileFunc processDataFunc, CachePaths outputPaths, ProcessDataCompletionHandler completionHandler) { if (updateDataUrls.empty()) { @@ -704,13 +846,17 @@ static void fetchLatestData(const std::vector &updateDataUrls, Proc wzAsyncExecOnMainThread([]{ debug(LOG_WARNING, "No more URLs to fetch - failed update check"); }); + if (completionHandler) + { + completionHandler(); + } return; } URLDataRequest* pRequest = new URLDataRequest(); pRequest->url = updateDataUrls.front(); std::vector additionalUrls(updateDataUrls.begin() + 1, updateDataUrls.end()); - pRequest->onSuccess = [additionalUrls, processDataFunc, outputPaths](const std::string& url, const HTTPResponseDetails& responseDetails, const std::shared_ptr& data) { + pRequest->onSuccess = [additionalUrls, processDataFunc, outputPaths, completionHandler](const std::string& url, const HTTPResponseDetails& responseDetails, const std::shared_ptr& data) { std::string urlCopy = url; long httpStatusCode = responseDetails.httpStatusCode(); @@ -719,7 +865,7 @@ static void fetchLatestData(const std::vector &updateDataUrls, Proc wzAsyncExecOnMainThread([httpStatusCode]{ debug(LOG_WARNING, "Update check returned HTTP status code: %ld", httpStatusCode); }); - fetchLatestData(additionalUrls, processDataFunc, outputPaths); + fetchLatestData(additionalUrls, processDataFunc, outputPaths, completionHandler); return; } @@ -734,7 +880,7 @@ static void fetchLatestData(const std::vector &updateDataUrls, Proc wzAsyncExecOnMainThread([urlCopy, errorStr]{ debug(LOG_INFO, "Failed to verify signature: %s; %s", errorStr.c_str(), urlCopy.c_str()); }); - fetchLatestData(additionalUrls, processDataFunc, outputPaths); + fetchLatestData(additionalUrls, processDataFunc, outputPaths, completionHandler); return; } @@ -748,7 +894,7 @@ static void fetchLatestData(const std::vector &updateDataUrls, Proc wzAsyncExecOnMainThread([urlCopy, errorStr]{ debug(LOG_INFO, "Failed to parse JSON: %s; %s", errorStr.c_str(), urlCopy.c_str()); }); - fetchLatestData(additionalUrls, processDataFunc, outputPaths); + fetchLatestData(additionalUrls, processDataFunc, outputPaths, completionHandler); return; } @@ -759,7 +905,7 @@ static void fetchLatestData(const std::vector &updateDataUrls, Proc { // signature is invalid, or data is expired, and there are further urls to try to fetch // instead of proceeding, try the next url - fetchLatestData(additionalUrls, processDataFunc, outputPaths); + fetchLatestData(additionalUrls, processDataFunc, outputPaths, completionHandler); return; } @@ -773,9 +919,17 @@ static void fetchLatestData(const std::vector &updateDataUrls, Proc wzAsyncExecOnMainThread([]{ debug(LOG_ERROR, "Missing processDataFunc"); }); + if (completionHandler) + { + completionHandler(); + } return; } const auto processResult = processDataFunc(updateData, validSignature, validExpiry); + if (completionHandler) + { + completionHandler(); + } if (validSignature && (processResult != ProcessResult::INVALID_JSON) && isValidExpiry(updateData)) { @@ -825,7 +979,7 @@ static void fetchLatestData(const std::vector &updateDataUrls, Proc } } }; - pRequest->onFailure = [additionalUrls, processDataFunc, outputPaths](const std::string& url, URLRequestFailureType type, optional transferDetails) { + pRequest->onFailure = [additionalUrls, processDataFunc, outputPaths, completionHandler](const std::string& url, URLRequestFailureType type, optional transferDetails) { bool tryNextUrl = false; switch (type) { @@ -865,7 +1019,7 @@ static void fetchLatestData(const std::vector &updateDataUrls, Proc } if (tryNextUrl) { - fetchLatestData(additionalUrls, processDataFunc, outputPaths); + fetchLatestData(additionalUrls, processDataFunc, outputPaths, completionHandler); } }; pRequest->maxDownloadSizeLimit = WZ_UPDATES_JSON_MAX_SIZE; // 32 MB (the response should never be this big) @@ -914,3 +1068,26 @@ void WzInfoManager::shutdown() { /* currently, no-op */ } + +// Get the compat check results +// NOTE: resultClosure may be called on any thread at any time - use wzAsyncExecOnMainThread inside your closure if you need to perform tasks on the main thread +void asyncGetCompatCheckResults(CompatCheckResultsHandlerFunc resultClosure) +{ + CompatCheckResults resultsCopy = CompatCheckResults(false); + + { + std::lock_guard guard(compatCheckResultsMutex); + // check if already have results + if (!compatCheckResults.has_value()) + { + // do not (yet) have results - add closure to the registry + registeredCompatCheckResultsHandlers.push_back(resultClosure); + return; + } + + // already have results - copy them + resultsCopy = compatCheckResults.value(); + } + + resultClosure(resultsCopy); +} diff --git a/src/updatemanager.h b/src/updatemanager.h index 86dd94f8c12..b465cd49d01 100644 --- a/src/updatemanager.h +++ b/src/updatemanager.h @@ -19,10 +19,61 @@ #ifndef _WZ_UPDATE_MANAGER_H_ #define _WZ_UPDATE_MANAGER_H_ +#include "terrain_defs.h" +#include +#include + +#include +using nonstd::optional; +using nonstd::nullopt; + + class WzInfoManager { public: static void initialize(); static void shutdown(); }; + +struct CompatCheckIssue +{ + enum class Severity + { + Warning, + Critical + }; + struct ConfigFlags + { + std::unordered_set supportedTerrain = {TerrainShaderQuality::CLASSIC, TerrainShaderQuality::MEDIUM, TerrainShaderQuality::NORMAL_MAPPING}; + bool multilobby = true; + }; + + std::string identifier; + Severity severity = Severity::Warning; + bool unsupported = false; + std::string infoLink; + ConfigFlags configFlags; +}; + +struct CompatCheckResults +{ + optional issue; + bool successfulCheck = false; + +public: + CompatCheckResults(bool successfulCheck, optional issue = nullopt) + : issue(issue) + , successfulCheck(successfulCheck) + { } + +public: + bool hasIssue() const { return issue.has_value(); } +}; + +typedef std::function CompatCheckResultsHandlerFunc; + +// Get the compat check results +// NOTE: resultClosure may be called on any thread at any time - use wzAsyncExecOnMainThread inside your closure if you need to perform tasks on the main thread +void asyncGetCompatCheckResults(CompatCheckResultsHandlerFunc resultClosure); + #endif //_WZ_UPDATE_MANAGER_H_