#!/usr/bin/env bash # Compila o CaveExpress do fredygames.com: o jogo de helicoptero de cargas de Martin Gerhardy (codigo # GPL-3.0-or-later; graficos, sons e musica CC BY-SA 4.0; fonte Luckiest Guy, da Astigmatic, Apache-2.0) # em WebAssembly, com o Emscripten 6.0.10 e as portas SDL2 2.32.10, SDL2_image 2.6.0 (libpng 1.6.58, # zlib 1.3.2), SDL2_mixer 2.8.0 (libogg 1.3.5, libvorbis 1.3.7) e SQLite 3.39.0 dele, mais o Lua 5.4.4 # e o Box2D 2.4.2 compilados aqui. Este script e o patch fazem parte do codigo-fonte publicado junto do # jogo. # # source /emsdk_env.sh # emsdk install 6.0.10 && emsdk activate 6.0.10 # src/FredGames.Bundler/Builds/build-caveexpress.sh # # Fonte: o tarball do commit 43fc1705 do repositorio do autor (github.com/mgerhardy/caveexpress, 2026-09-13, # o primeiro com o build web arrumado; a release 2.6 e anterior a ele), conferido pelo SHA-256 e guardado # em artifacts/cache/caveexpress/. O site hospeda o mesmo tarball sem o CavePacker (o outro jogo do # repositorio, cujas fases vem do XSokoban, do KSokoban e de outros autores): sem base/cavepacker, # src/cavepacker, docs/cavepacker, contrib/maps/cavepacker, as artes contrib/assets/png/cavepacker* e os # dados de teste base/tests/cavepacker* e base/tests/textures/cavepacker*. O script aceita os dois. # Lua 5.4.4 (lua.org, MIT) e Box2D 2.4.2 (Erin Catto, MIT) vem dos tarballs oficiais, tambem conferidos. # # Sem o CMake do jogo (ele baixa o Lua, o SQLite e o Box2D por git no meio do build): o script compila # os mesmos arquivos que os CMakeLists listam para o alvo caveexpress, sem rede (NONETWORK), sem o # CavePacker, sem os testes e sem as ferramentas. Os atlas de texturas (base/caveexpress/pics e # base/caveexpress/textures, que o repositorio nao versiona) saem do textureatlas do proprio projeto, # compilado com o mesmo Emscripten e rodado no node, a partir das artes de contrib/assets/png; so os # atlas "big" vao para o jogo. # # caveexpress-fredygames.patch: # - config.lua: no navegador, som pelo SDL_mixer (o projeto usava um motor mudo), tela fixa de # 1024x768 que a pagina escala, sem tela cheia e com as texturas "big"; # - sem os botoes Homepage e Quit no menu do navegador (o Quit abria o site do autor) e openURL nunca # abre outra pagina; # - nas opcoes, sem a troca de tela cheia (a pagina tem o proprio botao), e o botao de texturas # "small" nao faz nada (o build so leva as "big"); # - fredygames_save(), que a pagina chama quando a aba some ou fecha: o jogo so grava as opcoes ao # sair (ConfigManager::save, novo); o progresso da campanha ele grava sozinho. # # Saida: artifacts/build/caveexpress-43fc1705/caveexpress.{js,wasm,data}, que o bundle.yaml do jogo # confere pelo SHA-256. set -euo pipefail COMMIT=43fc170567c140d037d592148e652fc864ee0302 SHORT=43fc1705 SRC_SHA256=4ed6e9354ae0b913db5444932767fa676c010f8919f71f6369a3788f613398af PRUNED_SHA256=e460f13b3bf5dcc1464f39edfe5fc22837c5cd31bba9b023eddacd6a23ecb091 LUA=5.4.4 LUA_SHA256=164c7849653b80ae67bec4b7473b884bf5cc8d2dca05653475ec2ed27b9ebf61 BOX2D=2.4.2 BOX2D_SHA256=85b9b104d256c985e6e244b4227d447897fac429071cc114e5cc819dae848852 EMSCRIPTEN=6.0.10 here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" root="$(git -C "$here" rev-parse --show-toplevel 2>/dev/null || pwd)" out="$root/artifacts/build/caveexpress-$SHORT" cache="$root/artifacts/cache/caveexpress" if ! emcc --version 2>/dev/null | head -1 | grep -q " $EMSCRIPTEN "; then echo "Precisa do Emscripten $EMSCRIPTEN no PATH (source /emsdk_env.sh)." >&2 exit 1 fi node="${EMSDK_NODE:-node}" # Arquivo auxiliar: em caveexpress/ (repositorio) ou ao lado do script (pasta de fontes do site). aux() { if [ -f "$here/caveexpress/$1" ]; then echo "$here/caveexpress/$1"; else echo "$here/$1"; fi } # Arquivo baixado: ao lado do script ou no cache, conferido pelo SHA-256. fetch() { local name="$1" url="$2" sha="$3" file if [ -f "$here/$name" ]; then file="$here/$name" else file="$cache/$name" if [ ! -f "$file" ]; then mkdir -p "$cache" curl -fsSL -o "$file.part" "$url" mv "$file.part" "$file" fi fi echo "$sha $file" | shasum -a 256 -c - >&2 echo "$file" } # O jogo: o tarball podado (pasta de fontes do site) ou o original (cache ou GitHub). if [ -f "$here/caveexpress-$SHORT-pruned.tar.gz" ]; then game_tar="$(fetch "caveexpress-$SHORT-pruned.tar.gz" "" "$PRUNED_SHA256")" else game_tar="$(fetch "caveexpress-$SHORT.tar.gz" \ "https://github.com/mgerhardy/caveexpress/archive/$COMMIT.tar.gz" "$SRC_SHA256")" fi lua_tar="$(fetch "lua-$LUA.tar.gz" "https://www.lua.org/ftp/lua-$LUA.tar.gz" "$LUA_SHA256")" box2d_tar="$(fetch "box2d-$BOX2D.tar.gz" \ "https://github.com/erincatto/box2d/archive/refs/tags/v$BOX2D.tar.gz" "$BOX2D_SHA256")" rm -rf "$out" mkdir -p "$out/work" tar xzf "$game_tar" -C "$out/work" tar xzf "$lua_tar" -C "$out/work" tar xzf "$box2d_tar" -C "$out/work" S="$out/work/caveexpress-$COMMIT" L="$out/work" patch -d "$S" -p1 --no-backup-if-mismatch < "$(aux caveexpress-fredygames.patch)" >&2 # Atlas de texturas: o textureatlas do projeto (dominio publico; pugixml MIT, stb dominio publico ou MIT) # le cada .tps do TexturePacker e grava o PNG e o .lua de cada tamanho em base/caveexpress. ( cd "$S/src/tools/textureatlas" em++ -O2 -ffile-prefix-map="$S/=" -DPUGIXML_NO_EXCEPTIONS TextureAtlas.cpp external/pugixml.cpp \ -sNODERAWFS=1 -sALLOW_MEMORY_GROWTH=1 -sENVIRONMENT=node -sEXIT_RUNTIME=1 -sSTACK_SIZE=8MB \ -o "$out/work/textureatlas.js" ) ( cd "$S/contrib/assets/png" tps=() for f in $(LC_ALL=C ls caveexpress-*.tps); do tps+=("./$f"); done "$node" "$out/work/textureatlas.js" "${tps[@]}" >&2 ) rm -f "$S"/base/caveexpress/pics/*-small.png "$S"/base/caveexpress/textures/*-small.lua # Cabecalhos que o CMake geraria (cp-config.h e game.h, com os valores do alvo caveexpress). mkdir -p "$out/work/gen" cat > "$out/work/gen/cp-config.h" <<'EOF' #define HAVE_SYSLOG_H 1 #define HAVE_CLOCKGETTIME 1 #define CP_BOX2D_MAJOR_VERSION 2 #define CP_BOX2D_MINOR_VERSION 4 #define CP_BOX2D_MICRO_VERSION 2 EOF cat > "$out/work/gen/game.h" <<'EOF' #define GUI_IDENTIFIER "org.caveexpress" #define APPNAME "CaveExpress" #define VERSION "2.7" #define VERSION_CODE 31 EOF PORTS=(-sUSE_SDL=2 -sUSE_SDL_IMAGE=2 -sSDL2_IMAGE_FORMATS='["png"]' -sUSE_SDL_MIXER=2 -sSDL2_MIXER_FORMATS='["ogg"]' -sUSE_SQLITE3=1) FLAGS=(-O2 -ffile-prefix-map="$out/work/=" -w -DNDEBUG -DEMSCRIPTEN -DNONETWORK=1 -DIMGUI_IMPL_OPENGL_ES3 "${PORTS[@]}") INC=(-I"$out/work/gen" -I"$S/src" -I"$S/src/modules/physics" -I"$S/src/modules" -I"$S/src/libs/glm" -I"$S/src/libs/yajl" -I"$S/src/libs/imgui" -I"$S/src/libs/imgui/backends" -I"$S/src/libs/imgui/misc/cpp" -I"$L/lua-$LUA/src" -I"$L/box2d-$BOX2D/include") # Arquivos: os que os CMakeLists listam (set(..._SRCS ...)), menos os de outras plataformas. cmake_srcs() { awk -v RS='\\)' '/set\(/ {print}' "$1/CMakeLists.txt" | grep -oE '[A-Za-z0-9_/.-]+\.(cpp|c)' | grep -v '^ports/' } srcs=() for f in $(LC_ALL=C ls "$L/lua-$LUA/src"/*.c); do case "$(basename "$f")" in lua.c|luac.c) ;; *) srcs+=("$f") ;; esac done for f in $(LC_ALL=C ls "$L/box2d-$BOX2D/src"/*/*.cpp); do srcs+=("$f"); done for f in yajl_alloc yajl_buf yajl yajl_encode yajl_gen yajl_lex yajl_parser yajl_tree; do srcs+=("$S/src/libs/yajl/$f.c") done srcs+=("$S/src/libs/glm/glm/detail/glm.cpp") for f in misc/cpp/imgui_stdlib backends/imgui_impl_opengl3 backends/imgui_impl_sdl2 \ backends/imgui_impl_sdlrenderer2 imgui_widgets imgui_demo imgui_draw imgui_tables imgui; do srcs+=("$S/src/libs/imgui/$f.cpp") done for m in campaign client common game gfx mainloop network particles service sound sprites textures ui; do for f in $(cmake_srcs "$S/src/modules/$m"); do srcs+=("$S/src/modules/$m/$f"); done done srcs+=("$S/src/modules/common/ports/Unix.cpp" "$S/src/modules/common/ports/HTML5.cpp") srcs+=("$S/src/modules/physics/DebugRenderer.cpp" "$S/src/modules/physics/box2d24/PhysicsBackend.cpp") for d in shared client server; do for f in $(cmake_srcs "$S/src/caveexpress/$d"); do srcs+=("$S/src/caveexpress/$d/$f"); done done srcs+=("$S/src/caveexpress/main/CaveExpress.cpp" "$S/src/Main.cpp") # Compila em paralelo (um objeto por arquivo, nomeado pelo caminho) e liga na ordem da lista. mkdir -p "$out/work/obj" objs=() jobs=0 for f in "${srcs[@]}"; do rel="${f#"$out/work/"}" o="$out/work/obj/${rel//\//_}.o" objs+=("$o") case "$f" in *.c) emcc -std=gnu11 "${FLAGS[@]}" "${INC[@]}" -c "$f" -o "$o" & ;; *) em++ -std=c++17 "${FLAGS[@]}" "${INC[@]}" -c "$f" -o "$o" & ;; esac jobs=$((jobs + 1)) if [ "$jobs" -ge 8 ]; then wait; jobs=0; fi done wait for o in "${objs[@]}"; do [ -f "$o" ] || { echo "Falhou: $o" >&2; exit 1; }; done # Liga de dentro da pasta de saida, com caminhos relativos: o carregador do caveexpress.data guarda o # nome que a pasta tem aqui (base/caveexpress, que o jogo procura a partir da raiz). mkdir -p "$out/base" mv "$S/base/caveexpress" "$out/base/caveexpress" ( cd "$out" em++ -O2 "${objs[@]}" "${PORTS[@]}" -lidbfs.js \ -sALLOW_MEMORY_GROWTH=1 -sSTACK_SIZE=2MB -sFULL_ES3=1 -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2 \ -sENVIRONMENT=web -sEXIT_RUNTIME=1 \ -sEXPORTED_RUNTIME_METHODS=FS,addRunDependency,removeRunDependency \ --js-library "$S/contrib/installer/html5/library.js" \ --preload-file base/caveexpress@/base/caveexpress \ -o caveexpress.js ) rm -rf "$out/base" "$out/work" shasum -a 256 "$out/caveexpress.js" "$out/caveexpress.wasm" "$out/caveexpress.data"