From ef0cd0ba6ed40c0f9c2a0759ce6d3f1b748c6563 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 14 Aug 2023 23:37:16 +0300 Subject: [PATCH] Fixed mod dependencies check on save loading to prevent crashes: - mods that don't affect game objects can be ignored when loading save - gameplay-affecting mods that were present in save must be active - gameplay-affecting mods that were not in save must not be active --- lib/CModHandler.cpp | 42 +++++++++++++++++++++++++++++++++++++++++ lib/CModHandler.h | 46 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index 1a78d708f..9b8247295 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -649,6 +649,48 @@ void CModInfo::updateChecksum(ui32 newChecksum) } } +bool CModInfo::checkModGameplayAffecting() const +{ + if (modGameplayAffecting.has_value()) + return *modGameplayAffecting; + + static const std::vector keysToTest = { + "heroClasses", + "artifacts", + "creatures", + "factions", + "objects", + "heroes", + "spells", + "skills", + "templates", + "scripts", + "battlefields", + "terrains", + "rivers", + "roads", + "obstacles" + }; + + ResourceID modFileResource(CModInfo::getModFile(identifier)); + + if(CResourceHandler::get("initial")->existsResource(modFileResource)) + { + const JsonNode modConfig(modFileResource); + + for (auto const & key : keysToTest) + { + if (!modConfig[key].isNull()) + { + modGameplayAffecting = true; + return *modGameplayAffecting; + } + } + } + modGameplayAffecting = false; + return *modGameplayAffecting; +} + void CModInfo::loadLocalData(const JsonNode & data) { bool validated = false; diff --git a/lib/CModHandler.h b/lib/CModHandler.h index 30325f246..887422dbc 100644 --- a/lib/CModHandler.h +++ b/lib/CModHandler.h @@ -179,6 +179,10 @@ using TModID = std::string; class DLL_LINKAGE CModInfo { + /// cached result of checkModGameplayAffecting() call + /// Do not serialize - depends on local mod version, not server/save mod version + mutable std::optional modGameplayAffecting; + public: enum EValidationStatus { @@ -223,6 +227,9 @@ public: JsonNode saveLocalData() const; void updateChecksum(ui32 newChecksum); + /// return true if this mod can affect gameplay, e.g. adds or modifies any game objects + bool checkModGameplayAffecting() const; + bool isEnabled() const; void setEnabled(bool on); @@ -351,19 +358,46 @@ public: else { loadMods(); + std::vector saveActiveMods; std::vector newActiveMods; - h & newActiveMods; + h & saveActiveMods; Incompatibility::ModList missingMods; - for(const auto & m : newActiveMods) + for(const auto & m : activeMods) + { + if (vstd::contains(saveActiveMods, m)) + continue; + + auto & modInfo = allMods.at(m); + if(modInfo.checkModGameplayAffecting()) + missingMods.emplace_back(m, modInfo.version.toString()); + } + + for(const auto & m : saveActiveMods) { CModVersion mver; h & mver; - - if(allMods.count(m) && (allMods[m].version.isNull() || mver.isNull() || allMods[m].version.compatible(mver))) - allMods[m].setEnabled(true); - else + + if (allMods.count(m) == 0) + { + missingMods.emplace_back(m, mver.toString()); + continue; + } + + auto & modInfo = allMods.at(m); + + bool modAffectsGameplay = modInfo.checkModGameplayAffecting(); + bool modVersionCompatible = modInfo.version.isNull() || mver.isNull() || modInfo.version.compatible(mver); + bool modEnabledLocally = vstd::contains(activeMods, m); + bool modCanBeEnabled = modEnabledLocally && modVersionCompatible; + + allMods[m].setEnabled(modCanBeEnabled); + + if (modCanBeEnabled) + newActiveMods.push_back(m); + + if (!modCanBeEnabled && modAffectsGameplay) missingMods.emplace_back(m, mver.toString()); }