From 5654fef9010cb39a1843a39ed5aae5309a513133 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 19 Aug 2013 11:50:53 +0000 Subject: [PATCH] - 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 --- AI/VCAI/VCAI.cpp | 2 +- CMakeLists.txt | 13 +++- Mods/WoG/mod.json | 41 ++++++------ Mods/vcmi/mod.json | 11 ++-- config/schemas/mod.json | 24 ++++++- config/schemas/settings.json | 17 ++++- lib/CBattleCallback.cpp | 2 +- lib/CHeroHandler.cpp | 15 ++--- lib/CModHandler.cpp | 4 -- lib/JsonNode.cpp | 3 +- lib/NetPacksLib.cpp | 4 +- lib/filesystem/CCompressedStream.h | 6 +- lib/filesystem/CZipLoader.cpp | 102 ++++++++++++++++++++++++++++- lib/filesystem/CZipLoader.h | 15 ++++- server/CGameHandler.cpp | 3 +- 15 files changed, 209 insertions(+), 53 deletions(-) diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index ab51569b1..038b27d30 100644 --- a/AI/VCAI/VCAI.cpp +++ b/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() diff --git a/CMakeLists.txt b/CMakeLists.txt index 54d0cc975..f187994c6 100644 --- a/CMakeLists.txt +++ b/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() diff --git a/Mods/WoG/mod.json b/Mods/WoG/mod.json index 71d50067e..0563a6876 100644 --- a/Mods/WoG/mod.json +++ b/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" - ] + } } diff --git a/Mods/vcmi/mod.json b/Mods/vcmi/mod.json index d9e284bae..98ad0bdfb 100644 --- a/Mods/vcmi/mod.json +++ b/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" + } } diff --git a/config/schemas/mod.json b/config/schemas/mod.json index c97aecc2a..75664704b 100644 --- a/config/schemas/mod.json +++ b/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" } } diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 25d1db8ab..e81ead63d 100644 --- a/config/schemas/settings.json +++ b/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" + } + } + } } } } diff --git a/lib/CBattleCallback.cpp b/lib/CBattleCallback.cpp index 8d7b3c553..3a47a42d0 100644 --- a/lib/CBattleCallback.cpp +++ b/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)) diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 0e61fe3cc..8d9271a6f 100644 --- a/lib/CHeroHandler.cpp +++ b/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)); + }); } } diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index 9fb453234..cbe5ad214 100644 --- a/lib/CModHandler.cpp +++ b/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; diff --git a/lib/JsonNode.cpp b/lib/JsonNode.cpp index 7ddee47ed..7a8b5e491 100644 --- a/lib/JsonNode.cpp +++ b/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'; diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index a9a925795..f4e764160 100644 --- a/lib/NetPacksLib.cpp +++ b/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 ) diff --git a/lib/filesystem/CCompressedStream.h b/lib/filesystem/CCompressedStream.h index 409f27470..c46757566 100644 --- a/lib/filesystem/CCompressedStream.h +++ b/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 diff --git a/lib/filesystem/CZipLoader.cpp b/lib/filesystem/CZipLoader.cpp index 642cd519d..088a7aef7 100644 --- a/lib/filesystem/CZipLoader.cpp +++ b/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 CZipLoader::getFilteredFiles(std::function 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 ZipArchive::listFiles(std::string filename) +{ + std::vector ret; + + unzFile file = unzOpen(filename.c_str()); + + if (unzGoToFirstFile(file) == UNZ_OK) + { + do + { + unz_file_info info; + std::vector 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 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; +} diff --git a/lib/filesystem/CZipLoader.h b/lib/filesystem/CZipLoader.h index 5e12cd6be..fda3c2dbd 100644 --- a/lib/filesystem/CZipLoader.h +++ b/lib/filesystem/CZipLoader.h @@ -54,4 +54,17 @@ public: bool existsResource(const ResourceID & resourceName) const override; std::string getMountPoint() const override; std::unordered_set getFilteredFiles(std::function filter) const override; -}; \ No newline at end of file +}; + + +namespace ZipArchive +{ + /// List all files present in archive + std::vector 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 what); +} \ No newline at end of file diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 2de72030f..6c89a863a 100644 --- a/server/CGameHandler.cpp +++ b/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)];