From 29e98b2d516e323dfbb0f997301818108c995a88 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 9 Nov 2013 19:10:16 +0000 Subject: [PATCH] - 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. --- launcher/jsonutils.cpp | 24 ++++-- lib/CModHandler.cpp | 170 ++++++++++++++++++++++++++++------------- lib/CModHandler.h | 20 +++-- lib/VCMI_Lib.cpp | 8 +- 4 files changed, 147 insertions(+), 75 deletions(-) diff --git a/launcher/jsonutils.cpp b/launcher/jsonutils.cpp index 7ef4cbca8..736bc01cf 100644 --- a/launcher/jsonutils.cpp +++ b/launcher/jsonutils.cpp @@ -64,8 +64,16 @@ QVariant JsonFromFile(QString filename) file.open(QFile::ReadOnly); auto data = file.readAll(); - JsonNode node(data.data(), data.size()); - return toVariant(node); + if (data.size() == 0) + { + 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) @@ -74,14 +82,14 @@ JsonNode toJson(QVariant object) if (object.canConvert()) ret.Struct() = VariantToMap(object.toMap()); - if (object.canConvert()) + else if (object.canConvert()) ret.Vector() = VariantToList(object.toList()); - if (object.canConvert()) + else if (object.type() == QMetaType::QString) ret.String() = object.toString().toUtf8().data(); - if (object.canConvert()) - ret.Bool() = object.toFloat(); - if (object.canConvert()) + else if (object.type() == QMetaType::Bool) ret.Bool() = object.toBool(); + else if (object.canConvert()) + ret.Float() = object.toFloat(); return ret; } @@ -93,4 +101,4 @@ void JsonToFile(QString filename, QVariant object) file << toJson(object); } -} \ No newline at end of file +} diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index 6fabc04cd..f3be239c2 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -204,7 +204,7 @@ CContentHandler::ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, } } -bool CContentHandler::ContentTypeHandler::preloadModData(std::string modName, std::vector fileList) +bool CContentHandler::ContentTypeHandler::preloadModData(std::string modName, std::vector fileList, bool validate) { bool result; JsonNode data = JsonUtils::assembleFromFiles(fileList, result); @@ -238,7 +238,7 @@ bool CContentHandler::ContentTypeHandler::preloadModData(std::string modName, st return result; } -bool CContentHandler::ContentTypeHandler::loadMod(std::string modName) +bool CContentHandler::ContentTypeHandler::loadMod(std::string modName, bool validate) { ModInfo & modInfo = modData[modName]; bool result = true; @@ -260,7 +260,8 @@ bool CContentHandler::ContentTypeHandler::loadMod(std::string modName) if (originalData.size() > index) { 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); 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 - result &= JsonUtils::validate(data, "vcmi:" + objectName, name); + if (validate) + result &= JsonUtils::validate(data, "vcmi:" + objectName, name); handler->loadObject(modName, name, data); } return result; @@ -291,22 +293,22 @@ CContentHandler::CContentHandler() //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; for(auto & handler : handlers) { - result &= handler.second.preloadModData(modName, modConfig[handler.first].convertTo >()); + result &= handler.second.preloadModData(modName, modConfig[handler.first].convertTo >(), validate); } return result; } -bool CContentHandler::loadMod(std::string modName) +bool CContentHandler::loadMod(std::string modName, bool validate) { bool result = true; for(auto & handler : handlers) { - result &= handler.second.loadMod(modName); + result &= handler.second.loadMod(modName, validate); } return result; } @@ -453,33 +455,60 @@ std::vector CModHandler::resolveDependencies(std::vector input 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) { entry.second["active"].Bool() = entry.second.Bool(); } } + return config; } -void CModHandler::initialize(std::vector availableMods) +static JsonNode loadModSettings(std::string path) { - std::string confName = "config/modSettings.json"; - JsonNode modConfig; - + if (CResourceHandler::get()->existsResource(ResourceID(path))) + { + // mod compatibility: check if modSettings has old, 0.94 format + return updateModSettingsFormat(JsonNode(ResourceID(path, EResType::TEXT))); + } // Probably new install. Create initial configuration - if (!CResourceHandler::get()->existsResource(ResourceID(confName))) - CResourceHandler::get()->createResource(confName); + CResourceHandler::get()->createResource(path); + 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 >(); + mod.conflicts = config["conflicts"].convertTo >(); +} + +/// 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 - modConfig = JsonNode(ResourceID(confName)); - - // mod compatibility: check if modSettings has old, 0.94 format - updateModSettingsFormat(modConfig["activeMods"]); + { + mod.enabled = config["active"].Bool(); + mod.validated = config["validated"].Bool(); + mod.checksum = strtol(config["checksum"].String().c_str(), nullptr, 16); + } +} +void CModHandler::initializeMods(std::vector availableMods) +{ + const JsonNode modConfig = loadModSettings("config/modSettings.json"); const JsonNode & modList = modConfig["activeMods"]; - JsonNode resultingList; std::vector detectedMods; @@ -491,25 +520,16 @@ void CModHandler::initialize(std::vector availableMods) if (CResourceHandler::get()->existsResource(ResourceID(modFileName))) { const JsonNode config = JsonNode(ResourceID(modFileName)); - - 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; + assert(!config.isNull()); CModInfo & mod = allMods[name]; mod.identifier = name; - mod.name = config["name"].String(); - mod.description = config["description"].String(); - mod.dependencies = config["depends"].convertTo >(); - mod.conflicts = config["conflicts"].convertTo >(); - detectedMods.push_back(name); + loadModInfoJson(mod, config); + loadModInfoConfig(mod, modList[name]); + + if (mod.enabled) + detectedMods.push_back(name); } else logGlobal->warnStream() << "\t\t Directory " << name << " does not contains VCMI mod"; @@ -522,13 +542,6 @@ void CModHandler::initialize(std::vector availableMods) } 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(); } @@ -590,7 +603,12 @@ void CModHandler::loadModFilesystems() auto filesystem = genModFilesystem(modName, fsConfig); 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 return mod; } -void CModHandler::beforeLoad() + +void CModHandler::initializeConfig() { loadConfigFromFile("defaultMods.json"); } -void CModHandler::loadGameContent() +void CModHandler::load() { - CStopWatch timer, totalTime; + CStopWatch totalTime, timer; + + std::set modsForValidation; + for (auto & mod : allMods) + { + if (mod.second.enabled && !mod.second.validated) + modsForValidation.insert(mod.first); + } CContentHandler content; logGlobal->infoStream() << "\tInitializing content handler: " << timer.getDiff() << " ms"; // first - load virtual "core" mod that contains all data // 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"; for(const TModID & modName : activeMods) { + bool needsValidation = modsForValidation.count(modName); + // print message in format [<8-symbols checksum>] logGlobal->infoStream() << "\t\t[" << std::noshowbase << std::hex << std::setw(8) << std::setfill('0') << allMods[modName].checksum << "] " << allMods[modName].name; @@ -626,27 +654,36 @@ void CModHandler::loadGameContent() std::string modFileName = "mods/" + modName + "/mod.json"; 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"; - content.loadMod("core"); + content.loadMod("core", true); logGlobal->infoStream() << "\tLoading original game data: " << timer.getDiff() << " ms"; for(const TModID & modName : activeMods) { - allMods[modName].validated &= content.loadMod(modName); - if (allMods[modName].validated) - logGlobal->infoStream() << "\t\t[DONE] " << allMods[modName].name; + bool needsValidation = modsForValidation.count(modName); + + 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 - logGlobal->errorStream() << "\t\t[FAIL] " << allMods[modName].name; + logGlobal->infoStream() << "\t\t[SKIP] " << allMods[modName].name; } logGlobal->infoStream() << "\tLoading mod data: " << timer.getDiff() << "ms"; VLC->creh->loadCrExpBon(); VLC->creh->buildBonusTreeForTiers(); //do that after all new creatures are loaded + identifiers.finalize(); logGlobal->infoStream() << "\tResolving identifiers: " << timer.getDiff() << " ms"; @@ -656,6 +693,29 @@ void CModHandler::loadGameContent() 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() { { diff --git a/lib/CModHandler.h b/lib/CModHandler.h index 7355c0786..6891fc9fb 100644 --- a/lib/CModHandler.h +++ b/lib/CModHandler.h @@ -92,8 +92,8 @@ class CContentHandler /// local version of methods in ContentHandler /// returns true if loading was successfull - bool preloadModData(std::string modName, std::vector fileList); - bool loadMod(std::string modName); + bool preloadModData(std::string modName, std::vector fileList, bool validate); + bool loadMod(std::string modName, bool validate); void afterLoadFinalization(); }; @@ -104,11 +104,11 @@ public: /// preloads all data from fileList as data from modName. /// 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 /// 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 void afterLoadFinalization(); @@ -138,12 +138,15 @@ public: /// true if mod has passed validation successfully bool validated; + /// true if mod is enabled + bool enabled; + // mod configuration (mod.json). (no need to store it right now) // std::shared_ptr config; //TODO: unique_ptr can't be serialized template 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; /// receives list of available mods and trying to load mod.json from all of them - void initialize(std::vector availableMods); + void initializeMods(std::vector availableMods); + void initializeConfig(); CModInfo & getModData(TModID modId); /// load content from all available mods - void beforeLoad(); - void loadGameContent(); + void load(); + void afterLoad(); /// actions that should be triggered on map restart /// TODO: merge into appropriate handlers? diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index 0228fd355..36051cc29 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -71,7 +71,7 @@ void LibClasses::loadFilesystem() modh = new CModHandler; logGlobal->infoStream()<<"\tMod handler: "<initialize(CResourceHandler::getAvailableMods()); + modh->initializeMods(CResourceHandler::getAvailableMods()); logGlobal->infoStream()<<"\t Mod filesystems: "<infoStream()<<"Basic initialization: "<beforeLoad(); + modh->initializeConfig(); createHandler(bth, "Bonus type", pomtime); @@ -118,8 +118,8 @@ void LibClasses::init() logGlobal->infoStream()<<"\tInitializing handlers: "<< totalTime.getDiff(); - modh->loadGameContent(); - modh->reload(); + modh->load(); + modh->afterLoad(); //FIXME: make sure that everything is ok after game restart //TODO: This should be done every time mod config changes