Index: binaries/data/mods/mod/gui/modmod/modmod.js =================================================================== --- binaries/data/mods/mod/gui/modmod/modmod.js +++ binaries/data/mods/mod/gui/modmod/modmod.js @@ -127,8 +127,7 @@ function startMods() { sortEnabledMods(); - Engine.SetMods(["mod"].concat(g_ModsEnabled)); - Engine.RestartEngine(); + Engine.SetModsAndRestartEngine(["mod"].concat(g_ModsEnabled)); } function displayModLists() Index: source/ps/GameSetup/GameSetup.cpp =================================================================== --- source/ps/GameSetup/GameSetup.cpp +++ source/ps/GameSetup/GameSetup.cpp @@ -937,7 +937,12 @@ { std::vector mods; boost::split(mods, modstring, boost::is_any_of(" "), boost::token_compress_on); - std::swap(g_modsLoaded, mods); + if (!Mod::CheckAndEnableMods(g_ScriptContext, mods)) { + // Force default mods when they are not compatible + modstring = "mod public"; + boost::split(mods, modstring, boost::is_any_of(" "), boost::token_compress_on); + Mod::CheckAndEnableMods(g_ScriptContext, mods); + } // Abort init and restart RestartEngine(); Index: source/ps/Mod.h =================================================================== --- source/ps/Mod.h +++ source/ps/Mod.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -39,6 +39,10 @@ */ void CacheEnabledModVersions(const shared_ptr& scriptContext); + bool CheckAndEnableMods(const shared_ptr& scriptContext, const std::vector& mods); + bool CheckAndEnableMods(const ScriptInterface& scriptInterface, const std::vector& mods); + bool CompareVersionStrings(const CStr& version, const CStr& op, const CStr& target); + /** * Get the loaded mods and their version. * "user" mod and "mod" mod are ignored as they are irrelevant for compatibility checks. Index: source/ps/Mod.cpp =================================================================== --- source/ps/Mod.cpp +++ source/ps/Mod.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -20,6 +20,9 @@ #include "ps/Mod.h" #include +#include +#include +#include #include "lib/file/file_system.h" #include "lib/file/vfs/vfs.h" @@ -127,6 +130,115 @@ } } +bool Mod::CheckAndEnableMods(const shared_ptr& scriptContext, const std::vector& mods) +{ + ScriptInterface scriptInterface("Engine", "CheckAndEnableMods", scriptContext); + return Mod::CheckAndEnableMods(scriptInterface, mods); +} + +bool Mod::CheckAndEnableMods(const ScriptInterface& scriptInterface, const std::vector& mods) +{ + ScriptRequest rq(scriptInterface); + + CStr engine = engine_version; + JS::RootedValue availableMods(rq.cx, GetAvailableMods(scriptInterface)); + std::map> modDependencies; + std::map map; + std::map names; + for (const CStr& mod : mods) + { + if (mod == "mod" || mod == "user") + continue; + + CStr dependencies; + CStr version; + CStr name; + JS::RootedValue modData(rq.cx); + if (scriptInterface.GetProperty(availableMods, mod.c_str(), &modData)) + { + scriptInterface.GetProperty(modData, "dependencies", dependencies); + scriptInterface.GetProperty(modData, "version", version); + scriptInterface.GetProperty(modData, "name", name); + } + map.emplace(name, version); + std::vector deps; + boost::split(deps, dependencies, boost::is_any_of(","), boost::token_compress_on); + modDependencies.emplace(mod, deps); + } + + const std::vector toCheck = { "<=", ">=", "=", "<", ">" }; + for (const CStr& mod : mods) + { + if (mod == "mod" || mod == "user") + continue; + const std::map>::iterator res = modDependencies.find(mod); + if (res == modDependencies.end()) + continue; + const std::vector deps = res->second; + if (deps.empty()) + continue; + + for (const CStr& dep : deps) + { + if (dep.empty()) + continue; + + for (const CStr& op : toCheck) + { + const int pos = dep.Find(op.c_str()); + if (pos == -1) + continue; + const CStr modToCheck = dep.substr(0, pos); + const CStr versionToCheck = dep.substr(pos + op.size()); + const std::map::iterator it = map.find(modToCheck); + if (it == map.end()) + return false; + if (!CompareVersionStrings(versionToCheck, op, it->second)) + return false; + break; + } + } + + } + + g_modsLoaded = mods; + return true; +} + +bool Mod::CompareVersionStrings(const CStr& target, const CStr& op, const CStr& version) +{ + std::vector vSplit; + std::vector tSplit; + boost::split(vSplit, version, boost::is_any_of("-"), boost::token_compress_on); + boost::split(vSplit, vSplit.at(0), boost::is_any_of("_"), boost::token_compress_on); + boost::split(tSplit, target, boost::is_any_of("-"), boost::token_compress_on); + boost::split(tSplit, tSplit.at(0), boost::is_any_of("_"), boost::token_compress_on); + boost::split(vSplit, vSplit.at(0), boost::is_any_of("."), boost::token_compress_on); + boost::split(tSplit, tSplit.at(0), boost::is_any_of("."), boost::token_compress_on); + + const bool eq = op.Find("=") != -1; + const bool lt = op.Find("<") != -1; + const bool gt = op.Find(">") != -1; + + const int min = std::min(vSplit.size(), tSplit.size()); + + for (int i = 0; i < min; ++i) + { + const int diff = vSplit.at(i).ToInt() - tSplit.at(i).ToInt(); + if (gt && diff > 0 || lt && diff < 0) + return true; + + if (gt && diff < 0 || lt && diff > 0 || eq && diff) + return false; + } + + const int diff = vSplit.size() - tSplit.size(); + if (diff == 0) + return eq; + + return diff < 0 ? lt : gt; +} + JS::Value Mod::GetLoadedModsWithVersions(const ScriptInterface& scriptInterface) { ScriptRequest rq(scriptInterface); Index: source/ps/scripting/JSInterface_Mod.h =================================================================== --- source/ps/scripting/JSInterface_Mod.h +++ source/ps/scripting/JSInterface_Mod.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -28,7 +28,7 @@ JS::Value GetEngineInfo(ScriptInterface::CmptPrivate* pCmptPrivate); JS::Value GetAvailableMods(ScriptInterface::CmptPrivate* pCmptPrivate); void RestartEngine(ScriptInterface::CmptPrivate* pCmptPrivate); - void SetMods(ScriptInterface::CmptPrivate* pCmptPrivate, const std::vector& mods); + void SetModsAndRestartEngine(ScriptInterface::CmptPrivate* pCmptPrivate, const std::vector& mods); } #endif // INCLUDED_JSI_MOD Index: source/ps/scripting/JSInterface_Mod.cpp =================================================================== --- source/ps/scripting/JSInterface_Mod.cpp +++ source/ps/scripting/JSInterface_Mod.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -49,9 +49,10 @@ ::RestartEngine(); } -void JSI_Mod::SetMods(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate), const std::vector& mods) +void JSI_Mod::SetModsAndRestartEngine(ScriptInterface::CmptPrivate* pCmptPrivate, const std::vector& mods) { - g_modsLoaded = mods; + if (Mod::CheckAndEnableMods(*(pCmptPrivate->pScriptInterface), mods)) + ::RestartEngine(); } void JSI_Mod::RegisterScriptFunctions(const ScriptInterface& scriptInterface) @@ -59,5 +60,5 @@ scriptInterface.RegisterFunction("GetEngineInfo"); scriptInterface.RegisterFunction("GetAvailableMods"); scriptInterface.RegisterFunction("RestartEngine"); - scriptInterface.RegisterFunction, &JSI_Mod::SetMods>("SetMods"); + scriptInterface.RegisterFunction, &JSI_Mod::SetModsAndRestartEngine>("SetModsAndRestartEngine"); }