From eb167d94a6ec22931f0252e537daaced6345caeb Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 7 Nov 2023 18:32:40 +0200 Subject: [PATCH] Mod compatibility check is now in a separate class and not part of ModHandler --- client/mapView/mapHandler.cpp | 4 + cmake_modules/VCMI_lib.cmake | 3 + lib/IGameCallback.cpp | 9 +++ lib/mapping/CMapHeader.h | 2 +- lib/mapping/CMapService.h | 2 +- lib/mapping/MapFormatJson.cpp | 2 +- lib/modding/ActiveModsInSaveList.cpp | 112 +++++++++++++++++++++++++++ lib/modding/ActiveModsInSaveList.h | 51 ++++++++++++ lib/modding/CModHandler.cpp | 82 -------------------- lib/modding/CModHandler.h | 37 ++------- lib/modding/CModInfo.cpp | 2 +- lib/modding/CModInfo.h | 36 +-------- lib/modding/CModVersion.h | 2 + lib/modding/ModVerificationInfo.h | 44 +++++++++++ mapeditor/mapcontroller.h | 2 +- 15 files changed, 238 insertions(+), 152 deletions(-) create mode 100644 lib/modding/ActiveModsInSaveList.cpp create mode 100644 lib/modding/ActiveModsInSaveList.h create mode 100644 lib/modding/ModVerificationInfo.h diff --git a/client/mapView/mapHandler.cpp b/client/mapView/mapHandler.cpp index fdd4fae86..aac3232e1 100644 --- a/client/mapView/mapHandler.cpp +++ b/client/mapView/mapHandler.cpp @@ -74,6 +74,10 @@ std::string CMapHandler::getTerrainDescr(const int3 & pos, bool rightClick) cons bool CMapHandler::compareObjectBlitOrder(const CGObjectInstance * a, const CGObjectInstance * b) { + //FIXME: Optimize + // this method is called A LOT on game start and some parts, e.g. for loops are too slow for that + + assert(a && b); if(!a) return true; if(!b) diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index 0b969d102..3e7663c2b 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/cmake_modules/VCMI_lib.cmake @@ -116,6 +116,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/mapping/MapFormatJson.cpp ${MAIN_LIB_DIR}/mapping/ObstacleProxy.cpp + ${MAIN_LIB_DIR}/modding/ActiveModsInSaveList.cpp ${MAIN_LIB_DIR}/modding/CModHandler.cpp ${MAIN_LIB_DIR}/modding/CModInfo.cpp ${MAIN_LIB_DIR}/modding/CModVersion.cpp @@ -466,6 +467,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/mapping/MapFormatJson.h ${MAIN_LIB_DIR}/mapping/ObstacleProxy.h + ${MAIN_LIB_DIR}/modding/ActiveModsInSaveList.h ${MAIN_LIB_DIR}/modding/CModHandler.h ${MAIN_LIB_DIR}/modding/CModInfo.h ${MAIN_LIB_DIR}/modding/CModVersion.h @@ -474,6 +476,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/modding/ModIncompatibility.h ${MAIN_LIB_DIR}/modding/ModScope.h ${MAIN_LIB_DIR}/modding/ModUtility.h + ${MAIN_LIB_DIR}/modding/ModVerificationInfo.h ${MAIN_LIB_DIR}/networkPacks/ArtifactLocation.h ${MAIN_LIB_DIR}/networkPacks/BattleChanges.h diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index 4f625228a..76ff89457 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -40,6 +40,7 @@ #include "modding/CModInfo.h" #include "modding/IdentifierStorage.h" #include "modding/CModVersion.h" +#include "modding/ActiveModsInSaveList.h" #include "CPlayerState.h" #include "GameSettings.h" #include "ScriptHandler.h" @@ -183,6 +184,7 @@ void CPrivilegedInfoCallback::loadCommonState(Loader & in) CMapHeader dum; StartInfo * si = nullptr; + ActiveModsInSaveList activeMods; logGlobal->info("\tReading header"); in.serializer & dum; @@ -190,6 +192,9 @@ void CPrivilegedInfoCallback::loadCommonState(Loader & in) logGlobal->info("\tReading options"); in.serializer & si; + logGlobal->info("\tReading mod list"); + in.serializer & activeMods; + logGlobal->info("\tReading gamestate"); in.serializer & gs; } @@ -197,12 +202,16 @@ void CPrivilegedInfoCallback::loadCommonState(Loader & in) template void CPrivilegedInfoCallback::saveCommonState(Saver & out) const { + ActiveModsInSaveList activeMods; + logGlobal->info("Saving lib part of game..."); out.putMagicBytes(SAVEGAME_MAGIC); logGlobal->info("\tSaving header"); out.serializer & static_cast(*gs->map); logGlobal->info("\tSaving options"); out.serializer & gs->scenarioOps; + logGlobal->info("\tSaving mod list"); + out.serializer & activeMods; logGlobal->info("\tSaving gamestate"); out.serializer & gs; } diff --git a/lib/mapping/CMapHeader.h b/lib/mapping/CMapHeader.h index a64a08dfe..b492237dc 100644 --- a/lib/mapping/CMapHeader.h +++ b/lib/mapping/CMapHeader.h @@ -20,7 +20,7 @@ VCMI_LIB_NAMESPACE_BEGIN class CGObjectInstance; enum class EMapFormat : uint8_t; -using ModCompatibilityInfo = std::map; +using ModCompatibilityInfo = std::map; /// The hero name struct consists of the hero id and the hero name. struct DLL_LINKAGE SHeroName diff --git a/lib/mapping/CMapService.h b/lib/mapping/CMapService.h index ed791197b..f2b89d423 100644 --- a/lib/mapping/CMapService.h +++ b/lib/mapping/CMapService.h @@ -23,7 +23,7 @@ class CInputStream; class IMapLoader; class IMapPatcher; -using ModCompatibilityInfo = std::map; +using ModCompatibilityInfo = std::map; /** * The map service provides loading of VCMI/H3 map files. It can diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index 0f5691da8..62ad052f4 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -966,7 +966,7 @@ void CMapLoaderJson::readHeader(const bool complete) { for(auto & mod : header["mods"].Vector()) { - CModInfo::VerificationInfo info; + ModVerificationInfo info; info.version = CModVersion::fromString(mod["version"].String()); info.checksum = mod["checksum"].Integer(); info.name = mod["name"].String(); diff --git a/lib/modding/ActiveModsInSaveList.cpp b/lib/modding/ActiveModsInSaveList.cpp new file mode 100644 index 000000000..264e122ae --- /dev/null +++ b/lib/modding/ActiveModsInSaveList.cpp @@ -0,0 +1,112 @@ +/* + * ActiveModsInSaveList.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "ActiveModsInSaveList.h" + +#include "../VCMI_Lib.h" +#include "CModInfo.h" +#include "CModHandler.h" +#include "ModIncompatibility.h" + +VCMI_LIB_NAMESPACE_BEGIN + +std::vector ActiveModsInSaveList::getActiveMods() +{ + return VLC->modh->getActiveMods(); +} + +const ModVerificationInfo & ActiveModsInSaveList::getVerificationInfo(TModID mod) +{ + return VLC->modh->getModInfo(mod).getVerificationInfo(); +} + +void ActiveModsInSaveList::verifyActiveMods(const std::vector> & modList) +{ + auto searchVerificationInfo = [&modList](const TModID & m) -> const ModVerificationInfo* + { + for(auto & i : modList) + if(i.first == m) + return &i.second; + return nullptr; + }; + + std::vector missingMods, excessiveMods; + ModIncompatibility::ModListWithVersion missingModsResult; + ModIncompatibility::ModList excessiveModsResult; + + for(const auto & m : VLC->modh->getActiveMods()) + { + if(searchVerificationInfo(m)) + continue; + + //TODO: support actual disabling of these mods + if(VLC->modh->getModInfo(m).checkModGameplayAffecting()) + excessiveMods.push_back(m); + } + + for(const auto & infoPair : modList) + { + auto & remoteModId = infoPair.first; + auto & remoteModInfo = infoPair.second; + + bool modAffectsGameplay = remoteModInfo.impactsGameplay; + //parent mod affects gameplay if child affects too + for(const auto & subInfoPair : modList) + modAffectsGameplay |= (subInfoPair.second.impactsGameplay && subInfoPair.second.parent == remoteModId); + + if(!vstd::contains(VLC->modh->getAllMods(), remoteModId)) + { + if(modAffectsGameplay) + missingMods.push_back(remoteModId); //mod is not installed + continue; + } + + auto & localModInfo = VLC->modh->getModInfo(remoteModId).getVerificationInfo(); + modAffectsGameplay |= VLC->modh->getModInfo(remoteModId).checkModGameplayAffecting(); + bool modVersionCompatible = localModInfo.version.isNull() + || remoteModInfo.version.isNull() + || localModInfo.version.compatible(remoteModInfo.version); + bool modLocalyEnabled = vstd::contains(VLC->modh->getActiveMods(), remoteModId); + + if(modVersionCompatible && modAffectsGameplay && modLocalyEnabled) + continue; + + if(modAffectsGameplay) + missingMods.push_back(remoteModId); //incompatible mod impacts gameplay + } + + //filter mods + for(auto & m : missingMods) + { + if(auto * vInfo = searchVerificationInfo(m)) + { + assert(vInfo->parent != m); + if(!vInfo->parent.empty() && vstd::contains(missingMods, vInfo->parent)) + continue; + missingModsResult.push_back({vInfo->name, vInfo->version.toString()}); + } + } + for(auto & m : excessiveMods) + { + auto & vInfo = VLC->modh->getModInfo(m).getVerificationInfo(); + assert(vInfo.parent != m); + if(!vInfo.parent.empty() && vstd::contains(excessiveMods, vInfo.parent)) + continue; + excessiveModsResult.push_back(vInfo.name); + } + + if(!missingModsResult.empty() || !excessiveModsResult.empty()) + throw ModIncompatibility(missingModsResult, excessiveModsResult); + + //TODO: support actual enabling of required mods +} + + +VCMI_LIB_NAMESPACE_END diff --git a/lib/modding/ActiveModsInSaveList.h b/lib/modding/ActiveModsInSaveList.h new file mode 100644 index 000000000..846b51af7 --- /dev/null +++ b/lib/modding/ActiveModsInSaveList.h @@ -0,0 +1,51 @@ +/* + * ActiveModsInSaveList.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "ModVerificationInfo.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class ActiveModsInSaveList +{ + std::vector getActiveMods(); + const ModVerificationInfo & getVerificationInfo(TModID mod); + + /// Checks whether provided mod list is compatible with current VLC and throws on failure + void verifyActiveMods(const std::vector> & modList); +public: + template void serialize(Handler &h, const int version) + { + if(h.saving) + { + std::vector activeMods = getActiveMods(); + h & activeMods; + for(const auto & m : activeMods) + h & getVerificationInfo(m); + } + else + { + std::vector saveActiveMods; + h & saveActiveMods; + + std::vector> saveModInfos(saveActiveMods.size()); + for(int i = 0; i < saveActiveMods.size(); ++i) + { + saveModInfos[i].first = saveActiveMods[i]; + h & saveModInfos[i].second; + } + + verifyActiveMods(saveModInfos); + } + } +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index 983e80130..7c220914d 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -491,88 +491,6 @@ void CModHandler::afterLoad(bool onlyEssential) std::fstream file(CResourceHandler::get()->getResourceName(ResourcePath("config/modSettings.json"))->c_str(), std::ofstream::out | std::ofstream::trunc); file << modSettings.toJson(); } - -} - -void CModHandler::trySetActiveMods(const std::vector> & modList) -{ - auto searchVerificationInfo = [&modList](const TModID & m) -> const CModInfo::VerificationInfo* - { - for(auto & i : modList) - if(i.first == m) - return &i.second; - return nullptr; - }; - - std::vector missingMods, excessiveMods; - ModIncompatibility::ModListWithVersion missingModsResult; - ModIncompatibility::ModList excessiveModsResult; - - for(const auto & m : activeMods) - { - if(searchVerificationInfo(m)) - continue; - - //TODO: support actual disabling of these mods - if(getModInfo(m).checkModGameplayAffecting()) - excessiveMods.push_back(m); - } - - for(const auto & infoPair : modList) - { - auto & remoteModId = infoPair.first; - auto & remoteModInfo = infoPair.second; - - bool modAffectsGameplay = remoteModInfo.impactsGameplay; - //parent mod affects gameplay if child affects too - for(const auto & subInfoPair : modList) - modAffectsGameplay |= (subInfoPair.second.impactsGameplay && subInfoPair.second.parent == remoteModId); - - if(!allMods.count(remoteModId)) - { - if(modAffectsGameplay) - missingMods.push_back(remoteModId); //mod is not installed - continue; - } - - auto & localModInfo = getModInfo(remoteModId).getVerificationInfo(); - modAffectsGameplay |= getModInfo(remoteModId).checkModGameplayAffecting(); - bool modVersionCompatible = localModInfo.version.isNull() - || remoteModInfo.version.isNull() - || localModInfo.version.compatible(remoteModInfo.version); - bool modLocalyEnabled = vstd::contains(activeMods, remoteModId); - - if(modVersionCompatible && modAffectsGameplay && modLocalyEnabled) - continue; - - if(modAffectsGameplay) - missingMods.push_back(remoteModId); //incompatible mod impacts gameplay - } - - //filter mods - for(auto & m : missingMods) - { - if(auto * vInfo = searchVerificationInfo(m)) - { - assert(vInfo->parent != m); - if(!vInfo->parent.empty() && vstd::contains(missingMods, vInfo->parent)) - continue; - missingModsResult.push_back({vInfo->name, vInfo->version.toString()}); - } - } - for(auto & m : excessiveMods) - { - auto & vInfo = getModInfo(m).getVerificationInfo(); - assert(vInfo.parent != m); - if(!vInfo.parent.empty() && vstd::contains(excessiveMods, vInfo.parent)) - continue; - excessiveModsResult.push_back(vInfo.name); - } - - if(!missingModsResult.empty() || !excessiveModsResult.empty()) - throw ModIncompatibility(missingModsResult, excessiveModsResult); - - //TODO: support actual enabling of required mods } VCMI_LIB_NAMESPACE_END diff --git a/lib/modding/CModHandler.h b/lib/modding/CModHandler.h index af0959fe9..2842d90aa 100644 --- a/lib/modding/CModHandler.h +++ b/lib/modding/CModHandler.h @@ -9,21 +9,22 @@ */ #pragma once -#include "CModInfo.h" - VCMI_LIB_NAMESPACE_BEGIN class CModHandler; class CModIndentifier; +class CModInfo; +struct CModVersion; class JsonNode; class IHandlerBase; class CIdentifierStorage; class CContentHandler; +struct ModVerificationInfo; class ResourcePath; using TModID = std::string; -class DLL_LINKAGE CModHandler : boost::noncopyable +class DLL_LINKAGE CModHandler final : boost::noncopyable { std::map allMods; std::vector activeMods;//active mods, in order in which they were loaded @@ -50,9 +51,6 @@ class DLL_LINKAGE CModHandler : boost::noncopyable CModVersion getModVersion(TModID modName) const; - /// Attempt to set active mods according to provided list of mods from save, throws on failure - void trySetActiveMods(const std::vector> & modList); - public: std::shared_ptr content; //(!)Do not serialize FIXME: make private @@ -79,32 +77,7 @@ public: void afterLoad(bool onlyEssential); CModHandler(); - virtual ~CModHandler(); - - template void serialize(Handler &h, const int version) - { - if(h.saving) - { - h & activeMods; - for(const auto & m : activeMods) - h & getModInfo(m).getVerificationInfo(); - } - else - { - loadMods(); - std::vector saveActiveMods; - h & saveActiveMods; - - std::vector> saveModInfos(saveActiveMods.size()); - for(int i = 0; i < saveActiveMods.size(); ++i) - { - saveModInfos[i].first = saveActiveMods[i]; - h & saveModInfos[i].second; - } - - trySetActiveMods(saveModInfos); - } - } + ~CModHandler(); }; VCMI_LIB_NAMESPACE_END diff --git a/lib/modding/CModInfo.cpp b/lib/modding/CModInfo.cpp index 0e534da45..7cb346789 100644 --- a/lib/modding/CModInfo.cpp +++ b/lib/modding/CModInfo.cpp @@ -177,7 +177,7 @@ bool CModInfo::checkModGameplayAffecting() const return *modGameplayAffecting; } -const CModInfo::VerificationInfo & CModInfo::getVerificationInfo() const +const ModVerificationInfo & CModInfo::getVerificationInfo() const { return verificationInfo; } diff --git a/lib/modding/CModInfo.h b/lib/modding/CModInfo.h index f9f227e2a..2c29e2159 100644 --- a/lib/modding/CModInfo.h +++ b/lib/modding/CModInfo.h @@ -10,12 +10,10 @@ #pragma once #include "../JsonNode.h" -#include "CModVersion.h" +#include "ModVerificationInfo.h" VCMI_LIB_NAMESPACE_BEGIN -using TModID = std::string; - class DLL_LINKAGE CModInfo { /// cached result of checkModGameplayAffecting() call @@ -30,34 +28,6 @@ public: PASSED }; - struct VerificationInfo - { - /// human-readable mod name - std::string name; - - /// version of the mod - CModVersion version; - - /// CRC-32 checksum of the mod - ui32 checksum = 0; - - /// parent mod ID, empty if root-level mod - TModID parent; - - /// for serialization purposes - bool impactsGameplay = true; - - template - void serialize(Handler & h, const int v) - { - h & name; - h & version; - h & checksum; - h & parent; - h & impactsGameplay; - } - }; - /// identifier, identical to name of folder with mod std::string identifier; @@ -94,7 +64,7 @@ public: /// return true if this mod can affect gameplay, e.g. adds or modifies any game objects bool checkModGameplayAffecting() const; - const VerificationInfo & getVerificationInfo() const; + const ModVerificationInfo & getVerificationInfo() const; private: /// true if mod is enabled by user, e.g. in Launcher UI @@ -103,7 +73,7 @@ private: /// true if mod can be loaded - compatible and has no missing deps bool implicitlyEnabled; - VerificationInfo verificationInfo; + ModVerificationInfo verificationInfo; void loadLocalData(const JsonNode & data); }; diff --git a/lib/modding/CModVersion.h b/lib/modding/CModVersion.h index 15221dab3..e0ae7c8be 100644 --- a/lib/modding/CModVersion.h +++ b/lib/modding/CModVersion.h @@ -18,6 +18,8 @@ VCMI_LIB_NAMESPACE_BEGIN +using TModID = std::string; + struct DLL_LINKAGE CModVersion { static const int Any = -1; diff --git a/lib/modding/ModVerificationInfo.h b/lib/modding/ModVerificationInfo.h new file mode 100644 index 000000000..e6652e40d --- /dev/null +++ b/lib/modding/ModVerificationInfo.h @@ -0,0 +1,44 @@ +/* + * ModVerificationInfo.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "CModVersion.h" + +VCMI_LIB_NAMESPACE_BEGIN + +struct ModVerificationInfo +{ + /// human-readable mod name + std::string name; + + /// version of the mod + CModVersion version; + + /// CRC-32 checksum of the mod + ui32 checksum = 0; + + /// parent mod ID, empty if root-level mod + TModID parent; + + /// for serialization purposes + bool impactsGameplay = true; + + template + void serialize(Handler & h, const int v) + { + h & name; + h & version; + h & checksum; + h & parent; + h & impactsGameplay; + } +}; + +VCMI_LIB_NAMESPACE_END diff --git a/mapeditor/mapcontroller.h b/mapeditor/mapcontroller.h index 18d660a3a..7b8a246eb 100644 --- a/mapeditor/mapcontroller.h +++ b/mapeditor/mapcontroller.h @@ -16,7 +16,7 @@ #include "../lib/modding/CModInfo.h" VCMI_LIB_NAMESPACE_BEGIN -using ModCompatibilityInfo = std::map; +using ModCompatibilityInfo = std::map; class EditorObstaclePlacer; VCMI_LIB_NAMESPACE_END