diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 65e8184..5ca025b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,6 +40,8 @@ jobs: archive: FramePort-macos-arm64.zip - os: ubuntu-22.04 # oldest supported: the bundle needs the build host's GLib/glibc or newer archive: FramePort-linux-x64.tar.gz + - os: ubuntu-22.04-arm # Linux ARM64 (same base as x64) + archive: FramePort-linux-arm64.tar.gz runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v7 @@ -50,9 +52,9 @@ jobs: - run: uv sync --extra dev - id: flet run: uv run python scripts/package.py - continue-on-error: ${{ runner.os == 'macOS' }} - - name: macOS fallback (PyInstaller bundle) - if: runner.os == 'macOS' && steps.flet.outcome == 'failure' + continue-on-error: ${{ runner.os == 'macOS' || runner.arch == 'ARM64' }} + - name: macOS / Linux ARM64 fallback (PyInstaller bundle) + if: (runner.os == 'macOS' || runner.arch == 'ARM64') && steps.flet.outcome == 'failure' run: uv run --with pyinstaller python scripts/package.py --pyinstaller # ---- signing: self-signed certificate on Windows, ad-hoc on macOS ---- @@ -95,7 +97,9 @@ jobs: run: ditto -c -k --keepParent "$(find dist -maxdepth 3 -name '*.app' -type d | head -1)" ${{ matrix.archive }} - name: Package (Linux) if: runner.os == 'Linux' - run: tar czf ${{ matrix.archive }} -C dist --transform 's,^linux,FramePort,' linux + run: | # flet build -> dist/linux; the PyInstaller fallback -> dist/FramePort (one folder) + if [ -d dist/linux ]; then tar czf ${{ matrix.archive }} -C dist --transform 's,^linux,FramePort,' linux + else tar czf ${{ matrix.archive }} -C dist FramePort; fi # self-update with this archive: unpack, verify the layout (+ the signer on Windows), run the real swap script - name: Update smoke test run: uv run python scripts/update_smoke.py ${{ matrix.archive }} @@ -137,7 +141,7 @@ jobs: git fetch --force origin "refs/tags/${tag}:refs/tags/${tag}" whatsnew=$(git tag -l --format='%(contents:body)' "$tag") [ -n "$whatsnew" ] || whatsnew=$(git tag -l --format='%(contents:subject)' "$tag") - { echo "FramePort ${tag}: Windows (x64), macOS (Apple Silicon) and Linux (x64)."; echo; + { echo "FramePort ${tag}: Windows (x64), macOS (Apple Silicon) and Linux (x64, ARM64)."; echo; if [ -n "$whatsnew" ] && [ "$(git cat-file -t "$tag")" = tag ]; then echo "## What's new"; echo; echo "$whatsnew"; echo; fi echo "Already have FramePort? It offers this update itself (Library → **Update now**)." diff --git a/README.md b/README.md index daaac65..68bac3e 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,8 @@ fix, so Steam's on-screen keyboard and Type on Frame work in them too. If a game has already been tested with FramePort, it will automatically use the optimal game config. Otherwise, FramePort will attempt to guess key patches. If you find a new config that works for an app you are testing, please consider submitting it to the community! +**[List of tested games](docs/GAMES.md)** + ## Built on [OVRPort](https://github.com/Android-XR-Bridge/OVRPort) (Quest → OpenXR, originally @@ -81,6 +83,7 @@ What FramePort adds itself: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). | [INSTALL.md](docs/INSTALL.md) | Install, connect, update, PC VR, files, problem reports | | [FRAME_SETUP.md](docs/FRAME_SETUP.md) | What setup changes on the Frame, networks and firewalls, undoing it | | [COMPATIBILITY.md](docs/COMPATIBILITY.md) | What runs and how well | +| [GAMES.md](docs/GAMES.md) | Tested games and how well they run | | [PLAYBOOK.md](docs/PLAYBOOK.md) | Symptoms and fixes per game | | [FRAME_RUNTIME.md](docs/FRAME_RUNTIME.md) | Steam Frame runtime facts | | [ARCHITECTURE.md](docs/ARCHITECTURE.md) | How the code is organised | diff --git a/docs/COMPATIBILITY.md b/docs/COMPATIBILITY.md index 4ae0101..fe311ad 100644 --- a/docs/COMPATIBILITY.md +++ b/docs/COMPATIBILITY.md @@ -1,7 +1,7 @@ # Compatibility The built-in catalog has tested settings for games, each marked as working, working with known issues, or not -running on the Frame (the recipes are in [catalog/games](../catalog/games)). +running on the Frame: see the [list of tested games](GAMES.md) (recipes in [catalog/games](../catalog/games)). Other games get suggested patches from detection rules; each suggestion states its reason, and every patch can be switched on or off under **Customize**: described in plain words, with **Show technical details** for the exact effect of each patch. diff --git a/docs/GAMES.md b/docs/GAMES.md new file mode 100644 index 0000000..96328fd --- /dev/null +++ b/docs/GAMES.md @@ -0,0 +1,46 @@ +# Tested games + +Games tested on the Steam Frame with FramePort's recipes. Games not listed here may work too: FramePort suggests patches for them, and a working config can be shared from the app (**Share working config…**). +Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`. + +| Game | Platform | Status | Notes | +|---|---|---|---| +| 4XVR Video Player | Quest | ✅ Works | | +| Asgard's Wrath 2 | Quest | ✅ Works | | +| BAM | Quest | ✅ Works | | +| BARTENDER VR SIMULATOR | Quest | ✅ Works | | +| Batman: Arkham Shadow | Quest | ✅ Works | | +| Carve Snowboarding | Quest | ✅ Works | | +| Demeter | Quest | ✅ Works | | +| Espire 2 | Quest | ✅ Works | | +| Genotype | Quest | ✅ Works | | +| H.U.N.T | Quest | ✅ Works | | +| In Death: Unchained | Quest | ✅ Works | | +| LEGO® Bricktales | Quest | ✅ Works | | +| Marvel's Deadpool VR | Quest | ✅ Works | | +| Medieval Dynasty New Settlement | Quest | ✅ Works | | +| Mobile Suit Gundam: Silver Phantom | Quest | ✅ Works | | +| Nano | Quest | ✅ Works | | +| NEX Player | Quest | ✅ Works | | +| NOPE CHALLENGE | Quest | ✅ Works | | +| Path of the Warrior | Quest | ✅ Works | | +| PowerWash Simulator VR | Quest | ✅ Works | | +| Rick and Morty: Virtual Rick-ality | PC VR | ✅ Works | | +| Robo Recall | Quest | ✅ Works | | +| Sniper Elite VR: Winter Warrior | Quest | ✅ Works | | +| Stremio | Quest | ✅ Works | | +| The Climb 2 | Quest | ✅ Works | | +| Toy Master | Quest | ✅ Works | | +| Under Cover | Quest | ✅ Works | | +| Wallace & Gromit in The Grand Getaway | Quest | ✅ Works | | +| Arcsmith | Quest | ⚠️ Works with issues | Right eye distorts during movement (unresolved; swap, tracking, Valve layers, depth and pacing ruled out). | +| Assassin's Creed Nexus | Quest | ⚠️ Works with issues | Some launch warning text is still upside down; the rest of the UI is fixed by flip emulation. | +| Phantom: Covert Ops | Quest | ⚠️ Works with issues | DLC/store button crashes (no Meta store). | +| Silhouette | Quest | ⚠️ Works with issues | Hand-tracking game; the Frame synthesizes hands from controllers, so it is janky. | +| Time Stall | Quest | ⚠️ Works with issues | Both eyes distort during movement (unresolved). | +| Espire 1: VR Operative (Quest Edition) | Quest | ❌ Doesn't run | Mesa GL driver crash during texture upload. | +| HITMAN 3 VR: Reloaded | Quest | ❌ Doesn't run | Vulkan driver crash (freedreno), even without Valve layers. | +| Journey of the Gods | Quest | ❌ Doesn't run | 32-bit only; the Frame has no AArch32. | +| Shadow Point | Quest | ❌ Doesn't run | 32-bit only; the Frame has no AArch32. | +| Sniper Elite VR | Quest | ❌ Doesn't run | GPU hang (zink: DEVICE LOST) even with MSAA off. | +| Sports Scramble (Santa Cruz) | Quest | ❌ Doesn't run | 32-bit only; the Frame has no AArch32. | diff --git a/docs/INSTALL.md b/docs/INSTALL.md index ecbf68d..a088ad3 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -9,6 +9,7 @@ runtime, the OVRPort CLI and apksigner into its data folder (Settings → Tools | Windows 10/11 (x64) | `FramePort-windows-x64.zip` | `FramePort.exe` | | macOS (Apple Silicon) | `FramePort-macos-arm64.zip` | `FramePort.app` | | Linux (x64, GTK 3; Ubuntu 22.04 or newer) | `FramePort-linux-x64.tar.gz` | `FramePort/FramePort` | +| Linux (ARM64, GTK 3; Ubuntu 22.04 or newer) | `FramePort-linux-arm64.tar.gz` | `FramePort/FramePort` | | Command line only (Python 3.11+) | `frameport--py3-none-any.whl` | `frameport --help` | The command-line version installs from the wheel's release link with `uv tool install ` (or pipx / pip). @@ -22,7 +23,7 @@ start shows a warning: `FramePort-selfsigned.cer` (attached to each release) into *Trusted Root Certification Authorities* (Current User) to show FramePort as the publisher; the certificate can only sign code. Remove it with `certmgr.msc`. - **macOS:** right-click `FramePort.app` → **Open** → **Open** (once), or `xattr -dr com.apple.quarantine FramePort.app`. -- **Linux:** `tar xzf FramePort-linux-x64.tar.gz && ./FramePort/FramePort`. +- **Linux:** `tar xzf FramePort-linux-x64.tar.gz && ./FramePort/FramePort` (ARM64: `FramePort-linux-arm64.tar.gz`). ## Connecting the Steam Frame diff --git a/packaging/release-footer.md b/packaging/release-footer.md index b41606f..abffb93 100644 --- a/packaging/release-footer.md +++ b/packaging/release-footer.md @@ -2,7 +2,7 @@ - **Windows:** unzip `FramePort-windows-x64.zip`, run `FramePort.exe` (warning → **More info → Run anyway**). - **macOS:** unzip `FramePort-macos-arm64.zip`, right-click `FramePort.app` → **Open**. -- **Linux:** `tar xzf FramePort-linux-x64.tar.gz && ./FramePort/FramePort`. +- **Linux:** `tar xzf FramePort-linux-x64.tar.gz && ./FramePort/FramePort` (ARM64: `FramePort-linux-arm64.tar.gz`). Full guide: [docs/INSTALL.md](https://github.com/spoopyghosty0/frameport/blob/main/docs/INSTALL.md) · checksums in `SHA256SUMS.txt`. diff --git a/scripts/compat_list.py b/scripts/compat_list.py new file mode 100644 index 0000000..c3ed9ac --- /dev/null +++ b/scripts/compat_list.py @@ -0,0 +1,42 @@ +"""Write docs/GAMES.md: the tested games from the bundled catalog as a simple table to share. +Run after catalog changes: python scripts/compat_list.py""" +from __future__ import annotations + +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "src")) + +from frameport.recommend import catalog # noqa: E402 + +STATUS = {"works": "✅ Works", "issues": "⚠️ Works with issues", "unsupported": "❌ Doesn't run"} +ORDER = {"works": 0, "issues": 1, "unsupported": 2} + + +def short(note: str) -> str: + """The note's first sentence, without technical asides.""" + first = re.split(r"(?<=[.!?])\s", " ".join((note or "").split()), maxsplit=1)[0] + return first if len(first) <= 160 else first[:157].rstrip() + "…" + + +def render() -> str: + entries = [e for e in catalog.load().values() if e.origin == "bundled" and e.status in STATUS] + entries.sort(key=lambda e: (ORDER[e.status], e.title.lower())) + rows = ["| Game | Platform | Status | Notes |", "|---|---|---|---|"] + for e in entries: + platform = "PC VR" if e.kind == "rift" else "Quest" + note = "" if e.status == "works" else short(e.notes).replace("|", "/") + rows.append(f"| {e.title} | {platform} | {STATUS[e.status]} | {note} |") + return ("# Tested games\n\n" + "Games tested on the Steam Frame with FramePort's recipes. Games not listed here may work too: FramePort " + "suggests patches for them, and a working config can be shared from the app (**Share working config…**).\n" + "Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.\n\n" + + "\n".join(rows) + "\n") + + +if __name__ == "__main__": + out = ROOT / "docs" / "GAMES.md" + out.write_text(render(), encoding="utf-8") + print(f"wrote {out}") diff --git a/scripts/package.py b/scripts/package.py index b9790c2..246645e 100644 --- a/scripts/package.py +++ b/scripts/package.py @@ -70,6 +70,8 @@ def main() -> int: # translations (data files PyInstaller doesn't pick up from the imports) "--add-data", f"{ROOT / 'src/frameport/locales'}{sep}frameport/locales", "--distpath", str(ROOT / "dist"), "--yes"] + if TARGET == "linux": + cmd.append("--onedir") # a folder: the bundle check sees the agent source, the archive the app else: # --yes: install the Flutter SDK etc. without prompting; --no-rich-output: plain logs (CI, Windows consoles) cmd = ["flet", "build", TARGET, str(ROOT), "--project", "FramePort", "--product", "FramePort", diff --git a/src/frameport/_version.py b/src/frameport/_version.py index 3ba3159..aee8ee2 100644 --- a/src/frameport/_version.py +++ b/src/frameport/_version.py @@ -1,2 +1,2 @@ # The FramePort version: the single source (pyproject reads it via hatch; CI checks a release tag matches it). -__version__ = "0.6.0" +__version__ = "0.6.1" diff --git a/src/frameport/updates.py b/src/frameport/updates.py index 18e4f7c..361e729 100644 --- a/src/frameport/updates.py +++ b/src/frameport/updates.py @@ -18,6 +18,7 @@ import hashlib import json import logging import os +import platform import re import shutil import subprocess @@ -39,7 +40,7 @@ REPO = REPO_URL.removeprefix("https://github.com/").strip("/") LATEST_API = f"https://api.github.com/repos/{REPO}/releases/latest" CHECK_EVERY = 6 * 3600 # seconds between automatic checks ASSETS = {"win32": "FramePort-windows-x64.zip", "darwin": "FramePort-macos-arm64.zip", - "linux": "FramePort-linux-x64.tar.gz"} + "linux": "FramePort-linux-x64.tar.gz", "linux-arm64": "FramePort-linux-arm64.tar.gz"} # never rename SUMS = "SHA256SUMS.txt" @@ -75,6 +76,8 @@ def is_newer(candidate: str, current: str = __version__) -> bool: def platform_asset() -> str | None: key = "linux" if sys.platform.startswith("linux") else sys.platform + if key == "linux" and platform.machine().lower() in ("aarch64", "arm64"): + key = "linux-arm64" # the ARM64 bundle (from 0.6.0) return ASSETS.get(key) diff --git a/tests/test_updates.py b/tests/test_updates.py index 499084d..9168ece 100644 --- a/tests/test_updates.py +++ b/tests/test_updates.py @@ -246,3 +246,11 @@ def test_apply_reports_a_script_that_never_starts(monkeypatch, tmp_path): monkeypatch.setattr(updates.subprocess, "Popen", lambda *a, **k: type("P", (), {"poll": lambda self: None})()) with pytest.raises(updates.UpdateError): updates.apply(staged, installed, relaunch=False, pid=1, platform="linux") + + +def test_platform_asset_picks_the_arm64_linux_bundle(monkeypatch): + monkeypatch.setattr(updates.sys, "platform", "linux") + monkeypatch.setattr(updates.platform, "machine", lambda: "aarch64") + assert updates.platform_asset() == "FramePort-linux-arm64.tar.gz" + monkeypatch.setattr(updates.platform, "machine", lambda: "x86_64") + assert updates.platform_asset() == "FramePort-linux-x64.tar.gz"