Explorar el Código

- ZipArchive namespace for operations with zip archives, located in CZipLoader.h/cpp.
- new fields in mod format, for use with mod manager (check config/shemas/mod.json for details)
- removed some 0.92 compatibility from mods loading
- several compile fixes

Ivan Savenko hace 12 años
padre
commit
5654fef901

+ 1 - 1
AI/VCAI/VCAI.cpp

@@ -1612,7 +1612,7 @@ void VCAI::reserveObject(HeroPtr h, const CGObjectInstance *obj)
 {
 	reservedObjs.push_back(obj);
 	reservedHeroesMap[h].push_back(obj);
-	logAi->debugStream() << "reserved object id=" << obj->id << "; address=" << (int)obj << "; name=" << obj->getHoverText();
+	logAi->debugStream() << "reserved object id=" << obj->id << "; address=" << (intptr_t)obj << "; name=" << obj->getHoverText();
 }
 
 void VCAI::validateVisitableObjs()

+ 12 - 1
CMakeLists.txt

@@ -1,5 +1,8 @@
 project(vcmi)
 cmake_minimum_required(VERSION 2.6)
+# TODO:
+# 1) Detection of system version of minizip and use it instead of local
+# 2) Detection of Qt5 and compilation of launcher, unless explicitly disabled
 
 # where to look for cmake modules
 set(CMAKE_MODULE_PATH ${CMAKE_HOME_DIRECTORY}/cmake_modules)
@@ -16,6 +19,7 @@ set(VCMI_VERSION_PATCH 0)
 
 option(ENABLE_ERM "Enable compilation of ERM scripting module" OFF)
 option(ENABLE_EDITOR "Enable compilation of map editor" OFF)
+option(ENABLE_LAUNCHER "Enable compilation of launcher" OFF)
 option(ENABLE_TEST "Enable compilation of unit tests" OFF)
 
 ############################################
@@ -52,11 +56,15 @@ find_package(SDL_mixer REQUIRED)
 find_package(SDL_ttf REQUIRED)
 find_package(ZLIB REQUIRED)
 
-if (ENABLE_EDITOR)
+if (ENABLE_EDITOR OR ENABLE_LAUNCHER)
 	# Widgets finds its own dependencies (QtGui and QtCore).
 	find_package(Qt5Widgets REQUIRED)
 endif()
 
+if (ENABLE_LAUNCHER)
+	find_package(Qt5Network REQUIRED)
+endif()
+
 if(ENABLE_TEST)
 	# find_package overwrites BOOST_* variables which are already set, so all components have to be
 	# included again
@@ -133,6 +141,9 @@ endif()
 if (ENABLE_EDITOR)
 	add_subdirectory(editor)
 endif()
+if (ENABLE_LAUNCHER)
+	add_subdirectory(launcher)
+endif()
 if(ENABLE_TEST)
 	add_subdirectory(test)
 endif()

+ 22 - 19
Mods/WoG/mod.json

@@ -1,4 +1,25 @@
 {
+	"name" : "In The Wake of Gods",
+	"description" : "Unnofficial addon for Heroes of Might and Magic III",
+
+	"version" : "3.58.0",
+	"author" : "WoG Team",
+
+	"artifacts" : 
+	[
+		"config/wog/artifacts.json"
+	],
+
+	"creatures" : 
+	[
+		"config/wog/creatures.json"
+	],
+
+	"factions" : 
+	[
+		"config/wog/factions.json"
+	],
+
 	"filesystem":
 	{
 		"" :
@@ -39,23 +60,5 @@
 		[
 			{"type" : "dir", "path" : "/Maps"}
 		]
-	},
-
-	"name" : "In The Wake of Gods",
-	"description" : "Unnofficial addon for Heroes of Might and Magic III",
-
-	"artifacts" : 
-	[
-		"config/wog/artifacts.json"
-	],
-
-	"creatures" : 
-	[
-		"config/wog/creatures.json"
-	],
-
-	"factions" : 
-	[
-		"config/wog/factions.json"
-	]
+	}
 }

+ 7 - 4
Mods/vcmi/mod.json

@@ -1,4 +1,10 @@
 {
+	"name" : "VCMI essential files",
+	"description" : "Essential files required for VCMI to run correctly",
+
+	"version" : "0.0",
+	"author" : "VCMI Team",
+
 	"filesystem":
 	{
 		"DATA/" :
@@ -13,8 +19,5 @@
 		[
 			{"type" : "dir",  "path" : "/Maps"}
 		]
-	},
-
-	"name" : "VCMI essential files",
-	"description" : "Essential files required for VCMI to run correctly"
+	}
 }

+ 22 - 2
config/schemas/mod.json

@@ -3,7 +3,7 @@
 	"$schema": "http://json-schema.org/draft-04/schema",
 	"title" : "VCMI mod file format",
 	"description" : "Format used to define main mod file (mod.json) in VCMI",
-	"required" : [ "name", "description" ],
+	"required" : [ "name", "description", "version", "author" ],
 
 	"additionalProperties" : false,
 	"properties":{
@@ -16,6 +16,26 @@
 			"description": "More lengthy description of mod. No hard limit"
 		},
 
+		"modType" : {
+			"type":"string",
+			"description": "Type of mod, e.g. Town, Artifacts, Graphical."
+		},
+
+		"version" : {
+			"type":"string",
+			"description": "Current mod version, up to 3 numbers, dot-separated. Format: A.B.C"
+		},
+
+		"author" : {
+			"type":"string",
+			"description": "Author of the mod. Can be nickname, real name or name of team"
+		},
+
+		"weblink" : {
+			"type":"string",
+			"description": "Home page of mod or link to forum thread"
+		},
+
 		"depends": {
 			"type":"array",
 			"description": "List of mods that are required to run this one",
@@ -69,7 +89,7 @@
 						},
 						"type": {
 							"type" : "string",
-							"enum" : [ "dir", "lod", "snd", "vid", "map" ],
+							"enum" : [ "dir", "lod", "snd", "vid", "map", "zip" ],
 							"description" : "Type of data source"
 						}
 					}

+ 16 - 1
config/schemas/settings.json

@@ -3,7 +3,7 @@
 {
 	"type" : "object",
 	"$schema": "http://json-schema.org/draft-04/schema",
-	"required" : [ "general", "video", "adventure", "battle", "server", "logging" ],
+	"required" : [ "general", "video", "adventure", "battle", "server", "logging", "launcher" ],
 	"definitions" : {
 		"logLevelEnum" : { 
 			"type" : "string", 
@@ -224,6 +224,21 @@
 					}
 				}
 			}
+		},
+		"launcher" : {
+			"type" : "object",
+			"default": {},
+			"additionalProperties" : false,
+			"required" : [ "repositoryURL" ],
+			"properties" : {
+				"repositoryURL" : {
+					"type" : "array",
+					"default" : [ ],
+					"items" : {
+						"type" : "string"
+					}
+				}
+			}
 		}
 	}
 }

+ 1 - 1
lib/CBattleCallback.cpp

@@ -2184,7 +2184,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(const CStack * subject) co
 				break;
 			case SpellID::SLAYER://only if monsters are present
 				{
-					auto kingMonster = getStackIf([&](const CStack *stack) //look for enemy, non-shooting stack
+					auto kingMonster = getStackIf([&](const CStack *stack) -> bool //look for enemy, non-shooting stack
 					{
 						const auto isKing = Selector::type(Bonus::KING1)
 							.Or(Selector::type(Bonus::KING2))

+ 4 - 11
lib/CHeroHandler.cpp

@@ -294,18 +294,11 @@ void CHeroHandler::loadHeroSkills(CHero * hero, const JsonNode & node)
 
 	for(const JsonNode & spell : node["spellbook"].Vector())
 	{
-		if (spell.getType() == JsonNode::DATA_FLOAT) // for compatibility
+		VLC->modh->identifiers.requestIdentifier("spell", spell,
+		[=](si32 spellID)
 		{
-			hero->spells.insert(SpellID(spell.Float()));
-		}
-		else
-		{
-			VLC->modh->identifiers.requestIdentifier("spell", spell,
-			[=](si32 spellID)
-			{
-				hero->spells.insert(SpellID(spellID));
-			});
-		}
+			hero->spells.insert(SpellID(spellID));
+		});
 	}
 }
 

+ 0 - 4
lib/CModHandler.cpp

@@ -156,10 +156,6 @@ bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request)
 		{
 			logGlobal->errorStream() << "\tID is available in mod " << it->second.scope;
 		}
-
-		// temporary code to smooth 0.92->0.93 transition
-		request.callback(entries.first->second.id);
-		return true;
 	}
 	logGlobal->errorStream() << "Unknown identifier " << request.type << "." << request.name << " from mod " << request.localScope;
 	return false;

+ 1 - 2
lib/JsonNode.cpp

@@ -339,7 +339,7 @@ void JsonWriter::writeEntry(JsonVector::const_iterator entry)
 
 void JsonWriter::writeString(const std::string &string)
 {
-	static const std::string escaped = "\"\\/\b\f\n\r\t";
+	static const std::string escaped = "\"\\\b\f\n\r\t";
 
 	out <<'\"';
 	size_t pos=0, start=0;
@@ -506,7 +506,6 @@ bool JsonParser::extractEscaping(std::string &str)
 	{
 		break; case '\"': str += '\"';
 		break; case '\\': str += '\\';
-		break; case  '/': str += '/';
 		break; case 'b': str += '\b';
 		break; case 'f': str += '\f';
 		break; case 'n': str += '\n';

+ 2 - 2
lib/NetPacksLib.cpp

@@ -302,7 +302,7 @@ DLL_LINKAGE void RemoveObject::applyGs( CGameState *gs )
 {
 
 	CGObjectInstance *obj = gs->getObjInstance(id);
-	logGlobal->debugStream() << "removing object id=" << id << "; address=" << (int)obj << "; name=" << obj->getHoverText();
+	logGlobal->debugStream() << "removing object id=" << id << "; address=" << (intptr_t)obj << "; name=" << obj->getHoverText();
 	//unblock tiles
 	if(obj->defInfo)
 	{
@@ -595,7 +595,7 @@ DLL_LINKAGE void NewObject::applyGs( CGameState *gs )
 	o->initObj();
 	assert(o->defInfo);
 
-	logGlobal->debugStream() << "added object id=" << id << "; address=" << (int)o << "; name=" << o->getHoverText();
+	logGlobal->debugStream() << "added object id=" << id << "; address=" << (intptr_t)o << "; name=" << o->getHoverText();
 }
 
 DLL_LINKAGE void NewArtifact::applyGs( CGameState *gs )

+ 4 - 2
lib/filesystem/CCompressedStream.h

@@ -14,6 +14,8 @@
 
 struct z_stream_s;
 
+/// Abstract class that provides buffer for one-directional input streams (e.g. compressed data)
+/// Used for zip archives support and in .lod deflate compression
 class CBufferedStream : public CInputStream
 {
 public:
@@ -86,7 +88,7 @@ private:
 
 /**
  * A class which provides method definitions for reading a gzip-compressed file
- * This class implements lazy loading - data will be decompressed (and cached by this class) only by request
+ * This class implements lazy loading - data will be decompressed (and cached) only by request
  */
 class DLL_LINKAGE CCompressedStream : public CBufferedStream
 {
@@ -103,7 +105,7 @@ public:
 	~CCompressedStream();
 
 	/**
-	 * Prepare stream for decompression of next block (e.g. nect part of h3c)
+	 * Prepare stream for decompression of next block (e.g. next part of h3c)
 	 * Applicable only for streams that contain multiple concatenated compressed data
 	 *
 	 * @return false if next block was not found, true othervice

+ 101 - 1
lib/filesystem/CZipLoader.cpp

@@ -2,6 +2,8 @@
 #include "../../Global.h"
 #include "CZipLoader.h"
 
+#include "../ScopeGuard.h"
+
 /*
  * CZipLoader.cpp, part of VCMI engine
  *
@@ -99,4 +101,102 @@ std::unordered_set<ResourceID> CZipLoader::getFilteredFiles(std::function<bool(c
 			foundID.insert(file.first);
 	}
 	return foundID;
-}
+}
+
+/// extracts currently selected file from zip into stream "where"
+static bool extractCurrent(unzFile file, std::ostream & where)
+{
+	std::array<char, 8 * 1024> buffer;
+
+	unzOpenCurrentFile(file);
+
+	while (1)
+	{
+		int readSize = unzReadCurrentFile(file, buffer.data(), buffer.size());
+
+		if (readSize < 0) // error
+			break;
+
+		if (readSize == 0) // end-of-file. Also performs CRC check
+			return unzCloseCurrentFile(file) == UNZ_OK;
+
+		if (readSize > 0) // successfull read
+		{
+			where.write(buffer.data(), readSize);
+			if (!where.good())
+				break;
+		}
+	}
+
+	// extraction failed. Close file and exit
+	unzCloseCurrentFile(file);
+	return false;
+}
+
+std::vector<std::string> ZipArchive::listFiles(std::string filename)
+{
+	std::vector<std::string> ret;
+
+	unzFile file = unzOpen(filename.c_str());
+
+	if (unzGoToFirstFile(file) == UNZ_OK)
+	{
+		do
+		{
+			unz_file_info info;
+			std::vector<char> filename;
+
+			unzGetCurrentFileInfo (file, &info, nullptr, 0, nullptr, 0, nullptr, 0);
+
+			filename.resize(info.size_filename);
+			// Get name of current file. Contrary to docs "info" parameter can't be null
+			unzGetCurrentFileInfo (file, &info, filename.data(), filename.size(), nullptr, 0, nullptr, 0);
+
+			ret.push_back(std::string(filename.data(), filename.size()));
+		}
+		while (unzGoToNextFile(file) == UNZ_OK);
+	}
+	unzClose(file);
+
+	return ret;
+}
+
+bool ZipArchive::extract(std::string from, std::string where)
+{
+	// Note: may not be fast enough for large archives (should NOT happen with mods)
+	// because locating each file by name may be slow. Unlikely slower than decompression though
+	return extract(from, where, listFiles(from));
+}
+
+bool ZipArchive::extract(std::string from, std::string where, std::vector<std::string> what)
+{
+	unzFile archive = unzOpen(from.c_str());
+
+	auto onExit = vstd::makeScopeGuard([&]()
+	{
+		unzClose(archive);
+	});
+
+	for (std::string & file : what)
+	{
+		if (unzLocateFile(archive, file.c_str(), 1) != UNZ_OK)
+			return false;
+
+		std::string fullName = where + '/' + file;
+		std::string fullPath = fullName.substr(0, fullName.find_last_of("/"));
+
+		boost::filesystem::create_directories(fullPath);
+		// directory. No file to extract
+		// TODO: better way to detect directory? Probably check return value of unzOpenCurrentFile?
+		if (boost::algorithm::ends_with(file, "/"))
+			continue;
+
+		std::ofstream destFile(fullName);
+		if (!destFile.good())
+			return false;
+
+		if (!extractCurrent(archive, destFile))
+			return false;
+	}
+	return true;
+}

+ 14 - 1
lib/filesystem/CZipLoader.h

@@ -54,4 +54,17 @@ public:
 	bool existsResource(const ResourceID & resourceName) const override;
 	std::string getMountPoint() const override;
 	std::unordered_set<ResourceID> getFilteredFiles(std::function<bool(const ResourceID &)> filter) const override;
-};
+};
+
+
+namespace ZipArchive
+{
+	/// List all files present in archive
+	std::vector<std::string> DLL_LINKAGE listFiles(std::string filename);
+
+	/// extracts all files from archive "from" into destination directory "where". Directory must exist
+	bool DLL_LINKAGE extract(std::string from, std::string where);
+
+	///same as above, but extracts only files mentioned in "what" list
+	bool DLL_LINKAGE extract(std::string from, std::string where, std::vector<std::string> what);
+}

+ 2 - 1
server/CGameHandler.cpp

@@ -22,6 +22,7 @@
 #include "../lib/VCMI_Lib.h"
 #include "../lib/mapping/CMap.h"
 #include "../lib/VCMIDirs.h"
+#include "../lib/ScopeGuard.h"
 #include "../client/CSoundBase.h"
 #include "CGameHandler.h"
 #include "CVCMIServer.h"
@@ -3536,7 +3537,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba )
 
 			StartAction start_action(ba);
 			sendAndApply(&start_action);
-			auto makeScopeGuard([&]{ sendAndApply(&end_action); }); //if we started than we have to finish
+			auto onExit = vstd::makeScopeGuard([&]{ sendAndApply(&end_action); }); //if we started than we have to finish
 
 			const CGHeroInstance * attackingHero = gs->curB->battleGetFightingHero(ba.side);
 			CHeroHandler::SBallisticsLevelInfo sbi = VLC->heroh->ballistics[attackingHero->getSecSkillLevel(SecondarySkill::BALLISTICS)];