Запаковка файлов , присвоение метаданных и чтение метаданных из файла при помощи zlib
Я не понимаю как мне корректно запаковать файлы , присваивая метаданные запакованным файлам. Код который запаковывает файлы и присваивает метаданные:
#include <zlib.h>
#include <fstream>
#include <vector>
#include <iostream>
#include <unordered_map>
// Структура для хранения метаданных ресурса
struct ResourceMetaData {
std::string id;
size_t originalSize;
size_t compressedSize;
size_t offset;
std::string type;
};
// Функция для сжатия данных
std::vector<uint8_t> compressData(const std::vector<uint8_t>& data) {
uLongf compressedSize = compressBound(data.size());
std::vector<uint8_t> compressedData(compressedSize);
if (compress(compressedData.data(), &compressedSize, data.data(), data.size()) != Z_OK) {
throw std::runtime_error("Compression failed");
}
compressedData.resize(compressedSize);
return compressedData;
}
// Функция для упаковки ресурсов в один файл
void packResources(const std::unordered_map<std::string, std::pair<std::string, std::string>>& resources, const std::string& outputPath) {
std::ofstream outFile(outputPath, std::ios::binary);
if (!outFile) {
throw std::runtime_error("Failed to open output file");
}
// Записываем количество ресурсов
uint32_t resourceCount = resources.size();
outFile.write(reinterpret_cast<const char*>(&resourceCount), sizeof(resourceCount));
// Сохраняем место для метаданных
std::streampos metaDataOffset = outFile.tellp();
outFile.seekp(resourceCount * sizeof(ResourceMetaData), std::ios::cur);
std::vector<ResourceMetaData> metaDataList;
size_t currentOffset = outFile.tellp();
for (const auto& resource : resources) {
const std::string& id = resource.first;
const std::string& type = resource.second.first;
const std::string& path = resource.second.second;
std::ifstream inFile(path, std::ios::binary | std::ios::ate);
if (!inFile) {
throw std::runtime_error("Failed to open resource file: " + path);
}
std::streamsize size = inFile.tellg();
inFile.seekg(0, std::ios::beg);
std::vector<uint8_t> buffer(size);
if (!inFile.read(reinterpret_cast<char*>(buffer.data()), size)) {
throw std::runtime_error("Failed to read resource file: " + path);
}
std::vector<uint8_t> compressedData = compressData(buffer);
ResourceMetaData metaData;
metaData.id = id;
metaData.originalSize = buffer.size();
metaData.compressedSize = compressedData.size();
metaData.offset = currentOffset;
metaData.type = type;
metaDataList.push_back(metaData);
outFile.write(reinterpret_cast<const char*>(compressedData.data()), compressedData.size());
currentOffset += compressedData.size();
}
// Записываем метаданные
outFile.seekp(metaDataOffset);
for (const auto& metaData : metaDataList) {
outFile.write(reinterpret_cast<const char*>(&metaData), sizeof(metaData));
}
}
int main() {
try {
// Указываем ресурсы для упаковки
std::unordered_map<std::string, std::pair<std::string, std::string>> resources = {
{"fontSansation", {"font", "Fonts/Sansation.ttf"}},
{"textureButton", {"texture", "Textures/Buttons.png"}},
{"textureEntities", {"texture", "Textures/Entities.png"}},
{"textureExplosion", {"texture", "Textures/Explosion.png"}},
{"textureFinishLine", {"texture", "Textures/FinishLine.png"}},
{"textureJungle", {"texture", "Textures/Jungle.png"}},
{"textureParticle", {"texture", "Textures/Particle.png"}},
{"textureTitleScreen", {"texture", "Textures/TitleScreen.png"}},
{"menuTheme", {"music", "Music/MenuTheme.ogg"}},
{"missionTheme", {"music", "Music/MissionTheme.ogg"}},
{"shaderAdd", {"shader", "Shaders/Add.frag"}},
{"shaderBrightness", {"shader", "Shaders/Brightness.frag"}},
{"shaderDownSample", {"shader", "Shaders/DownSample.frag"}},
{"shaderFullpass", {"shader", "Shaders/Fullpass.vert"}},
{"shaderGuassianBlur", {"shader", "Shaders/GuassianBlur.frag"}},
{"soundAlliedGunfire", {"sound", "Sound/AlliedGunfire.wav"}},
{"soundButton", {"sound", "Sound/Button.wav"}},
{"soundCollectPickup", {"sound", "Sound/CollectPickup.wav"}},
{"soundEnemyGunfire", {"sound", "Sound/EnemyGunfire.wav"}},
{"soundExplosion1", {"sound", "Sound/Explosion1.wav"}},
{"soundExplosion2", {"sound", "Sound/Explosion2.wav"}},
{"soundLaunchMissile", {"sound", "Sound/LaunchMissile.wav"}}
};
packResources(resources, "data.bin");
std::cout << "Resources packed successfully." << std::endl;
}
catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
}
system("pause");
return 0;
}
Код который читает файл и добавляет считанные метаданные в массив:
bool ResourceHolder<Resource, Identifier>::loadPackedResources(const std::string& filePath) {
std::ifstream inFile(filePath, std::ios::binary);
if (!inFile) {
throw std::runtime_error("ResourceHolder::load - Failed to load " + filePath);
}
uint32_t resourceCount;
inFile.read(reinterpret_cast<char*>(&resourceCount), sizeof(resourceCount));
uint32_t compressedMetaDataSize;
inFile.read(reinterpret_cast<char*>(&compressedMetaDataSize), sizeof(compressedMetaDataSize));
std::vector<char> compressedMetaData(compressedMetaDataSize);
inFile.read(compressedMetaData.data(), compressedMetaDataSize);
uLongf uncompressedMetaDataSize = resourceCount * sizeof(ResourceMetaData);
std::vector<char> uncompressedMetaData(uncompressedMetaDataSize);
int result = uncompress(reinterpret_cast<Bytef*>(uncompressedMetaData.data()), &uncompressedMetaDataSize,
reinterpret_cast<const Bytef*>(compressedMetaData.data()), compressedMetaDataSize);
if (result != Z_OK) {
throw std::runtime_error("ResourceHolder::load - Failed to decompress metadata");
}
for (uint32_t i = 0; i < resourceCount; ++i) {
ResourceMetaData metaData;
std::memcpy(&metaData, uncompressedMetaData.data() + i * sizeof(ResourceMetaData), sizeof(ResourceMetaData));
resourcesMeta[metaData.id] = metaData;
std::cout << "Loaded resource: ID = " << metaData.id << ", Type = " << metaData.type << std::endl;
}
this->inFile.swap(inFile);
return true;
}
Вот что выводит консоль:
EXCEPTION: ResourceHolder::load - Failed to decompress metadata
Уже 3 часа долблюсь об эту проблему, не могу понять как ее решить
Ответы (2 шт):
У вас проблема возникает из за неверной записи и чтения метаданных (несоответствик размеров данных, записанных в файл, и тех, которые пытаетесь прочитать и разархивировать). Вот ResourceMetaData
вы напрямую в файл запихиваете, а так делать нельзя (выравнивание данных в оператосе и прочее (вот здесь вопрос обсуждался). А в loadPackedResources
при чтении данных вы их обрабатываете, считая, что это один сжатый блок, который потом разархивируете. Короче говоря, стандартизируемся и используем сериализацию.
На примере бинарной сериализации (upd):
#include <iostream>
#include <fstream>
#include <cstring>
struct ResourceMetaData {
std::string id;
size_t originalSize;
size_t compressedSize;
size_t offset;
std::string type;
};
// Бинарная сериализация
void serializeBinary(const ResourceMetaData& metaData, const std::string& filename) {
std::ofstream outFile(filename, std::ios::binary);
if (!outFile) {
std::cerr << "Failed to open output file: " << filename << std::endl;
return;
}
// Записываем данные в бинарный формат
size_t idLength = metaData.id.size() + 1; // +1 для нуль-терминатора
outFile.write(reinterpret_cast<const char*>(&idLength), sizeof(idLength));
outFile.write(metaData.id.c_str(), idLength);
outFile.write(reinterpret_cast<const char*>(&metaData.originalSize), sizeof(metaData.originalSize));
outFile.write(reinterpret_cast<const char*>(&metaData.compressedSize), sizeof(metaData.compressedSize));
outFile.write(reinterpret_cast<const char*>(&metaData.offset), sizeof(metaData.offset));
size_t typeLength = metaData.type.size() + 1; // +1 для нуль-терминатора
outFile.write(reinterpret_cast<const char*>(&typeLength), sizeof(typeLength));
outFile.write(metaData.type.c_str(), typeLength);
outFile.close();
}
// Десериализация из бинарного формата
ResourceMetaData deserializeBinary(const std::string& filename) {
ResourceMetaData metaData;
std::ifstream inFile(filename, std::ios::binary);
if (!inFile) {
std::cerr << "Failed to open input file: " << filename << std::endl;
return metaData;
}
// Читаем данные из бинарного файла
size_t idLength;
inFile.read(reinterpret_cast<char*>(&idLength), sizeof(idLength));
char* idBuffer = new char[idLength];
inFile.read(idBuffer, idLength);
metaData.id = std::string(idBuffer);
delete[] idBuffer;
inFile.read(reinterpret_cast<char*>(&metaData.originalSize), sizeof(metaData.originalSize));
inFile.read(reinterpret_cast<char*>(&metaData.compressedSize), sizeof(metaData.compressedSize));
inFile.read(reinterpret_cast<char*>(&metaData.offset), sizeof(metaData.offset));
size_t typeLength;
inFile.read(reinterpret_cast<char*>(&typeLength), sizeof(typeLength));
char* typeBuffer = new char[typeLength];
inFile.read(typeBuffer, typeLength);
metaData.type = std::string(typeBuffer);
delete[] typeBuffer;
inFile.close();
return metaData;
}
int main() {
ResourceMetaData metaData;
metaData.id = "textureButton";
metaData.originalSize = 1024;
metaData.compressedSize = 512;
metaData.offset = 0;
metaData.type = "texture";
// Сохраняем в бинарный файл
serializeBinary(metaData, "metaData.bin");
// Восстанавливаем из бинарного файла
ResourceMetaData restoredMetaData = deserializeBinary("metaData.bin");
// Выводим восстановленные данные
std::cout << "Restored Metadata:\n"
<< "ID: " << restoredMetaData.id << "\n"
<< "Original Size: " << restoredMetaData.originalSize << "\n"
<< "Compressed Size: " << restoredMetaData.compressedSize << "\n"
<< "Offset: " << restoredMetaData.offset << "\n"
<< "Type: " << restoredMetaData.type << std::endl;
return 0;
}
Можете код вот так отредачить:
Упаковка
#include <zlib.h>
#include <fstream>
#include <vector>
#include <iostream>
#include <unordered_map>
#include <cstring>
struct ResourceMetaData {
char id[64];
size_t originalSize;
size_t compressedSize;
size_t offset;
char type[32];
};
// Функция для сжатия данных
std::vector<uint8_t> compressData(const std::vector<uint8_t>& data) {
uLongf compressedSize = compressBound(data.size());
std::vector<uint8_t> compressedData(compressedSize);
if (compress(compressedData.data(), &compressedSize, data.data(), data.size()) != Z_OK) {
throw std::runtime_error("Compression failed");
}
compressedData.resize(compressedSize);
return compressedData;
}
// Функция для упаковки ресурсов в один файл
void packResources(const std::unordered_map<std::string, std::pair<std::string, std::string>>& resources, const std::string& outputPath) {
std::ofstream outFile(outputPath, std::ios::binary);
if (!outFile) {
throw std::runtime_error("Failed to open output file");
}
// Записываем количество ресурсов
uint32_t resourceCount = resources.size();
outFile.write(reinterpret_cast<const char*>(&resourceCount), sizeof(resourceCount));
std::vector<ResourceMetaData> metaDataList;
size_t currentOffset = sizeof(resourceCount) + sizeof(uint32_t); // Смещение для количества ресурсов и размера сжатых метаданных
for (const auto& resource : resources) {
const std::string& id = resource.first;
const std::string& type = resource.second.first;
const std::string& path = resource.second.second;
std::ifstream inFile(path, std::ios::binary | std::ios::ate);
if (!inFile) {
throw std::runtime_error("Failed to open resource file: " + path);
}
std::streamsize size = inFile.tellg();
inFile.seekg(0, std::ios::beg);
std::vector<uint8_t> buffer(size);
if (!inFile.read(reinterpret_cast<char*>(buffer.data()), size)) {
throw std::runtime_error("Failed to read resource file: " + path);
}
std::vector<uint8_t> compressedData = compressData(buffer);
ResourceMetaData metaData;
std::memset(metaData.id, 0, sizeof(metaData.id));
std::strncpy(metaData.id, id.c_str(), sizeof(metaData.id) - 1);
metaData.originalSize = buffer.size();
metaData.compressedSize = compressedData.size();
metaData.offset = currentOffset;
std::memset(metaData.type, 0, sizeof(metaData.type));
std::strncpy(metaData.type, type.c_str(), sizeof(metaData.type) - 1);
metaDataList.push_back(metaData);
outFile.write(reinterpret_cast<const char*>(compressedData.data()), compressedData.size());
currentOffset += compressedData.size();
}
// Сжимаем метаданные
std::vector<uint8_t> metaDataBuffer(metaDataList.size() * sizeof(ResourceMetaData));
std::memcpy(metaDataBuffer.data(), metaDataList.data(), metaDataBuffer.size());
std::vector<uint8_t> compressedMetaData = compressData(metaDataBuffer);
// Записываем размер сжатых метаданных
uint32_t compressedMetaDataSize = compressedMetaData.size();
outFile.write(reinterpret_cast<const char*>(&compressedMetaDataSize), sizeof(compressedMetaDataSize));
// Записываем сжатые метаданные
outFile.write(reinterpret_cast<const char*>(compressedMetaData.data()), compressedMetaData.size());
outFile.close();
}
int main() {
try {
// Указываем ресурсы для упаковки
std::unordered_map<std::string, std::pair<std::string, std::string>> resources = {
{"fontSansation", {"font", "Fonts/Sansation.ttf"}},
{"textureButton", {"texture", "Textures/Buttons.png"}},
{"textureEntities", {"texture", "Textures/Entities.png"}},
{"textureExplosion", {"texture", "Textures/Explosion.png"}},
{"textureFinishLine", {"texture", "Textures/FinishLine.png"}},
{"textureJungle", {"texture", "Textures/Jungle.png"}},
{"textureParticle", {"texture", "Textures/Particle.png"}},
{"textureTitleScreen", {"texture", "Textures/TitleScreen.png"}},
{"menuTheme", {"music", "Music/MenuTheme.ogg"}},
{"missionTheme", {"music", "Music/MissionTheme.ogg"}},
{"shaderAdd", {"shader", "Shaders/Add.frag"}},
{"shaderBrightness", {"shader", "Shaders/Brightness.frag"}},
{"shaderDownSample", {"shader", "Shaders/DownSample.frag"}},
{"shaderFullpass", {"shader", "Shaders/Fullpass.vert"}},
{"shaderGuassianBlur", {"shader", "Shaders/GuassianBlur.frag"}},
{"soundAlliedGunfire", {"sound", "Sound/AlliedGunfire.wav"}},
{"soundButton", {"sound", "Sound/Button.wav"}},
{"soundCollectPickup", {"sound", "Sound/CollectPickup.wav"}},
{"soundEnemyGunfire", {"sound", "Sound/EnemyGunfire.wav"}},
{"soundExplosion1", {"sound", "Sound/Explosion1.wav"}},
{"soundExplosion2", {"sound", "Sound/Explosion2.wav"}},
{"soundLaunchMissile", {"sound", "Sound/LaunchMissile.wav"}}
};
packResources(resources, "data.bin");
std::cout << "Resources packed successfully." << std::endl;
}
catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
}
return 0;
}
Чтение упакованных данных:
bool loadPackedResources(const std::string& filePath, std::unordered_map<std::string, ResourceMetaData>& resourcesMeta) {
std::ifstream inFile(filePath, std::ios::binary);
if (!inFile) {
throw std::runtime_error("ResourceHolder::load - Failed to load " + filePath);
}
uint32_t resourceCount;
inFile.read(reinterpret_cast<char*>(&resourceCount), sizeof(resourceCount));
uint32_t compressedMetaDataSize;
inFile.read(reinterpret_cast<char*>(&compressedMetaDataSize), sizeof(compressedMetaDataSize));
std::vector<char> compressedMetaData(compressedMetaDataSize);
inFile.read(compressedMetaData.data(), compressedMetaDataSize);
uLongf uncompressedMetaDataSize = resourceCount * sizeof(ResourceMetaData);
std::vector<char> uncompressedMetaData(uncompressedMetaDataSize);
int result = uncompress(reinterpret_cast<Bytef*>(uncompressedMetaData.data()), &uncompressedMetaDataSize,
reinterpret_cast<const Bytef*>(compressedMetaData.data()), compressedMetaDataSize);
if (result != Z_OK) {
throw std::runtime_error("ResourceHolder::load - Failed to decompress metadata");
}
for (uint32_t i = 0; i < resourceCount; ++i) {
ResourceMetaData metaData;
std::memcpy(&metaData, uncompressedMetaData.data() + i * sizeof(ResourceMetaData), sizeof(ResourceMetaData));
resourcesMeta[metaData.id] = metaData;
std::cout << "Loaded resource: ID = " << metaData.id << ", Type = " << metaData.type << std::endl;
}
return true;
}
По изменениям:
- Поменял структуру для хранения метаданных для хранения фиксированного размера строк (для простоты се/десериализации)
- Метаданные сжимаются перед записью
- Сжатые метаданные и их размер читаютяс и распаковываются перед использованием
Проблема была с тем, что вы предпологаете, что метаданные уже сжаты, и пытаетесь их записать. Чтобы пофиксить, надо их сжать во время записи, а потом распаковать при чтении.