mirror of
https://github.com/simplysanatori/KartFrame.git
synced 2026-10-17 12:01:20 +02:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
004f33aa0d | ||
|
|
45d71a37df | ||
|
|
cfa6aa1ded | ||
|
|
ed7c28f37c |
No files matched your search
@@ -92,3 +92,4 @@ output.txt
|
||||
/steam-frame/out/
|
||||
/steam-frame/release/
|
||||
/steam-frame/signing/
|
||||
__pycache__/
|
||||
@@ -87,10 +87,12 @@ What the patches do, in the order they were needed:
|
||||
background, which costs nothing. (`openxr_integration.cpp`; `menu_shader_quality = 0` in the
|
||||
config.)
|
||||
|
||||
5. **The item box.** In VR the item roulette slid up after use and stayed visible. Aurora's
|
||||
virtual-screen HUD draws ignore the game's scissor rectangle; the port now skips a 2D draw that
|
||||
lies entirely outside a scissor the game narrowed, which is what hides it on a TV.
|
||||
(`aurora-main/lib/gfx/common.cpp`.) During the slide itself a sliver can still show.
|
||||
5. **The item box (attempted, does not work yet).** In VR the item roulette slides up after use
|
||||
and stays visible. Aurora's virtual-screen HUD draws ignore the game's scissor rectangle, so the
|
||||
port now skips a 2D draw that lies entirely outside a scissor the game narrowed, which is what
|
||||
hides it on a TV. (`aurora-main/lib/gfx/common.cpp`.) In the headset the empty box still shows,
|
||||
so either the box is not drawn the way that check assumes or the scissor is not what hides it.
|
||||
The change is harmless and stays in until someone finds the real cause.
|
||||
|
||||
6. **Settings that suit the Frame**, seeded into `Config.toml` on the first launch by the wrapper
|
||||
([`steam-frame/Config.frame.toml`](steam-frame/Config.frame.toml)): interpolation on auto, menu
|
||||
@@ -100,9 +102,10 @@ What the patches do, in the order they were needed:
|
||||
together with the interpolation and I have not tested them one at a time.
|
||||
|
||||
7. **A launch wrapper**, because the app's 2D launcher never shows up in the headset on the Frame
|
||||
(SteamVR does not pick up Lepton's flat window for this app). Once a game is built, the wrapper
|
||||
waits for the launcher to come up and starts the game activity itself, so a launch from the Steam
|
||||
library goes straight to the title screen in about ten seconds.
|
||||
(SteamVR does not pick up Lepton's flat window for this app). Once a game built against this
|
||||
APK's kit is installed (it compares the kit fingerprints), the wrapper waits for the launcher to
|
||||
come up and starts the game activity itself, so a launch from the Steam library goes straight to
|
||||
the title screen in about ten seconds.
|
||||
|
||||
Everything else, the cameras, the hand IK, the HUD, Retro Rewind support, is upstream's work and is
|
||||
unchanged.
|
||||
@@ -114,17 +117,26 @@ Confirmed on my Frame, in the headset:
|
||||
- Launching from the Steam library into the game, title screen to race, at 90 Hz.
|
||||
- Racing in the original camera; the first-person cockpit, briefly; the menus at full rate; the
|
||||
VR settings panel; the controller layout in the table below, including pause and back.
|
||||
- The item box fix is in the build I play and I have not seen the box stuck since; it had the
|
||||
least testing of the five changes.
|
||||
- The release path itself: the APK from the Releases page installed with `install.sh`, the game
|
||||
built on the headset against its kit (about 13 minutes on my Frame), and launched from the
|
||||
library. That is what I play now. The first release (`v1.3.1-frame.1`) failed at the final link
|
||||
on the headset because its objects were compiled with the wrong NDK; see "Building" below.
|
||||
|
||||
Known problems and things I have not tested:
|
||||
|
||||
- **The release APK has not yet been through a clean run.** What I play is the same four objects,
|
||||
linked into `libmain.so` on the Frame by the AI's host-side build. The APK on the Releases page
|
||||
was made by `build.sh` from those objects and passes the same archive, alignment and signature
|
||||
checks as upstream's APK, but the full install, disc import and on-headset build with it is the
|
||||
next thing I do, and this line changes when it has happened. If you get there first, an issue
|
||||
with the build log would be welcome.
|
||||
- **The empty item box stays on screen** after the roulette slides up. The attempted fix (change 5)
|
||||
does not do it. It is cosmetic, so I live with it.
|
||||
- **The controllers drawn in the menus are Quest Touch controllers**, not the Frame's. Upstream
|
||||
draws SteamVR's own model of whatever controller you hold when SteamVR offers one and falls back
|
||||
to its built-in Touch Plus models otherwise; inside Lepton the app gets no SteamVR render models,
|
||||
so it always falls back. The buttons the tutorials point at are the Quest ones too; the table
|
||||
below is what the Frame buttons actually do.
|
||||
- **Grips do not grab the wheel in first person.** That is on purpose: on this layout the grips
|
||||
are hop and drift, and with upstream's hand steering on, squeezing a grip to drift also grabbed
|
||||
the wheel and steering jumped to your hand. So `hand_steering = false`, and you steer with the
|
||||
stick in every camera. If you would rather steer with your hands in the cockpit, set
|
||||
`hand_steering = true` under `[vr]` in `Config.toml` (path under "Where things are" below) and
|
||||
drift with the right bumper instead. I have not tried that combination on the Frame.
|
||||
- **Subtle slowdowns remain.** Races are a solid 90 on the counter, but there are brief hitches
|
||||
now and then that I notice and cannot pin to anything in the logs. Good enough that I play it;
|
||||
not perfect.
|
||||
@@ -135,7 +147,6 @@ Known problems and things I have not tested:
|
||||
looked better and dropped frames all over the place (10 to 24 ms late, 30 to 50 ms stalls), so
|
||||
it went back to 1.0x and 1728x1728, which is what the installer sets. If you want sharper menus,
|
||||
make the menu screen bigger or closer in VR settings instead.
|
||||
- **Item roulette** can still show a sliver while sliding.
|
||||
- **Not tested:** Retro Rewind, online play, the diorama camera for more than a minute, USB
|
||||
wheels, multiplayer, passthrough (there is nothing to pass through to; the Frame shows SteamVR's
|
||||
void around the menus), hand tracking.
|
||||
@@ -240,9 +251,11 @@ from another machine with developer mode on. Nothing needs root and nothing is i
|
||||
saved to `Config.toml`.
|
||||
|
||||
If you already had upstream's APK set up on the Frame under the same library entry name, step 2
|
||||
repoints that entry, and Lepton clears the old app's data on the next launch because the APK
|
||||
changed; the extracted disc files survive in `external/`, the built game does not, so you repeat
|
||||
the `BuildGame` command (not the extraction).
|
||||
repoints that entry. On the next launch Lepton reinstalls the app because the APK changed; the
|
||||
extracted disc files and the previously built game both survive, but that game was built against
|
||||
upstream's kit and the app will not run it (the launcher calls it stale, and the wrapper sees the
|
||||
fingerprint mismatch and leaves the launcher up). Run the `BuildGame` command from step 5; the
|
||||
extraction is not needed again.
|
||||
|
||||
## Building the APK yourself
|
||||
|
||||
@@ -255,17 +268,19 @@ On the Frame this takes about 20 seconds once the toolchain is there, and produc
|
||||
`steam-frame/release/WiiCompiled-VR-SteamFrame-<git tag>.apk`. It needs `curl`, `tar`, `python3`
|
||||
and `openssl`, all on SteamOS already, and no root. What it does:
|
||||
|
||||
1. Fetches an Android NDK into `~/.local/opt/android-build/` (Google ships no arm64 Linux NDK, so
|
||||
on the Frame it uses [HomuHomu833's r30 build](https://github.com/HomuHomu833/android-ndk-custom);
|
||||
on x86_64 it would use Google's r29, untried). If you have built
|
||||
[Moonlight XR for the Frame](https://github.com/simplysanatori/moonlight-android-xr-steam-frame) the
|
||||
NDK is already there.
|
||||
1. Fetches Android NDK r29 into `~/.local/opt/android-build/` (Google ships no arm64 Linux NDK, so
|
||||
on the Frame it uses [HomuHomu833's r29 build](https://github.com/HomuHomu833/android-ndk-custom);
|
||||
on x86_64 it would use Google's r29, untried). It has to be r29, the NDK the kit was built with:
|
||||
the first release APK was compiled with r30, whose libc++ headers emit a helper
|
||||
(`std::__hash_memory`) that r29's library lacks, and the headset's link failed on it. The build
|
||||
now also refuses any object that imports a C++ symbol the kit's original does not.
|
||||
2. Compiles the two runtime unity groups that contain the patched files and Aurora's `common.cpp`
|
||||
with the same flags upstream's Android build uses. The third-party headers those three units need
|
||||
are vendored in [`steam-frame/build-deps/`](steam-frame/build-deps/) (see its `NOTICE.md`),
|
||||
because upstream fetches its dependencies with CMake on Windows and this build runs no CMake.
|
||||
3. Extracts the matching objects from the upstream APK's kit and checks that each new object exports
|
||||
exactly the same symbols. If not, it stops: the sources and the APK are out of step.
|
||||
exactly the same symbols and imports no C++ symbol the original did not. If not, it stops: the
|
||||
sources and the APK are out of step, or the toolchain is wrong.
|
||||
4. `make-apk.py` rewrites the APK: the four kit files (`007_unity_runtime_4`, `001_unity_runtime_6`,
|
||||
`020_libaurora_gx.a` with its `common.cpp.o` swapped, and `include/vr/openxr_input.h`), the new
|
||||
kit fingerprint in `kit.json`, the patched `classes.dex`, zipalign-equivalent alignment, and an
|
||||
@@ -322,7 +337,12 @@ Over about a week in October 2026, in a series of Claude Code sessions on the Fr
|
||||
Lepton has no clock conversion extension and wrote the fallback, re-signed the APK, and the result
|
||||
was "very good". Then the controls, in code rather than only in the binding file because the
|
||||
game reads X and Y as the left hand's buttons. Then the menus, after the frame-timing diagnostics
|
||||
showed exactly which draw was slow. Then the item box.
|
||||
showed exactly which draw was slow. Then the item box, which did not work.
|
||||
- Publishing went wrong once too. The first release APK carried objects compiled with NDK r30,
|
||||
whose C++ headers call a helper that the kit's NDK r29 runtime does not have, so the headset's
|
||||
build got all the way to the last step and failed to link. The fix was to build with r29, plus a
|
||||
check in `build.sh` that refuses any object importing a C++ symbol the kit's original does not.
|
||||
The second release then built and ran on the headset from a clean start.
|
||||
- The last change before publishing was a mistake: raising the resolution for sharper menus,
|
||||
which I reported as a performance regression the next day and which was rolled back. The
|
||||
numbers in the "Resolution" note above are from that log.
|
||||
|
||||
Binary file not shown.
+23
-14
@@ -35,15 +35,18 @@ if ! echo "$UPSTREAM_APK_SHA256 $APK" | sha256sum -c --quiet 2>/dev/null; then
|
||||
echo "warning: $APK is not the upstream v1.3.1 APK this build was made for; carrying on, the symbol check below decides"
|
||||
fi
|
||||
|
||||
# 1. Toolchain. Google ships no arm64 Linux NDK; HomuHomu833's build of r30 fills in on the Frame.
|
||||
# 1. Toolchain: NDK r29, the one the kit itself was built with and the one the headset links with.
|
||||
# A newer NDK's libc++ headers emit helpers (std::__hash_memory in r30) that r29's libc++ does
|
||||
# not have, and the on-device link then fails. Google ships no arm64 Linux NDK; HomuHomu833's
|
||||
# build of r29 fills in on the Frame.
|
||||
ARCH=$(uname -m)
|
||||
if [ -n "${WCF_NDK:-}" ]; then
|
||||
NDK=$WCF_NDK
|
||||
elif [ "$ARCH" = aarch64 ]; then
|
||||
NDK=$TOOLS/ndk/android-ndk-r30
|
||||
NDK=$TOOLS/ndk/android-ndk-r29
|
||||
if [ ! -d "$NDK/toolchains/llvm/prebuilt" ]; then
|
||||
fetch https://github.com/HomuHomu833/android-ndk-custom/releases/download/r30/android-ndk-r30-aarch64-linux-gnu.tar.xz "$TOOLS/dl/ndk-r30-aarch64.tar.xz"
|
||||
mkdir -p "$TOOLS/ndk" && tar xJf "$TOOLS/dl/ndk-r30-aarch64.tar.xz" -C "$TOOLS/ndk"
|
||||
fetch https://github.com/HomuHomu833/android-ndk-custom/releases/download/r29/android-ndk-r29-aarch64-linux-gnu.tar.xz "$TOOLS/dl/ndk-r29-aarch64.tar.xz"
|
||||
mkdir -p "$TOOLS/ndk" && tar xJf "$TOOLS/dl/ndk-r29-aarch64.tar.xz" -C "$TOOLS/ndk"
|
||||
fi
|
||||
elif [ "$ARCH" = x86_64 ]; then
|
||||
NDK=$TOOLS/ndk/android-ndk-r29
|
||||
@@ -86,7 +89,7 @@ for entry in 007:runtime_4 001:runtime_6; do
|
||||
"$CXX" "${TARGET[@]}" "${RUNTIME_DEFS[@]}" "${RUNTIME_INC[@]}" "${COMMON[@]}" -include mkw_pch.h \
|
||||
-c "$OUT/obj/unity_${g}_cxx.cxx" -o "$OUT/kit/objects/${n}_unity_${g}_cxx.cxx.o" &
|
||||
done
|
||||
# fmt's format.h uses malloc without including <cstdlib> itself; NDK r30's libc++ no longer does it for it.
|
||||
# fmt's format.h uses malloc without including <cstdlib> itself; a newer libc++ no longer does it for it.
|
||||
"$CXX" "${TARGET[@]}" "${DEFS[@]}" "${AURORA_INC[@]}" "${COMMON[@]}" -include cstdlib \
|
||||
-c "$SRC/aurora-main/lib/gfx/common.cpp" -o "$OUT/obj/common.cpp.o" &
|
||||
wait
|
||||
@@ -98,20 +101,26 @@ unzip -q -o "$APK" 'assets/game_kit/objects/007_unity_runtime_4_cxx.cxx.o' 'asse
|
||||
'assets/game_kit/libs/020_libaurora_gx.a' 'assets/game_kit/include/vr/openxr_input.h' -d "$OUT/upstream-kit"
|
||||
UK=$OUT/upstream-kit/assets/game_kit
|
||||
exported() { "$NM" --defined-only "$1" | awk '$2 ~ /[TDBR]/ {print $3}' | sort -u; }
|
||||
imported() { "$NM" -u "$1" | awk '{print $2}' | sort -u; }
|
||||
ok=1
|
||||
for o in 007_unity_runtime_4_cxx.cxx.o 001_unity_runtime_6_cxx.cxx.o; do
|
||||
if diff <(exported "$UK/objects/$o") <(exported "$OUT/kit/objects/$o") >/dev/null; then
|
||||
echo "$o: exports the kit's symbols"
|
||||
# Same exports as the kit's object, and no new C++ imports: a new mangled import means a different
|
||||
# libc++ than the kit's, which the on-device link cannot satisfy. New plain C imports (bionic) are fine.
|
||||
check() { # label kit-object new-object
|
||||
if diff <(exported "$2") <(exported "$3") >/dev/null; then
|
||||
echo "$1: exports the kit's symbols"
|
||||
else
|
||||
echo "$o: exported symbols DIFFER from the kit's (sources out of step with this APK?)"; ok=0
|
||||
echo "$1: exported symbols DIFFER from the kit's (sources out of step with this APK?)"; ok=0
|
||||
fi
|
||||
new_cxx=$(comm -13 <(imported "$2") <(imported "$3") | grep '^_Z' || true)
|
||||
if [ -n "$new_cxx" ]; then
|
||||
echo "$1: imports C++ symbols the kit's object does not (wrong NDK/libc++?):"; echo "$new_cxx" | head -5; ok=0
|
||||
fi
|
||||
}
|
||||
for o in 007_unity_runtime_4_cxx.cxx.o 001_unity_runtime_6_cxx.cxx.o; do
|
||||
check "$o" "$UK/objects/$o" "$OUT/kit/objects/$o"
|
||||
done
|
||||
t=$(mktemp -d); (cd "$t" && "$AR" x "$UK/libs/020_libaurora_gx.a" common.cpp.o)
|
||||
if diff <(exported "$t/common.cpp.o") <(exported "$OUT/obj/common.cpp.o") >/dev/null; then
|
||||
echo "020_libaurora_gx.a(common.cpp.o): exports the kit's symbols"
|
||||
else
|
||||
echo "020_libaurora_gx.a(common.cpp.o): exported symbols DIFFER from the kit's"; ok=0
|
||||
fi
|
||||
check "020_libaurora_gx.a(common.cpp.o)" "$t/common.cpp.o" "$OUT/obj/common.cpp.o"
|
||||
rm -rf "$t"
|
||||
[ $ok = 1 ] || { echo "not making an APK from objects that do not match the kit"; exit 1; }
|
||||
cp "$UK/libs/020_libaurora_gx.a" "$OUT/kit/libs/020_libaurora_gx.a"
|
||||
|
||||
@@ -14,17 +14,33 @@ LOG=/tmp/wiicompiled-launch.log
|
||||
SAVED_LD_PRELOAD=$LD_PRELOAD
|
||||
unset LD_PRELOAD # Steam's overlay preload only spams errors for adb
|
||||
|
||||
GAME=
|
||||
READY= # set once a game built against this APK's kit is installed
|
||||
REASON="no STEAM_COMPAT_DATA_PATH"
|
||||
if [ -n "$STEAM_COMPAT_DATA_PATH" ]; then
|
||||
# Lepton mounts $STEAM_COMPAT_DATA_PATH/external as the container's shared storage and
|
||||
# internal/<package> as the app's private data.
|
||||
# internal/<package> as the app's private data. The latter survives an APK update, so a game
|
||||
# built against another kit can be sitting there: the app calls it stale and will not run it.
|
||||
CFG=$STEAM_COMPAT_DATA_PATH/external/Android/data/$PKG/files/WiiCompiledOpenXRVR
|
||||
if [ ! -f "$CFG/Config.toml" ] && [ -f "$HERE/Config.frame.toml" ]; then
|
||||
mkdir -p "$CFG"
|
||||
cp "$HERE/Config.frame.toml" "$CFG/Config.toml" && chmod 664 "$CFG/Config.toml"
|
||||
echo "$(date +%T) seeded $CFG/Config.toml with the Frame settings" >> "$LOG"
|
||||
fi
|
||||
GAME=$STEAM_COMPAT_DATA_PATH/internal/$PKG/files/game/base/libmain.so
|
||||
MANIFEST=$STEAM_COMPAT_DATA_PATH/internal/$PKG/files/game/base/game.json
|
||||
APK=$(ls "$HERE"/app/*.apk 2>/dev/null | head -1)
|
||||
if [ ! -f "$MANIFEST" ]; then
|
||||
REASON="no game built yet"
|
||||
elif [ -z "$APK" ]; then
|
||||
REASON="no APK next to this script to compare the kit with"
|
||||
else
|
||||
KIT=$(unzip -p "$APK" assets/game_kit/kit.json 2>/dev/null | python3 -c 'import json,sys; print(json.load(sys.stdin)["fingerprint"])' 2>/dev/null)
|
||||
BUILT=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("kitFingerprint", ""))' "$MANIFEST" 2>/dev/null)
|
||||
if [ -n "$KIT" ] && [ "$KIT" = "$BUILT" ]; then
|
||||
READY=1
|
||||
else
|
||||
REASON="the built game is for another kit (${BUILT:0:12} vs this APK's ${KIT:0:12}); it needs building again"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
(
|
||||
@@ -34,8 +50,8 @@ fi
|
||||
timeout 5 "$ADB" connect localhost:5555 >/dev/null 2>&1
|
||||
if timeout 5 "$ADB" -s localhost:5555 shell dumpsys window 2>/dev/null \
|
||||
| grep -q "mCurrentFocus=.*$PKG/.*LauncherActivity"; then
|
||||
if [ -n "$GAME" ] && [ ! -f "$GAME" ]; then
|
||||
echo "$(date +%T) no game built yet ($GAME missing): leaving the launcher up"
|
||||
if [ -z "$READY" ]; then
|
||||
echo "$(date +%T) leaving the launcher up: $REASON"
|
||||
exit 0
|
||||
fi
|
||||
if [ -n "$WCF_STAY_IN_LAUNCHER" ]; then
|
||||
|
||||
Reference in new issue
Block a user