#!/usr/bin/env bash # Compila o Bos Wars 2.8 do fredygames.com: o jogo de estrategia em tempo real do projeto Bos Wars (motor derivado # do Stratagus, codigo, scripts, graficos e sons sob a GPL-2.0-or-later: COPYRIGHT.txt diz que a licenca cobre o # jogo inteiro) em WebAssembly, com o Emscripten 6.0.10, a porta SDL2 2.32.10 dele, o libpng/zlib e o libvorbis das # portas dele e o Lua 5.1.5 (MIT) compilado junto. Este script, o patch e os utilitarios 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-bos-wars.sh # # Fontes: o tarball original do Debian (boswars_2.8.orig.tar.gz) e o do Lua 5.1.5, conferidos pelo SHA-256 e # guardados em artifacts/cache/bos-wars/ (o site hospeda o mesmo tarball). Na pasta de fontes do site, o script # acha os arquivos ao lado dele. # # O motor e escrito para o SDL 1.2, que nao tem porta no Emscripten. O patch (bos-wars-fredygames.patch) acrescenta # engine/sdl1compat/ (so o pedaco do SDL 1.2 que o jogo usa, sobre o SDL2: tela de 32 bits em software enviada a # uma textura, eventos, paleta, color key, alfa) e tem estas mudancas: # - o renderer e sempre o de software (sem OpenGL; os gl* do desenho imediato viram no-ops que nunca rodam); # - sem rede: engine/network/{lowlevel,netconnect,network}.cpp ficam fora e network_stub.cpp define o que # sobra como no-op (IsNetworkGame() e sempre falso); o menu MultiPlayer sai; # - sem Theora (o motor ja compila sem USE_THEORA) e sem editor de mapas no menu; # - o programa nao pode ser fechado de dentro da pagina: Exit e "Exit Bos Wars" saem dos menus; # - a aba em segundo plano pausa o jogo (SDL_WINDOWEVENT_HIDDEN vira o SDL_ACTIVEEVENT do SDL 1.2). # Cada quadro devolve a vez ao navegador pelo SDL_Delay (Asyncify; excecoes do C++ pelo -fexceptions). # # Dados (/bw, carregados pelo --preload-file): scripts, graficos, unidades, campanhas e idiomas inteiros; mapas # podados (sem a campanha Islands; ver MAPS_OUT abaixo: os maiores, que sao so terreno pintado de 3 a 15 MB, ficam de fora) e so os # patches de terreno que os mapas e campanhas que ficam usam; os sons WAV viram Ogg Vorbis (wav2ogg de # Builds/pygame-music, qualidade 0.3; mono vira estereo por wav2stereo.py) e os scripts passam a apontar para .ogg; # o video de abertura (intro/boswars_intro.ogg, que o jogo nem usa) fica de fora. # # Saida: artifacts/build/bos-wars-2.8/bos-wars.{js,wasm,data}, que o bundle.yaml do jogo confere pelo SHA-256. set -euo pipefail VERSION=2.8 SRC_SHA256=5185206990bf6765c7a5d3802ce6e6048a1087cf4c307b099a5691a3eec6b86a LUA_SHA256=2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333 EMSCRIPTEN=6.0.10 OGG_QUALITY=0.3 # Mapas que ficam de fora (terreno pintado: 3 a 15 MB cada) e o mapa de teste do editor. MAPS_OUT="wetlands01.map guerillawarfare.map bridge.map bridge2far.map wetlands02.map canyon.map river.map patches.map" here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" root="$(git -C "$here" rev-parse --show-toplevel 2>/dev/null || pwd)" out="$root/artifacts/build/bos-wars-$VERSION" cache="$root/artifacts/cache/bos-wars" tools="$root/artifacts/tools" 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 wav2ogg="$tools/pygame-music/wav2ogg" [ -x "$wav2ogg" ] || { echo "Compile antes o wav2ogg: $here/pygame-music/README" >&2; exit 1; } # Arquivo auxiliar: em bos-wars/ (repositorio) ou ao lado do script (pasta de fontes do site). aux() { if [ -f "$here/bos-wars/$1" ]; then echo "$here/bos-wars/$1"; else echo "$here/$1"; fi } # Arquivo baixado: ao lado do script, ou no cache (baixa se faltar). fetch() { local name="$1" url="$2" sha="$3" path if [ -f "$here/$name" ]; then path="$here/$name" else path="$cache/$name" if [ ! -f "$path" ]; then mkdir -p "$cache" curl -fsSL -o "$path.part" "$url" mv "$path.part" "$path" fi fi echo "$sha $path" | shasum -a 256 -c - >&2 echo "$path" } src_tar="$(fetch boswars_$VERSION.orig.tar.gz "https://deb.debian.org/debian/pool/main/b/boswars/boswars_$VERSION.orig.tar.gz" "$SRC_SHA256")" lua_tar="$(fetch lua-5.1.5.tar.gz "https://www.lua.org/ftp/lua-5.1.5.tar.gz" "$LUA_SHA256")" rm -rf "$out" mkdir -p "$out/obj" tar xzf "$src_tar" -C "$out" s="$out/boswars-$VERSION-src" [ -d "$s" ] || { echo "O tarball nao tem boswars-$VERSION-src/" >&2; exit 1; } tar xzf "$lua_tar" -C "$out" lua="$out/lua-5.1.5/src" patch -d "$s" -p1 --no-backup-if-mismatch < "$(aux bos-wars-fredygames.patch)" >/dev/null # ---- Compilacao: todos os .cpp do motor (menos a rede de verdade) e o Lua (como C) ---- export SOURCE_DATE_EPOCH=0 flags="-O2 -fsigned-char -D_GNU_SOURCE=1 -D_REENTRANT -fexceptions -w -ffile-prefix-map=$out/= \ -I$s/engine/sdl1compat -I$s/engine/include -I$s/engine/guichan/include -I$lua \ -sUSE_SDL=2 -sUSE_LIBPNG=1 -sUSE_VORBIS=1 -sUSE_ZLIB=1 -DUSE_VORBIS" export flags out s compile() { local f="$1" o o="$out/obj/$(echo "${f#$out/}" | tr '/.' '__').o" case "$f" in *.c) emcc $flags -c "$f" -o "$o" ;; *) em++ -std=gnu++14 $flags -c "$f" -o "$o" ;; esac } export -f compile { (cd "$s" && find engine \( -name '*.cpp' -o -name '*.c' \) | sort \ | grep -v -E 'engine/network/(lowlevel|netconnect|network)\.cpp$' | sed "s#^#$s/#") ls "$lua"/*.c | grep -v -E '/(lua|luac|print)\.c$' } > "$out/sources.list" xargs -P "$(sysctl -n hw.ncpu 2>/dev/null || nproc)" -n 1 bash -c 'compile "$0"' < "$out/sources.list" # ---- Dados ---- stage="$out/stage" mkdir -p "$stage" for d in campaigns graphics languages patches scripts sounds units maps; do cp -R "$s/$d" "$stage/$d"; done mkdir -p "$stage/intro" cp "$s/intro/logo_boswars.png" "$stage/intro/" cp "$s/COPYRIGHT.txt" "$s/LICENSE.txt" "$stage/" find "$stage" \( -name '.DS_Store' -o -name 'README&FAQ.txt' \) -delete for m in $MAPS_OUT; do rm -rf "$stage/maps/$m"; done # Campanha Islands (de Connor MacKay, fora por decisao do projeto do site): a pasta inteira, antes da poda dos patches. rm -rf "$stage/campaigns/islands" # Patches de terreno: so os PNG que os mapas e campanhas que ficam usam (os .lua com as definicoes ficam todos). python3 -I - "$stage" <<'PY' import glob, os, re, sys stage = sys.argv[1] os.chdir(stage) defs = {} for f in glob.glob('patches/*.lua'): for m in re.finditer(r'patchType\("([^"]+)",\s*(?:GetCurrentLuaPath\(\)\s*\.\.\s*"/([^"]+)"|"([^"]+)")', open(f, encoding='utf-8', errors='replace').read()): defs[m.group(1)] = m.group(3) or 'patches/' + m.group(2) used = set() for d in ('maps', 'campaigns'): for f in glob.glob(d + '/**/*.lua', recursive=True) + glob.glob(d + '/**/*.sms', recursive=True) + glob.glob(d + '/**/*.smp', recursive=True): text = open(f, encoding='utf-8', errors='replace').read() for n in re.findall(r'patch\("([^"]+)"', text): if n in defs: used.add(defs[n]) # Mapas que citam o PNG do patch direto (ex.: multiple-fronts-total-war usa brown-mud-960x640.png). used.update(re.findall(r'"(patches/[^"]+\.png)"', text)) removed = 0 for f in glob.glob('patches/**/*.png', recursive=True): if f not in used: os.remove(f) removed += 1 for d in sorted(glob.glob('patches/*/')): if not os.listdir(d): os.rmdir(d) print(f"patches: {len(used)} PNG mantidos, {removed} removidos", file=sys.stderr) PY # Sons: WAV -> Ogg Vorbis. O click vem como .wav.gz no tarball. [ -f "$stage/sounds/ui/click.wav.gz" ] && gunzip -f "$stage/sounds/ui/click.wav.gz" tmpwav="$out/tmp.wav" while IFS= read -r w; do python3 -I "$(aux wav2stereo.py)" "$w" "$tmpwav" "$wav2ogg" "$tmpwav" "${w%.wav}.ogg" "$OGG_QUALITY" rm -f "$w" done < <(find "$stage" -name '*.wav' | sort) rm -f "$tmpwav" # Os scripts apontam para .ogg (o motor tenta WAV e depois Vorbis, pelo conteudo). find "$stage" \( -name '*.lua' -o -name '*.sms' -o -name '*.smp' \) -print0 | xargs -0 perl -pi -e 's/\.wav"/.ogg"/g' echo "dados: $(du -sk "$stage" | cut -f1) KB" >&2 # ---- Ligacao ---- em++ -O2 "$out"/obj/*.o -o "$out/bos-wars.js" \ -sUSE_SDL=2 -sUSE_LIBPNG=1 -sUSE_VORBIS=1 -sUSE_ZLIB=1 -fexceptions -sASYNCIFY \ -sALLOW_MEMORY_GROWTH -sINITIAL_MEMORY=128MB -sSTACK_SIZE=1MB -sENVIRONMENT=web -sEXIT_RUNTIME=1 \ -lidbfs.js -sEXPORTED_RUNTIME_METHODS=FS,ENV -sFORCE_FILESYSTEM=1 \ --preload-file "$stage@/bw" ls -l "$out"/bos-wars.{js,wasm,data} shasum -a 256 "$out"/bos-wars.{js,wasm,data}