Corregidos ~2570 issues automáticamente con clang-tidy --fix-errors más ajustes manuales posteriores: - modernize: designated-initializers, trailing-return-type, use-auto, avoid-c-arrays (→ std::array<>), use-ranges, use-emplace, deprecated-headers, use-equals-default, pass-by-value, return-braced-init-list, use-default-member-init - readability: math-missing-parentheses, implicit-bool-conversion, braces-around-statements, isolate-declaration, use-std-min-max, identifier-naming, else-after-return, redundant-casting, convert-member-functions-to-static, make-member-function-const, static-accessed-through-instance - performance: avoid-endl, unnecessary-value-param, type-promotion, inefficient-vector-operation - dead code: XOR_KEY (orphan tras eliminar encryptData/decryptData), dead stores en engine.cpp y png_shape.cpp - NOLINT justificado en 10 funciones con alta complejidad cognitiva (initialize, render, main, processEvents, update×3, performDemoAction, randomizeOnDemoStart, renderDebugHUD, AppLogo::update) Compilación: gcc -Wall sin warnings. clang-tidy: 0 issues. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
261 lines
8.6 KiB
C++
261 lines
8.6 KiB
C++
#include "resource_pack.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <iostream>
|
|
|
|
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<unsigned char> buffer(file_size);
|
|
file.read(reinterpret_cast<char*>(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<uint32_t>(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<uint32_t>(resources_.size());
|
|
pack_file.write(reinterpret_cast<const char*>(&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<uint32_t>(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<uint32_t>(path.size());
|
|
pack_file.write(reinterpret_cast<const char*>(&path_len), sizeof(uint32_t));
|
|
pack_file.write(path.c_str(), path_len);
|
|
pack_file.write(reinterpret_cast<const char*>(&entry.offset), sizeof(uint32_t));
|
|
pack_file.write(reinterpret_cast<const char*>(&entry.size), sizeof(uint32_t));
|
|
pack_file.write(reinterpret_cast<const char*>(&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<unsigned char> buffer(entry.size);
|
|
file.read(reinterpret_cast<char*>(buffer.data()), entry.size);
|
|
file.close();
|
|
|
|
pack_file.write(reinterpret_cast<const char*>(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<char*>(&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<char*>(&path_len), sizeof(uint32_t));
|
|
|
|
std::vector<char> path_buffer(path_len + 1, '\0');
|
|
packFile_.read(path_buffer.data(), path_len);
|
|
entry.path = std::string(path_buffer.data());
|
|
|
|
packFile_.read(reinterpret_cast<char*>(&entry.offset), sizeof(uint32_t));
|
|
packFile_.read(reinterpret_cast<char*>(&entry.size), sizeof(uint32_t));
|
|
packFile_.read(reinterpret_cast<char*>(&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<char*>(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::string> {
|
|
std::vector<std::string> 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<uint32_t>(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;
|
|
}
|