#include "resource_pack.hpp" #include #include #include #include namespace fs = std::filesystem; // Clave XOR para ofuscación simple (puede cambiarse) constexpr uint8_t XOR_KEY = 0x5A; ResourcePack::ResourcePack() : isLoaded_(false) {} ResourcePack::~ResourcePack() { clear(); } // ============================================================================ // EMPAQUETADO (herramienta pack_resources) // ============================================================================ bool ResourcePack::addDirectory(const std::string& dirPath, const std::string& prefix) { if (!fs::exists(dirPath) || !fs::is_directory(dirPath)) { std::cerr << "Error: Directorio no existe: " << dirPath << std::endl; return false; } for (const auto& entry : fs::recursive_directory_iterator(dirPath)) { if (entry.is_regular_file()) { // Construir ruta relativa desde data/ (ej: "data/ball.png" → "ball.png") std::string relativePath = fs::relative(entry.path(), dirPath).string(); std::string fullPath = prefix.empty() ? relativePath : prefix + "/" + relativePath; fullPath = normalizePath(fullPath); // Leer archivo completo std::ifstream file(entry.path(), std::ios::binary); if (!file) { std::cerr << "Error: No se pudo abrir: " << entry.path() << std::endl; continue; } file.seekg(0, std::ios::end); size_t fileSize = file.tellg(); file.seekg(0, std::ios::beg); std::vector buffer(fileSize); file.read(reinterpret_cast(buffer.data()), fileSize); file.close(); // Crear entrada de recurso ResourceEntry resource; resource.path = fullPath; resource.offset = 0; // Se calculará al guardar resource.size = static_cast(fileSize); resource.checksum = calculateChecksum(buffer.data(), fileSize); resources_[fullPath] = resource; std::cout << " Añadido: " << fullPath << " (" << fileSize << " bytes)" << std::endl; } } return !resources_.empty(); } bool ResourcePack::savePack(const std::string& packFilePath) { std::ofstream packFile(packFilePath, std::ios::binary); if (!packFile) { std::cerr << "Error: No se pudo crear pack: " << packFilePath << std::endl; return false; } // 1. Escribir header PackHeader header; std::memcpy(header.magic, "VBE3", 4); header.version = 1; header.fileCount = static_cast(resources_.size()); packFile.write(reinterpret_cast(&header), sizeof(PackHeader)); // 2. Calcular offsets (después del header + índice) uint32_t currentOffset = sizeof(PackHeader); // Calcular tamaño del índice (cada entrada: uint32_t pathLen + path + 3*uint32_t) for (const auto& [path, entry] : resources_) { currentOffset += sizeof(uint32_t); // pathLen currentOffset += static_cast(path.size()); // path currentOffset += sizeof(uint32_t) * 3; // offset, size, checksum } // 3. Escribir índice for (auto& [path, entry] : resources_) { entry.offset = currentOffset; uint32_t pathLen = static_cast(path.size()); packFile.write(reinterpret_cast(&pathLen), sizeof(uint32_t)); packFile.write(path.c_str(), pathLen); packFile.write(reinterpret_cast(&entry.offset), sizeof(uint32_t)); packFile.write(reinterpret_cast(&entry.size), sizeof(uint32_t)); packFile.write(reinterpret_cast(&entry.checksum), sizeof(uint32_t)); currentOffset += 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 originalPath = fs::current_path() / "data" / path; std::ifstream file(originalPath, std::ios::binary); if (!file) { std::cerr << "Error: No se pudo re-leer: " << originalPath << std::endl; continue; } std::vector buffer(entry.size); file.read(reinterpret_cast(buffer.data()), entry.size); file.close(); packFile.write(reinterpret_cast(buffer.data()), entry.size); } packFile.close(); return true; } // ============================================================================ // DESEMPAQUETADO (juego) // ============================================================================ bool ResourcePack::loadPack(const std::string& packFilePath) { clear(); packFile_.open(packFilePath, 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)" << std::endl; packFile_.close(); return false; } if (header.version != 1) { std::cerr << "Error: Versión de pack no soportada: " << header.version << std::endl; packFile_.close(); return false; } // 2. Leer índice for (uint32_t i = 0; i < header.fileCount; i++) { ResourceEntry entry; uint32_t pathLen; packFile_.read(reinterpret_cast(&pathLen), sizeof(uint32_t)); std::vector pathBuffer(pathLen + 1, '\0'); packFile_.read(pathBuffer.data(), pathLen); entry.path = std::string(pathBuffer.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; } ResourcePack::ResourceData ResourcePack::loadResource(const std::string& resourcePath) { ResourceData result = {nullptr, 0}; if (!isLoaded_) { return result; } std::string normalizedPath = normalizePath(resourcePath); auto it = resources_.find(normalizedPath); 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: " << resourcePath << std::endl; } return result; } // ============================================================================ // UTILIDADES // ============================================================================ std::vector ResourcePack::getResourceList() const { std::vector list; for (const auto& [path, entry] : resources_) { list.push_back(path); } return list; } size_t ResourcePack::getResourceCount() const { return resources_.size(); } void ResourcePack::clear() { resources_.clear(); if (packFile_.is_open()) { packFile_.close(); } isLoaded_ = false; } // ============================================================================ // FUNCIONES AUXILIARES // ============================================================================ void ResourcePack::encryptData(unsigned char* data, size_t size) { for (size_t i = 0; i < size; i++) { data[i] ^= XOR_KEY; } } void ResourcePack::decryptData(unsigned char* data, size_t size) { // XOR es simétrico encryptData(data, size); } uint32_t ResourcePack::calculateChecksum(const unsigned char* data, size_t size) { 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; } std::string ResourcePack::normalizePath(const std::string& path) { std::string normalized = path; // Reemplazar \ por / std::replace(normalized.begin(), normalized.end(), '\\', '/'); // 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; }