1
0
forked from mirrors/0ad

Allow mods to say they should be ignored in replay/MP compatibility checks

Since it is very non-trivial to determine which mods change checksums
and which don't, this relies on modder goodwill (and on verification on
our end for signed mods).

The declaration is an optional "ignoreInCompatibilityChecks" boolean in
mod.json

Also rework slightly the MP lobby mod display to always show the host
mods in a clear manner.

Differential Revision: https://code.wildfiregames.com/D3968
This was SVN commit r25634.
This commit is contained in:
wraitii
2021-06-02 06:50:16 +00:00
parent 693f296e71
commit 07e44a75a1
10 changed files with 409 additions and 238 deletions
+111 -106
View File
@@ -27,7 +27,6 @@
#include "ps/GameSetup/GameSetup.h"
#include "ps/GameSetup/Paths.h"
#include "ps/Profiler2.h"
#include "ps/Pyrogenesis.h"
#include "scriptinterface/JSON.h"
#include "scriptinterface/Object.h"
#include "scriptinterface/ScriptExceptions.h"
@@ -105,13 +104,11 @@ bool ParseModJSON(const ScriptRequest& rq, const PIVFS& vfs, OsPath modsPath, Os
if (!Script::ParseJSON(rq, text, &json))
return false;
Script::FromJSVal(rq, json, data);
// Complete - FromJSVal won't convert everything.
data.m_Pathname = utf8_from_wstring(mod.string());
data.m_Text = text;
if (!Script::GetProperty(rq, json, "version", data.m_Version))
return false;
if (!Script::GetProperty(rq, json, "name", data.m_Name))
return false;
if (!Script::GetProperty(rq, json, "dependencies", data.m_Dependencies))
return false;
return true;
@@ -124,6 +121,114 @@ Mod& Mod::Instance()
return g_ModInstance;
}
const std::vector<CStr>& Mod::GetEnabledMods() const
{
return m_EnabledMods;
}
const std::vector<CStr>& Mod::GetIncompatibleMods() const
{
return m_IncompatibleMods;
}
const std::vector<Mod::ModData>& Mod::GetAvailableMods() const
{
return m_AvailableMods;
}
bool Mod::EnableMods(const ScriptInterface& scriptInterface, const std::vector<CStr>& mods, const bool addPublic)
{
m_IncompatibleMods.clear();
m_EnabledMods.clear();
std::unordered_map<CStr, int> counts;
for (const CStr& mod : mods)
{
// Ignore duplicates.
if (counts.try_emplace(mod, 0).first->second++ > 0)
continue;
m_EnabledMods.emplace_back(mod);
}
if (addPublic && counts["public"] == 0)
m_EnabledMods.insert(m_EnabledMods.begin(), "public");
if (counts["mod"] == 0)
m_EnabledMods.insert(m_EnabledMods.begin(), "mod");
UpdateAvailableMods(scriptInterface);
m_IncompatibleMods = CheckForIncompatibleMods(m_EnabledMods);
for (const CStr& mod : m_IncompatibleMods)
m_EnabledMods.erase(std::find(m_EnabledMods.begin(), m_EnabledMods.end(), mod));
return m_IncompatibleMods.empty();
}
const Mod::ModData* Mod::GetModData(const CStr& mod) const
{
std::vector<ModData>::const_iterator it = std::find_if(m_AvailableMods.begin(), m_AvailableMods.end(),
[&mod](const ModData& modData) { return modData.m_Pathname == mod; });
if (it == m_AvailableMods.end())
return nullptr;
return std::addressof(*it);
}
const std::vector<const Mod::ModData*> Mod::GetEnabledModsData() const
{
std::vector<const ModData*> loadedMods;
for (const CStr& mod : m_EnabledMods)
{
if (mod == "mod" || mod == "user")
continue;
const ModData* data = GetModData(mod);
// This ought be impossible, but let's handle it anyways since it's not a reason to crash.
if (!data)
{
LOGERROR("Unavailable mod '%s' was enabled.", mod);
continue;
}
loadedMods.emplace_back(data);
}
return loadedMods;
}
bool Mod::AreModsPlayCompatible(const std::vector<const Mod::ModData*>& modsA, const std::vector<const Mod::ModData*>& modsB)
{
// Mods must be loaded in the same order.
std::vector<const Mod::ModData*>::const_iterator a = modsA.begin();
std::vector<const Mod::ModData*>::const_iterator b = modsB.begin();
while (a != modsA.end() || b != modsB.end())
{
if (a != modsA.end() && (*a)->m_IgnoreInCompatibilityChecks)
{
++a;
continue;
}
if (b != modsB.end() && (*b)->m_IgnoreInCompatibilityChecks)
{
++b;
continue;
}
// If at this point one of the two lists still contains items, the sizes are different -> fail.
if (a == modsA.end() || b == modsB.end())
return false;
if ((*a)->m_Pathname != (*b)->m_Pathname)
return false;
if ((*a)->m_Version != (*b)->m_Version)
return false;
++a;
++b;
}
return true;
}
void Mod::UpdateAvailableMods(const ScriptInterface& scriptInterface)
{
PROFILE2("UpdateAvailableMods");
@@ -169,46 +274,6 @@ void Mod::UpdateAvailableMods(const ScriptInterface& scriptInterface)
}
}
const std::vector<CStr>& Mod::GetEnabledMods() const
{
return m_EnabledMods;
}
const std::vector<CStr>& Mod::GetIncompatibleMods() const
{
return m_IncompatibleMods;
}
bool Mod::EnableMods(const ScriptInterface& scriptInterface, const std::vector<CStr>& mods, const bool addPublic)
{
m_IncompatibleMods.clear();
m_EnabledMods.clear();
std::unordered_map<CStr, int> counts;
for (const CStr& mod : mods)
{
// Ignore duplicates.
if (counts.try_emplace(mod, 0).first->second++ > 0)
continue;
m_EnabledMods.emplace_back(mod);
}
if (addPublic && counts["public"] == 0)
m_EnabledMods.insert(m_EnabledMods.begin(), "public");
if (counts["mod"] == 0)
m_EnabledMods.insert(m_EnabledMods.begin(), "mod");
UpdateAvailableMods(scriptInterface);
m_IncompatibleMods = CheckForIncompatibleMods(m_EnabledMods);
for (const CStr& mod : m_IncompatibleMods)
m_EnabledMods.erase(std::find(m_EnabledMods.begin(), m_EnabledMods.end(), mod));
return m_IncompatibleMods.empty();
}
std::vector<CStr> Mod::CheckForIncompatibleMods(const std::vector<CStr>& mods) const
{
std::vector<CStr> incompatibleMods;
@@ -312,63 +377,3 @@ bool Mod::CompareVersionStrings(const CStr& version, const CStr& op, const CStr&
return eq;
return versionSize < requiredSize ? lt : gt;
}
JS::Value Mod::GetLoadedModsWithVersions(const ScriptInterface& scriptInterface) const
{
std::vector<std::vector<CStr>> loadedMods;
for (const CStr& mod : m_EnabledMods)
{
if (mod == "mod" || mod == "user")
continue;
std::vector<ModData>::const_iterator it = std::find_if(m_AvailableMods.begin(), m_AvailableMods.end(),
[&mod](const ModData& modData) { return modData.m_Pathname == mod; });
// This ought be impossible, but let's handle it anyways since it's not a reason to crash.
if (it == m_AvailableMods.end())
{
LOGERROR("Unavailable mod '%s' was enabled.", mod);
continue;
}
loadedMods.emplace_back(std::vector<CStr>{ it->m_Pathname, it->m_Version });
}
ScriptRequest rq(scriptInterface);
JS::RootedValue returnValue(rq.cx);
Script::ToJSVal(rq, &returnValue, loadedMods);
return returnValue;
}
JS::Value Mod::GetEngineInfo(const ScriptInterface& scriptInterface) const
{
ScriptRequest rq(scriptInterface);
JS::RootedValue mods(rq.cx, GetLoadedModsWithVersions(scriptInterface));
JS::RootedValue metainfo(rq.cx);
Script::CreateObject(
rq,
&metainfo,
"engine_version", engine_version,
"mods", mods);
Script::FreezeObject(rq, metainfo, true);
return metainfo;
}
JS::Value Mod::GetAvailableMods(const ScriptRequest& rq) const
{
JS::RootedValue ret(rq.cx, Script::CreateObject(rq));
for (const ModData& data : m_AvailableMods)
{
JS::RootedValue json(rq.cx);
if (!Script::ParseJSON(rq, data.m_Text, &json))
{
ScriptException::Raise(rq, "Error parsing mod.json of '%s'", data.m_Pathname.c_str());
continue;
}
Script::SetProperty(rq, ret, data.m_Pathname.c_str(), json);
}
return ret.get();
}