diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index 35fe4bc80..b39946ed3 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -321,6 +321,41 @@ void CContentHandler::afterLoadFinalization() } } +void CContentHandler::preloadData(CModInfo & mod) +{ + bool validate = (mod.validation != CModInfo::PASSED); + + // print message in format [<8-symbols checksum>] + logGlobal->infoStream() << "\t\t[" << std::noshowbase << std::hex << std::setw(8) << std::setfill('0') + << mod.checksum << "] " << mod.name; + + if (validate && mod.identifier != "core") + { + if (!JsonUtils::validate(mod.config, "vcmi:mod", mod.identifier)) + mod.validation = CModInfo::FAILED; + } + if (!preloadModData(mod.identifier, mod.config, validate)) + mod.validation = CModInfo::FAILED; +} + +void CContentHandler::load(CModInfo & mod) +{ + bool validate = (mod.validation != CModInfo::PASSED); + + if (!loadMod(mod.identifier, validate)) + mod.validation = CModInfo::FAILED; + + if (validate) + { + if (mod.validation != CModInfo::FAILED) + logGlobal->infoStream() << "\t\t[DONE] " << mod.name; + else + logGlobal->errorStream() << "\t\t[FAIL] " << mod.name; + } + else + logGlobal->infoStream() << "\t\t[SKIP] " << mod.name; +} + CModHandler::CModHandler() { for (int i = 0; i < GameConstants::RESOURCE_QUANTITY; ++i) @@ -479,30 +514,58 @@ static JsonNode loadModSettings(std::string path) return JsonNode(); } -/// loads mod info data from mod.json -static void loadModInfoJson(CModInfo & mod, const JsonNode & config) +CModInfo::CModInfo(std::string identifier,const JsonNode & local, const JsonNode & config): + identifier(identifier), + name(config["name"].String()), + description(config["description"].String()), + dependencies(config["depends"].convertTo >()), + conflicts(config["conflicts"].convertTo >()), + validation(PENDING), + config(config) { - mod.name = config["name"].String(); - mod.description = config["description"].String(); - mod.dependencies = config["depends"].convertTo >(); - mod.conflicts = config["conflicts"].convertTo >(); + loadLocalData(local); } -/// load mod info from local config -static void loadModInfoConfig(CModInfo & mod, const JsonNode & config) +JsonNode CModInfo::saveLocalData() { - if (config.isNull()) + std::ostringstream stream; + stream << std::noshowbase << std::hex << std::setw(8) << std::setfill('0') << checksum; + + JsonNode conf; + conf["active"].Bool() = enabled; + conf["validated"].Bool() = validation != FAILED; + conf["checksum"].String() = stream.str(); + return conf; +} + +void CModInfo::updateChecksum(ui32 newChecksum) +{ + if (newChecksum != checksum) { - mod.enabled = true; - mod.validated = false; - mod.checksum = 0; + checksum = newChecksum; + validation = PENDING; + } +} + +void CModInfo::loadLocalData(const JsonNode & data) +{ + bool validated = false; + if (data.isNull()) + { + enabled = true; + checksum = 0; } else { - mod.enabled = config["active"].Bool(); - mod.validated = config["validated"].Bool(); - mod.checksum = strtol(config["checksum"].String().c_str(), nullptr, 16); + enabled = data["active"].Bool(); + validated = data["validated"].Bool(); + checksum = strtol(data["checksum"].String().c_str(), nullptr, 16); } + + if (enabled) + validation = validated ? PASSED : PENDING; + else + validation = validated ? PASSED : FAILED; } void CModHandler::initializeMods(std::vector availableMods) @@ -519,21 +582,17 @@ void CModHandler::initializeMods(std::vector availableMods) if (CResourceHandler::get()->existsResource(ResourceID(modFileName))) { - const JsonNode config = JsonNode(ResourceID(modFileName)); - assert(!config.isNull()); - - CModInfo & mod = allMods[name]; - - mod.identifier = name; - loadModInfoJson(mod, config); - loadModInfoConfig(mod, modList[name]); + CModInfo mod(name, modList[name], JsonNode(ResourceID(modFileName))); + allMods[name] = mod; if (mod.enabled) detectedMods.push_back(name); } else logGlobal->warnStream() << "\t\t Directory " << name << " does not contains VCMI mod"; } + coreMod = CModInfo("core", modConfig["core"], JsonNode(ResourceID("config/gameConfig.json"))); + coreMod.name = "Original game files"; if (!checkDependencies(detectedMods)) { @@ -574,16 +633,25 @@ static ui32 calculateModChecksum(const std::string modName, ISimpleResourceLoade modChecksum.process_bytes(reinterpret_cast(GameConstants::VCMI_VERSION.data()), GameConstants::VCMI_VERSION.size()); // second - add mod.json into checksum because filesystem does not contains this file - ResourceID modConfFile("mods/" + modName + "/mod", EResType::TEXT); - ui32 configChecksum = CResourceHandler::getInitial()->load(modConfFile)->calculateCRC32(); - modChecksum.process_bytes(reinterpret_cast(&configChecksum), sizeof(configChecksum)); - - // third - add all loaded files from this mod into checksum + // FIXME: remove workaround for core mod + if (modName != "core") + { + ResourceID modConfFile("mods/" + modName + "/mod", EResType::TEXT); + ui32 configChecksum = CResourceHandler::getInitial()->load(modConfFile)->calculateCRC32(); + modChecksum.process_bytes(reinterpret_cast(&configChecksum), sizeof(configChecksum)); + } + // third - add all detected text files from this mod into checksum auto files = filesystem->getFilteredFiles([](const ResourceID & resID) { - return resID.getType() == EResType::TEXT; + return resID.getType() == EResType::TEXT && + ( boost::starts_with(resID.getName(), "DATA") || + boost::starts_with(resID.getName(), "CONFIG")); }); + // these two files may change between two runs of vcmi and must be handled separately + files.erase(ResourceID("CONFIG/SETTINGS", EResType::TEXT)); + files.erase(ResourceID("CONFIG/MODSETTINGS", EResType::TEXT)); + for (const ResourceID & file : files) { ui32 fileChecksum = filesystem->load(file)->calculateCRC32(); @@ -594,22 +662,16 @@ static ui32 calculateModChecksum(const std::string modName, ISimpleResourceLoade void CModHandler::loadModFilesystems() { + coreMod.updateChecksum(calculateModChecksum("core", CResourceHandler::getCoreData())); + for(std::string & modName : activeMods) { - ResourceID modConfFile("mods/" + modName + "/mod", EResType::TEXT); - auto fsConfigData = CResourceHandler::getInitial()->load(modConfFile)->readAll(); - const JsonNode fsConfig((char*)fsConfigData.first.get(), fsConfigData.second); - - auto filesystem = genModFilesystem(modName, fsConfig); + CModInfo & mod = allMods[modName]; + auto filesystem = genModFilesystem(modName, mod.config); CResourceHandler::get()->addLoader(filesystem, false); logGlobal->traceStream() << "Generating checksum for " << modName; - ui32 newChecksum = calculateModChecksum(modName, filesystem); - if (allMods[modName].checksum != newChecksum) - { - allMods[modName].checksum = newChecksum; - allMods[modName].validated = false; // force (re-)validation - } + mod.updateChecksum(calculateModChecksum(modName, filesystem)); } } @@ -629,57 +691,20 @@ void CModHandler::load() { 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")), true); - logGlobal->infoStream() << "\tParsing original game data: " << timer.getDiff() << " ms"; - + content.preloadData(coreMod); 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; - - std::string modFileName = "mods/" + modName + "/mod.json"; - - const JsonNode config = JsonNode(ResourceID(modFileName)); - if (needsValidation) - allMods[modName].validated = JsonUtils::validate(config, "vcmi:mod", modName); - - allMods[modName].validated &= content.preloadModData(modName, config, needsValidation); - } + content.preloadData(allMods[modName]); logGlobal->infoStream() << "\tParsing mod data: " << timer.getDiff() << " ms"; - content.loadMod("core", true); - logGlobal->infoStream() << "\tLoading original game data: " << timer.getDiff() << " ms"; - + content.load(coreMod); for(const TModID & modName : activeMods) - { - bool needsValidation = modsForValidation.count(modName); + content.load(allMods[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->infoStream() << "\t\t[SKIP] " << allMods[modName].name; - } logGlobal->infoStream() << "\tLoading mod data: " << timer.getDiff() << "ms"; VLC->creh->loadCrExpBon(); @@ -690,27 +715,15 @@ void CModHandler::load() content.afterLoadFinalization(); logGlobal->infoStream() << "\tHandlers post-load finalization: " << timer.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); + modSettings["activeMods"][modEntry.first] = modEntry.second.saveLocalData(); + modSettings["core"] = coreMod.saveLocalData(); std::ofstream file(*CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json")), std::ofstream::trunc); file << modSettings; diff --git a/lib/CModHandler.h b/lib/CModHandler.h index 6891fc9fb..93f388f26 100644 --- a/lib/CModHandler.h +++ b/lib/CModHandler.h @@ -97,18 +97,22 @@ class CContentHandler void afterLoadFinalization(); }; + /// preloads all data from fileList as data from modName. + bool preloadModData(std::string modName, JsonNode modConfig, bool validate); + + /// actually loads data in mod + bool loadMod(std::string modName, bool validate); + std::map handlers; public: /// fully initialize object. Will cause reading of H3 config files CContentHandler(); /// preloads all data from fileList as data from modName. - /// returns true if loading was successfull - bool preloadModData(std::string modName, JsonNode modConfig, bool validate); + void preloadData(CModInfo & mod); /// actually loads data in mod - /// returns true if loading was successfull - bool loadMod(std::string modName, bool validate); + void load(CModInfo & mod); /// all data was loaded, time for final validation / integration void afterLoadFinalization(); @@ -119,6 +123,13 @@ typedef std::string TModID; class DLL_LINKAGE CModInfo { public: + enum EValidationStatus + { + PENDING, + FAILED, + PASSED + }; + /// identifier, identical to name of folder with mod std::string identifier; @@ -135,25 +146,34 @@ public: /// CRC-32 checksum of the mod ui32 checksum; - /// 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 + EValidationStatus validation; + + JsonNode config; + + CModInfo(){} + CModInfo(std::string identifier, const JsonNode & local, const JsonNode & config); + + JsonNode saveLocalData(); + void updateChecksum(ui32 newChecksum); template void serialize(Handler &h, const int version) { - h & identifier & description & name & dependencies & conflicts & checksum & validated & enabled; + h & identifier & description & name; + h & dependencies & conflicts & config; + h & checksum & validation & enabled; } +private: + void loadLocalData(const JsonNode & data); }; class DLL_LINKAGE CModHandler { std::map allMods; std::vector activeMods;//active mods, in order in which they were loaded + CModInfo coreMod; void loadConfigFromFile(std::string name); void loadModFilesystems(); diff --git a/lib/filesystem/Filesystem.cpp b/lib/filesystem/Filesystem.cpp index fd95cc1f6..c8ecb54c6 100644 --- a/lib/filesystem/Filesystem.cpp +++ b/lib/filesystem/Filesystem.cpp @@ -16,6 +16,7 @@ CFilesystemList * CResourceHandler::resourceLoader = nullptr; CFilesystemList * CResourceHandler::initialLoader = nullptr; +CFilesystemList * CResourceHandler::coreDataLoader = nullptr; CFilesystemGenerator::CFilesystemGenerator(std::string prefix): filesystem(new CFilesystemList()), @@ -162,13 +163,21 @@ CFilesystemList * CResourceHandler::getInitial() return initialLoader; } +CFilesystemList * CResourceHandler::getCoreData() +{ + assert(coreDataLoader); + return coreDataLoader; +} + void CResourceHandler::load(const std::string &fsConfigURI) { auto fsConfigData = initialLoader->load(ResourceID(fsConfigURI, EResType::TEXT))->readAll(); const JsonNode fsConfig((char*)fsConfigData.first.get(), fsConfigData.second); - resourceLoader = createFileSystem("", fsConfig["filesystem"]); + coreDataLoader = createFileSystem("", fsConfig["filesystem"]); + resourceLoader = new CFilesystemList(); + resourceLoader->addLoader(coreDataLoader, false); // hardcoded system-specific path, may not be inside any of data directories resourceLoader->addLoader(new CFilesystemLoader("SAVES/", VCMIDirs::get().userSavePath()), true); diff --git a/lib/filesystem/Filesystem.h b/lib/filesystem/Filesystem.h index 98770344c..fe9b02cd1 100644 --- a/lib/filesystem/Filesystem.h +++ b/lib/filesystem/Filesystem.h @@ -61,6 +61,7 @@ public: */ static CFilesystemList * get(); static CFilesystemList * getInitial(); + static CFilesystemList * getCoreData(); /** * Creates instance of initial resource loader. @@ -99,4 +100,5 @@ private: /** Instance of resource loader */ static CFilesystemList * resourceLoader; static CFilesystemList * initialLoader; + static CFilesystemList * coreDataLoader; };