Files
valknarandClaude Sonnet 5 9479cea280 Add real PinMAME (VPinMAME.Controller) integration for ROM-based tables
Statically links the real PinMAME emulation core (libpinmame) instead of
leaving VPinMAME.Controller creation to fail, which was crashing table
scripts on ROM-based tables before they could spawn a ball. PinMAME's own
run_game()->cpu_run() loop is split into a one-shot init, a per-frame step,
and a one-shot teardown (patches/pinmame/0004) so it runs cooperatively on
the same frame callback as vpinball's own loop instead of on a real
std::thread, which hard-aborts under Emscripten's single-threaded runtime -
three smaller wasm32 portability fixes to libpinmame itself round out the
patch set (0001-0003). Adds pinball.loadRom() to supply a ROM zip, written
to the table-relative pinmame/roms/ path vpinball's own plugin already
checks. Confirmed against a real community ROM-based table: Controller
creation and game identification succeed, and a missing ROM now fails
cleanly instead of crashing the page - actual ROM-driven gameplay is still
unconfirmed since no ROM was available (or sought out) to test with.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SoSarxLgY33Kax5UNXcafZ
2026-08-23 12:09:35 +02:00

163 lines
8.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# Builds the wasm32 static-library dependency set vpinball needs:
# SDL3, SDL3_image, SDL3_ttf (+freetype), FreeImage, libwinevbs.
#
# Modeled on vendor/vpinball/platforms/linux-x64/external.sh's own build
# sections, with `cmake` swapped for `emcmake cmake` and shared libs swapped
# for static (a single wasm binary has no runtime dynamic loader). Codec/font
# backends are scoped down from vpinball's native default to what's realistic
# for browser-loaded .vpx textures (PNG/JPEG/BMP/etc. via SDL3_image's
# built-in decoders + stb, no libpng/libjpeg/libwebp/libtiff/libjxl/libavif
# needed at all; SDL3_ttf uses vendored freetype only, no harfbuzz/plutosvg)
# - this avoids pulling in several hundred MB of vendored codec submodules
# (AV1/AVIF in particular) that real VPX tables don't exercise.
#
# Requires: scripts/setup.sh has already run (vendor/ populated+patched,
# emsdk installed).
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
source "$ROOT_DIR/scripts/versions.sh"
VENDOR_DIR="$ROOT_DIR/vendor"
DEPS_DIR="$ROOT_DIR/build/deps-wasm32"
WORK_DIR="$ROOT_DIR/build/deps-src"
NUM_PROCS="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)"
if [ ! -d "$VENDOR_DIR/vpinball" ] || [ ! -d "$VENDOR_DIR/libwinevbs" ]; then
echo "ERROR: vendor/ not populated. Run scripts/setup.sh first." >&2
exit 1
fi
if ! command -v emcc >/dev/null 2>&1; then
echo "ERROR: emcc not on PATH. Run: source $ROOT_DIR/emsdk/emsdk_env.sh" >&2
exit 1
fi
mkdir -p "$DEPS_DIR/lib" "$DEPS_DIR/include" "$WORK_DIR"
cache_check() { [ "$(cat "$1/.cache-sha" 2>/dev/null || true)" = "$2" ]; }
# --- SDL3 core -------------------------------------------------------------
if ! cache_check "$WORK_DIR/SDL3/SDL" "$SDL_SHA"; then
echo "== Building SDL3 core for wasm32 =="
rm -rf "$WORK_DIR/SDL3/SDL"
mkdir -p "$WORK_DIR/SDL3"
cd "$WORK_DIR/SDL3"
curl -sL "https://github.com/libsdl-org/SDL/archive/${SDL_SHA}.tar.gz" | tar xz
mv "SDL-${SDL_SHA}" SDL
( cd SDL && emcmake cmake -DSDL_SHARED=OFF -DSDL_STATIC=ON -DSDL_TEST_LIBRARY=OFF \
-DCMAKE_BUILD_TYPE=Release -B build && cmake --build build -- -j"$NUM_PROCS" )
echo "$SDL_SHA" > "$WORK_DIR/SDL3/SDL/.cache-sha"
cd "$ROOT_DIR"
fi
# --- SDL3_image (built-in decoders + stb only, no vendored codec libs) ----
if ! cache_check "$WORK_DIR/SDL3/SDL_image" "$SDL_IMAGE_SHA"; then
echo "== Building SDL3_image for wasm32 =="
rm -rf "$WORK_DIR/SDL3/SDL_image"
cd "$WORK_DIR/SDL3"
curl -sL "https://github.com/libsdl-org/SDL_image/archive/${SDL_IMAGE_SHA}.tar.gz" | tar xz
mv "SDL_image-${SDL_IMAGE_SHA}" SDL_image
( cd SDL_image && emcmake cmake \
-DBUILD_SHARED_LIBS=OFF -DSDLIMAGE_SAMPLES=OFF -DSDLIMAGE_VENDORED=OFF \
-DSDLIMAGE_AVIF=OFF -DSDLIMAGE_WEBP=OFF -DSDLIMAGE_JXL=OFF -DSDLIMAGE_TIF=OFF \
-DSDLIMAGE_PNG_LIBPNG=OFF \
-DSDL3_DIR=../SDL/build -DCMAKE_BUILD_TYPE=Release -B build \
&& cmake --build build -- -j"$NUM_PROCS" )
echo "$SDL_IMAGE_SHA" > "$WORK_DIR/SDL3/SDL_image/.cache-sha"
cd "$ROOT_DIR"
fi
# --- SDL3_ttf (vendored freetype only, no harfbuzz/plutosvg) --------------
if ! cache_check "$WORK_DIR/SDL3/SDL_ttf" "$SDL_TTF_SHA"; then
echo "== Building SDL3_ttf for wasm32 =="
rm -rf "$WORK_DIR/SDL3/SDL_ttf"
cd "$WORK_DIR/SDL3"
curl -sL "https://github.com/libsdl-org/SDL_ttf/archive/${SDL_TTF_SHA}.tar.gz" | tar xz
mv "SDL_ttf-${SDL_TTF_SHA}" SDL_ttf
( cd SDL_ttf && git clone --filter=blob:none --depth 1 \
https://github.com/libsdl-org/freetype.git external/freetype \
&& emcmake cmake \
-DBUILD_SHARED_LIBS=OFF -DSDLTTF_SAMPLES=OFF -DSDLTTF_VENDORED=ON \
-DSDLTTF_HARFBUZZ=OFF -DSDLTTF_PLUTOSVG=OFF \
-DSDL3_DIR=../SDL/build -DCMAKE_BUILD_TYPE=Release -B build \
&& cmake --build build -- -j"$NUM_PROCS" )
echo "$SDL_TTF_SHA" > "$WORK_DIR/SDL3/SDL_ttf/.cache-sha"
cd "$ROOT_DIR"
fi
cp "$WORK_DIR"/SDL3/SDL/build/libSDL3.a "$DEPS_DIR/lib/"
cp "$WORK_DIR"/SDL3/SDL_image/build/libSDL3_image.a "$DEPS_DIR/lib/"
cp "$WORK_DIR"/SDL3/SDL_ttf/build/libSDL3_ttf.a "$DEPS_DIR/lib/"
cp "$WORK_DIR"/SDL3/SDL_ttf/build/external/freetype/libfreetype.a "$DEPS_DIR/lib/"
cp -r "$WORK_DIR/SDL3/SDL/include/SDL3" "$DEPS_DIR/include/"
cp -r "$WORK_DIR/SDL3/SDL_image/include/SDL3_image" "$DEPS_DIR/include/"
cp -r "$WORK_DIR/SDL3/SDL_ttf/include/SDL3_ttf" "$DEPS_DIR/include/"
# --- FreeImage (bundles all its codecs as vendored source, no submodules) -
if ! cache_check "$WORK_DIR/freeimage" "$FREEIMAGE_SHA"; then
echo "== Building FreeImage for wasm32 =="
rm -rf "$WORK_DIR/freeimage"
mkdir -p "$WORK_DIR/freeimage"
cd "$WORK_DIR/freeimage"
curl -sL "https://github.com/toxieainc/freeimage/archive/${FREEIMAGE_SHA}.tar.gz" | tar xz
mv "freeimage-${FREEIMAGE_SHA}" freeimage
# PLATFORM/ARCH here are freeimage's own bookkeeping vars (like
# libwinevbs's), not a toolchain gate - emcmake supplies the real wasm32
# toolchain, so any placeholder PLATFORM value that isn't specially
# branched on (macos/ios/tvos/android) works unmodified.
( cd freeimage && emcmake cmake -DPLATFORM=linux -DARCH=wasm32 -DBUILD_STATIC=ON \
-DBUILD_SHARED=OFF -DCMAKE_BUILD_TYPE=Release -B build && cmake --build build -- -j"$NUM_PROCS" )
echo "$FREEIMAGE_SHA" > "$WORK_DIR/freeimage/.cache-sha"
cd "$ROOT_DIR"
fi
cp "$WORK_DIR"/freeimage/freeimage/build/libfreeimage.a "$DEPS_DIR/lib/"
cp "$WORK_DIR/freeimage/freeimage/Source/FreeImage.h" "$DEPS_DIR/include/"
# --- libwinevbs (its own CMakeLists.txt needs no patch beyond the winnt.h --
# --- CONTEXT stub already applied by setup.sh; PLATFORM/ARCH are likewise --
# --- just its own bookkeeping, same as FreeImage above) --------------------
if ! cache_check "$VENDOR_DIR/libwinevbs" "built-${LIBWINEVBS_SHA}"; then
echo "== Building libwinevbs for wasm32 =="
( cd "$VENDOR_DIR/libwinevbs" && emcmake cmake -DPLATFORM=emscripten -DARCH=wasm32 \
-DBUILD_STATIC=ON -DBUILD_SHARED=OFF -DCMAKE_BUILD_TYPE=Release -B build \
&& cmake --build build -- -j"$NUM_PROCS" )
echo "built-${LIBWINEVBS_SHA}" > "$VENDOR_DIR/libwinevbs/.cache-sha"
fi
cp "$VENDOR_DIR"/libwinevbs/build/libwinevbs.a "$DEPS_DIR/lib/"
mkdir -p "$DEPS_DIR/include/libwinevbs/wine/include" "$DEPS_DIR/include/libwinevbs/atl/include" "$DEPS_DIR/include/libwinevbs/atlmfc/include"
cp "$VENDOR_DIR/libwinevbs/include/libwinevbs.h" "$DEPS_DIR/include/libwinevbs/"
cp -r "$VENDOR_DIR/libwinevbs/wine/include/"* "$DEPS_DIR/include/libwinevbs/wine/include/"
cp -r "$VENDOR_DIR/libwinevbs/atl/include/"* "$DEPS_DIR/include/libwinevbs/atl/include/"
cp -r "$VENDOR_DIR/libwinevbs/atlmfc/include/"* "$DEPS_DIR/include/libwinevbs/atlmfc/include/"
# --- libpinmame (the real ROM/hardware emulation core VPinMAME.Controller --
# --- wraps on every other platform; validated end-to-end for wasm32 by this
# --- project's own pinmame-wasm spike - see patches/pinmame/ for the 3
# --- portability fixes it needed. ARCH=wasm32 (not x86/x64/arm64/aarch64)
# --- makes its own CMakeLists.txt skip the asmjit-based ARM7 JIT backend and
# --- fall back to its portable interpreter, exactly as it already does for
# --- 32-bit arm targets - no patch needed for that part. libpinmame's
# --- CMakeLists.txt lives at cmake/libpinmame/ but its source paths are all
# --- relative to the repo root, so upstream's own CI copies it there first;
# --- mirrored here rather than patched, to stay a plain file copy diff-free.
if ! cache_check "$VENDOR_DIR/pinmame" "built-${PINMAME_SHA}"; then
echo "== Building libpinmame for wasm32 =="
cp "$VENDOR_DIR/pinmame/cmake/libpinmame/CMakeLists.txt" "$VENDOR_DIR/pinmame/CMakeLists.txt"
( cd "$VENDOR_DIR/pinmame" && emcmake cmake -DPLATFORM=linux -DARCH=wasm32 \
-DBUILD_STATIC=ON -DBUILD_SHARED=OFF -DCMAKE_BUILD_TYPE=Release -B build \
&& cmake --build build --target pinmame_static -- -j"$NUM_PROCS" )
echo "built-${PINMAME_SHA}" > "$VENDOR_DIR/pinmame/.cache-sha"
fi
cp "$VENDOR_DIR/pinmame/build/libpinmame.a" "$DEPS_DIR/lib/"
mkdir -p "$DEPS_DIR/include/pinmame"
cp "$VENDOR_DIR/pinmame/src/libpinmame/libpinmame.h" "$DEPS_DIR/include/pinmame/"
cp "$VENDOR_DIR/pinmame/src/libpinmame/PinMAMEPlugin.h" "$DEPS_DIR/include/pinmame/"
echo ""
echo "== build-deps.sh complete: $DEPS_DIR =="
ls -la "$DEPS_DIR/lib"