diff --git a/CLAUDE.md b/CLAUDE.md index 971072c..69405f4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -228,7 +228,7 @@ JD8_Flip produces ABGR byte order: `0xFF000000 + R + (G<<8) + (B<<16)`. SDL text | `~/.config/jailgames/aee/postfx.yaml` | PostFX shader presets (6 defaults: CRT, NTSC, CURVED, SCANLINES, SUBTLE, CRT LIVE) | | `~/.config/jailgames/aee/crtpi.yaml` | CRT-Pi shader presets (4 defaults: DEFAULT, CURVED, SHARP, MINIMAL) | -### Resource Pack (`source/core/resources/`) — **en construcció** +### Resource Pack (`source/core/resources/`) Sistema d'empaquetat d'assets a l'estil `coffee_crisis_arcade_edition`. Genera un sol fitxer binari opac `resource.pack` que substitueix la carpeta `data/` als releases natius. @@ -249,22 +249,13 @@ Checksum: djb2-like amb seed `0x12345678`. Càrrega full-to-RAM (sense mmap). - `make pack` compila l'eina (target `pack_resources` a `EXCLUDE_FROM_ALL` de [CMakeLists.txt](CMakeLists.txt)) i genera `resource.pack` a la rel. 33 entrades ≈ 4 MB. - `./build/pack_resources --list resource.pack` inspecciona el pack. -**Estat actual (Fase 1 completada, 2026-04-16)**: -- Classes + eina compilen i funcionen. El pack es genera correctament i el `--list` mostra les 33 entrades (totes les GIFs, OGGs, font `.fnt`+`.gif`, `locale/ca.yaml`, `shaders/*`). -- **Encara no està cablejat al joc**: cap callsite usa `ResourceHelper::loadFile`. Tots segueixen cridant `file_readfile`. El joc funciona exactament igual que abans. -- Nou getter `file_getresourcefolder()` afegit a [jfile.hpp](source/core/jail/jfile.hpp) perquè ResourceHelper puga construir el path del fallback. - -**Pendent (Fases 2-6 del pla [.claude/plans/declarative-popping-breeze.md](/home/sergio/.claude/plans/declarative-popping-breeze.md))**: -1. **Fase 2** — Afegir `ResourceHelper::initializeResourceSystem(pack_path, enable_fallback=true)` a [main.cpp](source/main.cpp) just després de `file_setresourcefolder`, i `shutdownResourceSystem()` a `SDL_AppQuit`. -2. **Fase 3** — Migrar callsites `file_readfile` → `ResourceHelper::loadFile` (tipus canvia `std::vector` → `std::vector`): - - [locale.cpp:30](source/core/locale/locale.cpp#L30) - - [text.cpp:65, 129](source/core/rendering/text.cpp) (`.fnt` + bitmap) - - [scene_utils.cpp:12](source/scenes/scene_utils.cpp) (música escenes) - - [modulegame.cpp:52](source/game/modulegame.cpp) (música gameplay) - - [jdraw8.cpp:47, 65](source/core/jail/jdraw8.cpp) (`JD8_LoadSurface`, `JD8_LoadPalette`) -3. **Fase 4** — Eliminar scaffold `.jrf` de [jfile.cpp](source/core/jail/jfile.cpp): `file_setresourcefilename`, `file_setsource`, `SOURCE_FILE`/`SOURCE_FOLDER`, `dictionary_loaded()`, `file_getfilepointer()`, `file_readfile()`. Mantenir només config-folder + `file_setresourcefolder` + `file_getresourcefolder`. -4. **Fase 5** — Als targets release de [Makefile](Makefile) (`_linux_release`/`_windows_release`/`_macos_release`): afegir dependència `pack` i canviar `cp -r data` → `cp resource.pack`. WASM intacte (segueix usant `--preload-file data@/data`). -5. **Fase 6** — A [main.cpp](source/main.cpp): `enable_fallback = false` només per a `NDEBUG && !__EMSCRIPTEN__` (pack obligatori a Release natiu; Debug i WASM mantenen fallback). +**Estat actual (Fases 1-6 completades, 2026-04-16)**: +- `ResourcePack` + `ResourceHelper` + eina `pack_resources` compilen i funcionen. El pack genera 33 entrades ≈ 4 MB. +- Cablejat al joc via `ResourceHelper::initializeResourceSystem` a [main.cpp](source/main.cpp) (amb `return SDL_APP_FAILURE` si falla), i `shutdownResourceSystem` a `SDL_AppQuit`. +- Tots els callsites de recursos usen `ResourceHelper::loadFile` (`std::vector`): [locale.cpp](source/core/locale/locale.cpp), [text.cpp](source/core/rendering/text.cpp), [scene_utils.cpp](source/scenes/scene_utils.cpp), [modulegame.cpp](source/game/modulegame.cpp), [jdraw8.cpp](source/core/jail/jdraw8.cpp). +- Scaffold `.jrf` eliminat de [jfile.cpp](source/core/jail/jfile.cpp): `file_setresourcefilename`, `file_setsource`, `SOURCE_FILE`/`SOURCE_FOLDER`, `dictionary_loaded`, `file_getfilepointer`, `file_readfile`. Només queden config-folder i resource-folder getters/setters. +- Targets release a [Makefile](Makefile) (`_linux_release`/`_windows_release`/`_macos_release`) depenen de `pack` i copien `resource.pack` en lloc de `data/`. WASM intacte (`--preload-file data@/data`). +- `enable_fallback = false` a Release natiu (`NDEBUG && !__EMSCRIPTEN__`): el pack és obligatori. Debug i WASM mantenen el fallback actiu. ### External Libraries (`source/external/`) @@ -317,7 +308,6 @@ Checksum: djb2-like amb seed `0x12345678`. Càrrega full-to-RAM (sense mmap). - **FPS counter jitter**: time segment width changes per frame (100 Hz centi updates) causes ~1-2 px horizontal jitter in centered layout. Could lock to max-width or use monospace digits. - **Notification messages partially hardcoded**: overlay/global_inputs/director now use Locale, but the window title (`Texts::WINDOW_TITLE`) and some game-layer strings remain hardcoded. - **jail_audio `JA_Sound_t` RAII**: `JA_Music_t` ja està net (vector + string), però `JA_Sound_t` encara usa `Uint8*` via `SDL_LoadWAV` out-param. Petit polish per a completar la coherència RAII. -- **Resource Pack — Fases 2-6**: la classe `ResourcePack`, `ResourceHelper` i l'eina `pack_resources` ja estan fetes (Fase 1). Queda cablejar-ho al joc: init a `main.cpp`, migrar 5 callsites de `file_readfile` a `ResourceHelper::loadFile`, eliminar l'scaffold `.jrf` de `jfile`, integrar `resource.pack` als bundles release, i flip `enable_fallback=false` per a Release natiu. Detall complet a la secció *Resource Pack* i al pla [.claude/plans/declarative-popping-breeze.md](/home/sergio/.claude/plans/declarative-popping-breeze.md). ### Previously Fixed (kept for reference) diff --git a/Makefile b/Makefile index ec7c5d5..1eb474d 100644 --- a/Makefile +++ b/Makefile @@ -99,7 +99,7 @@ endif # ============================================================================== # COMPILACIÓN PARA WINDOWS (RELEASE) # ============================================================================== -_windows_release: +_windows_release: pack @echo off @echo Creando release para Windows - Version: $(VERSION) @@ -112,8 +112,8 @@ _windows_release: @powershell -Command "if (Test-Path '$(RELEASE_FOLDER)') {Remove-Item '$(RELEASE_FOLDER)' -Recurse -Force}" @powershell -Command "if (-not (Test-Path '$(RELEASE_FOLDER)')) {New-Item '$(RELEASE_FOLDER)' -ItemType Directory}" -# Copia ficheros - @powershell -Command "Copy-Item -Path 'data' -Destination '$(RELEASE_FOLDER)' -Recurse" +# Copia ficheros (resource.pack substitueix la carpeta data/) + @powershell -Command "Copy-Item 'resource.pack' -Destination '$(RELEASE_FOLDER)'" @powershell -Command "Copy-Item 'LICENSE' -Destination '$(RELEASE_FOLDER)'" @powershell -Command "Copy-Item 'README.md' -Destination '$(RELEASE_FOLDER)'" @powershell -Command "Copy-Item 'gamecontrollerdb.txt' -Destination '$(RELEASE_FOLDER)'" @@ -132,7 +132,7 @@ _windows_release: # ============================================================================== # COMPILACIÓN PARA MACOS (RELEASE) # ============================================================================== -_macos_release: +_macos_release: pack @echo "Creando release para macOS - Version: $(VERSION)" # Verificar e instalar create-dmg si es necesario @@ -154,8 +154,8 @@ _macos_release: $(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS" $(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources" -# Copia carpetas y ficheros - cp -R data "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources" +# Copia carpetas y ficheros (resource.pack substitueix la carpeta data/) + cp resource.pack "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources" cp gamecontrollerdb.txt "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources" cp -R release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64/SDL3.framework "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks" cp release/icons/*.icns "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources" @@ -252,7 +252,7 @@ wasm: # ============================================================================== # COMPILACIÓN PARA LINUX (RELEASE) # ============================================================================== -_linux_release: +_linux_release: pack @echo "Creando release para Linux - Version: $(VERSION)" # Compila con cmake @@ -263,8 +263,8 @@ _linux_release: $(RMDIR) "$(RELEASE_FOLDER)" $(MKDIR) "$(RELEASE_FOLDER)" -# Copia ficheros - cp -r data "$(RELEASE_FOLDER)" +# Copia ficheros (resource.pack substitueix la carpeta data/) + cp resource.pack "$(RELEASE_FOLDER)" cp LICENSE "$(RELEASE_FOLDER)" cp README.md "$(RELEASE_FOLDER)" cp gamecontrollerdb.txt "$(RELEASE_FOLDER)" diff --git a/resource.pack b/resource.pack index bc4909f..469bd28 100644 Binary files a/resource.pack and b/resource.pack differ diff --git a/source/core/jail/jdraw8.cpp b/source/core/jail/jdraw8.cpp index 873528f..354df25 100644 --- a/source/core/jail/jdraw8.cpp +++ b/source/core/jail/jdraw8.cpp @@ -2,7 +2,7 @@ #include -#include "core/jail/jfile.hpp" +#include "core/resources/resource_helper.hpp" #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-but-set-variable" @@ -44,10 +44,10 @@ JD8_Surface JD8_NewSurface() { } JD8_Surface JD8_LoadSurface(const char* file) { - auto buffer = file_readfile(file); + auto buffer = ResourceHelper::loadFile(file); unsigned short w, h; - Uint8* pixels = LoadGif(reinterpret_cast(buffer.data()), &w, &h); + Uint8* pixels = LoadGif(buffer.data(), &w, &h); if (pixels == NULL) { printf("Unable to load bitmap: %s\n", SDL_GetError()); @@ -62,8 +62,8 @@ JD8_Surface JD8_LoadSurface(const char* file) { } JD8_Palette JD8_LoadPalette(const char* file) { - auto buffer = file_readfile(file); - return (JD8_Palette)LoadPalette(reinterpret_cast(buffer.data())); + auto buffer = ResourceHelper::loadFile(file); + return (JD8_Palette)LoadPalette(buffer.data()); } void JD8_SetScreenPalette(JD8_Palette palette) { diff --git a/source/core/jail/jfile.cpp b/source/core/jail/jfile.cpp index 444c6b4..e63972a 100644 --- a/source/core/jail/jfile.cpp +++ b/source/core/jail/jfile.cpp @@ -1,15 +1,10 @@ #include "core/jail/jfile.hpp" -#include -#include -#include -#include #include #include #include #include -#include #include #include @@ -19,58 +14,14 @@ namespace { -constexpr const char* DEFAULT_FILENAME = "data.jf2"; -constexpr const char* DEFAULT_FOLDER = "data/"; - -struct file_entry { - std::string path; - uint32_t size; - uint32_t offset; -}; - struct keyvalue { std::string key; std::string value; }; -std::vector toc; std::vector config; - -std::string resource_filename; std::string resource_folder; std::string config_folder; -int file_source = SOURCE_FILE; - -bool dictionary_loaded() { - if (resource_filename.empty()) resource_filename = DEFAULT_FILENAME; - - std::ifstream fi(resource_filename, std::ios::binary); - if (!fi.is_open()) return false; - - char header[4]; - fi.read(header, 4); - uint32_t num_files, toc_offset; - fi.read(reinterpret_cast(&num_files), 4); - fi.read(reinterpret_cast(&toc_offset), 4); - fi.seekg(toc_offset); - - for (uint32_t i = 0; i < num_files; ++i) { - uint32_t file_offset, file_size; - fi.read(reinterpret_cast(&file_offset), 4); - fi.read(reinterpret_cast(&file_size), 4); - uint8_t path_size; - fi.read(reinterpret_cast(&path_size), 1); - char file_name[256]; - fi.read(file_name, path_size); - file_name[path_size] = 0; - toc.push_back({std::string(file_name), file_size, file_offset}); - } - return true; -} - -std::string filename_with_folder(const char* filename) { - return resource_folder + filename; -} void load_config_values() { config.clear(); @@ -97,10 +48,6 @@ void save_config_values() { } // namespace -void file_setresourcefilename(const char* str) { - resource_filename = str; -} - void file_setresourcefolder(const char* str) { resource_folder = str; } @@ -109,58 +56,6 @@ const char* file_getresourcefolder() { return resource_folder.c_str(); } -void file_setsource(const int src) { - file_source = src % 2; - if (src == SOURCE_FOLDER && resource_folder.empty()) file_setresourcefolder(DEFAULT_FOLDER); -} - -FILE* file_getfilepointer(const char* resourcename, int& filesize, const bool binary) { - if (file_source == SOURCE_FILE && toc.empty()) { - if (!dictionary_loaded()) file_setsource(SOURCE_FOLDER); - } - - FILE* f = nullptr; - - if (file_source == SOURCE_FILE) { - const std::string name(resourcename); - size_t count = 0; - for (; count < toc.size(); ++count) { - if (toc[count].path == name) break; - } - if (count == toc.size()) { - perror("El recurs no s'ha trobat en l'arxiu de recursos"); - exit(1); - } - - filesize = static_cast(toc[count].size); - - f = fopen(resource_filename.c_str(), binary ? "rb" : "r"); - if (!f) { - perror("No s'ha pogut obrir l'arxiu de recursos"); - exit(1); - } - fseek(f, toc[count].offset, SEEK_SET); - } else { - const std::string full = filename_with_folder(resourcename); - f = fopen(full.c_str(), binary ? "rb" : "r"); - if (!f) return nullptr; - fseek(f, 0, SEEK_END); - filesize = static_cast(ftell(f)); - fseek(f, 0, SEEK_SET); - } - return f; -} - -std::vector file_readfile(const char* resourcename) { - int filesize = 0; - FILE* f = file_getfilepointer(resourcename, filesize, true); - if (!f) return {}; - std::vector buffer(filesize); - fread(buffer.data(), filesize, 1, f); - fclose(f); - return buffer; -} - // Crea la carpeta del sistema on guardar les dades. // Accepta rutes amb subdirectoris (ex: "jailgames/aee") i crea tota la jerarquia. void file_setconfigfolder(const char* foldername) { diff --git a/source/core/jail/jfile.hpp b/source/core/jail/jfile.hpp index 35b190a..43bd467 100644 --- a/source/core/jail/jfile.hpp +++ b/source/core/jail/jfile.hpp @@ -1,26 +1,10 @@ #pragma once -#include - -#include - -#define SOURCE_FILE 0 -#define SOURCE_FOLDER 1 void file_setconfigfolder(const char* foldername); const char* file_getconfigfolder(); -void file_setresourcefilename(const char* str); void file_setresourcefolder(const char* str); const char* file_getresourcefolder(); -void file_setsource(const int src); - -FILE* file_getfilepointer(const char* resourcename, int& filesize, const bool binary = false); - -// Llig tot el contingut d'un recurs (fitxer solt o entrada del .jrf). -// Retorna un vector buit si el recurs no existeix. El vector es destrueix -// automàticament en eixir d'àmbit — no fa falta cap free() manual. Mida = -// bytes llegits (el buffer no està null-terminated). -std::vector file_readfile(const char* resourcename); const char* file_getconfigvalue(const char* key); void file_setconfigvalue(const char* key, const char* value); diff --git a/source/core/locale/locale.cpp b/source/core/locale/locale.cpp index a05edb9..e4958fa 100644 --- a/source/core/locale/locale.cpp +++ b/source/core/locale/locale.cpp @@ -4,7 +4,7 @@ #include #include -#include "core/jail/jfile.hpp" +#include "core/resources/resource_helper.hpp" #include "external/fkyaml_node.hpp" namespace Locale { @@ -27,12 +27,12 @@ namespace Locale { } bool load(const char* filename) { - auto buffer = file_readfile(filename); + auto buffer = ResourceHelper::loadFile(filename); if (buffer.empty()) { std::cerr << "Locale: unable to load " << filename << '\n'; return false; } - std::string content(buffer.data(), buffer.size()); + std::string content(reinterpret_cast(buffer.data()), buffer.size()); try { auto yaml = fkyaml::node::deserialize(content); diff --git a/source/core/rendering/text.cpp b/source/core/rendering/text.cpp index 5341aa6..89e836e 100644 --- a/source/core/rendering/text.cpp +++ b/source/core/rendering/text.cpp @@ -8,7 +8,7 @@ #include #include -#include "core/jail/jfile.hpp" +#include "core/resources/resource_helper.hpp" // Forward declarations de gif.h (inclòs des de jdraw8.cpp, no es pot incloure dos vegades) struct rgb; @@ -62,13 +62,13 @@ auto Text::nextCodepoint(const char*& ptr) -> uint32_t { // --- Càrrega de font --- void Text::loadFont(const char* fnt_file) { - auto buffer = file_readfile(fnt_file); + auto buffer = ResourceHelper::loadFile(fnt_file); if (buffer.empty()) { std::cerr << "Text: unable to load font file: " << fnt_file << '\n'; return; } - std::istringstream stream(std::string(buffer.data(), buffer.size())); + std::istringstream stream(std::string(reinterpret_cast(buffer.data()), buffer.size())); std::string line; int glyph_index = 0; @@ -126,14 +126,14 @@ void Text::loadFont(const char* fnt_file) { } void Text::loadBitmap(const char* gif_file) { - auto buffer = file_readfile(gif_file); + auto buffer = ResourceHelper::loadFile(gif_file); if (buffer.empty()) { std::cerr << "Text: unable to load bitmap: " << gif_file << '\n'; return; } // Extrau dimensions del header GIF (bytes 6-7 = width, 8-9 = height, little-endian) - auto* raw = reinterpret_cast(buffer.data()); + auto* raw = buffer.data(); int w = raw[6] | (raw[7] << 8); int h = raw[8] | (raw[9] << 8); diff --git a/source/game/modulegame.cpp b/source/game/modulegame.cpp index 951afeb..fe5d29d 100644 --- a/source/game/modulegame.cpp +++ b/source/game/modulegame.cpp @@ -2,9 +2,9 @@ #include "core/jail/jail_audio.hpp" #include "core/jail/jdraw8.hpp" -#include "core/jail/jfile.hpp" #include "core/jail/jgame.hpp" #include "core/jail/jinput.hpp" +#include "core/resources/resource_helper.hpp" ModuleGame::ModuleGame() { this->gfx = JD8_LoadSurface(info::ctx.pepe_activat ? "frames2.gif" : "frames.gif"); @@ -49,8 +49,8 @@ void ModuleGame::onEnter() { const char* current_music = JA_GetMusicFilename(); if ((JA_GetMusicState() != JA_MUSIC_PLAYING) || !current_music || strcmp(music, current_music) != 0) { - auto buffer = file_readfile(music); - JA_PlayMusic(JA_LoadMusic(reinterpret_cast(buffer.data()), + auto buffer = ResourceHelper::loadFile(music); + JA_PlayMusic(JA_LoadMusic(buffer.data(), static_cast(buffer.size()), music)); } diff --git a/source/main.cpp b/source/main.cpp index 99502dc..ef74dff 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -19,6 +19,7 @@ #include "core/rendering/menu.hpp" #include "core/rendering/overlay.hpp" #include "core/rendering/screen.hpp" +#include "core/resources/resource_helper.hpp" #include "core/system/director.hpp" #include "game/options.hpp" @@ -32,10 +33,27 @@ SDL_AppResult SDL_AppInit(void** /*appstate*/, int /*argc*/, char* /*argv*/[]) { // SDL_GetBasePath() detecta automàticament si estem dins d'un .app bundle // (retorna Contents/Resources/) o en un executable normal (carpeta del binari). const char* base_path = SDL_GetBasePath(); + std::string resource_pack_path; if (base_path) { const std::string data_path = std::string(base_path) + "data/"; file_setresourcefolder(data_path.c_str()); + resource_pack_path = std::string(base_path) + "resource.pack"; + } else { + resource_pack_path = "resource.pack"; } + + // Sistema de recursos: prova el pack i cau a fitxers solts dins data/. + // Release natiu exigix el pack (sense fallback); Debug i WASM mantenen + // el fallback actiu per a desenvolupament i per al build amb MEMFS. +#if defined(NDEBUG) && !defined(__EMSCRIPTEN__) + const bool enable_fallback = false; +#else + const bool enable_fallback = true; +#endif + if (!ResourceHelper::initializeResourceSystem(resource_pack_path, enable_fallback)) { + return SDL_APP_FAILURE; + } + Options::setConfigFile(std::string(file_getconfigfolder()) + "config.yaml"); Options::loadFromFile(); @@ -102,4 +120,5 @@ void SDL_AppQuit(void* /*appstate*/, SDL_AppResult /*result*/) { JD8_Quit(); Screen::destroy(); JG_Finalize(); + ResourceHelper::shutdownResourceSystem(); } diff --git a/source/scenes/scene_utils.cpp b/source/scenes/scene_utils.cpp index babb16f..1d689f2 100644 --- a/source/scenes/scene_utils.cpp +++ b/source/scenes/scene_utils.cpp @@ -3,17 +3,17 @@ #include #include "core/jail/jail_audio.hpp" -#include "core/jail/jfile.hpp" +#include "core/resources/resource_helper.hpp" namespace scenes { void playMusic(const char* filename, int loop) { if (!filename) return; - auto buffer = file_readfile(filename); + auto buffer = ResourceHelper::loadFile(filename); if (buffer.empty()) return; // JA_LoadMusic fa una còpia interna del OGG comprimit (via SDL_malloc) // per a stb_vorbis. El `buffer` local es destruirà en sortir d'àmbit. - JA_PlayMusic(JA_LoadMusic(reinterpret_cast(buffer.data()), + JA_PlayMusic(JA_LoadMusic(buffer.data(), static_cast(buffer.size()), filename), loop); }