From e393484d4217ea109d46d7e2b07f550809f0e7c1 Mon Sep 17 00:00:00 2001 From: past-due <30942300+past-due@users.noreply.github.com> Date: Sat, 6 Jul 2024 14:27:39 -0400 Subject: [PATCH] Display information for loading errors caused by mods --- lib/ivis_opengl/imd.h | 3 +++ lib/ivis_opengl/imdload.cpp | 32 +++++++++++++++++++++- src/init.cpp | 53 +++++++++++++++++++++++++++++++++++++ src/stats.cpp | 11 ++++++++ src/stats.h | 2 ++ 5 files changed, 100 insertions(+), 1 deletion(-) diff --git a/lib/ivis_opengl/imd.h b/lib/ivis_opengl/imd.h index 703308eff01..6f325cc4cc9 100644 --- a/lib/ivis_opengl/imd.h +++ b/lib/ivis_opengl/imd.h @@ -69,4 +69,7 @@ iIMDBaseShape *modelGet(const WzString &filename); void modelReloadAllModelTextures(); +size_t getModelLoadingErrorCount(); +size_t getModelTextureLoadingFailuresCount(); + #endif diff --git a/lib/ivis_opengl/imdload.cpp b/lib/ivis_opengl/imdload.cpp index 8aa0e810368..e6e9493e2f4 100644 --- a/lib/ivis_opengl/imdload.cpp +++ b/lib/ivis_opengl/imdload.cpp @@ -53,6 +53,9 @@ typedef std::unordered_map> ModelMap static ModelMap models; static size_t currentTilesetIdx = 0; +static size_t modelLoadingErrors = 0; +static size_t modelTextureLoadingFailures = 0; + static std::unique_ptr iV_ProcessIMD(const WzString &filename, const char **ppFileData, const char *FileDataEnd, bool skipGPUData); static bool _imd_load_level_textures(const iIMDShape& s, size_t tilesetIdx, iIMDShapeTextures& output); @@ -70,7 +73,10 @@ const iIMDShapeTextures& iIMDShape::getTextures() const if (!m_textures->initialized) { // Load the textures on-demand - _imd_load_level_textures(*this, currentTilesetIdx, *m_textures); + if (!_imd_load_level_textures(*this, currentTilesetIdx, *m_textures)) + { + ++modelTextureLoadingFailures; + } m_textures->initialized = true; } @@ -111,9 +117,21 @@ void iIMDBaseShape::replaceDisplayModel(std::unique_ptr newDisplayMod m_displayModel = std::move(newDisplayModel); } +size_t getModelLoadingErrorCount() +{ + return modelLoadingErrors; +} + +size_t getModelTextureLoadingFailuresCount() +{ + return modelTextureLoadingFailures; +} + void modelShutdown() { models.clear(); + modelLoadingErrors = 0; + modelTextureLoadingFailures = 0; } void modelReloadAllModelTextures() @@ -1680,6 +1698,7 @@ static std::unique_ptr iV_ProcessIMD(const WzString &filename, const if (!_imd_get_next_line(pFileData, FileDataEnd, lineToProcess) || sscanf(lineToProcess.lineContents.c_str(), "%255s %d", buffer, &imd_version) != 2) { debug(LOG_ERROR, "%s: bad PIE version: (%s)", filename.toUtf8().c_str(), buffer); + ++modelLoadingErrors; assert(false); return nullptr; } @@ -1688,6 +1707,7 @@ static std::unique_ptr iV_ProcessIMD(const WzString &filename, const if (strcmp(PIE_NAME, buffer) != 0) { debug(LOG_ERROR, "%s: Not an IMD file (%s %d)", filename.toUtf8().c_str(), buffer, imd_version); + ++modelLoadingErrors; return nullptr; } @@ -1695,6 +1715,7 @@ static std::unique_ptr iV_ProcessIMD(const WzString &filename, const if (imd_version < PIE_MIN_VER || imd_version > PIE_MAX_VER) { debug(LOG_ERROR, "%s: Version %d not supported", filename.toUtf8().c_str(), imd_version); + ++modelLoadingErrors; return nullptr; } @@ -1702,6 +1723,7 @@ static std::unique_ptr iV_ProcessIMD(const WzString &filename, const if (!_imd_load_level_settings(filename, &pFileData, FileDataEnd, imd_version, true, globalLevelSettings)) { debug(LOG_ERROR, "%s: Failed to load level settings", filename.toUtf8().c_str()); + ++modelLoadingErrors; return nullptr; } // TYPE is required in the global scope @@ -1728,6 +1750,7 @@ static std::unique_ptr iV_ProcessIMD(const WzString &filename, const if (!getNextPossibleCommandLine()) { debug(LOG_ERROR, "%s: Expecting EVENT or LEVELS: %s", filename.toUtf8().c_str(), buffer); + ++modelLoadingErrors; return nullptr; } @@ -1744,6 +1767,7 @@ static std::unique_ptr iV_ProcessIMD(const WzString &filename, const if (sscanf(pRestOfLine, "%255s%n", animpie, &cnt) != 1) { debug(LOG_ERROR, "%s animation model corrupt: %s", filename.toUtf8().c_str(), buffer); + ++modelLoadingErrors; return nullptr; } @@ -1753,6 +1777,7 @@ static std::unique_ptr iV_ProcessIMD(const WzString &filename, const if (!getNextPossibleCommandLine()) { debug(LOG_ERROR, "%s: Bad levels info: %s", filename.toUtf8().c_str(), buffer); + ++modelLoadingErrors; return nullptr; } } @@ -1760,6 +1785,7 @@ static std::unique_ptr iV_ProcessIMD(const WzString &filename, const if (strncmp(buffer, "LEVELS", 6) != 0) { debug(LOG_ERROR, "%s: Expecting 'LEVELS' directive (%s)", filename.toUtf8().c_str(), buffer); + ++modelLoadingErrors; return nullptr; } nlevels = value; @@ -1772,16 +1798,19 @@ static std::unique_ptr iV_ProcessIMD(const WzString &filename, const if (!getNextPossibleCommandLine()) { debug(LOG_ERROR, "(_load_level) file corrupt -J"); + ++modelLoadingErrors; return nullptr; } if (strncmp(buffer, "LEVEL", 5) != 0) { debug(LOG_ERROR, "%s: Expecting 'LEVEL' directive (%s)", filename.toUtf8().c_str(), buffer); + ++modelLoadingErrors; return nullptr; } if (value != (level + 1)) { debug(LOG_ERROR, "%s: LEVEL %" PRIu32 " is invalid - expecting LEVEL %" PRIu32 " (LEVELS must be sequential, starting at 1)", filename.toUtf8().c_str(), value, level); + ++modelLoadingErrors; return nullptr; } @@ -1789,6 +1818,7 @@ static std::unique_ptr iV_ProcessIMD(const WzString &filename, const if (shape == nullptr) { debug(LOG_ERROR, "%s: Unsuccessful loading level %" PRIu32, filename.toUtf8().c_str(), (level + 1)); + ++modelLoadingErrors; return nullptr; } diff --git a/src/init.cpp b/src/init.cpp index b007a9053e5..6d6ce0b4d5a 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1815,6 +1815,56 @@ bool stageTwoShutDown() return true; } +static void displayLoadingErrors() +{ + std::vector loadingErrors; + + // warn the user if a mod might have corrupted models / files + auto modelLoadingErrorCount = getModelLoadingErrorCount(); + if (modelLoadingErrorCount > 0) + { + debug(LOG_INFO, "Failure to load %zu game model(s) - please remove any outdated or broken mods", modelLoadingErrorCount); + loadingErrors.push_back(astringf(_("Failure to load %zu game model(s)"), modelLoadingErrorCount)); + } + auto modelTextureLoadingFailures = getModelTextureLoadingFailuresCount(); + if (modelTextureLoadingFailures > 0) + { + debug(LOG_INFO, "Failed to load textures for %zu models - please remove any outdated or broken mods", modelTextureLoadingFailures); + loadingErrors.push_back(astringf(_("Failure to load textures for %zu model(s)"), modelTextureLoadingFailures)); + } + auto statModelLoadingFailures = getStatModelLoadingFailures(); + if (statModelLoadingFailures > 0) + { + debug(LOG_INFO, "Failed to load model for %zu stats entries - please remove any outdated or broken mods", statModelLoadingFailures); + loadingErrors.push_back(astringf(_("Failure to load model for %zu stat(s)"), statModelLoadingFailures)); + } + + if (!loadingErrors.empty()) + { + std::string modelErrorDescription = _("Warzone 2100 encountered error(s) loading game data:"); + for (const auto& error : loadingErrors) + { + modelErrorDescription += "\n"; + modelErrorDescription += "- "; + modelErrorDescription += error; + } + modelErrorDescription += "\n\n"; + if (!getLoadedMods().empty()) + { + modelErrorDescription += _("Please try removing any new mods - they may have issues or be incompatible with this version."); + modelErrorDescription += "\n\n"; + modelErrorDescription += _("Loaded mod(s):"); + modelErrorDescription += "\n"; + modelErrorDescription += std::string("- ") + getModList(); + } + else + { + modelErrorDescription += _("Base game files may be corrupt or outdated - please try reinstalling the game."); + } + wzDisplayDialog(Dialog_Error, _("Error Loading Game Data"), modelErrorDescription.c_str()); + } +} + bool stageThreeInitialise() { bool fromSave = (getSaveGameType() == GTYPE_SAVE_START || getSaveGameType() == GTYPE_SAVE_MIDMISSION); @@ -1929,6 +1979,9 @@ bool stageThreeInitialise() // always start off with a refresh of the groups UI data intGroupsChanged(); + // warn the user if a mod might have corrupted models / files + displayLoadingErrors(); + if (bMultiPlayer) { cameraToHome(selectedPlayer, false, fromSave); diff --git a/src/stats.cpp b/src/stats.cpp index 32eba101284..d74e53532a1 100644 --- a/src/stats.cpp +++ b/src/stats.cpp @@ -70,6 +70,7 @@ UBYTE *apStructTypeLists[MAX_PLAYERS]; static std::unordered_map lookupStatPtr; static std::unordered_map lookupCompStatPtr; +static size_t statModelLoadingFailures = 0; static bool getMovementModel(const WzString &movementModel, MOVEMENT_MODEL *model); static bool statsGetAudioIDFromString(const WzString &szStatName, const WzString &szWavName, int *piWavID); @@ -118,9 +119,15 @@ bool statsShutDown() deallocPropulsionTypes(); deallocTerrainTable(); + statModelLoadingFailures = 0; + return true; } +size_t getStatModelLoadingFailures() +{ + return statModelLoadingFailures; +} /******************************************************************************* * Allocate stats functions @@ -254,6 +261,10 @@ static iIMDBaseShape *statsGetIMD(WzConfig &json, BASE_STATS *psStats, const WzS WzString filename = json_variant(value).toWzString(); deprecatedModelUpgrade(filename); // FUTURE TODO: Use output of this to auto-upgrade old model references? (Check for empty string) retval = modelGet(filename); + if (retval == nullptr) + { + ++statModelLoadingFailures; + } ASSERT(retval != nullptr, "Cannot find the PIE model %s for stat %s in %s", filename.toUtf8().c_str(), getStatsName(psStats), json.fileName().toUtf8().c_str()); } diff --git a/src/stats.h b/src/stats.h index b8cb9232933..488824fdab8 100644 --- a/src/stats.h +++ b/src/stats.h @@ -144,6 +144,8 @@ bool loadWeaponModifiers(WzConfig &ini); /*calls the STATS_DEALLOC macro for each set of stats*/ bool statsShutDown(); +size_t getStatModelLoadingFailures(); + UDWORD getSpeedFactor(UDWORD terrainType, UDWORD propulsionType); /// Get the component index for a component based on the name, verifying with type.