Index: binaries/data/mods/public/gui/common/functions_utility_loadsave.js =================================================================== --- binaries/data/mods/public/gui/common/functions_utility_loadsave.js +++ binaries/data/mods/public/gui/common/functions_utility_loadsave.js @@ -55,18 +55,35 @@ */ function hasSameMods(metadata, engineInfo) { - if (!metadata.mods || !engineInfo.mods) + if (!metadata.mods || !engineInfo.mods || !metadata.mod_versions || !engineInfo.mod_versions) return false; + let modsA = metadata.mods; + let modsB = engineInfo.mods; + let versionsA = metadata.mod_versions; + let versionsB = engineInfo.mod_versions; + // Ignore the "user" mod which is loaded for releases but not working-copies - let modsA = metadata.mods.filter(mod => mod != "user"); - let modsB = engineInfo.mods.filter(mod => mod != "user"); + + let userIdx = modsA.indexOf("user"); + if (userIdx != -1) + { + modsA.splice(userIdx, 1); + versionsA.splice(userIdx, 1); + } + + userIdx = modsB.indexOf("user"); + if (userIdx != -1) + { + modsB.splice(userIdx, 1); + versionsB.splice(userIdx, 1); + } if (modsA.length != modsB.length) return false; // Mods must be loaded in the same order - return modsA.every((mod, index) => mod == modsB[index]); + return modsA.every((mod, index) => mod == modsB[index] && versionsA[index] == versionsB[index]); } function deleteGame() Index: source/ps/Mod.h =================================================================== --- source/ps/Mod.h +++ source/ps/Mod.h @@ -28,5 +28,6 @@ namespace Mod { JS::Value GetAvailableMods(const ScriptInterface& scriptInterface); + std::vector GetLoadedModVersions(const ScriptInterface& scriptInterface); } #endif // INCLUDED_MOD Index: source/ps/Mod.cpp =================================================================== --- source/ps/Mod.cpp +++ source/ps/Mod.cpp @@ -99,3 +99,22 @@ return JS::ObjectValue(*obj); } + +std::vector Mod::GetLoadedModVersions(const ScriptInterface& scriptInterface) +{ + JSContext* cx = scriptInterface.GetContext(); + JSAutoRequest rq(cx); + + JS::RootedValue availableMods(cx, GetAvailableMods(scriptInterface)); + + std::vector modVersions; + for (const CStr& mod : g_modsLoaded) + { + CStr version; + JS::RootedValue modData(cx); + if (scriptInterface.GetProperty(availableMods, mod.c_str(), &modData)) + scriptInterface.GetProperty(modData, "version", version); + modVersions.push_back(version); + } + return modVersions; +} Index: source/ps/Replay.cpp =================================================================== --- source/ps/Replay.cpp +++ source/ps/Replay.cpp @@ -31,6 +31,7 @@ #include "ps/Profile.h" #include "ps/ProfileViewer.h" #include "ps/Pyrogenesis.h" +#include "ps/Mod.h" #include "ps/Util.h" #include "ps/VisualReplay.h" #include "scriptinterface/ScriptInterface.h" @@ -59,6 +60,7 @@ // Add engine version and currently loaded mods for sanity checks when replaying m_ScriptInterface.SetProperty(attribs, "engine_version", CStr(engine_version)); m_ScriptInterface.SetProperty(attribs, "mods", g_modsLoaded); + m_ScriptInterface.SetProperty(attribs, "mod_versions", Mod::GetLoadedModVersions(m_ScriptInterface)); m_Directory = createDateIndexSubdirectory(VisualReplay::GetDirectoryName()); debug_printf("Writing replay to %s\n", m_Directory.string8().c_str()); Index: source/ps/SavedGame.cpp =================================================================== --- source/ps/SavedGame.cpp +++ source/ps/SavedGame.cpp @@ -305,6 +305,7 @@ scriptInterface.SetProperty(metainfo, "version_minor", SAVED_GAME_VERSION_MINOR); scriptInterface.SetProperty(metainfo, "engine_version", std::string(engine_version)); scriptInterface.SetProperty(metainfo, "mods", g_modsLoaded); + scriptInterface.SetProperty(metainfo, "mod_versions", Mod::GetLoadedModVersions(scriptInterface)); scriptInterface.FreezeObject(metainfo, true);