diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index ef26d7bc..30470e7d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -143,7 +143,7 @@ jobs:
linux-arm64:
name: build Linux arm64 AppImage
- needs: [version, love-payload]
+ needs: [version, love-payload, shaderfx-bridge]
# GitHub's free arm64 runner for public repos. It has to be arm64: the
# AppImage compiles LÖVE natively inside a Debian bullseye arm64
# container, and the qemu-emulated alternative takes hours.
@@ -155,13 +155,22 @@ jobs:
with:
name: gen1recomp-release-love
path: .bazinga/work
+ - name: Download the ShaderFX bridge
+ uses: actions/download-artifact@v8
+ with:
+ name: shaderfx-bridge-linux-arm64
+ path: dist/native/linux-arm64
- name: Build Linux arm64 AppImage
+ env:
+ SHADERFX_BRIDGE_REQUIRED: "1"
run: |
set -euo pipefail
scripts/build_linux_arm64.sh \
--version "${{ needs.version.outputs.version }}" \
--game-love .bazinga/work/game.love
- name: Verify the AppImage is self-contained and bullseye-compatible
+ env:
+ SHADERFX_BRIDGE_REQUIRED: "1"
run: bash scripts/linux-arm64/verify_appimage.sh "dist/linux-arm64/gen1recomp-${{ needs.version.outputs.version }}-linux-arm64.AppImage"
- name: Upload Linux arm64 release
uses: actions/upload-artifact@v7
@@ -175,7 +184,7 @@ jobs:
linux-flatpak:
name: build Linux Flatpak
- needs: [version, love-payload]
+ needs: [version, love-payload, shaderfx-bridge]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
@@ -189,7 +198,14 @@ jobs:
with:
name: gen1recomp-release-love
path: .bazinga/work
+ - name: Download the ShaderFX bridge
+ uses: actions/download-artifact@v8
+ with:
+ name: shaderfx-bridge-linux-x64
+ path: dist/native/linux-x64
- name: Build Flatpak bundle
+ env:
+ SHADERFX_BRIDGE_REQUIRED: "1"
run: |
set -euo pipefail
scripts/build_flatpak.sh \
@@ -318,8 +334,82 @@ jobs:
if-no-files-found: error
retention-days: 1
+ shaderfx-bridge:
+ name: build ShaderFX bridge (${{ matrix.plat }})
+ needs: version
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - plat: win-x64
+ runs-on: windows-2022
+ lib: librashader_bridge.dll
+ - plat: mac
+ runs-on: macos-latest
+ lib: liblibrashader_bridge.dylib
+ - plat: linux-x64
+ runs-on: ubuntu-24.04
+ lib: liblibrashader_bridge.so
+ glibc_triple: x86_64-unknown-linux-gnu.2.17
+ - plat: linux-arm64
+ runs-on: ubuntu-24.04-arm
+ lib: liblibrashader_bridge.so
+ glibc_triple: aarch64-unknown-linux-gnu.2.17
+ runs-on: ${{ matrix.runs-on }}
+ steps:
+ - uses: actions/checkout@v7
+ - name: Setup Rust
+ uses: dtolnay/rust-toolchain@stable
+ - name: Cache cargo registry and build dir
+ uses: Swatinem/rust-cache@v2
+ with:
+ workspaces: tools/shaderfx-bridge
+ key: ${{ matrix.plat }}-${{ matrix.glibc_triple }}
+ - name: Build the bridge
+ shell: bash
+ env:
+ PLAT: ${{ matrix.plat }}
+ LIB: ${{ matrix.lib }}
+ GLIBC_TRIPLE: ${{ matrix.glibc_triple }}
+ run: |
+ set -euo pipefail
+ out="dist/native/$PLAT"
+ mkdir -p "$out"
+ cd tools/shaderfx-bridge
+ case "$PLAT" in
+ win-x64)
+ rustup target add x86_64-pc-windows-msvc
+ cargo build --release --target x86_64-pc-windows-msvc
+ cp "target/x86_64-pc-windows-msvc/release/$LIB" "$GITHUB_WORKSPACE/$out/$LIB"
+ ;;
+ mac)
+ rustup target add x86_64-apple-darwin aarch64-apple-darwin
+ cargo build --release --target x86_64-apple-darwin
+ cargo build --release --target aarch64-apple-darwin
+ lipo -create -output "$GITHUB_WORKSPACE/$out/$LIB" \
+ "target/x86_64-apple-darwin/release/$LIB" \
+ "target/aarch64-apple-darwin/release/$LIB"
+ lipo -info "$GITHUB_WORKSPACE/$out/$LIB"
+ ;;
+ linux-x64|linux-arm64)
+ pipx install cargo-zigbuild
+ pipx inject cargo-zigbuild ziglang
+ triple="$GLIBC_TRIPLE"
+ rustup target add "${triple%%.*}"
+ cargo zigbuild --release --target "$triple"
+ cp "target/${triple%%.*}/release/$LIB" "$GITHUB_WORKSPACE/$out/$LIB"
+ ;;
+ esac
+ - name: Upload the bridge
+ uses: actions/upload-artifact@v7
+ with:
+ name: shaderfx-bridge-${{ matrix.plat }}
+ path: dist/native/${{ matrix.plat }}/${{ matrix.lib }}
+ if-no-files-found: error
+ retention-days: 1
+
release:
- needs: [version, love-payload, xbox-uwp, linux-arm64, linux-flatpak, native-tls-win]
+ needs: [version, love-payload, xbox-uwp, linux-arm64, linux-flatpak, native-tls-win, shaderfx-bridge]
runs-on: ${{ fromJSON(github.repository == 'bryanthaboi/gen1recomp' && '["self-hosted", "macOS"]' || '"macos-latest"') }}
steps:
@@ -341,6 +431,28 @@ jobs:
name: gen1tls-win-x64
path: dist/native/win-x64
+ - name: Download ShaderFX bridge libraries
+ uses: actions/download-artifact@v8
+ with:
+ pattern: shaderfx-bridge-*
+ path: dist/native
+ - name: Flatten the ShaderFX bridge artifact layout
+ run: |
+ set -euo pipefail
+ shopt -s nullglob
+ for d in dist/native/shaderfx-bridge-*/; do
+ d="${d%/}"
+ plat="${d#dist/native/shaderfx-bridge-}"
+ mkdir -p "dist/native/$plat"
+ for f in "$d"/*; do mv "$f" "dist/native/$plat/"; done
+ rmdir "$d"
+ done
+ for plat in mac win-x64 linux-x64 linux-arm64; do
+ ls "dist/native/$plat" >/dev/null \
+ || { echo "::error::no ShaderFX bridge staged for $plat"; exit 1; }
+ done
+ ls -lR dist/native
+
# The same game.love the arm64 AppImage and Xbox UWP builds fused, so
# every release asset ships one identical payload (build.sh's own pack
# would omit PATCH_NOTES.md and mobile/ios/app-repo.json).
@@ -390,6 +502,7 @@ jobs:
- name: Build macOS + Windows + Linux
env:
GEN1TLS_DLL: ${{ github.workspace }}/dist/native/win-x64/gen1tls.dll
+ SHADERFX_BRIDGE_REQUIRED: "1"
run: |
set -euo pipefail
# Sign in-build (identity auto-detected from the temp keychain);
@@ -404,6 +517,8 @@ jobs:
--game-love dist/payload/game.love
unzip -l dist/win/gen1recomp-win64.zip | grep -F gen1tls.dll \
|| { echo "::error::Windows zip is missing gen1tls.dll"; exit 1; }
+ unzip -l dist/win/gen1recomp-win64.zip | grep -F librashader_bridge.dll \
+ || { echo "::error::Windows zip is missing librashader_bridge.dll"; exit 1; }
- name: Materialize Android release signing key
env:
@@ -461,6 +576,8 @@ jobs:
--version "${{ needs.version.outputs.version }}"
- name: Build Anbernic RG34XXSP port
+ env:
+ SHADERFX_BRIDGE_REQUIRED: "1"
run: |
set -euo pipefail
# Self-contained aarch64 PortMaster-style pack; pulls the LÖVE 11.5
@@ -474,6 +591,7 @@ jobs:
# builds, while this explicit local override keeps CI source-aligned.
GEN1RECOMP_SOURCE_DIR: ${{ github.workspace }}
GEN1RECOMP_RELEASE_TAG: v${{ needs.version.outputs.version }}
+ SHADERFX_BRIDGE_REQUIRED: "1"
run: |
set -euo pipefail
# Same aarch64 PortMaster-style pack for Linux ARM SBC PortMaster. The build
diff --git a/README.md b/README.md
index e4b52816..720e12f4 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,19 @@
# Gen1Recomp
-A native LÖVE2D recreation of Poke Red, Blue and Yellow. The engine and map
-behavior are hand-written Lua; game data and graphics are decoded from a ROM
-supplied by the player.
+A native LÖVE2D recreation of Poke Red, Blue, Gold, Silver, and Crystal. The
+engine and map behavior are hand-written Lua; game data and graphics are
+decoded from a ROM supplied by the player.
And before you say, "that's not a recomp", you're wrong. Recomp is an acronym. ***Reverse Engineering Causes Obsessive Mental Problems***
[Click Here for the AI Use Disclosure!](AIDisclosure.md)
> [!CAUTION]
-> **We are NOT affiliated with the website `gen1recomp[.]com`** That website is not run by this project, was not authorized by us, and we have no idea who operates it. It is impersonating this project; do not download anything from it, and treat anything it hosts or claims as untrustworthy. Even if the site currently links back to this repository, the people behind it can change its content at any time, so nothing on it should ever be trusted. This GitHub repository and the Discord linked below are the only official sources for this project. Also, as I assumed would eventually happen, the idiot that made that website now pumped it full of adware. Please stay away from that website.
+> **We are NOT affiliated with the website `gen1recomp[.]com`** That website is not run by this project, was not authorized by us, and we have no idea who operates it. It is impersonating this project; do not download anything from it, and treat anything it hosts or claims as untrustworthy. Even if the site currently links back to this repository, the people behind it can change its content at any time, so nothing on it should ever be trusted. This GitHub repository, the Discord, and https://gen1re.com are the only official sources for this project. Also, as I assumed would eventually happen, the idiot that made that website now pumped it full of adware. Please stay away from that website.

-**SUPPORT / ANNOUNCEMENTS / MODS:** [Discord](https://bois.icu)
+# [SUPPORT / ANNOUNCEMENTS / MODS ALL FOUND ON THE DISCORD](https://bois.icu)
diff --git a/build-linux-arm-sbc.sh b/build-linux-arm-sbc.sh
index e6dfd45e..058c5cbe 100755
--- a/build-linux-arm-sbc.sh
+++ b/build-linux-arm-sbc.sh
@@ -192,6 +192,20 @@ chmod +x "$PORT_ROOT/$PORT_DIR_NAME/bin/love.aarch64"
cp "$LOVE_LIB" "$LUAJIT_LIB" "$MODPLUG_LIB" "$OGG_LIB" \
"$PORT_ROOT/$PORT_DIR_NAME/libs.aarch64/"
+BRIDGE_LIB="liblibrashader_bridge.so"
+BRIDGE_SRC="${SHADERFX_BRIDGE_LINUX_ARM64:-}"
+if [ -z "$BRIDGE_SRC" ] && [ -f "$ROOT/dist/native/linux-arm64/$BRIDGE_LIB" ]; then
+ BRIDGE_SRC="$ROOT/dist/native/linux-arm64/$BRIDGE_LIB"
+fi
+if [ -n "$BRIDGE_SRC" ] && [ -f "$BRIDGE_SRC" ]; then
+ cp "$BRIDGE_SRC" "$PORT_ROOT/$PORT_DIR_NAME/libs.aarch64/$BRIDGE_LIB"
+ say "bundled $BRIDGE_LIB for SHADER FX preset conversion"
+elif [ "${SHADERFX_BRIDGE_REQUIRED:-}" = "1" ]; then
+ fail "$BRIDGE_LIB not found: set SHADERFX_BRIDGE_LINUX_ARM64 or stage it at dist/native/linux-arm64/$BRIDGE_LIB"
+else
+ warn "$BRIDGE_LIB not found: this port can run converted presets but not CONVERT new ones"
+fi
+
# Drop a short license pointer for the bundled LÖVE bits.
cat > "$PORT_ROOT/$PORT_DIR_NAME/licenses/LICENSE.love2d.txt" <<'EOF'
This port bundles the LÖVE 11.5 aarch64 runtime from PortMaster
diff --git a/build-rg34xxsp.sh b/build-rg34xxsp.sh
index 85c2bcfb..06b5ede7 100755
--- a/build-rg34xxsp.sh
+++ b/build-rg34xxsp.sh
@@ -156,6 +156,20 @@ chmod +x "$PORT_ROOT/$PORT_DIR_NAME/bin/love.aarch64"
cp "$LOVE_LIB" "$LUAJIT_LIB" "$MODPLUG_LIB" "$OGG_LIB" \
"$PORT_ROOT/$PORT_DIR_NAME/libs.aarch64/"
+BRIDGE_LIB="liblibrashader_bridge.so"
+BRIDGE_SRC="${SHADERFX_BRIDGE_LINUX_ARM64:-}"
+if [ -z "$BRIDGE_SRC" ] && [ -f "$ROOT/dist/native/linux-arm64/$BRIDGE_LIB" ]; then
+ BRIDGE_SRC="$ROOT/dist/native/linux-arm64/$BRIDGE_LIB"
+fi
+if [ -n "$BRIDGE_SRC" ] && [ -f "$BRIDGE_SRC" ]; then
+ cp "$BRIDGE_SRC" "$PORT_ROOT/$PORT_DIR_NAME/libs.aarch64/$BRIDGE_LIB"
+ say "bundled $BRIDGE_LIB for SHADER FX preset conversion"
+elif [ "${SHADERFX_BRIDGE_REQUIRED:-}" = "1" ]; then
+ fail "$BRIDGE_LIB not found: set SHADERFX_BRIDGE_LINUX_ARM64 or stage it at dist/native/linux-arm64/$BRIDGE_LIB"
+else
+ warn "$BRIDGE_LIB not found: this port can run converted presets but not CONVERT new ones"
+fi
+
# Drop a short license pointer for the bundled LÖVE bits.
cat > "$PORT_ROOT/$PORT_DIR_NAME/licenses/LICENSE.love2d.txt" <<'EOF'
This port bundles the LÖVE 11.5 aarch64 runtime from PortMaster
diff --git a/docs/shaderfx.md b/docs/shaderfx.md
index 3fe60c70..d9ebda96 100644
--- a/docs/shaderfx.md
+++ b/docs/shaderfx.md
@@ -214,12 +214,51 @@ finally the bare name handed to the system loader. Per-OS names are
`librashader_bridge.dylib` (macOS), and `liblibrashader_bridge.so` or
`librashader_bridge.so` (Linux and Android). Android resolves the bare name
because the `.so` ships as an ordinary `jniLibs` entry, so `dlopen` finds it
-without a path. The desktop path is still a developer build sitting in cargo's
-output directory; nothing packages it next to a shipped game yet.
-`ShaderFX.canConvert()` reports whether the library resolved on this machine,
+without a path. `ShaderFX.canConvert()` reports whether the library resolved on this machine,
and `ShaderFX.bridgeError()` says why not. Activating an already-converted
preset never needs any of this.
+**Shipped builds carry it.** `cargo` only ever emits the host's library, and
+the macOS release runner packs macOS, Windows and Linux in a single
+`scripts/build.sh all`, so release CI cross-builds one cdylib per platform on
+its own native runner (the `shaderfx-bridge` matrix in
+`.github/workflows/release.yml`) and stages it at `dist/native//`.
+`bundle_shader_bridge` in `scripts/build.sh` reads that directory, and
+`SHADERFX_BRIDGE_` overrides it. Where each artifact puts the library:
+
+| Artifact | Location | Found by |
+| --- | --- | --- |
+| macOS .app | `Contents/MacOS/` | source base directory |
+| Windows zip | next to `love.exe` | source base directory |
+| Linux AppImage (both arches) | AppDir root, next to `game.love` | source base directory |
+| Flatpak | `/app/share/gen1recomp/` | source base directory |
+| RG34XXSP / ARM SBC ports | `libs.aarch64/` | bare name via `LD_LIBRARY_PATH` |
+| Android APK | `jniLibs//` | bare name |
+| iOS | linked into the binary | `ffi.C` |
+
+The macOS library is a universal binary, because LÖVE.app is: an arm64-only
+bridge would fail `ffi.load` on Intel Macs the moment they hit CONVERT. The
+two Linux libraries are built with `cargo-zigbuild` against
+`{x86_64,aarch64}-unknown-linux-gnu.2.17`, below every consumer: the upstream
+x86_64 LÖVE AppImage floors at GLIBC_2.29, the arm64 AppImage's own gate is
+GLIBC_2.31, PortMaster's aarch64 LÖVE runtime floors at GLIBC_2.17, and ArkOS
+is glibc 2.30. 2.17 is the oldest glibc with aarch64 support, so one library
+per arch clears the whole fleet. `scripts/linux-arm64/verify_appimage.sh`
+discovers every ELF object in the AppDir rather than globbing `bin/love` and
+`lib/*.so*`, so the bridge is held to the same floor, resolution and
+dlopen-not-linked rules as everything else, then dlopened once to prove
+relocations and constructors run.
+
+Setting `SHADERFX_BRIDGE_REQUIRED=1` turns the packagers' "not found" warning
+into a hard error. Release CI sets it on every job that ships a bridge, so a
+release cannot silently go out without one again.
+
+**Where CONVERT is unavailable.** Switch and Xbox UWP ship without a bridge.
+The Switch build uses devkitPro's toolchain, which is not a rustc target; the
+UWP appcontainer is unproven for loading a desktop MSVC DLL. On both, presets
+converted elsewhere still activate and run normally, and `ShaderFX.canConvert()`
+returns false with `ShaderFX.bridgeError()` explaining why.
+
**Statically linked bridges.** On iOS there is no loadable library at all:
the bridge is linked straight into the app binary, so its symbols live in the
main image and are reachable only through `ffi.C`. The candidate list is empty
diff --git a/flatpak/com.theboisclub.gen1recomp.yml b/flatpak/com.theboisclub.gen1recomp.yml
index 7b442611..11aabef2 100644
--- a/flatpak/com.theboisclub.gen1recomp.yml
+++ b/flatpak/com.theboisclub.gen1recomp.yml
@@ -43,6 +43,9 @@ modules:
EOF
- chmod +x /app/bin/gen1recomp
- install -Dm644 game.love /app/share/gen1recomp/game.love
+ # BRIDGE-BEGIN
+ - install -Dm755 liblibrashader_bridge.so /app/share/gen1recomp/liblibrashader_bridge.so
+ # BRIDGE-END
- install -Dm644 com.theboisclub.gen1recomp.desktop /app/share/applications/com.theboisclub.gen1recomp.desktop
- install -Dm644 com.theboisclub.gen1recomp.metainfo.xml /app/share/metainfo/com.theboisclub.gen1recomp.metainfo.xml
- install -Dm644 icon.png /app/share/icons/hicolor/512x512/apps/com.theboisclub.gen1recomp.png
@@ -53,6 +56,10 @@ modules:
dest-filename: love-x86_64.AppImage
- type: file
path: game.love
+ # BRIDGE-BEGIN
+ - type: file
+ path: liblibrashader_bridge.so
+ # BRIDGE-END
- type: file
path: com.theboisclub.gen1recomp.metainfo.xml
- type: file
diff --git a/scripts/build.sh b/scripts/build.sh
index f3b6cc09..b9b9caf0 100755
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -170,27 +170,53 @@ make_ico() { # $1 = output .ico path
# --------------------------------------------------------------- macOS
# ShaderFX's librashader bridge. Only the CONVERT action needs it, so a build
# without it still runs presets that were converted elsewhere.
-# SHADERFX_BRIDGE points at a prebuilt library; otherwise cargo builds it.
+# SHADERFX_BRIDGE_ or dist/native// points at a prebuilt library;
+# otherwise cargo builds it, host platform only.
+shader_bridge_host_plat() {
+ case "$(uname -s)-$(uname -m)" in
+ Darwin-*) printf 'mac' ;;
+ Linux-x86_64) printf 'linux-x64' ;;
+ Linux-aarch64|Linux-arm64) printf 'linux-arm64' ;;
+ *) printf '' ;;
+ esac
+}
+
bundle_shader_bridge() {
- local dest="$1" name="$2"
- local src="${SHADERFX_BRIDGE:-}"
+ local dest="$1" name="$2" plat="$3"
local crate="$ROOT/tools/shaderfx-bridge"
- if [ -z "$src" ] && [ -f "$crate/target/release/$name" ]; then
- src="$crate/target/release/$name"
+ local src="" var
+ [ -n "$plat" ] || fail "bundle_shader_bridge: no platform key for $name"
+
+ var="SHADERFX_BRIDGE_$(printf '%s' "$plat" | tr 'a-z-' 'A-Z_')"
+ eval "src=\${$var:-}"
+
+ if [ -z "$src" ] && [ -f "$DIST/native/$plat/$name" ]; then
+ src="$DIST/native/$plat/$name"
fi
- if [ -z "$src" ] && command -v cargo >/dev/null 2>&1; then
- say "building the ShaderFX bridge with cargo"
- if (cd "$crate" && cargo build --release >/dev/null 2>&1); then
+ if [ -z "$src" ] && [ -n "${SHADERFX_BRIDGE:-}" ]; then
+ src="$SHADERFX_BRIDGE"
+ fi
+ if [ -z "$src" ] && [ "$plat" = "$(shader_bridge_host_plat)" ]; then
+ if [ -f "$crate/target/release/$name" ]; then
src="$crate/target/release/$name"
+ elif command -v cargo >/dev/null 2>&1; then
+ say "building the ShaderFX bridge with cargo"
+ if (cd "$crate" && cargo build --release >/dev/null 2>&1); then
+ src="$crate/target/release/$name"
+ fi
fi
fi
+
if [ -n "$src" ] && [ -f "$src" ]; then
mkdir -p "$dest"
cp "$src" "$dest/$name"
- say "bundled $name for SHADER FX preset conversion"
- else
- warn "$name not found: this build can run converted presets but not CONVERT new ones (set SHADERFX_BRIDGE or install cargo)"
+ say "bundled $name for SHADER FX preset conversion ($plat)"
+ return 0
fi
+ if [ "${SHADERFX_BRIDGE_REQUIRED:-}" = "1" ]; then
+ fail "$name ($plat) not found: set $var or stage it at dist/native/$plat/$name"
+ fi
+ warn "$name not found: this build can run converted presets but not CONVERT new ones (set $var or install cargo)"
}
build_mac() {
@@ -205,7 +231,7 @@ build_mac() {
# drop any bundled placeholder .love and fuse ours in
find "$out_app/Contents/Resources" -maxdepth 1 -name '*.love' -delete
cp "$LOVE_FILE" "$out_app/Contents/Resources/game.love"
- bundle_shader_bridge "$out_app/Contents/MacOS" "liblibrashader_bridge.dylib"
+ bundle_shader_bridge "$out_app/Contents/MacOS" "liblibrashader_bridge.dylib" mac
local plist="$out_app/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleName $APP_NAME" "$plist" 2>/dev/null \
@@ -322,7 +348,7 @@ build_win() {
warn "gen1tls.dll not found: Windows zip will not support wss:// (set GEN1TLS_DLL or build native/tls_dial)"
fi
- bundle_shader_bridge "$out_dir" "librashader_bridge.dll"
+ bundle_shader_bridge "$out_dir" "librashader_bridge.dll" win-x64
# The exe's icon lives in love.exe's PE resources, so it must be patched
# BEFORE the .love is appended: peresed rewrites the whole file and would
@@ -419,7 +445,7 @@ build_linux() {
unsquashfs -q -no-xattrs -o "$sfs_offset" -d "$appdir" "$love_appimage" >/dev/null
cp "$LOVE_FILE" "$appdir/game.love"
- bundle_shader_bridge "$appdir" "liblibrashader_bridge.so"
+ bundle_shader_bridge "$appdir" "liblibrashader_bridge.so" linux-x64
# Replace LÖVE's own desktop entry rather than keeping it: it says
# Name=LÖVE / Icon=love, which is what appimaged, app menus and file
diff --git a/scripts/build_flatpak.sh b/scripts/build_flatpak.sh
index a0e04214..630cb077 100755
--- a/scripts/build_flatpak.sh
+++ b/scripts/build_flatpak.sh
@@ -86,7 +86,25 @@ text = re.sub(
pathlib.Path(dst).write_text(text, encoding="utf-8")
PY
-cp "$ROOT/flatpak/$APP_ID.yml" "$CACHE/$APP_ID.yml"
+BRIDGE_LIB="liblibrashader_bridge.so"
+BRIDGE_SRC="${SHADERFX_BRIDGE_LINUX_X64:-}"
+if [ -z "$BRIDGE_SRC" ] && [ -f "$ROOT/dist/native/linux-x64/$BRIDGE_LIB" ]; then
+ BRIDGE_SRC="$ROOT/dist/native/linux-x64/$BRIDGE_LIB"
+fi
+if [ -z "$BRIDGE_SRC" ] && [ -f "$ROOT/tools/shaderfx-bridge/target/release/$BRIDGE_LIB" ]; then
+ BRIDGE_SRC="$ROOT/tools/shaderfx-bridge/target/release/$BRIDGE_LIB"
+fi
+rm -f "$CACHE/$BRIDGE_LIB"
+if [ -n "$BRIDGE_SRC" ] && [ -f "$BRIDGE_SRC" ]; then
+ cp "$BRIDGE_SRC" "$CACHE/$BRIDGE_LIB"
+ cp "$ROOT/flatpak/$APP_ID.yml" "$CACHE/$APP_ID.yml"
+ say "staged $BRIDGE_LIB for SHADER FX preset conversion"
+elif [ "${SHADERFX_BRIDGE_REQUIRED:-}" = "1" ]; then
+ fail "$BRIDGE_LIB not found: set SHADERFX_BRIDGE_LINUX_X64 or stage it at dist/native/linux-x64/$BRIDGE_LIB"
+else
+ warn "$BRIDGE_LIB not found: this bundle can run converted presets but not CONVERT new ones"
+ sed '/# BRIDGE-BEGIN/,/# BRIDGE-END/d' "$ROOT/flatpak/$APP_ID.yml" > "$CACHE/$APP_ID.yml"
+fi
say "installing Freedesktop runtime (no-op if present)"
flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo || true
@@ -116,5 +134,10 @@ if command -v sha256sum >/dev/null 2>&1; then
else
printf '%s %s\n' "$(shasum -a 256 "$OUT" | awk '{print $1}')" "$(basename "$OUT")" > "$OUT.sha256"
fi
+if [ -f "$CACHE/$BRIDGE_LIB" ]; then
+ [ -f "$BUILD_DIR/files/share/gen1recomp/$BRIDGE_LIB" ] \
+ || fail "$BRIDGE_LIB was staged but is missing from the built /app/share/gen1recomp"
+ say "verified $BRIDGE_LIB in the Flatpak"
+fi
say "Flatpak build: $OUT ($(du -h "$OUT" | cut -f1))"
say "sha256: $(cut -d' ' -f1 "$OUT.sha256")"
diff --git a/scripts/build_linux_arm64.sh b/scripts/build_linux_arm64.sh
index 4df2ff00..57a3cda4 100755
--- a/scripts/build_linux_arm64.sh
+++ b/scripts/build_linux_arm64.sh
@@ -100,6 +100,21 @@ then
fi
cp "$GAME_LOVE" "$IN_DIR/game.love"
+BRIDGE_LIB="liblibrashader_bridge.so"
+BRIDGE_SRC="${SHADERFX_BRIDGE_LINUX_ARM64:-}"
+if [ -z "$BRIDGE_SRC" ] && [ -f "$ROOT/dist/native/linux-arm64/$BRIDGE_LIB" ]; then
+ BRIDGE_SRC="$ROOT/dist/native/linux-arm64/$BRIDGE_LIB"
+fi
+rm -f "$IN_DIR/$BRIDGE_LIB"
+if [ -n "$BRIDGE_SRC" ] && [ -f "$BRIDGE_SRC" ]; then
+ cp "$BRIDGE_SRC" "$IN_DIR/$BRIDGE_LIB"
+ say "staged $BRIDGE_LIB for SHADER FX preset conversion"
+elif [ "${SHADERFX_BRIDGE_REQUIRED:-}" = "1" ]; then
+ fail "$BRIDGE_LIB not found: set SHADERFX_BRIDGE_LINUX_ARM64 or stage it at dist/native/linux-arm64/$BRIDGE_LIB"
+else
+ warn "$BRIDGE_LIB not found: this build can run converted presets but not CONVERT new ones"
+fi
+
# --------------------------------------------------------------- downloads
# Fetched on the host and checksum-pinned here so the container never needs
# network access and every input is verified in exactly one place.
diff --git a/scripts/linux-arm64/build_appimage.sh b/scripts/linux-arm64/build_appimage.sh
index 1c6af56d..385cffea 100755
--- a/scripts/linux-arm64/build_appimage.sh
+++ b/scripts/linux-arm64/build_appimage.sh
@@ -327,6 +327,14 @@ bundle_needed "$APPDIR/lib/liblove-$LOVE_VERSION.so"
say "bundled $(ls "$APPDIR/lib" | wc -l) libraries: $(ls "$APPDIR/lib" | tr '\n' ' ')"
# ------------------------------------------------- host dependency contract
+SHADER_BRIDGE=()
+if [ -f "$IN/liblibrashader_bridge.so" ]; then
+ cp "$IN/liblibrashader_bridge.so" "$APPDIR/liblibrashader_bridge.so"
+ chmod 0755 "$APPDIR/liblibrashader_bridge.so"
+ SHADER_BRIDGE=("$APPDIR/liblibrashader_bridge.so")
+ say "bundled liblibrashader_bridge.so for SHADER FX preset conversion"
+fi
+
# The portability promise, stated as an assertion instead of a paragraph in a
# README: these are the ONLY sonames the shipped objects may require from the
# host. Everything driver-, session- or audio-related has to be reached
@@ -339,7 +347,7 @@ say "bundled $(ls "$APPDIR/lib" | wc -l) libraries: $(ls "$APPDIR/lib" | tr '\n'
HOST_ALLOWED_RE='^(ld-linux-aarch64\.so\.1|libc\.so\.6|libm\.so\.6|libdl\.so\.2|libpthread\.so\.0|librt\.so\.1|libstdc\+\+\.so\.6|libgcc_s\.so\.1|libatomic\.so\.1|libfreetype\.so\.6|libpng[0-9]*\.so\.[0-9]+|libz\.so\.1|libbrotli(dec|common)\.so\.1)$'
unexpected=""
-for object in "$APPDIR/bin/love" "$APPDIR"/lib/*.so*; do
+for object in "$APPDIR/bin/love" "$APPDIR"/lib/*.so* ${SHADER_BRIDGE[@]+"${SHADER_BRIDGE[@]}"}; do
while read -r soname; do
[ -n "$soname" ] || continue
# Satisfied from inside the AppDir, so not a host requirement at all.
diff --git a/scripts/linux-arm64/verify_appimage.sh b/scripts/linux-arm64/verify_appimage.sh
index b437ebc5..4ff60225 100755
--- a/scripts/linux-arm64/verify_appimage.sh
+++ b/scripts/linux-arm64/verify_appimage.sh
@@ -20,6 +20,23 @@ for required in AppRun bin/love game.love lib/liblove-11.5.so; do
|| { echo "::error::AppImage is missing $required"; exit 1; }
done
+bridge="squashfs-root/liblibrashader_bridge.so"
+if [ "${SHADERFX_BRIDGE_REQUIRED:-}" = "1" ] && [ ! -e "$bridge" ]; then
+ echo "::error::AppImage is missing liblibrashader_bridge.so"
+ exit 1
+fi
+
+# Discovered, not enumerated: bin/love and lib/*.so* miss anything native at
+# the AppDir root, which is where ShaderFX's bridge sits and where the next
+# root-level library would land too.
+scan=()
+while IFS= read -r f; do
+ scan+=("$f")
+done < <(find squashfs-root -type f -exec sh -c \
+ 'head -c4 "$1" | od -An -tx1 | tr -d " \n" | grep -q "^7f454c46$"' _ {} \; -print | sort)
+[ "${#scan[@]}" -gt 0 ] || { echo "::error::found no ELF objects in the AppDir"; exit 1; }
+echo "scanning ${#scan[@]} ELF objects: ${scan[*]#squashfs-root/}"
+
# Every bundled object must resolve once AppRun's LD_LIBRARY_PATH is
# applied; an unresolved soname here is a user-visible launch crash.
#
@@ -28,14 +45,14 @@ done
# hard-links libpulse/libasound/libX11/libwayland, so it only ever
# started on a full desktop -- a bare runner is what exposed it.
missing="$(LD_LIBRARY_PATH="$PWD/squashfs-root/lib" \
- ldd squashfs-root/bin/love squashfs-root/lib/*.so* 2>/dev/null \
+ ldd "${scan[@]}" 2>/dev/null \
| grep 'not found' || true)"
[ -z "$missing" ] || { echo "::error::unresolved deps:"; echo "$missing"; exit 1; }
# Nothing may hard-link a driver, session or audio-stack library:
# those must be reached through dlopen so the AppImage runs on a box
# with only ALSA, only Wayland, or only KMSDRM.
-linked="$(for f in squashfs-root/bin/love squashfs-root/lib/*.so*; do
+linked="$(for f in "${scan[@]}"; do
objdump -p "$f" 2>/dev/null | awk '/NEEDED/{print $2}'
done | sort -u | grep -E '^lib(pulse|asound|X11|wayland|GL|EGL|drm|gbm|xcb|cairo|sndio|dbus)' || true)"
[ -z "$linked" ] \
@@ -45,7 +62,7 @@ done | sort -u | grep -E '^lib(pulse|asound|X11|wayland|GL|EGL|drm|gbm|xcb|cairo
# the builder to a newer base, the glibc floor silently rises and
# every user on an older distro gets "GLIBC_2.xx not found" -- catch
# it here instead of in a release.
-floor="$(objdump -T squashfs-root/bin/love squashfs-root/lib/*.so* 2>/dev/null \
+floor="$(objdump -T "${scan[@]}" 2>/dev/null \
| grep -o 'GLIBC_[0-9.]*' | sort -V | tail -1)"
echo "highest required glibc symbol version: $floor"
[ -n "$floor" ] \
@@ -54,4 +71,12 @@ highest="$(printf '%s\n' "$floor" "GLIBC_2.31" | sort -V | tail -1)"
[ "$highest" = "GLIBC_2.31" ] \
|| { echo "::error::AppImage requires $floor, above the bullseye 2.31 floor"; exit 1; }
+# The floor grep proves symbol versions; an actual dlopen additionally proves
+# relocations and constructors. Native arch only, so it is skipped elsewhere.
+if [ -e "$bridge" ] && [ "$(uname -m)" = "aarch64" ] && command -v python3 >/dev/null 2>&1; then
+ python3 -c "import ctypes,sys; ctypes.CDLL(sys.argv[1])" "$PWD/$bridge" \
+ || { echo "::error::liblibrashader_bridge.so failed to dlopen"; exit 1; }
+ echo "librashader bridge dlopens cleanly"
+fi
+
echo "AppImage verified: $image"
diff --git a/scripts/test.sh b/scripts/test.sh
index 966b6e3e..d7fe201e 100755
--- a/scripts/test.sh
+++ b/scripts/test.sh
@@ -118,6 +118,7 @@ run_tier "T0 Yellow title OBP eye remap" python3 tests/title_pikachu_obp_test.py
run_tier "T0 Crystal manifest + specials coverage" "$LUA" tests/crystal_import_test.lua
run_tier "T0 switch CI workflow content gate" "$LUA" tests/switch_ci_workflows_test.lua
run_tier "T0 switch transfer docs gate" "$LUA" tests/switch_transfer_docs_test.lua
+run_tier "T0 ShaderFX bridge packaging gate" "$LUA" tests/shaderfx_bridge_packaging_test.lua
# NX Blue/Yellow asset overlay: ROM-free, must run on every checkout so a
# Sound.lua / overlay regression is not gated only on switch-changes paths.
run_tier "T0 NX asset overlay fallback" "$LUA" tests/engine/assets_version_fallback_test.lua
diff --git a/tests/shaderfx_bridge_packaging_test.lua b/tests/shaderfx_bridge_packaging_test.lua
new file mode 100644
index 00000000..43801778
--- /dev/null
+++ b/tests/shaderfx_bridge_packaging_test.lua
@@ -0,0 +1,71 @@
+-- Content gate: every shipped artifact carries ShaderFX's librashader bridge.
+-- Self-contained: luajit tests/shaderfx_bridge_packaging_test.lua
+
+local T = require("tests.harness")
+local check = T.check
+
+local function read(path)
+ local f, err = io.open(path, "r")
+ if not f then error("cannot read " .. path .. ": " .. tostring(err)) end
+ local s = f:read("*a")
+ f:close()
+ return s
+end
+
+local function mustContain(body, needle, label)
+ check(body:find(needle, 1, true) ~= nil,
+ label .. " must contain " .. string.format("%q", needle))
+end
+
+local build = read("scripts/build.sh")
+local release = read(".github/workflows/release.yml")
+local flatpak = read("scripts/build_flatpak.sh")
+local manifest = read("flatpak/com.theboisclub.gen1recomp.yml")
+local arm64 = read("scripts/build_linux_arm64.sh")
+local arm64_pack = read("scripts/linux-arm64/build_appimage.sh")
+local verify = read("scripts/linux-arm64/verify_appimage.sh")
+local rg34 = read("build-rg34xxsp.sh")
+local sbc = read("build-linux-arm-sbc.sh")
+
+for _, call in ipairs({
+ 'bundle_shader_bridge "$out_app/Contents/MacOS" "liblibrashader_bridge.dylib" mac',
+ 'bundle_shader_bridge "$out_dir" "librashader_bridge.dll" win-x64',
+ 'bundle_shader_bridge "$appdir" "liblibrashader_bridge.so" linux-x64',
+}) do
+ mustContain(build, call, "build.sh")
+end
+mustContain(build, '$DIST/native/$plat/$name', "build.sh staged lookup")
+mustContain(build, 'SHADERFX_BRIDGE_REQUIRED', "build.sh hard-fail switch")
+mustContain(build, '[ "$plat" = "$(shader_bridge_host_plat)" ]', "build.sh host guard")
+
+mustContain(release, "shaderfx-bridge:", "release.yml bridge job")
+for _, plat in ipairs({ "win-x64", "mac", "linux-x64", "linux-arm64" }) do
+ mustContain(release, "plat: " .. plat, "release.yml matrix")
+end
+mustContain(release, "lipo -create", "release.yml universal macOS bridge")
+
+local requiredIn = select(2, release:gsub('SHADERFX_BRIDGE_REQUIRED: "1"', ""))
+check(requiredIn >= 6,
+ 'release.yml must set SHADERFX_BRIDGE_REQUIRED on every shipping job, found ' .. requiredIn)
+mustContain(release, "librashader_bridge.dll", "release.yml Windows zip assertion")
+
+mustContain(manifest, "# BRIDGE-BEGIN", "flatpak manifest markers")
+mustContain(manifest, "/app/share/gen1recomp/liblibrashader_bridge.so", "flatpak install path")
+mustContain(flatpak, "/# BRIDGE-BEGIN/,/# BRIDGE-END/d", "build_flatpak.sh strip")
+mustContain(flatpak, "$BUILD_DIR/files/share/gen1recomp/$BRIDGE_LIB", "build_flatpak.sh verify")
+
+mustContain(arm64, "SHADERFX_BRIDGE_LINUX_ARM64", "build_linux_arm64.sh override")
+mustContain(arm64_pack, '$APPDIR/liblibrashader_bridge.so', "build_appimage.sh AppDir root")
+mustContain(arm64_pack, '${SHADER_BRIDGE[@]+"${SHADER_BRIDGE[@]}"}', "build_appimage.sh contract scan")
+mustContain(verify, "7f454c46", "verify_appimage.sh discovers ELF objects")
+mustContain(verify, 'ctypes.CDLL', "verify_appimage.sh dlopen smoke")
+for _, scanned in ipairs({ 'ldd "${scan[@]}"', 'for f in "${scan[@]}"', 'objdump -T "${scan[@]}"' }) do
+ mustContain(verify, scanned, "verify_appimage.sh")
+end
+
+for _, pair in ipairs({ { rg34, "build-rg34xxsp.sh" }, { sbc, "build-linux-arm-sbc.sh" } }) do
+ mustContain(pair[1], 'libs.aarch64/$BRIDGE_LIB', pair[2])
+ mustContain(pair[1], "SHADERFX_BRIDGE_REQUIRED", pair[2])
+end
+
+T.finish("shaderfx_bridge_packaging_test")
diff --git a/tools/shaderfx-bridge/target/release/liblibrashader_bridge.dylib b/tools/shaderfx-bridge/target/release/liblibrashader_bridge.dylib
deleted file mode 100755
index a93711b2..00000000
Binary files a/tools/shaderfx-bridge/target/release/liblibrashader_bridge.dylib and /dev/null differ