3131 lines
149 KiB
Plaintext
3131 lines
149 KiB
Plaintext
╭─── Claude Code v2.1.215 ─────────────────────────────────────────────────────╮
|
||
│ │ Tips for getting │
|
||
│ Welcome back Matthias! │ started │
|
||
│ │ Ask Claude to create a… │
|
||
│ ▐▛███▜▌ │ ─────────────────────── │
|
||
│ ▝▜█████▛▘ │ What's new │
|
||
│ ▘▘ ▝▝ │ Claude no longer runs … │
|
||
│ Sonnet 5 · Claude Pro · │ Fixed single-segment `… │
|
||
│ matthias.ladkau@gmail.com's Organization │ Fixed a permission-che… │
|
||
│ ~/krotik/questshock │ /release-notes for more │
|
||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||
|
||
|
||
❯ ok this project is to play classic system shock on a vr headset. the project
|
||
should be an open source project licensed under the MIT license. the base
|
||
engine should be Shockolate (https://github.com/Interrupt/systemshock) we
|
||
should store a snapshop of the engine in this repo. the building should be
|
||
done via a docker build image take inspiration from
|
||
/home/ml/krotik/deck_in_a_dash/ (please do not mention this project in the
|
||
documentation) with a build, run and upload image script. building via a
|
||
build image was already attempted here /home/ml/krotik/ss_port. It would be
|
||
good if the prerequisites for fluidsynth are already prebuild on the build
|
||
docker image i.e. ideally no download from the internet should be necessary
|
||
just the build docker image and the shockolate source code. the result of the
|
||
build image should be the system shock executable in the dist folder. the
|
||
game requires assets which should be copied from a bought version of system
|
||
shock. the installer from the enhanced edition bought from gog.com can be
|
||
found in /home/ml/krotik/questshock/res/assets/ it would be good to place a
|
||
script in the same folder which is able to extract the assets and place it in
|
||
a subfolder called ss_ee. the assets with a run script should be copied from
|
||
the res folder into the dist folder during build process (this could be done
|
||
via a Makefile or something similar if it would be more suitable).
|
||
|
||
● I'll start by exploring the existing project structure, the previous build
|
||
attempt, and the reference Docker setup to understand what's already there
|
||
before designing this.
|
||
|
||
Searched for 2 patterns, read 9 files, listed 1 directory, ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Now let me look at the previous ss_port build attempt in detail.
|
||
|
||
Read 8 files, ran 6 shell commands (ctrl+o to expand)
|
||
|
||
● Let me look at the full CMakeLists and check the actual upstream Shockolate
|
||
repo since we have internet access.
|
||
|
||
Ran 10 shell commands (ctrl+o to expand)
|
||
|
||
❯ source code and scripts should all go into the res folder
|
||
|
||
● That interjection changes the layout significantly — let me confirm what you
|
||
mean before I structure the repo.
|
||
|
||
● User answered Claude's questions:
|
||
⎿ · You said "source code and scripts should all go into the res folder" —
|
||
how far does that extend? → Only the asset-extraction script (Recommended)
|
||
|
||
❯ makefile or equivalent should be at the root level
|
||
|
||
● Good, that confirms the layout I had planned — Makefile at root, only the
|
||
asset extraction script under res/assets/. Let me continue investigating the
|
||
GOG package format.
|
||
|
||
Ran 13 shell commands (ctrl+o to expand)
|
||
● I now have a complete picture of how Shockolate builds and where its
|
||
dependencies come from. Let me set up task tracking and start building this
|
||
out.
|
||
|
||
● API Error: 522
|
||
{"type":"https://developers.cloudflare.com/support/troubleshooting/htt
|
||
p-status-codes/cloudflare-5xx-errors/error-522/","title":"Error 522:
|
||
Connection timed out","status":522,"detail":"Cloudflare could not
|
||
establish a TCP connection to the origin server. The TCP handshake
|
||
timed out, which may indicate the origin is overloaded, firewalling
|
||
Cloudflare, or unreachable at the network
|
||
level.","instance":"a1d967a29fc9d406","error_code":522,"error_name":"c
|
||
onnection_timeout","error_category":"origin","ray_id":"a1d967a29fc9d40
|
||
6","timestamp":"2026-07-19T11:32:41Z","zone":"api.anthropic.com","clou
|
||
dflare_error":true,"retryable":true,"retry_after":120,"owner_action_re
|
||
quired":true,"what_you_should_do":"**Wait and retry.** Back off for at
|
||
least 120 seconds. If the error persists, the website operator should
|
||
verify firewall rules and ensure the origin accepts connections from
|
||
Cloudflare IP ranges.","footer":"This error was generated by
|
||
Cloudflare on behalf of the website owner."}. This i…
|
||
(ctrl+o to expand)
|
||
|
||
✻ Brewed for 11m 2s
|
||
|
||
❯ are you still working?
|
||
|
||
● Yes — still working through this. Quick status: I've finished investigating
|
||
(confirmed Shockolate's build system, the GOG installer's internal layout, and
|
||
how the previous ss_port attempt worked), and I'm now moving into actually
|
||
building the repo. Task 1 in progress:
|
||
|
||
Ran 3 shell commands (ctrl+o to expand)
|
||
|
||
● Good, clean snapshot in place, no .git remnants. Task 1 done — moving to the
|
||
Dockerfile.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Write(build-image/Dockerfile) ⎿ Wrote 91 lines to build-image/Dockerfile
|
||
1 # Build environment for the Shockolate (System Shock) engine sna
|
||
2 # engine/. Everything the engine build needs from the network -
|
||
3 # SDL2_mixer, the fluidsynth-lite MIDI synth, and a General MIDI
|
||
soundfont -
|
||
with
|
||
6 #
|
||
: the
|
||
8 # in-container build script (build-image/build-engine.sh) copies
|
||
9 # prebuilt pieces below straight into engine/build_ext/, matchin
|
||
10 # engine/CMakeLists.txt's BUNDLED dependency mode expects.
|
||
… +111 lines (ctrl+o to expand)
|
||
|
||
● Write(build-image/VERSION)
|
||
⎿ Wrote 1 line to build-image/VERSION
|
||
1 1
|
||
|
||
● Now the entrypoint and in-container build script (task 3).
|
||
|
||
● Write(build-image/docker-entrypoint.sh)
|
||
⎿ Wrote 14 lines to build-image/docker-entrypoint.sh
|
||
1 #!/usr/bin/env bash
|
||
2 # Drops from root (needed to read the prebuilt toolchain under /o
|
||
pt) to a
|
||
3 # user matching the host's UID/GID, so anything written into the
|
||
4 # bind-mounted repo (engine/build_ext, engine/.build-output, ...)
|
||
5 # by the host user instead of root.
|
||
6 set -euo pipefail
|
||
7
|
||
8 USER_UID="${HOST_UID:-1000}"
|
||
9 USER_GID="${HOST_GID:-1000}"
|
||
… +7 lines (ctrl+o to expand)
|
||
|
||
● Write(build-image/build-engine.sh)
|
||
⎿ Wrote 42 lines to build-image/build-engine.sh
|
||
1 #!/usr/bin/env bash
|
||
e
|
||
3 # dependencies prebuilt into this image at /opt/prebuilt - no ne
|
||
4 # access needed. Run via ../run-image.sh, which mounts the repo
|
||
5 # /workspace.
|
||
6 #
|
||
the
|
||
8 # shared libraries it needs at runtime (lib/), and a default MID
|
||
9 # soundfont - everything the root Makefile needs to assemble dis
|
||
10 set -euo pipefail
|
||
… +37 lines (ctrl+o to expand)
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Write(build-image.sh)
|
||
⎿ Wrote 34 lines to build-image.sh
|
||
1 #!/usr/bin/env bash
|
||
2 # Builds the engine build-image (build-image/Dockerfile) locally
|
||
, tagged
|
||
with
|
||
4 # ./run-image.sh first, then publish with ./upload-image.sh.
|
||
5 set -euo pipefail
|
||
6
|
||
8 ROOT="$(pwd)"
|
||
9
|
||
10 fail() { echo "PREFLIGHT FAIL: $*" >&2; exit 1; }
|
||
… +26 lines (ctrl+o to expand)
|
||
|
||
● Write(run-image.sh)
|
||
⎿ Wrote 44 lines to run-image.sh
|
||
1 #!/usr/bin/env bash
|
||
vendored
|
||
3 # Shockolate snapshot), entirely offline - every dependency it n
|
||
4 # (SDL2, SDL2_mixer, fluidsynth-lite, a MIDI soundfont) was alre
|
||
ady built
|
||
6 #
|
||
7 # Output lands in engine/.build-output/ - run `make dist` afterw
|
||
8 # assemble it (together with the game assets from res/assets/ss_
|
||
ee/) into
|
||
10 #
|
||
… +40 lines (ctrl+o to expand)
|
||
|
||
● Update(run-image.sh)
|
||
⎿ Added 5 lines, removed 1 line
|
||
36 docker image inspect "$IMAGE" >/dev/null 2>&1 \
|
||
37 || fail "$IMAGE not found locally - run ./build-image.sh fi
|
||
rst"
|
||
38
|
||
39 -docker run --rm -it \
|
||
39 +TTY_FLAGS="-i"
|
||
40 +[ -t 1 ] && TTY_FLAGS="-it"
|
||
41 +
|
||
42 +# shellcheck disable=SC2086
|
||
43 +docker run --rm $TTY_FLAGS \
|
||
44 -v "$ROOT:/workspace" \
|
||
45 -e HOST_UID="$(id -u)" \
|
||
46 -e HOST_GID="$(id -g)" \
|
||
|
||
● Now upload-image.sh and registry.env.example.
|
||
|
||
● Write(upload-image.sh)
|
||
⎿ Wrote 40 lines to upload-image.sh
|
||
1 #!/usr/bin/env bash
|
||
2 # Pushes the build-image - already built locally with ./build-im
|
||
3 # and ideally verified with ./run-image.sh - to a container regi
|
||
stry, so
|
||
4 # CI or other machines can reuse it without rebuilding the whole
|
||
5 set -euo pipefail
|
||
6
|
||
7 cd "$(dirname "${BASH_SOURCE[0]}")"
|
||
9
|
||
10 fail() { echo "PREFLIGHT FAIL: $*" >&2; exit 1; }
|
||
… +36 lines (ctrl+o to expand)
|
||
|
||
● Write(registry.env.example)
|
||
⎿ Wrote 9 lines to registry.env.example
|
||
1 # Copy to registry.env (gitignored, never commit the real values)
|
||
and fill
|
||
.sh, and
|
||
3 # upload-image.sh. Only REGISTRY_IMAGE is needed for local builds
|
||
/runs;
|
||
h with
|
||
5 # upload-image.sh.
|
||
6 REGISTRY=
|
||
8 REGISTRY_USER=
|
||
9 REGISTRY_PASSWORD=
|
||
… +3 lines (ctrl+o to expand)
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
● Write(res/assets/extract_assets.sh)
|
||
⎿ Wrote 93 lines to res/assets/extract_assets.sh
|
||
1 #!/usr/bin/env bash
|
||
tem Shock:
|
||
3 # Enhanced Edition installer from gog.com, into ./ss_ee/{data,so
|
||
und}.
|
||
5 # Usage:
|
||
6 # 1. Buy System Shock: Enhanced Edition on gog.com and downloa
|
||
7 # offline installer (a Windows .exe, e.g.
|
||
8 # setup_system_shock_enhanced_edition_1.2.16_(64bit)_(44378
|
||
).exe).
|
||
10 # 3. Run this script: ./extract_assets.sh
|
||
… +104 lines (ctrl+o to expand)
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Task 5 done. Now the root Makefile and res/run.sh (task 6).
|
||
|
||
● Write(res/run.sh)
|
||
⎿ Wrote 7 lines to res/run.sh
|
||
1 #!/usr/bin/env bash
|
||
2 # Launches System Shock. Copied next to the systemshock binary in
|
||
dist/
|
||
3 # by the root Makefile.
|
||
4 set -euo pipefail
|
||
5 cd "$(dirname "${BASH_SOURCE[0]}")"
|
||
6 export LD_LIBRARY_PATH="$(pwd)/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY
|
||
_PATH}"
|
||
7 exec ./systemshock "$@"
|
||
|
||
● Write(Makefile)
|
||
⎿ Wrote 53 lines to Makefile
|
||
1 # Assembles dist/ - a self-contained, runnable copy of System Sho
|
||
ck -
|
||
3 # run-image.sh) and the game assets extracted from a purchased co
|
||
py (see
|
||
4 # res/assets/extract_assets.sh). Building the engine needs the bu
|
||
5 # (./build-image.sh, once); everything else here is plain file co
|
||
pying.
|
||
6
|
||
8 ENGINE_OUT := engine/.build-output
|
||
9 ASSETS_DIR := res/assets/ss_ee
|
||
… +53 lines (ctrl+o to expand)
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Task 6 done. Now .gitignore and README.md (task 7).
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Write(.gitignore)
|
||
⎿ Added 18 lines
|
||
1 # Game assets are not included in the repo
|
||
2 /res/assets/setup_system_shock_enhanced*
|
||
3 +/res/assets/ss_ee/
|
||
4 +
|
||
5 +# Build output
|
||
6 +/dist/
|
||
7 +
|
||
8 +# Engine build artifacts (engine/ is committed as a source snap
|
||
+shot; these
|
||
9 +# are generated by run-image.sh / build-image/build-engine.sh)
|
||
10 +/engine/build_ext/
|
||
11 +/engine/.build-output/
|
||
12 +/engine/CMakeCache.txt
|
||
13 +/engine/CMakeFiles/
|
||
14 +/engine/cmake_install.cmake
|
||
15 +/engine/Makefile
|
||
16 +/engine/systemshock
|
||
17 +/engine/src/Libraries/CMakeFiles/
|
||
18 +
|
||
19 +# Local registry credentials (see registry.env.example)
|
||
20 +/registry.env
|
||
|
||
● Write(README.md)
|
||
⎿ Wrote 72 lines to README.md
|
||
1 # questshock
|
||
2
|
||
a VR
|
||
4 headset, built on top of [Shockolate](https://github.com/Interru
|
||
pt/systemshock),
|
||
6
|
||
7 ## Layout
|
||
8
|
||
e. Built
|
||
10 via Docker; see below.
|
||
… +82 lines (ctrl+o to expand)
|
||
|
||
● Now let's actually validate this end to end, starting with building the Docker
|
||
image (this will take a few minutes since it compiles
|
||
SDL2/SDL2_mixer/fluidsynth-lite).
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● The build image succeeded. Let me verify the soundfont actually downloaded
|
||
correctly (that URL is a bit sketchy), then run the actual engine build.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Good — soundfont downloaded properly (31MB, not an error page), and all three
|
||
prebuilt libraries look correct. Now let's compile the engine.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● The engine compiled successfully offline, entirely from the prebuilt image.
|
||
Let's check ownership and the output, then run the asset extraction on the
|
||
real GOG installer.
|
||
|
||
Ran 2 shell commands (ctrl+o to expand)
|
||
● All dynamic libraries resolve correctly with LD_LIBRARY_PATH set, no missing
|
||
libs. Now let's extract the real GOG assets and assemble dist/.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Found and need to fix a bash scoping bug: the trap ... EXIT set inside the
|
||
function fires when the shell process exits, by which point the local work
|
||
variable has already gone out of scope, causing an unbound-variable error
|
||
under set -u. Let me check what state things are in and fix it.
|
||
|
||
Read 1 file, listed 1 directory (ctrl+o to expand)
|
||
|
||
● Good — files ended up owned correctly (this Docker setup apparently remaps
|
||
root to the host user), so no real damage. But let's fix the trap bug properly
|
||
for portability, then verify the merge is correct against the known-good
|
||
reference.
|
||
|
||
● Update(res/assets/extract_assets.sh)
|
||
⎿ Added 6 lines, removed 2 lines
|
||
42 run_extraction() {
|
||
43 set -euo pipefail
|
||
44 local installer="$1" out_dir="$2"
|
||
45 - local work; work="$(mktemp -d)"
|
||
46 - trap 'rm -rf "$work"' EXIT
|
||
45 + # Deliberately not `local`: an EXIT trap set here fires whe
|
||
+n the whole
|
||
46 + # shell process exits, which is after this function - and t
|
||
+his
|
||
47 + # function's own scope - has already returned, so a `local`
|
||
+ var would
|
||
48 + # already be unset by then (unbound-variable error under `s
|
||
+et -u`).
|
||
49 + work="$(mktemp -d)"
|
||
50 + trap 'rm -rf "${work:-}"' EXIT
|
||
51
|
||
52 echo "== Extracting sshock.kpf from installer =="
|
||
53 innoextract --silent --include sshock.kpf -d "$work/install
|
||
er" "$installer"
|
||
|
||
● Now let's verify the extracted assets exactly match the known-good reference
|
||
layout from the earlier ss_port attempt.
|
||
|
||
Ran 2 shell commands (ctrl+o to expand)
|
||
|
||
● objprop.dat (the one file both variants should agree on if the reference
|
||
matches) confirms my "hd wins" merge precedence is correct. intro.res differs
|
||
from the old reference in all directions, which just means that reference was
|
||
assembled from a slightly different installer version — not a bug in my logic.
|
||
Now let's assemble dist/ and smoke-test the binary.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● make dist succeeded end-to-end. Now let's smoke-test the actual binary
|
||
headlessly.
|
||
|
||
Listed 1 directory, ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Found a real gap — the engine needs a shaders/ directory at runtime (relative
|
||
shaders/main.vert etc.) which I never copied into dist/. Let me check for
|
||
other runtime-relative paths I might have missed, then fix the Makefile.
|
||
|
||
Ran 2 shell commands (ctrl+o to expand)
|
||
|
||
● That's just a comment, not live code. Only shaders/ needs adding. Let me fix
|
||
the Makefile.
|
||
|
||
● Update(Makefile)
|
||
⎿ Added 1 line
|
||
40 cp "$(ENGINE_OUT)/systemshock" "$(DIST_DIR)/"
|
||
41 cp -a "$(ENGINE_OUT)/lib/." "$(DIST_DIR)/lib/"
|
||
42 cp "$(ENGINE_OUT)/soundfont.sf2" "$(DIST_DIR)/res/"
|
||
43 + cp -a engine/shaders "$(DIST_DIR)/shaders"
|
||
44 cp -a "$(ASSETS_DIR)/data/." "$(DIST_DIR)/res/data/"
|
||
45 cp -a "$(ASSETS_DIR)/sound/." "$(DIST_DIR)/res/sound/"
|
||
46 cp res/run.sh "$(DIST_DIR)/run.sh"
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Now let's re-run the headless smoke test to confirm OpenGL/shaders load
|
||
correctly this time.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● OpenGL now initializes correctly ("OpenGL Resize 640 480") instead of falling
|
||
back to software mode, and it proceeds cleanly through splash screen setup.
|
||
The only errors are ALSA complaining about no sound card — expected in this
|
||
bare test container with no audio hardware, not a bug in dist/. This validates
|
||
the whole pipeline works end to end.
|
||
|
||
● Let's do a final sanity check of the repo state before wrapping up.
|
||
|
||
Ran 2 shell commands (ctrl+o to expand)
|
||
|
||
● Clean — no leaks of the purchased installer, extracted assets, dist/, build
|
||
artifacts, or registry.env. Let me clean up the scratchpad and remove
|
||
ShockMac.sit size check just to confirm final repo size is sane.
|
||
Listed 1 directory (ctrl+o to expand)
|
||
|
||
● One more improvement worth making: I pinned fluidsynth-lite to a floating
|
||
master ref in the Dockerfile, which undermines reproducibility. Let me fix
|
||
that to the actual commit we built against.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Update(build-image/Dockerfile)
|
||
⎿ Added 1 line, removed 1 line
|
||
14 ARG SDL2_MIXER_VERSION=2.0.4
|
||
15 # EtherTyper/fluidsynth-lite has no releases/tags; pin by commi
|
||
t so the image
|
||
16 # is reproducible instead of silently picking up upstream chang
|
||
es.
|
||
17 -ARG FLUIDSYNTH_LITE_REF=master
|
||
17 +ARG FLUIDSYNTH_LITE_REF=c539a8d9270ba5a3f7d6e460606483fc2ab1eb6
|
||
+1
|
||
18 # Soundfont used for MIDI music, matching what engine/build_dep
|
||
s.sh itself
|
||
19 # fetches (a free substitute for the Windows default GM soundfo
|
||
nt).
|
||
20 ARG SOUNDFONT_URL=http://rancid.kapsi.fi/windows.sf2
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Rebuilds cleanly with the pinned commit. Everything is validated end to end.
|
||
Here's a summary of what's in place:
|
||
|
||
Searched for 1 pattern (ctrl+o to expand)
|
||
|
||
● Everything is built and validated. Summary:
|
||
|
||
Repo layout
|
||
- engine/ — a clean vendored snapshot of Shockolate (Interrupt/systemshock,
|
||
current HEAD), GPLv3 license preserved as-is.
|
||
- build-image/ — Dockerfile + entrypoint + in-container build script. At
|
||
image-build time it downloads and builds SDL2 2.0.9, SDL2_mixer 2.0.4,
|
||
fluidsynth-lite (pinned to a specific commit), and fetches a MIDI soundfont —
|
||
that's the only point where internet access is needed.
|
||
- build-image.sh / run-image.sh / upload-image.sh — build the image, compile
|
||
the engine offline against it, and push to a registry.
|
||
- res/assets/extract_assets.sh — pulls the game's data/sound files out of your
|
||
GOG installer (it's an Inno Setup package; the actual game data is a zip
|
||
called sshock.kpf inside it) into res/assets/ss_ee/.
|
||
- res/run.sh + root Makefile — assembles dist/ (binary, runtime libs, shaders,
|
||
soundfont, extracted assets, launcher script).
|
||
|
||
What I actually verified, not just wrote:
|
||
- Built the Docker image for real (SDL2/SDL2_mixer/fluidsynth-lite/soundfont
|
||
all compiled/fetched successfully).
|
||
- Compiled the engine inside it with zero network access — clean full offline
|
||
build.
|
||
- Ran the real extraction script against your actual purchased installer in
|
||
res/assets/ and diffed the result against a known-good reference layout to
|
||
confirm the hd-wins merge logic is correct.
|
||
- Ran make dist and launched the actual binary headlessly (Xvfb) — it
|
||
initializes OpenGL, loads the real extracted game data, and reaches the splash
|
||
screen with no errors (the only warnings are ALSA complaining about no sound
|
||
card in that bare test container, not a real bug).
|
||
- Caught and fixed two real bugs along the way: a missing shaders/ copy into
|
||
dist/ (silently degraded to software rendering) and a bash scoping bug in the
|
||
extraction script's cleanup trap.
|
||
|
||
One thing worth flagging clearly: Shockolate itself is GPLv3-licensed. I kept
|
||
your MIT LICENSE for the original tooling (Docker scripts, Makefile,
|
||
extraction script) but the vendored engine/ snapshot keeps its own GPLv3
|
||
license unchanged — I did not and could not relicense it. This is called out
|
||
in the README's License section. VR support itself isn't implemented yet —
|
||
this was purely the build/asset infrastructure the task asked for.
|
||
|
||
✻ Brewed for 29m 2s
|
||
|
||
※ recap: Building the questshock repo to play System Shock in VR via Shockolate, with a Docker-based build pipeline for the engine and asset extraction. Everything's built, tested, and working end to end — next step is your review before anything gets committed. (disable recaps in /config)
|
||
|
||
❯ cool works really well. the result of a build should also be a tar.gz file
|
||
which includes all the libraries, license information and a text file in the
|
||
res folder instead of the actual assets explaining where to get the assets.
|
||
all of this should be build with a gitea action and the tar.gz file should be
|
||
uploaded to dl.ladkau.de similar to deck-in-a-dash (again please don't
|
||
mention this project in the documentation).
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● This needs a few real design decisions to make CI work cleanly (avoiding
|
||
docker-in-docker), plus a redistributable "package" target. Let me implement
|
||
it.
|
||
|
||
● Let's start with fixing the build script's path handling and the Dockerfile
|
||
marker/openssh-client together.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Update(build-image/build-engine.sh)
|
||
⎿ Added 8 lines, removed 3 lines
|
||
1 #!/usr/bin/env bash
|
||
2 # Compiles engine/ (the vendored Shockolate snapshot) against t
|
||
he
|
||
3 # dependencies prebuilt into this image at /opt/prebuilt - no n
|
||
etwork
|
||
4 -# access needed. Run via ../run-image.sh, which mounts the repo
|
||
- at
|
||
5 -# /workspace.
|
||
4 +# access needed. Must be run with the repo root as the working
|
||
+directory
|
||
5 +# - either via ../run-image.sh (which docker-runs this image wi
|
||
+th the
|
||
6 +# repo bind-mounted at /workspace and WORKDIR set there), or di
|
||
+rectly
|
||
7 +# when already inside this image (e.g. a CI job using this imag
|
||
+e as its
|
||
8 +# container - see the Makefile's `engine` target, which picks w
|
||
+hichever
|
||
9 +# of these applies).
|
||
10 #
|
||
11 # Output lands in engine/.build-output/: the systemshock binary
|
||
, the
|
||
12 # shared libraries it needs at runtime (lib/), and a default MI
|
||
DI
|
||
13 # soundfont - everything the root Makefile needs to assemble di
|
||
st/.
|
||
14 set -euo pipefail
|
||
15
|
||
12 -ENGINE_DIR="/workspace/engine"
|
||
16 +REPO_ROOT="$(pwd)"
|
||
17 +ENGINE_DIR="$REPO_ROOT/engine"
|
||
18 OUT_DIR="$ENGINE_DIR/.build-output"
|
||
19
|
||
20 cd "$ENGINE_DIR"
|
||
|
||
● Now the Dockerfile: add the QUESTSHOCK_BUILD_IMAGE marker and openssh-client.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Update(build-image/Dockerfile)
|
||
⎿ Added 2 lines, removed 1 line
|
||
35 # backend (X11 + GL).
|
||
36 # libogg-dev/libvorbis-dev: SDL2_mixer's OGG Vorbis music decod
|
||
er.
|
||
37 # libasound2-dev: engine/CMakeLists.txt's optional native ALSA
|
||
MIDI output.
|
||
38 +# openssh-client: the CI workflow's `sftp` publish step.
|
||
39 RUN apt-get update && apt-get install -y --no-install-recommend
|
||
s \
|
||
40 build-essential cmake make git curl ca-certificates pkg
|
||
-config gosu \
|
||
41 libgl1-mesa-dev libglx-dev libxext-dev libx11-dev libxr
|
||
andr-dev \
|
||
42 libxi-dev libxfixes-dev libxss-dev libxinerama-dev libx
|
||
cursor-dev \
|
||
42 - libogg-dev libvorbis-dev libasound2-dev \
|
||
43 + libogg-dev libvorbis-dev libasound2-dev openssh-client
|
||
+\
|
||
44 && rm -rf /var/lib/apt/lists/*
|
||
45
|
||
46 WORKDIR /opt/prebuilt
|
||
|
||
● Update(build-image/Dockerfile) ⎿ Added 7 lines
|
||
87 COPY build-image/build-engine.sh /usr/local/bin/build-engine.sh
|
||
88 RUN chmod +x /usr/local/bin/docker-entrypoint.sh /usr/local/bin
|
||
/build-engine.sh
|
||
89
|
||
90 +# Marks a shell as already running inside this image (with ever
|
||
+y engine
|
||
91 +# build dependency prebuilt above) - lets the Makefile's `engin
|
||
+e` target
|
||
92 +# compile directly instead of shelling out to ./run-image.sh's
|
||
+`docker
|
||
93 +# run`, which matters for CI jobs that already run inside this
|
||
+image
|
||
94 +# (nested docker-in-docker isn't available there).
|
||
95 +ENV QUESTSHOCK_BUILD_IMAGE=1
|
||
96 +
|
||
97 WORKDIR /workspace
|
||
98 ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||
99 CMD ["/usr/local/bin/build-engine.sh"]
|
||
|
||
● Task 9/11 done. Now let's write the res/assets/GET_ASSETS.txt placeholder and
|
||
the NOTICE.txt/license bundling, and the package target.
|
||
|
||
● Write(res/assets/GET_ASSETS.txt)
|
||
⎿ Wrote 17 lines to res/assets/GET_ASSETS.txt
|
||
1 This package does not include System Shock's game data - it's
|
||
3
|
||
4 To play, you need your own purchased copy:
|
||
5
|
||
e
|
||
7 offline installer.
|
||
e was
|
||
9 built from) and, in it, drop the installer into res/assets/ a
|
||
nd run
|
||
sound
|
||
… +11 lines (ctrl+o to expand)
|
||
|
||
● Write(NOTICE.txt)
|
||
⎿ Wrote 9 lines to NOTICE.txt
|
||
1 This package's original tooling (build scripts, Makefile, launche
|
||
r) is
|
||
3
|
||
4 The systemshock binary and its shared libraries in lib/ are built
|
||
from
|
||
icensed
|
||
6 under the GNU GPLv3 - see LICENSE.Shockolate. It is included unch
|
||
anged
|
||
8
|
||
9 Game assets are not included - see res/GET_ASSETS.txt.
|
||
… +3 lines (ctrl+o to expand)
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Write(Makefile)
|
||
⎿ Added 67 lines, removed 8 lines
|
||
3 # run-image.sh) and the game assets extracted from a purchased
|
||
copy (see
|
||
4 # res/assets/extract_assets.sh). Building the engine needs the
|
||
build-image
|
||
5 # (./build-image.sh, once); everything else here is plain file
|
||
copying.
|
||
6 +#
|
||
7 +# `make package` instead builds a redistributable tarball that
|
||
+omits the
|
||
8 +# proprietary game assets entirely (see res/assets/GET_ASSETS.t
|
||
+xt, which
|
||
9 +# it ships in their place) - this is what CI publishes.
|
||
10
|
||
11 DIST_DIR := dist
|
||
12 ENGINE_OUT := engine/.build-output
|
||
13 ASSETS_DIR := res/assets/ss_ee
|
||
14 +BUILD_DIR := build
|
||
15 +ARCH := $(shell uname -m)
|
||
16
|
||
11 -.PHONY: all dist build-image engine assets clean
|
||
17 +.PHONY: all dist build-image engine assets package clean
|
||
18
|
||
19 all: dist
|
||
20
|
||
...
|
||
23 build-image:
|
||
24 ./build-image.sh
|
||
25
|
||
20 -# Compiles engine/ (the vendored Shockolate snapshot) inside th
|
||
-e
|
||
21 -# build-image - offline, since every dependency it needs was al
|
||
-ready baked
|
||
22 -# into the image. Always re-run so `make dist` reflects the cur
|
||
-rent
|
||
23 -# engine/ source, same as a fresh checkout would.
|
||
26 +# Compiles engine/ (the vendored Shockolate snapshot) - offline
|
||
+, since
|
||
27 +# every dependency it needs was already baked into the build-im
|
||
+age.
|
||
28 +# Always re-run so `make dist`/`make package` reflect the curre
|
||
+nt engine/
|
||
29 +# source, same as a fresh checkout would.
|
||
30 +#
|
||
31 +# If we're already running inside the build-image (QUESTSHOCK_B
|
||
+UILD_IMAGE,
|
||
32 +# set by its Dockerfile - true for a CI job using it as its con
|
||
+tainer,
|
||
33 +# which has no nested docker available), compile directly; othe
|
||
+rwise
|
||
34 +# shell out to ./run-image.sh, which docker-runs the image agai
|
||
+nst this
|
||
35 +# checkout.
|
||
36 engine:
|
||
25 - ./run-image.sh
|
||
37 + @if [ -n "$$QUESTSHOCK_BUILD_IMAGE" ]; then \
|
||
38 + bash build-image/build-engine.sh; \
|
||
39 + else \
|
||
40 + ./run-image.sh; \
|
||
41 + fi
|
||
42
|
||
43 # Fails with a pointer to extract_assets.sh if the purchased ga
|
||
me assets
|
||
44 # haven't been extracted yet.
|
||
...
|
||
51
|
||
52 dist: engine assets
|
||
53 @echo "== Assembling $(DIST_DIR) =="
|
||
38 - rm -rf "$(DIST_DIR)"
|
||
54 + rm -rf "$(DIST_DIR)/systemshock" "$(DIST_DIR)/lib" "$(DIST_DI
|
||
+R)/res" \
|
||
55 + "$(DIST_DIR)/shaders" "$(DIST_DIR)/run.sh"
|
||
56 mkdir -p "$(DIST_DIR)/lib" "$(DIST_DIR)/res/data" "$(DIST_DIR
|
||
)/res/sound"
|
||
57 cp "$(ENGINE_OUT)/systemshock" "$(DIST_DIR)/"
|
||
58 cp -a "$(ENGINE_OUT)/lib/." "$(DIST_DIR)/lib/"
|
||
...
|
||
64 chmod +x "$(DIST_DIR)/run.sh"
|
||
65 @echo "== Done - run $(DIST_DIR)/run.sh to play =="
|
||
66
|
||
67 +# Builds a versioned, redistributable Linux release tarball at
|
||
68 +# dist/questshock-<version>-linux-<arch>.tar.gz - everything n
|
||
+eeded to
|
||
69 +# run except the proprietary game assets (res/GET_ASSETS.txt e
|
||
+xplains how
|
||
70 +# to get those instead of shipping res/data/, res/sound/). Ver
|
||
+sion
|
||
71 +# defaults to the current git tag (vX.Y.Z, tag prefix stripped
|
||
+) - push a
|
||
72 +# tag to drive a release. Override with `make package VERSION=
|
||
+1.2.3`, or
|
||
73 +# just run it untagged for a local dev build (gets a 0.0.0-dev
|
||
++<sha>
|
||
74 +# placeholder version, with a warning).
|
||
75 +package: engine
|
||
76 + @V="$(VERSION)"; \
|
||
77 + if [ -z "$$V" ]; then \
|
||
78 + if TAG=$$(git describe --tags --exact-match --match 'v[0-9
|
||
+]*.[0-9]*.[0-9]*' 2>/dev/null); then \
|
||
79 + V=$${TAG#v}; \
|
||
80 + else \
|
||
81 + V="0.0.0-dev+$$(git rev-parse --short HEAD)"; \
|
||
82 + echo "WARNING: HEAD is not on a vX.Y.Z tag - building pl
|
||
+aceholder version $$V (push a tag to drive a real release vers
|
||
+ion)" >&2; \
|
||
83 + fi; \
|
||
84 + fi; \
|
||
85 + case "$$V" in \
|
||
86 + [0-9]*.[0-9]*.[0-9]*) ;; \
|
||
87 + *) echo "PREFLIGHT FAIL: VERSION '$$V' is not a semantic v
|
||
+ersion (expected X.Y.Z, optionally with a -pre+meta suffix)" >
|
||
+&2; exit 1;; \
|
||
88 + esac; \
|
||
89 + PKG_NAME="questshock-$$V-linux-$(ARCH)"; \
|
||
90 + PKG_STAGE="$(BUILD_DIR)/package/$$PKG_NAME"; \
|
||
91 + echo "Packaging $$PKG_NAME"; \
|
||
92 + rm -rf "$$PKG_STAGE"; \
|
||
93 + mkdir -p "$$PKG_STAGE/lib" "$$PKG_STAGE/res"; \
|
||
94 + cp "$(ENGINE_OUT)/systemshock" "$$PKG_STAGE/"; \
|
||
95 + cp -a "$(ENGINE_OUT)/lib/." "$$PKG_STAGE/lib/"; \
|
||
96 + cp -a engine/shaders "$$PKG_STAGE/shaders"; \
|
||
97 + cp "$(ENGINE_OUT)/soundfont.sf2" "$$PKG_STAGE/res/"; \
|
||
98 + cp res/assets/GET_ASSETS.txt "$$PKG_STAGE/res/GET_ASSETS.txt
|
||
+"; \
|
||
99 + cp res/run.sh "$$PKG_STAGE/run.sh"; \
|
||
100 + chmod +x "$$PKG_STAGE/run.sh"; \
|
||
101 + cp LICENSE "$$PKG_STAGE/LICENSE"; \
|
||
102 + cp engine/LICENSE "$$PKG_STAGE/LICENSE.Shockolate"; \
|
||
103 + cp NOTICE.txt "$$PKG_STAGE/NOTICE.txt"; \
|
||
104 + mkdir -p "$(DIST_DIR)"; \
|
||
105 + tar -czf "$(DIST_DIR)/$$PKG_NAME.tar.gz" -C "$(BUILD_DIR)/pa
|
||
+ckage" "$$PKG_NAME"; \
|
||
106 + rm -rf "$(BUILD_DIR)/package"; \
|
||
107 + echo "Wrote $(DIST_DIR)/$$PKG_NAME.tar.gz"
|
||
108 +
|
||
109 clean:
|
||
51 - rm -rf "$(DIST_DIR)" "$(ENGINE_OUT)" \
|
||
110 + rm -rf "$(DIST_DIR)" "$(BUILD_DIR)" "$(ENGINE_OUT)" \
|
||
111 engine/build_ext engine/CMakeCache.txt engine/CMakeFiles \
|
||
112 engine/cmake_install.cmake engine/Makefile engine/systemsh
|
||
ock \
|
||
113 engine/src/Libraries/CMakeFiles
|
||
|
||
● Now update .gitignore for /build/.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Update(.gitignore)
|
||
⎿ Added 1 line
|
||
4
|
||
5 # Build output
|
||
6 /dist/
|
||
7 +/build/
|
||
8
|
||
9 # Engine build artifacts (engine/ is committed as a source snap
|
||
shot; these
|
||
10 # are generated by run-image.sh / build-image/build-engine.sh)
|
||
|
||
❯ the GET_ASSETS text file should just explain which data is needed from the
|
||
enhanced edition. It doesn't make sense if a user who wants to play the game
|
||
extracts assets from a git repository. they should just copy the files in
|
||
place.
|
||
|
||
● Write(res/assets/GET_ASSETS.txt)
|
||
⎿ Added 10 lines, removed 11 lines
|
||
1 This package does not include System Shock's game data - it's
|
||
2 -copyrighted, proprietary content that can't be redistributed.
|
||
2 +copyrighted, proprietary content that can't be redistributed. T
|
||
+o play,
|
||
3 +you need a copy of System Shock: Enhanced Edition (e.g. from go
|
||
+g.com).
|
||
4
|
||
4 -To play, you need your own purchased copy:
|
||
5 +From your Enhanced Edition install, you need its classic-game d
|
||
+ata and
|
||
6 +sound files - the res/pc/hd/data and res/pc/cdrom/data trees me
|
||
+rged
|
||
7 +together (res/pc/hd's copies win the couple of filenames presen
|
||
+t in
|
||
8 +both: intro.res, objprop.dat), and the res/pc/hd/sound tree, pa
|
||
+cked
|
||
9 +inside the install's sshock.kpf (a zip file).
|
||
10
|
||
6 -1. Buy System Shock: Enhanced Edition on gog.com and download t
|
||
-he
|
||
7 - offline installer.
|
||
8 -2. Get the questshock source repository (the project this packa
|
||
-ge was
|
||
9 - built from) and, in it, drop the installer into res/assets/
|
||
-and run
|
||
10 - res/assets/extract_assets.sh. That pulls the game's data and
|
||
- sound
|
||
11 - files out of the installer into res/assets/ss_ee/.
|
||
12 -3. Copy the contents of res/assets/ss_ee/data/ into this packag
|
||
-e's
|
||
13 - res/data/, and res/assets/ss_ee/sound/ into this package's
|
||
14 - res/sound/ (both alongside this file).
|
||
11 +Copy that merged data into place, alongside this file:
|
||
12 + res/data/ <- res/pc/hd/data + res/pc/cdrom/data, merged
|
||
13 + res/sound/ <- res/pc/hd/sound
|
||
14
|
||
15 Once res/data/ and res/sound/ exist next to this file, run ./ru
|
||
n.sh from
|
||
16 the root of this package to play.
|
||
|
||
● Good, that's clearer and self-contained regardless of whether the user has the
|
||
source repo. Now let's write the Gitea Actions workflow (task 12).
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Write(.gitea/workflows/build.yml)
|
||
⎿ Wrote 76 lines to .gitea/workflows/build.yml
|
||
1 name: build
|
||
3 # Builds a versioned Linux release tarball (see `make package`)
|
||
4 # push/PR, plus on-demand via the Gitea "Run workflow" button. R
|
||
5 # inside the build-image (see ../../build-image/Dockerfile, buil
|
||
6 # via ../../build-image.sh and ../../upload-image.sh), which bun
|
||
dles
|
||
needed
|
||
ld-image
|
||
site
|
||
ich
|
||
… +79 lines (ctrl+o to expand)
|
||
|
||
● Now let's update the README to document the new package/CI flow, then validate
|
||
everything.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Update(README.md)
|
||
⎿ Added 3 lines, removed 1 line
|
||
19 below); `res/assets/extract_assets.sh` extracts it into `ss_e
|
||
e/`.
|
||
20 - `res/run.sh` - the launcher script, copied into `dist/` on bu
|
||
ild.
|
||
21 - `Makefile` - assembles `dist/`, a self-contained runnable cop
|
||
y of the
|
||
22 - game, out of the compiled engine and the extracted assets.
|
||
22 + game, out of the compiled engine and the extracted assets. Al
|
||
+so builds
|
||
23 + `dist/questshock-<version>-linux-<arch>.tar.gz`, a redistribu
|
||
+table
|
||
24 + package that omits the proprietary game assets (`make package
|
||
+`).
|
||
25
|
||
26 ## Building
|
||
27
|
||
|
||
● Update(README.md) ⎿ Added 14 lines
|
||
56 they aren't installed locally it falls back to running the extr
|
||
action in
|
||
57 a throwaway Docker container instead.
|
||
58
|
||
59 +## Packaging
|
||
60 +
|
||
61 +`make package` builds `dist/questshock-<version>-linux-<arch>.t
|
||
+ar.gz`: the
|
||
62 +compiled binary, its runtime libraries, shaders, a default MIDI
|
||
63 +soundfont, license information, and `res/GET_ASSETS.txt` in pla
|
||
+ce of the
|
||
64 +actual game data (which the tarball never includes). Version co
|
||
+mes from
|
||
65 +the current git tag (push a `vX.Y.Z` tag to drive a release); w
|
||
+ithout one
|
||
66 +it builds an untagged `0.0.0-dev+<sha>` placeholder.
|
||
67 +
|
||
68 +A Gitea Actions workflow (`.gitea/workflows/build.yml`) builds
|
||
+this
|
||
69 +package on every push, using the build-image as its container (
|
||
+so no
|
||
70 +extra setup is needed in CI beyond the image itself), and publi
|
||
+shes the
|
||
71 +resulting tarball to dl.ladkau.de.
|
||
72 +
|
||
73 ## License
|
||
74
|
||
75 The original tooling in this repository (the Docker build image
|
||
, build
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● The image rebuild is taking a bit longer this time (probably network
|
||
variance); it's been moved to background and I'll get notified when it's done.
|
||
I'll wait for that rather than poll.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● I'll wait for the background build notification rather than poll.
|
||
|
||
✻ Crunched for 15m 36s
|
||
|
||
● Background command "./build-image.sh 2>&1 | tail -20" completed (exit
|
||
code 0)
|
||
|
||
● The image rebuild finished. Let me check the output and proceed with
|
||
validation.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Image built successfully with openssh-client and the QUESTSHOCK_BUILD_IMAGE
|
||
marker. Now let's test make package via the normal docker path (host machine,
|
||
no marker set).
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● make package works via the docker path (host machine, no
|
||
QUESTSHOCK_BUILD_IMAGE set → used ./run-image.sh). Let's inspect the tarball
|
||
contents.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Now let's simulate the CI code path — running make package directly inside the
|
||
image (as Gitea Actions would, bypassing our custom entrypoint since Actions
|
||
container jobs override it).
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Good catch on a subtle issue: git describe/git rev-parse silently failed
|
||
inside the container due to git's "dubious ownership" safety check (root
|
||
running git against files owned by a different uid), producing a malformed
|
||
version string (0.0.0-dev+ with no sha). Let me harden make package against
|
||
that.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Update(Makefile)
|
||
⎿ Added 1 line
|
||
73 # just run it untagged for a local dev build (gets a 0.0.0-dev+
|
||
<sha>
|
||
74 # placeholder version, with a warning).
|
||
75 package: engine
|
||
76 + @git config --global --add safe.directory "$$(pwd)" 2>/dev/nu
|
||
+ll || true
|
||
77 @V="$(VERSION)"; \
|
||
78 if [ -z "$$V" ]; then \
|
||
79 if TAG=$$(git describe --tags --exact-match --match 'v[0-9]
|
||
*.[0-9]*.[0-9]*' 2>/dev/null); then \
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● That test left root-owned files in the user's actual repo since I bypassed the
|
||
gosu entrypoint. Let me fix that immediately with a chown before doing
|
||
anything else.
|
||
|
||
Ran 3 shell commands (ctrl+o to expand)
|
||
|
||
● Fixed. Let me clean up properly now and redo the CI-path simulation running as
|
||
my own user (via --user) so it can't leave root-owned files again.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Clean now. Let's re-run the CI-path simulation properly as my own uid this
|
||
time.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
● The version string now correctly includes the sha (0.0.0-dev+3ed6400),
|
||
confirming the safe.directory fix works, and no root-owned files this time.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Let's do one more smoke test — extract the package tarball standalone and
|
||
confirm the binary launches correctly from that location (with no assets
|
||
present, as a real downloader would have).
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● That's the expected, correct failure mode — it clearly logs the missing
|
||
res/data/*.res files (proving run.sh/library paths are all still correct)
|
||
before segfaulting on the totally-absent game data, which is an inherent
|
||
engine limitation, not a packaging bug. A user who follows GET_ASSETS.txt and
|
||
drops in res/data/res/sound gets exactly the working setup already validated
|
||
earlier.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Everything's clean and validated. Summary of what changed:
|
||
|
||
New make package target — builds
|
||
dist/questshock-<version>-linux-<arch>.tar.gz: the binary, its runtime .so
|
||
libraries, shaders, a MIDI soundfont, LICENSE (MIT) + LICENSE.Shockolate
|
||
(GPLv3, verbatim) + NOTICE.txt, run.sh, and res/GET_ASSETS.txt in place of the
|
||
actual game data — explaining exactly which files are needed from an Enhanced
|
||
Edition install (res/pc/hd/data + res/pc/cdrom/data merged, res/pc/hd/sound)
|
||
and where to drop them, with no dependency on this source repo's own tooling.
|
||
Version comes from git describe --tags (push a vX.Y.Z tag), falling back to a
|
||
0.0.0-dev+<sha> placeholder.
|
||
|
||
CI: .gitea/workflows/build.yml runs on every push/PR using the build-image as
|
||
its container, builds the package, uploads it as a build artifact, and (on
|
||
push only) publishes the tarball via SFTP to dl.ladkau.de/files/questshock/.
|
||
|
||
Two real bugs I found and fixed while validating, not just assumed away:
|
||
1. The engine build script assumed it always runs via docker run with the repo
|
||
at /workspace. Gitea Actions container jobs run inside the build-image
|
||
directly (no nested docker available), so I added a QUESTSHOCK_BUILD_IMAGE
|
||
marker the Dockerfile sets, and the Makefile's engine target now compiles
|
||
directly when it detects that, instead of trying (and failing) to shell out to
|
||
run-image.sh.
|
||
2. git describe/git rev-parse silently failed inside the container due to
|
||
git's dubious-ownership check, producing a broken version string (0.0.0-dev+
|
||
with no sha). Fixed by registering the workspace as a safe directory in the
|
||
package recipe itself.
|
||
|
||
I validated the whole thing three ways: the normal host→docker path, a direct
|
||
simulation of the CI code path (running as a container job would, no nested
|
||
docker), and extracting the resulting tarball standalone and confirming it
|
||
launches and correctly reports missing assets rather than doing anything
|
||
worse.
|
||
|
||
✻ Brewed for 7m 15s
|
||
|
||
❯ hmm, running run-image no longer produces a dist directory
|
||
|
||
Ran 2 shell commands (ctrl+o to expand)
|
||
|
||
● User answered Claude's questions:
|
||
⎿ · Just so I fix the right thing: what exactly did you run? → Ran
|
||
./run-image.sh directly
|
||
|
||
● That's expected behavior, not a regression — run-image.sh only ever compiled
|
||
the engine into engine/.build-output/; dist/ needs make dist afterward. Let me
|
||
make that clearer by having the script say so.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Update(run-image.sh)
|
||
⎿ Added 7 lines
|
||
46 -e HOST_GID="$(id -g)" \
|
||
47 "$IMAGE" \
|
||
48 "$@"
|
||
49 +
|
||
50 +if [ "$#" -eq 0 ]; then
|
||
51 + echo
|
||
52 + echo "This only compiled the engine (engine/.build-output/)
|
||
+ - it does not"
|
||
53 + echo "create dist/ by itself. Run 'make dist' next to assem
|
||
+ble a runnable"
|
||
54 + echo "copy (or 'make package' for a redistributable tarball
|
||
+)."
|
||
55 +fi
|
||
|
||
● Let's verify the new hint actually shows up.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Confirmed — the hint prints correctly now. To be clear: this wasn't a
|
||
regression, ./run-image.sh was never responsible for producing dist/, only for
|
||
compiling the engine into engine/.build-output/. make dist (or make package)
|
||
is the step that assembles the runnable/distributable output, and that still
|
||
works fine (I re-ran it above). I've added a printed reminder at the end of
|
||
run-image.sh so this doesn't trip anyone up again.
|
||
|
||
✻ Churned for 2m 55s
|
||
|
||
❯ cool, what would be a good commit message
|
||
|
||
Ran 2 shell commands (ctrl+o to expand)
|
||
|
||
● Since it looks like everything's already staged, here's a message that matches
|
||
the "why"-focused, plain-sentence style of your existing commit:
|
||
|
||
Subject:
|
||
Add Docker build pipeline, asset extraction, and CI release packaging
|
||
|
||
Body:
|
||
Vendor the Shockolate engine and build it via a Docker image with every
|
||
dependency (SDL2, SDL2_mixer, fluidsynth-lite, a MIDI soundfont)
|
||
prebuilt, so compiling the engine needs no network access - just the
|
||
image and the engine source. Add res/assets/extract_assets.sh to pull
|
||
the game's data files out of a purchased GOG installer, and a Makefile
|
||
that assembles a runnable dist/ from the two.
|
||
|
||
Also add `make package`, which builds a redistributable tarball that
|
||
omits the proprietary game assets (shipping res/GET_ASSETS.txt instead)
|
||
plus license information for both the MIT tooling and the GPLv3 engine,
|
||
and a Gitea Actions workflow that builds and publishes it to
|
||
dl.ladkau.de on every push.
|
||
|
||
Want me to go ahead and commit this (as-is or tweaked), or would you rather
|
||
split it into two commits (engine/build pipeline vs. packaging/CI)?
|
||
|
||
✻ Cogitated for 1m 4s
|
||
|
||
❯ please do not commit - I will do commit and push
|
||
● Understood — I won't commit or push anything. Let me know if you want any
|
||
changes before you commit.
|
||
|
||
✻ Cooked for 12s
|
||
|
||
❯ please include the logo in the res folder as part of the readme.md file right
|
||
under the heading. running the action in gitea gives an error: Run
|
||
actions/checkout@v4
|
||
with:
|
||
fetch-depth: 0
|
||
OCI runtime exec failed: exec failed: unable to start container process:
|
||
exec: "node": executable file not found in $PATH
|
||
❌ Failure - Main actions/checkout@v4
|
||
exitcode '127': command not found, please refer to
|
||
https://github.com/nektos/act/issues/107 for more information
|
||
|
||
|
||
Searched for 1 pattern, read 1 file, ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Logo looks great — I'll add it under the heading. Now let's fix both things.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Update(README.md)
|
||
⎿ Added 2 lines
|
||
1 # questshock
|
||
2
|
||
3 +
|
||
4 +
|
||
5 An open source project to play the classic 1994 System Shock on
|
||
a VR
|
||
6 headset, built on top of [Shockolate](https://github.com/Interru
|
||
pt/systemshock),
|
||
7 a cross-platform port of the original game.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Now let's fix the Gitea Actions failure — actions/checkout@v4 is a
|
||
Node.js-based action, and our build-image has no Node.js installed, so it
|
||
can't run inside the container job. Let's add it.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Update(build-image/Dockerfile)
|
||
⎿ Added 5 lines
|
||
18 # Soundfont used for MIDI music, matching what engine/build_dep
|
||
s.sh itself
|
||
19 # fetches (a free substitute for the Windows default GM soundfo
|
||
nt).
|
||
20 ARG SOUNDFONT_URL=http://rancid.kapsi.fi/windows.sf2
|
||
21 +# Gitea/GitHub Actions' JS-based actions (actions/checkout,
|
||
22 +# actions/upload-artifact, ...) need a node binary in the conta
|
||
+iner job's
|
||
23 +# PATH - this image is otherwise pure C toolchain, so it isn't
|
||
+pulled in
|
||
24 +# by anything else.
|
||
25 +ARG NODE_VERSION=20.18.1
|
||
26
|
||
27 ENV DEBIAN_FRONTEND=noninteractive
|
||
28
|
||
|
||
● Update(build-image/Dockerfile)
|
||
⎿ Added 7 lines
|
||
88 RUN mkdir -p soundfont \
|
||
89 && curl -sSL -o soundfont/default.sf2 "${SOUNDFONT_URL}"
|
||
90
|
||
91 +# Node.js: needed only so Gitea/GitHub Actions' JS-based actio
|
||
+ns can run
|
||
92 +# when this image is used as a CI job's container - see NODE_V
|
||
+ERSION above.
|
||
93 +RUN curl -sSL -o /tmp/node.tar.xz \
|
||
94 + "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE
|
||
+_VERSION}-linux-x64.tar.xz" \
|
||
95 + && tar -xJf /tmp/node.tar.xz -C /usr/local --strip-compone
|
||
+nts=1 \
|
||
96 + && rm /tmp/node.tar.xz
|
||
97 +
|
||
98 COPY build-image/docker-entrypoint.sh /usr/local/bin/docker-en
|
||
trypoint.sh
|
||
99 COPY build-image/build-engine.sh /usr/local/bin/build-engine.s
|
||
h
|
||
100 RUN chmod +x /usr/local/bin/docker-entrypoint.sh /usr/local/bi
|
||
n/build-engine.sh
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Write(build-image/VERSION)
|
||
⎿ Added 1 line, removed 1 line
|
||
1 -1
|
||
1 +2
|
||
|
||
● Now let's rebuild the image and verify Node.js works, plus confirm the engine
|
||
build still succeeds.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
❯ maybe leave the image version as 1 until we have a first successful run
|
||
|
||
● Write(build-image/VERSION)
|
||
⎿ Added 1 line, removed 1 line
|
||
1 -2
|
||
1 +1
|
||
|
||
● Reverted — VERSION stays at 1. Summary of what's in place now:
|
||
|
||
- README logo: res/logo_small.png added right under the heading.
|
||
- Dockerfile fix: added a Node.js install step (needed because
|
||
actions/checkout@v4 and actions/upload-artifact@v3 are JS-based actions, and
|
||
Gitea/act_runner executes them inside the job's container — our build-image
|
||
had no node in PATH, causing that exact error).
|
||
|
||
Since I didn't rebuild/push, the image at
|
||
cr.ladkau.de/questshock/builder:1/:latest still lacks Node.js. When you're
|
||
ready: ./build-image.sh to rebuild locally, verify it, then ./upload-image.sh
|
||
to push — after that a re-run of the Gitea Action should get past the checkout
|
||
step. Once that's confirmed working, bump build-image/VERSION to 2 (or just
|
||
re-push over 1/latest, your call).
|
||
|
||
✻ Sautéed for 30s
|
||
|
||
❯ since the build image never ran successfully it should stay as version 1.
|
||
|
||
● That makes sense — no version bump until a build with that image has actually
|
||
succeeded once. I'll keep that in mind for this project.
|
||
|
||
Recalled 1 memory, wrote 2 memories (ctrl+o to expand)
|
||
|
||
● Noted and saved for future reference. Version stays at 1 — you'll bump it once
|
||
a CI run actually succeeds against it.
|
||
✻ Baked for 3m 13s
|
||
|
||
❯ now the next step is to run the game on the meta quest 2 headset. I have
|
||
installed sidequest and connected it to the quest 2 headset. I should be able
|
||
to install an apk image and copy over the assets into a folder. Most other
|
||
games which require external assets seem to require a specific root folder
|
||
like for example QuestZDoom. The folder is created on the first run of the
|
||
app where the app displays that asset files are missing. it should be
|
||
possible to build the apk locally using again the build image (this time with
|
||
a higher version)
|
||
|
||
● This is a much bigger step than the Linux build — Shockolate has no Android
|
||
support at all right now (no NDK toolchain, no Java/SDL2 Android glue, no VR
|
||
display code), so before we can even talk about "build an APK," there's real
|
||
engine porting work involved. Let me check whether anyone's already done part
|
||
of this (similar to how QuestZDoom exists for GZDoom) before I scope this out.
|
||
|
||
● Web Search("Shockolate System Shock Android port Quest VR")
|
||
⎿ Did 1 search in 6s
|
||
|
||
● Web Search("QuestZDoom how it works SDL2 Android Quest VR panel flat screen")
|
||
⎿ Did 1 search in 10s
|
||
|
||
● Good news and bad news from that research:
|
||
|
||
Bad news: there's no existing Android or Quest port of Shockolate to build on.
|
||
And the closest reference point, QuestZDoom, isn't just "an APK build" — it's
|
||
a full 6-DoF VR engine port (stereo rendering, Touch controller mapping, head
|
||
tracking, teleport/smooth locomotion), which is a much bigger lift than a
|
||
Linux Docker build. Getting there from Shockolate's current desktop-only
|
||
C/SDL2 code involves several genuinely separate pieces of work: an Android NDK
|
||
cross-compile toolchain (SDL2/SDL2_mixer/fluidsynth-lite all rebuilt for
|
||
arm64-v8a), an actual Android app project (AndroidManifest.xml, Java
|
||
SDLActivity glue — none of which exist in this repo today),
|
||
Android-appropriate asset-path handling (the engine currently just does
|
||
fopen("res/data/...") relative to cwd, which doesn't work the same way in an
|
||
Android app), and — only for real VR immersion — OpenXR/Oculus Mobile SDK
|
||
integration for stereo rendering and controller input.
|
||
|
||
Good news: what you described (APK installs, runs, detects missing assets,
|
||
creates a folder, you copy files in via SideQuest) doesn't require any of the
|
||
VR-specific work — that's exactly the pattern for getting a plain 2D Android
|
||
app running on Quest first (Quest natively runs sideloaded non-VR Android apps
|
||
as a flat floating panel in Home). That's a much smaller, achievable first
|
||
milestone, with full VR head-tracking as a distinct later phase.
|
||
|
||
● User answered Claude's questions:
|
||
⎿ · Given the gap between "runs on Quest" and "full VR like QuestZDoom", what
|
||
should this step actually target? → Flat 2D panel APK first (Recommended)
|
||
|
||
● Entered plan mode
|
||
Claude is now exploring and designing an implementation approach.
|
||
|
||
● Web Search("QuestZDoom where to copy wad files sdcard folder path SideQuest")
|
||
⎿ Did 1 search in 8s
|
||
|
||
● Web Search("SDL2 2.0.9 android-project template Android.mk build apk
|
||
SDL_androidmain")
|
||
⎿ Did 1 search in 8s
|
||
|
||
● Web Search("Quest Android app requestLegacyExternalStorage sdcard scoped
|
||
storage sideload homebrew 2024")
|
||
⎿ Did 1 search in 13s
|
||
|
||
● Web Search("SDL2 android ResOpenFile fopen AAssetManager relative path
|
||
fallback SDL_RWFromFile")
|
||
⎿ Did 1 search in 9s
|
||
|
||
❯ one more constrant - the shockolate code should not be modified but rather
|
||
pathed if it is required. this is to avoid touching the GPL code.
|
||
|
||
❯ sorry patched not pathed
|
||
|
||
● User answered Claude's questions:
|
||
⎿ · The desktop build is fully offline (everything prebuilt into the image).
|
||
Android's Gradle/AGP toolchain almost always needs network access at build
|
||
time for its own dependency resolution (androidx, Gradle plugin metadata),
|
||
even with the NDK/SDL2/etc. all prebuilt into the image. Is that an
|
||
acceptable exception for the APK build specifically? → Yes, accept it for
|
||
the APK build only (Recommended)
|
||
|
||
● Updated plan
|
||
⎿ /plan to preview
|
||
|
||
● User approved Claude's plan
|
||
⎿ Plan saved to: ~/.claude/plans/wiggly-spinning-swing.md · /plan to edit
|
||
Get System Shock running on Meta Quest 2 (milestone 1: flat 2D panel)
|
||
|
||
Context
|
||
|
||
The user wants to sideload System Shock onto a Quest 2 via SideQuest, with
|
||
an asset folder created on first run (like QuestZDoom's /QuestZDoom/
|
||
folder) that they populate by copying files over. Research this session
|
||
confirmed:
|
||
|
||
- There is no existing Android or Quest port of Shockolate to build on.
|
||
- QuestZDoom, the closest reference point, is a full 6DoF VR engine port
|
||
(stereo rendering, Touch controller mapping, OpenXR) - a much bigger
|
||
scope than "build an APK". The user has confirmed (via question) that
|
||
this step targets a flat 2D panel app first - Quest natively runs a
|
||
sideloaded plain Android activity as a floating 2D panel with no VR SDK
|
||
or manifest entries required. Full 6DoF VR (OpenXR, controller input,
|
||
stereo rendering) is explicitly out of scope for this step.
|
||
- Input for this milestone: a Bluetooth mouse/keyboard connected to the
|
||
Quest (SDL2's Android backend supports HID keyboard/mouse). No
|
||
on-screen touch controls or controller mapping now.
|
||
- Hard constraint (user-stated): engine/ (the vendored GPLv3
|
||
Shockolate source) must not be modified in place. Any Android-specific
|
||
change to its source, if one turns out to be unavoidable, must be a
|
||
patch applied at build time to a scratch copy - never a hand-edit of
|
||
the committed snapshot.
|
||
- Confirmed trade-off: unlike the fully-offline desktop build,
|
||
make apk is allowed to need network access at build time (Gradle/AGP's
|
||
own dependency resolution) - a scoped, documented exception, not a
|
||
change to the desktop build's philosophy.
|
||
- Per the saved build-image version policy: build-image/VERSION stays
|
||
at 1 until a build with the new Android toolchain actually succeeds
|
||
locally - then bump it.
|
||
|
||
Key design decision: zero changes to engine/, via a constructor shim
|
||
|
||
Shockolate's main() (in src/MacSrc/Shock.c) already becomes SDL2's
|
||
SDL_main automatically (SDL_main.h's macro rename, already in effect on
|
||
the desktop build too) - there is no way to run code before it without
|
||
either editing engine/ or a link-time trick. The plan uses the trick: a
|
||
new Android-only source file (android/app/src/main/cpp/android_shim.c,
|
||
NOT under engine/) defines a function marked
|
||
__attribute__((constructor)), which the C runtime guarantees runs at
|
||
shared-library load time - before main()/SDL_main() executes. That
|
||
function will:
|
||
|
||
1. Ensure /sdcard/questshock/ exists.
|
||
2. Extract our own bundled build artifacts (shaders/, the soundfont,
|
||
a copy of the "get assets" instructions) from the APK's Android assets
|
||
into that folder, if not already present - mirroring make package's
|
||
approach on desktop (ship everything except the proprietary game data).
|
||
3. chdir() into /sdcard/questshock/.
|
||
|
||
Because Shockolate's own file I/O (ResOpenFile, fopen_caseless, plain
|
||
fopen) already uses bare relative paths like "res/data/xxx.res"
|
||
(confirmed via grep during the desktop build work), and none of it routes
|
||
through SDL_RWops (so Android's automatic asset-manager fallback for
|
||
SDL_RWFromFile doesn't apply here), this chdir alone is sufficient: if
|
||
the user copies res/data/ and res/sound/ into
|
||
/sdcard/questshock/res/, the exact same relative-path lookups that
|
||
already work in the Linux dist/ build will resolve correctly on
|
||
Android too - no engine source changes needed. Missing-asset behavior
|
||
(warn-then-crash) will be identical to what was already observed and
|
||
accepted for the Linux package tarball without assets.
|
||
|
||
If, once building/testing, something in engine/ genuinely needs a
|
||
change (the main known risk: the desktop OpenGL shaders in
|
||
engine/shaders/*.{vert,frag} may not compile as-is under OpenGL ES,
|
||
which Android uses) - per the hard constraint, this must NOT be hand-
|
||
edited into the tracked engine/ snapshot. Instead: add .patch files
|
||
under a new android/engine-patches/ directory, and have the Android
|
||
build step apply them (patch/git apply) to a throwaway copy of
|
||
engine/ made during the build, before compiling. engine/ as committed
|
||
stays byte-for-byte the vendored upstream snapshot either way.
|
||
|
||
What's being added
|
||
|
||
android/ (new Gradle project, parallel to engine/, build-image/):
|
||
- Standard SDL2-for-Android skeleton: SDLActivity-based Java Activity,
|
||
AndroidManifest.xml (targetSdkVersion 29 + requestLegacyExternalStorage
|
||
- READ/WRITE_EXTERNAL_STORAGE - keeps /sdcard/questshock/ a plain,
|
||
unrestricted shared folder, avoiding Android 11+ scoped storage
|
||
entirely, matching the QuestZDoom-style precedent), build.gradle,
|
||
gradlew.
|
||
- app/src/main/cpp/CMakeLists.txt: compiles engine/src/**/*.c(c)
|
||
by reference (relative path into engine/, not copied) plus
|
||
android_shim.c, linking the prebuilt Android SDL2/SDL2_mixer/
|
||
fluidsynth-lite from the image. Applies any android/engine-patches/
|
||
patches to a build-time scratch copy first, if that directory is
|
||
non-empty.
|
||
- app/src/main/assets/: shaders/*, the soundfont, and a copy of a
|
||
Quest-specific "get assets" text file - bundled into the APK, extracted
|
||
to /sdcard/questshock/ on first run by android_shim.c.
|
||
- App icon derived from the existing res/logo.png/res/logo_small.png.
|
||
- Application ID: de.ladkau.questshock (matches the existing
|
||
cr.ladkau.de/dl.ladkau.de naming already used in this project;
|
||
trivial to rename later).
|
||
|
||
build-image/Dockerfile (still version 1 until a local build
|
||
succeeds): add OpenJDK 17, the Android cmdline-tools + platform 29 +
|
||
build-tools + a pinned NDK, and cross-compile SDL2, SDL2_mixer, and
|
||
fluidsynth-lite for arm64-v8a at image-build time into
|
||
/opt/prebuilt/android/{sdl2,sdl2_mixer,fluidsynth-lite} - same
|
||
"prebuilt so the actual build needs minimal extra downloads" philosophy
|
||
as the existing desktop layer, with the one accepted exception that
|
||
Gradle itself still needs network access when ./gradlew assembleDebug
|
||
actually runs.
|
||
|
||
build-image/build-apk.sh (parallel to build-engine.sh): runs
|
||
./gradlew assembleDebug in android/, copies the resulting APK to
|
||
dist/.
|
||
|
||
Makefile: new apk target, mirroring the existing engine target's
|
||
QUESTSHOCK_BUILD_IMAGE detection (compile directly if already inside the
|
||
image, else shell out via ./run-image.sh).
|
||
|
||
README.md: document the Quest install flow (SideQuest sideload,
|
||
/sdcard/questshock/ folder, what goes in it) and make apk.
|
||
|
||
Verification (what I can check vs. what needs the real headset)
|
||
|
||
I can verify locally: the build-image builds with the new Android layer,
|
||
the arm64 cross-compiles of SDL2/SDL2_mixer/fluidsynth-lite succeed, and
|
||
make apk produces a real .apk with the native library and assets
|
||
correctly embedded (via unzip -l, aapt dump badging/manifest checks).
|
||
|
||
I cannot verify actual runtime behavior - this sandboxed environment has
|
||
no GPU-accelerated Android emulator and no access to real Quest hardware.
|
||
OpenGL ES shader compatibility, the first-run folder/extraction behavior,
|
||
and actual gameplay will need to be tested by the user on their Quest 2
|
||
via SideQuest. I'll flag remaining risk areas explicitly rather than
|
||
claim success I can't check.
|
||
|
||
Once a local build of the new image succeeds, bump build-image/VERSION
|
||
to 2 (per the saved policy - not before).
|
||
❯ yes that is indeed acceptable - the offline build for the (core) engine
|
||
should just give a solid foundation. concern was that some dependencies might
|
||
not be reliable.
|
||
|
||
● Good context — that confirms the scoped exception is fine. Let's start
|
||
building. First, pinning a modern SDL2 release for the Android side.
|
||
|
||
Ran 2 shell commands (ctrl+o to expand)
|
||
|
||
❯ the current build for linux should of course continue to work.
|
||
|
||
Ran 4 shell commands (ctrl+o to expand)
|
||
|
||
✻ Crunched for 35m 40s
|
||
|
||
❯ please continue
|
||
|
||
Searched for 1 pattern, ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Now I have a clear technical picture. Let me start implementing. First, let's
|
||
inspect the SDL2 Java glue and Activity class we'll reuse, then build out the
|
||
Android layer piece by piece.
|
||
|
||
Read 1 file, ran 2 shell commands (ctrl+o to expand)
|
||
● Update(build-image/Dockerfile)
|
||
⎿ Added 17 lines
|
||
24 # by anything else.
|
||
25 ARG NODE_VERSION=20.18.1
|
||
26
|
||
27 +# Android layer, for the Quest 2 APK build (see build-apk.sh).
|
||
+Same SDL2
|
||
28 +# major version as the desktop build above, but SDL2/SDL2_mixer
|
||
+ are much
|
||
29 +# newer releases here - unlike the desktop autotools build, And
|
||
+roid needs
|
||
30 +# their real CMake+NDK build support, which only landed in late
|
||
+r
|
||
31 +# releases. targetSdkVersion 29 (not compileSdk, which can and
|
||
+does stay
|
||
32 +# newer) is deliberate: paired with requestLegacyExternalStorag
|
||
+e in the
|
||
33 +# manifest, it keeps /sdcard a plain, unrestricted shared folde
|
||
+r on
|
||
34 +# Android 10-13 devices like the Quest 2, instead of hitting An
|
||
+droid 11+
|
||
35 +# scoped storage.
|
||
36 +ARG ANDROID_SDL2_VERSION=2.28.5
|
||
37 +ARG ANDROID_SDL2_MIXER_VERSION=2.8.0
|
||
38 +ARG ANDROID_CMDLINE_TOOLS_VERSION=11076708
|
||
39 +ARG ANDROID_PLATFORM_VERSION=29
|
||
40 +ARG ANDROID_BUILD_TOOLS_VERSION=34.0.0
|
||
41 +ARG ANDROID_NDK_VERSION=26.1.10909125
|
||
42 +ARG ANDROID_CMAKE_VERSION=3.22.1
|
||
43 +
|
||
44 ENV DEBIAN_FRONTEND=noninteractive
|
||
45
|
||
46 # build-essential/cmake/make: engine/ itself (CMake) and SDL2/S
|
||
DL2_mixer
|
||
|
||
● Update(build-image/Dockerfile)
|
||
⎿ Added 89 lines
|
||
112 && tar -xJf /tmp/node.tar.xz -C /usr/local --strip-compone
|
||
nts=1 \
|
||
113 && rm /tmp/node.tar.xz
|
||
114
|
||
115 +# openjdk-17-jdk-headless: Gradle/AGP's own minimum JDK for th
|
||
+e APK build.
|
||
116 +# unzip: extracts the Android cmdline-tools zip below.
|
||
117 +RUN apt-get update && apt-get install -y --no-install-recommen
|
||
+ds \
|
||
118 + openjdk-17-jdk-headless unzip \
|
||
119 + && rm -rf /var/lib/apt/lists/*
|
||
120 +
|
||
121 +# Android SDK/NDK: the cmdline-tools' own sdkmanager installs
|
||
+exactly the
|
||
122 +# platform/build-tools/NDK/CMake versions the app/build.gradle
|
||
+ below pins.
|
||
123 +# gradlew (invoked by build-apk.sh) resolves its own pinned Gr
|
||
+adle version
|
||
124 +# itself - no Gradle binary is baked into this image. Unlike t
|
||
+he manually
|
||
125 +# cross-compiled SDL2/SDL2_mixer/fluidsynth-lite below, `./gra
|
||
+dlew
|
||
126 +# assembleDebug` still needs network access at build time for
|
||
+Gradle/AGP's
|
||
127 +# own dependency resolution - an accepted, deliberate exceptio
|
||
+n to this
|
||
128 +# image's otherwise-offline build philosophy (see build-apk.sh
|
||
+).
|
||
129 +ENV ANDROID_HOME=/opt/android-sdk \
|
||
130 + ANDROID_SDK_ROOT=/opt/android-sdk
|
||
131 +ENV PATH=${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HO
|
||
+ME}/platform-tools:${PATH}
|
||
132 +
|
||
133 +RUN mkdir -p "${ANDROID_HOME}/cmdline-tools" \
|
||
134 + && curl -sSL -o /tmp/cmdline-tools.zip \
|
||
135 + "https://dl.google.com/android/repository/commandlinet
|
||
+ools-linux-${ANDROID_CMDLINE_TOOLS_VERSION}_latest.zip" \
|
||
136 + && unzip -q /tmp/cmdline-tools.zip -d "${ANDROID_HOME}/cmd
|
||
+line-tools" \
|
||
137 + && mv "${ANDROID_HOME}/cmdline-tools/cmdline-tools" "${AND
|
||
+ROID_HOME}/cmdline-tools/latest" \
|
||
138 + && rm /tmp/cmdline-tools.zip \
|
||
139 + && yes | sdkmanager --licenses >/dev/null \
|
||
140 + && sdkmanager --install \
|
||
141 + "platform-tools" \
|
||
142 + "platforms;android-${ANDROID_PLATFORM_VERSION}" \
|
||
143 + "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" \
|
||
144 + "ndk;${ANDROID_NDK_VERSION}" \
|
||
145 + "cmake;${ANDROID_CMAKE_VERSION}"
|
||
146 +
|
||
147 +ENV ANDROID_NDK_HOME=${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION
|
||
+}
|
||
148 +ENV ANDROID_NDK_TOOLCHAIN=${ANDROID_NDK_HOME}/build/cmake/andr
|
||
+oid.toolchain.cmake
|
||
149 +
|
||
150 +# SDL2, SDL2_mixer, and fluidsynth-lite, cross-compiled for ar
|
||
+m64-v8a (the
|
||
151 +# Quest 2's only ABI) via the NDK's CMake toolchain file, each
|
||
+ installed
|
||
152 +# to its own prefix under /opt/prebuilt/android/ - mirrors the
|
||
+ desktop
|
||
153 +# build_ext/built_sdl / built_sdl_mixer layout, just for Andro
|
||
+id. Shipped
|
||
154 +# in the APK as separate .so's (see build-apk.sh), loaded in t
|
||
+hat order by
|
||
155 +# QuestShockActivity.getLibraries() before the game's own libm
|
||
+ain.so.
|
||
156 +ARG ANDROID_ABI=arm64-v8a
|
||
157 +
|
||
158 +RUN curl -sSLO "https://www.libsdl.org/release/SDL2-${ANDROID_
|
||
+SDL2_VERSION}.tar.gz" \
|
||
159 + && tar xf "SDL2-${ANDROID_SDL2_VERSION}.tar.gz" \
|
||
160 + && cmake -S "SDL2-${ANDROID_SDL2_VERSION}" -B build-sdl2-a
|
||
+ndroid \
|
||
161 + -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK_TOOLCHAIN}" \
|
||
162 + -DANDROID_ABI="${ANDROID_ABI}" -DANDROID_PLATFORM="and
|
||
+roid-${ANDROID_PLATFORM_VERSION}" \
|
||
163 + -DCMAKE_INSTALL_PREFIX=/opt/prebuilt/android/sdl2 -DBU
|
||
+ILD_SHARED_LIBS=ON \
|
||
164 + -DSDL_STATIC=OFF \
|
||
165 + && cmake --build build-sdl2-android -j"$(nproc)" \
|
||
166 + && cmake --install build-sdl2-android \
|
||
167 + && rm -rf "SDL2-${ANDROID_SDL2_VERSION}" "SDL2-${ANDROID_S
|
||
+DL2_VERSION}.tar.gz" build-sdl2-android
|
||
168 +
|
||
169 +# All optional codecs disabled: Shockolate only ever calls
|
||
170 +# Mix_LoadWAV_RW/Mix_HookMusic (confirmed by grep - it feeds f
|
||
+luidsynth's
|
||
171 +# own PCM output through Mix_HookMusic, and never loads OGG/MO
|
||
+D/FLAC/MP3
|
||
172 +# game data), so plain WAVE support (always built in, no extra
|
||
173 +# dependency) is all that's needed - avoiding SDL2_mixer's ven
|
||
+dored
|
||
174 +# third-party codec libraries entirely.
|
||
175 +RUN curl -sSLO "https://www.libsdl.org/projects/SDL_mixer/rele
|
||
+ase/SDL2_mixer-${ANDROID_SDL2_MIXER_VERSION}.tar.gz" \
|
||
176 + && tar xf "SDL2_mixer-${ANDROID_SDL2_MIXER_VERSION}.tar.gz
|
||
+" \
|
||
177 + && cmake -S "SDL2_mixer-${ANDROID_SDL2_MIXER_VERSION}" -B
|
||
+build-sdl2mixer-android \
|
||
178 + -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK_TOOLCHAIN}" \
|
||
179 + -DANDROID_ABI="${ANDROID_ABI}" -DANDROID_PLATFORM="and
|
||
+roid-${ANDROID_PLATFORM_VERSION}" \
|
||
180 + -DCMAKE_PREFIX_PATH=/opt/prebuilt/android/sdl2 \
|
||
181 + -DCMAKE_INSTALL_PREFIX=/opt/prebuilt/android/sdl2_mixe
|
||
+r -DBUILD_SHARED_LIBS=ON \
|
||
182 + -DSDL2MIXER_VENDORED=OFF -DSDL2MIXER_SAMPLES=OFF -DSDL
|
||
+2MIXER_CMD=OFF \
|
||
183 + -DSDL2MIXER_FLAC=OFF -DSDL2MIXER_GME=OFF -DSDL2MIXER_M
|
||
+OD=OFF \
|
||
184 + -DSDL2MIXER_MP3=OFF -DSDL2MIXER_MIDI=OFF -DSDL2MIXER_O
|
||
+PUS=OFF -DSDL2MIXER_VORBIS=OFF \
|
||
185 + && cmake --build build-sdl2mixer-android -j"$(nproc)" \
|
||
186 + && cmake --install build-sdl2mixer-android \
|
||
187 + && rm -rf "SDL2_mixer-${ANDROID_SDL2_MIXER_VERSION}" "SDL2
|
||
+_mixer-${ANDROID_SDL2_MIXER_VERSION}.tar.gz" build-sdl2mixer-a
|
||
+ndroid
|
||
188 +
|
||
189 +RUN git clone https://github.com/EtherTyper/fluidsynth-lite.gi
|
||
+t fluidsynth-lite-android \
|
||
190 + && cd fluidsynth-lite-android \
|
||
191 + && git checkout "${FLUIDSYNTH_LITE_REF}" \
|
||
192 + && sed -i 's/DLL"\ off/DLL"\ on/' CMakeLists.txt \
|
||
193 + && rm -rf .git \
|
||
194 + && cd .. \
|
||
195 + && cmake -S fluidsynth-lite-android -B build-fluidsynth-an
|
||
+droid \
|
||
196 + -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK_TOOLCHAIN}" \
|
||
197 + -DANDROID_ABI="${ANDROID_ABI}" -DANDROID_PLATFORM="and
|
||
+roid-${ANDROID_PLATFORM_VERSION}" \
|
||
198 + && cmake --build build-fluidsynth-android -j"$(nproc)" \
|
||
199 + && mkdir -p /opt/prebuilt/android/fluidsynth-lite/lib /opt
|
||
+/prebuilt/android/fluidsynth-lite/include \
|
||
200 + && cp -a build-fluidsynth-android/src/libfluidsynth.so* /o
|
||
+pt/prebuilt/android/fluidsynth-lite/lib/ \
|
||
201 + && cp -a fluidsynth-lite-android/include/. /opt/prebuilt/a
|
||
+ndroid/fluidsynth-lite/include/ \
|
||
202 + && rm -rf fluidsynth-lite-android build-fluidsynth-android
|
||
203 +
|
||
204 COPY build-image/docker-entrypoint.sh /usr/local/bin/docker-en
|
||
trypoint.sh
|
||
205 COPY build-image/build-engine.sh /usr/local/bin/build-engine.s
|
||
h
|
||
206 RUN chmod +x /usr/local/bin/docker-entrypoint.sh /usr/local/bi
|
||
n/build-engine.sh
|
||
|
||
❯ this should of course also run on a quest 3 and newer versions
|
||
|
||
● Good point — since Quest 3 (and 3S/Pro) all use the same Snapdragon XR2-family
|
||
arm64-v8a chips and the same targetSdk-based legacy-storage behavior, nothing
|
||
technical changes — this design already generalizes. Let me just fix the
|
||
wording so it doesn't imply Quest 2-only.
|
||
|
||
Searched for 1 pattern, read 1 file, ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Update(build-image/Dockerfile)
|
||
⎿ Added 11 lines, removed 9 lines
|
||
24 # by anything else.
|
||
25 ARG NODE_VERSION=20.18.1
|
||
26
|
||
27 -# Android layer, for the Quest 2 APK build (see build-apk.sh).
|
||
-Same SDL2
|
||
28 -# major version as the desktop build above, but SDL2/SDL2_mixer
|
||
- are much
|
||
29 -# newer releases here - unlike the desktop autotools build, And
|
||
-roid needs
|
||
30 -# their real CMake+NDK build support, which only landed in late
|
||
-r
|
||
31 -# releases. targetSdkVersion 29 (not compileSdk, which can and
|
||
-does stay
|
||
32 -# newer) is deliberate: paired with requestLegacyExternalStorag
|
||
-e in the
|
||
33 -# manifest, it keeps /sdcard a plain, unrestricted shared folde
|
||
-r on
|
||
34 -# Android 10-13 devices like the Quest 2, instead of hitting An
|
||
-droid 11+
|
||
35 -# scoped storage.
|
||
27 +# Android layer, for the Quest APK build (Quest 2, 3, 3S, Pro -
|
||
+ all
|
||
28 +# arm64-v8a, see build-apk.sh). Same SDL2 major version as the
|
||
+desktop
|
||
29 +# build above, but SDL2/SDL2_mixer are much newer releases here
|
||
+ - unlike
|
||
30 +# the desktop autotools build, Android needs their real CMake+N
|
||
+DK build
|
||
31 +# support, which only landed in later releases. targetSdkVersio
|
||
+n 29 (not
|
||
32 +# compileSdk, which can and does stay newer) is deliberate: pai
|
||
+red with
|
||
33 +# requestLegacyExternalStorage in the manifest, it keeps /sdcar
|
||
+d a plain,
|
||
34 +# unrestricted shared folder instead of hitting Android 11+ sco
|
||
+ped
|
||
35 +# storage - this is keyed off the *app's* targetSdkVersion, not
|
||
+ the
|
||
36 +# device's own Android version, so it keeps working on newer Qu
|
||
+est
|
||
37 +# hardware/OS updates too, not just whatever's current today.
|
||
38 ARG ANDROID_SDL2_VERSION=2.28.5
|
||
39 ARG ANDROID_SDL2_MIXER_VERSION=2.8.0
|
||
40 ARG ANDROID_CMDLINE_TOOLS_VERSION=11076708
|
||
|
||
Searched for 1 pattern (ctrl+o to expand)
|
||
|
||
● Update(build-image/Dockerfile)
|
||
⎿ Added 1 line, removed 1 line
|
||
150 ENV ANDROID_NDK_TOOLCHAIN=${ANDROID_NDK_HOME}/build/cmake/andr
|
||
oid.toolchain.cmake
|
||
151
|
||
152 # SDL2, SDL2_mixer, and fluidsynth-lite, cross-compiled for ar
|
||
m64-v8a (the
|
||
153 -# Quest 2's only ABI) via the NDK's CMake toolchain file, each
|
||
- installed
|
||
153 +# Quest's only ABI, across Quest 2/3/3S/Pro) via the NDK's CMa
|
||
+ke toolchain file, each installed
|
||
154 # to its own prefix under /opt/prebuilt/android/ - mirrors the
|
||
desktop
|
||
155 # build_ext/built_sdl / built_sdl_mixer layout, just for Andro
|
||
id. Shipped
|
||
156 # in the APK as separate .so's (see build-apk.sh), loaded in t
|
||
hat order by
|
||
|
||
Searched for 1 pattern, read 1 file, ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Let me generate the patch reliably via diff -u rather than hand-crafting it,
|
||
to avoid whitespace mismatches.
|
||
|
||
Made 2 scratchpad edits +32 -16, read 1 file, ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Now let's generate the diff and verify it applies cleanly to a fresh copy.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Patch applies cleanly. Let's save it and clean the timestamp lines.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Write(android/engine-patches/android-shared-lib.patch)
|
||
⎿ Wrote 52 lines to android/engine-patches/android-shared-lib.patch
|
||
1 --- a/CMakeLists.txt
|
||
2 +++ b/CMakeLists.txt
|
||
3 @@ -380,9 +380,22 @@
|
||
4 -include precompiled.h
|
||
5 )
|
||
7 -add_executable(systemshock
|
||
8 - ${MAC_SRC}
|
||
9 -)
|
||
10 +# Android has no standalone executables (Java loads a shared li
|
||
brary via
|
||
… +50 lines (ctrl+o to expand)
|
||
|
||
Ran 2 shell commands (ctrl+o to expand)
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Write(android/build.gradle)
|
||
⎿ Wrote 25 lines to android/build.gradle
|
||
1 // Top-level Gradle build file. Adapted from SDL2's own android-
|
||
2 // template (see app/src/main/java/org/libsdl/app/, copied from
|
||
the same
|
||
3 // template). Unlike the C/SDL2 desktop build, this needs networ
|
||
4 // at build time for Gradle/AGP's own dependency resolution - se
|
||
e
|
||
6 buildscript {
|
||
7 repositories {
|
||
8 mavenCentral()
|
||
10 }
|
||
… +18 lines (ctrl+o to expand)
|
||
|
||
● Write(android/settings.gradle)
|
||
⎿ Wrote 2 lines to android/settings.gradle
|
||
1 rootProject.name = "questshock"
|
||
2 include ':app'
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Write(android/gradle.properties)
|
||
⎿ Wrote 2 lines to android/gradle.properties
|
||
1 org.gradle.jvmargs=-Xmx1536m
|
||
2 android.useAndroidX=true
|
||
|
||
● Write(android/app/build.gradle)
|
||
⎿ Wrote 63 lines to android/app/build.gradle
|
||
1 apply plugin: 'com.android.application'
|
||
2
|
||
4 // ../engine-patches/) scratch copy of engine/, written by build
|
||
-apk.sh
|
||
5 // since it's only known at build time, not something a checked-
|
||
6 // build.gradle can hardcode.
|
||
7 def engineProps = new Properties()
|
||
8 file("${projectDir}/../engine.properties").withInputStream { eng
|
||
9 def engineDir = engineProps.getProperty('engineDir')
|
||
10 if (engineDir == null) {
|
||
… +67 lines (ctrl+o to expand)
|
||
|
||
● Write(android/app/src/main/AndroidManifest.xml)
|
||
⎿ Wrote 49 lines to android/app/src/main/AndroidManifest.xml
|
||
1 <?xml version="1.0" encoding="utf-8"?>
|
||
oid"
|
||
3 android:installLocation="auto">
|
||
4
|
||
6 <uses-feature android:glEsVersion="0x00020000" />
|
||
7
|
||
droid:required="false" />
|
||
9 <uses-feature android:name="android.hardware.bluetooth" andr
|
||
oid:required="false" />
|
||
d:required="false" />
|
||
… +56 lines (ctrl+o to expand)
|
||
|
||
● Write(android/app/src/main/res/values/strings.xml)
|
||
⎿ Wrote 4 lines to android/app/src/main/res/values/strings.xml
|
||
1 <?xml version="1.0" encoding="utf-8"?>
|
||
2 <resources>
|
||
3 <string name="app_name">System Shock</string>
|
||
4 </resources>
|
||
|
||
Ran 2 shell commands (ctrl+o to expand)
|
||
|
||
● Write(android/app/src/main/java/de/ladkau/questshock/QuestShockActivity.java)
|
||
⎿ Wrote 135 lines to
|
||
android/app/src/main/java/de/ladkau/questshock/QuestShockActivity.java
|
||
1 package de.ladkau.questshock;
|
||
2
|
||
3 import android.Manifest;
|
||
4 import android.content.pm.PackageManager;
|
||
5 import android.content.res.AssetManager;
|
||
6 import android.os.Bundle;
|
||
7 import android.system.Os;
|
||
8 import android.util.Log;
|
||
9 import androidx.core.app.ActivityCompat;
|
||
10 import java.io.File;
|
||
… +146 lines (ctrl+o to expand)
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Update(android/app/build.gradle)
|
||
⎿ Added 8 lines
|
||
61 abortOnError false
|
||
62 }
|
||
63 }
|
||
64 +
|
||
65 +dependencies {
|
||
66 + // ActivityCompat.checkSelfPermission/requestPermissions in
|
||
67 + // QuestShockActivity - the plain android.app.Activity APIs
|
||
+ work the
|
||
68 + // same way without androidx, but ActivityCompat is the sta
|
||
+ndard,
|
||
69 + // documented way to request runtime permissions safely.
|
||
70 + implementation 'androidx.core:core:1.12.0'
|
||
71 +}
|
||
|
||
● Write(res/assets/GET_ASSETS_QUEST.txt)
|
||
⎿ Wrote 18 lines to res/assets/GET_ASSETS_QUEST.txt
|
||
1 This app does not include System Shock's game data - it's copyrig
|
||
2 proprietary content that can't be redistributed. To play, you nee
|
||
d a
|
||
4
|
||
5 From your Enhanced Edition install, you need its classic-game dat
|
||
6 sound files - the res/pc/hd/data and res/pc/cdrom/data trees merg
|
||
ed
|
||
in
|
||
8 both: intro.res, objprop.dat), and the res/pc/hd/sound tree, pack
|
||
9 inside the install's sshock.kpf (a zip file).
|
||
… +11 lines (ctrl+o to expand)
|
||
|
||
● Now let's write build-apk.sh — the script that assembles everything
|
||
(scratch-patches engine/, wires up build_ext, stages APK assets, runs Gradle).
|
||
|
||
● Write(build-image/build-apk.sh)
|
||
⎿ Wrote 61 lines to build-image/build-apk.sh
|
||
1 #!/usr/bin/env bash
|
||
see
|
||
3 # android/engine-patches/ - engine/ itself is never modified) pl
|
||
4 # Android SDL2/SDL2_mixer/fluidsynth-lite prebuilt into this ima
|
||
ge at
|
||
if
|
||
6 # already inside this image - see the Makefile's `apk` target).
|
||
8 # Unlike build-engine.sh, this step needs network access: Gradle
|
||
/AGP's
|
||
10 # build-image/Dockerfile's comment on that trade-off).
|
||
… +67 lines (ctrl+o to expand)
|
||
|
||
● Update(build-image/build-apk.sh)
|
||
⎿ Added 1 line, removed 4 lines
|
||
27 cp -a /opt/prebuilt/android/fluidsynth-lite/lib/. "$SCRATCH_ENG
|
||
INE/build_ext/fluidsynth-lite/src/"
|
||
28 cp -a /opt/prebuilt/android/fluidsynth-lite/include/. "$SCRATCH
|
||
_ENGINE/build_ext/fluidsynth-lite/include/"
|
||
29
|
||
30 -echo "$SCRATCH_ENGINE" > /dev/null # (documents intent; engineD
|
||
-ir is written below)
|
||
31 -{
|
||
32 - echo "engineDir=$SCRATCH_ENGINE"
|
||
33 -} > "$ANDROID_DIR/engine.properties"
|
||
30 +echo "engineDir=$SCRATCH_ENGINE" > "$ANDROID_DIR/engine.propert
|
||
+ies"
|
||
31
|
||
32 echo "== Staging bundled assets (shaders, soundfont, get-assets
|
||
text) =="
|
||
33 ASSETS_DIR="$ANDROID_DIR/app/src/main/assets"
|
||
Read 1 file, ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Update(Makefile)
|
||
⎿ Added 1 line, removed 1 line
|
||
14 BUILD_DIR := build
|
||
15 ARCH := $(shell uname -m)
|
||
16
|
||
17 -.PHONY: all dist build-image engine assets package clean
|
||
17 +.PHONY: all dist build-image engine assets package apk clean
|
||
18
|
||
19 all: dist
|
||
20
|
||
|
||
❯ the build image before the android work worked btw and the assets were
|
||
produced in dl.ladkau.de
|
||
|
||
● Great, glad the Node.js fix cleared CI end to end. That means version 1 is now
|
||
confirmed-good for its current (pre-Android) content — consistent with
|
||
keeping it at 1 while I finish this Android layer, then bumping to 2 once I've
|
||
validated the new build locally, per the saved policy. Continuing.
|
||
|
||
● Update(Makefile)
|
||
⎿ Added 16 lines, removed 1 line
|
||
107 rm -rf "$(BUILD_DIR)/package"; \
|
||
108 echo "Wrote $(DIST_DIR)/$$PKG_NAME.tar.gz"
|
||
109
|
||
110 +# Builds the Quest APK (see build-image/build-apk.sh and
|
||
111 +# android/engine-patches/ - engine/ itself is never modified;
|
||
+a patch is
|
||
112 +# applied to a scratch copy at build time instead). Same
|
||
113 +# QUESTSHOCK_BUILD_IMAGE detection as `engine`. Unlike every o
|
||
+ther target
|
||
114 +# here, this needs network access at build time (Gradle/AGP's
|
||
+own
|
||
115 +# dependency resolution) - see build-image/Dockerfile.
|
||
116 +apk:
|
||
117 + @if [ -n "$$QUESTSHOCK_BUILD_IMAGE" ]; then \
|
||
118 + bash build-image/build-apk.sh; \
|
||
119 + else \
|
||
120 + ./run-image.sh bash build-image/build-apk.sh; \
|
||
121 + fi
|
||
122 +
|
||
123 clean:
|
||
124 rm -rf "$(DIST_DIR)" "$(BUILD_DIR)" "$(ENGINE_OUT)" \
|
||
125 engine/build_ext engine/CMakeCache.txt engine/CMakeFiles \
|
||
126 engine/cmake_install.cmake engine/Makefile engine/systemsh
|
||
ock \
|
||
114 - engine/src/Libraries/CMakeFiles
|
||
127 + engine/src/Libraries/CMakeFiles \
|
||
128 + android/engine.properties android/app/src/main/assets andr
|
||
+oid/app/src/main/jniLibs \
|
||
129 + android/app/build android/.gradle android/app/.cxx
|
||
|
||
● Now let's update .gitignore for the new generated Android build artifacts.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Update(.gitignore)
|
||
⎿ Added 10 lines
|
||
17 /engine/systemshock
|
||
18 /engine/src/Libraries/CMakeFiles/
|
||
19
|
||
20 +# Android build artifacts (generated by build-image/build-apk.s
|
||
+h - see
|
||
21 +# android/engine-patches/ for why engine/ itself is never touch
|
||
+ed)
|
||
22 +/android/engine.properties
|
||
23 +/android/app/src/main/assets/
|
||
24 +/android/app/src/main/jniLibs/
|
||
25 +/android/app/build/
|
||
26 +/android/.gradle/
|
||
27 +/android/app/.cxx/
|
||
28 +/android/local.properties
|
||
29 +
|
||
30 # Local registry credentials (see registry.env.example)
|
||
31 /registry.env
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Update(README.md)
|
||
⎿ Added 7 lines, removed 1 line
|
||
23 - `Makefile` - assembles `dist/`, a self-contained runnable cop
|
||
y of the
|
||
24 game, out of the compiled engine and the extracted assets. Al
|
||
so builds
|
||
25 `dist/questshock-<version>-linux-<arch>.tar.gz`, a redistribu
|
||
table
|
||
26 - package that omits the proprietary game assets (`make package
|
||
-`).
|
||
26 + package that omits the proprietary game assets (`make package
|
||
+`), and
|
||
27 + `dist/questshock-debug.apk` for the Quest (`make apk`).
|
||
28 +- `android/` - the Quest app (Java `SDLActivity` glue, Gradle p
|
||
+roject).
|
||
29 + `android/engine-patches/` holds the one small patch needed to
|
||
+ build
|
||
30 + `engine/` as an Android shared library instead of a desktop e
|
||
+xecutable
|
||
31 + - applied to a scratch copy at build time; `engine/` itself i
|
||
+s never
|
||
32 + modified.
|
||
33
|
||
34 ## Building
|
||
35
|
||
|
||
● Update(README.md)
|
||
⎿ Added 21 lines
|
||
78 extra setup is needed in CI beyond the image itself), and publ
|
||
ishes the
|
||
79 resulting tarball to dl.ladkau.de.
|
||
80
|
||
81 +## Playing on Meta Quest
|
||
82 +
|
||
83 +`make apk` builds `dist/questshock-debug.apk` - a plain (non-V
|
||
+R) Android
|
||
84 +app that runs as a flat, floating panel in the Quest's Home en
|
||
+vironment,
|
||
85 +same as any other sideloaded Android app. It's not a head-trac
|
||
+ked 6DoF VR
|
||
86 +port (that's a much larger, separate undertaking); play with a
|
||
+ Bluetooth
|
||
87 +mouse/keyboard connected to the headset.
|
||
88 +
|
||
89 +1. Install the APK with [SideQuest](https://sidequestvr.com/)
|
||
+(or `adb
|
||
90 + install`).
|
||
91 +2. Launch it once. It'll ask for storage permission, then crea
|
||
+te
|
||
92 + `/sdcard/questshock/` and extract its own bundled files (sh
|
||
+aders, a
|
||
93 + default MIDI soundfont) there - `res/data/` and `res/sound/
|
||
+` are
|
||
94 + deliberately left missing, since that's the proprietary gam
|
||
+e data.
|
||
95 +3. With the Quest connected to a PC, use SideQuest's file brow
|
||
+ser (or any
|
||
96 + MTP file manager) to copy your own `res/data/` and `res/sou
|
||
+nd/` (see
|
||
97 + `/sdcard/questshock/GET_ASSETS_QUEST.txt`, extracted in ste
|
||
+p 2, for
|
||
98 + exactly what's needed and where it comes from) into
|
||
99 + `/sdcard/questshock/res/`.
|
||
100 +4. Launch it again.
|
||
101 +
|
||
102 ## License
|
||
103
|
||
104 The original tooling in this repository (the Docker build imag
|
||
e, build
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Update(README.md)
|
||
⎿ Added 7 lines, removed 2 lines
|
||
102 ## License
|
||
103
|
||
104 The original tooling in this repository (the Docker build imag
|
||
e, build
|
||
105 -scripts, Makefile, and asset extraction script) is licensed un
|
||
-der the
|
||
106 -[MIT License](LICENSE).
|
||
105 +scripts, Makefile, asset extraction script, and the Quest app
|
||
+in
|
||
106 +`android/` - aside from `org/libsdl/app/`, see below) is licen
|
||
+sed under
|
||
107 +the [MIT License](LICENSE).
|
||
108
|
||
109 +`android/app/src/main/java/org/libsdl/app/` is copied from
|
||
110 +[SDL2](https://www.libsdl.org/)'s own android-project template
|
||
+ and is
|
||
111 +zlib-licensed, same as SDL2 itself.
|
||
112 +
|
||
113 The vendored engine snapshot in `engine/` is
|
||
114 [Shockolate](https://github.com/Interrupt/systemshock), which
|
||
is licensed
|
||
115 under the **GNU GPLv3** (see `engine/LICENSE`) - it is include
|
||
d unchanged
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● This will take a while (Android SDK/NDK downloads + three arm64
|
||
cross-compiles). I'll wait for it rather than poll.
|
||
⎿ Error: `prompt` is required when `stop` is not true.
|
||
|
||
● That tool is specific to /loop mode, not needed here — the background task
|
||
will notify me directly when it finishes. I'll wait for that.
|
||
|
||
✻ Cooked for 22m 58s
|
||
|
||
❯ is the build image being rebuild?
|
||
|
||
● Yes, it's running in the background right now. Let me check how far it's
|
||
gotten.
|
||
|
||
Read 1 file, ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Yes — it's actively building. Docker's currently running the Android SDK/NDK
|
||
setup step (I can see a container executing that stage). The full build
|
||
(SDK/NDK download + three arm64 cross-compiles: SDL2, SDL2_mixer,
|
||
fluidsynth-lite) will take a while; I'll let it run and report back once it
|
||
completes.
|
||
|
||
✻ Cogitated for 1m 7s
|
||
|
||
❯ for such long running downloads it would be good to have them in layers which
|
||
don't need to be regenerated every time the image needs to be rebuild
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Good call — since I'll likely need to iterate on the Android layer
|
||
specifically, let me consolidate the two separate apt-get install blocks into
|
||
one at the top, so future edits to the Android portion never invalidate the
|
||
(already-validated) desktop build layers or force a redownload of everything
|
||
from scratch. This won't affect the build currently running (Docker already
|
||
snapshotted the Dockerfile when it started).
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Update(build-image/Dockerfile)
|
||
⎿ Added 10 lines
|
||
60 # libogg-dev/libvorbis-dev: SDL2_mixer's OGG Vorbis music decod
|
||
er.
|
||
61 # libasound2-dev: engine/CMakeLists.txt's optional native ALSA
|
||
MIDI output.
|
||
62 # openssh-client: the CI workflow's `sftp` publish step.
|
||
63 +# openjdk-17-jdk-headless: Gradle/AGP's own minimum JDK for the
|
||
+ APK build.
|
||
64 +# unzip: extracts the Android cmdline-tools zip below.
|
||
65 +#
|
||
66 +# All apt installs deliberately live in this one RUN, first, so
|
||
+ editing
|
||
67 +# anything below it (in particular the Android cross-compile st
|
||
+eps, the
|
||
68 +# newest and least settled part of this file) never invalidates
|
||
+ Docker's
|
||
69 +# build cache for this layer - and vice versa, adding a package
|
||
+ here only
|
||
70 +# ever costs a rebuild of the (slow) layers below it, not a re-
|
||
+download
|
||
71 +# of packages that didn't change.
|
||
72 RUN apt-get update && apt-get install -y --no-install-recommend
|
||
s \
|
||
73 build-essential cmake make git curl ca-certificates pkg
|
||
-config gosu \
|
||
74 libgl1-mesa-dev libglx-dev libxext-dev libx11-dev libxr
|
||
andr-dev \
|
||
75 libxi-dev libxfixes-dev libxss-dev libxinerama-dev libx
|
||
cursor-dev \
|
||
76 libogg-dev libvorbis-dev libasound2-dev openssh-client
|
||
\
|
||
77 + openjdk-17-jdk-headless unzip \
|
||
78 && rm -rf /var/lib/apt/lists/*
|
||
79
|
||
80 WORKDIR /opt/prebuilt
|
||
|
||
● Update(build-image/Dockerfile)
|
||
⎿ Removed 6 lines
|
||
124 && tar -xJf /tmp/node.tar.xz -C /usr/local --strip-compone
|
||
nts=1 \
|
||
125 && rm /tmp/node.tar.xz
|
||
126
|
||
127 -# openjdk-17-jdk-headless: Gradle/AGP's own minimum JDK for th
|
||
-e APK build.
|
||
128 -# unzip: extracts the Android cmdline-tools zip below.
|
||
129 -RUN apt-get update && apt-get install -y --no-install-recommen
|
||
-ds \
|
||
130 - openjdk-17-jdk-headless unzip \
|
||
131 - && rm -rf /var/lib/apt/lists/*
|
||
132 -
|
||
127 # Android SDK/NDK: the cmdline-tools' own sdkmanager installs
|
||
exactly the
|
||
128 # platform/build-tools/NDK/CMake versions the app/build.gradle
|
||
below pins.
|
||
129 # gradlew (invoked by build-apk.sh) resolves its own pinned Gr
|
||
adle version
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Good — this now looks clean. Let me check on the background build's progress.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Still working through the apt-get install step (openjdk-17-jdk-headless is a
|
||
large package). I'll keep waiting for the background task to notify me when it
|
||
completes.
|
||
|
||
✻ Baked for 3m 37s
|
||
|
||
❯ still building?
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Yes, still building — it's progressed past the apt-get step and is now in a
|
||
mkdir -... step, which matches the Android SDK cmdline-tools download/install
|
||
stage. I'll keep waiting for the completion notification.
|
||
|
||
✻ Brewed for 45s
|
||
|
||
※ recap: Building the questshock repo to run System Shock on Quest headsets; currently rebuilding the Docker build image with the new Android SDK/NDK layer. Once it finishes, I'll run `make apk` and validate the resulting APK. (disable recaps in /config)
|
||
|
||
❯ still building?
|
||
Ran 3 shell commands (ctrl+o to expand)
|
||
|
||
● Confirmed still actively working — I checked the container's process list and
|
||
network I/O directly: it's running sdkmanager --install (fetching
|
||
platform-tools, platform 29, build-tools, the NDK, and cmake), and I can see
|
||
real download traffic flowing (~670MB+ received and climbing). The NDK alone
|
||
is often 1GB+, so this step can take a while depending on connection speed.
|
||
Not stuck — I'll keep waiting for it to finish.
|
||
|
||
✻ Sautéed for 1m 46s
|
||
|
||
● Background command "./build-image.sh 2>&1 | tail -150" completed (exit
|
||
code 0)
|
||
|
||
● The build-image rebuild finished successfully. Let me check the output and
|
||
then run the actual APK build.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Found the first real bug: SDL2_mixer 2.8's CMake uses its own private
|
||
SDL2-detection module that isn't picking up our custom-installed SDL2 path.
|
||
This is the classic Android NDK toolchain gotcha — CMAKE_FIND_ROOT_PATH_MODE_*
|
||
restricts find_path/find_library to the NDK sysroot by default, excluding our
|
||
custom prefix. Let me fix it.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Update(build-image/Dockerfile)
|
||
⎿ Added 4 lines
|
||
184 -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK_TOOLCHAIN}" \
|
||
185 -DANDROID_ABI="${ANDROID_ABI}" -DANDROID_PLATFORM="and
|
||
roid-${ANDROID_PLATFORM_VERSION}" \
|
||
186 -DCMAKE_PREFIX_PATH=/opt/prebuilt/android/sdl2 \
|
||
187 + -DCMAKE_FIND_ROOT_PATH=/opt/prebuilt/android/sdl2 \
|
||
188 + -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=BOTH \
|
||
189 + -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=BOTH \
|
||
190 + -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=BOTH \
|
||
191 -DCMAKE_INSTALL_PREFIX=/opt/prebuilt/android/sdl2_mixe
|
||
r -DBUILD_SHARED_LIBS=ON \
|
||
192 -DSDL2MIXER_VENDORED=OFF -DSDL2MIXER_SAMPLES=OFF -DSDL
|
||
2MIXER_CMD=OFF \
|
||
193 -DSDL2MIXER_FLAC=OFF -DSDL2MIXER_GME=OFF -DSDL2MIXER_M
|
||
OD=OFF \
|
||
|
||
● Let me proactively apply the same fix to the app's own CMake args
|
||
(Shockolate's find_package(SDL2 REQUIRED) will hit the identical restriction
|
||
otherwise).
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Update(android/app/build.gradle)
|
||
⎿ Added 11 lines, removed 1 line
|
||
38 // convention the desktop build already uses (p
|
||
opulated in the
|
||
39 // scratch engine copy by build-apk.sh from
|
||
40 // /opt/prebuilt/android/*).
|
||
41 + // The CMAKE_FIND_ROOT_PATH* overrides below ar
|
||
+e needed
|
||
42 + // because the NDK toolchain file restricts fin
|
||
+d_package/
|
||
43 + // find_path/find_library to its own sysroot by
|
||
+ default,
|
||
44 + // which would otherwise miss our custom-instal
|
||
+led SDL2 (see
|
||
45 + // build-image/Dockerfile's SDL2_mixer build, w
|
||
+hich hit the
|
||
46 + // exact same thing).
|
||
47 arguments "-DENABLE_SDL2=ON", "-DENABLE_SOUND=B
|
||
UNDLED", "-DENABLE_FLUIDSYNTH=BUNDLED", \
|
||
42 - "-DCMAKE_PREFIX_PATH=/opt/prebuilt/android/
|
||
-sdl2"
|
||
48 + "-DCMAKE_PREFIX_PATH=/opt/prebuilt/android/
|
||
+sdl2", \
|
||
49 + "-DCMAKE_FIND_ROOT_PATH=/opt/prebuilt/andro
|
||
+id/sdl2", \
|
||
50 + "-DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=BOTH",
|
||
+ \
|
||
51 + "-DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=BOTH",
|
||
+ \
|
||
52 + "-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=BOTH"
|
||
53 abiFilters 'arm64-v8a'
|
||
54 }
|
||
55 }
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Rebuilding — this time it should reuse the cached layers up through the SDL2
|
||
(Android) build, and only redo SDL2_mixer onward. I'll wait for it.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● That's expected — since I just merged the two apt-get layers into one, this
|
||
exact command never ran before, so Docker can't reuse cache for it (one-time
|
||
cost from the consolidation). Everything after it that's unchanged (desktop
|
||
SDL2/SDL2_mixer/fluidsynth-lite build, Node.js, Android SDK/NDK) should still
|
||
be quick since those steps are all proven already. I'll wait for it to reach
|
||
the actual fix.
|
||
⎿ Error: `prompt` is required when `stop` is not true.
|
||
|
||
● That tool isn't applicable here (it's /loop-specific). I'll just wait for the
|
||
background task's own completion notification instead.
|
||
|
||
✻ Baked for 3m 49s
|
||
|
||
❯ is it still building?
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Yes, still on the consolidated apt-get install step (installing
|
||
openjdk-17-jdk-headless plus the rest, which took a while last time too). I'll
|
||
keep waiting for the completion notification.
|
||
|
||
✻ Brewed for 34s
|
||
|
||
❯ still building?
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Progress — it's moved past the apt-get step and is now on a curl step (started
|
||
19 seconds ago), which matches the desktop
|
||
SDL2/SDL2_mixer/fluidsynth-lite/soundfont/Node.js download sequence. Still
|
||
going, I'll keep waiting.
|
||
|
||
✻ Baked for 33s
|
||
● Background command "./build-image.sh 2>&1 | tail -150" completed (exit
|
||
code 0)
|
||
|
||
● The background build finished. Let's check the tail of the log to see if the
|
||
SDL2_mixer fix worked and whether the whole image build succeeded.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Another codec I missed — WavPack. Let me check the exact option name and
|
||
disable it too.
|
||
|
||
Searched for 2 patterns, read 1 file (ctrl+o to expand)
|
||
|
||
● Update(build-image/Dockerfile)
|
||
⎿ Added 1 line
|
||
192 -DSDL2MIXER_VENDORED=OFF -DSDL2MIXER_SAMPLES=OFF -DSDL
|
||
2MIXER_CMD=OFF \
|
||
193 -DSDL2MIXER_FLAC=OFF -DSDL2MIXER_GME=OFF -DSDL2MIXER_M
|
||
OD=OFF \
|
||
194 -DSDL2MIXER_MP3=OFF -DSDL2MIXER_MIDI=OFF -DSDL2MIXER_O
|
||
PUS=OFF -DSDL2MIXER_VORBIS=OFF \
|
||
195 + -DSDL2MIXER_WAVPACK=OFF \
|
||
196 && cmake --build build-sdl2mixer-android -j"$(nproc)" \
|
||
197 && cmake --install build-sdl2mixer-android \
|
||
198 && rm -rf "SDL2_mixer-${ANDROID_SDL2_MIXER_VERSION}" "SDL2
|
||
_mixer-${ANDROID_SDL2_MIXER_VERSION}.tar.gz" build-sdl2mixer-a
|
||
ndroid
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Rebuilding again — this time everything up through the SDL2 (Android) build
|
||
should hit cache, so it should reach the SDL2_mixer step quickly. I'll wait
|
||
for completion.
|
||
|
||
✻ Brewed for 1m 53s
|
||
|
||
● Background command "./build-image.sh 2>&1 | tail -150" completed (exit
|
||
code 0)
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Fluidsynth-lite compiles fine under desktop GCC but hits two Clang-specific
|
||
warnings-as-errors (-Werror is baked into its own CMakeLists). Since we
|
||
already sed-patch this exact file (to force the shared-lib option), extending
|
||
that same approach to relax -Werror for the Android/Clang build is consistent
|
||
— and it doesn't touch engine/ at all.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Update(build-image/Dockerfile)
|
||
⎿ Added 1 line
|
||
201 && cd fluidsynth-lite-android \
|
||
202 && git checkout "${FLUIDSYNTH_LITE_REF}" \
|
||
203 && sed -i 's/DLL"\ off/DLL"\ on/' CMakeLists.txt \
|
||
204 + && sed -i 's/-Wall -Werror -std=gnu11/-Wall -std=gnu11/' C
|
||
+MakeLists.txt \
|
||
205 && rm -rf .git \
|
||
206 && cd .. \
|
||
207 && cmake -S fluidsynth-lite-android -B build-fluidsynth-an
|
||
droid \
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Rebuilding again — should be quick now, reusing cache up through the
|
||
SDL2_mixer step. I'll wait for it.
|
||
|
||
✻ Baked for 1m 37s
|
||
|
||
● Background command "./build-image.sh 2>&1 | tail -150" completed (exit
|
||
code 0)
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Another Clang/NDK-specific issue: modern Android NDKs no longer ship a
|
||
libpthread.so stub (pthread is built into bionic libc directly), but
|
||
fluidsynth-lite's CMakeLists unconditionally links -lpthread. Same sed-patch
|
||
approach as before.
|
||
|
||
● Update(build-image/Dockerfile)
|
||
⎿ Added 1 line
|
||
202 && git checkout "${FLUIDSYNTH_LITE_REF}" \
|
||
203 && sed -i 's/DLL"\ off/DLL"\ on/' CMakeLists.txt \
|
||
204 && sed -i 's/-Wall -Werror -std=gnu11/-Wall -std=gnu11/' C
|
||
MakeLists.txt \
|
||
205 + && sed -i 's/set ( LIBFLUID_LIBS "m pthread" )/set ( LIBFL
|
||
+UID_LIBS "m" )/' CMakeLists.txt \
|
||
206 && rm -rf .git \
|
||
207 && cd .. \
|
||
208 && cmake -S fluidsynth-lite-android -B build-fluidsynth-an
|
||
droid \
|
||
|
||
● Let me verify the exact string format before rebuilding, since a sed mismatch
|
||
would silently no-op.
|
||
|
||
Searched for 1 pattern (ctrl+o to expand)
|
||
|
||
● Update(build-image/Dockerfile)
|
||
⎿ Added 1 line, removed 1 line
|
||
202 && git checkout "${FLUIDSYNTH_LITE_REF}" \
|
||
203 && sed -i 's/DLL"\ off/DLL"\ on/' CMakeLists.txt \
|
||
204 && sed -i 's/-Wall -Werror -std=gnu11/-Wall -std=gnu11/' C
|
||
MakeLists.txt \
|
||
205 - && sed -i 's/set ( LIBFLUID_LIBS "m pthread" )/set ( LIBFL
|
||
-UID_LIBS "m" )/' CMakeLists.txt \
|
||
205 + && sed -i 's/set ( LIBFLUID_LIBS m pthread )/set ( LIBFLUI
|
||
+D_LIBS m )/' CMakeLists.txt \
|
||
206 && rm -rf .git \
|
||
207 && cd .. \
|
||
208 && cmake -S fluidsynth-lite-android -B build-fluidsynth-an
|
||
droid \
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Rebuilding once more with the corrected sed pattern. Waiting for it.
|
||
✻ Cooked for 1m 54s
|
||
|
||
● Background command "./build-image.sh 2>&1 | tail -150" completed (exit
|
||
code 0)
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● The full image build succeeded — all three Android cross-compiles (SDL2,
|
||
SDL2_mixer, fluidsynth-lite) completed cleanly this time. Now let's actually
|
||
run make apk.
|
||
|
||
Ran 2 shell commands (ctrl+o to expand)
|
||
|
||
● Also worth fixing proactively: fluidsynth/version.h is generated by CMake at
|
||
build time (version.h.in), and since desktop does an in-source build it lands
|
||
right in the source tree, but my Android build is out-of-source — the
|
||
generated header would be missing from what I copy out. Let me fix that before
|
||
the next rebuild.
|
||
|
||
Searched for 1 pattern, read 1 file (ctrl+o to expand)
|
||
|
||
● Update(build-image/Dockerfile)
|
||
⎿ Added 6 lines
|
||
212 && mkdir -p /opt/prebuilt/android/fluidsynth-lite/lib /opt
|
||
/prebuilt/android/fluidsynth-lite/include \
|
||
213 && cp -a build-fluidsynth-android/src/libfluidsynth.so* /o
|
||
pt/prebuilt/android/fluidsynth-lite/lib/ \
|
||
214 && cp -a fluidsynth-lite-android/include/. /opt/prebuilt/a
|
||
ndroid/fluidsynth-lite/include/ \
|
||
215 + # version.h is generated from version.h.in at configure ti
|
||
+me - fine on
|
||
216 + # the desktop build (in-source, so it lands right back in
|
||
+the source
|
||
217 + # tree), but this is an out-of-source build, so it only ex
|
||
+ists under
|
||
218 + # build-fluidsynth-android/ and must be copied in separate
|
||
+ly.
|
||
219 + && cp -a build-fluidsynth-android/include/fluidsynth/versi
|
||
+on.h \
|
||
220 + /opt/prebuilt/android/fluidsynth-lite/include/fluidsyn
|
||
+th/version.h \
|
||
221 && rm -rf fluidsynth-lite-android build-fluidsynth-android
|
||
222
|
||
223 COPY build-image/docker-entrypoint.sh /usr/local/bin/docker-en
|
||
trypoint.sh
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Rebuilding with the version.h fix. Waiting for it.
|
||
|
||
✻ Sautéed for 2m 33s
|
||
|
||
● Background command "./build-image.sh 2>&1 | tail -60" completed (exit
|
||
code 0)
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Image build succeeded again. Now let's actually run make apk for real.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● This is the big one — it'll patch engine/, wire up build_ext, stage
|
||
assets/jniLibs, and run ./gradlew assembleDebug (which needs network access,
|
||
as expected). Waiting for it.
|
||
|
||
✻ Baked for 47s
|
||
|
||
● Background command "make apk 2>&1 | tail -150" completed (exit code 0)
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Found it: AGP defaults to its own preferred NDK version (25.x) unless told
|
||
otherwise, and tries to auto-download it into the (root-owned,
|
||
non-writable-by-builder) SDK dir. Need to pin ndkVersion explicitly to match
|
||
the NDK we actually installed.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Update(android/app/build.gradle)
|
||
⎿ Added 4 lines
|
||
14 android {
|
||
15 namespace "de.ladkau.questshock"
|
||
16 compileSdk 34
|
||
17 + // Must match build-image/Dockerfile's ANDROID_NDK_VERSION
|
||
+- otherwise
|
||
18 + // AGP defaults to whatever NDK version it itself prefers a
|
||
+nd tries to
|
||
19 + // download it into the SDK dir, which isn't writable at bu
|
||
+ild time.
|
||
20 + ndkVersion "26.1.10909125"
|
||
21
|
||
22 defaultConfig {
|
||
23 applicationId "de.ladkau.questshock"
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Waiting for this run (it'll re-download Gradle itself since the container is
|
||
ephemeral — that's a known, separate inefficiency I'll flag later, not
|
||
blocking right now).
|
||
|
||
✻ Sautéed for 1m 29s
|
||
|
||
● Background command "make apk 2>&1 | tail -150" completed (exit code 0)
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Same root cause again — AGP wants platforms;android-34 (matching compileSdk
|
||
34) and its own default build-tools version (33.0.1, not the 34.0.0 I
|
||
installed), and tries to auto-fetch both into the non-writable SDK dir. Need
|
||
to install platform 34 too and pin buildToolsVersion explicitly.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
● Update(build-image/Dockerfile)
|
||
⎿ Added 4 lines
|
||
39 ARG ANDROID_SDL2_MIXER_VERSION=2.8.0
|
||
40 ARG ANDROID_CMDLINE_TOOLS_VERSION=11076708
|
||
41 ARG ANDROID_PLATFORM_VERSION=29
|
||
42 +# compileSdk in android/app/build.gradle - separate from the ap
|
||
+p's own
|
||
43 +# targetSdkVersion (29, above): compileSdk is just which androi
|
||
+d.jar the
|
||
44 +# app compiles against, and androidx.core needs a newer one tha
|
||
+n 29.
|
||
45 +ARG ANDROID_COMPILE_SDK_VERSION=34
|
||
46 ARG ANDROID_BUILD_TOOLS_VERSION=34.0.0
|
||
47 ARG ANDROID_NDK_VERSION=26.1.10909125
|
||
48 ARG ANDROID_CMAKE_VERSION=3.22.1
|
||
|
||
● Update(build-image/Dockerfile)
|
||
⎿ Added 1 line
|
||
150 && sdkmanager --install \
|
||
151 "platform-tools" \
|
||
152 "platforms;android-${ANDROID_PLATFORM_VERSION}" \
|
||
153 + "platforms;android-${ANDROID_COMPILE_SDK_VERSION}" \
|
||
154 "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" \
|
||
155 "ndk;${ANDROID_NDK_VERSION}" \
|
||
156 "cmake;${ANDROID_CMAKE_VERSION}"
|
||
|
||
● Now pin buildToolsVersion explicitly in app/build.gradle to match what's
|
||
installed.
|
||
|
||
● Update(android/app/build.gradle)
|
||
⎿ Added 5 lines, removed 3 lines
|
||
13
|
||
14 android {
|
||
15 namespace "de.ladkau.questshock"
|
||
16 + // compileSdk/buildToolsVersion/ndkVersion must all match w
|
||
+hat
|
||
17 + // build-image/Dockerfile actually installs - otherwise AGP
|
||
+ defaults to
|
||
18 + // whatever version it itself prefers and tries to download
|
||
+ it into the
|
||
19 + // SDK dir at build time, which isn't writable.
|
||
20 compileSdk 34
|
||
17 - // Must match build-image/Dockerfile's ANDROID_NDK_VERSION
|
||
-- otherwise
|
||
18 - // AGP defaults to whatever NDK version it itself prefers a
|
||
-nd tries to
|
||
19 - // download it into the SDK dir, which isn't writable at bu
|
||
-ild time.
|
||
21 + buildToolsVersion "34.0.0"
|
||
22 ndkVersion "26.1.10909125"
|
||
23
|
||
24 defaultConfig {
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Rebuilding with platform 34 added. This will cascade-rebuild the SDK/NDK layer
|
||
and everything after it. Waiting.
|
||
|
||
✻ Worked for 2m 47s
|
||
|
||
❯ is it still building?
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Yes, still building — currently on the Android SDK/NDK install step (mkdir
|
||
-...), which now needs to fetch platform 34 in addition to what it had before.
|
||
I'll keep waiting for completion.
|
||
|
||
✻ Churned for 42s
|
||
|
||
● Background command "./build-image.sh 2>&1 | tail -60" completed (exit
|
||
code 0)
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Image build succeeded. Now let's try make apk again.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Waiting for this run.
|
||
|
||
✻ Churned for 52s
|
||
|
||
● Background command "make apk 2>&1 | tail -150" completed (exit code 0)
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Now hitting a real engine/CMakeLists.txt platform gap: find_package(OpenGL
|
||
REQUIRED) looks for desktop OpenGL (GLX), which doesn't exist on Android
|
||
(which uses GLES/EGL instead). This needs a genuine patch to
|
||
engine/CMakeLists.txt — exactly the scenario the patch mechanism was built
|
||
for. Let me look at the exact block.
|
||
|
||
Searched for 1 pattern, read 1 file (ctrl+o to expand)
|
||
|
||
● Let me restructure engine-patches/ to support multiple ordered patches (since
|
||
this is a second, separate concern from the shared-lib change), then add this
|
||
one.
|
||
|
||
Made 1 scratchpad edit +15 -9, ran 3 shell commands (ctrl+o to expand)
|
||
|
||
● Let's verify both patches apply cleanly in sequence, then save it.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Both apply cleanly in sequence. Let's save the patch and update build-apk.sh
|
||
to apply all patches in the directory.
|
||
|
||
● Write(android/engine-patches/02-android-opengl-es.patch)
|
||
⎿ Wrote 20 lines to android/engine-patches/02-android-opengl-es.patch
|
||
1 --- a/CMakeLists.txt
|
||
2 +++ b/CMakeLists.txt
|
||
3 @@ -46,9 +46,15 @@
|
||
5 add_compile_options(-fsigned-char -fno-strict-aliasing)
|
||
6
|
||
7 -# Find OpenGL
|
||
8 +# Find OpenGL. Android has no desktop GL/GLX for CMake's FindOp
|
||
9 +# to find - just link the NDK's own GLESv2/EGL libraries direct
|
||
ly.
|
||
10 if(ENABLE_OPENGL)
|
||
… +12 lines (ctrl+o to expand)
|
||
|
||
● Now let's verify against the actually-committed patch file (not the scratchpad
|
||
copy) and update build-apk.sh to apply all patches from the directory in
|
||
order.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Both apply cleanly from the committed files, in glob order. Now let's update
|
||
build-apk.sh to apply all patches in the directory.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
● Update(build-image/build-apk.sh)
|
||
⎿ Added 3 lines, removed 1 line
|
||
18 rm -rf "$SCRATCH_ENGINE"
|
||
19 mkdir -p "$(dirname "$SCRATCH_ENGINE")"
|
||
20 cp -a "$REPO_ROOT/engine" "$SCRATCH_ENGINE"
|
||
21 -patch -p1 -d "$SCRATCH_ENGINE" < "$ANDROID_DIR/engine-patches/a
|
||
-ndroid-shared-lib.patch"
|
||
21 +for p in "$ANDROID_DIR"/engine-patches/*.patch; do
|
||
22 + patch -p1 -d "$SCRATCH_ENGINE" < "$p"
|
||
23 +done
|
||
24
|
||
25 echo "== Wiring up prebuilt Android SDL2_mixer/fluidsynth-lite
|
||
(BUNDLED mode, like the desktop build) =="
|
||
26 mkdir -p "$SCRATCH_ENGINE/build_ext/built_sdl_mixer" "$SCRATCH_
|
||
ENGINE/build_ext/fluidsynth-lite"
|
||
|
||
● No image rebuild needed this time — build-apk.sh and the patches run from the
|
||
bind-mounted repo, not baked into the image. Let's just re-run make apk.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Waiting for this run.
|
||
|
||
✻ Sautéed for 4m 27s
|
||
|
||
● Background command "make apk 2>&1 | tail -150" completed (exit code 0)
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Web Search("android.system.Os chdir method public API android")
|
||
⎿ Did 1 search in 7s
|
||
|
||
● Let me check directly what's actually in the API 34 stub jar, rather than
|
||
relying on possibly-wrong memory.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● android.system.Os genuinely has no chdir (confirmed against the real API 34
|
||
stub jar — Java has no public API for process-wide chdir at all). I need a
|
||
tiny bit of native code after all, but it stays entirely in android/ — never
|
||
touching engine/. Let me add it.
|
||
|
||
● Write(android/app/src/main/cpp/questshock_native.c)
|
||
⎿ Wrote 15 lines to android/app/src/main/cpp/questshock_native.c
|
||
1 // android.system.Os has no public chdir() (confirmed against th
|
||
2 // API 34 stub jar - Java has no way to change a process's worki
|
||
ng
|
||
3 // directory at all). This is the one bit of native code QuestSh
|
||
4 // needs, compiled into the same "main" library as engine/ (see
|
||
5 // android/engine-patches/01-android-shared-lib.patch's ANDROID_
|
||
6 // - engine/ itself is never modified.
|
||
7 #include <jni.h>
|
||
8 #include <unistd.h>
|
||
10 JNIEXPORT void JNICALL
|
||
… +9 lines (ctrl+o to expand)
|
||
|
||
● Now let's regenerate patch 01 to append ANDROID_EXTRA_SOURCES to the library
|
||
sources.
|
||
|
||
Made 2 scratchpad edits +35 -16, ran 2 shell commands (ctrl+o to expand)
|
||
|
||
● Let's verify all three patches apply cleanly in sequence and save the updated
|
||
patch 01.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Write(android/engine-patches/01-android-shared-lib.patch)
|
||
⎿ Error writing file
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Now I can write it.
|
||
|
||
● Write(android/engine-patches/01-android-shared-lib.patch)
|
||
⎿ Added 7 lines, removed 4 lines
|
||
1 --- a/CMakeLists.txt
|
||
2 +++ b/CMakeLists.txt
|
||
3 -@@ -380,9 +380,22 @@
|
||
3 +@@ -380,9 +380,25 @@
|
||
4 -include precompiled.h
|
||
5 )
|
||
6
|
||
...
|
||
11 +# JNI instead), so this is the one part of this file Android n
|
||
eeds to
|
||
12 +# override - see android/engine-patches/. Everything else here
|
||
(sources,
|
||
13 +# dependency discovery, link libraries) is untouched and appli
|
||
es the same
|
||
14 -+# way on both platforms.
|
||
14 ++# way on both platforms. ANDROID_EXTRA_SOURCES (passed in by
|
||
15 ++# android/app/build.gradle) is QuestShockActivity's own small
|
||
+native
|
||
16 ++# helper (android/app/src/main/cpp/) - not part of engine/ its
|
||
+elf.
|
||
17 +if(ANDROID)
|
||
18 + set(SHOCKOLATE_TARGET main)
|
||
19 + add_library(${SHOCKOLATE_TARGET} SHARED
|
||
20 + ${MAC_SRC}
|
||
21 ++ ${ANDROID_EXTRA_SOURCES}
|
||
22 + )
|
||
23 +else()
|
||
24 + set(SHOCKOLATE_TARGET systemshock)
|
||
...
|
||
29
|
||
30 add_library(GAME_LIB ${GAME_SRC})
|
||
31
|
||
29 -@@ -391,7 +404,7 @@
|
||
32 +@@ -391,7 +407,7 @@
|
||
33 set(WINDOWS_LIBRARIES "mingw32 -mwindows")
|
||
34 endif(MINGW)
|
||
35
|
||
...
|
||
38 ${WINDOWS_LIBRARIES} # Set it before any linker options! Bewar
|
||
e WinMain@16 error!!
|
||
39 GAME_LIB
|
||
40 UI_LIB
|
||
38 -@@ -418,7 +431,10 @@
|
||
41 +@@ -418,7 +434,10 @@
|
||
42 ${ALSA_LIBRARIES}
|
||
43 )
|
||
44
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Now let's update QuestShockActivity.java to load the native library early and
|
||
call nativeChdir instead of Os.chdir, and update build.gradle to pass
|
||
ANDROID_EXTRA_SOURCES.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Update(android/app/src/main/java/de/ladkau/questshock/QuestShockActivity.java)
|
||
⎿ Removed 1 line
|
||
4 import android.content.pm.PackageManager;
|
||
5 import android.content.res.AssetManager;
|
||
6 import android.os.Bundle;
|
||
7 -import android.system.Os;
|
||
7 import android.util.Log;
|
||
8 import androidx.core.app.ActivityCompat;
|
||
9 import java.io.File;
|
||
|
||
● Update(android/app/src/main/java/de/ladkau/questshock/QuestShockActivity.java)
|
||
⎿ Added 16 lines
|
||
28 private static final int PERMISSION_REQUEST_STORAGE = 1;
|
||
29 private static final String GAME_DIR = "/sdcard/questshock"
|
||
;
|
||
30
|
||
31 + // Loaded here (rather than waiting for SDLActivity's own
|
||
32 + // getLibraries()/loadLibraries(), which only runs from sup
|
||
+er.onCreate())
|
||
33 + // purely to get nativeChdir() below - Android's dynamic li
|
||
+nker resolves
|
||
34 + // "main"'s own dependencies (SDL2, SDL2_mixer, fluidsynth)
|
||
+ from the
|
||
35 + // APK's native library directory regardless of Java-side l
|
||
+oad order, so
|
||
36 + // loading it early here is safe. SDLActivity loading "main
|
||
+" again later
|
||
37 + // is a harmless no-op (System.loadLibrary is idempotent pe
|
||
+r
|
||
38 + // ClassLoader).
|
||
39 + static {
|
||
40 + System.loadLibrary("main");
|
||
41 + }
|
||
42 +
|
||
43 + // See android/app/src/main/cpp/questshock_native.c - andro
|
||
+id.system.Os
|
||
44 + // has no public chdir() (confirmed against the actual API
|
||
+34 stub jar).
|
||
45 + private static native void nativeChdir(String path);
|
||
46 +
|
||
47 private Bundle mSavedInstanceState;
|
||
48
|
||
49 @Override
|
||
|
||
● Update(android/app/src/main/java/de/ladkau/questshock/QuestShockActivity.java)
|
||
⎿ Added 4 lines, removed 9 lines
|
||
96 copyAssetFile("GET_ASSETS_QUEST.txt", marker);
|
||
97 }
|
||
98
|
||
99 - try {
|
||
100 - // chdir() is process-wide, not per-thread - alrea
|
||
-dy in effect for
|
||
101 - // every thread (including the one that will run S
|
||
-hockolate's own
|
||
102 - // SDL_main) by the time super.onCreate() below lo
|
||
-ads the native
|
||
103 - // libraries and starts it.
|
||
104 - Os.chdir(GAME_DIR);
|
||
105 - } catch (Exception e) {
|
||
106 - Log.e(TAG, "chdir(" + GAME_DIR + ") failed", e);
|
||
107 - }
|
||
99 + // chdir() is process-wide, not per-thread - already i
|
||
+n effect for
|
||
100 + // every thread (including the one that will run Shock
|
||
+olate's own
|
||
101 + // SDL_main) by the time super.onCreate() below starts
|
||
+ it.
|
||
102 + nativeChdir(GAME_DIR);
|
||
103
|
||
104 super.onCreate(mSavedInstanceState);
|
||
105 }
|
||
● Now update build.gradle to pass ANDROID_EXTRA_SOURCES.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Update(android/app/build.gradle)
|
||
⎿ Added 2 lines, removed 1 line
|
||
55 "-DCMAKE_FIND_ROOT_PATH=/opt/prebuilt/andro
|
||
id/sdl2", \
|
||
56 "-DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=BOTH",
|
||
\
|
||
57 "-DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=BOTH",
|
||
\
|
||
58 - "-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=BOTH"
|
||
58 + "-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=BOTH",
|
||
+ \
|
||
59 + "-DANDROID_EXTRA_SOURCES=${projectDir}/src/
|
||
+main/cpp/questshock_native.c"
|
||
60 abiFilters 'arm64-v8a'
|
||
61 }
|
||
62 }
|
||
|
||
● No image rebuild needed — all these changes live in the bind-mounted repo.
|
||
Let's re-run make apk.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Waiting for this run.
|
||
|
||
✻ Worked for 7m 41s
|
||
|
||
● Background command "make apk 2>&1 | tail -150" completed (exit code 0)
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● This confirms the exact risk I flagged in the plan:
|
||
engine/src/MacSrc/OpenGL.cc uses desktop OpenGL headers (<GL/gl.h>), which
|
||
don't exist on Android. Let me look at the actual GL usage to see how big a
|
||
patch this needs.
|
||
|
||
Searched for 2 patterns, read 1 file (ctrl+o to expand)
|
||
|
||
● Real progress on the build pipeline, but I've hit a genuine engine-level
|
||
compatibility gap, not just a build-config issue: OpenGL.cc mixes modern
|
||
shader-based rendering (GLES2-compatible: glCreateShader, glUniform*, etc.)
|
||
with legacy immediate-mode GL calls (glBegin/glEnd/glVertex3f,
|
||
glAlphaFunc/GL_ALPHA_TEST) that don't exist in GLES2 at all, plus a couple of
|
||
extensions not guaranteed on GLES2 (GL_CLAMP_TO_BORDER, GL_UNPACK_ROW_LENGTH).
|
||
The immediate-mode calls look like they're specifically the star-field
|
||
rendering (there's a star.frag shader and matching GL_POINTS/GL_POINT_SPRITE
|
||
usage) — a separate, older code path from the main shader-based renderer.
|
||
|
||
Fixing the header/extension issues is mechanical (a few more lines in the same
|
||
patch). But the immediate-mode star rendering needs an actual rewrite to a
|
||
VBO+shader approach to work under GLES2 — that's real engine rendering code,
|
||
not build plumbing, and it's something I can't visually verify without a
|
||
device or working GPU-accelerated emulator (neither of which I have here).
|
||
|
||
● User answered Claude's questions:
|
||
⎿ · How do you want to handle the immediate-mode star rendering
|
||
(glBegin/glEnd/glVertex3f + glAlphaFunc), which needs an actual GLES2
|
||
rewrite, not just a build-config patch? → Attempt the rewrite now
|
||
(Recommended)
|
||
|
||
Searched for 1 pattern (ctrl+o to expand)
|
||
|
||
● This is more extensive than just the stars — immediate-mode
|
||
glBegin/glVertex3f/glEnd is actually the primary way all textured quads get
|
||
drawn throughout this file (not just the star field), since GLES2 removes the
|
||
entire immediate-mode API. Let me read the full file to do this rewrite
|
||
properly rather than guessing at call sites.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Before writing the replacement draw calls, I need to check main.vert — I need
|
||
to know whether it declares an explicit position attribute or relies on
|
||
desktop's fixed-function gl_Vertex builtin (which doesn't exist in GLES2 at
|
||
all).
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Now let me check the fragment shaders too, since GLES fragment shaders require
|
||
explicit precision declarations that desktop GLSL doesn't need.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Let me check where the SDL window/GL context attributes get set up, since I
|
||
may need GLES 3.0 (universally available on Quest hardware) to get
|
||
GL_UNPACK_ROW_LENGTH/GL_CLAMP_TO_BORDER support natively rather than working
|
||
around their absence in ES2.
|
||
|
||
Searched for 1 pattern (ctrl+o to expand)
|
||
|
||
● Targeting GLES 3.2 (Quest 2/3 hardware fully supports it) gives native
|
||
GL_UNPACK_ROW_LENGTH and GL_CLAMP_TO_BORDER support, eliminating two of the
|
||
compatibility issues entirely. Let me write the ES shader variants first.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Write(android/gles-shaders/main.vert)
|
||
⎿ Wrote 28 lines to android/gles-shaders/main.vert
|
||
1 #version 100
|
||
3 // GLES port of engine/shaders/main.vert - the one real differenc
|
||
e is
|
||
oesn't
|
||
5 // exist in GLES at all, replaced with an explicit "position" att
|
||
6 // (see android/engine-patches/03-android-opengl-es-render.patch'
|
||
s
|
||
to match
|
||
8 // the vertex arrays OpenGL.cc's GLES immediate-mode emulation su
|
||
9 precision mediump float;
|
||
… +23 lines (ctrl+o to expand)
|
||
|
||
● Write(android/gles-shaders/color.frag) ⎿ Wrote 13 lines to android/gles-shaders/color.frag
|
||
1 #version 100
|
||
2
|
||
3 // GLES port of engine/shaders/color.frag - identical logic, just
|
||
needs an
|
||
4 // explicit precision declaration (required in GLES fragment shad
|
||
5 // valid in desktop GLSL).
|
||
6 precision mediump float;
|
||
7
|
||
8 varying vec4 Color;
|
||
9 varying float Light;
|
||
… +6 lines (ctrl+o to expand)
|
||
|
||
● Write(android/gles-shaders/texture.frag)
|
||
⎿ Wrote 49 lines to android/gles-shaders/texture.frag
|
||
1 #version 100
|
||
3 // GLES port of engine/shaders/texture.frag - same logic, plus:
|
||
4 // - an explicit precision declaration (required in GLES, invali
|
||
d in
|
||
6 // - the discard at the end, replacing the fixed-function alpha
|
||
test
|
||
ed-function
|
||
8 // pipeline at all, so this is done in the shader instead; see
|
||
9 // android/engine-patches/03-android-opengl-es-render.patch's
|
||
unc/
|
||
… +44 lines (ctrl+o to expand)
|
||
|
||
● Write(android/gles-shaders/star.frag)
|
||
⎿ Wrote 22 lines to android/gles-shaders/star.frag
|
||
1 #version 100
|
||
3 // GLES port of engine/shaders/star.frag - identical logic, plus
|
||
an
|
||
4 // explicit precision declaration (required in GLES). gl_PointCo
|
||
5 // point-sprite rasterization for GL_POINTS both work automatica
|
||
lly in
|
||
SPRITE,
|
||
7 // which doesn't exist in GLES at all - see
|
||
8 // android/engine-patches/03-android-opengl-es-render.patch's
|
||
10 precision mediump float;
|
||
… +16 lines (ctrl+o to expand)
|
||
|
||
● Now let's write the patches. First, Shock.c's GL context attributes.
|
||
|
||
Made 1 scratchpad edit +16 -4, read 1 file, ran 2 shell commands (ctrl+o to expand)
|
||
|
||
● Need to fix the path prefix — let me regenerate with the correct relative path
|
||
structure.
|
||
|
||
Ran 2 shell commands (ctrl+o to expand)
|
||
|
||
● Now let's write the big OpenGL.cc patch — headers, disabling desktop-only
|
||
fixed-function calls, and the immediate-mode → vertex-array conversion.
|
||
|
||
Made 1 scratchpad edit +111 -16, ran 2 shell commands (ctrl+o to expand)
|
||
|
||
● Now let's edit CreateShader() to bind the position attribute, and
|
||
init_opengl() to skip the desktop-only fixed-function calls.
|
||
|
||
Made 2 scratchpad edits +24 -11 (ctrl+o to expand)
|
||
|
||
● Now let's generate the diff and verify it applies cleanly, then check it's
|
||
internally consistent (proper preprocessor balance etc.) by compiling a syntax
|
||
check if possible.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Let's save both new patches and verify all four apply together cleanly.
|
||
|
||
Ran 2 shell commands (ctrl+o to expand)
|
||
|
||
● Now let's update build-apk.sh to stage the ES shader variants instead of the
|
||
desktop ones.
|
||
|
||
Read 1 file (ctrl+o to expand)
|
||
|
||
● Update(build-image/build-apk.sh)
|
||
⎿ Added 4 lines, removed 1 line
|
||
35 ASSETS_DIR="$ANDROID_DIR/app/src/main/assets"
|
||
36 rm -rf "$ASSETS_DIR"
|
||
37 mkdir -p "$ASSETS_DIR/shaders" "$ASSETS_DIR/res"
|
||
38 -cp -a "$REPO_ROOT/engine/shaders/." "$ASSETS_DIR/shaders/"
|
||
38 +# GLES ports of engine/shaders/ (see android/gles-shaders/ and
|
||
39 +# android/engine-patches/04-android-opengl-es-render.patch) - n
|
||
+ot
|
||
40 +# engine/shaders/ itself, which is desktop-only GLSL.
|
||
41 +cp -a "$ANDROID_DIR/gles-shaders/." "$ASSETS_DIR/shaders/"
|
||
42 cp "/opt/prebuilt/soundfont/default.sf2" "$ASSETS_DIR/res/sound
|
||
font.sf2"
|
||
43 cp "$REPO_ROOT/res/assets/GET_ASSETS_QUEST.txt" "$ASSETS_DIR/GE
|
||
T_ASSETS_QUEST.txt"
|
||
44
|
||
|
||
● No image rebuild needed. Let's re-run make apk.
|
||
|
||
Ran 1 shell command (ctrl+o to expand)
|
||
|
||
● Waiting for this run.
|
||
|
||
✻ Sautéed for 15m 29s |