Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ features:
<span>Allium</span>
<em class="supported-platforms__tag">experimental</em>
</div>
<div class="supported-platforms__item">
<img class="supported-platforms__logo--rounded" src="/darkos.png" alt="dArkOS logo">
<span>dArkOS</span>
</div>
</div>
</div>
</div>
Expand Down
6 changes: 6 additions & 0 deletions docs/linux-support/cfg-patching.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ Allium launches stock RetroArch directly, so the usual config is the one that ge

Allium has no RetroAchievements settings of its own, so nothing rewrites these keys behind the proxy: RetroArch's own achievements login is the single source of your account details.

== dArkOS

```text
/home/ark/.config/retroarch/retroarch.cfg
```

:::

## What Gets Patched
Expand Down
15 changes: 13 additions & 2 deletions docs/linux-support/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The Linux version is for handheld Linux devices where you want the same basic of
- Keep earning casual achievements while offline
- Let queued awards sync later when you reconnect

Use the `KNULLI`, `Onion`, `muOS`, `ROCKNIX`, `spruce`, and `Allium` tabs throughout the Linux section to switch target-specific instructions.
Use the `KNULLI`, `Onion`, `muOS`, `ROCKNIX`, `spruce`, `Allium`, and `dArkOS` tabs throughout the Linux section to switch target-specific instructions.

## Supported Targets

Expand All @@ -21,6 +21,7 @@ Use the `KNULLI`, `Onion`, `muOS`, `ROCKNIX`, `spruce`, and `Allium` tabs throug
- ROCKNIX
- spruce (experimental)
- Allium (experimental)
- dArkOS (community-contributed, unverified by the developer)

## Specifics

Expand Down Expand Up @@ -62,10 +63,20 @@ Allium support is experimental. It is currently compatible with [Allium v1.0.1](

Allium is a Miyoo-only launcher, so this target covers exactly the devices Allium itself runs on: the Miyoo Mini, Mini Plus and Mini Flip.

== dArkOS

Community-contributed target for [dArkOS](https://github.com/christianhaitian/dArkOS) ("Debian based ArkOS"). Not yet verified against real hardware by the developer — please report issues.

Current rough edges:

- dArkOS is systemd-native (no `custom.sh`-style startup hook), so autostart installs a systemd unit instead — this uses passwordless `sudo` for the device user (dArkOS's own Tools scripts rely on the same mechanism), and is silently skipped with an on-screen hint if that's not available
- `pygame` comes from `apt` rather than a bundled runtime; the installer installs it automatically via `sudo`, otherwise install it manually if the menu fails to launch
- No known equivalent of `batocera.conf`/`knulli.conf`, so only the RetroArch-side patch applies

:::

## Important Notes

- Linux support is currently in alpha and should still be treated as a prerelease feature.
- Linux install, startup, and UI behavior can vary a lot by firmware and frontend.
- Always check the correct tab for your device instead of assuming KNULLI, Onion, muOS, ROCKNIX, and spruce behave the same way.
- Always check the correct tab for your device instead of assuming KNULLI, Onion, muOS, ROCKNIX, spruce, Allium, and dArkOS behave the same way.
23 changes: 23 additions & 0 deletions docs/linux-support/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ Before using `RAOfflineProxy` on Linux:
- Make sure the system clock is correct so award timestamps are accurate<br>
> Settings -> Wi-Fi -> enable NTP

== dArkOS

- Enter your RetroAchievements account details in RetroArch
- The installer installs `python3-pygame` via `apt` automatically using passwordless `sudo`; if that's not available on your setup, install it manually: `sudo apt install -y python3-pygame`

:::

::: warning Emulator configs are patched automatically
Expand Down Expand Up @@ -168,6 +173,24 @@ RAOfflineProxy patches the emulator configs it needs in order to redirect RetroA
4. Start the proxy while online
5. Launch a game once so its data is cached

== dArkOS

> dArkOS support is community-contributed and not yet verified by the developer on real hardware.

1. Download the latest `RAOfflineProxy-DarkOS-*-Install.sh` from [GitHub Releases](https://github.com/misantronic/RAOfflineProxy/releases)
2. Copy it into:

```text
/roms/tools
```

3. Refresh or update gamelists so **RAOfflineProxy Install** appears in the **Tools** menu
4. Launch **RAOfflineProxy Install** from **Tools** — no SSH or root needed; it uses passwordless `sudo` for the few steps that need it (same as dArkOS's own Tools scripts)
5. Refresh or update gamelists again so the main **RAOfflineProxy** entry appears
6. Launch **RAOfflineProxy** from the **Tools** menu
7. Start the proxy while online
8. Cache games either from **Cached Games** then **Add ROM** or by launching them once in RetroArch

:::

See [Caching Games](/linux-support/caching-games) for more detail.
Expand Down
2 changes: 1 addition & 1 deletion docs/linux-support/pending-awards.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ From the user side, this means offline unlocks still register locally right away

Queued awards are sent oldest first. Duplicate pending awards are discarded. Authentication failures require refreshed login state. Network failures keep awards queued for later retry.

On KNULLI and muOS, pending awards can be reviewed directly from the on-device menu. Onion does not currently include a dedicated pending-awards browser yet.
On KNULLI, muOS, and dArkOS, pending awards can be reviewed directly from the on-device menu. Onion does not currently include a dedicated pending-awards browser yet.

## Anti-Tamper

Expand Down
12 changes: 12 additions & 0 deletions docs/linux-support/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ It is also skipped entirely while an Allium firmware update is pending, so the p
An Allium update rewrites `/.tmp_update` and removes the block. The app reinstalls it every time you open RAOfflineProxy, so autostart repairs itself after an Allium update.
:::

== dArkOS

dArkOS is systemd-native and has no `custom.sh`-style startup hook, so autostart works differently than on the other targets.

If you enable it, RAOfflineProxy installs (and permanently enables) a systemd unit:

```text
/etc/systemd/system/raofflineproxy-autostart.service
```

That unit always runs the proxy's boot-reconcile step on boot; enabling/disabling autostart from the menu just flips a config flag that boot-reconcile checks, so toggling it doesn't need `sudo` once the unit is installed. Installing the unit itself is done automatically during install using passwordless `sudo` for the device user (the same mechanism dArkOS's own Tools scripts use). If that's not available on your setup, the installer prints an on-screen message instead of failing.

:::

## Start / Stop Behavior
Expand Down
38 changes: 38 additions & 0 deletions linux/darkos/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# dArkOS Bundle

Portable bundle for [dArkOS](https://github.com/christianhaitian/dArkOS) ("Debian based ArkOS").

## Status

Community-contributed: a dArkOS user got RAOfflineProxy running by hand-patching the KNULLI bundle's install/uninstall/launcher scripts (`/userdata/roms` → `/roms`, `/userdata/system` → `/home/ark`). This directory formalizes that into a proper installer, and the shared `raofflineproxy/` Python source now recognizes `/home/ark` as an alternate platform root. **Not yet verified against real hardware by the maintainer** — please report issues.

## Differences from the KNULLI bundle

- Install root: `/home/ark/raofflineproxy` (KNULLI: `/userdata/system/raofflineproxy`)
- ROMs root: `/roms` (KNULLI: `/userdata/roms`); Tools entries live under `/roms/tools`, bind-mounted to `/opt/system/Tools`
- dArkOS is systemd-native and has no `custom.sh`-style boot hook, so autostart installs a systemd unit at `/etc/systemd/system/raofflineproxy-autostart.service` instead. **ES Tools-menu scripts run unprivileged on dArkOS (confirmed: they never run as root, even on handhelds)**, so this — and the `python3-pygame` install below — go through `sudo -n` (non-interactive `sudo`), relying on passwordless sudo for the device user. That's the same mechanism dArkOS's own Tools scripts use (e.g. `Enable Remote Services.sh` calls `sudo systemctl ...` non-interactively from the same context). `-n` makes sudo fail fast instead of hanging if that assumption is ever wrong, in which case the installer prints an on-screen message and continues; nothing else is affected (proxy start/stop, caching, awards, uninstall never needed root to begin with).
- dArkOS ships `apt`, so pygame comes from the system package (`python3-pygame`) rather than a vendored runtime like the KNULLI/muOS bundles use. `install.sh` installs it automatically via `sudo -n apt install`; if that fails, it prints `sudo apt install -y python3-pygame` and continues. (`apt-get` doesn't work reliably on dArkOS — confirmed by community testing — so the script uses `apt` specifically.)
- No `batocera.conf`/`knulli.conf` equivalent was found in dArkOS, so only the RetroArch-side RA host patch applies; there's no known ES-side achievements-screen override to patch yet.
- In-app "Install Update" now fetches the `*DarkOS*.sh` release asset instead of the KNULLI one.

## Build

From repo root:

```bash
./linux/darkos/build_bundle.sh
```

This creates `linux/darkos/dist/RAOfflineProxy-DarkOS-v<VER>-Install.sh`.

## Install

Copy the installer to `/roms/tools` on the device and run it from the EmulationStation Tools menu — no SSH or root session needed. It can also be run directly:

```bash
bash "RAOfflineProxy-DarkOS-v<VER>-Install.sh"
```

## Uninstall

Use `Uninstall` inside the RAOfflineProxy menu, or run `/home/ark/raofflineproxy/bin/raofflineproxy-uninstall` directly.
88 changes: 88 additions & 0 deletions linux/darkos/build_bundle.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LINUX_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
DIST_DIR="${SCRIPT_DIR}/dist"
BUILD_DIR="${DIST_DIR}/raofflineproxy-darkos-bundle"
APP_DIR="${BUILD_DIR}/app"
LIB_DIR="${BUILD_DIR}/lib"
INSTALLER_PATH="${DIST_DIR}/RAOfflineProxy-DarkOS-v1.12.0-alpha1-Install.sh"
TEMP_TARBALL="${DIST_DIR}/.raofflineproxy-darkos-bundle.tar.gz"

TARGET="aarch64-linux-gnu.2.17" OUT_DIR="${SCRIPT_DIR}/native" \
"${LINUX_DIR}/build_rchash.sh"

rm -rf "${BUILD_DIR}"
rm -f "${DIST_DIR}/raofflineproxy-darkos-bundle.tar.gz"
mkdir -p "${APP_DIR}"
mkdir -p "${LIB_DIR}"

export COPYFILE_DISABLE=1

cp -r "${LINUX_DIR}/raofflineproxy" "${APP_DIR}/raofflineproxy"
cp "${LINUX_DIR}/requirements.txt" "${APP_DIR}/requirements.txt"
cp "${LINUX_DIR}/../docs/public/logo-320.png" "${APP_DIR}/raofflineproxy/logo-320.png"
cp -r "${SCRIPT_DIR}/scripts" "${BUILD_DIR}/scripts"
cp "${SCRIPT_DIR}/native/libraproxy_rchash.so" "${LIB_DIR}/libraproxy_rchash.so"
cp "${SCRIPT_DIR}/scripts/install.sh" "${BUILD_DIR}/install.sh"
cp "${SCRIPT_DIR}/scripts/uninstall.sh" "${BUILD_DIR}/uninstall.sh"

find "${APP_DIR}" -name "__pycache__" -type d -prune -exec rm -rf {} +
find "${APP_DIR}" -name "*.pyc" -delete

chmod +x "${BUILD_DIR}/install.sh"
chmod +x "${BUILD_DIR}/uninstall.sh"
chmod +x "${BUILD_DIR}/scripts/launcher-raofflineproxy"
chmod +x "${BUILD_DIR}/scripts/launcher-raofflineproxy-uninstall"

mkdir -p "${DIST_DIR}"
rm -f "${TEMP_TARBALL}"
tar -czf "${TEMP_TARBALL}" -C "${DIST_DIR}" "raofflineproxy-darkos-bundle"

cat > "${INSTALLER_PATH}" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_PATH="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")"
PAYLOAD_MARKER="__RAOFFLINEPROXY_PAYLOAD_BELOW__"
TARGET_DIR="/home/ark/raofflineproxy-darkos-bundle"
TOOLS_INSTALL_SCRIPT="/roms/tools/RAOfflineProxy Install.sh"

marker_line="$(awk -v marker="${PAYLOAD_MARKER}" '$0 == marker { print NR; exit }' "${SCRIPT_PATH}")"
if [ -z "${marker_line}" ]; then
echo "Installer payload marker not found."
exit 1
fi

payload_line=$((marker_line + 1))
rm -rf "${TARGET_DIR}"
mkdir -p "/home/ark"

PAYLOAD_TARBALL="$(mktemp "/home/ark/.raofflineproxy-payload.XXXXXX")"
trap 'rm -f "${PAYLOAD_TARBALL}"' EXIT
tail -n +"${payload_line}" "${SCRIPT_PATH}" | base64 -d > "${PAYLOAD_TARBALL}"
tar -xzf "${PAYLOAD_TARBALL}" -C "/home/ark" --no-same-owner

SENTINEL="${TARGET_DIR}/app/raofflineproxy/main.py"
if [ ! -f "${SENTINEL}" ]; then
echo "Installer payload did not extract correctly (missing ${SENTINEL})."
echo "Your device's base64/tar may have truncated the payload."
exit 1
fi

cd "${TARGET_DIR}"
./install.sh
rm -f "${TOOLS_INSTALL_SCRIPT}" "${SCRIPT_PATH}"
echo "RAOfflineProxy installed."
exit 0
__RAOFFLINEPROXY_PAYLOAD_BELOW__
EOF

base64 < "${TEMP_TARBALL}" | fold -w 76 >> "${INSTALLER_PATH}"
chmod +x "${INSTALLER_PATH}"

rm -f "${TEMP_TARBALL}"
rm -rf "${BUILD_DIR}"

echo "Created ${INSTALLER_PATH}"
113 changes: 113 additions & 0 deletions linux/darkos/scripts/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BASE_DIR="/home/ark/raofflineproxy"
APP_DIR="${BASE_DIR}/app"
BIN_DIR="${BASE_DIR}/bin"
LIB_DIR="${BASE_DIR}/lib"
TOOLS_DIR="/roms/tools"
INSTALL_SCRIPT="${TOOLS_DIR}/RAOfflineProxy Install.sh"
OLD_BIN="${BASE_DIR}/bin/raofflineproxy"
UPDATE_STATUS_FILE="/home/ark/.config/raofflineproxy/update_status.json"
AUTOSTART_UNIT="/etc/systemd/system/raofflineproxy-autostart.service"
FB_WIDTH=0
FB_HEIGHT=0
WAS_RUNNING=0
RESTARTED=0

if [ -r /sys/class/graphics/fb0/virtual_size ]; then
IFS=, read -r FB_WIDTH FB_HEIGHT < /sys/class/graphics/fb0/virtual_size || true
fi

mkdir -p "${APP_DIR}"
mkdir -p "${BIN_DIR}"
mkdir -p "${LIB_DIR}"
mkdir -p "${TOOLS_DIR}"

if [ -x "${OLD_BIN}" ]; then
if status_output="$(${OLD_BIN} status 2>/dev/null)" && printf '%s' "${status_output}" | grep -q 'running: yes'; then
WAS_RUNNING=1
fi
${OLD_BIN} stop-proxy >/dev/null 2>&1 || true
fi

cp -r "${SCRIPT_DIR}/app/"* "${APP_DIR}/"
cp -r "${SCRIPT_DIR}/lib/"* "${LIB_DIR}/"

cp "${SCRIPT_DIR}/scripts/launcher-raofflineproxy" "${BIN_DIR}/raofflineproxy"
cp "${SCRIPT_DIR}/scripts/launcher-raofflineproxy-uninstall" "${BIN_DIR}/raofflineproxy-uninstall"

rm -f "${UPDATE_STATUS_FILE}"

chmod +x "${BIN_DIR}/raofflineproxy"
chmod +x "${BIN_DIR}/raofflineproxy-uninstall"

# ES Tools-menu scripts run unprivileged on dArkOS, so we rely on
# passwordless sudo for the device user (the same mechanism dArkOS's own
# Tools scripts use, e.g. "Enable Remote Services.sh" calling
# `sudo systemctl ...` non-interactively). `sudo -n` fails fast instead of
# hanging on a password prompt if that assumption doesn't hold here.
if ! /usr/bin/python3 -c "import pygame" >/dev/null 2>&1; then
if command -v apt >/dev/null 2>&1 && sudo -n apt install -y python3-pygame >/dev/null 2>&1; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There must be sudo apt update before sudo apt install ... By default, after a fresh dArkOS install, a package list for apt is empty. If sudo apt update is not executed, sudo apt install any_package will return an error that a package is not found.

I tested it today on RGB30 and dArkOS 07282026. I was able to build the installer locally on my Fedora desktop (had to do ZIG_BIN=/usr/bin/zig ./build_bundle.sh ). After installation RAOfflineProxy didn't work, because pygame was missing due to what I wrote about apt above. After manual sudo apt update and sudo apt install python3-pygame RAOfflineProxy launches fine from the Tools menu.

I haven't had a chance to test autolaunch and actual work tho. At this point I can only confirm the app launches if package list was refreshed manually.

@xxblx xxblx Aug 26, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There must be sudo apt update before sudo apt install ...

Like this

echo "python3-pygame installed."
else
echo "pygame is not installed for python3 -- the RAOfflineProxy menu needs it."
echo "Run: sudo apt install -y python3-pygame"
fi
fi

# Installs and enables the systemd boot-reconcile unit once; toggling
# autostart afterwards only flips a config flag (no sudo needed at runtime).
"${BIN_DIR}/raofflineproxy" ensure-boot-hook >/dev/null 2>&1 || true
if [ -f "${AUTOSTART_UNIT}" ]; then
echo "Autostart unit installed (${AUTOSTART_UNIT})."
else
echo "Could not install the autostart unit -- needs passwordless sudo for $(whoami)."
echo "Enable autostart from the menu once sudo access is available."
fi

if [ "${WAS_RUNNING}" -eq 1 ]; then
if "${BIN_DIR}/raofflineproxy" start-proxy >/dev/null 2>&1; then
RESTARTED=1
fi
fi

cat > "${TOOLS_DIR}/RAOfflineProxy.sh" <<'EOF'
#!/bin/sh
exec /home/ark/raofflineproxy/bin/raofflineproxy menu
EOF
chmod +x "${TOOLS_DIR}/RAOfflineProxy.sh"

rm -f "${INSTALL_SCRIPT}"

cat > "${BASE_DIR}/ui-state.txt" <<'EOF'
RAOfflineProxy Install

dArkOS bundle installed.
Please Update Gamelists.
EOF

if [ "${RESTARTED}" -eq 1 ]; then
cat >> "${BASE_DIR}/ui-state.txt" <<'EOF'

Proxy restarted to apply update.
EOF
fi

"${BIN_DIR}/raofflineproxy" text-image --output "${BASE_DIR}/ui-state.bmp" --text "$(cat "${BASE_DIR}/ui-state.txt")" --image-width "${FB_WIDTH}" --image-height "${FB_HEIGHT}" --font-scale 2 >/dev/null 2>&1 || true

if command -v fbv >/dev/null 2>&1; then
/bin/sh -c '
fbv -i -c -u "$1" >/dev/null 2>&1 &
viewer_pid=$!
sleep 6
kill "$viewer_pid" >/dev/null 2>&1 || true
' sh "${BASE_DIR}/ui-state.bmp" >/dev/null 2>&1 || true
fi

echo "RAOfflineProxy dArkOS bundle installed."
if [ "${RESTARTED}" -eq 1 ]; then
echo "Proxy restarted to apply update."
fi
echo "Please Update Gamelists."
14 changes: 14 additions & 0 deletions linux/darkos/scripts/launcher-raofflineproxy
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -euo pipefail

BASE_DIR="/home/ark/raofflineproxy"
PYTHONPATH_ENTRIES="${BASE_DIR}/app"

export HOME="/home/ark"
export XDG_CONFIG_HOME="/home/ark/.config"
export PATH="/usr/local/bin:/usr/bin:/bin:/home/ark/raofflineproxy/bin:${PATH:-}"
export LD_LIBRARY_PATH="${BASE_DIR}/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"

export PYTHONPATH="${PYTHONPATH_ENTRIES}${PYTHONPATH:+:${PYTHONPATH}}"

exec /usr/bin/python3 -m raofflineproxy.main "$@"
4 changes: 4 additions & 0 deletions linux/darkos/scripts/launcher-raofflineproxy-uninstall
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail

exec /home/ark/raofflineproxy-darkos-bundle/uninstall.sh
Loading