#!/usr/bin/env bash # Compila jogos feitos no motor Bevy (Rust -> WebAssembly) a partir do fonte, num commit fixo: # cargo build --locked --profile --target wasm32-unknown-unknown (Cargo.lock do autor, Rust 1.92.0) # wasm-bindgen --target web --no-typescript --remove-name-section ... (CLI na versao do Cargo.lock) # O codigo do jogo so muda pelos patches de Builds/bevy//*.patch (publicados junto do fonte, quando a licenca # pede). A pagina (bevy/index.html.in, bevy/start.js.in) e nossa: o jogo so inicia depois de um clique e a politica de # seguranca bloqueia qualquer conexao fora da origem. O .wasm sai comprimido (o Bundler faz o gzip; o CloudFront nao # comprime nada acima de 10 MB) e a pagina descomprime com DecompressionStream. # # src/FredGames.Bundler/Builds/build-bevy-games.sh [slug ...] (sem argumento: todos) # # Saida de cada jogo: artifacts/build//{_bg.wasm, --dist.tar.gz, -licenses.txt}. O # tar.gz tem a pagina, o JS do wasm-bindgen e as imagens/sons/fontes do jogo (sem o .wasm). Dois builds seguidos # tem que dar os mesmos sha256 (o script confere o .wasm do cargo antes do wasm-bindgen, e o bundle.yaml fixa o resto). # Rust e wasm-bindgen ficam em artifacts/tools/. set -euo pipefail here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" root="$(git -C "$here" rev-parse --show-toplevel 2>/dev/null || pwd)" cache="$root/artifacts/build/cache" tools="$root/artifacts/tools" mkdir -p "$cache" export RUSTUP_HOME="$tools/rustup" CARGO_HOME="$tools/cargo" export PATH="$CARGO_HOME/bin:$PATH" TOOLCHAIN=1.92.0 WB_ARCH=aarch64-apple-darwin fetch() { # url arquivo sha256 if [ ! -f "$cache/$2" ]; then curl -fsSL --retry 4 "$1" -o "$cache/$2.partial" && mv "$cache/$2.partial" "$cache/$2" fi echo "$3 $cache/$2" | shasum -a 256 -c - >/dev/null } load() { # REPO COMMIT SHA256 (tarball do commit) | WB / WB_SHA (wasm-bindgen-cli do Cargo.lock) | PROFILE e BIN (o .wasm que o cargo # gera) | NAME (nome do .js/.wasm publicados) | CANVAS=fit (o patch pede fit_canvas_to_parent: o canvas enche a pagina) # ou fixed (canvas #bevy de tamanho fixo W x H, escalado por CSS) | BG (cor de fundo da pagina) CANVAS=fit W=0 H=0 BG='#000' EXTRAFLAGS= ALLOW= FEATURES= ASSETS=dir case "$1" in tower-quest-3200) REPO=kayhhh/tower-quest COMMIT=3e46c52fe5c31a7ea43c1c59c180f633a27f1256 SHA256=e8660720838419cd8153fa27b0ac3914e207f2c1b6fb929f363c75bfa9273927 PROFILE=wasm-release BIN=tower-quest NAME=tower_quest TITLE="Tower Quest 3200" BG='#2e1730' WB=0.2.89 WB_SHA=cff8b011832e0f17fc94e4673a2910b04cb11bd527736b96f94abea24ff674be INIT=wasm ;; undefended) REPO=rparrett/undefended COMMIT=81884fbe86f49715d7506122f995751de0383b2d SHA256=09ec8e4dcf076fdeb40c29c6e01147f64cb2bdd5d02a046d0e2d03c030ecf4aa PROFILE=web-dist BIN=undefended NAME=undefended TITLE="Undefended" CANVAS=fixed W=1280 H=720 ALLOW=dyn-eq WB=0.2.99 WB_SHA=e80d43ef00d0f94da07f77f42109bb10b86eb9a2a8c3dceae279c4655e77d99a INIT='{ module_or_path: wasm }' ;; taipo) REPO=rparrett/taipo COMMIT=3743db6cd6168feb92e6538361491b56e842bb83 SHA256=796ed83e4d09ea6dc6eea9908d8aae4d86dbaafb4de7fc816ac1d9f8beedcb18 PROFILE=web-dist BIN=taipo NAME=taipo TITLE="Taipo" CANVAS=fixed W=720 H=480 BG='#2b2c2f' WB=0.2.100 WB_SHA=69f25cb910de7e19777b3f93347f5e62a64c8f81709b41ba7242d00a9543573c INIT='{ module_or_path: wasm }' ;; gaia-maker) REPO=garkimasera/gaia-maker COMMIT=246ba31bf7f7e5341b865bf18e9fea175d04c0fb SHA256=cb48954f1430bcd3fa135b77bf2dec4b04a339fa12a641e4c428e8da2f452428 PROFILE=release BIN=gaia-maker NAME=gaia_maker TITLE="Gaia Maker" CANVAS=gaia BG='#05060d' FEATURES=asset_tar ASSETS=tar ALLOW=option-ext,symphonia,symphonia-codec-vorbis,symphonia-core,symphonia-format-ogg,symphonia-metadata,symphonia-utils-xiph,triple_buffer EXTRAFLAGS='--cfg getrandom_backend="wasm_js"' WB=0.2.100 WB_SHA=69f25cb910de7e19777b3f93347f5e62a64c8f81709b41ba7242d00a9543573c INIT='{ module_or_path: wasm }' ;; *) echo "jogo desconhecido: $1" >&2; exit 1 ;; esac } # Arquivos de assets que ficam fora do pacote (existem no repositorio, mas o jogo nao os usa). prune_assets() { # slug pasta case "$1" in # Fora: as capturas do README e os .ase (fontes do Aseprite), que o jogo nao carrega. tower-quest-3200) rm -f "$2/images/cover.png" "$2/images/ss1.png" "$2/images/ss2.png" "$2/images/ss3.png" find "$2" -name "*.ase" -delete ;; esac } mit_text() { # titular do copyright echo "------------------------------------------------------------------------" echo "MIT License" echo "------------------------------------------------------------------------" echo "Copyright (c) $1" echo echo "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated" echo "documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the" echo "rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit" echo "persons to whom the Software is furnished to do so, subject to the following conditions:" echo echo "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the" echo "Software." echo echo "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE" echo "WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR" echo "COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR" echo "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." echo } # Texto de licenca do jogo (do repositorio, no commit pinado) + o dos crates que entram no .wasm. write_licenses() { # slug arvore crates.txt local slug="$1" tree="$2" crates="$3" f case "$slug" in tower-quest-3200) echo "Tower Quest 3200 - licence" echo "==========================" echo echo "Source: https://github.com/$REPO at commit $COMMIT. The repository is licensed under the GNU GPL v3 (file LICENSE;" echo "Cargo.toml: GPL-3.0-or-later). The author's itch.io page (https://kayh-online.itch.io/tower-quest) says: \"All assets by me.\"" echo "The font assets/font/vt323.ttf is VT323, licensed under the SIL Open Font License 1.1 (assets/font/LICENSE)." echo for f in LICENSE assets/font/LICENSE; do echo "------------------------------------------------------------------------" echo "$f" echo "------------------------------------------------------------------------" cat "$tree/$f"; echo done ;; undefended) echo "Undefended - licence" echo "====================" echo echo "Source: https://github.com/$REPO at commit $COMMIT. The repository's LICENSE file says: UNDEFENDED is dual-licensed under" echo "either the MIT License or the Apache License, Version 2.0, at your option. (It points to docs/LICENSE-MIT and" echo "docs/LICENSE-APACHE, which the repository does not contain; the standard texts are reproduced below.)" echo "The README says: \"All other assets are original creations by me for this project.\" The only third-party assets" echo "are two fonts under the SIL Open Font License 1.1: Orbitron (Copyright 2018 The Orbitron Project Authors," echo "https://github.com/theleagueof/orbitron) and PromptFont (Copyright 2018-2023 Yukari Hafner, Copyright 2011-2016" echo "Severin Meyer, https://shinmera.com/promptfont)." echo echo "------------------------------------------------------------------------" echo "LICENSE (as in the repository)" echo "------------------------------------------------------------------------" cat "$tree/LICENSE"; echo mit_text "Rob Parrett" echo "------------------------------------------------------------------------" echo "Apache License, Version 2.0" echo "------------------------------------------------------------------------" cat "$here/bevy/Apache-2.0.txt"; echo echo "------------------------------------------------------------------------" echo "SIL Open Font License 1.1 (Orbitron and PromptFont)" echo "------------------------------------------------------------------------" cat "$here/bevy/OFL-1.1.txt"; echo ;; taipo) echo "Taipo - licence" echo "===============" echo echo "Source: https://github.com/$REPO at commit $COMMIT." echo "Code: MIT License or Apache License 2.0, at your option (LICENSE; Cargo.toml: \"MIT OR Apache-2.0\"). The LICENSE file" echo "points to docs/LICENSE-MIT and docs/LICENSE-APACHE, which the repository does not contain; the standard texts are" echo "reproduced below." echo "Assets: the repository's LICENSE says \"Enemy Art from https://github.com/mozilla/BrowserQuest is licensed under" echo "CC-BY-SA 3.0\" and \"Other assets are licensed under CC-BY-SA 4.0\". The English word list (assets/data/word_list/" echo "english.txt) is not part of this build. The two fonts are Noto Sans Light (Copyright 2015 Google LLC) and Noto Sans" echo "JP Light (Copyright 2014-2020 Adobe), both under the SIL Open Font License 1.1." echo echo "------------------------------------------------------------------------" echo "LICENSE (as in the repository)" echo "------------------------------------------------------------------------" cat "$tree/LICENSE"; echo echo "------------------------------------------------------------------------" echo "BrowserQuest notice (enemy artwork: crab, deathknight, skeleton, skeleton2, snake)" echo "------------------------------------------------------------------------" cat "$here/bevy/taipo/browserquest-notice.txt"; echo echo "------------------------------------------------------------------------" echo "Creative Commons Attribution-ShareAlike 4.0 International (the game's own assets)" echo "------------------------------------------------------------------------" cat "$here/bevy/CC-BY-SA-4.0.txt"; echo echo "------------------------------------------------------------------------" echo "Creative Commons Attribution-ShareAlike 3.0 Unported (BrowserQuest artwork)" echo "------------------------------------------------------------------------" cat "$here/bevy/CC-BY-SA-3.0.txt"; echo mit_text "Rob Parrett" echo "------------------------------------------------------------------------" echo "Apache License, Version 2.0" echo "------------------------------------------------------------------------" cat "$here/bevy/Apache-2.0.txt"; echo echo "------------------------------------------------------------------------" echo "SIL Open Font License 1.1 (Noto Sans, Noto Sans JP)" echo "------------------------------------------------------------------------" cat "$here/bevy/OFL-1.1.txt"; echo ;; gaia-maker) echo "Gaia Maker - licence" echo "====================" echo echo "Source: https://github.com/$REPO at commit $COMMIT. The README says: \"GPL v3. Files under assets are licensed as CC BY 4.0" echo "(except for font files).\" The font Mplus2 (assets/fonts/Mplus2-SemiBold.otf.gz) is M+ FONTS, Copyright 2021 The M+ FONTS" echo "Project Authors, under the SIL Open Font License 1.1 (assets/fonts/OFL.txt). The Steam edition's extra assets are not in" echo "the repository and are not part of this build. The library tile-geom (MIT, Copyright 2022 T. Okubo) is linked from" echo "https://github.com/garkimasera/tile-geom at commit ed1a5750f0e0638ea9c261b1502fb907fb6c1e48." echo for f in LICENSE assets/LICENSE assets/fonts/OFL.txt; do echo "------------------------------------------------------------------------" echo "$f" echo "------------------------------------------------------------------------" cat "$tree/$f"; echo done ;; esac echo "========================================================================" cat "$crates" } build() { local slug="$1" load "$slug" local out="$root/artifacts/build/$slug" tree="$root/artifacts/build/$slug/tree" local short="${COMMIT:0:7}" fetch "https://github.com/$REPO/archive/$COMMIT.tar.gz" "$slug-$short.tar.gz" "$SHA256" fetch "https://github.com/rustwasm/wasm-bindgen/releases/download/$WB/wasm-bindgen-$WB-$WB_ARCH.tar.gz" \ "wasm-bindgen-$WB-$WB_ARCH.tar.gz" "$WB_SHA" if [ ! -x "$tools/wasm-bindgen-$WB/wasm-bindgen" ]; then rm -rf "$tools/wasm-bindgen-$WB.tmp"; mkdir -p "$tools/wasm-bindgen-$WB.tmp" tar -xzf "$cache/wasm-bindgen-$WB-$WB_ARCH.tar.gz" -C "$tools/wasm-bindgen-$WB.tmp" --strip-components 1 mv "$tools/wasm-bindgen-$WB.tmp" "$tools/wasm-bindgen-$WB" fi rustup toolchain install "$TOOLCHAIN" --profile minimal --target wasm32-unknown-unknown >/dev/null 2>&1 export RUSTUP_TOOLCHAIN="$TOOLCHAIN" rm -rf "$out"; mkdir -p "$tree" "$out/dist" tar -xzf "$cache/$slug-$short.tar.gz" -C "$tree" --strip-components 1 for p in "$here/bevy/$slug"/*.patch; do [ -e "$p" ] || continue ( cd "$tree" && patch -p1 --no-backup-if-mismatch < "$p" >/dev/null ) || { echo "patch $p falhou" >&2; exit 1; } done [ -x "$here/bevy/$slug/prepare.sh" ] && "$here/bevy/$slug/prepare.sh" "$tree" # Os caminhos do codigo vao para dentro do .wasm (mensagens de panico): o do jogo vira /src e o do CARGO_HOME vira /cargo. local target="$tree/target/wasm32-unknown-unknown" # Licencas dos crates primeiro (falha cedo): cargo metadata baixa os crates e o script confere cada licenca. ( cd "$tree" RUST_LICENSES_ALLOW="$ALLOW" cargo metadata --locked --format-version 1 --filter-platform wasm32-unknown-unknown ${FEATURES:+--features $FEATURES} 2>/dev/null \ | RUST_LICENSES_ALLOW="$ALLOW" python3 -I "$here/gridbugs/rust-licenses.py" "$BIN" "$out/crates.txt" ) [ -n "${LICENSES_ONLY:-}" ] && { cat "$out/crates.txt.copyleft" 2>/dev/null || true; return 0; } ( cd "$tree" CARGO_INCREMENTAL=0 RUSTFLAGS="--remap-path-prefix=$tree=/src --remap-path-prefix=$CARGO_HOME=/cargo $EXTRAFLAGS" \ cargo build --locked --profile "$PROFILE" --target wasm32-unknown-unknown ${FEATURES:+--features $FEATURES} 2> "$out/cargo.log" \ || { tail -40 "$out/cargo.log"; exit 1; } local profdir="$PROFILE"; [ "$PROFILE" = release ] && profdir=release shasum -a 256 "$target/$profdir/$BIN.wasm" > "$out/cargo-wasm.sha256" "$tools/wasm-bindgen-$WB/wasm-bindgen" --target web --no-typescript --remove-name-section --remove-producers-section \ --out-name "$NAME" --out-dir "$out/dist" "$target/$profdir/$BIN.wasm" ) # Imagens, sons e fontes que o jogo carrega em tempo de execucao. if [ "$ASSETS" != tar ]; then mkdir -p "$out/dist/assets"; fi if [ "$ASSETS" = tar ]; then # Gaia Maker le os assets de um assets.tar (feature asset_tar, o caminho que o autor usa na versao web): o plugin # busca assets.tar ao lado da pagina (o leitor dele nao usa a pasta assets/). Tar deterministico, na ordem do sort, sem dono e com data fixa. local stage="$out/assets-stage"; mkdir -p "$stage" cp -R "$tree/assets/." "$stage/" prune_assets "$slug" "$stage" find "$stage" -name ".DS_Store" -delete find "$stage" -exec touch -t 202511010000 {} + # Com entradas de pasta (o plugin so lista uma pasta se ela esta no tar) e sem recursao: a ordem e a do sort. ( cd "$stage" && find . -mindepth 1 | LC_ALL=C sort | sed 's|^\./||' > ../assets-files.txt \ && COPYFILE_DISABLE=1 tar --no-recursion --format=ustar --uid 0 --gid 0 --uname root --gname root -n -cf "$out/dist/assets.tar" -T ../assets-files.txt ) rm -rf "$stage" "$out/assets-files.txt" else cp -R "$tree/assets/." "$out/dist/assets/" prune_assets "$slug" "$out/dist/assets" find "$out/dist/assets" -name ".DS_Store" -delete fi # Pagina e script de partida. local canvas='' canvascss='width: 100%; height: 100%;' if [ "$CANVAS" = fixed ]; then canvas=' ' # O canvas fica no tamanho de projeto e e escalado por transform (o start.js poe --s): o mouse continua certo. canvascss="position: fixed; left: 50%; top: 50%; width: ${W}px !important; height: ${H}px !important; transform: translate(-50%, -50%) scale(var(--s, 1));" fi if [ "$CANVAS" = gaia ]; then # O jogo pede o canvas #game-screen (escondido ate ele abrir a janela) e acompanha o tamanho da janela do navegador. canvas=' ' canvascss='' fi python3 -I - "$here/bevy/index.html.in" "$out/dist/index.html" "$TITLE" "$BG" "$canvas" "$canvascss" <<'PY' import sys src, dst, title, bg, canvas, css = sys.argv[1:7] s = open(src, encoding='utf-8').read() for k, v in (('@TITLE@', title), ('@BG@', bg), ('@CANVAS@', canvas), ('@CANVASCSS@', css)): s = s.replace(k, v) open(dst, 'w', encoding='utf-8').write(s) PY python3 -I - "$here/bevy/start.js.in" "$out/dist/start.js" "$NAME" "$INIT" "$CANVAS" "$W" "$H" <<'PY' import sys src, dst, name, init, canvas, w, h = sys.argv[1:8] s = open(src, encoding='utf-8').read().replace('@NAME@', name).replace('@INIT@', init) s = s.replace('@FIXED@', 'true' if canvas == 'fixed' else 'false').replace('@W@', w).replace('@H@', h) open(dst, 'w', encoding='utf-8').write(s) PY # O .wasm sai a parte (o Bundler comprime); o resto vira um tar.gz deterministico (datas fixas, ordem fixa, sem dono). mv "$out/dist/${NAME}_bg.wasm" "$out/${NAME}_bg.wasm" write_licenses "$slug" "$tree" "$out/crates.txt" > "$out/$slug-licenses.txt" find "$out/dist" -exec touch -t 202511010000 {} + ( cd "$out" && find dist -type f | LC_ALL=C sort > files.txt \ && COPYFILE_DISABLE=1 tar --format=ustar --uid 0 --gid 0 --uname root --gname root -n -cf - -T files.txt \ | gzip -n -9 > "$slug-$short-dist.tar.gz" && rm files.txt ) rm -rf "$tree" cat "$out/cargo-wasm.sha256" ls -l "$out/${NAME}_bg.wasm" "$out/$slug-$short-dist.tar.gz" shasum -a 256 "$out/${NAME}_bg.wasm" "$out/$slug-$short-dist.tar.gz" "$out/$slug-licenses.txt" } for g in "${@:-tower-quest-3200 undefended taipo gaia-maker}"; do for s in $g; do build "$s"; done; done