diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index 1c30b1208..135d386a4 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -532,6 +532,47 @@ JsonNode addMeta(JsonNode config, std::string meta) return config; } +CModInfo::Version CModInfo::Version::GameVersion() +{ + return Version(GameConstants::VCMI_VERSION_MAJOR, GameConstants::VCMI_VERSION_MINOR, GameConstants::VCMI_VERSION_PATCH); +} + +CModInfo::Version CModInfo::Version::fromString(std::string from) +{ + int major = 0, minor = 0, patch = 0; + try + { + auto pointPos = from.find('.'); + major = std::stoi(from.substr(0, pointPos)); + from = from.substr(pointPos); + pointPos = from.find('.'); + minor = std::stoi(from.substr(0, pointPos)); + patch = std::stoi(from.substr(pointPos)); + } + catch(const std::invalid_argument & e) + { + return Version(); + } + return Version(major, minor, patch); +} + +std::string CModInfo::Version::toString() const +{ + return std::to_string(major) + '.' + std::to_string(minor) + '.' + std::to_string(patch); +} + +bool CModInfo::Version::compatible(const Version & other, bool checkMinor, bool checkPatch) const +{ + return (major == other.major && + (!checkMinor || minor >= other.minor) && + (!checkPatch || minor > other.minor || (minor == other.minor && patch >= other.patch))); +} + +bool CModInfo::Version::isNull() const +{ + return major == 0 && minor == 0 && patch == 0; +} + CModInfo::CModInfo(): checksum(0), enabled(false), @@ -551,6 +592,12 @@ CModInfo::CModInfo(std::string identifier,const JsonNode & local, const JsonNode validation(PENDING), config(addMeta(config, identifier)) { + version = Version::fromString(config["version"].String()); + if(!config["compatibility"].isNull()) + { + vcmiCompatibleMin = Version::fromString(config["compatibility"]["min"].String()); + vcmiCompatibleMax = Version::fromString(config["compatibility"]["max"].String()); + } loadLocalData(local); } @@ -601,6 +648,10 @@ void CModInfo::loadLocalData(const JsonNode & data) validated = data["validated"].Bool(); checksum = strtol(data["checksum"].String().c_str(), nullptr, 16); } + + //check compatibility + enabled &= vcmiCompatibleMin.isNull() || Version::GameVersion().compatible(vcmiCompatibleMin); + enabled &= vcmiCompatibleMax.isNull() || vcmiCompatibleMax.compatible(Version::GameVersion()); if (enabled) validation = validated ? PASSED : PENDING; diff --git a/lib/CModHandler.h b/lib/CModHandler.h index 5df3dfc38..ae225a7cb 100644 --- a/lib/CModHandler.h +++ b/lib/CModHandler.h @@ -177,6 +177,30 @@ public: FAILED, PASSED }; + + struct Version + { + int major = 0; + int minor = 0; + int patch = 0; + + Version() = default; + Version(int mj, int mi, int p): major(mj), minor(mi), patch(p) {} + + static Version GameVersion(); + static Version fromString(std::string from); + std::string toString() const; + + bool compatible(const Version & other, bool checkMinor = false, bool checkPatch = false) const; + bool isNull() const; + + template void serialize(Handler &h, const int version) + { + h & major; + h & minor; + h & patch; + } + }; /// identifier, identical to name of folder with mod std::string identifier; @@ -184,6 +208,12 @@ public: /// human-readable strings std::string name; std::string description; + + /// version of the mod + Version version; + + ///The vcmi versions compatible with the mod + Version vcmiCompatibleMin, vcmiCompatibleMax; /// list of mods that should be loaded before this one std::set dependencies; @@ -210,7 +240,8 @@ public: static std::string getModDir(std::string name); static std::string getModFile(std::string name); - template void serialize(Handler &h, const int version) + //TODO: remove as soon as backward compatilibity for versions earlier 806 is not preserved. + template void serialize(Handler &h, const int ver) { h & identifier; h & description; @@ -256,6 +287,13 @@ class DLL_LINKAGE CModHandler void loadMods(std::string path, std::string parent, const JsonNode & modSettings, bool enableMods); void loadOneMod(std::string modName, std::string parent, const JsonNode & modSettings, bool enableMods); public: + + class Incompatibility: public std::logic_error + { + public: + Incompatibility(const std::string & w): std::logic_error(w) + {} + }; CIdentifierStorage identifiers; @@ -336,8 +374,43 @@ public: template void serialize(Handler &h, const int version) { - h & allMods; - h & activeMods; + if(version < 806) + { + h & allMods; //don't serialize mods + h & activeMods; + } + else + { + if(h.saving) + { + h & activeMods; + for(auto & m : activeMods) + h & allMods[m].version; + } + else + { + std::vector newActiveMods; + h & newActiveMods; + for(auto & m : newActiveMods) + { + if(!allMods.count(m)) + throw Incompatibility(m + " unkown mod"); + + CModInfo::Version mver; + h & mver; + if(!allMods[m].version.isNull() && !mver.isNull() && !allMods[m].version.compatible(mver)) + { + std::string err = allMods[m].name + + ": version needed " + mver.toString() + + "but you have installed " + allMods[m].version.toString(); + throw Incompatibility(err); + } + allMods[m].enabled = true; + } + std::swap(activeMods, newActiveMods); + } + } + h & settings; h & modules; h & identifiers; diff --git a/lib/serializer/CSerializer.h b/lib/serializer/CSerializer.h index 00891b786..78f50a224 100644 --- a/lib/serializer/CSerializer.h +++ b/lib/serializer/CSerializer.h @@ -12,7 +12,7 @@ #include "../ConstTransitivePtr.h" #include "../GameConstants.h" -const ui32 SERIALIZATION_VERSION = 805; +const ui32 SERIALIZATION_VERSION = 806; const ui32 MINIMAL_SERIALIZATION_VERSION = 805; const std::string SAVEGAME_MAGIC = "VCMISVG";