Changeset View
Standalone View
source/ps/Mod.cpp
/* Copyright (C) 2020 Wildfire Games. | /* Copyright (C) 2021 Wildfire Games. | |||||||||||||||||||||||||||||||||
* This file is part of 0 A.D. | * This file is part of 0 A.D. | |||||||||||||||||||||||||||||||||
* | * | |||||||||||||||||||||||||||||||||
* 0 A.D. is free software: you can redistribute it and/or modify | * 0 A.D. is free software: you can redistribute it and/or modify | |||||||||||||||||||||||||||||||||
* it under the terms of the GNU General Public License as published by | * it under the terms of the GNU General Public License as published by | |||||||||||||||||||||||||||||||||
* the Free Software Foundation, either version 2 of the License, or | * the Free Software Foundation, either version 2 of the License, or | |||||||||||||||||||||||||||||||||
* (at your option) any later version. | * (at your option) any later version. | |||||||||||||||||||||||||||||||||
* | * | |||||||||||||||||||||||||||||||||
* 0 A.D. is distributed in the hope that it will be useful, | * 0 A.D. is distributed in the hope that it will be useful, | |||||||||||||||||||||||||||||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||||||||||||||||||||||||||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||||||||||||||||||||||||||||||
* GNU General Public License for more details. | * GNU General Public License for more details. | |||||||||||||||||||||||||||||||||
* | * | |||||||||||||||||||||||||||||||||
* You should have received a copy of the GNU General Public License | * You should have received a copy of the GNU General Public License | |||||||||||||||||||||||||||||||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | |||||||||||||||||||||||||||||||||
*/ | */ | |||||||||||||||||||||||||||||||||
#include "precompiled.h" | #include "precompiled.h" | |||||||||||||||||||||||||||||||||
#include "ps/Mod.h" | #include "ps/Mod.h" | |||||||||||||||||||||||||||||||||
#include <algorithm> | ||||||||||||||||||||||||||||||||||
#include "lib/file/file_system.h" | #include "lib/file/file_system.h" | |||||||||||||||||||||||||||||||||
#include "lib/file/vfs/vfs.h" | #include "lib/file/vfs/vfs.h" | |||||||||||||||||||||||||||||||||
#include "lib/utf8.h" | #include "lib/utf8.h" | |||||||||||||||||||||||||||||||||
#include "ps/Filesystem.h" | #include "ps/Filesystem.h" | |||||||||||||||||||||||||||||||||
#include "ps/GameSetup/GameSetup.h" | #include "ps/GameSetup/GameSetup.h" | |||||||||||||||||||||||||||||||||
#include "ps/GameSetup/Paths.h" | #include "ps/GameSetup/Paths.h" | |||||||||||||||||||||||||||||||||
#include "ps/Pyrogenesis.h" | #include "ps/Pyrogenesis.h" | |||||||||||||||||||||||||||||||||
#include "scriptinterface/ScriptInterface.h" | #include "scriptinterface/ScriptInterface.h" | |||||||||||||||||||||||||||||||||
StanUnsubmitted Not Done Inline Actions
Stan: | ||||||||||||||||||||||||||||||||||
#include <algorithm> | ||||||||||||||||||||||||||||||||||
#include <boost/algorithm/string/split.hpp> | ||||||||||||||||||||||||||||||||||
#include <boost/algorithm/string/classification.hpp> | ||||||||||||||||||||||||||||||||||
#include <unordered_map> | ||||||||||||||||||||||||||||||||||
Not Done Inline ActionsI think boost and other <> includes are ordered alphabetically. @wraitii @vladislavbelov Stan: I think boost and other <> includes are ordered alphabetically. @wraitii @vladislavbelov | ||||||||||||||||||||||||||||||||||
std::vector<CStr> g_modsLoaded; | std::vector<CStr> g_modsLoaded; | |||||||||||||||||||||||||||||||||
std::vector<std::vector<CStr>> g_LoadedModVersions; | std::vector<std::vector<CStr>> g_LoadedModVersions; | |||||||||||||||||||||||||||||||||
CmdLineArgs g_args; | CmdLineArgs g_args; | |||||||||||||||||||||||||||||||||
JS::Value Mod::GetAvailableMods(const ScriptInterface& scriptInterface) | JS::Value Mod::GetAvailableMods(const ScriptInterface& scriptInterface) | |||||||||||||||||||||||||||||||||
{ | { | |||||||||||||||||||||||||||||||||
▲ Show 20 Lines • Show All 57 Lines • ▼ Show 20 Lines | for (DirectoryNames::iterator iter = modDirsUser.begin(); iter != modDirsUser.end(); ++iter) | |||||||||||||||||||||||||||||||||
// Valid mod, add it to our structure | // Valid mod, add it to our structure | |||||||||||||||||||||||||||||||||
JS_SetProperty(rq.cx, obj, utf8_from_wstring(iter->string()).c_str(), json); | JS_SetProperty(rq.cx, obj, utf8_from_wstring(iter->string()).c_str(), json); | |||||||||||||||||||||||||||||||||
} | } | |||||||||||||||||||||||||||||||||
return JS::ObjectValue(*obj); | return JS::ObjectValue(*obj); | |||||||||||||||||||||||||||||||||
} | } | |||||||||||||||||||||||||||||||||
std::vector<CStr> Mod::GetEnabledMods(const ScriptInterface& scriptInterface) | ||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||
return g_modsLoaded; | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
void Mod::CacheEnabledModVersions(const shared_ptr<ScriptContext>& scriptContext) | void Mod::CacheEnabledModVersions(const shared_ptr<ScriptContext>& scriptContext) | |||||||||||||||||||||||||||||||||
{ | { | |||||||||||||||||||||||||||||||||
ScriptInterface scriptInterface("Engine", "CacheEnabledModVersions", scriptContext); | ScriptInterface scriptInterface("Engine", "CacheEnabledModVersions", scriptContext); | |||||||||||||||||||||||||||||||||
ScriptRequest rq(scriptInterface); | ScriptRequest rq(scriptInterface); | |||||||||||||||||||||||||||||||||
JS::RootedValue availableMods(rq.cx, GetAvailableMods(scriptInterface)); | JS::RootedValue availableMods(rq.cx, GetAvailableMods(scriptInterface)); | |||||||||||||||||||||||||||||||||
g_LoadedModVersions.clear(); | g_LoadedModVersions.clear(); | |||||||||||||||||||||||||||||||||
for (const CStr& mod : g_modsLoaded) | for (const CStr& mod : g_modsLoaded) | |||||||||||||||||||||||||||||||||
{ | { | |||||||||||||||||||||||||||||||||
// Ignore user and mod mod as they are irrelevant for compatibility checks | // Ignore user and mod mod as they are irrelevant for compatibility checks | |||||||||||||||||||||||||||||||||
if (mod == "mod" || mod == "user") | if (mod == "mod" || mod == "user") | |||||||||||||||||||||||||||||||||
continue; | continue; | |||||||||||||||||||||||||||||||||
CStr version; | CStr version; | |||||||||||||||||||||||||||||||||
JS::RootedValue modData(rq.cx); | JS::RootedValue modData(rq.cx); | |||||||||||||||||||||||||||||||||
if (scriptInterface.GetProperty(availableMods, mod.c_str(), &modData)) | if (scriptInterface.GetProperty(availableMods, mod.c_str(), &modData)) | |||||||||||||||||||||||||||||||||
scriptInterface.GetProperty(modData, "version", version); | scriptInterface.GetProperty(modData, "version", version); | |||||||||||||||||||||||||||||||||
g_LoadedModVersions.push_back({mod, version}); | g_LoadedModVersions.push_back({mod, version}); | |||||||||||||||||||||||||||||||||
} | } | |||||||||||||||||||||||||||||||||
} | } | |||||||||||||||||||||||||||||||||
const std::vector<CStr>& Mod::GetModsFromArguments(const CmdLineArgs& args, int flags) | ||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||
const bool init_mods = (flags & INIT_MODS) == INIT_MODS; | ||||||||||||||||||||||||||||||||||
const bool add_user = !InDevelopmentCopy() && !args.Has("noUserMod"); | ||||||||||||||||||||||||||||||||||
Not Done Inline ActionsPlease, follow CC. The same below. vladislavbelov: Please, follow CC. The same below. | ||||||||||||||||||||||||||||||||||
const bool add_public = (flags & INIT_MODS_PUBLIC) == INIT_MODS_PUBLIC; | ||||||||||||||||||||||||||||||||||
if (!init_mods) | ||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||
// Add the user mod if it should be present | ||||||||||||||||||||||||||||||||||
if (add_user && (g_modsLoaded.empty() || g_modsLoaded.back() != "user")) | ||||||||||||||||||||||||||||||||||
g_modsLoaded.push_back("user"); | ||||||||||||||||||||||||||||||||||
Not Done Inline ActionsProbably need <vector> Stan: Probably need <vector> | ||||||||||||||||||||||||||||||||||
return g_modsLoaded; | ||||||||||||||||||||||||||||||||||
Not Done Inline Actionsdo you need ordering here and below else use unordered_map and the correct include. Stan: do you need ordering here and below else use unordered_map and the correct include. | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
g_modsLoaded = args.GetMultiple("mod"); | ||||||||||||||||||||||||||||||||||
if (add_public) | ||||||||||||||||||||||||||||||||||
g_modsLoaded.insert(g_modsLoaded.begin(), "public"); | ||||||||||||||||||||||||||||||||||
g_modsLoaded.insert(g_modsLoaded.begin(), "mod"); | ||||||||||||||||||||||||||||||||||
// Add the user mod if not explicitly disabled or we have a dev copy so | ||||||||||||||||||||||||||||||||||
// that saved files end up in version control and not in the user mod. | ||||||||||||||||||||||||||||||||||
if (add_user) | ||||||||||||||||||||||||||||||||||
g_modsLoaded.push_back("user"); | ||||||||||||||||||||||||||||||||||
return g_modsLoaded; | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
void Mod::SetDefaultMods(const shared_ptr<ScriptContext>& scriptContext, const CmdLineArgs& args, int flags) | ||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||
const bool add_user = !InDevelopmentCopy() && !args.Has("noUserMod"); | ||||||||||||||||||||||||||||||||||
g_modsLoaded.clear(); | ||||||||||||||||||||||||||||||||||
g_modsLoaded.insert(g_modsLoaded.begin(), "public"); | ||||||||||||||||||||||||||||||||||
g_modsLoaded.insert(g_modsLoaded.begin(), "mod"); | ||||||||||||||||||||||||||||||||||
// Add the user mod if not explicitly disabled or we have a dev copy so | ||||||||||||||||||||||||||||||||||
// that saved files end up in version control and not in the user mod. | ||||||||||||||||||||||||||||||||||
if (add_user) | ||||||||||||||||||||||||||||||||||
g_modsLoaded.push_back("user"); | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
bool Mod::CheckAndEnableMods(const shared_ptr<ScriptContext>& scriptContext, const std::vector<CStr>& mods) | ||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||
ScriptInterface scriptInterface("Engine", "CheckAndEnableMods", scriptContext); | ||||||||||||||||||||||||||||||||||
return Mod::CheckAndEnableMods(scriptInterface, mods); | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
bool Mod::CheckAndEnableMods(const ScriptInterface& scriptInterface, const std::vector<CStr>& mods) | ||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||
ScriptRequest rq(scriptInterface); | ||||||||||||||||||||||||||||||||||
JS::RootedValue availableMods(rq.cx, GetAvailableMods(scriptInterface)); | ||||||||||||||||||||||||||||||||||
std::unordered_map<CStr, std::vector<CStr>> modDependencies; | ||||||||||||||||||||||||||||||||||
std::unordered_map<CStr, CStr> map; | ||||||||||||||||||||||||||||||||||
Not Done Inline ActionsNot clear what to what relation. vladislavbelov: Not clear what to what relation. | ||||||||||||||||||||||||||||||||||
std::unordered_map<CStr, CStr> names; | ||||||||||||||||||||||||||||||||||
for (const CStr& mod : mods) | ||||||||||||||||||||||||||||||||||
Not Done Inline ActionsDo you need ordering? Stan: Do you need ordering? | ||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||
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); | ||||||||||||||||||||||||||||||||||
Not Done Inline ActionsWhat's going to happen if some of these properties are not defined? vladislavbelov: What's going to happen if some of these properties are not defined? | ||||||||||||||||||||||||||||||||||
Done Inline Actionsthey will be "undefined", if some other mod would want to refer to one without version, dependencies will be not met, if with name, it will be not met, if undefined dependencies actually mod page itself breaks Silier: they will be "undefined", if some other mod would want to refer to one without version… | ||||||||||||||||||||||||||||||||||
Not Done Inline ActionsI think we shouldn't break the page if a mod is broken. Because a user might not know what's causing the error. vladislavbelov: I think we shouldn't break the page if a mod is broken. Because a user might not know what's… | ||||||||||||||||||||||||||||||||||
scriptInterface.GetProperty(modData, "name", name); | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
map.emplace(name, version); | ||||||||||||||||||||||||||||||||||
std::vector<CStr> deps; | ||||||||||||||||||||||||||||||||||
boost::split(deps, dependencies, boost::is_any_of(","), boost::token_compress_on); | ||||||||||||||||||||||||||||||||||
modDependencies.emplace(mod, deps); | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
const std::vector<CStr> toCheck = { "<=", ">=", "=", "<", ">" }; | ||||||||||||||||||||||||||||||||||
wraitiiUnsubmitted Not Done Inline Actionsstatic const wraitii: static const | ||||||||||||||||||||||||||||||||||
for (const CStr& mod : mods) | ||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||
if (mod == "mod" || mod == "user") | ||||||||||||||||||||||||||||||||||
continue; | ||||||||||||||||||||||||||||||||||
const std::unordered_map<CStr, std::vector<CStr>>::iterator res = modDependencies.find(mod); | ||||||||||||||||||||||||||||||||||
if (res == modDependencies.end()) | ||||||||||||||||||||||||||||||||||
continue; | ||||||||||||||||||||||||||||||||||
const std::vector<CStr> 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::unordered_map<CStr, CStr>::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<CStr> vSplit; | ||||||||||||||||||||||||||||||||||
Not Done Inline ActionsMight be a full name. vladislavbelov: Might be a full name. | ||||||||||||||||||||||||||||||||||
std::vector<CStr> tSplit; | ||||||||||||||||||||||||||||||||||
std::string toIgnore = "-,_"; | ||||||||||||||||||||||||||||||||||
wraitiiUnsubmitted Not Done Inline Actionscould also static, but maybe not worth it here wraitii: could also static, but maybe not worth it here | ||||||||||||||||||||||||||||||||||
boost::split(vSplit, version, boost::is_any_of(toIgnore), boost::token_compress_on); | ||||||||||||||||||||||||||||||||||
Not Done Inline ActionsI wonder if there is a smarter way to do it, e.g. using a lambda function. Stan: I wonder if there is a smarter way to do it, e.g. using a lambda function.
See also https… | ||||||||||||||||||||||||||||||||||
boost::split(tSplit, target, boost::is_any_of(toIgnore), 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; | ||||||||||||||||||||||||||||||||||
Not Done Inline Actionssize_t. vladislavbelov: `size_t`. | ||||||||||||||||||||||||||||||||||
const int min = std::min(vSplit.size(), tSplit.size()); | ||||||||||||||||||||||||||||||||||
Not Done Inline Actionssize_t. vladislavbelov: `size_t`. | ||||||||||||||||||||||||||||||||||
for (int i = 0; i < min; ++i) | ||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||
Not Done Inline Actionsoperator[] instead of at. vladislavbelov: operator[] instead of at. | ||||||||||||||||||||||||||||||||||
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; | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
Not Done Inline ActionsThat's bad and implementation defined. vladislavbelov: That's bad and implementation defined. | ||||||||||||||||||||||||||||||||||
Done Inline Actionswhat do you refer to? Silier: what do you refer to? | ||||||||||||||||||||||||||||||||||
Not Done Inline ActionsThe difference of two unsigned values. vladislavbelov: The difference of two unsigned values. | ||||||||||||||||||||||||||||||||||
Done Inline Actionsyes, you are correct I totally missed that :) Silier: yes, you are correct I totally missed that :) | ||||||||||||||||||||||||||||||||||
const int diff = vSplit.size() - tSplit.size(); | ||||||||||||||||||||||||||||||||||
if (diff == 0) | ||||||||||||||||||||||||||||||||||
return eq; | ||||||||||||||||||||||||||||||||||
return diff < 0 ? lt : gt; | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
Not Done Inline Actionsdoes this handle lteq ? Stan: does this handle lteq ? | ||||||||||||||||||||||||||||||||||
Done Inline Actionsyes it does, as essentially it is or less or equal and both cases are handled :) Silier: yes it does, as essentially it is or less or equal and both cases are handled :) | ||||||||||||||||||||||||||||||||||
JS::Value Mod::GetLoadedModsWithVersions(const ScriptInterface& scriptInterface) | JS::Value Mod::GetLoadedModsWithVersions(const ScriptInterface& scriptInterface) | |||||||||||||||||||||||||||||||||
{ | { | |||||||||||||||||||||||||||||||||
ScriptRequest rq(scriptInterface); | ScriptRequest rq(scriptInterface); | |||||||||||||||||||||||||||||||||
JS::RootedValue returnValue(rq.cx); | JS::RootedValue returnValue(rq.cx); | |||||||||||||||||||||||||||||||||
scriptInterface.ToJSVal(rq, &returnValue, g_LoadedModVersions); | scriptInterface.ToJSVal(rq, &returnValue, g_LoadedModVersions); | |||||||||||||||||||||||||||||||||
return returnValue; | return returnValue; | |||||||||||||||||||||||||||||||||
} | } | |||||||||||||||||||||||||||||||||
Show All 17 Lines |