Jelajahi Sumber

- 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.

Ivan Savenko 12 tahun lalu
induk
melakukan
29e98b2d51
4 mengubah file dengan 147 tambahan dan 75 penghapusan
  1. 16 8
      launcher/jsonutils.cpp
  2. 115 55
      lib/CModHandler.cpp
  3. 12 8
      lib/CModHandler.h
  4. 4 4
      lib/VCMI_Lib.cpp

+ 16 - 8
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<QVariantMap>())
 		ret.Struct() = VariantToMap(object.toMap());
-	if (object.canConvert<QVariantList>())
+	else if (object.canConvert<QVariantList>())
 		ret.Vector() = VariantToList(object.toList());
-	if (object.canConvert<QString>())
+	else if (object.type() == QMetaType::QString)
 		ret.String() = object.toString().toUtf8().data();
-	if (object.canConvert<double>())
-		ret.Bool() = object.toFloat();
-	if (object.canConvert<bool>())
+	else if (object.type() == QMetaType::Bool)
 		ret.Bool() = object.toBool();
+	else if (object.canConvert<double>())
+		ret.Float() = object.toFloat();
 
 	return ret;
 }
@@ -93,4 +101,4 @@ void JsonToFile(QString filename, QVariant object)
 	file << toJson(object);
 }
 
-}
+}

+ 115 - 55
lib/CModHandler.cpp

@@ -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;
 	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<std::vector<std::string> >());
+		result &= handler.second.preloadModData(modName, modConfig[handler.first].convertTo<std::vector<std::string> >(), 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 <TModID> CModHandler::resolveDependencies(std::vector <TModID> 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<std::string> 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);
-	else
-		modConfig = JsonNode(ResourceID(confName));
+	CResourceHandler::get()->createResource(path);
+	return JsonNode();
+}
 
-	// mod compatibility: check if modSettings has old, 0.94 format
-	updateModSettingsFormat(modConfig["activeMods"]);
+/// 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
+	{
+		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<std::string> availableMods)
+{
+	const JsonNode modConfig = loadModSettings("config/modSettings.json");
 	const JsonNode & modList = modConfig["activeMods"];
-	JsonNode resultingList;
 
 	std::vector <TModID> detectedMods;
 
@@ -491,25 +520,16 @@ void CModHandler::initialize(std::vector<std::string> 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<std::set<std::string> >();
-			mod.conflicts =    config["conflicts"].convertTo<std::set<std::string> >();
-			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<std::string> 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<std::string> 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>] <modname>
 		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()
 {
 	{

+ 12 - 8
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<std::string> fileList);
-		bool loadMod(std::string modName);
+		bool preloadModData(std::string modName, std::vector<std::string> 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<JsonNode> config; //TODO: unique_ptr can't be serialized
 
 	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;
 
 	/// 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);
 
 	/// 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?

+ 4 - 4
lib/VCMI_Lib.cpp

@@ -71,7 +71,7 @@ void LibClasses::loadFilesystem()
 	modh = new CModHandler;
     logGlobal->infoStream()<<"\tMod handler: "<<loadTime.getDiff();
 
-	modh->initialize(CResourceHandler::getAvailableMods());
+	modh->initializeMods(CResourceHandler::getAvailableMods());
     logGlobal->infoStream()<<"\t Mod filesystems: "<<loadTime.getDiff();
 
     logGlobal->infoStream()<<"Basic initialization: "<<totalTime.getDiff();
@@ -92,7 +92,7 @@ void LibClasses::init()
 {
 	CStopWatch pomtime, totalTime;
 
-	modh->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