afegit resources.pack y prefixe a les rutes de recursos

This commit is contained in:
2025-12-08 21:48:52 +01:00
parent 4b7cbd88bb
commit a41e696b69
21 changed files with 1066 additions and 36 deletions

5
.gitignore vendored
View File

@@ -18,6 +18,11 @@ asteroids
*.out *.out
*.app *.app
# Releases
*.zip
*.tar.gz
*.dmg
# Compiled Object files # Compiled Object files
*.o *.o
*.obj *.obj

View File

@@ -73,6 +73,11 @@ target_compile_options(${PROJECT_NAME} PRIVATE $<$<CONFIG:RELEASE>:-O2 -ffunctio
target_compile_definitions(${PROJECT_NAME} PRIVATE $<$<CONFIG:DEBUG>:_DEBUG>) target_compile_definitions(${PROJECT_NAME} PRIVATE $<$<CONFIG:DEBUG>:_DEBUG>)
target_compile_definitions(${PROJECT_NAME} PRIVATE $<$<CONFIG:RELEASE>:RELEASE_BUILD>) target_compile_definitions(${PROJECT_NAME} PRIVATE $<$<CONFIG:RELEASE>:RELEASE_BUILD>)
# Definir MACOS_BUNDLE si es un bundle de macOS
if(APPLE AND MACOSX_BUNDLE)
target_compile_definitions(${PROJECT_NAME} PRIVATE MACOS_BUNDLE)
endif()
# Configuración específica para cada plataforma # Configuración específica para cada plataforma
if(WIN32) if(WIN32)
target_compile_definitions(${PROJECT_NAME} PRIVATE WINDOWS_BUILD) target_compile_definitions(${PROJECT_NAME} PRIVATE WINDOWS_BUILD)

View File

@@ -58,6 +58,20 @@ else
UNAME_S := $(shell uname -s) UNAME_S := $(shell uname -s)
endif endif
# ==============================================================================
# PACKING TOOL
# ==============================================================================
PACK_TOOL := tools/pack_resources/pack_resources
.PHONY: pack_tool resources.pack
pack_tool:
@$(MAKE) -C tools/pack_resources
resources.pack: pack_tool
@echo "Creating resources.pack..."
@./$(PACK_TOOL) data resources.pack
# ============================================================================== # ==============================================================================
# TARGETS # TARGETS
# ============================================================================== # ==============================================================================
@@ -87,7 +101,7 @@ debug:
# macOS Release (Apple Silicon) # macOS Release (Apple Silicon)
.PHONY: macos_release .PHONY: macos_release
macos_release: macos_release: pack_tool resources.pack
@echo "Creating macOS release - Version: $(VERSION)" @echo "Creating macOS release - Version: $(VERSION)"
# Check/install create-dmg # Check/install create-dmg
@@ -104,8 +118,8 @@ macos_release:
@$(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources" @$(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
@$(MKDIR) Frameworks @$(MKDIR) Frameworks
# Copy resources # Copy resources.pack to Resources
@cp -r resources "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources/" @cp resources.pack "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources/"
@ditto release/frameworks/SDL3.xcframework/macos-arm64_x86_64/SDL3.framework "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks/SDL3.framework" @ditto release/frameworks/SDL3.xcframework/macos-arm64_x86_64/SDL3.framework "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks/SDL3.framework"
@ditto release/frameworks/SDL3.xcframework/macos-arm64_x86_64/SDL3.framework Frameworks/SDL3.framework @ditto release/frameworks/SDL3.xcframework/macos-arm64_x86_64/SDL3.framework Frameworks/SDL3.framework
@@ -313,6 +327,8 @@ else
@$(RMFILE) $(TARGET_FILE) $(TARGET_FILE)_debug @$(RMFILE) $(TARGET_FILE) $(TARGET_FILE)_debug
@$(RMDIR) build $(RELEASE_FOLDER) @$(RMDIR) build $(RELEASE_FOLDER)
@$(RMFILE) *.dmg *.zip *.tar.gz 2>/dev/null || true @$(RMFILE) *.dmg *.zip *.tar.gz 2>/dev/null || true
@$(RMFILE) resources.pack 2>/dev/null || true
@$(MAKE) -C tools/pack_resources clean 2>/dev/null || true
endif endif
@echo "Clean complete" @echo "Clean complete"

BIN
resources.pack Normal file

Binary file not shown.

View File

@@ -3,6 +3,8 @@
#include "core/audio/audio_cache.hpp" #include "core/audio/audio_cache.hpp"
#include "core/resources/resource_helper.hpp"
#include <iostream> #include <iostream>
// Inicialització de variables estàtiques // Inicialització de variables estàtiques
@@ -19,17 +21,28 @@ JA_Sound_t* AudioCache::getSound(const std::string& name) {
return it->second; return it->second;
} }
// Cache miss - cargar archivo // Normalize path: "laser_shoot.wav" → "sounds/laser_shoot.wav"
std::string fullpath = resolveSoundPath(name); std::string normalized = name;
JA_Sound_t* sound = JA_LoadSound(fullpath.c_str()); if (normalized.find("sounds/") != 0 && normalized.find('/') == std::string::npos) {
normalized = "sounds/" + normalized;
}
// Load from resource system
std::vector<uint8_t> data = Resource::Helper::loadFile(normalized);
if (data.empty()) {
std::cerr << "[AudioCache] Error: no s'ha pogut carregar " << normalized << std::endl;
return nullptr;
}
// Load sound from memory
JA_Sound_t* sound = JA_LoadSound(data.data(), static_cast<uint32_t>(data.size()));
if (sound == nullptr) { if (sound == nullptr) {
std::cerr << "[AudioCache] Error: no s'ha pogut carregar " << fullpath std::cerr << "[AudioCache] Error: no s'ha pogut decodificar " << normalized
<< std::endl; << std::endl;
return nullptr; return nullptr;
} }
std::cout << "[AudioCache] Sound loaded: " << name << std::endl; std::cout << "[AudioCache] Sound loaded: " << normalized << std::endl;
sounds_[name] = sound; sounds_[name] = sound;
return sound; return sound;
} }
@@ -42,17 +55,28 @@ JA_Music_t* AudioCache::getMusic(const std::string& name) {
return it->second; return it->second;
} }
// Cache miss - cargar archivo // Normalize path: "title.ogg" → "music/title.ogg"
std::string fullpath = resolveMusicPath(name); std::string normalized = name;
JA_Music_t* music = JA_LoadMusic(fullpath.c_str()); if (normalized.find("music/") != 0 && normalized.find('/') == std::string::npos) {
normalized = "music/" + normalized;
}
// Load from resource system
std::vector<uint8_t> data = Resource::Helper::loadFile(normalized);
if (data.empty()) {
std::cerr << "[AudioCache] Error: no s'ha pogut carregar " << normalized << std::endl;
return nullptr;
}
// Load music from memory
JA_Music_t* music = JA_LoadMusic(data.data(), static_cast<uint32_t>(data.size()));
if (music == nullptr) { if (music == nullptr) {
std::cerr << "[AudioCache] Error: no s'ha pogut carregar " << fullpath std::cerr << "[AudioCache] Error: no s'ha pogut decodificar " << normalized
<< std::endl; << std::endl;
return nullptr; return nullptr;
} }
std::cout << "[AudioCache] Music loaded: " << name << std::endl; std::cout << "[AudioCache] Music loaded: " << normalized << std::endl;
musics_[name] = music; musics_[name] = music;
return music; return music;
} }

View File

@@ -32,6 +32,9 @@ class Shape {
// Carregar forma des de fitxer .shp // Carregar forma des de fitxer .shp
bool carregar(const std::string& filepath); bool carregar(const std::string& filepath);
// Parsejar forma des de buffer de memòria (per al sistema de recursos)
bool parsejar_fitxer(const std::string& contingut);
// Getters // Getters
const std::vector<ShapePrimitive>& get_primitives() const { const std::vector<ShapePrimitive>& get_primitives() const {
return primitives_; return primitives_;
@@ -50,9 +53,6 @@ class Shape {
float escala_defecte_; // Escala per defecte (normalment 1.0) float escala_defecte_; // Escala per defecte (normalment 1.0)
std::string nom_; // Nom de la forma (per depuració) std::string nom_; // Nom de la forma (per depuració)
// Parsejador del fitxer
bool parsejar_fitxer(const std::string& contingut);
// Helpers privats per parsejar // Helpers privats per parsejar
std::string trim(const std::string& str) const; std::string trim(const std::string& str) const;
bool starts_with(const std::string& str, const std::string& prefix) const; bool starts_with(const std::string& str, const std::string& prefix) const;

View File

@@ -3,6 +3,8 @@
#include "core/graphics/shape_loader.hpp" #include "core/graphics/shape_loader.hpp"
#include "core/resources/resource_helper.hpp"
#include <iostream> #include <iostream>
namespace Graphics { namespace Graphics {
@@ -19,28 +21,40 @@ std::shared_ptr<Shape> ShapeLoader::load(const std::string& filename) {
return it->second; // Cache hit return it->second; // Cache hit
} }
// Resolve full path // Normalize path: "ship.shp" → "shapes/ship.shp"
std::string fullpath = resolve_path(filename); // "logo/letra_j.shp" → "shapes/logo/letra_j.shp"
std::string normalized = filename;
if (normalized.find("shapes/") != 0) {
// Doesn't start with "shapes/", so add it
normalized = "shapes/" + normalized;
}
// Create and load shape // Load from resource system
std::vector<uint8_t> data = Resource::Helper::loadFile(normalized);
if (data.empty()) {
std::cerr << "[ShapeLoader] Error: no s'ha pogut carregar " << normalized
<< std::endl;
return nullptr;
}
// Convert bytes to string and parse
std::string file_content(data.begin(), data.end());
auto shape = std::make_shared<Shape>(); auto shape = std::make_shared<Shape>();
if (!shape->carregar(fullpath)) { if (!shape->parsejar_fitxer(file_content)) {
std::cerr << "[ShapeLoader] Error: no s'ha pogut carregar " << filename std::cerr << "[ShapeLoader] Error: no s'ha pogut parsejar " << normalized
<< std::endl; << std::endl;
return nullptr; return nullptr;
} }
// Verify shape is valid // Verify shape is valid
if (!shape->es_valida()) { if (!shape->es_valida()) {
std::cerr << "[ShapeLoader] Error: forma invàlida " << filename std::cerr << "[ShapeLoader] Error: forma invàlida " << normalized << std::endl;
<< std::endl;
return nullptr; return nullptr;
} }
// Cache and return // Cache and return
std::cout << "[ShapeLoader] Carregat: " << filename << " (" std::cout << "[ShapeLoader] Carregat: " << normalized << " (" << shape->get_nom()
<< shape->get_nom() << ", " << shape->get_num_primitives() << ", " << shape->get_num_primitives() << " primitives)" << std::endl;
<< " primitives)" << std::endl;
cache_[filename] = shape; cache_[filename] = shape;
return shape; return shape;

View File

@@ -8,6 +8,7 @@
#include <iostream> #include <iostream>
#include "core/defaults.hpp" #include "core/defaults.hpp"
#include "core/graphics/shape_loader.hpp"
#include "core/rendering/shape_renderer.hpp" #include "core/rendering/shape_renderer.hpp"
namespace Graphics { namespace Graphics {
@@ -21,11 +22,11 @@ Starfield::Starfield(SDL_Renderer* renderer,
punt_fuga_(punt_fuga), punt_fuga_(punt_fuga),
area_(area), area_(area),
densitat_(densitat) { densitat_(densitat) {
// Carregar forma d'estrella // Carregar forma d'estrella amb ShapeLoader
shape_estrella_ = std::make_shared<Shape>("data/shapes/star.shp"); shape_estrella_ = ShapeLoader::load("star.shp");
if (!shape_estrella_->es_valida()) { if (!shape_estrella_ || !shape_estrella_->es_valida()) {
std::cerr << "ERROR: No s'ha pogut carregar data/shapes/star.shp" << std::endl; std::cerr << "ERROR: No s'ha pogut carregar star.shp" << std::endl;
return; return;
} }

View File

@@ -0,0 +1,83 @@
// resource_helper.cpp - Implementació de funcions d'ajuda
// © 2025 Port a C++20 amb SDL3
#include "resource_helper.hpp"
#include "resource_loader.hpp"
#include <algorithm>
#include <iostream>
namespace Resource {
namespace Helper {
// Inicialitzar el sistema de recursos
bool initializeResourceSystem(const std::string& pack_file, bool fallback) {
return Loader::get().initialize(pack_file, fallback);
}
// Carregar un fitxer
std::vector<uint8_t> loadFile(const std::string& filepath) {
// Normalitzar la ruta
std::string normalized = normalizePath(filepath);
// Carregar del sistema de recursos
return Loader::get().loadResource(normalized);
}
// Comprovar si existeix un fitxer
bool fileExists(const std::string& filepath) {
std::string normalized = normalizePath(filepath);
return Loader::get().resourceExists(normalized);
}
// Obtenir ruta normalitzada per al paquet
// Elimina prefixos "data/", rutes absolutes, etc.
std::string getPackPath(const std::string& asset_path) {
std::string path = asset_path;
// Eliminar rutes absolutes (detectar / o C:\ al principi)
if (!path.empty() && path[0] == '/') {
// Buscar "data/" i agafar el que ve després
size_t data_pos = path.find("/data/");
if (data_pos != std::string::npos) {
path = path.substr(data_pos + 6); // Saltar "/data/"
}
}
// Eliminar "./" i "../" del principi
while (path.starts_with("./")) {
path = path.substr(2);
}
while (path.starts_with("../")) {
path = path.substr(3);
}
// Eliminar "data/" del principi
if (path.starts_with("data/")) {
path = path.substr(5);
}
// Eliminar "Resources/" (macOS bundles)
if (path.starts_with("Resources/")) {
path = path.substr(10);
}
// Convertir barres invertides a normals
std::ranges::replace(path, '\\', '/');
return path;
}
// Normalitzar ruta (alias de getPackPath)
std::string normalizePath(const std::string& path) {
return getPackPath(path);
}
// Comprovar si hi ha paquet carregat
bool isPackLoaded() {
return Loader::get().isPackLoaded();
}
} // namespace Helper
} // namespace Resource

View File

@@ -0,0 +1,28 @@
// resource_helper.hpp - Funcions d'ajuda per gestió de recursos
// © 2025 Port a C++20 amb SDL3
// API simplificada i normalització de rutes
#pragma once
#include <string>
#include <vector>
namespace Resource {
namespace Helper {
// Inicialització del sistema
bool initializeResourceSystem(const std::string& pack_file, bool fallback);
// Càrrega de fitxers
std::vector<uint8_t> loadFile(const std::string& filepath);
bool fileExists(const std::string& filepath);
// Normalització de rutes
std::string getPackPath(const std::string& asset_path);
std::string normalizePath(const std::string& path);
// Estat
bool isPackLoaded();
} // namespace Helper
} // namespace Resource

View File

@@ -0,0 +1,143 @@
// resource_loader.cpp - Implementació del carregador de recursos
// © 2025 Port a C++20 amb SDL3
#include "resource_loader.hpp"
#include <filesystem>
#include <fstream>
#include <iostream>
namespace Resource {
// Singleton
Loader& Loader::get() {
static Loader instance;
return instance;
}
// Inicialitzar el sistema de recursos
bool Loader::initialize(const std::string& pack_file, bool enable_fallback) {
fallback_enabled_ = enable_fallback;
// Intentar carregar el paquet
pack_ = std::make_unique<Pack>();
if (!pack_->loadPack(pack_file)) {
if (!fallback_enabled_) {
std::cerr << "[ResourceLoader] ERROR FATAL: No es pot carregar " << pack_file
<< " i el fallback està desactivat\n";
return false;
}
std::cout << "[ResourceLoader] Paquet no trobat, usant fallback al sistema de fitxers\n";
pack_.reset(); // No hi ha paquet
return true;
}
std::cout << "[ResourceLoader] Paquet carregat: " << pack_file << "\n";
return true;
}
// Carregar un recurs
std::vector<uint8_t> Loader::loadResource(const std::string& filename) {
// Intentar carregar del paquet primer
if (pack_) {
if (pack_->hasResource(filename)) {
auto data = pack_->getResource(filename);
if (!data.empty()) {
return data;
}
std::cerr << "[ResourceLoader] Advertència: recurs buit al paquet: " << filename
<< "\n";
}
// Si no està al paquet i no hi ha fallback, falla
if (!fallback_enabled_) {
std::cerr << "[ResourceLoader] ERROR: Recurs no trobat al paquet i fallback desactivat: "
<< filename << "\n";
return {};
}
}
// Fallback al sistema de fitxers
if (fallback_enabled_) {
return loadFromFilesystem(filename);
}
return {};
}
// Comprovar si existeix un recurs
bool Loader::resourceExists(const std::string& filename) {
// Comprovar al paquet
if (pack_ && pack_->hasResource(filename)) {
return true;
}
// Comprovar al sistema de fitxers si està activat el fallback
if (fallback_enabled_) {
std::string fullpath = base_path_.empty() ? "data/" + filename : base_path_ + "/data/" + filename;
return std::filesystem::exists(fullpath);
}
return false;
}
// Validar el paquet
bool Loader::validatePack() {
if (!pack_) {
std::cerr << "[ResourceLoader] Advertència: no hi ha paquet carregat per validar\n";
return false;
}
return pack_->validatePack();
}
// Comprovar si hi ha paquet carregat
bool Loader::isPackLoaded() const {
return pack_ != nullptr;
}
// Establir la ruta base
void Loader::setBasePath(const std::string& path) {
base_path_ = path;
std::cout << "[ResourceLoader] Ruta base establerta: " << base_path_ << "\n";
}
// Obtenir la ruta base
std::string Loader::getBasePath() const {
return base_path_;
}
// Carregar des del sistema de fitxers (fallback)
std::vector<uint8_t> Loader::loadFromFilesystem(const std::string& filename) {
// The filename is already normalized (e.g., "shapes/logo/letra_j.shp")
// We need to prepend base_path + "data/"
std::string fullpath;
if (base_path_.empty()) {
fullpath = "data/" + filename;
} else {
fullpath = base_path_ + "/data/" + filename;
}
std::ifstream file(fullpath, std::ios::binary | std::ios::ate);
if (!file) {
std::cerr << "[ResourceLoader] Error: no es pot obrir " << fullpath << "\n";
return {};
}
std::streamsize file_size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> data(file_size);
if (!file.read(reinterpret_cast<char*>(data.data()), file_size)) {
std::cerr << "[ResourceLoader] Error: no es pot llegir " << fullpath << "\n";
return {};
}
std::cout << "[ResourceLoader] Carregat des del sistema de fitxers: " << fullpath << "\n";
return data;
}
} // namespace Resource

View File

@@ -0,0 +1,53 @@
// resource_loader.hpp - Carregador de recursos (Singleton)
// © 2025 Port a C++20 amb SDL3
// Coordina càrrega des del paquet i/o sistema de fitxers
#pragma once
#include "resource_pack.hpp"
#include <memory>
#include <string>
#include <vector>
namespace Resource {
// Singleton per gestionar la càrrega de recursos
class Loader {
public:
// Singleton
static Loader& get();
// Inicialització
bool initialize(const std::string& pack_file, bool enable_fallback);
// Càrrega de recursos
std::vector<uint8_t> loadResource(const std::string& filename);
bool resourceExists(const std::string& filename);
// Validació
bool validatePack();
bool isPackLoaded() const;
// Estat
void setBasePath(const std::string& path);
std::string getBasePath() const;
private:
Loader() = default;
~Loader() = default;
// No es pot copiar ni moure
Loader(const Loader&) = delete;
Loader& operator=(const Loader&) = delete;
// Dades
std::unique_ptr<Pack> pack_;
bool fallback_enabled_ = false;
std::string base_path_;
// Funcions auxiliars
std::vector<uint8_t> loadFromFilesystem(const std::string& filename);
};
} // namespace Resource

View File

@@ -0,0 +1,309 @@
// resource_pack.cpp - Implementació del sistema d'empaquetament
// © 2025 Port a C++20 amb SDL3
#include "resource_pack.hpp"
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iostream>
namespace Resource {
// Calcular checksum CRC32 simplificat
uint32_t Pack::calculateChecksum(const std::vector<uint8_t>& data) const {
uint32_t checksum = 0x12345678;
for (unsigned char byte : data) {
checksum = ((checksum << 5) + checksum) + byte;
}
return checksum;
}
// Encriptació XOR (simètrica)
void Pack::encryptData(std::vector<uint8_t>& data, const std::string& key) {
if (key.empty()) {
return;
}
for (size_t i = 0; i < data.size(); ++i) {
data[i] ^= key[i % key.length()];
}
}
void Pack::decryptData(std::vector<uint8_t>& data, const std::string& key) {
// XOR és simètric
encryptData(data, key);
}
// Llegir fitxer complet a memòria
std::vector<uint8_t> Pack::readFile(const std::string& filepath) {
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file) {
std::cerr << "[ResourcePack] Error: no es pot obrir " << filepath << '\n';
return {};
}
std::streamsize file_size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> data(file_size);
if (!file.read(reinterpret_cast<char*>(data.data()), file_size)) {
std::cerr << "[ResourcePack] Error: no es pot llegir " << filepath << '\n';
return {};
}
return data;
}
// Afegir un fitxer individual al paquet
bool Pack::addFile(const std::string& filepath, const std::string& pack_name) {
auto file_data = readFile(filepath);
if (file_data.empty()) {
return false;
}
ResourceEntry entry{
.filename = pack_name,
.offset = data_.size(),
.size = file_data.size(),
.checksum = calculateChecksum(file_data)};
// Afegir dades al bloc de dades
data_.insert(data_.end(), file_data.begin(), file_data.end());
resources_[pack_name] = entry;
std::cout << "[ResourcePack] Afegit: " << pack_name << " (" << file_data.size()
<< " bytes)\n";
return true;
}
// Afegir tots els fitxers d'un directori recursivament
bool Pack::addDirectory(const std::string& dir_path,
const std::string& base_path) {
namespace fs = std::filesystem;
if (!fs::exists(dir_path) || !fs::is_directory(dir_path)) {
std::cerr << "[ResourcePack] Error: directori no trobat: " << dir_path << '\n';
return false;
}
std::string current_base = base_path.empty() ? "" : base_path + "/";
for (const auto& entry : fs::recursive_directory_iterator(dir_path)) {
if (!entry.is_regular_file()) {
continue;
}
std::string full_path = entry.path().string();
std::string relative_path = entry.path().lexically_relative(dir_path).string();
// Convertir barres invertides a normals (Windows)
std::ranges::replace(relative_path, '\\', '/');
// Saltar fitxers de desenvolupament
if (relative_path.find(".world") != std::string::npos ||
relative_path.find(".tsx") != std::string::npos ||
relative_path.find(".DS_Store") != std::string::npos ||
relative_path.find(".git") != std::string::npos) {
std::cout << "[ResourcePack] Saltant: " << relative_path << '\n';
continue;
}
std::string pack_name = current_base + relative_path;
addFile(full_path, pack_name);
}
return true;
}
// Guardar paquet a disc
bool Pack::savePack(const std::string& pack_file) {
std::ofstream file(pack_file, std::ios::binary);
if (!file) {
std::cerr << "[ResourcePack] Error: no es pot crear " << pack_file << '\n';
return false;
}
// Escriure capçalera
file.write(MAGIC_HEADER, 4);
file.write(reinterpret_cast<const char*>(&VERSION), sizeof(VERSION));
// Escriure nombre de recursos
auto resource_count = static_cast<uint32_t>(resources_.size());
file.write(reinterpret_cast<const char*>(&resource_count), sizeof(resource_count));
// Escriure metadades de recursos
for (const auto& [name, entry] : resources_) {
// Nom del fitxer
auto name_len = static_cast<uint32_t>(entry.filename.length());
file.write(reinterpret_cast<const char*>(&name_len), sizeof(name_len));
file.write(entry.filename.c_str(), name_len);
// Offset, mida, checksum
file.write(reinterpret_cast<const char*>(&entry.offset), sizeof(entry.offset));
file.write(reinterpret_cast<const char*>(&entry.size), sizeof(entry.size));
file.write(reinterpret_cast<const char*>(&entry.checksum), sizeof(entry.checksum));
}
// Encriptar dades
std::vector<uint8_t> encrypted_data = data_;
encryptData(encrypted_data, DEFAULT_ENCRYPT_KEY);
// Escriure mida de dades i dades encriptades
auto data_size = static_cast<uint64_t>(encrypted_data.size());
file.write(reinterpret_cast<const char*>(&data_size), sizeof(data_size));
file.write(reinterpret_cast<const char*>(encrypted_data.data()), encrypted_data.size());
std::cout << "[ResourcePack] Guardat: " << pack_file << " (" << resources_.size()
<< " recursos, " << data_size << " bytes)\n";
return true;
}
// Carregar paquet des de disc
bool Pack::loadPack(const std::string& pack_file) {
std::ifstream file(pack_file, std::ios::binary);
if (!file) {
std::cerr << "[ResourcePack] Error: no es pot obrir " << pack_file << '\n';
return false;
}
// Llegir capçalera
char magic[4];
file.read(magic, 4);
if (std::string(magic, 4) != MAGIC_HEADER) {
std::cerr << "[ResourcePack] Error: capçalera invàlida (esperava " << MAGIC_HEADER
<< ")\n";
return false;
}
uint32_t version;
file.read(reinterpret_cast<char*>(&version), sizeof(version));
if (version != VERSION) {
std::cerr << "[ResourcePack] Error: versió incompatible (esperava " << VERSION
<< ", trobat " << version << ")\n";
return false;
}
// Llegir nombre de recursos
uint32_t resource_count;
file.read(reinterpret_cast<char*>(&resource_count), sizeof(resource_count));
// Llegir metadades de recursos
resources_.clear();
for (uint32_t i = 0; i < resource_count; ++i) {
// Nom del fitxer
uint32_t name_len;
file.read(reinterpret_cast<char*>(&name_len), sizeof(name_len));
std::string filename(name_len, '\0');
file.read(&filename[0], name_len);
// Offset, mida, checksum
ResourceEntry entry;
entry.filename = filename;
file.read(reinterpret_cast<char*>(&entry.offset), sizeof(entry.offset));
file.read(reinterpret_cast<char*>(&entry.size), sizeof(entry.size));
file.read(reinterpret_cast<char*>(&entry.checksum), sizeof(entry.checksum));
resources_[filename] = entry;
}
// Llegir dades encriptades
uint64_t data_size;
file.read(reinterpret_cast<char*>(&data_size), sizeof(data_size));
data_.resize(data_size);
file.read(reinterpret_cast<char*>(data_.data()), data_size);
// Desencriptar
decryptData(data_, DEFAULT_ENCRYPT_KEY);
std::cout << "[ResourcePack] Carregat: " << pack_file << " (" << resources_.size()
<< " recursos)\n";
return true;
}
// Obtenir un recurs del paquet
std::vector<uint8_t> Pack::getResource(const std::string& filename) {
auto it = resources_.find(filename);
if (it == resources_.end()) {
std::cerr << "[ResourcePack] Error: recurs no trobat: " << filename << '\n';
return {};
}
const auto& entry = it->second;
// Extreure dades
if (entry.offset + entry.size > data_.size()) {
std::cerr << "[ResourcePack] Error: offset/mida invàlid per " << filename << '\n';
return {};
}
std::vector<uint8_t> resource_data(data_.begin() + entry.offset,
data_.begin() + entry.offset + entry.size);
// Verificar checksum
uint32_t computed_checksum = calculateChecksum(resource_data);
if (computed_checksum != entry.checksum) {
std::cerr << "[ResourcePack] ADVERTÈNCIA: checksum invàlid per " << filename
<< " (esperat " << entry.checksum << ", calculat " << computed_checksum
<< ")\n";
// No falla, però adverteix
}
return resource_data;
}
// Comprovar si existeix un recurs
bool Pack::hasResource(const std::string& filename) const {
return resources_.find(filename) != resources_.end();
}
// Obtenir llista de tots els recursos
std::vector<std::string> Pack::getResourceList() const {
std::vector<std::string> list;
list.reserve(resources_.size());
for (const auto& [name, entry] : resources_) {
list.push_back(name);
}
std::ranges::sort(list);
return list;
}
// Validar integritat del paquet
bool Pack::validatePack() const {
bool valid = true;
for (const auto& [name, entry] : resources_) {
// Verificar offset i mida
if (entry.offset + entry.size > data_.size()) {
std::cerr << "[ResourcePack] Error de validació: " << name
<< " té offset/mida invàlid\n";
valid = false;
continue;
}
// Extreure i verificar checksum
std::vector<uint8_t> resource_data(data_.begin() + entry.offset,
data_.begin() + entry.offset + entry.size);
uint32_t computed_checksum = calculateChecksum(resource_data);
if (computed_checksum != entry.checksum) {
std::cerr << "[ResourcePack] Error de validació: " << name
<< " té checksum invàlid\n";
valid = false;
}
}
if (valid) {
std::cout << "[ResourcePack] Validació OK (" << resources_.size() << " recursos)\n";
}
return valid;
}
} // namespace Resource

View File

@@ -0,0 +1,67 @@
// resource_pack.hpp - Sistema d'empaquetament de recursos
// © 2025 Port a C++20 amb SDL3
// Basat en el sistema de "pollo" amb adaptacions per Orni Attack
#pragma once
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
namespace Resource {
// Capçalera del fitxer de paquet
struct PackHeader {
char magic[4]; // "ORNI"
uint32_t version; // Versió del format (1)
};
// Entrada de recurs dins el paquet
struct ResourceEntry {
std::string filename; // Nom del recurs (amb barres normals)
uint64_t offset; // Posició dins el bloc de dades
uint64_t size; // Mida en bytes
uint32_t checksum; // Checksum CRC32 per verificació
};
// Classe principal per gestionar paquets de recursos
class Pack {
public:
Pack() = default;
~Pack() = default;
// Afegir fitxers al paquet
bool addFile(const std::string& filepath, const std::string& pack_name);
bool addDirectory(const std::string& dir_path, const std::string& base_path = "");
// Guardar i carregar paquets
bool savePack(const std::string& pack_file);
bool loadPack(const std::string& pack_file);
// Accés a recursos
std::vector<uint8_t> getResource(const std::string& filename);
bool hasResource(const std::string& filename) const;
std::vector<std::string> getResourceList() const;
// Validació
bool validatePack() const;
private:
// Constants
static constexpr const char* MAGIC_HEADER = "ORNI";
static constexpr uint32_t VERSION = 1;
static constexpr const char* DEFAULT_ENCRYPT_KEY = "ORNI_RESOURCES_2025";
// Dades del paquet
std::unordered_map<std::string, ResourceEntry> resources_;
std::vector<uint8_t> data_;
// Funcions auxiliars
std::vector<uint8_t> readFile(const std::string& filepath);
uint32_t calculateChecksum(const std::vector<uint8_t>& data) const;
void encryptData(std::vector<uint8_t>& data, const std::string& key);
void decryptData(std::vector<uint8_t>& data, const std::string& key);
};
} // namespace Resource

View File

@@ -11,6 +11,9 @@
#include "core/audio/audio_cache.hpp" #include "core/audio/audio_cache.hpp"
#include "core/defaults.hpp" #include "core/defaults.hpp"
#include "core/rendering/sdl_manager.hpp" #include "core/rendering/sdl_manager.hpp"
#include "core/resources/resource_helper.hpp"
#include "core/resources/resource_loader.hpp"
#include "core/utils/path_utils.hpp"
#include "game/escenes/escena_joc.hpp" #include "game/escenes/escena_joc.hpp"
#include "game/escenes/escena_logo.hpp" #include "game/escenes/escena_logo.hpp"
#include "game/escenes/escena_titol.hpp" #include "game/escenes/escena_titol.hpp"
@@ -37,6 +40,44 @@ Director::Director(std::vector<std::string> const& args) {
// Comprovar arguments del programa // Comprovar arguments del programa
executable_path_ = checkProgramArguments(args); executable_path_ = checkProgramArguments(args);
// Inicialitzar sistema de rutes
Utils::initializePathSystem(args[0].c_str());
// Obtenir ruta base dels recursos
std::string resource_base = Utils::getResourceBasePath();
// Inicialitzar sistema de recursos
#ifdef RELEASE_BUILD
// Mode release: paquet obligatori, sense fallback
std::string pack_path = resource_base + "/resources.pack";
if (!Resource::Helper::initializeResourceSystem(pack_path, false)) {
std::cerr << "ERROR FATAL: No es pot carregar " << pack_path << "\n";
std::cerr << "El joc no pot continuar sense els recursos.\n";
std::exit(1);
}
// Validar integritat del paquet
if (!Resource::Loader::get().validatePack()) {
std::cerr << "ERROR FATAL: El paquet de recursos està corromput\n";
std::exit(1);
}
std::cout << "Sistema de recursos inicialitzat (mode release)\n";
#else
// Mode desenvolupament: intentar paquet amb fallback a data/
std::string pack_path = resource_base + "/resources.pack";
Resource::Helper::initializeResourceSystem(pack_path, true);
if (Resource::Helper::isPackLoaded()) {
std::cout << "Sistema de recursos inicialitzat (mode dev amb paquet)\n";
} else {
std::cout << "Sistema de recursos inicialitzat (mode dev, fallback a data/)\n";
}
// Establir ruta base per al fallback
Resource::Loader::get().setBasePath(resource_base);
#endif
// Crear carpetes del sistema // Crear carpetes del sistema
createSystemFolder("jailgames"); createSystemFolder("jailgames");
createSystemFolder(std::string("jailgames/") + Project::NAME); createSystemFolder(std::string("jailgames/") + Project::NAME);

View File

@@ -0,0 +1,92 @@
// path_utils.cpp - Implementació de utilitats de rutes
// © 2025 Port a C++20 amb SDL3
#include "path_utils.hpp"
#include <algorithm>
#include <filesystem>
#include <iostream>
namespace Utils {
// Variables globals per guardar argv[0]
static std::string executable_path_;
static std::string executable_directory_;
// Inicialitzar el sistema de rutes amb argv[0]
void initializePathSystem(const char* argv0) {
if (!argv0) {
std::cerr << "[PathUtils] ADVERTÈNCIA: argv[0] és nullptr\n";
executable_path_ = "";
executable_directory_ = ".";
return;
}
executable_path_ = argv0;
// Extreure el directori
std::filesystem::path path(argv0);
executable_directory_ = path.parent_path().string();
if (executable_directory_.empty()) {
executable_directory_ = ".";
}
std::cout << "[PathUtils] Executable: " << executable_path_ << "\n";
std::cout << "[PathUtils] Directori: " << executable_directory_ << "\n";
}
// Obtenir el directori de l'executable
std::string getExecutableDirectory() {
if (executable_directory_.empty()) {
std::cerr << "[PathUtils] ADVERTÈNCIA: Sistema de rutes no inicialitzat\n";
return ".";
}
return executable_directory_;
}
// Detectar si estem dins un bundle de macOS
bool isMacOSBundle() {
#ifdef MACOS_BUNDLE
return true;
#else
// Detecció en temps d'execució
// Cercar ".app/Contents/MacOS" a la ruta de l'executable
std::string exe_dir = getExecutableDirectory();
return exe_dir.find(".app/Contents/MacOS") != std::string::npos;
#endif
}
// Obtenir la ruta base dels recursos
std::string getResourceBasePath() {
std::string exe_dir = getExecutableDirectory();
if (isMacOSBundle()) {
// Bundle de macOS: recursos a ../Resources des de MacOS/
std::cout << "[PathUtils] Detectat bundle de macOS\n";
return exe_dir + "/../Resources";
} else {
// Executable normal: recursos al mateix directori
return exe_dir;
}
}
// Normalitzar ruta (convertir barres, etc.)
std::string normalizePath(const std::string& path) {
std::string normalized = path;
// Convertir barres invertides a normals
std::ranges::replace(normalized, '\\', '/');
// Simplificar rutes amb filesystem
try {
std::filesystem::path fs_path(normalized);
normalized = fs_path.lexically_normal().string();
} catch (const std::exception& e) {
std::cerr << "[PathUtils] Error normalitzant ruta: " << e.what() << "\n";
}
return normalized;
}
} // namespace Utils

View File

@@ -0,0 +1,24 @@
// path_utils.hpp - Utilitats de gestió de rutes
// © 2025 Port a C++20 amb SDL3
// Detecció de directoris i bundles multiplataforma
#pragma once
#include <string>
namespace Utils {
// Inicialització amb argv[0]
void initializePathSystem(const char* argv0);
// Obtenció de rutes
std::string getExecutableDirectory();
std::string getResourceBasePath();
// Detecció de plataforma
bool isMacOSBundle();
// Normalització
std::string normalizePath(const std::string& path);
} // namespace Utils

View File

@@ -2,24 +2,37 @@
// © 2025 Orni Attack // © 2025 Orni Attack
#include "stage_loader.hpp" #include "stage_loader.hpp"
#include "core/resources/resource_helper.hpp"
#include "external/fkyaml_node.hpp" #include "external/fkyaml_node.hpp"
#include <iostream>
#include <fstream> #include <fstream>
#include <iostream>
#include <sstream> #include <sstream>
namespace StageSystem { namespace StageSystem {
std::unique_ptr<ConfigSistemaStages> StageLoader::carregar(const std::string& path) { std::unique_ptr<ConfigSistemaStages> StageLoader::carregar(const std::string& path) {
try { try {
// Llegir fitxer YAML // Normalize path: "data/stages/stages.yaml" → "stages/stages.yaml"
std::ifstream file(path); std::string normalized = path;
if (!file.is_open()) { if (normalized.starts_with("data/")) {
std::cerr << "[StageLoader] Error: no es pot obrir el fitxer " << path << std::endl; normalized = normalized.substr(5);
}
// Load from resource system
std::vector<uint8_t> data = Resource::Helper::loadFile(normalized);
if (data.empty()) {
std::cerr << "[StageLoader] Error: no es pot carregar " << normalized << std::endl;
return nullptr; return nullptr;
} }
// Convert to string
std::string yaml_content(data.begin(), data.end());
std::stringstream stream(yaml_content);
// Parse YAML // Parse YAML
fkyaml::node yaml = fkyaml::node::deserialize(file); fkyaml::node yaml = fkyaml::node::deserialize(stream);
auto config = std::make_unique<ConfigSistemaStages>(); auto config = std::make_unique<ConfigSistemaStages>();
// Parse metadata // Parse metadata

View File

@@ -0,0 +1,20 @@
# Makefile per a pack_resources
# © 2025 Orni Attack
CXX = clang++
CXXFLAGS = -std=c++20 -Wall -Wextra -I../../source
TARGET = pack_resources
SOURCES = pack_resources.cpp \
../../source/core/resources/resource_pack.cpp
$(TARGET): $(SOURCES)
@echo "Compilant $(TARGET)..."
@$(CXX) $(CXXFLAGS) -o $(TARGET) $(SOURCES)
@echo "$(TARGET) compilat"
clean:
@rm -f $(TARGET)
@echo "✓ Netejat"
.PHONY: clean

Binary file not shown.

View File

@@ -0,0 +1,92 @@
// pack_resources.cpp - Utilitat per crear paquets de recursos
// © 2025 Orni Attack
#include "../../source/core/resources/resource_pack.hpp"
#include <filesystem>
#include <iostream>
void print_usage(const char* program_name) {
std::cout << "Ús: " << program_name << " [opcions] [directori_entrada] [fitxer_sortida]\n";
std::cout << "\nOpcions:\n";
std::cout << " --list <fitxer> Llistar contingut d'un paquet\n";
std::cout << "\nExemples:\n";
std::cout << " " << program_name << " data resources.pack\n";
std::cout << " " << program_name << " --list resources.pack\n";
std::cout << "\nSi no s'especifiquen arguments, empaqueta 'data/' a 'resources.pack'\n";
}
int main(int argc, char* argv[]) {
std::string input_dir = "data";
std::string output_file = "resources.pack";
// Processar arguments
if (argc == 2 && std::string(argv[1]) == "--help") {
print_usage(argv[0]);
return 0;
}
// Mode --list
if (argc == 3 && std::string(argv[1]) == "--list") {
Resource::Pack pack;
if (!pack.loadPack(argv[2])) {
std::cerr << "ERROR: No es pot carregar " << argv[2] << "\n";
return 1;
}
std::cout << "Contingut de " << argv[2] << ":\n";
auto resources = pack.getResourceList();
std::cout << "Total: " << resources.size() << " recursos\n\n";
for (const auto& name : resources) {
std::cout << " " << name << "\n";
}
return 0;
}
// Mode empaquetar
if (argc >= 3) {
input_dir = argv[1];
output_file = argv[2];
}
// Verificar que existeix el directori
if (!std::filesystem::exists(input_dir)) {
std::cerr << "ERROR: Directori no trobat: " << input_dir << "\n";
return 1;
}
if (!std::filesystem::is_directory(input_dir)) {
std::cerr << "ERROR: " << input_dir << " no és un directori\n";
return 1;
}
// Crear paquet
std::cout << "Creant paquet de recursos...\n";
std::cout << " Entrada: " << input_dir << "\n";
std::cout << " Sortida: " << output_file << "\n\n";
Resource::Pack pack;
if (!pack.addDirectory(input_dir)) {
std::cerr << "ERROR: No s'ha pogut afegir el directori\n";
return 1;
}
if (!pack.savePack(output_file)) {
std::cerr << "ERROR: No s'ha pogut guardar el paquet\n";
return 1;
}
// Resum
auto resources = pack.getResourceList();
std::cout << "\n";
std::cout << "✓ Paquet creat amb èxit!\n";
std::cout << " Recursos: " << resources.size() << "\n";
// Mostrar mida del fitxer
auto file_size = std::filesystem::file_size(output_file);
std::cout << " Mida: " << (file_size / 1024) << " KB\n";
return 0;
}