mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Trying to organize mod handler a bit better.
- vcmi will now generate checksum for base game (H3 data) - only text files in Data in config directories affect checksum
This commit is contained in:
		| @@ -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>] <modname> | ||||
| 	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<std::set<std::string> >()), | ||||
| 	conflicts(config["conflicts"].convertTo<std::set<std::string> >()), | ||||
| 	validation(PENDING), | ||||
| 	config(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> >(); | ||||
| 	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<std::string> availableMods) | ||||
| @@ -519,21 +582,17 @@ void CModHandler::initializeMods(std::vector<std::string> 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<const void*>(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<const void *>(&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<const void *>(&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<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")), 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>] <modname> | ||||
| 		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; | ||||
|   | ||||
| @@ -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<std::string, ContentTypeHandler> 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<JsonNode> 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 <typename Handler> 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 <TModID, CModInfo> allMods; | ||||
| 	std::vector <TModID> activeMods;//active mods, in order in which they were loaded | ||||
| 	CModInfo coreMod; | ||||
|  | ||||
| 	void loadConfigFromFile(std::string name); | ||||
| 	void loadModFilesystems(); | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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; | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user