commit b0751fdbb19079ea01854d9dc4f8b80b5d36805a Author: Brad Anderson Date: Sat Oct 10 23:36:13 2026 -0700 Add Selkies streamer prototype for Java games Runs a Flashpoint game natively in a container and streams it to the browser. fp-launch follows the Linux launcher's sequence: start the game server, mount the game's zip, and run startJava.sh through the proxy. diff --git a/streaming/streamer/Dockerfile b/streaming/streamer/Dockerfile new file mode 100644 index 0000000..7cdd753 --- /dev/null +++ b/streaming/streamer/Dockerfile @@ -0,0 +1,14 @@ +FROM ghcr.io/linuxserver/baseimage-selkies:debiantrixie + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + fontconfig \ + fonts-dejavu-core \ + libasound2-plugins \ + libxi6 \ + libxrender1 \ + libxtst6 \ + sqlite3 && \ + rm -rf /var/lib/apt/lists/* + +COPY root/ / diff --git a/streaming/streamer/README.md b/streaming/streamer/README.md new file mode 100644 index 0000000..64ea793 --- /dev/null +++ b/streaming/streamer/README.md @@ -0,0 +1,35 @@ +# Flashpoint streamer (prototype) + +Runs a Flashpoint game natively on the server and streams it to the browser with +[Selkies](https://github.com/selkies-project/selkies), for platforms that cannot +run in a browser. One container plays one game, set by `GAME_ID`. Only Java +applets are supported so far. + +## Requirements + +A Linux Flashpoint install at `FLASHPOINT_HOST_PATH`. The streamer uses the +Infinity build's `Server/FlashpointGameServer` and `FPSoftware/Java`, which +Ultimate's packed zips do not include on Linux. To add them to an extracted +Ultimate without overwriting its data, copy an extracted +[Infinity for Linux](https://flashpointarchive.org/downloads) over it: + +```sh +rsync -a --ignore-existing /path/to/infinity/ /path/to/flashpoint/ +``` + +## Usage + +```sh +FLASHPOINT_HOST_PATH=/path/to/flashpoint docker compose up -d --build +``` + +Open `https://:3001` and accept the self-signed certificate. To play a +different game, set `GAME_ID` and recreate the container. The launch log is at +`/config/fp-launch.log` inside the container. + +## How it works + +`fp-launch` follows the Flashpoint launcher's Linux launch sequence: it starts +the game server, mounts the game's zip through the server's `mountzip` API, and +runs the game's application with its HTTP proxy pointed at the game server. +Selkies restarts the game when it exits (`RESTART_APP`). diff --git a/streaming/streamer/compose.yaml b/streaming/streamer/compose.yaml new file mode 100644 index 0000000..c4e5258 --- /dev/null +++ b/streaming/streamer/compose.yaml @@ -0,0 +1,16 @@ +services: + flashpoint-streamer: + build: . + container_name: flashpoint-streamer + shm_size: 1gb + ports: + - "${STREAMER_PORT:-3001}:3001" + volumes: + - ${FLASHPOINT_HOST_PATH:?FLASHPOINT_HOST_PATH is required}:/flashpoint:ro + environment: + - PUID=${PUID:-1000} + - PGID=${PGID:-1000} + - GAME_ID=${GAME_ID:-247a4c93-c252-49c8-8bc9-fb43d5c039df} + - RESTART_APP=true + - NO_DECOR=true + - TITLE=Flashpoint diff --git a/streaming/streamer/root/defaults/autostart b/streaming/streamer/root/defaults/autostart new file mode 100644 index 0000000..75a66c0 --- /dev/null +++ b/streaming/streamer/root/defaults/autostart @@ -0,0 +1 @@ +fp-launch "$GAME_ID" >>"$HOME/fp-launch.log" 2>&1 diff --git a/streaming/streamer/root/etc/asound.conf b/streaming/streamer/root/etc/asound.conf new file mode 100644 index 0000000..091dcca --- /dev/null +++ b/streaming/streamer/root/etc/asound.conf @@ -0,0 +1,2 @@ +pcm.!default { type pulse } +ctl.!default { type pulse } diff --git a/streaming/streamer/root/usr/local/bin/fp-launch b/streaming/streamer/root/usr/local/bin/fp-launch new file mode 100755 index 0000000..e94b4d3 --- /dev/null +++ b/streaming/streamer/root/usr/local/bin/fp-launch @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Launch a Flashpoint game by ID the way the Linux launcher does: start the +# game server, mount the game's zip, then run its application through the proxy. +set -euo pipefail + +FP_PATH=${FP_PATH:-/flashpoint} +ID=${1:?usage: fp-launch } +DB=$FP_PATH/Data/flashpoint.sqlite +API=http://127.0.0.1:22501/fpProxy/api + +[[ $ID =~ ^[0-9a-f-]{36}$ ]] || { echo "Invalid game id: $ID" >&2; exit 1; } + +if ! curl -s -o /dev/null "$API/listmountzip"; then + "$FP_PATH/Server/FlashpointGameServer" -rootPath "$FP_PATH" \ + -useInfinityServer=false -handleLegacyRequests=true & + until curl -s -o /dev/null "$API/listmountzip"; do sleep 0.2; done +fi + +IFS=$'\x1f' read -r app cmd date_added < <(sqlite3 -readonly -separator $'\x1f' "$DB" " + select coalesce(nullif(d.applicationPath, ''), g.applicationPath), + coalesce(nullif(d.launchCommand, ''), g.launchCommand), + coalesce(d.dateAdded, '') + from game g left join game_data d on d.id = g.activeDataId + where g.id = '$ID'") || true +[[ -n ${app:-} ]] || { echo "Game not found: $ID" >&2; exit 1; } + +if [[ -n $date_added ]]; then + zip=$FP_PATH/Data/Games/$ID-$(date -u -d "$date_added" +%s%3N).zip + curl -sf -X POST -H 'Content-Type: application/json' \ + -d "{\"filePath\": \"$zip\"}" "$API/mountzip" >/dev/null || + { echo "Failed to mount $zip" >&2; exit 1; } +fi + +echo "Launching $ID: $app $cmd" +case ${app,,} in + 'fpsoftware\startjava.bat' | 'fpsoftware\startjavainbrowser.bat') + FP_PATH=$FP_PATH exec "$FP_PATH/FPSoftware/startJava.sh" "$cmd" ;; + *) + echo "Unsupported application: $app" >&2; exit 1 ;; +esac