u/hellonbye

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.

reddit.com
u/hellonbye — 4 days ago