From 4da05b3d76570499d389cdcfca6630cf67805172 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 23 Apr 2026 08:51:21 +0200 Subject: [PATCH] =?UTF-8?q?chore(release):=20v0.21.0=20=E2=80=94=20empaque?= =?UTF-8?q?tat=20de=20recursos=20(AE26)=20i=20neteja=20de=20fonts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Nou format de pack AE26 v1 (XOR + djb2), eina `pack_resources`, regeneració automàtica de `build/resources.pack` a cada build. - `Resource::Helper` amb init/shutdown, fallback a disc només en Debug (apunta al source tree); Release exigeix pack present. - Callers de YAML migrats a `loadFile`+deserialize des de buffer. - Arrel neta: el pack viu a `build/`, eliminat `sync_data`. - Placeholder font -> `ari-w9500-condensed-display`; esborrades variants bold/condensed no usades a `data/fonts/`. --- CMakeLists.txt | 46 +++-- Makefile | 13 +- data/fonts/ari-w9500-bold.fnt | 132 -------------- data/fonts/ari-w9500-bold.gif | Bin 2303 -> 0 bytes data/fonts/ari-w9500-condensed-bold.fnt | 132 -------------- data/fonts/ari-w9500-condensed-bold.gif | Bin 2232 -> 0 bytes data/fonts/ari-w9500-condensed.fnt | 132 -------------- data/fonts/ari-w9500-condensed.gif | Bin 2019 -> 0 bytes data/fonts/ari-w9500.fnt | 132 -------------- data/fonts/ari-w9500.gif | Bin 2197 -> 0 bytes source/core/input/key_config.cpp | 17 +- source/core/locale/locale.cpp | 15 +- source/core/resources/resource_helper.cpp | 73 ++++++-- source/core/resources/resource_helper.hpp | 36 ++-- source/core/resources/resource_pack.cpp | 209 ++++++++++++++++++++++ source/core/resources/resource_pack.hpp | 63 +++++++ source/core/system/director.cpp | 24 ++- source/game/entities/entity_loader.cpp | 24 +-- source/game/entities/player.cpp | 14 +- source/game/map/map.cpp | 16 +- source/game/map/tilemap.cpp | 15 +- source/utils/defaults.hpp | 6 +- source/utils/defines.hpp | 2 +- tools/pack_resources/pack_resources.cpp | 98 ++++++++++ 24 files changed, 574 insertions(+), 625 deletions(-) delete mode 100644 data/fonts/ari-w9500-bold.fnt delete mode 100644 data/fonts/ari-w9500-bold.gif delete mode 100644 data/fonts/ari-w9500-condensed-bold.fnt delete mode 100644 data/fonts/ari-w9500-condensed-bold.gif delete mode 100644 data/fonts/ari-w9500-condensed.fnt delete mode 100644 data/fonts/ari-w9500-condensed.gif delete mode 100644 data/fonts/ari-w9500.fnt delete mode 100644 data/fonts/ari-w9500.gif create mode 100644 source/core/resources/resource_pack.cpp create mode 100644 source/core/resources/resource_pack.hpp create mode 100644 tools/pack_resources/pack_resources.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8085f2d..e17a016 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,7 @@ set(APP_SOURCES source/core/rendering/sdl3gpu/pipelines/crtpi_pipeline.cpp source/core/resources/resource_helper.cpp source/core/resources/resource_cache.cpp + source/core/resources/resource_pack.cpp source/core/resources/palette_loader.cpp source/utils/easing.cpp source/utils/utils.cpp @@ -160,24 +161,43 @@ target_include_directories(${PROJECT_NAME} PRIVATE target_link_libraries(${PROJECT_NAME} PRIVATE SDL3::SDL3) -# Còpia recursiva de data/ al costat del binari perquè les rutes relatives -# ("data/player/player.gif") funcionin sense desplegament. Es defineix com -# a custom target amb `ALL` — s'executa a cada `cmake --build` encara que -# el binari no hagi canviat, de manera que editar YAML/GIF sense tocar codi -# igualment sincronitza el build. Abans era un POST_BUILD, i si només es -# modificava una dada la còpia no es disparava. -add_custom_target(sync_data ALL - COMMAND ${CMAKE_COMMAND} -E copy_directory - "${CMAKE_SOURCE_DIR}/data" - "${CMAKE_BINARY_DIR}/data" - COMMENT "Copying data/ next to the binary" +# --- EINA STANDALONE: pack_resources --- +# Executable auxiliar que empaqueta `data/` a `build/resources.pack`. +# EXCLUDE_FROM_ALL: només es compila quan algun target en depèn (ho fa +# `resource_pack`). Build manual: `cmake --build build --target pack_resources`. +add_executable(pack_resources EXCLUDE_FROM_ALL + tools/pack_resources/pack_resources.cpp + source/core/resources/resource_pack.cpp ) -add_dependencies(${PROJECT_NAME} sync_data) +target_include_directories(pack_resources PRIVATE "${CMAKE_SOURCE_DIR}/source") +target_compile_options(pack_resources PRIVATE -Wall -Wextra -Wpedantic) + +# --- REGENERACIÓ AUTOMÀTICA DE build/resources.pack --- +# A cada build re-empaquetem data/ si algun fitxer dins ha canviat. Evita +# debugar amb un pack obsolet. CONFIGURE_DEPENDS força CMake a re-globbar +# a la pròxima invocació (recull fitxers nous afegits a data/). +file(GLOB_RECURSE DATA_FILES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/data/*") +set(RESOURCE_PACK "${CMAKE_BINARY_DIR}/resources.pack") + +add_custom_command( + OUTPUT ${RESOURCE_PACK} + COMMAND $ + "${CMAKE_SOURCE_DIR}/data" + "${RESOURCE_PACK}" + DEPENDS pack_resources ${DATA_FILES} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Empaquetant data/ → build/resources.pack" + VERBATIM +) +add_custom_target(resource_pack ALL DEPENDS ${RESOURCE_PACK}) +add_dependencies(${PROJECT_NAME} resource_pack) target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic) target_compile_definitions(${PROJECT_NAME} PRIVATE $<$:_DEBUG> $<$:RELEASE_BUILD> + # En Debug el fallback a disc apunta al source tree (sense re-packejar). + AEE_SOURCE_DIR="${CMAKE_SOURCE_DIR}/" ) # --- STATIC ANALYSIS / FORMAT TARGETS --- @@ -271,6 +291,7 @@ if(BUILD_TESTS) add_executable(test_entities_smoke tests/test_entities_smoke.cpp source/core/resources/resource_helper.cpp + source/core/resources/resource_pack.cpp source/game/map/map.cpp ${ENTITY_SOURCES} ) @@ -295,6 +316,7 @@ if(BUILD_TESTS) source/game/scenes/placeholder_input.cpp source/core/input/key_config.cpp source/core/resources/resource_helper.cpp + source/core/resources/resource_pack.cpp ) target_include_directories(test_scene_flow PRIVATE "${CMAKE_SOURCE_DIR}/source" diff --git a/Makefile b/Makefile index eb38c4f..3d2678d 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ endif CMAKE_DEFS := -DGIT_HASH=$(GIT_HASH) -.PHONY: all debug release run run-debug clean rebuild show-version format format-check tidy tidy-fix cppcheck hooks-install help +.PHONY: all debug release run run-debug clean rebuild show-version pack format format-check tidy tidy-fix cppcheck hooks-install help # ============================================================================== # COMPILACIÓ @@ -53,6 +53,14 @@ clean: rebuild: clean all +# Empaqueta data/ a $(BUILDDIR)/resources.pack (format AE26). Força un rebuild +# del pack encara que res no hagi canviat dins data/. L'empaquetat també es fa +# automàticament a cada `make`/`make debug` via el target CMake `resource_pack`. +pack: + @cmake -S . -B $(BUILDDIR) -DCMAKE_BUILD_TYPE=Release $(CMAKE_DEFS) + @cmake --build $(BUILDDIR) --target pack_resources + @./$(BUILDDIR)/pack_resources data $(BUILDDIR)/resources.pack + # ============================================================================== # CODE QUALITY (delegados a cmake) # ============================================================================== @@ -100,6 +108,9 @@ help: @echo " Release:" @echo " make release - Release + empaquetat (TODO pendent)" @echo "" + @echo " Recursos:" + @echo " make pack - Empaquetar data/ a $(BUILDDIR)/resources.pack (format AE26)" + @echo "" @echo " Qualitat de codi:" @echo " make format - Formatar codi amb clang-format" @echo " make format-check - Verificar format sense modificar" diff --git a/data/fonts/ari-w9500-bold.fnt b/data/fonts/ari-w9500-bold.fnt deleted file mode 100644 index 066e489..0000000 --- a/data/fonts/ari-w9500-bold.fnt +++ /dev/null @@ -1,132 +0,0 @@ -# Font: ari-w9500-bold — generado desde ari-w9500-bold.ttf size 11 -# Generado con tools/font_gen/font_gen.py - -box_width 13 -box_height 11 -columns 15 - -# codepoint_decimal ancho_visual -32 3 # U+0020 -33 3 # ! -34 6 # " -35 9 # # -36 7 # $ -37 12 # % -38 8 # & -39 3 # ' -40 5 # ( -41 5 # ) -42 5 # * -43 7 # + -44 3 # , -45 5 # - -46 3 # . -47 6 # / -48 7 # 0 -49 6 # 1 -50 7 # 2 -51 7 # 3 -52 7 # 4 -53 7 # 5 -54 7 # 6 -55 7 # 7 -56 7 # 8 -57 7 # 9 -58 3 # : -59 3 # ; -60 7 # < -61 7 # = -62 7 # > -63 7 # ? -64 13 # @ -65 9 # A -66 8 # B -67 8 # C -68 8 # D -69 7 # E -70 7 # F -71 8 # G -72 8 # H -73 3 # I -74 6 # J -75 8 # K -76 7 # L -77 10 # M -78 9 # N -79 9 # O -80 8 # P -81 9 # Q -82 8 # R -83 8 # S -84 7 # T -85 8 # U -86 8 # V -87 11 # W -88 9 # X -89 9 # Y -90 9 # Z -91 5 # [ -92 6 # \ -93 5 # ] -94 6 # ^ -95 7 # _ -96 4 # ` -97 7 # a -98 7 # b -99 7 # c -100 7 # d -101 7 # e -102 5 # f -103 7 # g -104 7 # h -105 3 # i -106 3 # j -107 7 # k -108 3 # l -109 9 # m -110 7 # n -111 7 # o -112 7 # p -113 7 # q -114 5 # r -115 6 # s -116 5 # t -117 7 # u -118 7 # v -119 9 # w -120 7 # x -121 7 # y -122 7 # z -123 6 # { -124 3 # | -125 6 # } -126 8 # ~ -192 9 # À -193 9 # Á -200 7 # È -201 7 # É -204 3 # Ì -205 3 # Í -210 9 # Ò -211 9 # Ó -217 8 # Ù -218 8 # Ú -209 9 # Ñ -199 8 # Ç -224 7 # à -225 7 # á -232 7 # è -233 7 # é -236 3 # ì -237 4 # í -242 7 # ò -243 7 # ó -249 7 # ù -250 7 # ú -241 7 # ñ -231 7 # ç -161 3 # ¡ -191 6 # ¿ -171 9 # « -187 9 # » -183 3 # · diff --git a/data/fonts/ari-w9500-bold.gif b/data/fonts/ari-w9500-bold.gif deleted file mode 100644 index 4ca1fc68964ffb36cfd9026dbac6b9505b24b44a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2303 zcmeH{{XY|k1IOLPhHjJcyzRD$ ziDg3;IWWuXd6g{1OTN~Q9D z`LF#;;Qx*Q_-o^@TX0cRs(hnn=y5wLQ)QQq@y=$ucTaOPHH-@xcHg^Y=@=b5&lo%i zCF#3fTkzo4iztUpis8Zdy1rXB=GXce+Lt}bPB?SNnB1m-0fDx<>r<-roF}KEr;$zk}(n$AGF3! zNZ#VtKTZnoE1L#RwZ0Lu1$ACs{Htkw=(p~Rh7)FMBdb-GjgpAR|GkN!HCp;<*ZPXf zJGE6u(5s@{$eZ2rVIXSBG>~$!PqrGGT^*k`MglDE2IlPssTn1;L^m4eyNKK(@|t^B zr*3F>KMyUM6tsnhWkZCZF|=$v51`~uDGf_z+~e8G;Qkw1<(U;HW4|G$8l&2 z>QID5rPth)TjI}JpfFh}0a1WyRi|~%ch#cV_VKgMA`E>x>v2SjZ9W*qo03&>*qh`6 z76q>tCJ)Di;FkL!8K-H_>{O{1kHCw0NRE8h+a$Ird4yHhf!tUULw6KBWPRsZV8b~2 z9T6v54-T>tPab=5Ogan-59kWr6i3vqtpVQAoLHlFLbH8^Ywd}V{hve{-zxRjicyqK z_Q)`z@W3tm7iZQPQRfUBr(Eu(+q_-k+c-{=oF1*b;k#>ggp;SiPW;%3Gm6;+_gTw1 z_D4hFd^3U2JZ?6s&?l2^uOSzbE}aUMyIF1yJ@2{szH>U8R=l~?3T3eAUa#Zg(#4f} zs4LQdM!DbJKH!$W^twJ|rl=&wxBN=EI-=tvw;$;X;kZhVA_`a(KwnAfc^@xZ0_;b(`Q)*zr>a^?=Do_aOa! z6?S;7^x~f7hO>K;7U7B9h7z_B4+AbA%Cm0nNJgM%n0WH5QI0P`0*`9xz+M=2KAC`w!<%Te87yT#4 z^jeUJ@ib?Ia|?Cmd(~5Zy#~0$_|0=+w>SKFa6t=vSAFg7tMxtmAsig~P zzG4!%WQYrNhAM3ANQOw2&T>=E-l8~tJ_;nwZBrj-VI5!817ny~2i<)j8ix!%r-)$E zrT(;M8dTX7-F5G;pOEOD$n~1*QR;8@6jwJ(L(RD+3UA@=F| zRC5r;nY~CEkPjACIW%kOGJ|{q-}E{;IwTFuqk2zv#U6>{)=iIC*uzGkNo%b;)zwr^ F{{xsq266xZ diff --git a/data/fonts/ari-w9500-condensed-bold.fnt b/data/fonts/ari-w9500-condensed-bold.fnt deleted file mode 100644 index 178b56b..0000000 --- a/data/fonts/ari-w9500-condensed-bold.fnt +++ /dev/null @@ -1,132 +0,0 @@ -# Font: ari-w9500-condensed-bold — generado desde ari-w9500-condensed-bold.ttf size 11 -# Generado con tools/font_gen/font_gen.py - -box_width 12 -box_height 11 -columns 15 - -# codepoint_decimal ancho_visual -32 2 # U+0020 -33 3 # ! -34 6 # " -35 9 # # -36 6 # $ -37 10 # % -38 7 # & -39 3 # ' -40 5 # ( -41 5 # ) -42 5 # * -43 7 # + -44 3 # , -45 5 # - -46 3 # . -47 5 # / -48 6 # 0 -49 5 # 1 -50 6 # 2 -51 6 # 3 -52 6 # 4 -53 6 # 5 -54 6 # 6 -55 6 # 7 -56 6 # 8 -57 6 # 9 -58 3 # : -59 3 # ; -60 7 # < -61 7 # = -62 7 # > -63 6 # ? -64 12 # @ -65 8 # A -66 7 # B -67 7 # C -68 7 # D -69 6 # E -70 6 # F -71 7 # G -72 7 # H -73 3 # I -74 6 # J -75 7 # K -76 7 # L -77 8 # M -78 8 # N -79 8 # O -80 7 # P -81 8 # Q -82 7 # R -83 7 # S -84 7 # T -85 7 # U -86 7 # V -87 9 # W -88 7 # X -89 7 # Y -90 7 # Z -91 5 # [ -92 5 # \ -93 5 # ] -94 6 # ^ -95 6 # _ -96 4 # ` -97 6 # a -98 6 # b -99 6 # c -100 6 # d -101 6 # e -102 5 # f -103 6 # g -104 6 # h -105 3 # i -106 3 # j -107 6 # k -108 3 # l -109 8 # m -110 6 # n -111 6 # o -112 6 # p -113 6 # q -114 5 # r -115 6 # s -116 5 # t -117 6 # u -118 6 # v -119 8 # w -120 6 # x -121 6 # y -122 6 # z -123 6 # { -124 3 # | -125 6 # } -126 8 # ~ -192 8 # À -193 8 # Á -200 6 # È -201 6 # É -204 3 # Ì -205 3 # Í -210 8 # Ò -211 8 # Ó -217 7 # Ù -218 7 # Ú -209 8 # Ñ -199 7 # Ç -224 6 # à -225 6 # á -232 6 # è -233 6 # é -236 3 # ì -237 4 # í -242 6 # ò -243 6 # ó -249 6 # ù -250 6 # ú -241 6 # ñ -231 6 # ç -161 3 # ¡ -191 6 # ¿ -171 9 # « -187 9 # » -183 3 # · diff --git a/data/fonts/ari-w9500-condensed-bold.gif b/data/fonts/ari-w9500-condensed-bold.gif deleted file mode 100644 index 63c81c57ebe75ae9452a76cbb7ed2e91e8621955..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2232 zcmeH``8yK~0LRxuq8!m%G9n2xS5ca?_i-efRPK9nk4UdkJ5UPEM$4JGhqag^_PEKB z+R(IVb00Zk5~bH1FYSGL|Bbir58v<4pU?ArZSAf=F5RL70stw1Ul9t0|LyPkZQy@r zK<5|nuP^*5E(9D9SGpSI_W*G4h_YzixzesADG6okWumGLUP2Ar?k-zqm8N1Gt7KPS z&U|+II@J0(hs?+pv$4b?>VxdFAu1ea#E;;YN;;dD5H8nfbB>jA&Q#nBf>U)<1iuo-Bqo@k27-~HS8Cc?4aaUH#yIuhxUZ{E@` zsofOzsSGvWcyU0k%9lV;fhDg(4CZ>1kB3j&Z|E!yl^ieLRBAQt z>9vqF@Q>)JYcu#d@sa<9*m+yS9VZe|bux8#z%W`Y>NWYM>-bI47ftIl^P@~C7VK2xmdF6xx+ ztssc>$wI=>dbd0j+T*^O*D_-1Q|i^C=J|{>a-*|}iTrzKodrptnQU^B`Yoc-E_I#= zHR7uij5Br?ubp@0`(^q2^MkSQv=N}Kr6WV#%1pp0FT!-%EfjQ}Iw(tWY8+caBzW>Q zu|4b&?`JmgCn+!{A{2xQtUys6TDb}#<(a`yGBsqwt}?IG0;EwgWz$z_-BBuZ(~2T+ zQCzqe5EouqU_wz2dcj)Oj+GlHspR#cS8i47rH`(9H;wgcRlkzX4}9DX9iY^;Om70= zfSet8P1;&&buKUhHbN~%PmE@+QFtX)7fAp3dlp)l=8^FNOi)1~o1y&zGVsPu#&&7-5$m z#~JLMrWnC68L#|~o2fv#oG{&5v1II8^H3t`?<$=#Fzth`h?0KRg?TQbL)VD_;+p&K z(R&;uv^Rdm-K}9k?hP-kfs}=Hsx9u`rGEOGBbNevIQ* z+Wl9&)Mzg3%Q8Tn*$5#5@l|2iVw-|X*Wi!F7OeY!nU3+=o{qmX_@XMd-wz^-|)8`8S_;=I} z9M$Lz*hL_o(ol&QGB^Mo_(^lk72N?R+L{*ceGGanD2#|@m`cY>GP?7PZ-fav<=GoX zjS>zz(3w_7rXR1VCx+lXmFgiOGTJ6BO^lWxT^kdU4t4rQyo-cnIpR1mJsO;+x3}=6 zM~@>lYh2h#rnkw1MlY;|*uI!YiEea`;afGm_m7NwW|LVU>Prc&Cp>(9VphuKLync6 zOpe<)g<%Vpt+ovP7_hNz)hF{;c+zG2z#K^h%TM3&DXHl4Z@wc|?s;p5fljQ+Jy7>G zmiU9=s&eFHQx{xU|FMq!BL4;iDiX=Y)J;wu@$u-V9ngG3nTy8jbY%(j>L%ZW>cLRqw<}6_pqT}=3&mzyE1a% -63 5 # ? -64 9 # @ -65 6 # A -66 6 # B -67 6 # C -68 6 # D -69 6 # E -70 6 # F -71 6 # G -72 6 # H -73 2 # I -74 4 # J -75 6 # K -76 6 # L -77 6 # M -78 6 # N -79 7 # O -80 6 # P -81 7 # Q -82 6 # R -83 6 # S -84 6 # T -85 6 # U -86 6 # V -87 8 # W -88 6 # X -89 6 # Y -90 6 # Z -91 4 # [ -92 4 # \ -93 4 # ] -94 6 # ^ -95 5 # _ -96 3 # ` -97 5 # a -98 5 # b -99 5 # c -100 5 # d -101 5 # e -102 4 # f -103 5 # g -104 5 # h -105 2 # i -106 2 # j -107 5 # k -108 2 # l -109 6 # m -110 5 # n -111 5 # o -112 5 # p -113 5 # q -114 4 # r -115 5 # s -116 4 # t -117 5 # u -118 5 # v -119 6 # w -120 5 # x -121 5 # y -122 5 # z -123 5 # { -124 2 # | -125 5 # } -126 6 # ~ -192 6 # À -193 6 # Á -200 6 # È -201 6 # É -204 2 # Ì -205 2 # Í -210 7 # Ò -211 7 # Ó -217 6 # Ù -218 6 # Ú -209 6 # Ñ -199 6 # Ç -224 5 # à -225 5 # á -232 5 # è -233 5 # é -236 2 # ì -237 3 # í -242 5 # ò -243 5 # ó -249 5 # ù -250 5 # ú -241 5 # ñ -231 5 # ç -161 2 # ¡ -191 5 # ¿ -171 7 # « -187 7 # » -183 2 # · diff --git a/data/fonts/ari-w9500-condensed.gif b/data/fonts/ari-w9500-condensed.gif deleted file mode 100644 index 19b085c44da48ad16d0b0b0d008331edd6953e7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2019 zcmeH`{W}u~0LRAWmaoizzVq3#UVmms#a4%-XzdhIuJ(o0lxvdE0Q! zm`xfbX2j*C%#gYVCwZN-dGF$p_jXVBZ(QFWzTclepXd3aoRQX6fiyrcfCl&)g+lS) zeyeW={&xn9z7l`^g1w3Ypriu1Y69p?1E@lO3yJe zIU))B4%P!Wl1ZRIq=Ai0W7gbQe=|aH324`MKZ1gSKRJ#{gRUizH6UMoin^H(0Q83;b+KG%!+#*c1 z+*A5q?U$AY6gyqbkh?5I1b193Tt5K%7N`a`WmVkYYtnNTL$9`B3lrY3`v zVkFgNs8Gd}Ei`6&X~c}`6Um5|Z~ZW7r@aKRUE85+f2g->stp@4OC`SxGXiFT$d#8t zlfD#-tQ#r28u5Qoj(T8cQe)sMv&q;#TEMM^KwY$Z!%gu-c89YbgnFFaTp8OVYC+>J z*kUU(Ec|FvdWJ`+pO<-I$ZQ5=tvC1~O$j*VQg$kCEaKo4JcLl?wo}6R+^H3Cj}x9X zUtH(~=H_)<=&;if-ay@lCzvRI*DtYJVc2RVs~S>Vfq7-h{Y-0FqMJ1Hdc|6~lRi6s z6n@(MWz$Yo$-+EeevfSIQg{>B2?kde>^%#W4r!Aw}m2aLk~aKJBHA97P< z;>x1OC-jV2B^|yKarp&pA$R9xnuI!ggVxEs5u9#NosjNANK~ldW(3kE+{c&k^S9%0nMX07E35UlOUrWS2zbWhG$|Pl^;-F7%`^}TthR^(JOd7C|9)J-;ony zJYeqVx-s~w)_{;X7AoO#>;wbTeDF!t`I5%Z<9^8j(lOJncIA`juUa;)Eb>;8ZR-Bq z?r7skwne2d!g4LTz6``g@Uy)zgHPyf+=DPrT{AhUHNI;YI^^%qK3 z$U9=<;Npulb)n@;yl_8iDs2wGGf4CQj>lJ0ykIjbDe#8=xn0ohF2SxUiEOp*8viV9 z_em7{?6j*%-NdY$_K=V$ptPaWY$;Xh<MjG%oPugjvg zo(LS6)7a4IUM56SV~r&1$X@I189H?a6DVD>6q_$`@7t@MKB -63 6 # ? -64 12 # @ -65 8 # A -66 7 # B -67 8 # C -68 8 # D -69 7 # E -70 7 # F -71 8 # G -72 7 # H -73 2 # I -74 5 # J -75 7 # K -76 7 # L -77 8 # M -78 7 # N -79 8 # O -80 7 # P -81 8 # Q -82 7 # R -83 7 # S -84 6 # T -85 7 # U -86 7 # V -87 10 # W -88 8 # X -89 8 # Y -90 8 # Z -91 4 # [ -92 5 # \ -93 4 # ] -94 6 # ^ -95 6 # _ -96 3 # ` -97 6 # a -98 6 # b -99 6 # c -100 6 # d -101 6 # e -102 4 # f -103 6 # g -104 6 # h -105 2 # i -106 2 # j -107 6 # k -108 2 # l -109 8 # m -110 6 # n -111 6 # o -112 6 # p -113 6 # q -114 4 # r -115 5 # s -116 4 # t -117 6 # u -118 6 # v -119 8 # w -120 6 # x -121 6 # y -122 6 # z -123 5 # { -124 2 # | -125 5 # } -126 6 # ~ -192 8 # À -193 8 # Á -200 7 # È -201 7 # É -204 2 # Ì -205 2 # Í -210 8 # Ò -211 8 # Ó -217 7 # Ù -218 7 # Ú -209 7 # Ñ -199 8 # Ç -224 6 # à -225 6 # á -232 6 # è -233 6 # é -236 2 # ì -237 3 # í -242 6 # ò -243 6 # ó -249 6 # ù -250 6 # ú -241 6 # ñ -231 6 # ç -161 2 # ¡ -191 6 # ¿ -171 7 # « -187 7 # » -183 2 # · diff --git a/data/fonts/ari-w9500.gif b/data/fonts/ari-w9500.gif deleted file mode 100644 index 994cb1be6b24e481c0c24c3030dd7cc24b0df07e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2197 zcmeH``8yK~0LO_pNtFA@q*ji(-aIx79hOMsoWUO|*I0$~AMFW9F>M z{YDWvvdDcWgqPa;^!^)f-ygo;pFf}H`I?v-Lv`J1c|3UHd47e<<^H$7>$idbodNY< z#J|39?HHGb_n7?6`>v0pkDXL7vcEqKi{?G2EuoTjuAL~T4EeB^n%|kCc%3LO_yX9I zbV1ozhrlT4$yBvO9(LO(b!RJMU3vDfa-j0 z^32kZEv7rFLpf@0kpQ#$vauSKI7CMS1o)ARo@D3h4bv?Ow3Y(PZ|$tv)oSPW*MaQS zrd#cW+cgrQ7>`$}z^zPkR?ut{PQzbhb$8Qq;C%a0VKwP(&S0|e!dxH2V}(?CkAa4s zc1OoBC4Gv@ZOcBft%IB1G_x8uXSxVLm;Pqf&AIEL2S3e3ai}3+`JL}2O*oe+dDbKD z27ZsZwY503prhHWAFy%q#`ENeQ1DLAcS#S#3gWP154*{{6S*xsFNZ1u*h?dhAV2)| zFy=A-`C!pTr6?{0XULnzb@Q*c2CaBq4J^2Mh)Dt;sgrAaRKl1n z&WjvO?54S3VW61^Xtsb~QA($*D*9P2$sHX=KmzDzlgG312~zei5|Az260!InX;?Zs zZxH2L(hz3)vV91Aieb!(hQY=kwEwARdKy_YC*62*)3HF1v*Wzu%imz2N3>cO%+M|pjN7zo~qx7so z|H@*Uhryi+!3P>Gqr5C&__%gLO`&c^>1=eqdsju<9-y@5J#~KK&w+ApgX~^4q2{WO zmN8Ds@DtoO)j9pB$z@+wG9dW1Yiq7{+3iaK%qM%uh+R%MhP!AthTlS?red#>y+e4v z71QAN8O2Id*2ePFW8jHD>LxaENky{+&f={3v#ZK_88Jg2D7Xy~^mmG#z@*;%iR%^Q z)r>)OaM0y5dJmQeFnk?D35CbuVwPoc*L4KOupevrvZ~f}<%G1q)^Bhi+tPeg{3i^o z>-*C2ht8o}4VjCj0s8umpjCETS-^U-v?lk^Q`BHTkn=MKT_py@X747;N%K4cO37a| zMwk-MY=7Hr>}Oeqm+}Etw&Nj%ZWf*gwv^3iNOK`v{jQk@@~H3#`4pnc(`mwjv&hsH z`swR1a^Ot&gkGRx-p27YidV30uS^_jMO{rqp5nUg${ywPI!>Qi`v?a8}FZFpgxl@$p5Ojf{d(nwaXWsXA7z*}kWIQvPpi#Q| z5)(1XgF^Frcx<07eT@u0LS1scWU9TaVKrh{*xu`HY(-%_7cy{uPqa7s96IFZh4-4V zkg?Eox4Oy}b5uct`y7iZ(|r#Ge*gdg diff --git a/source/core/input/key_config.cpp b/source/core/input/key_config.cpp index c9a915f..fa8967f 100644 --- a/source/core/input/key_config.cpp +++ b/source/core/input/key_config.cpp @@ -13,17 +13,18 @@ auto KeyConfig::get() -> KeyConfig& { } void KeyConfig::load(const std::string& file_path) { - const auto FULL = Resource::Helper::resolve(file_path); - std::ifstream file(FULL); - if (!file.is_open()) { - std::cerr << "KeyConfig: no s'ha pogut obrir " << FULL << '\n'; + auto bytes = Resource::Helper::loadFile(file_path); + if (bytes.empty()) { + std::cerr << "KeyConfig: no s'ha pogut obrir " << file_path << '\n'; return; } try { - auto yaml = fkyaml::node::deserialize(file); + const auto* begin = reinterpret_cast(bytes.data()); + const auto* end = begin + bytes.size(); + auto yaml = fkyaml::node::deserialize(begin, end); if (!yaml.contains("keys") || !yaml["keys"].is_sequence()) { - std::cerr << "KeyConfig: falta la secció 'keys' a " << FULL << '\n'; + std::cerr << "KeyConfig: falta la secció 'keys' a " << file_path << '\n'; return; } bindings_.clear(); @@ -38,9 +39,9 @@ void KeyConfig::load(const std::string& file_path) { } bindings_[ID] = SC; } - std::cout << "KeyConfig: " << bindings_.size() << " bindings des de " << FULL << '\n'; + std::cout << "KeyConfig: " << bindings_.size() << " bindings des de " << file_path << '\n'; } catch (const std::exception& e) { - std::cerr << "KeyConfig: error parsejant " << FULL << ": " << e.what() << '\n'; + std::cerr << "KeyConfig: error parsejant " << file_path << ": " << e.what() << '\n'; } } diff --git a/source/core/locale/locale.cpp b/source/core/locale/locale.cpp index 0a3241a..42c9647 100644 --- a/source/core/locale/locale.cpp +++ b/source/core/locale/locale.cpp @@ -34,20 +34,21 @@ auto Locale::get() -> Locale& { } void Locale::load(const std::string& file_path) { - const auto FULL = Resource::Helper::resolve(file_path); - std::ifstream file(FULL); - if (!file.is_open()) { - std::cerr << "Locale: no s'ha pogut obrir " << FULL << '\n'; + auto bytes = Resource::Helper::loadFile(file_path); + if (bytes.empty()) { + std::cerr << "Locale: no s'ha pogut obrir " << file_path << '\n'; return; } try { - auto yaml = fkyaml::node::deserialize(file); + const auto* begin = reinterpret_cast(bytes.data()); + const auto* end = begin + bytes.size(); + auto yaml = fkyaml::node::deserialize(begin, end); strings_.clear(); flatten(yaml, "", strings_); - std::cout << "Locale: " << strings_.size() << " traduccions des de " << FULL << '\n'; + std::cout << "Locale: " << strings_.size() << " traduccions des de " << file_path << '\n'; } catch (const std::exception& e) { - std::cerr << "Locale: error parsejant " << FULL << ": " << e.what() << '\n'; + std::cerr << "Locale: error parsejant " << file_path << ": " << e.what() << '\n'; } } diff --git a/source/core/resources/resource_helper.cpp b/source/core/resources/resource_helper.cpp index ef3e869..79c6d5c 100644 --- a/source/core/resources/resource_helper.cpp +++ b/source/core/resources/resource_helper.cpp @@ -3,15 +3,65 @@ #include #include #include +#include + +#include "core/resources/resource_pack.hpp" namespace Resource::Helper { namespace { std::string g_data_root; + Pack g_pack; + bool g_pack_loaded = false; + bool g_fallback_enabled = true; + + auto readFromDisk(const std::string& filepath) -> std::vector { + const auto FULL = resolve(filepath); + std::ifstream file(FULL, std::ios::binary | std::ios::ate); + if (!file) { + std::cerr << "Resource::Helper: cannot open " << FULL << '\n'; + return {}; + } + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + std::vector data(static_cast(size)); + if (!file.read(reinterpret_cast(data.data()), size)) { + std::cerr << "Resource::Helper: failed to read " << FULL << '\n'; + return {}; + } + return data; + } } // namespace + auto initializeResourceSystem(const std::string& pack_file, bool enable_fallback) -> bool { + g_fallback_enabled = enable_fallback; + g_pack_loaded = g_pack.loadPack(pack_file); + + if (g_pack_loaded) { + std::cout << "Resource::Helper: pack loaded (" << g_pack.getResourceCount() + << " entries) from " << pack_file << '\n'; + } else if (enable_fallback) { + std::cout << "Resource::Helper: no pack at " << pack_file + << " — using filesystem fallback\n"; + } else { + std::cerr << "Resource::Helper: FATAL — no pack at " << pack_file + << " and fallback disabled\n"; + return false; + } + return true; + } + + void shutdownResourceSystem() { + g_pack.clear(); + g_pack_loaded = false; + } + + auto hasPack() -> bool { + return g_pack_loaded; + } + void setDataRoot(const std::string& absolute_path) { g_data_root = absolute_path; if (!g_data_root.empty() && g_data_root.back() != '/') { @@ -26,20 +76,17 @@ namespace Resource::Helper { } auto loadFile(const std::string& filepath) -> std::vector { - const auto FULL = resolve(filepath); - std::ifstream file(FULL, std::ios::binary | std::ios::ate); - if (!file) { - std::cerr << "Resource::Helper: cannot open " << FULL << '\n'; - return {}; + if (g_pack_loaded) { + // El pack està indexat relatiu a `data/` (la carpeta que + // s'empaqueta), però els callers passen "data/foo.yaml" amb + // prefix. Fem el strip ací perquè no hagin de tenir-ho en compte. + constexpr std::string_view PREFIX = "data/"; + std::string key = filepath; + if (key.starts_with(PREFIX)) { key.erase(0, PREFIX.size()); } + if (g_pack.hasResource(key)) { return g_pack.getResource(key); } } - std::streamsize size = file.tellg(); - file.seekg(0, std::ios::beg); - std::vector data(static_cast(size)); - if (!file.read(reinterpret_cast(data.data()), size)) { - std::cerr << "Resource::Helper: failed to read " << FULL << '\n'; - return {}; - } - return data; + if (g_fallback_enabled) { return readFromDisk(filepath); } + return {}; } } // namespace Resource::Helper diff --git a/source/core/resources/resource_helper.hpp b/source/core/resources/resource_helper.hpp index 2ddc4f2..3f2b3d8 100644 --- a/source/core/resources/resource_helper.hpp +++ b/source/core/resources/resource_helper.hpp @@ -6,23 +6,33 @@ namespace Resource::Helper { - // Estableix el prefix absolut des d'on es resolen totes les rutes - // relatives passades a `loadFile`/`resolve`. Pensat per a cridar-se una - // sola vegada al primer pas del Director amb `SDL_GetBasePath()`: així el - // binari troba `data/` sense dependre del CWD des d'on s'ha llançat (és - // obligatori per a .app de macOS i útil a Linux per no haver d'estar - // dins de `build/`). + // Inicialitza el sistema de recursos. Carrega el pack des de `pack_file` + // i decideix si `loadFile` pot caure a disc quan un recurs no és al pack. + // En Release el pack és obligatori: passar `enable_fallback=false` fa + // que `initializeResourceSystem` retorne false si no s'ha pogut carregar. + auto initializeResourceSystem(const std::string& pack_file, bool enable_fallback) -> bool; + + // Allibera el pack carregat a memòria. + void shutdownResourceSystem(); + + // True si el sistema es va inicialitzar amb un pack vàlid. + [[nodiscard]] auto hasPack() -> bool; + + // Estableix el prefix des d'on es resolen les rutes quan el fallback + // llig de disc. En Debug apunta al source tree (`${CMAKE_SOURCE_DIR}/`) + // perquè editar YAML/GIF dins `data/` es vegi reflectit sense re-packejar. + // En Release el fallback està desactivat i aquest camí no es fa servir. void setDataRoot(const std::string& absolute_path); - // Resol una ruta relativa contra el data root. Si la ruta ja és absoluta - // (comença per '/') la retorna sense modificar — així els fitxers de - // config (system_folder_ + "config.yaml") segueixen funcionant sense - // canvis al call site. + // Resol una ruta relativa contra el data root. Si ja és absoluta + // (comença per '/') la retorna sense modificar. S'usa per a rutes que + // NO passen pel pack (p.ex. fitxers de config dins system_folder_). [[nodiscard]] auto resolve(const std::string& filepath) -> std::string; - // Carga un fitxer del filesystem (a aquesta fase encara no hi ha pack). - // Les rutes relatives es resolen via `resolve()`; les absolutes s'obrin - // directament. + // Carrega un recurs pel seu camí relatiu (p.ex. "fonts/hud.gif", + // "input/keys.yaml"). Prova primer el pack; si no hi és i el fallback + // està actiu, cau a disc (data root + ruta). Retorna vector buit si + // no el troba. auto loadFile(const std::string& filepath) -> std::vector; } // namespace Resource::Helper diff --git a/source/core/resources/resource_pack.cpp b/source/core/resources/resource_pack.cpp new file mode 100644 index 0000000..1ddf04c --- /dev/null +++ b/source/core/resources/resource_pack.cpp @@ -0,0 +1,209 @@ +#include "core/resources/resource_pack.hpp" + +#include +#include +#include +#include +#include + +namespace Resource { + + const std::string Pack::DEFAULT_ENCRYPT_KEY = "AEE_2026_PACK_KEY"; + + namespace { + constexpr const char* MAGIC = "AE26"; + constexpr std::uint32_t VERSION = 1; + } // namespace + + auto Pack::calculateChecksum(const std::vector& data) -> std::uint32_t { + // djb2: seed 0x12345678. Compatible amb aee/projecte_2026 per si mai + // reutilitzem eines de diagnòstic entre projectes. + std::uint32_t checksum = 0x12345678; + for (unsigned char b : data) { + // cppcheck-suppress useStlAlgorithm + checksum = ((checksum << 5) + checksum) + b; + } + return checksum; + } + + void Pack::xorInPlace(std::vector& data, const std::string& key) { + if (key.empty()) { return; } + for (std::size_t i = 0; i < data.size(); ++i) { + data[i] ^= static_cast(key[i % key.length()]); + } + } + + auto Pack::loadPack(const std::string& pack_file) -> bool { + std::ifstream file(pack_file, std::ios::binary); + if (!file) { return false; } + + std::array header{}; + file.read(header.data(), 4); + if (std::string(header.data(), 4) != MAGIC) { + std::cerr << "Resource::Pack: invalid pack file format (bad magic): " << pack_file << '\n'; + return false; + } + + std::uint32_t version = 0; + file.read(reinterpret_cast(&version), sizeof(version)); + if (version != VERSION) { + std::cerr << "Resource::Pack: unsupported pack version: " << version << '\n'; + return false; + } + + std::uint32_t resource_count = 0; + file.read(reinterpret_cast(&resource_count), sizeof(resource_count)); + + resources_.clear(); + resources_.reserve(resource_count); + + for (std::uint32_t i = 0; i < resource_count; ++i) { + std::uint32_t filename_length = 0; + file.read(reinterpret_cast(&filename_length), sizeof(filename_length)); + + std::string filename(filename_length, '\0'); + file.read(filename.data(), filename_length); + + PackEntry entry; + entry.filename = filename; + file.read(reinterpret_cast(&entry.offset), sizeof(entry.offset)); + file.read(reinterpret_cast(&entry.size), sizeof(entry.size)); + file.read(reinterpret_cast(&entry.checksum), sizeof(entry.checksum)); + + resources_[filename] = entry; + } + + std::uint64_t data_size = 0; + file.read(reinterpret_cast(&data_size), sizeof(data_size)); + + data_.resize(data_size); + file.read(reinterpret_cast(data_.data()), static_cast(data_size)); + + xorInPlace(data_, DEFAULT_ENCRYPT_KEY); + return true; + } + + auto Pack::savePack(const std::string& pack_file) const -> bool { + std::ofstream file(pack_file, std::ios::binary); + if (!file) { + std::cerr << "Resource::Pack: could not create pack file: " << pack_file << '\n'; + return false; + } + + file.write(MAGIC, 4); + + std::uint32_t version = VERSION; + file.write(reinterpret_cast(&version), sizeof(version)); + + auto resource_count = static_cast(resources_.size()); + file.write(reinterpret_cast(&resource_count), sizeof(resource_count)); + + for (const auto& [filename, entry] : resources_) { + auto filename_length = static_cast(filename.length()); + file.write(reinterpret_cast(&filename_length), sizeof(filename_length)); + file.write(filename.c_str(), filename_length); + file.write(reinterpret_cast(&entry.offset), sizeof(entry.offset)); + file.write(reinterpret_cast(&entry.size), sizeof(entry.size)); + file.write(reinterpret_cast(&entry.checksum), sizeof(entry.checksum)); + } + + std::vector encrypted = data_; + xorInPlace(encrypted, DEFAULT_ENCRYPT_KEY); + + std::uint64_t data_size = encrypted.size(); + file.write(reinterpret_cast(&data_size), sizeof(data_size)); + file.write(reinterpret_cast(encrypted.data()), static_cast(data_size)); + + return true; + } + + auto Pack::addFile(const std::string& filename, const std::string& filepath) -> bool { + std::ifstream file(filepath, std::ios::binary | std::ios::ate); + if (!file) { + std::cerr << "Resource::Pack: could not open file: " << filepath << '\n'; + return false; + } + + std::streamsize file_size = file.tellg(); + file.seekg(0, std::ios::beg); + + std::vector file_data(file_size); + if (!file.read(reinterpret_cast(file_data.data()), file_size)) { + std::cerr << "Resource::Pack: could not read file: " << filepath << '\n'; + return false; + } + + PackEntry entry; + entry.filename = filename; + entry.offset = data_.size(); + entry.size = file_data.size(); + entry.checksum = calculateChecksum(file_data); + + data_.insert(data_.end(), file_data.begin(), file_data.end()); + resources_[filename] = entry; + return true; + } + + auto Pack::addDirectory(const std::string& directory) -> bool { + if (!std::filesystem::exists(directory)) { + std::cerr << "Resource::Pack: directory does not exist: " << directory << '\n'; + return false; + } + + // cppcheck-suppress useStlAlgorithm + for (const auto& entry : std::filesystem::recursive_directory_iterator(directory)) { + if (!entry.is_regular_file()) { continue; } + + std::string filepath = entry.path().string(); + std::string filename = std::filesystem::relative(entry.path(), directory).string(); + std::ranges::replace(filename, '\\', '/'); + + if (!addFile(filename, filepath)) { return false; } + } + return true; + } + + auto Pack::getResource(const std::string& filename) const -> std::vector { + auto it = resources_.find(filename); + if (it == resources_.end()) { return {}; } + + const PackEntry& entry = it->second; + if (entry.offset + entry.size > data_.size()) { + std::cerr << "Resource::Pack: invalid resource data: " << filename << '\n'; + return {}; + } + + std::vector result( + data_.begin() + static_cast(entry.offset), + data_.begin() + static_cast(entry.offset + entry.size)); + + std::uint32_t checksum = calculateChecksum(result); + if (checksum != entry.checksum) { + std::cerr << "Resource::Pack: checksum mismatch for: " << filename << '\n'; + } + return result; + } + + auto Pack::hasResource(const std::string& filename) const -> bool { + return resources_.contains(filename); + } + + void Pack::clear() { + resources_.clear(); + data_.clear(); + } + + auto Pack::getResourceCount() const -> std::size_t { + return resources_.size(); + } + + auto Pack::getResourceList() const -> std::vector { + std::vector result; + result.reserve(resources_.size()); + for (const auto& [filename, entry] : resources_) { + result.push_back(filename); + } + return result; + } + +} // namespace Resource diff --git a/source/core/resources/resource_pack.hpp b/source/core/resources/resource_pack.hpp new file mode 100644 index 0000000..da26fa4 --- /dev/null +++ b/source/core/resources/resource_pack.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace Resource { + + // Entrada d'un recurs dins el pack. + struct PackEntry { + std::string filename; + std::uint64_t offset{0}; + std::uint64_t size{0}; + std::uint32_t checksum{0}; + }; + + // Pack binari de recursos carregat a memòria. Format: + // Header: "AE26" (4 bytes) + version uint32 + resource_count uint32 + // Index: per cada recurs -> filename_len uint32 + filename + // + offset uint64 + size uint64 + checksum uint32 + // Payload: data_size uint64 + bytes xifrats amb XOR (DEFAULT_ENCRYPT_KEY) + // La classe té dos rols: (a) loader en temps d'execució — carregar un + // .pack del disc i obtindre els bytes d'un recurs per nom; (b) builder + // usat per l'eina `pack_resources` per a empaquetar `data/`. + class Pack { + public: + Pack() = default; + ~Pack() = default; + + Pack(const Pack&) = delete; + auto operator=(const Pack&) -> Pack& = delete; + Pack(Pack&&) = default; + auto operator=(Pack&&) -> Pack& = default; + + // I/O del fitxer. `loadPack` retorna false silenciosament si el + // fitxer no existeix (el caller decideix si cal fallback). + auto loadPack(const std::string& pack_file) -> bool; + auto savePack(const std::string& pack_file) const -> bool; + + // Builders usats per l'eina `pack_resources`. + auto addFile(const std::string& filename, const std::string& filepath) -> bool; + auto addDirectory(const std::string& directory) -> bool; + + [[nodiscard]] auto getResource(const std::string& filename) const -> std::vector; + [[nodiscard]] auto hasResource(const std::string& filename) const -> bool; + + void clear(); + [[nodiscard]] auto getResourceCount() const -> std::size_t; + [[nodiscard]] auto getResourceList() const -> std::vector; + + static const std::string DEFAULT_ENCRYPT_KEY; + + private: + std::unordered_map resources_; + std::vector data_; + + static auto calculateChecksum(const std::vector& data) -> std::uint32_t; + static void xorInPlace(std::vector& data, const std::string& key); + }; + +} // namespace Resource diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index 2f50007..5ab36d0 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -40,13 +40,26 @@ Director::Director() { std::fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError()); } - // Rutes dels recursos relatives a l'executable, no al CWD. SDL_GetBasePath - // retorna la carpeta del binari al desplegament portable i Contents/ - // Resources/ dins d'un .app a macOS. Ha de fer-se abans de qualsevol - // loader que passi per Resource::Helper::resolve (Locale, KeyConfig, - // Resource::Cache, PaletteLoader, etc.). + // Sistema de recursos: el pack viu al costat del binari (build/), el + // regenera CMake a cada build. En Debug habilitem el fallback a disc + // apuntant al source tree perquè editar YAML/GIF dins `data/` es vegi + // sense re-packejar; en Release el pack és obligatori. + std::string pack_file = "resources.pack"; if (const char* base = SDL_GetBasePath()) { + pack_file = std::string(base) + "resources.pack"; +#ifndef _DEBUG Resource::Helper::setDataRoot(base); +#endif + } +#ifdef _DEBUG + Resource::Helper::setDataRoot(AEE_SOURCE_DIR); + constexpr bool ENABLE_FALLBACK = true; +#else + constexpr bool ENABLE_FALLBACK = false; +#endif + if (!Resource::Helper::initializeResourceSystem(pack_file, ENABLE_FALLBACK)) { + std::fprintf(stderr, "Director: sistema de recursos no disponible; avortem\n"); + std::exit(1); } // Prepara la carpeta de sistema (~/.config/jailgames/aee_2026/) i carrega @@ -207,6 +220,7 @@ Director::~Director() { // desfà les llibreries abans que corren els destructors estàtics. current_scene_.reset(); Resource::Cache::get().clearAudio(); + Resource::Helper::shutdownResourceSystem(); GamepadManager::get().shutdown(); Audio::destroy(); Screen::get().shutdown(); diff --git a/source/game/entities/entity_loader.cpp b/source/game/entities/entity_loader.cpp index 4ff809d..34c8a85 100644 --- a/source/game/entities/entity_loader.cpp +++ b/source/game/entities/entity_loader.cpp @@ -1,6 +1,5 @@ #include "game/entities/entity_loader.hpp" -#include #include #include @@ -31,23 +30,26 @@ namespace EntityLoader { auto it = cache.find(name); if (it != cache.end()) { return it->second; } - const std::string FULL = Resource::Helper::resolve(templatePath(name)); - std::ifstream file(FULL); - if (!file.is_open()) { - throw std::runtime_error("EntityLoader: no s'ha pogut obrir " + FULL); + const auto PATH = templatePath(name); + auto bytes = Resource::Helper::loadFile(PATH); + if (bytes.empty()) { + throw std::runtime_error("EntityLoader: no s'ha pogut obrir " + PATH); } - fkyaml::node node = fkyaml::node::deserialize(file); + const auto* begin = reinterpret_cast(bytes.data()); + const auto* end = begin + bytes.size(); + fkyaml::node node = fkyaml::node::deserialize(begin, end); cache.emplace(name, node); return node; } auto readSpawns(const std::string& level_yaml_path) -> std::vector { - const auto FULL = Resource::Helper::resolve(level_yaml_path); - std::ifstream file(FULL); - if (!file.is_open()) { - throw std::runtime_error("EntityLoader: no s'ha pogut obrir " + FULL); + auto bytes = Resource::Helper::loadFile(level_yaml_path); + if (bytes.empty()) { + throw std::runtime_error("EntityLoader: no s'ha pogut obrir " + level_yaml_path); } - fkyaml::node root = fkyaml::node::deserialize(file); + const auto* begin = reinterpret_cast(bytes.data()); + const auto* end = begin + bytes.size(); + fkyaml::node root = fkyaml::node::deserialize(begin, end); std::vector spawns; if (!root.contains("entities")) { return spawns; } const auto& list = root["entities"]; diff --git a/source/game/entities/player.cpp b/source/game/entities/player.cpp index 6cec2bb..dbe5787 100644 --- a/source/game/entities/player.cpp +++ b/source/game/entities/player.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include @@ -47,18 +46,19 @@ namespace { } // namespace void Player::loadFrom(const std::string& yaml_path) { - const auto FULL = Resource::Helper::resolve(yaml_path); - std::ifstream file(FULL); - if (!file.is_open()) { - throw std::runtime_error("Player: no s'ha pogut obrir " + FULL); + auto bytes = Resource::Helper::loadFile(yaml_path); + if (bytes.empty()) { + throw std::runtime_error("Player: no s'ha pogut obrir " + yaml_path); } - auto yaml = fkyaml::node::deserialize(file); + const auto* begin = reinterpret_cast(bytes.data()); + const auto* end = begin + bytes.size(); + auto yaml = fkyaml::node::deserialize(begin, end); sprite_w = yaml["frameWidth"].get_value(); sprite_h = yaml["frameHeight"].get_value(); if (!yaml.contains("collision")) { - throw std::runtime_error("Player: manca bloc `collision:` a " + FULL); + throw std::runtime_error("Player: manca bloc `collision:` a " + yaml_path); } const auto& c = yaml["collision"]; collision_size = c["size"].get_value(); diff --git a/source/game/map/map.cpp b/source/game/map/map.cpp index fc48bf9..32c417a 100644 --- a/source/game/map/map.cpp +++ b/source/game/map/map.cpp @@ -1,6 +1,5 @@ #include "game/map/map.hpp" -#include #include #include @@ -22,14 +21,15 @@ namespace { namespace Map { void loadFrom(const std::string& yaml_path) { - const auto FULL = Resource::Helper::resolve(yaml_path); - std::ifstream file(FULL); - if (!file.is_open()) { - throw std::runtime_error("Map: no s'ha pogut obrir " + FULL); + auto bytes = Resource::Helper::loadFile(yaml_path); + if (bytes.empty()) { + throw std::runtime_error("Map: no s'ha pogut obrir " + yaml_path); } - auto yaml = fkyaml::node::deserialize(file); + const auto* begin = reinterpret_cast(bytes.data()); + const auto* end = begin + bytes.size(); + auto yaml = fkyaml::node::deserialize(begin, end); if (!yaml.contains("geometry")) { - throw std::runtime_error("Map: manca bloc `geometry:` a " + FULL); + throw std::runtime_error("Map: manca bloc `geometry:` a " + yaml_path); } const auto& g = yaml["geometry"]; @@ -78,7 +78,7 @@ namespace Map { std::cout << "Map: geometria " << grid_cols << "x" << grid_rows << " sarcs " << sarc_w << "x" << sarc_h << " pitch " << pitch_x << "x" << pitch_y - << " des de " << FULL << '\n'; + << " des de " << yaml_path << '\n'; } auto getSarcRect(int col, int row) -> SDL_FRect { diff --git a/source/game/map/tilemap.cpp b/source/game/map/tilemap.cpp index 7cdf5a8..857a2d2 100644 --- a/source/game/map/tilemap.cpp +++ b/source/game/map/tilemap.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -68,13 +67,13 @@ namespace { } // namespace Tilemap::Tilemap(const std::string& yaml_path) { - const auto FULL = Resource::Helper::resolve(yaml_path); - std::ifstream file(FULL); - if (!file.is_open()) { - throw std::runtime_error("Tilemap: no s'ha pogut obrir " + FULL); + auto bytes = Resource::Helper::loadFile(yaml_path); + if (bytes.empty()) { + throw std::runtime_error("Tilemap: no s'ha pogut obrir " + yaml_path); } - - auto yaml = fkyaml::node::deserialize(file); + const auto* begin = reinterpret_cast(bytes.data()); + const auto* end = begin + bytes.size(); + auto yaml = fkyaml::node::deserialize(begin, end); const auto TILESET_PATH = yaml["tileset"].get_value(); const auto TILE_SIZE = yaml["tile_size"].get_value(); @@ -113,7 +112,7 @@ Tilemap::Tilemap(const std::string& yaml_path) { } std::cout << "Tilemap: " << COLS << "x" << ROWS << " tiles (" - << width_px_ << "x" << height_px_ << " px) des de " << FULL << '\n'; + << width_px_ << "x" << height_px_ << " px) des de " << yaml_path << '\n'; } void Tilemap::render() const { diff --git a/source/utils/defaults.hpp b/source/utils/defaults.hpp index 0c36559..e88e93d 100644 --- a/source/utils/defaults.hpp +++ b/source/utils/defaults.hpp @@ -107,9 +107,9 @@ namespace Defaults { // Placeholders (LOGO / TITLE / INTRO): font temporal fins que els // placeholders es substitueixin per les seues escenes finals. - constexpr const char* PLACEHOLDER_NAME = "ari-w9500"; - constexpr const char* PLACEHOLDER_GIF = "data/fonts/ari-w9500.gif"; - constexpr const char* PLACEHOLDER_FNT = "data/fonts/ari-w9500.fnt"; + constexpr const char* PLACEHOLDER_NAME = "ari-w9500-condensed-display"; + constexpr const char* PLACEHOLDER_GIF = "data/fonts/ari-w9500-condensed-display.gif"; + constexpr const char* PLACEHOLDER_FNT = "data/fonts/ari-w9500-condensed-display.fnt"; constexpr int PLACEHOLDER_KERNING = 1; // Menú de servei (F9): títol en display normal, items en la diff --git a/source/utils/defines.hpp b/source/utils/defines.hpp index c9f2f96..1c4a10c 100644 --- a/source/utils/defines.hpp +++ b/source/utils/defines.hpp @@ -3,7 +3,7 @@ namespace Texts { constexpr const char* WINDOW_TITLE = "© 2026 Aventures en Egipte — JailDesigner"; constexpr const char* COPYRIGHT = "© 2026 JailDesigner"; - constexpr const char* VERSION = "0.20.0"; // Single source of truth — Makefile el grep + constexpr const char* VERSION = "0.21.0"; // Single source of truth — Makefile el grep } // namespace Texts namespace GameCanvas { diff --git a/tools/pack_resources/pack_resources.cpp b/tools/pack_resources/pack_resources.cpp new file mode 100644 index 0000000..89bb28d --- /dev/null +++ b/tools/pack_resources/pack_resources.cpp @@ -0,0 +1,98 @@ +#include +#include +#include + +#include "core/resources/resource_pack.hpp" + +namespace { + + void showHelp() { + std::cout << "aee_2026 - Resource Packer\n"; + std::cout << "==========================\n"; + std::cout << "Usage: pack_resources [options] [input_dir] [output_file]\n\n"; + std::cout << "Options:\n"; + std::cout << " --help Show this help message\n"; + std::cout << " --list List contents of an existing pack file\n\n"; + std::cout << "Arguments:\n"; + std::cout << " input_dir Directory to pack (default: data)\n"; + std::cout << " output_file Pack file name (default: resources.pack)\n\n"; + std::cout << "Examples:\n"; + std::cout << " pack_resources # data -> resources.pack\n"; + std::cout << " pack_resources data build/resources.pack # data -> build/resources.pack\n"; + std::cout << " pack_resources --list build/resources.pack\n"; + } + + void listPackContents(const std::string& pack_file) { + Resource::Pack pack; + if (!pack.loadPack(pack_file)) { + std::cerr << "Error: cannot open pack file: " << pack_file << '\n'; + return; + } + auto resources = pack.getResourceList(); + std::cout << "Pack file: " << pack_file << '\n'; + std::cout << "Resources: " << resources.size() << '\n'; + for (const auto& r : resources) { std::cout << " " << r << '\n'; } + } + +} // namespace + +int main(int argc, char* argv[]) { + std::string data_dir = "data"; + std::string output_file = "resources.pack"; + bool list_mode = false; + bool data_dir_set = false; + + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if (arg == "--help" || arg == "-h") { + showHelp(); + return 0; + } + if (arg == "--list") { + list_mode = true; + if (i + 1 < argc) { output_file = argv[++i]; } + continue; + } + if (!arg.empty() && arg[0] != '-') { + if (!data_dir_set) { + data_dir = arg; + data_dir_set = true; + } else { + output_file = arg; + } + } + } + + if (list_mode) { + listPackContents(output_file); + return 0; + } + + std::cout << "aee_2026 - Resource Packer\n==========================\n"; + std::cout << "Input directory: " << data_dir << '\n'; + std::cout << "Output file: " << output_file << '\n'; + + if (!std::filesystem::exists(data_dir)) { + std::cerr << "Error: input directory does not exist: " << data_dir << '\n'; + return 1; + } + + Resource::Pack pack; + std::cout << "Scanning and packing resources...\n"; + if (!pack.addDirectory(data_dir)) { + std::cerr << "Error: failed to add directory to pack\n"; + return 1; + } + std::cout << "Found " << pack.getResourceCount() << " resources\n"; + + std::cout << "Saving pack file...\n"; + if (!pack.savePack(output_file)) { + std::cerr << "Error: failed to save pack file\n"; + return 1; + } + + auto file_size = std::filesystem::file_size(std::filesystem::path(output_file)); + std::cout << "Pack file created: " << output_file << " (" + << (static_cast(file_size) / 1024.0 / 1024.0) << " MB)\n"; + return 0; +}