1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-02-15 13:33:36 +02:00

- modhandler will only validate new, changed or mods that failed

validation last time.
- fixed several issues with modSettings.json file
- some refactoring of modhandler, still needs some work.
This commit is contained in:
Ivan Savenko 2013-11-09 19:10:16 +00:00
parent e01ef8e36a
commit 29e98b2d51
4 changed files with 147 additions and 75 deletions

View File

@ -64,8 +64,16 @@ QVariant JsonFromFile(QString filename)
file.open(QFile::ReadOnly); file.open(QFile::ReadOnly);
auto data = file.readAll(); auto data = file.readAll();
JsonNode node(data.data(), data.size()); if (data.size() == 0)
return toVariant(node); {
logGlobal->errorStream() << "Failed to open file " << filename.toUtf8().data();
return QVariant();
}
else
{
JsonNode node(data.data(), data.size());
return toVariant(node);
}
} }
JsonNode toJson(QVariant object) JsonNode toJson(QVariant object)
@ -74,14 +82,14 @@ JsonNode toJson(QVariant object)
if (object.canConvert<QVariantMap>()) if (object.canConvert<QVariantMap>())
ret.Struct() = VariantToMap(object.toMap()); ret.Struct() = VariantToMap(object.toMap());
if (object.canConvert<QVariantList>()) else if (object.canConvert<QVariantList>())
ret.Vector() = VariantToList(object.toList()); ret.Vector() = VariantToList(object.toList());
if (object.canConvert<QString>()) else if (object.type() == QMetaType::QString)
ret.String() = object.toString().toUtf8().data(); ret.String() = object.toString().toUtf8().data();
if (object.canConvert<double>()) else if (object.type() == QMetaType::Bool)
ret.Bool() = object.toFloat();
if (object.canConvert<bool>())
ret.Bool() = object.toBool(); ret.Bool() = object.toBool();
else if (object.canConvert<double>())
ret.Float() = object.toFloat();
return ret; return ret;
} }
@ -93,4 +101,4 @@ void JsonToFile(QString filename, QVariant object)
file << toJson(object); file << toJson(object);
} }
} }

View File

@ -204,7 +204,7 @@ CContentHandler::ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler,
} }
} }
bool CContentHandler::ContentTypeHandler::preloadModData(std::string modName, std::vector<std::string> fileList) bool CContentHandler::ContentTypeHandler::preloadModData(std::string modName, std::vector<std::string> fileList, bool validate)
{ {
bool result; bool result;
JsonNode data = JsonUtils::assembleFromFiles(fileList, result); JsonNode data = JsonUtils::assembleFromFiles(fileList, result);
@ -238,7 +238,7 @@ bool CContentHandler::ContentTypeHandler::preloadModData(std::string modName, st
return result; return result;
} }
bool CContentHandler::ContentTypeHandler::loadMod(std::string modName) bool CContentHandler::ContentTypeHandler::loadMod(std::string modName, bool validate)
{ {
ModInfo & modInfo = modData[modName]; ModInfo & modInfo = modData[modName];
bool result = true; bool result = true;
@ -260,7 +260,8 @@ bool CContentHandler::ContentTypeHandler::loadMod(std::string modName)
if (originalData.size() > index) if (originalData.size() > index)
{ {
JsonUtils::merge(originalData[index], data); JsonUtils::merge(originalData[index], data);
result &= JsonUtils::validate(originalData[index], "vcmi:" + objectName, name); if (validate)
result &= JsonUtils::validate(originalData[index], "vcmi:" + objectName, name);
handler->loadObject(modName, name, originalData[index], index); handler->loadObject(modName, name, originalData[index], index);
originalData[index].clear(); // do not use same data twice (same ID) originalData[index].clear(); // do not use same data twice (same ID)
@ -269,7 +270,8 @@ bool CContentHandler::ContentTypeHandler::loadMod(std::string modName)
} }
} }
// normal new object or one with index bigger that data size // normal new object or one with index bigger that data size
result &= JsonUtils::validate(data, "vcmi:" + objectName, name); if (validate)
result &= JsonUtils::validate(data, "vcmi:" + objectName, name);
handler->loadObject(modName, name, data); handler->loadObject(modName, name, data);
} }
return result; return result;
@ -291,22 +293,22 @@ CContentHandler::CContentHandler()
//TODO: spells, bonuses, something else? //TODO: spells, bonuses, something else?
} }
bool CContentHandler::preloadModData(std::string modName, JsonNode modConfig) bool CContentHandler::preloadModData(std::string modName, JsonNode modConfig, bool validate)
{ {
bool result = true; bool result = true;
for(auto & handler : handlers) for(auto & handler : handlers)
{ {
result &= handler.second.preloadModData(modName, modConfig[handler.first].convertTo<std::vector<std::string> >()); result &= handler.second.preloadModData(modName, modConfig[handler.first].convertTo<std::vector<std::string> >(), validate);
} }
return result; return result;
} }
bool CContentHandler::loadMod(std::string modName) bool CContentHandler::loadMod(std::string modName, bool validate)
{ {
bool result = true; bool result = true;
for(auto & handler : handlers) for(auto & handler : handlers)
{ {
result &= handler.second.loadMod(modName); result &= handler.second.loadMod(modName, validate);
} }
return result; return result;
} }
@ -453,33 +455,60 @@ std::vector <TModID> CModHandler::resolveDependencies(std::vector <TModID> input
return output; return output;
} }
static void updateModSettingsFormat(JsonNode & config) static JsonNode updateModSettingsFormat(JsonNode config)
{ {
for (auto & entry : config.Struct()) for (auto & entry : config["activeMods"].Struct())
{ {
if (entry.second.getType() == JsonNode::DATA_BOOL) if (entry.second.getType() == JsonNode::DATA_BOOL)
{ {
entry.second["active"].Bool() = entry.second.Bool(); entry.second["active"].Bool() = entry.second.Bool();
} }
} }
return config;
} }
void CModHandler::initialize(std::vector<std::string> availableMods) static JsonNode loadModSettings(std::string path)
{ {
std::string confName = "config/modSettings.json"; if (CResourceHandler::get()->existsResource(ResourceID(path)))
JsonNode modConfig; {
// mod compatibility: check if modSettings has old, 0.94 format
return updateModSettingsFormat(JsonNode(ResourceID(path, EResType::TEXT)));
}
// Probably new install. Create initial configuration // Probably new install. Create initial configuration
if (!CResourceHandler::get()->existsResource(ResourceID(confName))) CResourceHandler::get()->createResource(path);
CResourceHandler::get()->createResource(confName); return JsonNode();
}
/// loads mod info data from mod.json
static void loadModInfoJson(CModInfo & mod, const JsonNode & config)
{
mod.name = config["name"].String();
mod.description = config["description"].String();
mod.dependencies = config["depends"].convertTo<std::set<std::string> >();
mod.conflicts = config["conflicts"].convertTo<std::set<std::string> >();
}
/// load mod info from local config
static void loadModInfoConfig(CModInfo & mod, const JsonNode & config)
{
if (config.isNull())
{
mod.enabled = true;
mod.validated = false;
mod.checksum = 0;
}
else else
modConfig = JsonNode(ResourceID(confName)); {
mod.enabled = config["active"].Bool();
// mod compatibility: check if modSettings has old, 0.94 format mod.validated = config["validated"].Bool();
updateModSettingsFormat(modConfig["activeMods"]); mod.checksum = strtol(config["checksum"].String().c_str(), nullptr, 16);
}
}
void CModHandler::initializeMods(std::vector<std::string> availableMods)
{
const JsonNode modConfig = loadModSettings("config/modSettings.json");
const JsonNode & modList = modConfig["activeMods"]; const JsonNode & modList = modConfig["activeMods"];
JsonNode resultingList;
std::vector <TModID> detectedMods; std::vector <TModID> detectedMods;
@ -491,25 +520,16 @@ void CModHandler::initialize(std::vector<std::string> availableMods)
if (CResourceHandler::get()->existsResource(ResourceID(modFileName))) if (CResourceHandler::get()->existsResource(ResourceID(modFileName)))
{ {
const JsonNode config = JsonNode(ResourceID(modFileName)); const JsonNode config = JsonNode(ResourceID(modFileName));
assert(!config.isNull());
if (config.isNull())
continue;
if (!modList[name].isNull() && modList[name]["active"].Bool() == false )
{
resultingList[name]["active"].Bool() = false;
continue; // disabled mod
}
resultingList[name]["active"].Bool() = true;
CModInfo & mod = allMods[name]; CModInfo & mod = allMods[name];
mod.identifier = name; mod.identifier = name;
mod.name = config["name"].String(); loadModInfoJson(mod, config);
mod.description = config["description"].String(); loadModInfoConfig(mod, modList[name]);
mod.dependencies = config["depends"].convertTo<std::set<std::string> >();
mod.conflicts = config["conflicts"].convertTo<std::set<std::string> >(); if (mod.enabled)
detectedMods.push_back(name); detectedMods.push_back(name);
} }
else else
logGlobal->warnStream() << "\t\t Directory " << name << " does not contains VCMI mod"; logGlobal->warnStream() << "\t\t Directory " << name << " does not contains VCMI mod";
@ -522,13 +542,6 @@ void CModHandler::initialize(std::vector<std::string> availableMods)
} }
activeMods = resolveDependencies(detectedMods); activeMods = resolveDependencies(detectedMods);
modConfig["activeMods"] = resultingList;
CResourceHandler::get()->createResource("CONFIG/modSettings.json");
std::ofstream file(*CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json")), std::ofstream::trunc);
file << modConfig;
loadModFilesystems(); loadModFilesystems();
} }
@ -590,7 +603,12 @@ void CModHandler::loadModFilesystems()
auto filesystem = genModFilesystem(modName, fsConfig); auto filesystem = genModFilesystem(modName, fsConfig);
CResourceHandler::get()->addLoader(filesystem, false); CResourceHandler::get()->addLoader(filesystem, false);
allMods[modName].checksum = calculateModChecksum(modName, filesystem); ui32 newChecksum = calculateModChecksum(modName, filesystem);
if (allMods[modName].checksum != newChecksum)
{
allMods[modName].checksum = newChecksum;
allMods[modName].validated = false; // force (re-)validation
}
} }
} }
@ -600,25 +618,35 @@ CModInfo & CModHandler::getModData(TModID modId)
assert(vstd::contains(activeMods, modId)); // not really necessary but won't hurt assert(vstd::contains(activeMods, modId)); // not really necessary but won't hurt
return mod; return mod;
} }
void CModHandler::beforeLoad()
void CModHandler::initializeConfig()
{ {
loadConfigFromFile("defaultMods.json"); loadConfigFromFile("defaultMods.json");
} }
void CModHandler::loadGameContent() void CModHandler::load()
{ {
CStopWatch timer, totalTime; CStopWatch totalTime, timer;
std::set<std::string> modsForValidation;
for (auto & mod : allMods)
{
if (mod.second.enabled && !mod.second.validated)
modsForValidation.insert(mod.first);
}
CContentHandler content; CContentHandler content;
logGlobal->infoStream() << "\tInitializing content handler: " << timer.getDiff() << " ms"; logGlobal->infoStream() << "\tInitializing content handler: " << timer.getDiff() << " ms";
// first - load virtual "core" mod that contains all data // first - load virtual "core" mod that contains all data
// TODO? move all data into real mods? RoE, AB, SoD, WoG // TODO? move all data into real mods? RoE, AB, SoD, WoG
content.preloadModData("core", JsonNode(ResourceID("config/gameConfig.json"))); content.preloadModData("core", JsonNode(ResourceID("config/gameConfig.json")), true);
logGlobal->infoStream() << "\tParsing original game data: " << timer.getDiff() << " ms"; logGlobal->infoStream() << "\tParsing original game data: " << timer.getDiff() << " ms";
for(const TModID & modName : activeMods) for(const TModID & modName : activeMods)
{ {
bool needsValidation = modsForValidation.count(modName);
// print message in format [<8-symbols checksum>] <modname> // print message in format [<8-symbols checksum>] <modname>
logGlobal->infoStream() << "\t\t[" << std::noshowbase << std::hex << std::setw(8) << std::setfill('0') logGlobal->infoStream() << "\t\t[" << std::noshowbase << std::hex << std::setw(8) << std::setfill('0')
<< allMods[modName].checksum << "] " << allMods[modName].name; << allMods[modName].checksum << "] " << allMods[modName].name;
@ -626,27 +654,36 @@ void CModHandler::loadGameContent()
std::string modFileName = "mods/" + modName + "/mod.json"; std::string modFileName = "mods/" + modName + "/mod.json";
const JsonNode config = JsonNode(ResourceID(modFileName)); const JsonNode config = JsonNode(ResourceID(modFileName));
allMods[modName].validated = JsonUtils::validate(config, "vcmi:mod", modName); if (needsValidation)
allMods[modName].validated = JsonUtils::validate(config, "vcmi:mod", modName);
allMods[modName].validated &= content.preloadModData(modName, config); allMods[modName].validated &= content.preloadModData(modName, config, needsValidation);
} }
logGlobal->infoStream() << "\tParsing mod data: " << timer.getDiff() << " ms"; logGlobal->infoStream() << "\tParsing mod data: " << timer.getDiff() << " ms";
content.loadMod("core"); content.loadMod("core", true);
logGlobal->infoStream() << "\tLoading original game data: " << timer.getDiff() << " ms"; logGlobal->infoStream() << "\tLoading original game data: " << timer.getDiff() << " ms";
for(const TModID & modName : activeMods) for(const TModID & modName : activeMods)
{ {
allMods[modName].validated &= content.loadMod(modName); bool needsValidation = modsForValidation.count(modName);
if (allMods[modName].validated)
logGlobal->infoStream() << "\t\t[DONE] " << allMods[modName].name; allMods[modName].validated &= content.loadMod(modName, needsValidation);
if (needsValidation)
{
if (allMods[modName].validated)
logGlobal->infoStream() << "\t\t[DONE] " << allMods[modName].name;
else
logGlobal->errorStream() << "\t\t[FAIL] " << allMods[modName].name;
}
else else
logGlobal->errorStream() << "\t\t[FAIL] " << allMods[modName].name; logGlobal->infoStream() << "\t\t[SKIP] " << allMods[modName].name;
} }
logGlobal->infoStream() << "\tLoading mod data: " << timer.getDiff() << "ms"; logGlobal->infoStream() << "\tLoading mod data: " << timer.getDiff() << "ms";
VLC->creh->loadCrExpBon(); VLC->creh->loadCrExpBon();
VLC->creh->buildBonusTreeForTiers(); //do that after all new creatures are loaded VLC->creh->buildBonusTreeForTiers(); //do that after all new creatures are loaded
identifiers.finalize(); identifiers.finalize();
logGlobal->infoStream() << "\tResolving identifiers: " << timer.getDiff() << " ms"; logGlobal->infoStream() << "\tResolving identifiers: " << timer.getDiff() << " ms";
@ -656,6 +693,29 @@ void CModHandler::loadGameContent()
logGlobal->infoStream() << "\tAll game content loaded in " << totalTime.getDiff() << " ms"; logGlobal->infoStream() << "\tAll game content loaded in " << totalTime.getDiff() << " ms";
} }
static JsonNode modInfoToJson(const CModInfo & mod)
{
std::ostringstream stream;
stream << std::noshowbase << std::hex << std::setw(8) << std::setfill('0') << mod.checksum;
JsonNode conf;
conf["active"].Bool() = mod.enabled;
conf["validated"].Bool() = mod.validated;
conf["checksum"].String() = stream.str();
return conf;
}
void CModHandler::afterLoad()
{
JsonNode modSettings;
for (auto & modEntry : allMods)
modSettings["activeMods"][modEntry.first] = modInfoToJson(modEntry.second);
std::ofstream file(*CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json")), std::ofstream::trunc);
file << modSettings;
reload();
}
void CModHandler::reload() void CModHandler::reload()
{ {
{ {

View File

@ -92,8 +92,8 @@ class CContentHandler
/// local version of methods in ContentHandler /// local version of methods in ContentHandler
/// returns true if loading was successfull /// returns true if loading was successfull
bool preloadModData(std::string modName, std::vector<std::string> fileList); bool preloadModData(std::string modName, std::vector<std::string> fileList, bool validate);
bool loadMod(std::string modName); bool loadMod(std::string modName, bool validate);
void afterLoadFinalization(); void afterLoadFinalization();
}; };
@ -104,11 +104,11 @@ public:
/// preloads all data from fileList as data from modName. /// preloads all data from fileList as data from modName.
/// returns true if loading was successfull /// returns true if loading was successfull
bool preloadModData(std::string modName, JsonNode modConfig); bool preloadModData(std::string modName, JsonNode modConfig, bool validate);
/// actually loads data in mod /// actually loads data in mod
/// returns true if loading was successfull /// returns true if loading was successfull
bool loadMod(std::string modName); bool loadMod(std::string modName, bool validate);
/// all data was loaded, time for final validation / integration /// all data was loaded, time for final validation / integration
void afterLoadFinalization(); void afterLoadFinalization();
@ -138,12 +138,15 @@ public:
/// true if mod has passed validation successfully /// true if mod has passed validation successfully
bool validated; bool validated;
/// true if mod is enabled
bool enabled;
// mod configuration (mod.json). (no need to store it right now) // mod configuration (mod.json). (no need to store it right now)
// std::shared_ptr<JsonNode> config; //TODO: unique_ptr can't be serialized // std::shared_ptr<JsonNode> config; //TODO: unique_ptr can't be serialized
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)
{ {
h & identifier & description & name & dependencies & conflicts & checksum & validated; h & identifier & description & name & dependencies & conflicts & checksum & validated & enabled;
} }
}; };
@ -171,13 +174,14 @@ public:
CIdentifierStorage identifiers; CIdentifierStorage identifiers;
/// receives list of available mods and trying to load mod.json from all of them /// receives list of available mods and trying to load mod.json from all of them
void initialize(std::vector<std::string> availableMods); void initializeMods(std::vector<std::string> availableMods);
void initializeConfig();
CModInfo & getModData(TModID modId); CModInfo & getModData(TModID modId);
/// load content from all available mods /// load content from all available mods
void beforeLoad(); void load();
void loadGameContent(); void afterLoad();
/// actions that should be triggered on map restart /// actions that should be triggered on map restart
/// TODO: merge into appropriate handlers? /// TODO: merge into appropriate handlers?

View File

@ -71,7 +71,7 @@ void LibClasses::loadFilesystem()
modh = new CModHandler; modh = new CModHandler;
logGlobal->infoStream()<<"\tMod handler: "<<loadTime.getDiff(); logGlobal->infoStream()<<"\tMod handler: "<<loadTime.getDiff();
modh->initialize(CResourceHandler::getAvailableMods()); modh->initializeMods(CResourceHandler::getAvailableMods());
logGlobal->infoStream()<<"\t Mod filesystems: "<<loadTime.getDiff(); logGlobal->infoStream()<<"\t Mod filesystems: "<<loadTime.getDiff();
logGlobal->infoStream()<<"Basic initialization: "<<totalTime.getDiff(); logGlobal->infoStream()<<"Basic initialization: "<<totalTime.getDiff();
@ -92,7 +92,7 @@ void LibClasses::init()
{ {
CStopWatch pomtime, totalTime; CStopWatch pomtime, totalTime;
modh->beforeLoad(); modh->initializeConfig();
createHandler(bth, "Bonus type", pomtime); createHandler(bth, "Bonus type", pomtime);
@ -118,8 +118,8 @@ void LibClasses::init()
logGlobal->infoStream()<<"\tInitializing handlers: "<< totalTime.getDiff(); logGlobal->infoStream()<<"\tInitializing handlers: "<< totalTime.getDiff();
modh->loadGameContent(); modh->load();
modh->reload(); modh->afterLoad();
//FIXME: make sure that everything is ok after game restart //FIXME: make sure that everything is ok after game restart
//TODO: This should be done every time mod config changes //TODO: This should be done every time mod config changes