Switch Emulation setup guide for Rocknix on Ayn Thor
Because ROCKNIX doesn't include a Switch emulator, the "Switch" category won't even show up in your menus by default. We have to add it manually. Open /storage/.config/emulationstation/es_systems.cfg in a text editor. Scroll to the very bottom, right before the final </systemList> tag, and paste this block:
<system>
<name>switch</name>
<fullname>Nintendo Switch</fullname>
<path>/storage/roms/switch</path>
<extension>.nsp .NSP .xci .XCI .nro .NRO .nca .NCA</extension>
<command>/storage/roms/ports/Ryubing.sh %ROM%</command>
<platform>switch</platform>
<theme>switch</theme>
</system>
Next you'll need the actual emulator. Grab the latest Ryubing.AppImage (make sure it's the ARM64/AArch64 build for the Thor). Create a new folder on your handheld at /storage/ryubing/ and drop the AppImage file in there.
If you launch the emulator now and try to install firmware, your device will completely freeze. We have to do the installation on a PC first to bypass it. Install the normal Ryujinx emulator on your Windows/Mac/Linux PC. Open it, go to File -> Open Ryujinx Folder, and drop your prod.keys and title.keys inside the system folder. Then go to Tools -> Install Firmware -> Install from XCI or ZIP and select your firmware zip file. Once your PC shows the firmware version at the bottom right, the decryption is done and you can close the emulator.
Copy the keys from your PC's Ryujinx system folder into /storage/.config/Ryujinx/system/ on the handheld. Then copy the entire bis/system/Contents/registered/ folder from your PC and overwrite the one at /storage/.config/Ryujinx/bis/system/Contents/registered/ on your handheld. This bypasses the broken file picker entirely and permanently installs your firmware.
Finally, we need the script that EmulationStation uses to actually launch the games:
#!/bin/bash
sysctl -w vm.max_map_count=1048576
. /etc/profile
RYUBING_DIR="/storage/ryubing"
RYUBING_BIN="${RYUBING_DIR}/Ryubing.AppImage"
RYUBING_CONFIG="/storage/.config/ryubing"
RYUBING_DATA="/storage/.local/share/ryubing"
RYUBING_CACHE="/storage/.cache"
RYUBING_LOG="${RYUBING_CACHE}/ryubing_launcher.log"
MOUSE_SCRIPT="${RYUBING_DIR}/ryubing_mouse_layer.py"
QT_CONFIG="${RYUBING_CONFIG}/qt-config.ini"
MAX_ATTEMPTS=3
STARTUP_GRACE=8
RETRY_WAIT=3
GPU_TIMEOUT=10
CONTROLLER_RETRIES=4
MIN_MEM_MB=2048
CLEANUP_TERM_WAIT=3
MOUSE_DETECT_TIMEOUT=3
WM_POLL_INTERVAL=0.5
WM_MAX_FAILURES=20
CRASH_TIME_THRESHOLD=120
RYUBING_EMU_LOG="/storage/.local/share/ryubing/log/ryubing_log.txt"
TOUCHPAD_ID="1356:3302:Sony_Interactive_Entertainment_DualSense_Wireless_Controller_Touchpad"
MOUSE_ID="17229:1:Ryubing_Home_Mouse_Layer"
DISPLAY_OUTPUT="DSI-2"
_log() {
local level="$1"; shift
printf '[%s] [%-5s] %s\n' "$(date '+%H:%M:%S.%3N')" "$level" "$*" | tee -a "$RYUBING_LOG" >&2
}
info() { _log INFO "$@"; }
warn() { _log WARN "$@"; }
err() { _log ERROR "$@"; }
setup_environment() {
export HOME=/storage
export XDG_CONFIG_HOME=/storage/.config
export XDG_DATA_HOME=/storage/.local/share
export XDG_CACHE_HOME=/storage/.cache
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
export QT_SCALE_FACTOR=2
export QT_AUTO_SCREEN_SCALE_FACTOR=0
export QT_ENABLE_HIGHDPI_SCALING=1
export GDK_SCALE=2
export GDK_DPI_SCALE=1
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/var/run/0-runtime-dir}"
export WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-wayland-1}"
export SDL_VIDEODRIVER=x11
export SWAYSOCK="${SWAYSOCK:-${XDG_RUNTIME_DIR}/sway-ipc.0.sock}"
export DISPLAY="${DISPLAY:-:0}"
export AVALONIA_GLOBAL_SCALE_FACTOR=2
export GTK_USE_PORTAL=0
mkdir -p "$RYUBING_CONFIG" "$RYUBING_DATA" "$RYUBING_CACHE" \
"$RYUBING_DATA/nand" "$RYUBING_DATA/sdmc" \
"$RYUBING_DATA/load" "$RYUBING_DATA/dump" "$RYUBING_DATA/log"
}
preflight() {
local ok=true
if [ ! -x "$RYUBING_BIN" ]; then
err "Ryubing.AppImage missing or not executable: $RYUBING_BIN"
ok=false
fi
$ok
}
_pids_matching() {
local pid cmdline
for pid in $(pidof ryubing Ryubing.AppImage dwarfs python3 ryujinx Ryujinx 2>/dev/null || true); do
[ "$pid" = "$$" ] && continue
cmdline="$(tr '\0' ' ' <"/proc/${pid}/cmdline" 2>/dev/null || true)"
for pattern in "$@"; do
case "$cmdline" in *"$pattern"*) echo "$pid"; break ;; esac
done
done
}
cleanup_stale() {
local patterns=("/storage/ryubing/Ryubing.AppImage" "/tmp/.mount_Ryubing" "dwarfs /storage/ryubing" "Ryujinx" "ryujinx")
local mouse_patterns=("ryubing_mouse_layer.py" "prism_mouse_layer.py" "chromium_mouse_layer.py" "citron_mouse_layer.py")
local pids="$(_pids_matching "${patterns[@]}")"
if [ -n "$pids" ]; then
kill $pids 2>/dev/null || true
sleep 2
pids="$(_pids_matching "${patterns[@]}")"
[ -n "$pids" ] && kill -9 $pids 2>/dev/null || true
fi
pids="$(_pids_matching "${mouse_patterns[@]}")"
[ -n "$pids" ] && kill $pids 2>/dev/null || true
for mp in /tmp/.mount_Ryubing*; do
[ -e "$mp" ] || continue
if grep -qs " ${mp} " /proc/mounts; then
fusermount -uz "$mp" 2>/dev/null || umount -l "$mp" 2>/dev/null || true
fi
rm -rf "$mp" 2>/dev/null || true
done
}
CRASH_MARKER="${RYUBING_CACHE}/.ryubing_crash_marker"
set_crash_marker() { touch "$CRASH_MARKER"; }
clear_crash_marker() { rm -f "$CRASH_MARKER"; }
handle_previous_crash() {
if [ -f "$CRASH_MARKER" ]; then
nuke_shader_cache
clear_crash_marker
fi
}
nuke_shader_cache() {
local ts="$(date +%Y%m%d-%H%M%S)"
for target in "${RYUBING_DATA}/shader" "${RYUBING_CONFIG}/pipeline_cache" "${RYUBING_CACHE}/ryubing/shader" "${RYUBING_CACHE}/ryubing/pipeline_cache"; do
[ -e "$target" ] || continue
mv "$target" "${target}.bak-${ts}" 2>/dev/null || rm -rf "$target" 2>/dev/null || true
done
}
setup_controller() {
command -v inputplumber >/dev/null 2>&1 || return 0
inputplumber device 0 targets set ds5 keyboard >/dev/null 2>&1 || true
}
_has_sway() { [ -S "$SWAYSOCK" ] && command -v swaymsg >/dev/null 2>&1; }
set_touchpad() {
_has_sway || return 0
swaymsg input "$TOUCHPAD_ID" events "$1" >/dev/null 2>&1 || true
}
attach_mouse_seat() {
_has_sway || return 0
swaymsg seat seat1 fallback no >/dev/null 2>&1 || true
swaymsg seat seat0 attach "$MOUSE_ID" >/dev/null 2>&1 || true
swaymsg seat seat0 attach "$TOUCHPAD_ID" >/dev/null 2>&1 || true
swaymsg input "$TOUCHPAD_ID" events disabled >/dev/null 2>&1 || true
}
ryubing_window_manager() {
_has_sway || return 0
while true; do
local tree="$(swaymsg -t get_tree 2>/dev/null)" || { sleep 1; continue; }
local main_ids="$(printf '%s' "$tree" | jq -r '.. | objects | select((.window_properties.class // "") | test("ryubing|ryujinx"; "i")) | select((.window_properties.transient_for == null)) | .id' 2>/dev/null || true)"
local all_ids="$(printf '%s' "$tree" | jq -r '.. | objects | select(.type == "con" or .type == "floating_con") | select(.pid != null or .window != null or .app_id != null) | .id' 2>/dev/null || true)"
for id in $all_ids; do
if echo "$main_ids" | grep -qw "$id"; then
swaymsg "[con_id=${id}]" move workspace 1 >/dev/null 2>&1
swaymsg "[con_id=${id}]" move window to output "$DISPLAY_OUTPUT" >/dev/null 2>&1
swaymsg "[con_id=${id}]" floating disable >/dev/null 2>&1
swaymsg "[con_id=${id}]" fullscreen enable >/dev/null 2>&1
fi
done
sleep "$WM_POLL_INTERVAL"
done
}
start_mouse_layer() {
[ -x "$MOUSE_SCRIPT" ] || return 0
"$MOUSE_SCRIPT" >"${RYUBING_CACHE}/ryubing_mouse_layer.log" 2>&1 &
_mouse_pid=$!
sleep 1
attach_mouse_seat
}
start_helpers() {
ryubing_window_manager >"${RYUBING_CACHE}/ryubing_window_manager.log" 2>&1 &
_wm_pid=$!
set_touchpad disabled
start_mouse_layer
}
stop_helpers() {
for pid in $_wm_pid $_mouse_pid; do
[ -z "$pid" ] && continue
kill -9 "$pid" 2>/dev/null || true
done
set_touchpad enabled
}
launch_ryubing() {
cleanup_stale
start_helpers
set_crash_marker
cd "$RYUBING_DIR" || return 1
"$RYUBING_BIN" "$@" > "$RYUBING_EMU_LOG" 2>&1 &
_ryubing_pid=$!
wait "$_ryubing_pid" 2>/dev/null
local status=$?
clear_crash_marker
stop_helpers
return $status
}
on_exit() {
[ -n "$_ryubing_pid" ] && kill "$_ryubing_pid" 2>/dev/null || true
stop_helpers
}
main() {
trap on_exit EXIT INT TERM
setup_environment
preflight || exit 1
setup_controller
handle_previous_crash
launch_ryubing "$@"
exit $?
}
main "$@"
Place a game in /storage/roms/switch/, restart EmulationStation, and it will boot perfectly.
Should also work on other devices with a bit of tweaking.