From 5456e245a3d1ace2d0a48b2ef45656a9011f5f7e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 18 Jun 2025 09:39:22 +0300 Subject: [PATCH] Add mod version validation --- config/schemas/mod.json | 1 + lib/json/JsonValidator.cpp | 10 +++++++++ lib/modding/CModVersion.cpp | 44 +++++++++++++++++++++++-------------- lib/modding/CModVersion.h | 2 +- 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/config/schemas/mod.json b/config/schemas/mod.json index 55b87553b..5d3970cc8 100644 --- a/config/schemas/mod.json +++ b/config/schemas/mod.json @@ -90,6 +90,7 @@ }, "version" : { "type" : "string", + "format" : "version", "description" : "Current mod version, up to 3 numbers, dot-separated. Format: A.B.C" }, "changelog" : { diff --git a/lib/json/JsonValidator.cpp b/lib/json/JsonValidator.cpp index 90c150b0e..0d516c4cf 100644 --- a/lib/json/JsonValidator.cpp +++ b/lib/json/JsonValidator.cpp @@ -19,6 +19,7 @@ #include "../modding/CModHandler.h" #include "../texts/TextOperations.h" #include "../ScopeGuard.h" +#include "modding/CModVersion.h" VCMI_LIB_NAMESPACE_BEGIN @@ -538,6 +539,14 @@ static std::string videoFile(const JsonNode & node) } #undef TEST_FILE +static std::string version(const JsonNode & node) +{ + auto version = CModVersion::fromString(node.String()); + if (version == CModVersion()) + return "Failed to parse mod version: " + node.toCompactString() + ". Expected format X.Y.Z, where X, Y, Z are non-negative numbers"; + return ""; +} + JsonValidator::TValidatorMap createCommonFields() { JsonValidator::TValidatorMap ret; @@ -629,6 +638,7 @@ JsonValidator::TFormatMap createFormatMap() ret["animationFile"] = animationFile; ret["imageFile"] = imageFile; ret["videoFile"] = videoFile; + ret["version"] = version; //TODO: // uri-reference diff --git a/lib/modding/CModVersion.cpp b/lib/modding/CModVersion.cpp index c9b56a1fd..97b4cf72a 100644 --- a/lib/modding/CModVersion.cpp +++ b/lib/modding/CModVersion.cpp @@ -18,29 +18,41 @@ CModVersion CModVersion::GameVersion() return CModVersion(VCMI_VERSION_MAJOR, VCMI_VERSION_MINOR, VCMI_VERSION_PATCH); } -CModVersion CModVersion::fromString(std::string from) +CModVersion CModVersion::fromString(const std::string & from) { - int major = Any; - int minor = Any; - int patch = Any; + std::vector segments; + boost::split(segments, from, boost::is_any_of(".")); + + if (from.empty()) + return CModVersion(); + + if (segments.size() > 3) + return CModVersion(); + + static const std::string whitelist = "1234567890."; + + for (const auto & ch : from) + if (whitelist.find(ch) == std::string::npos) + return CModVersion(); + try { - auto pointPos = from.find('.'); - major = std::stoi(from.substr(0, pointPos)); - if(pointPos != std::string::npos) - { - from = from.substr(pointPos + 1); - pointPos = from.find('.'); - minor = std::stoi(from.substr(0, pointPos)); - if(pointPos != std::string::npos) - patch = std::stoi(from.substr(pointPos + 1)); - } + int major = Any; + int minor = Any; + int patch = Any; + + major = std::stoi(segments[0]); + if (segments.size() > 1) + minor = std::stoi(segments[1]); + if (segments.size() > 2) + patch = std::stoi(segments[2]); + + return CModVersion(major, minor, patch); } - catch(const std::invalid_argument &) + catch(const std::logic_error &) { return CModVersion(); } - return CModVersion(major, minor, patch); } std::string CModVersion::toString() const diff --git a/lib/modding/CModVersion.h b/lib/modding/CModVersion.h index 2a20876f9..94656fe84 100644 --- a/lib/modding/CModVersion.h +++ b/lib/modding/CModVersion.h @@ -32,7 +32,7 @@ struct DLL_LINKAGE CModVersion CModVersion(int mj, int mi, int p): major(mj), minor(mi), patch(p) {} static CModVersion GameVersion(); - static CModVersion fromString(std::string from); + static CModVersion fromString(const std::string & from); std::string toString() const; bool operator !=(const CModVersion & other) const;