primer commit

This commit is contained in:
2025-11-23 11:44:31 +01:00
commit 6ada29eaf8
613 changed files with 484459 additions and 0 deletions

View File

@@ -0,0 +1,492 @@
#include "core/resources/resource_cache.hpp"
#include <SDL3/SDL.h>
#include <algorithm> // Para find_if
#include <cstdlib> // Para exit, size_t
#include <iostream> // Para basic_ostream, operator<<, endl, cout
#include <stdexcept> // Para runtime_error
#include <utility>
#include "core/audio/jail_audio.hpp" // Para JA_DeleteMusic, JA_DeleteSound, JA_Loa...
#include "core/rendering/screen.hpp" // Para Screen
#include "core/rendering/text.hpp" // Para Text, loadTextFile
#include "core/resources/resource_helper.hpp" // Para Helper
#include "core/resources/resource_list.hpp" // Para List, List::Type
#include "game/defaults.hpp" // Para Defaults namespace
#include "game/gameplay/room.hpp" // Para RoomData, loadRoomFile, loadRoomTileFile
#include "game/options.hpp" // Para Options, OptionsGame, options
#include "project.h" // Para Project::GIT_HASH
#include "utils/color.hpp" // Para Color
#include "utils/utils.hpp" // Para getFileName, printWithDots
struct JA_Music_t; // lines 17-17
struct JA_Sound_t; // lines 18-18
namespace Resource {
// [SINGLETON] Hay que definir las variables estáticas, desde el .h sólo la hemos declarado
Cache* Cache::cache = nullptr;
// [SINGLETON] Crearemos el objeto cache con esta función estática
void Cache::init() { Cache::cache = new Cache(); }
// [SINGLETON] Destruiremos el objeto cache con esta función estática
void Cache::destroy() { delete Cache::cache; }
// [SINGLETON] Con este método obtenemos el objeto cache y podemos trabajar con él
auto Cache::get() -> Cache* { return Cache::cache; }
// Constructor
Cache::Cache()
: loading_text_(Screen::get()->getText()) {
load();
}
// Vacia todos los vectores de recursos
void Cache::clear() {
clearSounds();
clearMusics();
surfaces_.clear();
palettes_.clear();
text_files_.clear();
texts_.clear();
animations_.clear();
}
// Carga todos los recursos
void Cache::load() {
calculateTotal();
Screen::get()->setBorderColor(Color::index(Color::Cpc::BLACK));
std::cout << "\n** LOADING RESOURCES" << '\n';
loadSounds();
loadMusics();
loadSurfaces();
loadPalettes();
loadTextFiles();
loadAnimations();
loadRooms();
createText();
std::cout << "\n** RESOURCES LOADED" << '\n';
}
// Recarga todos los recursos
void Cache::reload() {
clear();
load();
}
// Obtiene el sonido a partir de un nombre
auto Cache::getSound(const std::string& name) -> JA_Sound_t* {
auto it = std::ranges::find_if(sounds_, [&name](const auto& s) { return s.name == name; });
if (it != sounds_.end()) {
return it->sound;
}
std::cerr << "Error: Sonido no encontrado " << name << '\n';
throw std::runtime_error("Sonido no encontrado: " + name);
}
// Obtiene la música a partir de un nombre
auto Cache::getMusic(const std::string& name) -> JA_Music_t* {
auto it = std::ranges::find_if(musics_, [&name](const auto& m) { return m.name == name; });
if (it != musics_.end()) {
return it->music;
}
std::cerr << "Error: Música no encontrada " << name << '\n';
throw std::runtime_error("Música no encontrada: " + name);
}
// Obtiene la surface a partir de un nombre
auto Cache::getSurface(const std::string& name) -> std::shared_ptr<Surface> {
auto it = std::ranges::find_if(surfaces_, [&name](const auto& t) { return t.name == name; });
if (it != surfaces_.end()) {
return it->surface;
}
std::cerr << "Error: Imagen no encontrada " << name << '\n';
throw std::runtime_error("Imagen no encontrada: " + name);
}
// Obtiene la paleta a partir de un nombre
auto Cache::getPalette(const std::string& name) -> Palette {
auto it = std::ranges::find_if(palettes_, [&name](const auto& t) { return t.name == name; });
if (it != palettes_.end()) {
return it->palette;
}
std::cerr << "Error: Paleta no encontrada " << name << '\n';
throw std::runtime_error("Paleta no encontrada: " + name);
}
// Obtiene el fichero de texto a partir de un nombre
auto Cache::getTextFile(const std::string& name) -> std::shared_ptr<Text::File> {
auto it = std::ranges::find_if(text_files_, [&name](const auto& t) { return t.name == name; });
if (it != text_files_.end()) {
return it->text_file;
}
std::cerr << "Error: TextFile no encontrado " << name << '\n';
throw std::runtime_error("TextFile no encontrado: " + name);
}
// Obtiene el objeto de texto a partir de un nombre
auto Cache::getText(const std::string& name) -> std::shared_ptr<Text> {
auto it = std::ranges::find_if(texts_, [&name](const auto& t) { return t.name == name; });
if (it != texts_.end()) {
return it->text;
}
std::cerr << "Error: Text no encontrado " << name << '\n';
throw std::runtime_error("Texto no encontrado: " + name);
}
// Obtiene los datos de animación parseados a partir de un nombre
auto Cache::getAnimationData(const std::string& name) -> const AnimationResource& {
auto it = std::ranges::find_if(animations_, [&name](const auto& a) { return a.name == name; });
if (it != animations_.end()) {
return *it;
}
std::cerr << "Error: Animación no encontrada " << name << '\n';
throw std::runtime_error("Animación no encontrada: " + name);
}
// Obtiene la habitación a partir de un nombre
auto Cache::getRoom(const std::string& name) -> std::shared_ptr<Room::Data> {
auto it = std::ranges::find_if(rooms_, [&name](const auto& r) { return r.name == name; });
if (it != rooms_.end()) {
return it->room;
}
std::cerr << "Error: Habitación no encontrada " << name << '\n';
throw std::runtime_error("Habitación no encontrada: " + name);
}
// Obtiene todas las habitaciones
auto Cache::getRooms() -> std::vector<RoomResource>& {
return rooms_;
}
// Helper para lanzar errores de carga con formato consistente
[[noreturn]] void Cache::throwLoadError(const std::string& asset_type, const std::string& file_path, const std::exception& e) {
std::cerr << "\n[ ERROR ] Failed to load " << asset_type << ": " << getFileName(file_path) << '\n';
std::cerr << "[ ERROR ] Path: " << file_path << '\n';
std::cerr << "[ ERROR ] Reason: " << e.what() << '\n';
std::cerr << "[ ERROR ] Check config/assets.yaml configuration\n";
throw;
}
// Carga los sonidos
void Cache::loadSounds() {
std::cout << "\n>> SOUND FILES" << '\n';
auto list = List::get()->getListByType(List::Type::SOUND);
sounds_.clear();
for (const auto& l : list) {
try {
auto name = getFileName(l);
JA_Sound_t* sound = nullptr;
// Try loading from resource pack first
auto audio_data = Helper::loadFile(l);
if (!audio_data.empty()) {
sound = JA_LoadSound(audio_data.data(), static_cast<Uint32>(audio_data.size()));
}
// Fallback to file path if memory loading failed
if (sound == nullptr) {
sound = JA_LoadSound(l.c_str());
}
if (sound == nullptr) {
throw std::runtime_error("Failed to decode audio file");
}
sounds_.emplace_back(SoundResource{.name = name, .sound = sound});
printWithDots("Sound : ", name, "[ LOADED ]");
updateLoadingProgress();
} catch (const std::exception& e) {
throwLoadError("SOUND", l, e);
}
}
}
// Carga las musicas
void Cache::loadMusics() {
std::cout << "\n>> MUSIC FILES" << '\n';
auto list = List::get()->getListByType(List::Type::MUSIC);
musics_.clear();
for (const auto& l : list) {
try {
auto name = getFileName(l);
JA_Music_t* music = nullptr;
// Try loading from resource pack first
auto audio_data = Helper::loadFile(l);
if (!audio_data.empty()) {
music = JA_LoadMusic(audio_data.data(), static_cast<Uint32>(audio_data.size()));
}
// Fallback to file path if memory loading failed
if (music == nullptr) {
music = JA_LoadMusic(l.c_str());
}
if (music == nullptr) {
throw std::runtime_error("Failed to decode music file");
}
musics_.emplace_back(MusicResource{.name = name, .music = music});
printWithDots("Music : ", name, "[ LOADED ]");
updateLoadingProgress(1);
} catch (const std::exception& e) {
throwLoadError("MUSIC", l, e);
}
}
}
// Carga las texturas
void Cache::loadSurfaces() {
std::cout << "\n>> SURFACES" << '\n';
auto list = List::get()->getListByType(List::Type::BITMAP);
surfaces_.clear();
for (const auto& l : list) {
try {
auto name = getFileName(l);
surfaces_.emplace_back(SurfaceResource{.name = name, .surface = std::make_shared<Surface>(l)});
surfaces_.back().surface->setTransparentColor(0);
updateLoadingProgress();
} catch (const std::exception& e) {
throwLoadError("BITMAP", l, e);
}
}
}
// Carga las paletas
void Cache::loadPalettes() {
std::cout << "\n>> PALETTES" << '\n';
auto list = List::get()->getListByType(List::Type::PALETTE);
palettes_.clear();
for (const auto& l : list) {
try {
auto name = getFileName(l);
palettes_.emplace_back(ResourcePalette{.name = name, .palette = readPalFile(l)});
updateLoadingProgress();
} catch (const std::exception& e) {
throwLoadError("PALETTE", l, e);
}
}
}
// Carga los ficheros de texto
void Cache::loadTextFiles() {
std::cout << "\n>> TEXT FILES" << '\n';
auto list = List::get()->getListByType(List::Type::FONT);
text_files_.clear();
for (const auto& l : list) {
try {
auto name = getFileName(l);
text_files_.emplace_back(TextFileResource{.name = name, .text_file = Text::loadTextFile(l)});
updateLoadingProgress();
} catch (const std::exception& e) {
throwLoadError("FONT", l, e);
}
}
}
// Carga las animaciones
void Cache::loadAnimations() {
std::cout << "\n>> ANIMATIONS" << '\n';
auto list = List::get()->getListByType(List::Type::ANIMATION);
animations_.clear();
for (const auto& l : list) {
try {
auto name = getFileName(l);
// Cargar bytes del archivo YAML sin parsear (carga lazy)
auto yaml_bytes = Helper::loadFile(l);
if (yaml_bytes.empty()) {
throw std::runtime_error("File is empty or could not be loaded");
}
animations_.emplace_back(AnimationResource{.name = name, .yaml_data = yaml_bytes});
printWithDots("Animation : ", name, "[ LOADED ]");
updateLoadingProgress();
} catch (const std::exception& e) {
throwLoadError("ANIMATION", l, e);
}
}
}
// Carga las habitaciones desde archivos YAML
void Cache::loadRooms() {
std::cout << "\n>> ROOMS" << '\n';
auto list = List::get()->getListByType(List::Type::ROOM);
rooms_.clear();
for (const auto& l : list) {
try {
auto name = getFileName(l);
rooms_.emplace_back(RoomResource{.name = name, .room = std::make_shared<Room::Data>(Room::loadYAML(l))});
printWithDots("Room : ", name, "[ LOADED ]");
updateLoadingProgress();
} catch (const std::exception& e) {
throwLoadError("ROOM", l, e);
}
}
}
void Cache::createText() {
struct ResourceInfo {
std::string key; // Identificador del recurso
std::string texture_file; // Nombre del archivo de textura
std::string text_file; // Nombre del archivo de texto
};
std::cout << "\n>> CREATING TEXT_OBJECTS" << '\n';
std::vector<ResourceInfo> resources = {
{.key = "aseprite", .texture_file = "aseprite.gif", .text_file = "aseprite.txt"},
{.key = "gauntlet", .texture_file = "gauntlet.gif", .text_file = "gauntlet.txt"},
{.key = "smb2", .texture_file = "smb2.gif", .text_file = "smb2.txt"},
{.key = "subatomic", .texture_file = "subatomic.gif", .text_file = "subatomic.txt"},
{.key = "8bithud", .texture_file = "8bithud.gif", .text_file = "8bithud.txt"}};
for (const auto& res_info : resources) {
texts_.emplace_back(TextResource{.name = res_info.key, .text = std::make_shared<Text>(getSurface(res_info.texture_file), getTextFile(res_info.text_file))});
printWithDots("Text : ", res_info.key, "[ DONE ]");
}
}
// Vacía el vector de sonidos
void Cache::clearSounds() {
// Itera sobre el vector y libera los recursos asociados a cada JA_Sound_t
for (auto& sound : sounds_) {
if (sound.sound != nullptr) {
JA_DeleteSound(sound.sound);
sound.sound = nullptr;
}
}
sounds_.clear(); // Limpia el vector después de liberar todos los recursos
}
// Vacía el vector de musicas
void Cache::clearMusics() {
// Itera sobre el vector y libera los recursos asociados a cada JA_Music_t
for (auto& music : musics_) {
if (music.music != nullptr) {
JA_DeleteMusic(music.music);
music.music = nullptr;
}
}
musics_.clear(); // Limpia el vector después de liberar todos los recursos
}
// Calcula el numero de recursos para cargar
void Cache::calculateTotal() {
std::vector<List::Type> asset_types = {
List::Type::SOUND,
List::Type::MUSIC,
List::Type::BITMAP,
List::Type::PALETTE,
List::Type::FONT,
List::Type::ANIMATION,
List::Type::ROOM};
int total = 0;
for (const auto& asset_type : asset_types) {
auto list = List::get()->getListByType(asset_type);
total += list.size();
}
count_ = ResourceCount{.total = total, .loaded = 0};
}
// Muestra el progreso de carga
void Cache::renderProgress() {
constexpr float X_PADDING = 60.0F;
constexpr float Y_PADDING = 10.0F;
constexpr float BAR_HEIGHT = 5.0F;
const float BAR_POSITION = Options::game.height - BAR_HEIGHT - Y_PADDING;
Screen::get()->start();
Screen::get()->clearSurface(Color::index(Color::Cpc::BLACK));
auto surface = Screen::get()->getRendererSurface();
const auto LOADING_TEXT_COLOR = Color::index(Color::Cpc::ORANGE);
const auto BAR_COLOR = Color::index(Color::Cpc::PASTEL_BLUE);
const int TEXT_HEIGHT = loading_text_->getCharacterSize();
const int CENTER_X = Options::game.width / 2;
const int CENTER_Y = Options::game.height / 2;
// Draw APP_NAME centered above center
const std::string APP_NAME = spaceBetweenLetters(Project::LONG_NAME);
loading_text_->writeColored(
CENTER_X - (loading_text_->length(APP_NAME) / 2),
CENTER_Y - TEXT_HEIGHT,
APP_NAME,
LOADING_TEXT_COLOR);
// Draw VERSION centered below center
const std::string VERSION_TEXT = "ver. " + std::string(Project::VERSION) + " (" + std::string(Project::GIT_HASH) + ")";
loading_text_->writeColored(
CENTER_X - (loading_text_->length(VERSION_TEXT) / 2),
CENTER_Y + TEXT_HEIGHT,
VERSION_TEXT,
LOADING_TEXT_COLOR);
// Draw progress bar border
const float WIRED_BAR_WIDTH = Options::game.width - (X_PADDING * 2);
SDL_FRect rect_wired = {X_PADDING, BAR_POSITION, WIRED_BAR_WIDTH, BAR_HEIGHT};
surface->drawRectBorder(&rect_wired, BAR_COLOR);
// Draw progress bar fill
const float FULL_BAR_WIDTH = WIRED_BAR_WIDTH * count_.getPercentage();
SDL_FRect rect_full = {X_PADDING, BAR_POSITION, FULL_BAR_WIDTH, BAR_HEIGHT};
surface->fillRect(&rect_full, BAR_COLOR);
Screen::get()->render();
}
// Comprueba los eventos de la pantalla de carga
void Cache::checkEvents() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_EVENT_QUIT:
exit(0);
break;
case SDL_EVENT_KEY_DOWN:
if (event.key.key == SDLK_ESCAPE) {
exit(0);
}
break;
}
}
}
// Actualiza el progreso de carga
void Cache::updateLoadingProgress(int steps) {
count_.add(1);
if (count_.loaded % steps == 0 || count_.loaded == count_.total) {
renderProgress();
}
checkEvents();
}
} // namespace Resource

View File

@@ -0,0 +1,93 @@
#pragma once
#include <memory> // Para shared_ptr
#include <string> // Para string
#include <utility>
#include <vector> // Para vector
#include "core/resources/resource_types.hpp" // Para structs de recursos
namespace Resource {
class Cache {
public:
static void init(); // Inicialización singleton
static void destroy(); // Destrucción singleton
static auto get() -> Cache*; // Acceso al singleton
auto getSound(const std::string& name) -> JA_Sound_t*; // Getters de recursos
auto getMusic(const std::string& name) -> JA_Music_t*;
auto getSurface(const std::string& name) -> std::shared_ptr<Surface>;
auto getPalette(const std::string& name) -> Palette;
auto getTextFile(const std::string& name) -> std::shared_ptr<Text::File>;
auto getText(const std::string& name) -> std::shared_ptr<Text>;
auto getAnimationData(const std::string& name) -> const AnimationResource&;
auto getRoom(const std::string& name) -> std::shared_ptr<Room::Data>;
auto getRooms() -> std::vector<RoomResource>&;
void reload(); // Recarga todos los recursos
private:
// Estructura para llevar la cuenta de los recursos cargados
struct ResourceCount {
int total{0}; // Número total de recursos
int loaded{0}; // Número de recursos cargados
// Añade una cantidad a los recursos cargados
void add(int amount) {
loaded += amount;
}
// Obtiene el porcentaje de recursos cargados
[[nodiscard]] auto getPercentage() const -> float {
return static_cast<float>(loaded) / static_cast<float>(total);
}
};
// Métodos de carga de recursos
void loadSounds();
void loadMusics();
void loadSurfaces();
void loadPalettes();
void loadTextFiles();
void loadAnimations();
void loadRooms();
void createText();
// Métodos de limpieza
void clear();
void clearSounds();
void clearMusics();
// Métodos de gestión de carga
void load();
void calculateTotal();
void renderProgress();
static void checkEvents();
void updateLoadingProgress(int steps = 5);
// Helper para mensajes de error de carga
[[noreturn]] static void throwLoadError(const std::string& asset_type, const std::string& file_path, const std::exception& e);
// Constructor y destructor
Cache();
~Cache() = default;
// Singleton instance
static Cache* cache;
// Variables miembro
std::vector<SoundResource> sounds_; // Vector con los sonidos
std::vector<MusicResource> musics_; // Vector con las musicas
std::vector<SurfaceResource> surfaces_; // Vector con las surfaces
std::vector<ResourcePalette> palettes_; // Vector con las paletas
std::vector<TextFileResource> text_files_; // Vector con los ficheros de texto
std::vector<TextResource> texts_; // Vector con los objetos de texto
std::vector<AnimationResource> animations_; // Vector con las animaciones
std::vector<RoomResource> rooms_; // Vector con las habitaciones
ResourceCount count_{}; // Contador de recursos
std::shared_ptr<Text> loading_text_; // Texto para la pantalla de carga
};
} // namespace Resource

View File

@@ -0,0 +1,182 @@
// resource_helper.cpp
// Resource helper implementation
#include "resource_helper.hpp"
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iostream>
#include "resource_loader.hpp"
namespace Resource::Helper {
static bool resource_system_initialized = false;
// Initialize the resource system
auto initializeResourceSystem(const std::string& pack_file, bool enable_fallback)
-> bool {
if (resource_system_initialized) {
std::cout << "ResourceHelper: Already initialized\n";
return true;
}
std::cout << "ResourceHelper: Initializing with pack: " << pack_file << '\n';
std::cout << "ResourceHelper: Fallback enabled: " << (enable_fallback ? "Yes" : "No")
<< '\n';
bool success = Loader::get().initialize(pack_file, enable_fallback);
if (success) {
resource_system_initialized = true;
std::cout << "ResourceHelper: Initialization successful\n";
} else {
std::cerr << "ResourceHelper: Initialization failed\n";
}
return success;
}
// Shutdown the resource system
void shutdownResourceSystem() {
if (resource_system_initialized) {
Loader::get().shutdown();
resource_system_initialized = false;
std::cout << "ResourceHelper: Shutdown complete\n";
}
}
// Load a file
auto loadFile(const std::string& filepath) -> std::vector<uint8_t> {
if (!resource_system_initialized) {
std::cerr << "ResourceHelper: System not initialized, loading from filesystem\n";
// Fallback to direct filesystem access
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file) {
return {};
}
std::streamsize file_size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> data(file_size);
file.read(reinterpret_cast<char*>(data.data()), file_size);
return data;
}
// Determine if we should use the pack
if (shouldUseResourcePack(filepath)) {
// Convert to pack path
std::string pack_path = getPackPath(filepath);
// Try to load from pack
auto data = Loader::get().loadResource(pack_path);
if (!data.empty()) {
return data;
}
// If pack loading failed, try filesystem as fallback
std::cerr << "ResourceHelper: Pack failed for " << pack_path
<< ", trying filesystem\n";
}
// Load from filesystem
return Loader::get().loadResource(filepath);
}
// Check if a file exists
auto fileExists(const std::string& filepath) -> bool {
if (!resource_system_initialized) {
return std::filesystem::exists(filepath);
}
// Check pack if appropriate
if (shouldUseResourcePack(filepath)) {
std::string pack_path = getPackPath(filepath);
if (Loader::get().resourceExists(pack_path)) {
return true;
}
}
// Check filesystem
return std::filesystem::exists(filepath);
}
// Convert asset path to pack path
auto getPackPath(const std::string& asset_path) -> std::string {
std::string path = asset_path;
// Convert backslashes to forward slashes
std::ranges::replace(path, '\\', '/');
// If it's an absolute path containing "/data/", extract everything after "/data/"
// This handles paths like: /Users/sergio/.../data/palette/file.pal -> palette/file.pal
size_t data_pos = path.find("/data/");
if (data_pos != std::string::npos) {
return path.substr(data_pos + 6); // +6 to skip "/data/"
}
// Remove leading slashes
while (!path.empty() && path[0] == '/') {
path = path.substr(1);
}
// Remove "./" prefix if present
if (path.starts_with("./")) {
path = path.substr(2);
}
// Remove "../" prefixes (for macOS bundle paths in development)
while (path.starts_with("../")) {
path = path.substr(3);
}
// Remove "Resources/" prefix if present (for macOS bundle)
const std::string RESOURCES_PREFIX = "Resources/";
if (path.starts_with(RESOURCES_PREFIX)) {
path = path.substr(RESOURCES_PREFIX.length());
}
// Remove "data/" prefix if present
const std::string DATA_PREFIX = "data/";
if (path.starts_with(DATA_PREFIX)) {
path = path.substr(DATA_PREFIX.length());
}
return path;
}
// Check if file should use resource pack
auto shouldUseResourcePack(const std::string& filepath) -> bool {
std::string path = filepath;
std::ranges::replace(path, '\\', '/');
// Don't use pack for most config files (except config/assets.yaml which is loaded
// directly via Loader::loadAssetsConfig() in release builds)
if (path.find("config/") != std::string::npos) {
return false;
}
// Use pack for data files
if (path.find("data/") != std::string::npos) {
return true;
}
// Check if it looks like a data file (has common extensions)
if (path.find(".ogg") != std::string::npos || path.find(".wav") != std::string::npos ||
path.find(".gif") != std::string::npos || path.find(".png") != std::string::npos ||
path.find(".pal") != std::string::npos || path.find(".yaml") != std::string::npos ||
path.find(".txt") != std::string::npos || path.find(".glsl") != std::string::npos) {
return true;
}
return false;
}
// Check if pack is loaded
auto isPackLoaded() -> bool {
if (!resource_system_initialized) {
return false;
}
return Loader::get().isPackLoaded();
}
} // namespace Resource::Helper

View File

@@ -0,0 +1,38 @@
// resource_helper.hpp
// Helper functions for resource loading (bridge to pack system)
#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace Resource::Helper {
// Initialize the resource system
// pack_file: Path to resources.pack
// enable_fallback: Allow loading from filesystem if pack not available
auto initializeResourceSystem(const std::string& pack_file = "resources.pack",
bool enable_fallback = true) -> bool;
// Shutdown the resource system
void shutdownResourceSystem();
// Load a file (tries pack first, then filesystem if fallback enabled)
auto loadFile(const std::string& filepath) -> std::vector<uint8_t>;
// Check if a file exists
auto fileExists(const std::string& filepath) -> bool;
// Convert an asset path to a pack path
// Example: "data/music/title.ogg" -> "music/title.ogg"
auto getPackPath(const std::string& asset_path) -> std::string;
// Check if a file should use the resource pack
// Returns false for config/ files (always from filesystem)
auto shouldUseResourcePack(const std::string& filepath) -> bool;
// Check if pack is loaded
auto isPackLoaded() -> bool;
} // namespace Resource::Helper

View File

@@ -0,0 +1,320 @@
#include "core/resources/resource_list.hpp"
#include <SDL3/SDL.h> // Para SDL_LogWarn, SDL_LogCategory, SDL_LogError
#include <algorithm> // Para sort
#include <cstddef> // Para size_t
#include <exception> // Para exception
#include <filesystem> // Para exists, path
#include <fstream> // Para ifstream, istringstream
#include <iostream> // Para cout
#include <sstream> // Para istringstream
#include <stdexcept> // Para runtime_error
#include "external/fkyaml_node.hpp" // Para parsear YAML
#include "utils/utils.hpp" // Para getFileName, printWithDots
namespace Resource {
// Singleton
List* List::instance = nullptr;
void List::init(const std::string& executable_path) {
List::instance = new List(executable_path);
}
void List::destroy() {
delete List::instance;
}
auto List::get() -> List* {
return List::instance;
}
// Añade un elemento al mapa (función auxiliar)
void List::addToMap(const std::string& file_path, Type type, bool required, bool absolute) {
std::string full_path = absolute ? file_path : executable_path_ + file_path;
std::string filename = getFileName(full_path);
// Verificar si ya existe el archivo
if (file_list_.find(filename) != file_list_.end()) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Warning: Asset '%s' already exists, overwriting",
filename.c_str());
}
file_list_.emplace(filename, Item{std::move(full_path), type, required});
}
// Añade un elemento a la lista
void List::add(const std::string& file_path, Type type, bool required, bool absolute) {
addToMap(file_path, type, required, absolute);
}
// Carga recursos desde un archivo de configuración con soporte para variables
void List::loadFromFile(const std::string& config_file_path, const std::string& prefix, const std::string& system_folder) {
std::ifstream file(config_file_path);
if (!file.is_open()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error: Cannot open config file: %s",
config_file_path.c_str());
return;
}
// Read entire file into string
std::stringstream buffer;
buffer << file.rdbuf();
file.close();
// Parse using loadFromString
loadFromString(buffer.str(), prefix, system_folder);
}
// Carga recursos desde un string de configuración (para release con pack)
void List::loadFromString(const std::string& config_content, const std::string& prefix, const std::string& system_folder) {
try {
// Parsear YAML
auto yaml = fkyaml::node::deserialize(config_content);
// Verificar estructura básica
if (!yaml.contains("assets")) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Invalid assets.yaml format - missing 'assets' key");
return;
}
const auto& assets = yaml["assets"];
// Iterar sobre cada categoría (fonts, palettes, etc.)
for (auto it = assets.begin(); it != assets.end(); ++it) {
const std::string& category = it.key().get_value<std::string>();
const auto& category_assets = it.value();
// Verificar que es un array
if (!category_assets.is_sequence()) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Warning: Category '%s' is not a sequence, skipping",
category.c_str());
continue;
}
// Procesar cada asset en la categoría
for (const auto& asset : category_assets) {
try {
// Verificar campos obligatorios
if (!asset.contains("type") || !asset.contains("path")) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Warning: Asset in category '%s' missing 'type' or 'path', skipping",
category.c_str());
continue;
}
// Extraer campos
auto type_str = asset["type"].get_value<std::string>();
auto path = asset["path"].get_value<std::string>();
// Valores por defecto
bool required = true;
bool absolute = false;
// Campos opcionales
if (asset.contains("required")) {
required = asset["required"].get_value<bool>();
}
if (asset.contains("absolute")) {
absolute = asset["absolute"].get_value<bool>();
}
// Reemplazar variables en la ruta
path = replaceVariables(path, prefix, system_folder);
// Parsear el tipo de asset
Type type = parseAssetType(type_str);
// Añadir al mapa
addToMap(path, type, required, absolute);
} catch (const std::exception& e) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error parsing asset in category '%s': %s",
category.c_str(),
e.what());
}
}
}
std::cout << "Loaded " << file_list_.size() << " assets from YAML config" << '\n';
} catch (const fkyaml::exception& e) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"YAML parsing error: %s",
e.what());
} catch (const std::exception& e) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error loading assets: %s",
e.what());
}
}
// Devuelve la ruta completa a un fichero (búsqueda O(1))
auto List::get(const std::string& filename) const -> std::string {
auto it = file_list_.find(filename);
if (it != file_list_.end()) {
return it->second.file;
}
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: file %s not found", filename.c_str());
return "";
}
// Carga datos del archivo
auto List::loadData(const std::string& filename) const -> std::vector<uint8_t> {
auto it = file_list_.find(filename);
if (it != file_list_.end()) {
std::ifstream file(it->second.file, std::ios::binary);
if (!file.is_open()) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Warning: Could not open file %s for data loading",
filename.c_str());
return {};
}
// Obtener tamaño del archivo
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
// Leer datos
std::vector<uint8_t> data(size);
file.read(reinterpret_cast<char*>(data.data()), size);
file.close();
return data;
}
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: file %s not found for data loading", filename.c_str());
return {};
}
// Verifica si un recurso existe
auto List::exists(const std::string& filename) const -> bool {
return file_list_.find(filename) != file_list_.end();
}
// Parsea string a Type
auto List::parseAssetType(const std::string& type_str) -> Type {
if (type_str == "DATA") {
return Type::DATA;
}
if (type_str == "BITMAP") {
return Type::BITMAP;
}
if (type_str == "ANIMATION") {
return Type::ANIMATION;
}
if (type_str == "MUSIC") {
return Type::MUSIC;
}
if (type_str == "SOUND") {
return Type::SOUND;
}
if (type_str == "FONT") {
return Type::FONT;
}
if (type_str == "ROOM") {
return Type::ROOM;
}
if (type_str == "TILEMAP") {
// TILEMAP está obsoleto, ahora todo es ROOM (.yaml unificado)
return Type::ROOM;
}
if (type_str == "PALETTE") {
return Type::PALETTE;
}
throw std::runtime_error("Unknown asset type: " + type_str);
}
// Devuelve el nombre del tipo de recurso
auto List::getTypeName(Type type) -> std::string {
switch (type) {
case Type::DATA:
return "DATA";
case Type::BITMAP:
return "BITMAP";
case Type::ANIMATION:
return "ANIMATION";
case Type::MUSIC:
return "MUSIC";
case Type::SOUND:
return "SOUND";
case Type::FONT:
return "FONT";
case Type::ROOM:
return "ROOM";
case Type::PALETTE:
return "PALETTE";
default:
return "ERROR";
}
}
// Devuelve la lista de recursos de un tipo
auto List::getListByType(Type type) const -> std::vector<std::string> {
std::vector<std::string> list;
for (const auto& [filename, item] : file_list_) {
if (item.type == type) {
list.push_back(item.file);
}
}
// Ordenar alfabéticamente para garantizar orden consistente
std::ranges::sort(list);
return list;
}
// Reemplaza variables en las rutas
auto List::replaceVariables(const std::string& path, const std::string& prefix, const std::string& system_folder) -> std::string {
std::string result = path;
// Reemplazar ${PREFIX}
size_t pos = 0;
while ((pos = result.find("${PREFIX}", pos)) != std::string::npos) {
result.replace(pos, 9, prefix); // 9 = longitud de "${PREFIX}"
pos += prefix.length();
}
// Reemplazar ${SYSTEM_FOLDER}
pos = 0;
while ((pos = result.find("${SYSTEM_FOLDER}", pos)) != std::string::npos) {
result.replace(pos, 16, system_folder); // 16 = longitud de "${SYSTEM_FOLDER}"
pos += system_folder.length();
}
return result;
}
// Parsea las opciones de una línea de configuración
auto List::parseOptions(const std::string& options, bool& required, bool& absolute) -> void {
if (options.empty()) {
return;
}
std::istringstream iss(options);
std::string option;
while (std::getline(iss, option, ',')) {
// Eliminar espacios
option.erase(0, option.find_first_not_of(" \t"));
option.erase(option.find_last_not_of(" \t") + 1);
if (option == "optional") {
required = false;
} else if (option == "absolute") {
absolute = true;
}
}
}
} // namespace Resource

View File

@@ -0,0 +1,76 @@
#pragma once
#include <cstdint> // Para uint8_t
#include <string> // Para string
#include <unordered_map> // Para unordered_map
#include <utility> // Para move
#include <vector> // Para vector
namespace Resource {
// --- Clase List: gestor optimizado de recursos (singleton) ---
class List {
public:
// --- Enums ---
enum class Type : int {
DATA, // Datos
BITMAP, // Imágenes
ANIMATION, // Animaciones
MUSIC, // Música
SOUND, // Sonidos
FONT, // Fuentes
ROOM, // Datos de habitación (.yaml - formato unificado con tilemap)
PALETTE, // Paletas
SIZE, // Tamaño (para iteración)
};
// --- Métodos de singleton ---
static void init(const std::string& executable_path);
static void destroy();
static auto get() -> List*;
List(const List&) = delete;
auto operator=(const List&) -> List& = delete;
// --- Métodos para la gestión de recursos ---
void add(const std::string& file_path, Type type, bool required = true, bool absolute = false);
void loadFromFile(const std::string& config_file_path, const std::string& prefix = "", const std::string& system_folder = ""); // Con soporte para variables
void loadFromString(const std::string& config_content, const std::string& prefix = "", const std::string& system_folder = ""); // Para cargar desde pack (release)
[[nodiscard]] auto get(const std::string& filename) const -> std::string; // Obtiene la ruta completa
[[nodiscard]] auto loadData(const std::string& filename) const -> std::vector<uint8_t>; // Carga datos del archivo
[[nodiscard]] auto getListByType(Type type) const -> std::vector<std::string>;
[[nodiscard]] auto exists(const std::string& filename) const -> bool; // Verifica si un asset existe
private:
// --- Estructuras privadas ---
struct Item {
std::string file; // Ruta completa del archivo
Type type; // Tipo de recurso
bool required; // Indica si el archivo es obligatorio
Item(std::string path, Type asset_type, bool is_required)
: file(std::move(path)),
type(asset_type),
required(is_required) {}
};
// --- Variables internas ---
std::unordered_map<std::string, Item> file_list_; // Mapa para búsqueda O(1)
std::string executable_path_; // Ruta del ejecutable
// --- Métodos internos ---
[[nodiscard]] static auto getTypeName(Type type) -> std::string; // Obtiene el nombre del tipo
[[nodiscard]] static auto parseAssetType(const std::string& type_str) -> Type; // Convierte string a tipo
void addToMap(const std::string& file_path, Type type, bool required, bool absolute); // Añade archivo al mapa
[[nodiscard]] static auto replaceVariables(const std::string& path, const std::string& prefix, const std::string& system_folder) -> std::string; // Reemplaza variables en la ruta
static auto parseOptions(const std::string& options, bool& required, bool& absolute) -> void; // Parsea opciones
// --- Constructores y destructor privados (singleton) ---
explicit List(std::string executable_path) // Constructor privado
: executable_path_(std::move(executable_path)) {}
~List() = default; // Destructor privado
// --- Instancia singleton ---
static List* instance; // Instancia única de List
};
} // namespace Resource

View File

@@ -0,0 +1,199 @@
// resource_loader.cpp
// Resource loader implementation
#include "resource_loader.hpp"
#include <filesystem>
#include <fstream>
#include <iostream>
namespace Resource {
// Get singleton instance
auto Loader::get() -> Loader& {
static Loader instance_;
return instance_;
}
// Initialize with a pack file
auto Loader::initialize(const std::string& pack_file, bool enable_fallback)
-> bool {
if (initialized_) {
std::cout << "Loader: Already initialized\n";
return true;
}
fallback_to_files_ = enable_fallback;
// Try to load the pack file
if (!pack_file.empty() && fileExistsOnFilesystem(pack_file)) {
std::cout << "Loader: Loading pack file: " << pack_file << '\n';
resource_pack_ = std::make_unique<Pack>();
if (resource_pack_->loadPack(pack_file)) {
std::cout << "Loader: Pack loaded successfully\n";
initialized_ = true;
return true;
}
std::cerr << "Loader: Failed to load pack file\n";
resource_pack_.reset();
} else {
std::cout << "Loader: Pack file not found: " << pack_file << '\n';
}
// If pack loading failed and fallback is disabled, fail
if (!fallback_to_files_) {
std::cerr << "Loader: Pack required but not found (fallback disabled)\n";
return false;
}
// Otherwise, fallback to filesystem
std::cout << "Loader: Using filesystem fallback\n";
initialized_ = true;
return true;
}
// Load a resource
auto Loader::loadResource(const std::string& filename) -> std::vector<uint8_t> {
if (!initialized_) {
std::cerr << "Loader: Not initialized\n";
return {};
}
// Try pack first if available
if (resource_pack_ && resource_pack_->isLoaded()) {
if (resource_pack_->hasResource(filename)) {
auto data = resource_pack_->getResource(filename);
if (!data.empty()) {
return data;
}
std::cerr << "Loader: Failed to extract from pack: " << filename
<< '\n';
}
}
// Fallback to filesystem if enabled
if (fallback_to_files_) {
return loadFromFilesystem(filename);
}
std::cerr << "Loader: Resource not found: " << filename << '\n';
return {};
}
// Check if a resource exists
auto Loader::resourceExists(const std::string& filename) -> bool {
if (!initialized_) {
return false;
}
// Check pack first
if (resource_pack_ && resource_pack_->isLoaded()) {
if (resource_pack_->hasResource(filename)) {
return true;
}
}
// Check filesystem if fallback enabled
if (fallback_to_files_) {
return fileExistsOnFilesystem(filename);
}
return false;
}
// Check if pack is loaded
auto Loader::isPackLoaded() const -> bool {
return resource_pack_ && resource_pack_->isLoaded();
}
// Get pack statistics
auto Loader::getPackResourceCount() const -> size_t {
if (resource_pack_ && resource_pack_->isLoaded()) {
return resource_pack_->getResourceCount();
}
return 0;
}
// Cleanup
void Loader::shutdown() {
resource_pack_.reset();
initialized_ = false;
std::cout << "Loader: Shutdown complete\n";
}
// Load from filesystem
auto Loader::loadFromFilesystem(const std::string& filepath)
-> std::vector<uint8_t> {
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file) {
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 << "Loader: Failed to read file: " << filepath << '\n';
return {};
}
return data;
}
// Check if file exists on filesystem
auto Loader::fileExistsOnFilesystem(const std::string& filepath) -> bool {
return std::filesystem::exists(filepath);
}
// Validate pack integrity
auto Loader::validatePack() const -> bool {
if (!initialized_ || !resource_pack_ || !resource_pack_->isLoaded()) {
std::cerr << "Loader: Cannot validate - pack not loaded\n";
return false;
}
// Calculate pack checksum
uint32_t checksum = resource_pack_->calculatePackChecksum();
if (checksum == 0) {
std::cerr << "Loader: Pack checksum is zero (invalid)\n";
return false;
}
std::cout << "Loader: Pack checksum: 0x" << std::hex << checksum << std::dec
<< '\n';
std::cout << "Loader: Pack validation successful\n";
return true;
}
// Load assets.yaml from pack
auto Loader::loadAssetsConfig() const -> std::string {
if (!initialized_ || !resource_pack_ || !resource_pack_->isLoaded()) {
std::cerr << "Loader: Cannot load assets config - pack not loaded\n";
return "";
}
// Try to load config/assets.yaml from pack
std::string config_path = "config/assets.yaml";
if (!resource_pack_->hasResource(config_path)) {
std::cerr << "Loader: assets.yaml not found in pack: " << config_path << '\n';
return "";
}
auto data = resource_pack_->getResource(config_path);
if (data.empty()) {
std::cerr << "Loader: Failed to load assets.yaml from pack\n";
return "";
}
// Convert bytes to string
std::string config_content(data.begin(), data.end());
std::cout << "Loader: Loaded assets.yaml from pack (" << data.size()
<< " bytes)\n";
return config_content;
}
} // namespace Resource

View File

@@ -0,0 +1,48 @@
// resource_loader.hpp
// Singleton resource loader for managing pack and filesystem access
#pragma once
#include <memory>
#include <string>
#include <vector>
#include "resource_pack.hpp"
namespace Resource {
// Singleton class for loading resources from pack or filesystem
class Loader {
public:
static auto get() -> Loader&; // Singleton instance access
auto initialize(const std::string& pack_file, bool enable_fallback = true) -> bool; // Initialize loader with pack file
auto loadResource(const std::string& filename) -> std::vector<uint8_t>; // Load resource data
auto resourceExists(const std::string& filename) -> bool; // Check resource availability
[[nodiscard]] auto isPackLoaded() const -> bool; // Pack status queries
[[nodiscard]] auto getPackResourceCount() const -> size_t;
[[nodiscard]] auto validatePack() const -> bool; // Validate pack integrity
[[nodiscard]] auto loadAssetsConfig() const -> std::string; // Load assets.yaml from pack
void shutdown(); // Cleanup
Loader(const Loader&) = delete; // Deleted copy/move constructors
auto operator=(const Loader&) -> Loader& = delete;
Loader(Loader&&) = delete;
auto operator=(Loader&&) -> Loader& = delete;
private:
Loader() = default;
~Loader() = default;
static auto loadFromFilesystem(const std::string& filepath) -> std::vector<uint8_t>; // Filesystem helpers
static auto fileExistsOnFilesystem(const std::string& filepath) -> bool;
std::unique_ptr<Pack> resource_pack_; // Member variables
bool fallback_to_files_{true};
bool initialized_{false};
};
} // namespace Resource

View File

@@ -0,0 +1,303 @@
// resource_pack.cpp
// Resource pack implementation for JailDoctor's Dilemma
#include "resource_pack.hpp"
#include <SDL3/SDL_filesystem.h>
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iostream>
namespace Resource {
// Calculate CRC32 checksum for data verification
auto Pack::calculateChecksum(const std::vector<uint8_t>& data) -> uint32_t {
uint32_t checksum = 0x12345678;
for (unsigned char byte : data) {
checksum = ((checksum << 5) + checksum) + byte;
}
return checksum;
}
// XOR encryption (symmetric - same function for encrypt/decrypt)
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 is symmetric
encryptData(data, key);
}
// Read entire file into memory
auto Pack::readFile(const std::string& filepath) -> std::vector<uint8_t> {
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file) {
std::cerr << "ResourcePack: Failed to open file: " << 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: Failed to read file: " << filepath << '\n';
return {};
}
return data;
}
// Add a single file to the pack
auto Pack::addFile(const std::string& filepath, const std::string& pack_name)
-> bool {
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)};
// Append file data to the data block
data_.insert(data_.end(), file_data.begin(), file_data.end());
resources_[pack_name] = entry;
std::cout << "Added: " << pack_name << " (" << file_data.size() << " bytes)\n";
return true;
}
// Add all files from a directory recursively
auto Pack::addDirectory(const std::string& dir_path,
const std::string& base_path) -> bool {
namespace fs = std::filesystem;
if (!fs::exists(dir_path) || !fs::is_directory(dir_path)) {
std::cerr << "ResourcePack: Directory not found: " << 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();
// Convert backslashes to forward slashes (Windows compatibility)
std::ranges::replace(relative_path, '\\', '/');
// Skip development files
if (relative_path.find(".world") != std::string::npos ||
relative_path.find(".tsx") != std::string::npos) {
std::cout << "Skipping development file: " << relative_path << '\n';
continue;
}
std::string pack_name = current_base + relative_path;
addFile(full_path, pack_name);
}
return true;
}
// Save the pack to a file
auto Pack::savePack(const std::string& pack_file) -> bool {
std::ofstream file(pack_file, std::ios::binary);
if (!file) {
std::cerr << "ResourcePack: Failed to create pack file: " << pack_file << '\n';
return false;
}
// Write header
file.write(MAGIC_HEADER.data(), MAGIC_HEADER.size());
file.write(reinterpret_cast<const char*>(&VERSION), sizeof(VERSION));
// Write resource count
auto resource_count = static_cast<uint32_t>(resources_.size());
file.write(reinterpret_cast<const char*>(&resource_count), sizeof(resource_count));
// Write resource entries
for (const auto& [name, entry] : resources_) {
// Write filename length and name
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);
// Write offset, size, 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));
}
// Encrypt data
std::vector<uint8_t> encrypted_data = data_;
encryptData(encrypted_data, DEFAULT_ENCRYPT_KEY);
// Write encrypted data size and data
uint64_t data_size = encrypted_data.size();
file.write(reinterpret_cast<const char*>(&data_size), sizeof(data_size));
file.write(reinterpret_cast<const char*>(encrypted_data.data()), data_size);
std::cout << "\nPack saved successfully: " << pack_file << '\n';
std::cout << "Resources: " << resource_count << '\n';
std::cout << "Total size: " << data_size << " bytes\n";
return true;
}
// Load a pack from a file
auto Pack::loadPack(const std::string& pack_file) -> bool {
std::ifstream file(pack_file, std::ios::binary);
if (!file) {
std::cerr << "ResourcePack: Failed to open pack file: " << pack_file << '\n';
return false;
}
// Read and verify header
std::array<char, 4> header{};
file.read(header.data(), header.size());
if (header != MAGIC_HEADER) {
std::cerr << "ResourcePack: Invalid pack header\n";
return false;
}
// Read and verify version
uint32_t version = 0;
file.read(reinterpret_cast<char*>(&version), sizeof(version));
if (version != VERSION) {
std::cerr << "ResourcePack: Unsupported pack version: " << version << '\n';
return false;
}
// Read resource count
uint32_t resource_count = 0;
file.read(reinterpret_cast<char*>(&resource_count), sizeof(resource_count));
// Read resource entries
resources_.clear();
for (uint32_t i = 0; i < resource_count; ++i) {
// Read filename
uint32_t name_len = 0;
file.read(reinterpret_cast<char*>(&name_len), sizeof(name_len));
std::string filename(name_len, '\0');
file.read(filename.data(), name_len);
// Read entry data
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;
}
// Read encrypted data
uint64_t data_size = 0;
file.read(reinterpret_cast<char*>(&data_size), sizeof(data_size));
data_.resize(data_size);
file.read(reinterpret_cast<char*>(data_.data()), data_size);
// Decrypt data
decryptData(data_, DEFAULT_ENCRYPT_KEY);
loaded_ = true;
std::cout << "ResourcePack loaded: " << pack_file << '\n';
std::cout << "Resources: " << resource_count << '\n';
std::cout << "Data size: " << data_size << " bytes\n";
return true;
}
// Get a resource by name
auto Pack::getResource(const std::string& filename) -> std::vector<uint8_t> {
auto it = resources_.find(filename);
if (it == resources_.end()) {
return {};
}
const ResourceEntry& entry = it->second;
// Extract data slice
if (entry.offset + entry.size > data_.size()) {
std::cerr << "ResourcePack: Invalid offset/size for: " << filename << '\n';
return {};
}
std::vector<uint8_t> result(data_.begin() + entry.offset,
data_.begin() + entry.offset + entry.size);
// Verify checksum
uint32_t checksum = calculateChecksum(result);
if (checksum != entry.checksum) {
std::cerr << "ResourcePack: Checksum mismatch for: " << filename << '\n';
std::cerr << " Expected: 0x" << std::hex << entry.checksum << '\n';
std::cerr << " Got: 0x" << std::hex << checksum << std::dec << '\n';
}
return result;
}
// Check if a resource exists
auto Pack::hasResource(const std::string& filename) const -> bool {
return resources_.find(filename) != resources_.end();
}
// Get list of all resources
auto Pack::getResourceList() const -> std::vector<std::string> {
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;
}
// Calculate overall pack checksum for validation
auto Pack::calculatePackChecksum() const -> uint32_t {
if (!loaded_ || data_.empty()) {
return 0;
}
// Combine checksums of all resources for a global checksum
uint32_t global_checksum = 0x87654321;
// Sort resources by name for deterministic checksum
std::vector<std::string> sorted_names;
sorted_names.reserve(resources_.size());
for (const auto& [name, entry] : resources_) {
sorted_names.push_back(name);
}
std::ranges::sort(sorted_names);
// Combine individual checksums
for (const auto& name : sorted_names) {
const auto& entry = resources_.at(name);
global_checksum = ((global_checksum << 5) + global_checksum) + entry.checksum;
global_checksum = ((global_checksum << 5) + global_checksum) + entry.size;
}
return global_checksum;
}
} // namespace Resource

View File

@@ -0,0 +1,68 @@
// resource_pack.hpp
// Resource pack file format and management for JailDoctor's Dilemma
#pragma once
#include <array>
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
namespace Resource {
// Entry metadata for each resource in the pack
struct ResourceEntry {
std::string filename; // Relative path within pack
uint64_t offset{0}; // Byte offset in data block
uint64_t size{0}; // Size in bytes
uint32_t checksum{0}; // CRC32 checksum for verification
};
// Resource pack file format
// Header: "JDDI" (4 bytes) + Version (4 bytes)
// Metadata: Count + array of ResourceEntry
// Data: Encrypted data block
class Pack {
public:
Pack() = default;
~Pack() = default;
Pack(const Pack&) = delete; // Deleted copy/move constructors
auto operator=(const Pack&) -> Pack& = delete;
Pack(Pack&&) = delete;
auto operator=(Pack&&) -> Pack& = delete;
auto addFile(const std::string& filepath, const std::string& pack_name) -> bool; // Building packs
auto addDirectory(const std::string& dir_path, const std::string& base_path = "") -> bool;
auto savePack(const std::string& pack_file) -> bool; // Pack I/O
auto loadPack(const std::string& pack_file) -> bool;
auto getResource(const std::string& filename) -> std::vector<uint8_t>; // Resource access
auto hasResource(const std::string& filename) const -> bool;
auto getResourceList() const -> std::vector<std::string>;
auto isLoaded() const -> bool { return loaded_; } // Status queries
auto getResourceCount() const -> size_t { return resources_.size(); }
auto getDataSize() const -> size_t { return data_.size(); }
auto calculatePackChecksum() const -> uint32_t; // Validation
private:
static constexpr std::array<char, 4> MAGIC_HEADER = {'J', 'D', 'D', 'I'}; // Pack format constants
static constexpr uint32_t VERSION = 1;
static constexpr const char* DEFAULT_ENCRYPT_KEY = "JDDI_RESOURCES_2024";
static auto calculateChecksum(const std::vector<uint8_t>& data) -> uint32_t; // Utility methods
static void encryptData(std::vector<uint8_t>& data, const std::string& key); // Encryption/decryption
static void decryptData(std::vector<uint8_t>& data, const std::string& key);
static auto readFile(const std::string& filepath) -> std::vector<uint8_t>; // File I/O
std::unordered_map<std::string, ResourceEntry> resources_; // Member variables
std::vector<uint8_t> data_; // Encrypted data block
bool loaded_{false};
};
} // namespace Resource

View File

@@ -0,0 +1,62 @@
#pragma once
#include <cstdint> // Para uint8_t
#include <memory> // Para shared_ptr
#include <string> // Para string
#include <vector> // Para vector
#include "core/rendering/surface.hpp" // Para Palette y Surface
#include "core/rendering/text.hpp" // Para Text y Text::File
#include "game/gameplay/room.hpp" // Para Room::Data
// Forward declarations
struct JA_Music_t;
struct JA_Sound_t;
// Estructura para almacenar ficheros de sonido y su nombre
struct SoundResource {
std::string name; // Nombre del sonido
JA_Sound_t* sound{nullptr}; // Objeto con el sonido
};
// Estructura para almacenar ficheros musicales y su nombre
struct MusicResource {
std::string name; // Nombre de la musica
JA_Music_t* music{nullptr}; // Objeto con la música
};
// Estructura para almacenar objetos Surface y su nombre
struct SurfaceResource {
std::string name; // Nombre de la surface
std::shared_ptr<Surface> surface; // Objeto con la surface
};
// Estructura para almacenar objetos Palette y su nombre
struct ResourcePalette {
std::string name; // Nombre de la surface
Palette palette{}; // Paleta
};
// Estructura para almacenar ficheros TextFile y su nombre
struct TextFileResource {
std::string name; // Nombre del fichero
std::shared_ptr<Text::File> text_file; // Objeto con los descriptores de la fuente de texto
};
// Estructura para almacenar objetos Text y su nombre
struct TextResource {
std::string name; // Nombre del objeto
std::shared_ptr<Text> text; // Objeto
};
// Estructura para almacenar ficheros animaciones y su nombre
struct AnimationResource {
std::string name; // Nombre del fichero
std::vector<uint8_t> yaml_data; // Bytes del archivo YAML sin parsear
};
// Estructura para almacenar habitaciones y su nombre
struct RoomResource {
std::string name; // Nombre de la habitación
std::shared_ptr<Room::Data> room; // Habitación
};