#!/usr/bin/env bash # Compila o LinCity-NG do fredygames.com: o jogo de construcao de cidade do projeto LinCity-NG (codigo # GPL-2.0-or-later; modelos, imagens, sons, musica e XML com licenca dupla GPL / CC BY-SA 2.0 pelo # COPYING-data.txt; fonte DejaVu Sans pelo COPYING-fonts.txt) em WebAssembly, com o Emscripten 6.0.10 e # as portas SDL3 3.4.2 e SDL3_ttf 3.2.2 (FreeType, HarfBuzz) dele, mais bibliotecas compiladas aqui: # SDL3_image 3.4.8 (zlib; PNG pelo stb_image embutido), SDL3_mixer 3.2.4 (zlib; WAV e Ogg Vorbis pelo # stb_vorbis embutido), libxml2 2.15.4 (MIT), libxml++ 5.6.1 (LGPL-2.1-or-later) e fmt 12.2.0 (MIT, so # cabecalhos). 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-lincity-ng.sh # precisa tambem de cmake, make e xsltproc # # Fontes: os tarballs das releases (lincity-ng-2.15.0-Source.tar.xz do GitHub do projeto, os das # bibliotecas nos enderecos oficiais), conferidos pelo SHA-256 e guardados em artifacts/cache/lincity-ng/ # (o site hospeda os mesmos arquivos). Na pasta de fontes do site, o script acha os tarballs e o patch ao # lado dele. # # Sem o CMake do jogo (ele exige as bibliotecas instaladas): os mesmos arquivos que ele compila, sem o # modo OpenGL (desligado por padrao no projeto) e sem traducoes (sem gettext; o jogo fica em ingles). Os # dados vao para /usr/share/lincity-ng num lincity-ng.data que o proprio Emscripten carrega # (--preload-file), montados como o CMake os instala: fonts/, help/, images/*.png e *.xml, music/, # opening/*.scn.gz, sounds/ e os XML de gui/ passados pelo identity.xsl e pelo # dialogs.xsl com NLS desligado. Os modelos do Blender (data/models) e as SVG ficam so no fonte. # # lincity-ng-fredygames.patch (so ativo com __EMSCRIPTEN__): # - cfgpath: o navegador usa os caminhos do Linux ($HOME/.config e $HOME/.local/share); # - os sons e as imagens carregam no proprio laco do jogo, sem threads (o build nao usa pthreads); # - os lacos do menu e do jogo devolvem a vez ao navegador a cada volta (o build usa Asyncify); # - fredygames_save(), que a pagina chama quando a aba some ou fecha: grava as opcoes. # # Saida: artifacts/build/lincity-ng-2.15.0/lincity-ng.{js,wasm,data}, que o bundle.yaml do jogo confere # pelo SHA-256. set -euo pipefail VERSION=2.15.0 SRC_SHA256=bc1eba0af24dd451ddf3e5ac8c335b48dcb29033ada424a3f5a798c3b1d91999 IMAGE=3.4.8 IMAGE_SHA256=e8223b424bc7541cf84ffff5cf7e4f24ec3711c60462cb1bd6dc1d7bd7f25477 MIXER=3.2.4 MIXER_SHA256=182a07c745375e113dc740d43964ff21b0be29f29f59876c4dbc4db3d32f6901 XML2=2.15.4 XML2_SHA256=98087fd181d9070724f3fbc65c7377db03038eb92bd882374daff44940138821 XMLPP=5.6.1 XMLPP_SHA256=4996e8a73995e8a4cd656c8591dce38181146edfc30cb47c97d1db3c56990ad7 FMT=12.2.0 FMT_SHA256=a2f4a8d51178f954e4c339007f77edd76ba0cb2e36f87a48e5a5403d9be5878f 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/lincity-ng-$VERSION" cache="$root/artifacts/cache/lincity-ng" 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 for tool in cmake make xsltproc; do command -v "$tool" >/dev/null || { echo "Precisa do $tool no PATH." >&2; exit 1; } done # Arquivo auxiliar: em lincity-ng/ (repositorio) ou ao lado do script (pasta de fontes do site). aux() { if [ -f "$here/lincity-ng/$1" ]; then echo "$here/lincity-ng/$1"; else echo "$here/$1"; fi } # Tarball ao lado do script, no cache ou baixado do endereco original; confere o 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" } src_tar="$(fetch "lincity-ng-$VERSION-Source.tar.xz" \ "https://github.com/lincity-ng/lincity-ng/releases/download/lincity-ng-$VERSION/lincity-ng-$VERSION-Source.tar.xz" \ "$SRC_SHA256")" image_tar="$(fetch "SDL3_image-$IMAGE.tar.gz" \ "https://github.com/libsdl-org/SDL_image/releases/download/release-$IMAGE/SDL3_image-$IMAGE.tar.gz" "$IMAGE_SHA256")" mixer_tar="$(fetch "SDL3_mixer-$MIXER.tar.gz" \ "https://github.com/libsdl-org/SDL_mixer/releases/download/release-$MIXER/SDL3_mixer-$MIXER.tar.gz" "$MIXER_SHA256")" xml2_tar="$(fetch "libxml2-$XML2.tar.xz" \ "https://download.gnome.org/sources/libxml2/${XML2%.*}/libxml2-$XML2.tar.xz" "$XML2_SHA256")" xmlpp_tar="$(fetch "libxml++-$XMLPP.tar.xz" \ "https://github.com/libxmlplusplus/libxmlplusplus/releases/download/$XMLPP/libxml%2B%2B-$XMLPP.tar.xz" "$XMLPP_SHA256")" fmt_zip="$(fetch "fmt-$FMT.zip" "https://github.com/fmtlib/fmt/releases/download/$FMT/fmt-$FMT.zip" "$FMT_SHA256")" rm -rf "$out" mkdir -p "$out/obj" "$out/prefix" for t in "$src_tar" "$xml2_tar" "$xmlpp_tar"; do tar xJf "$t" -C "$out"; done for t in "$image_tar" "$mixer_tar"; do tar xzf "$t" -C "$out"; done unzip -q "$fmt_zip" -d "$out" s="lincity-ng-$VERSION-Source" patch -d "$out/$s" -p1 --no-backup-if-mismatch < "$(aux lincity-ng-fredygames.patch)" cd "$out" # -ffile-prefix-map tira o caminho desta maquina dos binarios; -O2 em tudo. cflags=(-O2 -ffile-prefix-map="$out/=" -w) # libxml2: so a biblioteca, sem threads, iconv, modulos e programas. O prefixo /usr entra no binario # (caminho do catalogo XML); a instalacao vai para prefix/. emcmake cmake -S "libxml2-$XML2" -B build-libxml2 -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_C_FLAGS="${cflags[*]}" -DCMAKE_INSTALL_PREFIX=/usr \ -DBUILD_SHARED_LIBS=OFF -DLIBXML2_WITH_ICONV=OFF -DLIBXML2_WITH_THREADS=OFF \ -DLIBXML2_WITH_MODULES=OFF -DLIBXML2_WITH_PROGRAMS=OFF -DLIBXML2_WITH_TESTS=OFF \ -DLIBXML2_WITH_PYTHON=OFF -DLIBXML2_WITH_ZLIB=OFF -DLIBXML2_WITH_LZMA=OFF >/dev/null cmake --build build-libxml2 -j4 >/dev/null cmake --install build-libxml2 --prefix "$out/prefix" >/dev/null # libxml++: o config.h que o meson geraria, e todos os .cc da biblioteca. p="libxml++-$XMLPP" sed -e 's/#undef LIBXMLXX_HAVE_EXCEPTION_PTR/#define LIBXMLXX_HAVE_EXCEPTION_PTR 1/' \ -e "s/#undef LIBXMLXX_MAJOR_VERSION/#define LIBXMLXX_MAJOR_VERSION ${XMLPP%%.*}/" \ -e "s/#undef LIBXMLXX_MINOR_VERSION/#define LIBXMLXX_MINOR_VERSION $(echo "$XMLPP" | cut -d. -f2)/" \ -e "s/#undef LIBXMLXX_MICRO_VERSION/#define LIBXMLXX_MICRO_VERSION ${XMLPP##*.}/" \ "$p/libxml++config.h.in" > "$p/libxml++config.h" for f in $(cd "$p" && find libxml++ -name '*.cc' | LC_ALL=C sort); do o="obj/xmlpp_$(echo "${f%.cc}" | tr / _).o" em++ "${cflags[@]}" -std=c++17 -DLIBXMLPP_BUILD -I"$p" -I"prefix/include/libxml2" -c "$p/$f" -o "$o" done # SDL3_image: so PNG, pelo stb_image embutido. i="SDL3_image-$IMAGE" for f in $(LC_ALL=C ls "$i"/src/IMG*.c); do emcc "${cflags[@]}" -sUSE_SDL=3 -DUSE_STBIMAGE -DLOAD_PNG -DSAVE_PNG=0 -I"$i/include" -I"$i/src" \ -c "$f" -o "obj/img_$(basename "$f" .c).o" done # SDL3_mixer: WAV e Ogg Vorbis (stb_vorbis embutido). m="SDL3_mixer-$MIXER" for f in $(LC_ALL=C ls "$m"/src/*.c); do emcc "${cflags[@]}" -sUSE_SDL=3 -DBUILD_SDL -DDECODER_WAV -DDECODER_OGGVORBIS_STB \ -DSDL_BUILD_MAJOR_VERSION=3 -DSDL_BUILD_MINOR_VERSION="$(echo "$MIXER" | cut -d. -f2)" \ -DSDL_BUILD_MICRO_VERSION="${MIXER##*.}" \ -I"$m/include" -I"$m/src" -I"$m/src/codecs" -c "$f" -o "obj/mix_$(basename "$f" .c).o" done # O config.h que o CMake do jogo geraria. sed -e 's/${CMAKE_PROJECT_NAME}/lincity-ng/' -e "s/\${FULL_PROJECT_VERSION}/$VERSION/" \ -e 's/${PROJECT_NAME_PRETTY}/LinCity-NG/' -e 's|${CMAKE_INSTALL_PREFIX}|/usr|' \ -e 's|${CMAKE_INSTALL_BINDIR}|bin|' -e 's|${CMAKE_INSTALL_DATADIR}|share|' \ -e 's|${CMAKE_INSTALL_APPDATADIR}|share/lincity-ng|' -e 's|${CMAKE_INSTALL_FULL_BINDIR}|/usr/bin|' \ -e 's|${CMAKE_INSTALL_FULL_DATADIR}|/usr/share|' \ -e 's|${CMAKE_INSTALL_FULL_APPDATADIR}|/usr/share/lincity-ng|' \ -e 's|#cmakedefine LINCITYNG_RELOCATABLE|/* #undef LINCITYNG_RELOCATABLE */|' \ -e 's|#cmakedefine HAVE_NL_MSG_CAT_CNTR|/* #undef HAVE_NL_MSG_CAT_CNTR */|' \ "$s/src/config.h.in" > "$s/src/config.h" # O jogo: os mesmos .cpp que os CMakeLists.txt de src/ compilam, sem o PainterGL. game_srcs=$(cd "$s/src" && LC_ALL=C ls lincity-ng/*.cpp lincity/*.cpp lincity/modules/*.cpp gui/*.cpp \ gui/PainterSDL/*.cpp util/*.cpp) for f in $game_srcs; do em++ "${cflags[@]}" -std=c++17 -sUSE_SDL=3 -sUSE_SDL_TTF=3 -sUSE_ZLIB=1 \ -DUNIX -DDISABLE_GL_MODE -DDEFAULT_LIBDIR='"lib"' -DFMT_HEADER_ONLY \ -fmacro-prefix-map="$out/$s=." \ -I"$s/src" -I"$s/src/lincity" -I"$s/src/lincity-ng" -I"$s/src/gui" -I"$s/src/util" \ -I"$s/src/lincity/modules" -I"$s/lib/cfgpath" -I"$s/lib/gettext" \ -I"$i/include" -I"$m/include" -I"$p" -I"prefix/include/libxml2" -I"fmt-$FMT/include" \ -c "$s/src/$f" -o "obj/game_$(echo "${f%.cpp}" | tr / _).o" done # Os dados, montados como o CMake os instala (data/CMakeLists.txt e data/gui/). d="data-root/usr/share/lincity-ng" mkdir -p "$d" ( cd "$s/data" { find fonts help music sounds -type f find images -type f \( -name '*.png' -o -name '*.xml' \) find opening -type f -name '*.scn.gz' } | LC_ALL=C sort ) | while read -r f; do mkdir -p "$d/$(dirname "$f")" cp "$s/data/$f" "$d/$f" done mkdir -p "$d/gui/dialogs" for f in app buttonpanel creditslist credits dialog helpwindow loadgame mainmenu minimapswitchbuttons \ minimap mps newgame options pbar2nd pbar savegame speedpanel; do (cd "$s/data/gui" && xsltproc --stringparam NLS disabled -o "$out/$d/gui/$f.xml" "$f.xml") done for f in coal_survey_yn gamestats launch_rocket_yn portdialog tradedialog; do (cd "$s/data/gui/dialogs" && xsltproc --stringparam NLS disabled -o "$out/$d/gui/dialogs/$f.xml" "$f.xml") done em++ "${cflags[@]}" -sUSE_SDL=3 -sUSE_SDL_TTF=3 -sUSE_ZLIB=1 $(LC_ALL=C ls obj/*.o) \ prefix/lib/libxml2.a -lidbfs.js \ -sASYNCIFY -sASYNCIFY_STACK_SIZE=65536 -sALLOW_MEMORY_GROWTH -sINITIAL_MEMORY=256MB -sSTACK_SIZE=2MB \ -sENVIRONMENT=web -sEXIT_RUNTIME=1 -sEXPORTED_RUNTIME_METHODS=FS,addRunDependency,removeRunDependency \ --preload-file "data-root/usr@/usr" \ -o lincity-ng.js shasum -a 256 "$out/lincity-ng.js" "$out/lincity-ng.wasm" "$out/lincity-ng.data"