#include "resource_pack.hpp" #include #include #include #include namespace fs = std::filesystem; ResourcePack::ResourcePack() : isLoaded_(false) {} ResourcePack::~ResourcePack() { clear(); } // ============================================================================ // EMPAQUETADO (herramienta pack_resources) // ============================================================================ auto ResourcePack::addDirectory(const std::string& dir_path, const std::string& prefix) -> bool { if (!fs::exists(dir_path) || !fs::is_directory(dir_path)) { std::cerr << "Error: Directorio no existe: " << dir_path << '\n'; return false; } for (const auto& entry : fs::recursive_directory_iterator(dir_path)) { if (entry.is_regular_file()) { // Construir ruta relativa desde data/ (ej: "data/ball.png" → "ball.png") std::string relative_path = fs::relative(entry.path(), dir_path).string(); std::string full_path = prefix.empty() ? relative_path : prefix + "/" + relative_path; full_path = normalizePath(full_path); // Leer archivo completo std::ifstream file(entry.path(), std::ios::binary); if (!file) { std::cerr << "Error: No se pudo abrir: " << entry.path() << '\n'; continue; } file.seekg(0, std::ios::end); size_t file_size = file.tellg(); file.seekg(0, std::ios::beg); std::vector buffer(file_size); file.read(reinterpret_cast(buffer.data()), file_size); file.close(); // Crear entrada de recurso ResourceEntry resource; resource.path = full_path; resource.offset = 0; // Se calculará al guardar resource.size = static_cast(file_size); resource.checksum = calculateChecksum(buffer.data(), file_size); resources_[full_path] = resource; std::cout << " Añadido: " << full_path << " (" << file_size << " bytes)" << '\n'; } } return !resources_.empty(); } auto ResourcePack::savePack(const std::string& pack_file_path) -> bool { std::ofstream pack_file(pack_file_path, std::ios::binary); if (!pack_file) { std::cerr << "Error: No se pudo crear pack: " << pack_file_path << '\n'; return false; } // 1. Escribir header PackHeader header; std::memcpy(header.magic, "VBE3", 4); header.version = 1; header.fileCount = static_cast(resources_.size()); pack_file.write(reinterpret_cast(&header), sizeof(PackHeader)); // 2. Calcular offsets (después del header + índice) uint32_t current_offset = sizeof(PackHeader); // Calcular tamaño del índice (cada entrada: uint32_t pathLen + path + 3*uint32_t) for (const auto& [path, entry] : resources_) { current_offset += sizeof(uint32_t); // pathLen current_offset += static_cast(path.size()); // path current_offset += sizeof(uint32_t) * 3; // offset, size, checksum } // 3. Escribir índice for (auto& [path, entry] : resources_) { entry.offset = current_offset; auto path_len = static_cast(path.size()); pack_file.write(reinterpret_cast(&path_len), sizeof(uint32_t)); pack_file.write(path.c_str(), path_len); pack_file.write(reinterpret_cast(&entry.offset), sizeof(uint32_t)); pack_file.write(reinterpret_cast(&entry.size), sizeof(uint32_t)); pack_file.write(reinterpret_cast(&entry.checksum), sizeof(uint32_t)); current_offset += entry.size; } // 4. Escribir datos de archivos (sin encriptar en pack, se encripta al cargar) for (const auto& [path, entry] : resources_) { // Encontrar archivo original en disco fs::path original_path = fs::current_path() / "data" / path; std::ifstream file(original_path, std::ios::binary); if (!file) { std::cerr << "Error: No se pudo re-leer: " << original_path << '\n'; continue; } std::vector buffer(entry.size); file.read(reinterpret_cast(buffer.data()), entry.size); file.close(); pack_file.write(reinterpret_cast(buffer.data()), entry.size); } pack_file.close(); return true; } // ============================================================================ // DESEMPAQUETADO (juego) // ============================================================================ auto ResourcePack::loadPack(const std::string& pack_file_path) -> bool { clear(); packFile_.open(pack_file_path, std::ios::binary); if (!packFile_) { return false; } // 1. Leer header PackHeader header; packFile_.read(reinterpret_cast(&header), sizeof(PackHeader)); if (std::memcmp(header.magic, "VBE3", 4) != 0) { std::cerr << "Error: Pack inválido (magic incorrecto)" << '\n'; packFile_.close(); return false; } if (header.version != 1) { std::cerr << "Error: Versión de pack no soportada: " << header.version << '\n'; packFile_.close(); return false; } // 2. Leer índice for (uint32_t i = 0; i < header.fileCount; i++) { ResourceEntry entry; uint32_t path_len; packFile_.read(reinterpret_cast(&path_len), sizeof(uint32_t)); std::vector path_buffer(path_len + 1, '\0'); packFile_.read(path_buffer.data(), path_len); entry.path = std::string(path_buffer.data()); packFile_.read(reinterpret_cast(&entry.offset), sizeof(uint32_t)); packFile_.read(reinterpret_cast(&entry.size), sizeof(uint32_t)); packFile_.read(reinterpret_cast(&entry.checksum), sizeof(uint32_t)); resources_[entry.path] = entry; } isLoaded_ = true; return true; } auto ResourcePack::loadResource(const std::string& resource_path) -> ResourcePack::ResourceData { ResourceData result = {.data = nullptr, .size = 0}; if (!isLoaded_) { return result; } std::string normalized_path = normalizePath(resource_path); auto it = resources_.find(normalized_path); if (it == resources_.end()) { return result; } const ResourceEntry& entry = it->second; // Leer datos desde el pack packFile_.seekg(entry.offset); result.data = new unsigned char[entry.size]; result.size = entry.size; packFile_.read(reinterpret_cast(result.data), entry.size); // Verificar checksum uint32_t checksum = calculateChecksum(result.data, entry.size); if (checksum != entry.checksum) { std::cerr << "Warning: Checksum incorrecto para: " << resource_path << '\n'; } return result; } // ============================================================================ // UTILIDADES // ============================================================================ auto ResourcePack::getResourceList() const -> std::vector { std::vector list; list.reserve(resources_.size()); for (const auto& [path, entry] : resources_) { list.push_back(path); } return list; } auto ResourcePack::getResourceCount() const -> size_t { return resources_.size(); } void ResourcePack::clear() { resources_.clear(); if (packFile_.is_open()) { packFile_.close(); } isLoaded_ = false; } // ============================================================================ // FUNCIONES AUXILIARES // ============================================================================ auto ResourcePack::calculateChecksum(const unsigned char* data, size_t size) -> uint32_t { uint32_t checksum = 0; for (size_t i = 0; i < size; i++) { checksum ^= static_cast(data[i]); checksum = (checksum << 1) | (checksum >> 31); // Rotate left } return checksum; } auto ResourcePack::normalizePath(const std::string& path) -> std::string { std::string normalized = path; // Reemplazar \ por / std::ranges::replace(normalized, '\\', '/'); // Buscar "data/" en cualquier parte del path y extraer lo que viene después size_t data_pos = normalized.find("data/"); if (data_pos != std::string::npos) { normalized = normalized.substr(data_pos + 5); // +5 para saltar "data/" } // Eliminar ./ del inicio si quedó if (normalized.substr(0, 2) == "./") { normalized = normalized.substr(2); } return normalized; }