1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-03-19 21:10:12 +02:00

Merge pull request #1119 from krs0/feature/H3_original_files_extractor

VCMI H3 original archives extractor #1118
This commit is contained in:
Nordsoft91 2022-11-29 21:54:57 +04:00 committed by GitHub
commit c6d317e409
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 381 additions and 80 deletions

View File

@ -667,7 +667,7 @@ void processCommand(const std::string &message)
std::cout << "Command accepted.\t";
const bfs::path outPath =
VCMIDirs::get().userCachePath() / "extracted";
VCMIDirs::get().userExtractedPath();
bfs::create_directories(outPath);
@ -700,7 +700,7 @@ void processCommand(const std::string &message)
std::cout << "Command accepted.\t";
const bfs::path outPath =
VCMIDirs::get().userCachePath() / "extracted" / "configuration";
VCMIDirs::get().userExtractedPath() / "configuration";
bfs::create_directories(outPath);
@ -741,7 +741,7 @@ void processCommand(const std::string &message)
std::cout << "Command accepted.\t";
const bfs::path outPath =
VCMIDirs::get().userCachePath() / "extracted" / "scripts";
VCMIDirs::get().userExtractedPath() / "scripts";
bfs::create_directories(outPath);
@ -764,7 +764,7 @@ void processCommand(const std::string &message)
std::cout << "Command accepted.\t";
const bfs::path outPath =
VCMIDirs::get().userCachePath() / "extracted";
VCMIDirs::get().userExtractedPath();
auto list = CResourceHandler::get()->getFilteredFiles([](const ResourceID & ident)
{
@ -880,7 +880,7 @@ void processCommand(const std::string &message)
readed >> URI;
std::unique_ptr<CAnimation> anim = make_unique<CAnimation>(URI);
anim->preload();
anim->exportBitmaps(VCMIDirs::get().userCachePath() / "extracted");
anim->exportBitmaps(VCMIDirs::get().userExtractedPath());
}
else if(cn == "extract")
{
@ -889,7 +889,7 @@ void processCommand(const std::string &message)
if (CResourceHandler::get()->existsResource(ResourceID(URI)))
{
const bfs::path outPath = VCMIDirs::get().userCachePath() / "extracted" / URI;
const bfs::path outPath = VCMIDirs::get().userExtractedPath() / URI;
auto data = CResourceHandler::get()->load(ResourceID(URI))->readAll();

View File

@ -23,6 +23,8 @@ bfs::path IVCMIDirs::userLogsPath() const { return userCachePath(); }
bfs::path IVCMIDirs::userSavePath() const { return userDataPath() / "Saves"; }
bfs::path IVCMIDirs::userExtractedPath() const { return userCachePath() / "extracted"; }
bfs::path IVCMIDirs::fullLibraryPath(const std::string &desiredFolder, const std::string &baseLibName) const
{
return libraryPath() / desiredFolder / libraryName(baseLibName);
@ -36,15 +38,16 @@ std::string IVCMIDirs::genHelpString() const
const auto gdStringA = boost::algorithm::join(tempVec, ":");
return
" game data: " + gdStringA + "\n"
" libraries: " + libraryPath().string() + "\n"
" server: " + serverPath().string() + "\n"
" game data: " + gdStringA + "\n"
" libraries: " + libraryPath().string() + "\n"
" server: " + serverPath().string() + "\n"
"\n"
" user data: " + userDataPath().string() + "\n"
" user cache: " + userCachePath().string() + "\n"
" user config: " + userConfigPath().string() + "\n"
" user logs: " + userLogsPath().string() + "\n"
" user saves: " + userSavePath().string() + "\n"; // Should end without new-line?
" user data: " + userDataPath().string() + "\n"
" user cache: " + userCachePath().string() + "\n"
" user config: " + userConfigPath().string() + "\n"
" user logs: " + userLogsPath().string() + "\n"
" user saves: " + userSavePath().string() + "\n";
" user extracted: " + userExtractedPath().string() + "\n"; // Should end without new-line?
}
void IVCMIDirs::init()
@ -148,24 +151,24 @@ bool StartBatchCopyDataProgram(
class VCMIDirsWIN32 final : public IVCMIDirs
{
public:
boost::filesystem::path userDataPath() const override;
boost::filesystem::path userCachePath() const override;
boost::filesystem::path userConfigPath() const override;
bfs::path userDataPath() const override;
bfs::path userCachePath() const override;
bfs::path userConfigPath() const override;
std::vector<boost::filesystem::path> dataPaths() const override;
std::vector<bfs::path> dataPaths() const override;
boost::filesystem::path clientPath() const override;
boost::filesystem::path serverPath() const override;
bfs::path clientPath() const override;
bfs::path serverPath() const override;
boost::filesystem::path libraryPath() const override;
boost::filesystem::path binaryPath() const override;
bfs::path libraryPath() const override;
bfs::path binaryPath() const override;
std::string libraryName(const std::string& basename) const override;
void init() override;
protected:
boost::filesystem::path oldUserDataPath() const;
boost::filesystem::path oldUserSavePath() const;
bfs::path oldUserDataPath() const;
bfs::path oldUserSavePath() const;
};
void VCMIDirsWIN32::init()
@ -355,8 +358,8 @@ std::string VCMIDirsWIN32::libraryName(const std::string& basename) const { retu
class IVCMIDirsUNIX : public IVCMIDirs
{
public:
boost::filesystem::path clientPath() const override;
boost::filesystem::path serverPath() const override;
bfs::path clientPath() const override;
bfs::path serverPath() const override;
virtual bool developmentMode() const;
};
@ -427,14 +430,14 @@ bfs::path VCMIDirsIOS::binaryPath() const { return {iOS_utils::bundlePath()}; }
class VCMIDirsOSX final : public VCMIDirsApple
{
public:
boost::filesystem::path userDataPath() const override;
boost::filesystem::path userCachePath() const override;
boost::filesystem::path userLogsPath() const override;
bfs::path userDataPath() const override;
bfs::path userCachePath() const override;
bfs::path userLogsPath() const override;
std::vector<boost::filesystem::path> dataPaths() const override;
std::vector<bfs::path> dataPaths() const override;
boost::filesystem::path libraryPath() const override;
boost::filesystem::path binaryPath() const override;
bfs::path libraryPath() const override;
bfs::path binaryPath() const override;
void init() override;
};
@ -464,12 +467,12 @@ void VCMIDirsOSX::init()
for (bfs::directory_iterator file(from); file != bfs::directory_iterator(); ++file)
{
const boost::filesystem::path& srcFilePath = file->path();
const boost::filesystem::path dstFilePath = to / srcFilePath.filename();
const bfs::path& srcFilePath = file->path();
const bfs::path dstFilePath = to / srcFilePath.filename();
// TODO: Aplication should ask user what to do when file exists:
// replace/ignore/stop process/replace all/ignore all
if (!boost::filesystem::exists(dstFilePath))
if (!bfs::exists(dstFilePath))
bfs::rename(srcFilePath, dstFilePath);
}
@ -526,14 +529,14 @@ bfs::path VCMIDirsOSX::binaryPath() const { return "."; }
class VCMIDirsXDG : public IVCMIDirsUNIX
{
public:
boost::filesystem::path userDataPath() const override;
boost::filesystem::path userCachePath() const override;
boost::filesystem::path userConfigPath() const override;
bfs::path userDataPath() const override;
bfs::path userCachePath() const override;
bfs::path userConfigPath() const override;
std::vector<boost::filesystem::path> dataPaths() const override;
std::vector<bfs::path> dataPaths() const override;
boost::filesystem::path libraryPath() const override;
boost::filesystem::path binaryPath() const override;
bfs::path libraryPath() const override;
bfs::path binaryPath() const override;
std::string libraryName(const std::string& basename) const override;
};

View File

@ -29,6 +29,9 @@ public:
// Path to saved games
virtual boost::filesystem::path userSavePath() const;
// Path to "extracted" directory, used to temporarily hold extracted Original H3 files
virtual boost::filesystem::path userExtractedPath() const;
// Paths to global system-wide data directories. First items have higher priority
virtual std::vector<boost::filesystem::path> dataPaths() const = 0;

View File

@ -39,13 +39,13 @@ VCMI_LIB_NAMESPACE_BEGIN
LibClasses * VLC = nullptr;
DLL_LINKAGE void preinitDLL(CConsoleHandler * Console, bool onlyEssential)
DLL_LINKAGE void preinitDLL(CConsoleHandler * Console, bool onlyEssential, bool extractArchives)
{
console = Console;
VLC = new LibClasses();
try
{
VLC->loadFilesystem(onlyEssential);
VLC->loadFilesystem(onlyEssential, extractArchives);
}
catch(...)
{
@ -157,7 +157,7 @@ void LibClasses::updateEntity(Metatype metatype, int32_t index, const JsonNode &
}
}
void LibClasses::loadFilesystem(bool onlyEssential)
void LibClasses::loadFilesystem(bool onlyEssential, bool extractArchives)
{
CStopWatch totalTime;
CStopWatch loadTime;
@ -165,7 +165,7 @@ void LibClasses::loadFilesystem(bool onlyEssential)
CResourceHandler::initialize();
logGlobal->info("\tInitialization: %d ms", loadTime.getDiff());
CResourceHandler::load("config/filesystem.json");
CResourceHandler::load("config/filesystem.json", extractArchives);
logGlobal->info("\tData loading: %d ms", loadTime.getDiff());
modh = new CModHandler();

View File

@ -100,8 +100,8 @@ public:
void init(bool onlyEssential); //uses standard config file
void clear(); //deletes all handlers and its data
void loadFilesystem(bool onlyEssential);// basic initialization. should be called before init()
// basic initialization. should be called before init(). Can also extract original H3 archives
void loadFilesystem(bool onlyEssential, bool extractArchives = false);
#if SCRIPTING_ENABLED
void scriptsLoaded();
@ -151,7 +151,7 @@ public:
extern DLL_LINKAGE LibClasses * VLC;
DLL_LINKAGE void preinitDLL(CConsoleHandler * Console, bool onlyEssential = false);
DLL_LINKAGE void preinitDLL(CConsoleHandler * Console, bool onlyEssential = false, bool extractArchives = false);
DLL_LINKAGE void loadDLLClasses(bool onlyEssential = false);

View File

@ -10,6 +10,7 @@
#include "StdInc.h"
#include "CArchiveLoader.h"
#include "VCMIDirs.h"
#include "CFileInputStream.h"
#include "CCompressedStream.h"
@ -23,9 +24,10 @@ ArchiveEntry::ArchiveEntry()
}
CArchiveLoader::CArchiveLoader(std::string _mountPoint, boost::filesystem::path _archive) :
CArchiveLoader::CArchiveLoader(std::string _mountPoint, bfs::path _archive, bool extractArchives) :
archive(std::move(_archive)),
mountPoint(std::move(_mountPoint))
mountPoint(std::move(_mountPoint)),
extractArchives(extractArchives)
{
// Open archive file(.snd, .vid, .lod)
CFileInputStream fileStream(archive);
@ -77,6 +79,25 @@ 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
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 +133,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 +163,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 +209,39 @@ std::unordered_set<ResourceID> CArchiveLoader::getFilteredFiles(std::function<bo
return foundID;
}
void CArchiveLoader::extractToFolder(const std::string & outputSubFolder, CInputStream & fileStream, ArchiveEntry entry)
{
si64 currentPosition = fileStream.tell(); // save filestream position
std::vector<ui8> data(entry.fullSize);
fileStream.seek(entry.offset);
fileStream.read(data.data(), 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((char*)data.data(), entry.fullSize);
fileStream.seek(currentPosition); // restore filestream position
}
void CArchiveLoader::extractToFolder(const std::string & outputSubFolder, const std::string & mountPoint, ArchiveEntry entry)
{
std::unique_ptr<CInputStream> inputStream = load(ResourceID(mountPoint + entry.name));
extractToFolder(outputSubFolder, *inputStream, entry);
}
bfs::path createExtractedFilePath(const std::string & outputSubFolder, const std::string & entryName)
{
bfs::path extractionFolderPath = VCMIDirs::get().userExtractedPath() / outputSubFolder;
bfs::path extractedFilePath = extractionFolderPath / entryName;
bfs::create_directories(extractionFolderPath);
return extractedFilePath;
}
VCMI_LIB_NAMESPACE_END

View File

@ -12,6 +12,8 @@
#include "ISimpleResourceLoader.h"
#include "ResourceID.h"
namespace bfs = boost::filesystem;
VCMI_LIB_NAMESPACE_BEGIN
class CFileInputStream;
@ -52,10 +54,11 @@ public:
* These are valid extensions: .LOD, .SND, .VID
*
* @param archive Specifies the file path to the archive which should be indexed and loaded.
* @param extractArchives Specifies if the original H3 archives should be extracted to a separate folder.
*
* @throws std::runtime_error if the archive wasn't found or if the archive isn't supported
*/
CArchiveLoader(std::string mountPoint, boost::filesystem::path archive);
CArchiveLoader(std::string mountPoint, bfs::path archive, bool extractArchives = false);
/// Interface implementation
/// @see ISimpleResourceLoader
@ -64,6 +67,10 @@ public:
std::string getMountPoint() const override;
void updateFilteredFiles(std::function<bool(const std::string &)> filter) const override {}
std::unordered_set<ResourceID> getFilteredFiles(std::function<bool(const ResourceID &)> filter) const override;
/** Extracts one archive entry to the specified subfolder. Used for Video and Sound */
void extractToFolder(const std::string & outputSubFolder, CInputStream & fileStream, ArchiveEntry entry);
/** Extracts one archive entry to the specified subfolder. Used for Images, Sprites, etc */
void extractToFolder(const std::string & outputSubFolder, const std::string & mountPoint, ArchiveEntry entry);
private:
/**
@ -88,12 +95,18 @@ private:
void initSNDArchive(const std::string &mountPoint, CFileInputStream & fileStream);
/** The file path to the archive which is scanned and indexed. */
boost::filesystem::path archive;
bfs::path archive;
std::string mountPoint;
/** Holds all entries of the archive file. An entry can be accessed via the entry name. **/
std::unordered_map<ResourceID, ArchiveEntry> entries;
/** Specifies if Original H3 archives should be extracted to a separate folder **/
bool extractArchives;
};
/** Constructs the file path for the extracted file. Creates the subfolder hierarchy aswell **/
bfs::path createExtractedFilePath(const std::string & outputSubFolder, const std::string & entryName);
VCMI_LIB_NAMESPACE_END

View File

@ -26,9 +26,10 @@ VCMI_LIB_NAMESPACE_BEGIN
std::map<std::string, ISimpleResourceLoader*> CResourceHandler::knownLoaders = std::map<std::string, ISimpleResourceLoader*>();
CResourceHandler CResourceHandler::globalResourceHandler;
CFilesystemGenerator::CFilesystemGenerator(std::string prefix):
CFilesystemGenerator::CFilesystemGenerator(std::string prefix, bool extractArchives):
filesystem(new CFilesystemList()),
prefix(prefix)
prefix(prefix),
extractArchives(extractArchives)
{
}
@ -105,7 +106,7 @@ void CFilesystemGenerator::loadArchive(const std::string &mountPoint, const Json
std::string URI = prefix + config["path"].String();
auto filename = CResourceHandler::get("initial")->getResourceName(ResourceID(URI, archiveType));
if (filename)
filesystem->addLoader(new CArchiveLoader(mountPoint, *filename), false);
filesystem->addLoader(new CArchiveLoader(mountPoint, *filename, extractArchives), false);
}
void CFilesystemGenerator::loadJsonMap(const std::string &mountPoint, const JsonNode & config)
@ -200,13 +201,13 @@ ISimpleResourceLoader * CResourceHandler::get(std::string identifier)
return knownLoaders.at(identifier);
}
void CResourceHandler::load(const std::string &fsConfigURI)
void CResourceHandler::load(const std::string &fsConfigURI, bool extractArchives)
{
auto fsConfigData = get("initial")->load(ResourceID(fsConfigURI, EResType::TEXT))->readAll();
const JsonNode fsConfig((char*)fsConfigData.first.get(), fsConfigData.second);
addFilesystem("data", "core", createFileSystem("", fsConfig["filesystem"]));
addFilesystem("data", "core", createFileSystem("", fsConfig["filesystem"], extractArchives));
}
void CResourceHandler::addFilesystem(const std::string & parent, const std::string & identifier, ISimpleResourceLoader * loader)
@ -246,9 +247,9 @@ bool CResourceHandler::removeFilesystem(const std::string & parent, const std::s
return true;
}
ISimpleResourceLoader * CResourceHandler::createFileSystem(const std::string & prefix, const JsonNode &fsConfig)
ISimpleResourceLoader * CResourceHandler::createFileSystem(const std::string & prefix, const JsonNode &fsConfig, bool extractArchives)
{
CFilesystemGenerator generator(prefix);
CFilesystemGenerator generator(prefix, extractArchives);
generator.loadConfig(fsConfig);
return generator.getFilesystem();
}

View File

@ -36,7 +36,8 @@ class DLL_LINKAGE CFilesystemGenerator
TLoadFunctorMap genFunctorMap();
public:
/// prefix = prefix that will be given to file entries in all nodes of this filesystem
CFilesystemGenerator(std::string prefix);
/// extractArchives = Specifies if Original H3 archives should be extracted to a separate folder
CFilesystemGenerator(std::string prefix, bool extractArchives = false);
/// loads configuration from json
/// config - configuration to load, using format of "filesystem" entry in config/filesystem.json
@ -44,6 +45,9 @@ public:
/// returns generated filesystem
CFilesystemList * getFilesystem();
/** Specifies if Original H3 archives should be extracted to a separate folder **/
bool extractArchives;
};
/**
@ -81,7 +85,7 @@ public:
* Will load all filesystem data from Json data at this path (normally - config/filesystem.json)
* @param fsConfigURI - URI from which data will be loaded
*/
static void load(const std::string & fsConfigURI);
static void load(const std::string & fsConfigURI, bool extractArchives = false);
/**
* @brief addFilesystem adds filesystem into global resource loader
@ -104,7 +108,7 @@ public:
* @param fsConfig - configuration to load
* @return generated filesystem that contains all config entries
*/
static ISimpleResourceLoader * createFileSystem(const std::string &prefix, const JsonNode & fsConfig);
static ISimpleResourceLoader * createFileSystem(const std::string &prefix, const JsonNode & fsConfig, bool extractArchives = false);
~CResourceHandler() = default;
private:

View File

@ -19,7 +19,7 @@
#include "../lib/filesystem/ISimpleResourceLoader.h"
#include "../lib/JsonNode.h"
#include "../lib/CRandomGenerator.h"
#include "../lib/VCMIDirs.h"
typedef std::map<size_t, std::vector<JsonNode>> source_map;
@ -686,6 +686,39 @@ std::shared_ptr<QImage> Animation::getImage(size_t frame, size_t group, bool ver
return nullptr;
}
void Animation::exportBitmaps(const QDir & path) const
{
if(images.empty())
{
logGlobal->error("Nothing to export, animation is empty");
return;
}
QString actualPath = path.absolutePath() + "/SPRITES/" + QString::fromStdString(name);
QDir().mkdir(actualPath);
size_t counter = 0;
for(const auto& groupPair : images)
{
size_t group = groupPair.first;
for(const auto& imagePair : groupPair.second)
{
size_t frame = imagePair.first;
const auto img = imagePair.second;
QString filename = QString("%1_%2_%3.png").arg(QString::fromStdString(name)).arg(group).arg(frame);
QString filePath = actualPath + "/" + filename;
img->save(filePath, "PNG");
counter++;
}
}
logGlobal->info("Exported %d frames to %s", counter, actualPath.toStdString());
}
void Animation::load()
{
for (auto & elem : source)
@ -774,4 +807,4 @@ void Animation::createFlippedGroup(const size_t sourceGroup, const size_t target
auto image = getImage(frame, targetGroup);
*image = image->transformed(QTransform::fromScale(1, -1));
}
}
}

View File

@ -83,6 +83,8 @@ public:
void load (size_t frame, size_t group = 0);
void unload(size_t frame, size_t group = 0);
void exportBitmaps(const QDir & path) const;
//total count of frames in group (including not loaded)
size_t size(size_t group = 0) const;

View File

@ -24,6 +24,7 @@ set(editor_SRCS
inspector/messagewidget.cpp
inspector/rewardswidget.cpp
inspector/questwidget.cpp
resourceExtractor/ResourceConverter.cpp
)
set(editor_HEADERS
@ -51,6 +52,7 @@ set(editor_HEADERS
inspector/messagewidget.h
inspector/rewardswidget.h
inspector/questwidget.h
resourceExtractor/ResourceConverter.h
)
set(editor_FORMS

View File

@ -88,7 +88,34 @@ void MainWindow::saveUserSettings()
s.setValue(mainWindowPositionSetting, pos());
}
MainWindow::MainWindow(QWidget *parent) :
void MainWindow::parseCommandLine(ExtractionOptions & extractionOptions)
{
QCommandLineParser parser;
parser.addHelpOption();
parser.addPositionalArgument("map", QCoreApplication::translate("main", "Filepath of the map to open."));
parser.addOptions({
{"e", QCoreApplication::translate("main", "Extract original H3 archives into a separate folder.")},
{"s", QCoreApplication::translate("main", "From an extracted archive, it Splits TwCrPort, CPRSMALL, FlagPort, ITPA, ITPt, Un32 and Un44 into individual PNG's.")},
{"c", QCoreApplication::translate("main", "From an extracted archive, Converts single Images (found in Images folder) from .pcx to png.")},
{"d", QCoreApplication::translate("main", "Delete original files, for the ones splitted / converted.")},
});
parser.process(qApp->arguments());
const QStringList positionalArgs = parser.positionalArguments();
if(!positionalArgs.isEmpty())
mapFilePath = positionalArgs.at(0);
extractionOptions = {
parser.isSet("e"), {
parser.isSet("s"),
parser.isSet("c"),
parser.isSet("d")}};
}
MainWindow::MainWindow(QWidget* parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
controller(this)
@ -96,49 +123,55 @@ MainWindow::MainWindow(QWidget *parent) :
ui->setupUi(this);
loadUserSettings(); //For example window size
setTitle();
// Set current working dir to executable folder.
// This is important on Mac for relative paths to work inside DMG.
QDir::setCurrent(QApplication::applicationDirPath());
ExtractionOptions extractionOptions;
parseCommandLine(extractionOptions);
//configure logging
const boost::filesystem::path logPath = VCMIDirs::get().userLogsPath() / "VCMI_Editor_log.txt";
console = new CConsoleHandler();
logConfig = new CBasicLogConfigurator(logPath, console);
logConfig->configureDefault();
logGlobal->info("The log file will be saved to %s", logPath);
//init
preinitDLL(::console);
preinitDLL(::console, false, extractionOptions.extractArchives);
settings.init();
// Initialize logging based on settings
logConfig->configure();
logGlobal->debug("settings = %s", settings.toJsonNode().toJson());
// Some basic data validation to produce better error messages in cases of incorrect install
auto testFile = [](std::string filename, std::string message) -> bool
{
if (CResourceHandler::get()->existsResource(ResourceID(filename)))
return true;
logGlobal->error("Error: %s was not found!", message);
return false;
};
if(!testFile("DATA/HELP.TXT", "Heroes III data") ||
!testFile("MODS/VCMI/MOD.JSON", "VCMI data"))
if (!testFile("DATA/HELP.TXT", "Heroes III data") ||
!testFile("MODS/VCMI/MOD.JSON", "VCMI data"))
{
QApplication::quit();
}
conf.init();
logGlobal->info("Loading settings");
init();
graphics = new Graphics(); // should be before curh->init()
graphics->load();//must be after Content loading but should be in main thread
if (extractionOptions.extractArchives)
ResourceConverter::convertExtractedResourceFiles(extractionOptions.conversionOptions);
ui->mapView->setScene(controller.scene(0));
ui->mapView->setController(&controller);
@ -167,8 +200,8 @@ MainWindow::MainWindow(QWidget *parent) :
show();
//Load map from command line
if(qApp->arguments().size() >= 2)
openMap(qApp->arguments().at(1));
if(!mapFilePath.isEmpty())
openMap(mapFilePath);
}
MainWindow::~MainWindow()

View File

@ -5,7 +5,7 @@
#include <QStandardItemModel>
#include "mapcontroller.h"
#include "../lib/Terrain.h"
#include "resourceExtractor/ResourceConverter.h"
class CMap;
class ObjectBrowser;
@ -129,6 +129,8 @@ private:
void loadUserSettings();
void saveUserSettings();
void parseCommandLine(ExtractionOptions & extractionOptions);
private:
Ui::MainWindow * ui;
ObjectBrowser * objectBrowser = nullptr;
@ -142,4 +144,7 @@ private:
int mapLevel = 0;
std::set<int> catalog;
// command line options
QString mapFilePath; // FilePath to the H3 or VCMI map to open
};

View File

@ -0,0 +1,91 @@
/*
* ResourceConverter.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
*
*/
#include "StdInc.h"
#include "ResourceConverter.h"
#include "../lib/JsonNode.h"
#include "../lib/VCMIDirs.h"
#include "../lib/filesystem/Filesystem.h"
#include "BitmapHandler.h"
#include "Animation.h"
#include "boost/filesystem/path.hpp"
#include "boost/locale.hpp"
void ResourceConverter::convertExtractedResourceFiles(ConversionOptions conversionOptions)
{
if (conversionOptions.splitDefs)
splitDefFiles(conversionOptions.deleteOriginals);
if (conversionOptions.convertPcxToPng)
doConvertPcxToPng(conversionOptions.deleteOriginals);
}
void ResourceConverter::doConvertPcxToPng(bool deleteOriginals)
{
std::string filename;
bfs::path imagesPath = VCMIDirs::get().userExtractedPath() / "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()))
return;
std::string filePath = dir_itr->path().string();
std::string fileStem = dir_itr->path().stem().string();
filename = dir_itr->path().filename().string();
std::string filenameLowerCase = boost::locale::to_lower(filename);
if(bfs::extension(filenameLowerCase) == ".pcx")
{
auto img = BitmapHandler::loadBitmap(filenameLowerCase);
bfs::path pngFilePath = imagesPath / (fileStem + ".png");
img.save(pathToQString(pngFilePath), "PNG");
if(deleteOriginals)
bfs::remove(filePath);
}
}
catch(const std::exception & ex)
{
logGlobal->info(filename + " " + ex.what() + "\n");
}
}
}
void ResourceConverter::splitDefFile(const std::string & fileName, const bfs::path & spritesPath, bool deleteOriginals)
{
if(CResourceHandler::get()->existsResource(ResourceID("SPRITES/" + fileName)))
{
std::unique_ptr<Animation> anim = make_unique<Animation>(fileName);
anim->preload();
anim->exportBitmaps(pathToQString(VCMIDirs::get().userExtractedPath()));
if(deleteOriginals)
bfs::remove(spritesPath / fileName);
}
else
logGlobal->error("Def File Split error! " + fileName);
}
void ResourceConverter::splitDefFiles(bool deleteOriginals)
{
bfs::path spritesPath = VCMIDirs::get().userExtractedPath() / "SPRITES";
for(std::string defFilename : {"TwCrPort.def", "CPRSMALL.def", "FlagPort.def", "ITPA.def", "ITPt.def", "Un32.def", "Un44.def"})
splitDefFile(defFilename, spritesPath, deleteOriginals);
}

View File

@ -0,0 +1,49 @@
/*
* ResourceConverter.h, 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
*
*/
#pragma once
namespace bfs = boost::filesystem;
// Struct for holding all Convertor Options
struct ConversionOptions
{
bool splitDefs = false; // splits TwCrPort, CPRSMALL, FlagPort, ITPA, ITPt, Un32 and Un44 into individual PNG's
bool convertPcxToPng = false; // converts single Images (found in Images folder) from .pcx to png.
bool deleteOriginals = false; // delete original files, for the ones splitted / converted.
};
// Struct for holding all Resource Extractor / Converter options
struct ExtractionOptions
{
bool extractArchives = false; // if set, original H3 archives will be extracted into a separate folder
ConversionOptions conversionOptions;
};
class ResourceConverter
{
public:
// Splits def files that are shared between factions and converts pcx to bmp depending on Extraction Options
static void convertExtractedResourceFiles(ConversionOptions conversionOptions);
private:
// converts all pcx files from /Images into PNG
static void doConvertPcxToPng(bool deleteOriginals);
// splits a def file into individual parts and converts the output to PNG format
static void splitDefFile(const std::string& fileName, const bfs::path& spritesPath, bool deleteOriginals);
// splits def files (TwCrPort, CPRSMALL, FlagPort, ITPA, ITPt, Un32 and Un44) so that faction resources are independent
// (town creature portraits, hero army creature portraits, adventure map dwellings, small town icons, big town icons,
// hero speciality small icons, hero speciality large icons)
static void splitDefFiles(bool deleteOriginals);
};