2017-07-13 10:26:03 +02:00
|
|
|
/*
|
|
|
|
* CArchiveLoader.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
|
|
|
|
*
|
|
|
|
*/
|
2013-07-28 17:49:50 +03:00
|
|
|
#include "StdInc.h"
|
|
|
|
#include "CArchiveLoader.h"
|
|
|
|
|
2022-11-08 23:39:56 +02:00
|
|
|
#include "VCMIDirs.h"
|
2013-07-28 17:49:50 +03:00
|
|
|
#include "CFileInputStream.h"
|
|
|
|
#include "CCompressedStream.h"
|
|
|
|
|
|
|
|
#include "CBinaryReader.h"
|
|
|
|
|
2022-07-26 15:07:42 +02:00
|
|
|
VCMI_LIB_NAMESPACE_BEGIN
|
|
|
|
|
2013-07-28 17:49:50 +03:00
|
|
|
ArchiveEntry::ArchiveEntry()
|
|
|
|
: offset(0), fullSize(0), compressedSize(0)
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-07-28 14:51:14 +02:00
|
|
|
CArchiveLoader::CArchiveLoader(std::string _mountPoint, boost::filesystem::path _archive, bool _extractArchives) :
|
2014-08-21 23:26:28 +03:00
|
|
|
archive(std::move(_archive)),
|
2022-11-22 18:55:18 +02:00
|
|
|
mountPoint(std::move(_mountPoint)),
|
2023-02-28 20:01:16 +02:00
|
|
|
extractArchives(_extractArchives)
|
2013-07-28 17:49:50 +03:00
|
|
|
{
|
|
|
|
// Open archive file(.snd, .vid, .lod)
|
|
|
|
CFileInputStream fileStream(archive);
|
|
|
|
|
|
|
|
// Fake .lod file with no data has to be silently ignored.
|
|
|
|
if(fileStream.getSize() < 10)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Retrieve file extension of archive in uppercase
|
2014-08-21 23:26:28 +03:00
|
|
|
const std::string ext = boost::to_upper_copy(archive.extension().string());
|
2013-07-28 17:49:50 +03:00
|
|
|
|
|
|
|
// Init the specific lod container format
|
|
|
|
if(ext == ".LOD" || ext == ".PAC")
|
|
|
|
initLODArchive(mountPoint, fileStream);
|
|
|
|
else if(ext == ".VID")
|
|
|
|
initVIDArchive(mountPoint, fileStream);
|
|
|
|
else if(ext == ".SND")
|
|
|
|
initSNDArchive(mountPoint, fileStream);
|
|
|
|
else
|
2014-08-21 23:26:28 +03:00
|
|
|
throw std::runtime_error("LOD archive format unknown. Cannot deal with " + archive.string());
|
|
|
|
|
2017-08-11 13:38:10 +02:00
|
|
|
logGlobal->trace("%sArchive \"%s\" loaded (%d files found).", ext, archive.filename(), entries.size());
|
2013-07-28 17:49:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void CArchiveLoader::initLODArchive(const std::string &mountPoint, CFileInputStream & fileStream)
|
|
|
|
{
|
|
|
|
// Read count of total files
|
|
|
|
CBinaryReader reader(&fileStream);
|
|
|
|
|
|
|
|
fileStream.seek(8);
|
|
|
|
ui32 totalFiles = reader.readUInt32();
|
|
|
|
|
|
|
|
// Get all entries from file
|
|
|
|
fileStream.seek(0x5c);
|
|
|
|
|
|
|
|
// Insert entries to list
|
2014-08-21 23:26:28 +03:00
|
|
|
for(ui32 i = 0; i < totalFiles; ++i)
|
2013-07-28 17:49:50 +03:00
|
|
|
{
|
|
|
|
char filename[16];
|
|
|
|
reader.read(reinterpret_cast<ui8*>(filename), 16);
|
|
|
|
|
|
|
|
// Create archive entry
|
|
|
|
ArchiveEntry entry;
|
|
|
|
entry.name = filename;
|
|
|
|
entry.offset = reader.readUInt32();
|
|
|
|
entry.fullSize = reader.readUInt32();
|
|
|
|
fileStream.skip(4); // unused, unknown
|
|
|
|
entry.compressedSize = reader.readUInt32();
|
|
|
|
|
|
|
|
// Add lod entry to local entries map
|
2023-08-23 14:07:50 +02:00
|
|
|
entries[ResourcePath(mountPoint + entry.name)] = entry;
|
2022-11-08 23:39:56 +02:00
|
|
|
|
|
|
|
if(extractArchives)
|
|
|
|
{
|
|
|
|
si64 currentPosition = fileStream.tell(); // save filestream position
|
|
|
|
|
|
|
|
std::string fName = filename;
|
|
|
|
boost::to_upper(fName);
|
|
|
|
|
|
|
|
if(fName.find(".PCX") != std::string::npos)
|
2022-11-22 14:27:27 +02:00
|
|
|
extractToFolder("IMAGES", mountPoint, entry);
|
2022-11-08 23:39:56 +02:00
|
|
|
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))
|
2022-11-22 14:27:27 +02:00
|
|
|
extractToFolder("SPRITES", mountPoint, entry);
|
2023-01-17 01:08:23 +02:00
|
|
|
else if ((fName.find(".H3C") != std::string::npos))
|
2022-11-22 14:27:27 +02:00
|
|
|
extractToFolder("SPRITES", mountPoint, entry);
|
2022-11-08 23:39:56 +02:00
|
|
|
else
|
2022-11-22 14:27:27 +02:00
|
|
|
extractToFolder("MISC", mountPoint, entry);
|
2022-11-08 23:39:56 +02:00
|
|
|
|
|
|
|
fileStream.seek(currentPosition); // restore filestream position
|
|
|
|
}
|
2013-07-28 17:49:50 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CArchiveLoader::initVIDArchive(const std::string &mountPoint, CFileInputStream & fileStream)
|
|
|
|
{
|
|
|
|
|
|
|
|
// Read count of total files
|
|
|
|
CBinaryReader reader(&fileStream);
|
|
|
|
fileStream.seek(0);
|
|
|
|
ui32 totalFiles = reader.readUInt32();
|
|
|
|
|
|
|
|
std::set<int> offsets;
|
|
|
|
|
|
|
|
// Insert entries to list
|
2014-08-21 23:26:28 +03:00
|
|
|
for(ui32 i = 0; i < totalFiles; ++i)
|
2013-07-28 17:49:50 +03:00
|
|
|
{
|
|
|
|
char filename[40];
|
|
|
|
reader.read(reinterpret_cast<ui8*>(filename), 40);
|
|
|
|
|
|
|
|
ArchiveEntry entry;
|
|
|
|
entry.name = filename;
|
|
|
|
entry.offset = reader.readInt32();
|
|
|
|
entry.compressedSize = 0;
|
|
|
|
|
|
|
|
offsets.insert(entry.offset);
|
2023-08-23 14:07:50 +02:00
|
|
|
entries[ResourcePath(mountPoint + entry.name)] = entry;
|
2013-07-28 17:49:50 +03:00
|
|
|
}
|
2023-02-15 00:09:07 +02:00
|
|
|
offsets.insert(static_cast<int>(fileStream.getSize()));
|
2013-07-28 17:49:50 +03:00
|
|
|
|
2014-03-23 15:59:03 +03:00
|
|
|
// now when we know position of all files their sizes can be set correctly
|
2013-07-28 17:49:50 +03:00
|
|
|
for (auto & entry : entries)
|
|
|
|
{
|
|
|
|
auto it = offsets.find(entry.second.offset);
|
|
|
|
it++;
|
|
|
|
entry.second.fullSize = *it - entry.second.offset;
|
2022-11-08 23:39:56 +02:00
|
|
|
|
|
|
|
if(extractArchives)
|
2022-11-22 14:27:27 +02:00
|
|
|
extractToFolder("VIDEO", fileStream, entry.second);
|
2013-07-28 17:49:50 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CArchiveLoader::initSNDArchive(const std::string &mountPoint, CFileInputStream & fileStream)
|
|
|
|
{
|
|
|
|
// Read count of total files
|
|
|
|
CBinaryReader reader(&fileStream);
|
|
|
|
fileStream.seek(0);
|
|
|
|
ui32 totalFiles = reader.readUInt32();
|
|
|
|
|
|
|
|
// Insert entries to list
|
2014-08-21 23:26:28 +03:00
|
|
|
for(ui32 i = 0; i < totalFiles; ++i)
|
2013-07-28 17:49:50 +03:00
|
|
|
{
|
|
|
|
char filename[40];
|
|
|
|
reader.read(reinterpret_cast<ui8*>(filename), 40);
|
|
|
|
|
2022-12-27 16:51:19 +02:00
|
|
|
// for some reason entries in snd have format NAME\0WAVRUBBISH....
|
|
|
|
// and Polish version does not have extension at all
|
|
|
|
// we need to replace first \0 with dot and add wav extension manuall - we don't expect other types here anyway
|
2013-07-28 17:49:50 +03:00
|
|
|
ArchiveEntry entry;
|
|
|
|
entry.name = filename; // till 1st \0
|
2022-12-27 16:51:19 +02:00
|
|
|
entry.name += ".wav";
|
2013-07-28 17:49:50 +03:00
|
|
|
|
|
|
|
entry.offset = reader.readInt32();
|
|
|
|
entry.fullSize = reader.readInt32();
|
|
|
|
entry.compressedSize = 0;
|
2023-08-23 14:07:50 +02:00
|
|
|
entries[ResourcePath(mountPoint + entry.name)] = entry;
|
2022-11-08 23:39:56 +02:00
|
|
|
|
|
|
|
if(extractArchives)
|
2022-11-22 14:27:27 +02:00
|
|
|
extractToFolder("SOUND", fileStream, entry);
|
2013-07-28 17:49:50 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-23 14:07:50 +02:00
|
|
|
std::unique_ptr<CInputStream> CArchiveLoader::load(const ResourcePath & resourceName) const
|
2013-07-28 17:49:50 +03:00
|
|
|
{
|
|
|
|
assert(existsResource(resourceName));
|
|
|
|
|
|
|
|
const ArchiveEntry & entry = entries.at(resourceName);
|
|
|
|
|
|
|
|
if (entry.compressedSize != 0) //compressed data
|
|
|
|
{
|
2022-12-07 23:36:20 +02:00
|
|
|
auto fileStream = std::make_unique<CFileInputStream>(archive, entry.offset, entry.compressedSize);
|
2013-07-28 17:49:50 +03:00
|
|
|
|
2022-12-07 23:36:20 +02:00
|
|
|
return std::make_unique<CCompressedStream>(std::move(fileStream), false, entry.fullSize);
|
2013-07-28 17:49:50 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-12-07 23:36:20 +02:00
|
|
|
return std::make_unique<CFileInputStream>(archive, entry.offset, entry.fullSize);
|
2013-07-28 17:49:50 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-23 14:07:50 +02:00
|
|
|
bool CArchiveLoader::existsResource(const ResourcePath & resourceName) const
|
2013-07-28 17:49:50 +03:00
|
|
|
{
|
|
|
|
return entries.count(resourceName) != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string CArchiveLoader::getMountPoint() const
|
|
|
|
{
|
|
|
|
return mountPoint;
|
|
|
|
}
|
|
|
|
|
2024-09-01 12:23:10 +02:00
|
|
|
const std::unordered_map<ResourcePath, ArchiveEntry> & CArchiveLoader::getEntries() const
|
2024-08-31 00:44:20 +02:00
|
|
|
{
|
|
|
|
return entries;
|
|
|
|
}
|
|
|
|
|
2023-08-23 14:07:50 +02:00
|
|
|
std::unordered_set<ResourcePath> CArchiveLoader::getFilteredFiles(std::function<bool(const ResourcePath &)> filter) const
|
2013-07-28 17:49:50 +03:00
|
|
|
{
|
2023-08-23 14:07:50 +02:00
|
|
|
std::unordered_set<ResourcePath> foundID;
|
2013-07-28 17:49:50 +03:00
|
|
|
|
2023-02-15 00:09:07 +02:00
|
|
|
for(const auto & file : entries)
|
2013-07-28 17:49:50 +03:00
|
|
|
{
|
|
|
|
if (filter(file.first))
|
|
|
|
foundID.insert(file.first);
|
|
|
|
}
|
|
|
|
return foundID;
|
2013-11-10 16:43:55 +03:00
|
|
|
}
|
2022-07-26 15:07:42 +02:00
|
|
|
|
2024-08-31 00:44:20 +02:00
|
|
|
void CArchiveLoader::extractToFolder(const std::string & outputSubFolder, CInputStream & fileStream, const ArchiveEntry & entry, bool absolute) const
|
2022-11-08 23:39:56 +02:00
|
|
|
{
|
|
|
|
si64 currentPosition = fileStream.tell(); // save filestream position
|
|
|
|
|
2022-11-22 19:26:07 +02:00
|
|
|
std::vector<ui8> data(entry.fullSize);
|
2022-11-08 23:39:56 +02:00
|
|
|
fileStream.seek(entry.offset);
|
2022-11-22 19:26:07 +02:00
|
|
|
fileStream.read(data.data(), entry.fullSize);
|
2022-11-08 23:39:56 +02:00
|
|
|
|
2024-09-02 22:51:30 +02:00
|
|
|
boost::filesystem::path extractedFilePath = createExtractedFilePath(outputSubFolder, entry.name, absolute);
|
2022-11-08 23:39:56 +02:00
|
|
|
|
|
|
|
// writeToOutputFile
|
|
|
|
std::ofstream out(extractedFilePath.string(), std::ofstream::binary);
|
|
|
|
out.exceptions(std::ifstream::failbit | std::ifstream::badbit);
|
2023-02-15 00:09:07 +02:00
|
|
|
out.write(reinterpret_cast<char *>(data.data()), entry.fullSize);
|
2022-11-08 23:39:56 +02:00
|
|
|
|
|
|
|
fileStream.seek(currentPosition); // restore filestream position
|
|
|
|
}
|
|
|
|
|
2024-08-31 00:44:20 +02:00
|
|
|
void CArchiveLoader::extractToFolder(const std::string & outputSubFolder, const std::string & mountPoint, ArchiveEntry entry, bool absolute) const
|
2022-11-08 23:39:56 +02:00
|
|
|
{
|
2023-08-23 14:07:50 +02:00
|
|
|
std::unique_ptr<CInputStream> inputStream = load(ResourcePath(mountPoint + entry.name));
|
2022-11-08 23:39:56 +02:00
|
|
|
|
2022-12-06 22:33:58 +02:00
|
|
|
entry.offset = 0;
|
2024-08-31 00:44:20 +02:00
|
|
|
extractToFolder(outputSubFolder, *inputStream, entry, absolute);
|
2022-11-08 23:39:56 +02:00
|
|
|
}
|
|
|
|
|
2024-08-31 00:44:20 +02:00
|
|
|
boost::filesystem::path createExtractedFilePath(const std::string & outputSubFolder, const std::string & entryName, bool absolute)
|
2022-11-08 23:39:56 +02:00
|
|
|
{
|
2024-08-31 00:44:20 +02:00
|
|
|
boost::filesystem::path extractionFolderPath = absolute ? outputSubFolder : VCMIDirs::get().userExtractedPath() / outputSubFolder;
|
2023-07-28 14:51:14 +02:00
|
|
|
boost::filesystem::path extractedFilePath = extractionFolderPath / entryName;
|
2022-11-08 23:39:56 +02:00
|
|
|
|
2023-07-28 14:51:14 +02:00
|
|
|
boost::filesystem::create_directories(extractionFolderPath);
|
2022-11-08 23:39:56 +02:00
|
|
|
|
|
|
|
return extractedFilePath;
|
|
|
|
}
|
|
|
|
|
2022-07-26 15:07:42 +02:00
|
|
|
VCMI_LIB_NAMESPACE_END
|