diff --git a/client/CMT.cpp b/client/CMT.cpp index 26459e815..649ae3c98 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -59,6 +59,7 @@ #include "../lib/serializer/Connection.h" #include "CServerHandler.h" #include "gui/NotificationHandler.h" +#include "resourceExtractor/ResourceConverter.h" #include @@ -253,6 +254,7 @@ int main(int argc, char * argv[]) // Init filesystem and settings preinitDLL(::console); + settings.init(); Settings session = settings.write["session"]; auto setSettingBool = [](std::string key, std::string arg) { @@ -464,6 +466,8 @@ int main(int argc, char * argv[]) CCS->curh = new CCursorHandler(); graphics = new Graphics(); // should be before curh->init() + ConvertOriginalResourceFiles(); + CCS->curh->initCursor(); logGlobal->info("Screen handler: %d ms", pomtime.getDiff()); pomtime.getDiff(); diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 07fd813ae..faa155529 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -54,6 +54,8 @@ set(client_SRCS lobby/OptionsTab.cpp lobby/RandomMapTab.cpp lobby/SelectionTab.cpp + + resourceExtractor/ResourceConverter.cpp CBitmapHandler.cpp CreatureCostBox.cpp @@ -128,6 +130,8 @@ set(client_HEADERS lobby/OptionsTab.h lobby/RandomMapTab.h lobby/SelectionTab.h + + resourceExtractor/ResourceConverter.h CBitmapHandler.h CreatureCostBox.h diff --git a/client/gui/CAnimation.cpp b/client/gui/CAnimation.cpp index be4c357a2..e3fc8a7e4 100644 --- a/client/gui/CAnimation.cpp +++ b/client/gui/CAnimation.cpp @@ -951,7 +951,7 @@ void CAnimation::initFromJson(const JsonNode & config) } } -void CAnimation::exportBitmaps(const boost::filesystem::path& path) const +void CAnimation::exportBitmaps(const boost::filesystem::path& path, bool prependResourceName) const { if(images.empty()) { @@ -959,7 +959,7 @@ void CAnimation::exportBitmaps(const boost::filesystem::path& path) const return; } - boost::filesystem::path actualPath = path / "SPRITES" / name; + boost::filesystem::path actualPath = path / "Sprites" / name; boost::filesystem::create_directories(actualPath); size_t counter = 0; @@ -974,9 +974,13 @@ void CAnimation::exportBitmaps(const boost::filesystem::path& path) const const auto img = imagePair.second; boost::format fmt("%d_%d.bmp"); - fmt % group % frame; + fmt% group% frame; + std::string fileName = fmt.str(); + if (prependResourceName) + fileName = name + "_" + fileName; + + img->exportBitmap(actualPath / fileName); - img->exportBitmap(actualPath / fmt.str()); counter++; } } diff --git a/client/gui/CAnimation.h b/client/gui/CAnimation.h index cb885fa36..0ecd5e94c 100644 --- a/client/gui/CAnimation.h +++ b/client/gui/CAnimation.h @@ -122,7 +122,7 @@ public: std::shared_ptr getImage(size_t frame, size_t group=0, bool verbose=true) const; - void exportBitmaps(const boost::filesystem::path & path) const; + void exportBitmaps(const boost::filesystem::path & path, bool prependResourceName = false) const; //all available frames void load (); diff --git a/client/resourceExtractor/ResourceConverter.cpp b/client/resourceExtractor/ResourceConverter.cpp new file mode 100644 index 000000000..15bc8152b --- /dev/null +++ b/client/resourceExtractor/ResourceConverter.cpp @@ -0,0 +1,126 @@ +#include "StdInc.h" + +#include "ResourceConverter.h" + +#include "../lib/JsonNode.h" +#include "../lib/VCMIDirs.h" +#include "../lib/filesystem/Filesystem.h" + +#include "SDL.h" +#include "./gui/CAnimation.h" +#include "CBitmapHandler.h" + +#include "boost/filesystem/path.hpp" +#include "boost/locale.hpp" + +namespace bfs = boost::filesystem; + +bool split_def_files = 1; +bool convert_pcx_to_bmp = 1; // converts Images from .pcx to bmp. Can be used when you have .pcx converted already +bool delete_source_files = 1; // delete source files or leave a copy in place. + +// converts all pcx files into bmp (H3 saves images as .pcx) +void convertPcxToBmp() +{ + bfs::path extractedPath = VCMIDirs::get().userDataPath() / "extracted"; + bfs::path imagesPath = extractedPath / "Images"; + + bfs::directory_iterator end_iter; + + for ( bfs::directory_iterator dir_itr(imagesPath); dir_itr != end_iter; ++dir_itr ) + { + try + { + if ( bfs::is_regular_file( dir_itr->status() ) ) + { + std::string filename = dir_itr->path().filename().string(); + filename = boost::locale::to_lower(filename); + + if(filename.find(".pcx") != std::string::npos) + { + SDL_Surface *bitmap; + + bitmap = BitmapHandler::loadBitmap(filename); + + if(delete_source_files) + bfs::remove(imagesPath / filename); + + bfs::path outFilePath = imagesPath / filename; + outFilePath.replace_extension(".bmp"); + SDL_SaveBMP(bitmap, outFilePath.string().c_str()); + } + } + else + { + logGlobal->info(dir_itr->path().filename().string() + " [other]\n"); + } + } + catch ( const std::exception & ex ) + { + logGlobal->info(dir_itr->path().filename().string() + " " + ex.what() + "\n"); + } + } +} + +// splits a def file into individual parts +void splitDefFile(std::string fileName, bfs::path spritesPath) +{ + if (CResourceHandler::get()->existsResource(ResourceID("SPRITES/" + fileName))) + { + std::string URI = fileName; + std::unique_ptr anim = make_unique(URI); + anim->preload(); + anim->exportBitmaps(VCMIDirs::get().userCachePath() / "extracted", true); + + if(delete_source_files) + bfs::remove(spritesPath / fileName); + } + else + logGlobal->error("Def File Split error! " + fileName); +} + +// split def files, this way faction resources are independent +void splitDefFiles() +{ + bfs::path extractedPath = VCMIDirs::get().userDataPath() / "extracted"; + bfs::path spritesPath = extractedPath / "Sprites"; + + splitDefFile("TwCrPort.def", spritesPath); // split town creature portraits + splitDefFile("CPRSMALL.def", spritesPath); // split hero army creature portraits + splitDefFile("FlagPort.def", spritesPath); // adventure map dwellings + splitDefFile("ITPA.def", spritesPath); // small town icons + splitDefFile("ITPt.def", spritesPath); // big town icons + splitDefFile("Un32.def", spritesPath); // big town icons + splitDefFile("Un44.def", spritesPath); // big town icons +} + +// Splits def files that are shared between factions and converts pcx to bmp +void ConvertOriginalResourceFiles() +{ + if (split_def_files) + splitDefFiles(); + + if (convert_pcx_to_bmp) + convertPcxToBmp(); +} + + + + + + + + + + + + + + + + + + + + + diff --git a/client/resourceExtractor/ResourceConverter.h b/client/resourceExtractor/ResourceConverter.h new file mode 100644 index 000000000..57eda0cd4 --- /dev/null +++ b/client/resourceExtractor/ResourceConverter.h @@ -0,0 +1,13 @@ +#pragma once + +/* + * VCMI_Lib.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +void ConvertOriginalResourceFiles(); diff --git a/lib/filesystem/CArchiveLoader.cpp b/lib/filesystem/CArchiveLoader.cpp index f53241d46..d2a6f783e 100644 --- a/lib/filesystem/CArchiveLoader.cpp +++ b/lib/filesystem/CArchiveLoader.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "CArchiveLoader.h" +#include "VCMIDirs.h" #include "CFileInputStream.h" #include "CCompressedStream.h" @@ -17,13 +18,17 @@ VCMI_LIB_NAMESPACE_BEGIN +namespace bfs = boost::filesystem; + +const bool extractArchives = 1; + ArchiveEntry::ArchiveEntry() : offset(0), fullSize(0), compressedSize(0) { } -CArchiveLoader::CArchiveLoader(std::string _mountPoint, boost::filesystem::path _archive) : +CArchiveLoader::CArchiveLoader(std::string _mountPoint, bfs::path _archive) : archive(std::move(_archive)), mountPoint(std::move(_mountPoint)) { @@ -77,6 +82,28 @@ void CArchiveLoader::initLODArchive(const std::string &mountPoint, CFileInputStr // Add lod entry to local entries map entries[ResourceID(mountPoint + entry.name)] = entry; + + if(extractArchives) + { + si64 currentPosition = fileStream.tell(); // save filestream position + + boost::locale::generator gen; + std::locale::global(gen("")); // Create locale generator + + std::string fName = filename; + boost::to_upper(fName); + + if(fName.find(".PCX") != std::string::npos) + extractToFolder("Images", mountPoint, entry); + else if ((fName.find(".DEF") != std::string::npos ) || (fName.find(".MSK") != std::string::npos) || (fName.find(".FNT") != std::string::npos) || (fName.find(".PAL") != std::string::npos)) + extractToFolder("Sprites", mountPoint, entry); + else if ((fName.find(".h3c") != std::string::npos)) + extractToFolder("Sprites", mountPoint, entry); + else + extractToFolder("Misc", mountPoint, entry); + + fileStream.seek(currentPosition); // restore filestream position + } } } @@ -112,6 +139,9 @@ void CArchiveLoader::initVIDArchive(const std::string &mountPoint, CFileInputStr auto it = offsets.find(entry.second.offset); it++; entry.second.fullSize = *it - entry.second.offset; + + if(extractArchives) + extractToFolder("Video", fileStream, entry.second); } } @@ -139,6 +169,9 @@ void CArchiveLoader::initSNDArchive(const std::string &mountPoint, CFileInputStr entry.fullSize = reader.readInt32(); entry.compressedSize = 0; entries[ResourceID(mountPoint + entry.name)] = entry; + + if(extractArchives) + extractToFolder("Sound", fileStream, entry); } } @@ -182,4 +215,48 @@ std::unordered_set CArchiveLoader::getFilteredFiles(std::function data = std::unique_ptr(new char[entry.fullSize]); + fileStream.seek(entry.offset); + fileStream.read((ui8*)data.get(), entry.fullSize); + + bfs::path extractedFilePath = createExtractedFilePath(outputSubFolder, entry.name); + + // writeToOutputFile + std::ofstream out(extractedFilePath.string(), std::ofstream::binary); + out.exceptions(std::ifstream::failbit | std::ifstream::badbit); + out.write(data.get(), entry.fullSize); + + fileStream.seek(currentPosition); // restore filestream position +} + +void CArchiveLoader::extractToFolder( std::string outputSubFolder, const std::string& mountPoint, ArchiveEntry entry) +{ + + std::unique_ptr & inputStream = load(ResourceID(mountPoint + entry.name)); + + std::unique_ptr data = std::unique_ptr(new char[entry.fullSize]); + inputStream->read((ui8*)data.get(), entry.fullSize); + + bfs::path extractedFilePath = createExtractedFilePath(outputSubFolder, entry.name); + + // writeToOutputFile + std::ofstream out(extractedFilePath.string(), std::ofstream::binary); + out.exceptions(std::ifstream::failbit | std::ifstream::badbit); + out.write(data.get(), entry.fullSize); +} + +bfs::path createExtractedFilePath(std::string outputSubFolder, std::string entryName) +{ + bfs::path extractionFolderPath = VCMIDirs::get().userCachePath() / "extracted" / outputSubFolder; + bfs::path extractedFilePath = extractionFolderPath / entryName; + + bfs::create_directories(extractionFolderPath); + + return extractedFilePath; +} + VCMI_LIB_NAMESPACE_END diff --git a/lib/filesystem/CArchiveLoader.h b/lib/filesystem/CArchiveLoader.h index e7d294188..0e8d5d0a8 100644 --- a/lib/filesystem/CArchiveLoader.h +++ b/lib/filesystem/CArchiveLoader.h @@ -64,6 +64,10 @@ public: std::string getMountPoint() const override; void updateFilteredFiles(std::function filter) const override {} std::unordered_set getFilteredFiles(std::function filter) const override; + /** Extracts one archive entry to the specified subfolder. Used for Video and Sound */ + void extractToFolder(std::string outputSubFolder, CFileInputStream & fileStream, ArchiveEntry entry); + /** Extracts one archive entry to the specified subfolder. Used for Images, Sprites, etc */ + void extractToFolder(std::string outputSubFolder, const std::string &mountPoint, ArchiveEntry entry); private: /** @@ -96,4 +100,7 @@ private: std::unordered_map entries; }; +/** Constructs the file path for the extracted file. Creates the subfolder hierarchy aswell **/ +boost::filesystem::path createExtractedFilePath(std::string outputSubFolder, std::string entryName); + VCMI_LIB_NAMESPACE_END