Files
Daniel LynchandClaude Opus 5 1f3dc40c07 docs+build: hygiene pass — close leak risk, de-drift docs, fix build prereqs
Repo hygiene round following a full review. No shim behaviour changes.

Leak risk:
- .gitignore: ignore CLAUDE.md (personal assistant-lane config, was one
  `git add -A` away from a public commit) and scratch_obj/.

Docs vs. reality:
- shim/README.md: rewritten. It described a pre-implementation skeleton with
  "core fns are TODO stubs returning -1005", three mutually inconsistent stub
  counts, and four completed milestones listed as open. Now carries the verified
  breakdown: 438/438 exports = 371 generated stubs + 46 core + 7 layers + 2
  Vulkan queries + 12 passthru trampolines.
- TESTING.md: dropped the self-contradicting "NOT yet" block (5 of 6 items were
  done or misstated, and contradicted the same file 45 lines above). Path B now
  points at tools/desktop-harness, which exists, instead of the orphaned
  shim/tests/harness.c. Path A prereqs marked as the record they are.
- HOST.md: corrected the runtime assumption. The OpenXR runtime inside Lepton is
  SteamVR (vendor/etc/openxr/1/active_runtime.json -> vrclient.so), not Monado.
  Favourable: SteamVR emulates Oculus Touch by default and advertises the
  XR_FB_foveation family, so the existing input and foveation paths should carry
  over. The old "remaining unknowns" are resolved by Lepton's published source
  and replaced with the items to check before a first Frame boot.
- README.md: same runtime correction.
- docs/research/RECON.md: the four passages prescribing an entitlement
  NOP/stub/bypass are corrected in place rather than merely disclaimed by the
  top banner, which they contradicted.

Build correctness:
- shim/build_android.sh: missing patchelf is now fatal. It warned and exited 0,
  producing a .so that cannot resolve the OpenXR loader at runtime.
- scripts/fetch_deps.sh + packaging/build_openxr_loader.sh: pin the OpenXR and
  Vulkan header versions (were tracking `main`), overridable via OPENXR_TAG /
  VULKAN_HEADERS_TAG; require cmake for the loader build.
- packaging/steamframe_patches.sh: use the apktool.jar that fetch_deps.sh
  downloads. Its prereq check demanded an `apktool` binary on PATH that the
  documented setup never provides, so it could not run after a clean setup.
- shim/gen_stubs.sh: it reads all_exports.txt, not shim_surface.txt; comment and
  emitted banner corrected. stubs.c regenerated (banner line only).
- shim/src/core.c: split seven `if (out) ...; return ...;` one-liners. Host
  build now compiles with zero warnings, down from seven.

Verified: host build 0 warnings; gen_stubs.sh output identical on regeneration;
bash -n clean on all edited scripts; pinned header/tarball URLs return 200 and
the tag tarball extracts to the expected directory name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 02:15:10 -04:00

117 lines
6.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# steamframe_patches.sh — off-Quest APK patches for porting RE4 VR (UE4) to a NON-Meta
# Android VR device (Pico / Steam Frame / Monado-on-Android). These are NOT needed on a
# real Quest 2 (the shim alone suffices there); they only matter once Build.MANUFACTURER
# isn't "Oculus" and Meta-only manifest libs aren't present on the target.
#
# Our own implementation of the transforms; the set is informed by Overport's open-source
# patcher (docs/research/related-work.md). It does NOT use any of their binaries — we ship
# our own libOVRPlugin shim via repack.sh; this only fixes the APK's Java/manifest so UE
# takes the Oculus HMD path and the package installs off-Quest.
#
# Pipeline: apktool decode -> manifest + smali patches -> apktool build -> zipalign -> sign.
# Run repack.sh FIRST (it swaps in our shim libs); feed its output here.
#
# ./steamframe_patches.sh [in.apk] [out.apk]
# in.apk : shim-repacked APK (default: out/re4vr-shim.apk)
# out.apk : patched output (default: out/re4vr-steamframe.apk)
#
# Prereqs: tools/apktool.jar (scripts/fetch_deps.sh) or apktool on PATH, plus a JDK and
# the Android build-tools used by repack.sh.
# NOTE: prepared for the non-Quest bring-up; not yet validated on Steam Frame hardware.
set -euo pipefail
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_env.sh"
[ -n "${BT:-}" ] && [ -d "$BT" ] || { echo "Android build-tools not found. Run scripts/fetch_deps.sh"; exit 1; }
# Prefer the apktool.jar that scripts/fetch_deps.sh downloads (same as inspect_manifest.sh);
# fall back to an `apktool` wrapper on PATH.
if [ -f "${APKTOOL_JAR:-}" ]; then
APKTOOL=("${JAVA_HOME:+$JAVA_HOME/bin/}java" -jar "$APKTOOL_JAR")
elif command -v apktool >/dev/null; then
APKTOOL=(apktool)
else
echo "apktool not found — run scripts/fetch_deps.sh (fetches tools/apktool.jar) or install"
echo "apktool on PATH (https://apktool.org) to run the smali/manifest patches"; exit 1
fi
IN="${1:-$PKG/out/re4vr-shim.apk}"
OUT="${2:-$PKG/out/re4vr-steamframe.apk}"
[ -f "$IN" ] || { echo "input APK not found: $IN (run repack.sh first)"; exit 1; }
# --- DO NOT INSTALL ON QUEST guard ----------------------------------------------------
# This output is for NON-Quest devices. On a Quest the uses-(native-)library strip removes
# Meta libs the runtime relies on -> can break launch/features. The Quest build is the plain
# repack.sh output (out/re4vr-shim.apk). Require explicit acknowledgement so this can't be
# produced/installed against a Quest by accident. Bypass in automation with NOT_QUEST=1.
cat <<'WARN'
========================================================================
WARNING: NON-QUEST build. DO NOT install the output on a Meta Quest.
It strips Meta-only manifest libs the Quest needs (can break launch).
For Quest, install the plain repack.sh output: out/re4vr-shim.apk
========================================================================
WARN
if [ "${NOT_QUEST:-0}" != 1 ]; then
if [ -t 0 ]; then
read -r -p "Target is NOT a Quest and I understand this build will break on Quest [y/N] " ack
case "$ack" in y|Y|yes|YES) ;; *) echo "aborted (not confirmed)"; exit 1 ;; esac
else
echo "Refusing to run non-interactively. Re-run with NOT_QUEST=1 to confirm the target is not a Quest."
exit 1
fi
fi
WORK="$PKG/work/steamframe"
rm -rf "$WORK"; mkdir -p "$WORK"
DEC="$WORK/dec"
echo "== apktool decode =="
"${APKTOOL[@]}" d -f -o "$DEC" "$IN" >/dev/null
# Locate UE's GameActivity smali (ue4 or unreal namespace, across smali_classesN dirs).
mapfile -t GA < <(find "$DEC" -path '*/com/epicgames/ue4/GameActivity.smali' \
-o -path '*/com/epicgames/unreal/GameActivity.smali' 2>/dev/null)
[ "${#GA[@]}" -gt 0 ] || echo "WARN: no com/epicgames/{ue4,unreal}/GameActivity.smali found (UE patches skipped)"
# --- 1) Oculus device spoof: UE gates the Oculus HMD path on Build.MANUFACTURER/MODEL.
# Off-Quest these are wrong, so OculusHMD never inits and our shim is never called.
# Replace the field reads with constant "Oculus" / "Quest 2" in the same register. ---
spoofed=0
for f in "${GA[@]}"; do
perl -0777 -pe 's/sget-object (v\d+|p\d+), Landroid\/os\/Build;->MANUFACTURER:Ljava\/lang\/String;/const-string $1, "Oculus"/g' -i "$f"
perl -0777 -pe 's/sget-object (v\d+|p\d+), Landroid\/os\/Build;->MODEL:Ljava\/lang\/String;/const-string $1, "Quest 2"/g' -i "$f"
spoofed=1
done
[ "$spoofed" = 1 ] && echo "patched: Build.MANUFACTURER->\"Oculus\", MODEL->\"Quest 2\" (device spoof)"
# --- 2) Neuter AndroidThunkJava_ForceQuit: drop the System.exit(I) call so a failed
# off-Quest check can't hard-kill the app before we recover. ---
for f in "${GA[@]}"; do
perl -0777 -pe 's/(\.method public AndroidThunkJava_ForceQuit\(\)V.*?)(invoke-static \{[vp]\d+\}, Ljava\/lang\/System;->exit\(I\)V\n)(.*?\.end method)/$1$3/s' -i "$f" \
&& grep -q 'AndroidThunkJava_ForceQuit' "$f" && echo "patched: removed System.exit in AndroidThunkJava_ForceQuit ($(basename "$(dirname "$f")"))"
done
# --- 3) Manifest: strip <uses-library>/<uses-native-library> that name Meta-only libs
# (keep libopenxr.google.so) — they'd block install/launch off-Quest. ---
MAN="$DEC/AndroidManifest.xml"
if [ -f "$MAN" ]; then
before=$(grep -cE 'uses-(native-)?library' "$MAN" || true)
perl -0777 -pe 's/[ \t]*<uses-(native-)?library[^>]*android:name="(?!libopenxr\.google\.so)[^"]*"[^>]*\/>\n//g' -i "$MAN"
after=$(grep -cE 'uses-(native-)?library' "$MAN" || true)
echo "patched: manifest uses-(native-)library entries $before -> $after (kept libopenxr.google.so)"
fi
echo "== apktool build =="
"${APKTOOL[@]}" b -o "$WORK/unsigned.apk" "$DEC" >/dev/null
echo "== align + sign =="
"$BT/zipalign" -f -p 4 "$WORK/unsigned.apk" "$WORK/aligned.apk"
[ -f "$PKG/debug.keystore" ] || "$PKG/make_debug_keystore.sh"
"$BT/apksigner" sign --ks "$PKG/debug.keystore" --ks-pass pass:android --out "$OUT" "$WORK/aligned.apk"
"$BT/apksigner" verify "$OUT" && echo "signature OK"
echo
echo "built: $OUT"
echo "NOTE: NON-QUEST build — do NOT install on a Quest (use repack.sh's re4vr-shim.apk there)."
echo " prepared for the non-Quest bring-up; validate on the target headset."
echo "Still a shim TODO (runtime, not packaging): controller-pose offset for non-Touch"
echo "controllers (Overport's DisableControllerOffset) — handle in shim/src/xr_input.c."