|
#!/bin/bash |
|
|
|
# script for auto-install + config Nintendo Switch emulators |
|
# Supported emulators: citron, ryujinx, eden |
|
# how it works: |
|
# 1. download latest emulator release for your operating system (windows or linux supported by this script) |
|
# 2. downloads latest (if not --keys-version specified) prod.keys (decryption keys) and corresponding firmware.zip |
|
# 3. move prod.keys to correct emulator location, unzip firmware and move to correct emulator location |
|
|
|
set -e # Exit on any error |
|
|
|
# ============================================================================ |
|
# CONFIGURATION - Edit these values as needed |
|
# ============================================================================ |
|
|
|
# Emulator configurations |
|
declare -A EMULATOR_CONFIG |
|
|
|
# Citron configuration |
|
EMULATOR_CONFIG[citron_api_url]="https://git.citron-emu.org/api/v4/projects/1/releases" |
|
EMULATOR_CONFIG[citron_api_type]="gitlab" |
|
EMULATOR_CONFIG[citron_releases_url]="https://git.citron-emu.org/citron/emu/-/releases/" |
|
EMULATOR_CONFIG[citron_linux_base]="$HOME/.local/share/citron" |
|
EMULATOR_CONFIG[citron_windows_base]="$APPDATA/citron" |
|
EMULATOR_CONFIG[citron_desktop_name]="Citron" |
|
EMULATOR_CONFIG[citron_desktop_comment]="Nintendo Switch Emulator" |
|
|
|
# Ryujinx configuration |
|
EMULATOR_CONFIG[ryujinx_api_url]="https://git.ryujinx.app/api/v4/projects/1/releases" |
|
EMULATOR_CONFIG[ryujinx_api_type]="gitlab" |
|
EMULATOR_CONFIG[ryujinx_releases_url]="https://git.ryujinx.app/ryujinx/release-channel-master/-/releases/" |
|
EMULATOR_CONFIG[ryujinx_linux_base]="$HOME/.local/share/Ryujinx" |
|
EMULATOR_CONFIG[ryujinx_windows_base]="$APPDATA/Ryujinx" |
|
EMULATOR_CONFIG[ryujinx_desktop_name]="Ryujinx" |
|
EMULATOR_CONFIG[ryujinx_desktop_comment]="Nintendo Switch Emulator" |
|
|
|
# Eden configuration |
|
EMULATOR_CONFIG[eden_api_url]="https://api.github.com/repos/eden-emulator/Releases/releases/latest" |
|
EMULATOR_CONFIG[eden_api_type]="github" |
|
EMULATOR_CONFIG[eden_releases_url]="https://github.com/eden-emulator/Releases/releases" |
|
EMULATOR_CONFIG[eden_linux_base]="$HOME/.local/share/eden" |
|
EMULATOR_CONFIG[eden_windows_base]="$APPDATA/eden" |
|
EMULATOR_CONFIG[eden_desktop_name]="Eden" |
|
EMULATOR_CONFIG[eden_desktop_comment]="Nintendo Switch Emulator" |
|
|
|
# Desktop entry details (Linux only) |
|
DESKTOP_CATEGORIES="Game;Emulator;" |
|
|
|
# ============================================================================ |
|
# END CONFIGURATION |
|
# ============================================================================ |
|
|
|
|
|
RED='\033[0;31m' |
|
GREEN='\033[0;32m' |
|
YELLOW='\033[1;33m' |
|
BLUE='\033[0;34m' |
|
NC='\033[0m' |
|
|
|
# Global variables |
|
EMULATOR="citron" # Default emulator |
|
VERSION="" |
|
FORCE_REINSTALL=false |
|
SKIP_KEYS=false |
|
CUSTOM_DIR="" |
|
LINUX_X64_URL="" |
|
LINUX_ARM64_URL="" |
|
WINDOWS_URL="" |
|
KEYS_VERSION="" |
|
ORIG_PWD="$(pwd)" |
|
INSTALL_DIR="" |
|
API_URL="" |
|
RELEASES_URL="" |
|
API_TYPE="" |
|
|
|
print_info() { echo -e "${BLUE}[INFO]${NC} $1"; } |
|
print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } |
|
print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; } |
|
print_error() { echo -e "${RED}[ERROR]${NC} $1"; } |
|
|
|
# ============================================================================ |
|
# EMULATOR-SPECIFIC FUNCTIONS |
|
# ============================================================================ |
|
|
|
get_emulator_config() { |
|
local key="${EMULATOR}_${1}" |
|
echo "${EMULATOR_CONFIG[$key]}" |
|
} |
|
|
|
setup_emulator_vars() { |
|
API_URL=$(get_emulator_config "api_url") |
|
RELEASES_URL=$(get_emulator_config "releases_url") |
|
API_TYPE=$(get_emulator_config "api_type") |
|
|
|
if [[ -z "$API_URL" ]]; then |
|
print_error "Unsupported emulator: $EMULATOR" |
|
print_info "Supported emulators: citron, ryujinx, eden" |
|
exit 1 |
|
fi |
|
|
|
print_info "Selected emulator: $EMULATOR" |
|
} |
|
|
|
# ============================================================================ |
|
# FIRMWARE AND KEYS FUNCTIONS |
|
# ============================================================================ |
|
|
|
|
|
fetch_firmware_tags() { |
|
local tags_json |
|
if ! tags_json=$(curl -sL "https://api.github.com/repos/THZoria/NX_Firmware/tags"); then |
|
return 1 |
|
fi |
|
echo "$tags_json" | grep -oE '"name"[[:space:]]*:[[:space:]]*"[^\"]*"' | cut -d'"' -f4 |
|
} |
|
|
|
get_firmware_versions() { |
|
print_info "Fetching available firmware/key versions from GitHub..." |
|
local versions |
|
if ! versions=$(fetch_firmware_tags); then |
|
print_warning "Failed to fetch version tags from GitHub API" |
|
return 1 |
|
fi |
|
if [[ -z "$versions" ]]; then |
|
print_warning "No versions found in GitHub tags response" |
|
return 1 |
|
fi |
|
print_info "Available versions:" |
|
echo "$versions" |
|
print_info "re-run the script with --keys-version <version>" |
|
return 0 |
|
} |
|
|
|
get_latest_firmware_version() { |
|
local versions latest_version |
|
if ! versions=$(fetch_firmware_tags); then |
|
print_warning "Failed to fetch version tags from GitHub API" |
|
return 1 |
|
fi |
|
latest_version=$(echo "$versions" | head -1) |
|
if [[ -z "$latest_version" ]]; then |
|
print_warning "No versions found in GitHub tags response" |
|
return 1 |
|
fi |
|
echo "$latest_version" |
|
return 0 |
|
} |
|
|
|
ensure_keys_version() { |
|
if [[ -z "$KEYS_VERSION" ]]; then |
|
if ! KEYS_VERSION=$(get_latest_firmware_version); then |
|
print_error "Failed to determine firmware version" |
|
exit 1 |
|
fi |
|
print_info "Using latest firmware version: $KEYS_VERSION" |
|
else |
|
print_info "Using firmware version: $KEYS_VERSION" |
|
fi |
|
} |
|
|
|
usage() { |
|
echo "Usage: $0 [OPTIONS]" |
|
echo "Options:" |
|
echo " -e, --emulator EMU Specify emulator to install (citron, ryujinx, eden). Default: citron" |
|
echo " -f, --force Force reinstall even if emulator is already installed" |
|
echo " -s, --skip-keys Skip downloading prod.keys and firmware" |
|
echo " -d, --dir DIR Specify custom emulator installation directory" |
|
echo " -v, --keys-version VER Specify prod.keys / firmware version (e.g. 20.3.0)." |
|
echo " If omitted the script will use the GitHub 'latest'" |
|
echo " releases for prod.keys and firmware." |
|
echo " -h, --help Show this help message" |
|
echo "" |
|
echo "Examples:" |
|
echo " $0" |
|
echo " $0 -e ryujinx" |
|
echo " $0 --emulator eden --skip-keys" |
|
echo " $0 -e ryujinx -d /opt/ryujinx" |
|
echo "" |
|
echo "Supported emulators: citron, ryujinx, eden" |
|
exit 1 |
|
} |
|
|
|
while [[ $# -gt 0 ]]; do |
|
case $1 in |
|
-e|--emulator) |
|
EMULATOR="$2" |
|
shift 2 |
|
;; |
|
-f|--force) |
|
FORCE_REINSTALL=true |
|
shift |
|
;; |
|
-s|--skip-keys) |
|
SKIP_KEYS=true |
|
shift |
|
;; |
|
-d|--dir) |
|
CUSTOM_DIR="$2" |
|
shift 2 |
|
;; |
|
-v|--keys-version) |
|
if [[ -z "$2" || "$2" == -* ]]; then |
|
print_warning "No version provided for --keys-version" |
|
get_firmware_versions || true |
|
exit 1 |
|
fi |
|
KEYS_VERSION="$2" |
|
shift 2 |
|
;; |
|
-h|--help) |
|
usage |
|
;; |
|
*) |
|
echo "Unknown option: $1" |
|
usage |
|
;; |
|
esac |
|
done |
|
|
|
detect_os() { |
|
if [[ "$OSTYPE" == "linux-gnu"* ]]; then |
|
OS="linux" |
|
ARCH=$(uname -m) |
|
case $ARCH in |
|
x86_64) ARCH="x64" ;; |
|
aarch64) ARCH="arm64" ;; |
|
*) print_error "Unsupported architecture: $ARCH"; exit 1 ;; |
|
esac |
|
elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then |
|
OS="windows" |
|
ARCH="x64" |
|
else |
|
print_error "Unsupported operating system: $OSTYPE" |
|
exit 1 |
|
fi |
|
print_info "Detected OS: $OS, Architecture: $ARCH" |
|
} |
|
|
|
set_emu_directory() { |
|
if [[ -n "$CUSTOM_DIR" ]]; then |
|
INSTALL_DIR="$CUSTOM_DIR" |
|
elif [[ "$OS" == "linux" ]]; then |
|
INSTALL_DIR=$(get_emulator_config "linux_base") |
|
elif [[ "$OS" == "windows" ]]; then |
|
INSTALL_DIR=$(get_emulator_config "windows_base") |
|
fi |
|
mkdir -p "$INSTALL_DIR" |
|
INSTALL_DIR="$(cd "$INSTALL_DIR"; pwd)" # to absolute |
|
print_info "$EMULATOR will be installed to: $INSTALL_DIR" |
|
} |
|
|
|
check_existing_installation() { |
|
if [[ -d "$INSTALL_DIR" && "$FORCE_REINSTALL" == false ]]; then |
|
if [[ -n "$(ls -A "$INSTALL_DIR" 2>/dev/null)" ]]; then |
|
print_warning "$EMULATOR appears to be already installed at $INSTALL_DIR (directory is not empty)" |
|
print_info "Installation cancelled. Use --force to overwrite." |
|
exit 0 |
|
fi |
|
fi |
|
} |
|
|
|
get_latest_release_info() { |
|
print_info "Fetching latest $EMULATOR release information..." |
|
|
|
local api_response |
|
if ! api_response=$(curl -s "$API_URL"); then |
|
print_error "Failed to fetch releases from API: $API_URL" |
|
exit 1 |
|
fi |
|
|
|
# Extract tag/version once for all branches |
|
VERSION=$(echo "$api_response" | grep -o '"tag_name"[[:space:]]*:[[:space:]]*"[^\"]*"' | head -1 | cut -d'"' -f4 | sed 's/^v//') |
|
if [[ -z "$VERSION" ]]; then |
|
print_error "Could not extract version from API response" |
|
exit 1 |
|
fi |
|
print_info "Found latest version: $VERSION" |
|
|
|
if [[ "$API_TYPE" == "github" ]]; then |
|
# Extract download URLs from GitHub assets |
|
LINUX_X64_URL=$(echo "$api_response" | grep -o '"browser_download_url"[[:space:]]*:[[:space:]]*"[^\"]*[Ll]inux[^\"]*amd64[^\"]*\.AppImage[^\"]*"' | head -1 | cut -d'"' -f4) |
|
if [[ -z "$LINUX_X64_URL" ]]; then |
|
LINUX_X64_URL=$(echo "$api_response" | grep -o '"browser_download_url"[[:space:]]*:[[:space:]]*"[^\"]*[Ll]inux[^\"]*x86_64[^\"]*\.AppImage[^\"]*"' | head -1 | cut -d'"' -f4) |
|
fi |
|
LINUX_ARM64_URL=$(echo "$api_response" | grep -o '"browser_download_url"[[:space:]]*:[[:space:]]*"[^\"]*[Ll]inux[^\"]*aarch64[^\"]*\.AppImage[^\"]*"' | head -1 | cut -d'"' -f4) |
|
WINDOWS_URL=$(echo "$api_response" | grep -o '"browser_download_url"[[:space:]]*:[[:space:]]*"[^\"]*[Ww]indows[^\"]*\.zip[^\"]*"' | head -1 | cut -d'"' -f4) |
|
|
|
if [[ -z "$LINUX_X64_URL" ]]; then |
|
print_warning "Could not find Linux x64 download URL" |
|
fi |
|
if [[ -z "$LINUX_ARM64_URL" ]]; then |
|
print_warning "Could not find Linux ARM64 download URL" |
|
fi |
|
if [[ -z "$WINDOWS_URL" ]]; then |
|
print_warning "Could not find Windows download URL" |
|
fi |
|
|
|
elif [[ "$EMULATOR" == "ryujinx" ]]; then |
|
LINUX_X64_URL="https://git.ryujinx.app/api/v4/projects/1/packages/generic/Ryubing/$VERSION/ryujinx-$VERSION-linux_x64.tar.gz" |
|
LINUX_ARM64_URL="https://git.ryujinx.app/api/v4/projects/1/packages/generic/Ryubing/$VERSION/ryujinx-$VERSION-linux_arm64.tar.gz" |
|
WINDOWS_URL="https://git.ryujinx.app/api/v4/projects/1/packages/generic/Ryubing/$VERSION/ryujinx-$VERSION-win_x64.zip" |
|
print_info "Using predictable download URLs for Ryujinx (version: $VERSION)" |
|
|
|
elif [[ "$EMULATOR" == "citron" ]]; then |
|
LINUX_X64_URL=$(echo "$api_response" | grep -o '"url":"[^\"]*x86_64[^\"]*AppImage[^\"]*"' | head -1 | cut -d'"' -f4) |
|
LINUX_ARM64_URL=$(echo "$api_response" | grep -o '"url":"[^\"]*aarch64[^\"]*AppImage[^\"]*"' | head -1 | cut -d'"' -f4) |
|
WINDOWS_URL=$(echo "$api_response" | grep -o '"url":"[^\"]*windows[^\"]*zip[^\"]*"' | head -1 | cut -d'"' -f4) |
|
|
|
if [[ -z "$LINUX_X64_URL" ]]; then |
|
print_warning "Could not find Linux x64 download URL" |
|
fi |
|
if [[ -z "$LINUX_ARM64_URL" ]]; then |
|
print_warning "Could not find Linux ARM64 download URL" |
|
fi |
|
if [[ -z "$WINDOWS_URL" ]]; then |
|
print_warning "Could not find Windows download URL" |
|
fi |
|
fi |
|
} |
|
|
|
download_emu() { |
|
print_info "Downloading $EMULATOR v$VERSION for $OS-$ARCH..." |
|
if [[ "$OS" == "linux" ]]; then |
|
if [[ "$ARCH" == "x64" ]]; then |
|
if [[ -z "$LINUX_X64_URL" ]]; then |
|
print_error "No Linux x64 download URL available" |
|
exit 1 |
|
fi |
|
DOWNLOAD_URL="$LINUX_X64_URL" |
|
if [[ "$EMULATOR" == "citron" || "$EMULATOR" == "eden" ]]; then |
|
FILENAME="$(basename "$LINUX_X64_URL")" |
|
else |
|
FILENAME="$EMULATOR-v$VERSION-linux_x64.tar.gz" |
|
fi |
|
elif [[ "$ARCH" == "arm64" ]]; then |
|
if [[ -z "$LINUX_ARM64_URL" ]]; then |
|
print_error "No Linux ARM64 download URL available" |
|
exit 1 |
|
fi |
|
DOWNLOAD_URL="$LINUX_ARM64_URL" |
|
if [[ "$EMULATOR" == "citron" || "$EMULATOR" == "eden" ]]; then |
|
FILENAME="$(basename "$LINUX_ARM64_URL")" |
|
else |
|
FILENAME="$EMULATOR-v$VERSION-linux-arm64.tar.gz" |
|
fi |
|
else |
|
print_error "Unsupported Linux architecture: $ARCH" |
|
exit 1 |
|
fi |
|
elif [[ "$OS" == "windows" ]]; then |
|
if [[ -z "$WINDOWS_URL" ]]; then |
|
print_error "No Windows download URL available" |
|
exit 1 |
|
fi |
|
DOWNLOAD_URL="$WINDOWS_URL" |
|
if [[ "$EMULATOR" == "citron" ]]; then |
|
FILENAME="Citron-Windows-v$VERSION.zip" |
|
elif [[ "$EMULATOR" == "eden" ]]; then |
|
FILENAME="$(basename "$WINDOWS_URL")" |
|
else |
|
FILENAME="$EMULATOR-v$VERSION-win_x64.zip" |
|
fi |
|
fi |
|
TEMP_DIR=$(mktemp -d) |
|
print_info "Downloading from: $DOWNLOAD_URL" |
|
if ! curl -L -o "$TEMP_DIR/$FILENAME" "$DOWNLOAD_URL"; then |
|
print_error "Failed to download $EMULATOR from $DOWNLOAD_URL" |
|
print_info "Please check $RELEASES_URL for updated download links" |
|
exit 1 |
|
fi |
|
print_success "Downloaded $FILENAME" |
|
} |
|
|
|
install_emu() { |
|
print_info "Installing $EMULATOR to $INSTALL_DIR..." |
|
local archive_path="$TEMP_DIR/$FILENAME" |
|
|
|
if [[ "$FILENAME" == *.AppImage ]]; then |
|
cp "$archive_path" "$INSTALL_DIR/$EMULATOR" |
|
chmod +x "$INSTALL_DIR/$EMULATOR" |
|
print_info "AppImage installed as executable" |
|
|
|
elif [[ "$FILENAME" == *.zip ]]; then |
|
unzip -o -q "$archive_path" -d "$INSTALL_DIR" |
|
|
|
# Handle Ryujinx special case: rename 'publish' folder |
|
if [[ "$EMULATOR" == "ryujinx" ]]; then |
|
local publish_dir="$INSTALL_DIR/publish" |
|
if [[ -d "$publish_dir" ]]; then |
|
print_info "Renaming 'publish' folder to Ryujinx..." |
|
# Move contents from publish to parent, then remove publish |
|
shopt -s dotglob nullglob |
|
mv "$publish_dir"/* "$INSTALL_DIR"/ |
|
rmdir "$publish_dir" |
|
shopt -u dotglob nullglob |
|
fi |
|
else |
|
# Citron/Eden: flatten if single subdirectory |
|
local subdir |
|
subdir=$(find "$INSTALL_DIR" -mindepth 1 -maxdepth 1 -type d | head -n 1) |
|
if [[ -n "$subdir" ]] && [[ $(find "$INSTALL_DIR" -mindepth 1 -maxdepth 1 | wc -l) -eq 1 ]]; then |
|
print_info "Moving contents of $(basename "$subdir") up to $INSTALL_DIR..." |
|
shopt -s dotglob nullglob |
|
mv "$subdir"/* "$INSTALL_DIR"/ |
|
rmdir "$subdir" |
|
shopt -u dotglob nullglob |
|
fi |
|
fi |
|
|
|
if [[ "$OS" == "linux" && -f "$INSTALL_DIR/$EMULATOR" ]]; then |
|
chmod +x "$INSTALL_DIR/$EMULATOR" |
|
fi |
|
print_info "ZIP archive extracted" |
|
|
|
elif [[ "$FILENAME" == *.tar.gz ]]; then |
|
# For Ryujinx tar.gz, extract and handle 'publish' folder |
|
if [[ "$EMULATOR" == "ryujinx" ]]; then |
|
local temp_extract="$TEMP_DIR/extract" |
|
mkdir -p "$temp_extract" |
|
tar -xzf "$archive_path" -C "$temp_extract" |
|
|
|
# Check if there's a 'publish' folder |
|
if [[ -d "$temp_extract/publish" ]]; then |
|
print_info "Moving contents from 'publish' folder..." |
|
shopt -s dotglob nullglob |
|
mv "$temp_extract/publish"/* "$INSTALL_DIR"/ |
|
shopt -u dotglob nullglob |
|
else |
|
# No publish folder, move everything |
|
shopt -s dotglob nullglob |
|
mv "$temp_extract"/* "$INSTALL_DIR"/ |
|
shopt -u dotglob nullglob |
|
fi |
|
else |
|
tar -xzf "$archive_path" -C "$INSTALL_DIR" --strip-components=1 |
|
fi |
|
|
|
if [[ "$OS" == "linux" && -f "$INSTALL_DIR/$EMULATOR" ]]; then |
|
chmod +x "$INSTALL_DIR/$EMULATOR" |
|
fi |
|
print_info "Tar archive extracted" |
|
else |
|
print_error "Unsupported file format: $FILENAME" |
|
exit 1 |
|
fi |
|
print_success "$EMULATOR installed successfully" |
|
} |
|
|
|
download_keys() { |
|
if [[ "$SKIP_KEYS" == true ]]; then |
|
print_info "Skipping prod.keys download" |
|
return |
|
fi |
|
print_info "Preparing to download prod.keys..." |
|
ensure_keys_version |
|
|
|
# Determine keys directory based on emulator |
|
if [[ "$EMULATOR" == "citron" || "$EMULATOR" == "eden" ]]; then |
|
if [[ "$OS" == "linux" ]]; then |
|
KEYS_DIR=$(get_emulator_config "linux_base")"/keys" |
|
else |
|
KEYS_DIR="$INSTALL_DIR/user/keys" |
|
fi |
|
elif [[ "$EMULATOR" == "ryujinx" ]]; then |
|
if [[ "$OS" == "linux" ]]; then |
|
KEYS_DIR="$HOME/.config/Ryujinx/system" |
|
else |
|
KEYS_DIR="$INSTALL_DIR/system" |
|
fi |
|
fi |
|
|
|
mkdir -p "$KEYS_DIR" |
|
if [[ -z "$PROD_KEYS_URL" ]]; then |
|
PROD_KEYS_URL="https://github.com/DeaconBlood/PROD.KEYS/releases/download/${KEYS_VERSION}/prod.keys" |
|
fi |
|
print_info "Downloading prod.keys from: $PROD_KEYS_URL" |
|
if curl -L -o "$KEYS_DIR/prod.keys" "$PROD_KEYS_URL"; then |
|
print_success "prod.keys downloaded successfully" |
|
else |
|
print_warning "Failed to download prod.keys from $PROD_KEYS_URL." |
|
get_firmware_versions || true |
|
exit 1 |
|
fi |
|
} |
|
|
|
download_firmware() { |
|
if [[ "$SKIP_KEYS" == true ]]; then |
|
print_info "Skipping firmware download" |
|
return |
|
fi |
|
print_info "Preparing to download firmware..." |
|
ensure_keys_version |
|
|
|
# Determine firmware directory based on emulator |
|
if [[ "$EMULATOR" == "citron" || "$EMULATOR" == "eden" ]]; then |
|
if [[ "$OS" == "linux" ]]; then |
|
NAND_DIR=$(get_emulator_config "linux_base")"/nand/system/Contents/registered" |
|
else |
|
NAND_DIR="$INSTALL_DIR/user/nand/system/Contents/registered" |
|
fi |
|
elif [[ "$EMULATOR" == "ryujinx" ]]; then |
|
if [[ "$OS" == "linux" ]]; then |
|
NAND_DIR="$HOME/.config/Ryujinx/bis/system/Contents/registered" |
|
else |
|
NAND_DIR="$INSTALL_DIR/bis/system/Contents/registered" |
|
fi |
|
fi |
|
|
|
mkdir -p "$NAND_DIR" |
|
if [[ -z "$FIRMWARE_URL" ]]; then |
|
FIRMWARE_URL="https://github.com/THZoria/NX_Firmware/releases/download/${KEYS_VERSION}/Firmware.${KEYS_VERSION}.zip" |
|
fi |
|
if [[ -z "$TEMP_DIR" || ! -d "$TEMP_DIR" ]]; then |
|
TEMP_DIR=$(mktemp -d) |
|
fi |
|
print_info "Downloading firmware from: $FIRMWARE_URL" |
|
if curl -L -o "$TEMP_DIR/firmware.zip" "$FIRMWARE_URL"; then |
|
print_info "Extracting firmware..." |
|
|
|
if [[ "$EMULATOR" == "citron" || "$EMULATOR" == "eden" ]]; then |
|
# Citron/Eden: direct extraction |
|
if unzip -o -q "$TEMP_DIR/firmware.zip" -d "$NAND_DIR/"; then |
|
print_success "Firmware downloaded and extracted successfully" |
|
else |
|
print_warning "Downloaded firmware but failed to extract it. File may be different than expected." |
|
get_firmware_versions || true |
|
exit 1 |
|
fi |
|
elif [[ "$EMULATOR" == "ryujinx" ]]; then |
|
# Ryujinx: special handling - extract to temp, then process each file |
|
local temp_firmware="$TEMP_DIR/firmware_extract" |
|
mkdir -p "$temp_firmware" |
|
if unzip -o -q "$TEMP_DIR/firmware.zip" -d "$temp_firmware/"; then |
|
print_info "Processing firmware files for Ryujinx..." |
|
# For each file in the extracted firmware |
|
for file in "$temp_firmware"/*; do |
|
if [[ -f "$file" ]]; then |
|
local filename=$(basename "$file") |
|
# Create a folder with the filename (including extension) |
|
local target_dir="$NAND_DIR/$filename" |
|
mkdir -p "$target_dir" |
|
# Move the file into it as '00' (no extension) |
|
mv "$file" "$target_dir/00" |
|
print_info "Processed: $filename" |
|
fi |
|
done |
|
print_success "Firmware downloaded and processed successfully" |
|
else |
|
print_warning "Downloaded firmware but failed to extract it. File may be different than expected." |
|
get_firmware_versions || true |
|
exit 1 |
|
fi |
|
fi |
|
else |
|
print_warning "Failed to download firmware from $FIRMWARE_URL." |
|
get_firmware_versions || true |
|
exit 1 |
|
fi |
|
} |
|
|
|
download_keys_and_firmware() { |
|
download_keys |
|
download_firmware |
|
} |
|
|
|
create_desktop_shortcut() { |
|
local desktop_name=$(get_emulator_config "desktop_name") |
|
local desktop_comment=$(get_emulator_config "desktop_comment") |
|
|
|
if [[ "$OS" == "linux" ]]; then |
|
print_info "Creating desktop shortcut..." |
|
# Get desktop path from user-dirs.dirs, fallback to $HOME/Desktop |
|
if [[ -f "$HOME/.config/user-dirs.dirs" ]]; then |
|
local desktop_dir=$(grep XDG_DESKTOP_DIR "$HOME/.config/user-dirs.dirs" | cut -d'=' -f2 | tr -d '"') |
|
# Replace $HOME with actual path |
|
desktop_dir="${desktop_dir/#\$HOME/$HOME}" |
|
if [[ -n "$desktop_dir" && -d "$desktop_dir" ]]; then |
|
DESKTOP_FILE="$desktop_dir/${EMULATOR}.desktop" |
|
else |
|
mkdir -p "$HOME/Desktop" |
|
DESKTOP_FILE="$HOME/Desktop/${EMULATOR}.desktop" |
|
fi |
|
else |
|
mkdir -p "$HOME/Desktop" |
|
DESKTOP_FILE="$HOME/Desktop/${EMULATOR}.desktop" |
|
fi |
|
|
|
# Determine executable name |
|
local exec_name="$EMULATOR" |
|
if [[ "$EMULATOR" == "ryujinx" ]]; then |
|
exec_name="Ryujinx" |
|
fi |
|
|
|
cat > "$DESKTOP_FILE" << EOF |
|
[Desktop Entry] |
|
Name=$desktop_name |
|
Comment=$desktop_comment |
|
Exec=$INSTALL_DIR/$exec_name |
|
Icon=$INSTALL_DIR/$EMULATOR.png |
|
Terminal=false |
|
Type=Application |
|
Categories=$DESKTOP_CATEGORIES |
|
EOF |
|
chmod +x "$DESKTOP_FILE" |
|
print_success "Desktop shortcut created" |
|
elif [[ "$OS" == "windows" ]]; then |
|
print_info "Creating desktop shortcut..." |
|
WIN_DESKTOP="$(powershell.exe -NoProfile -Command "[Environment]::GetFolderPath('Desktop')" | tr -d '\r')" |
|
SHORTCUT_PATH="$WIN_DESKTOP/$desktop_name.lnk" |
|
|
|
# Determine executable name |
|
local target_exe="$EMULATOR.exe" |
|
if [[ "$EMULATOR" == "ryujinx" ]]; then |
|
target_exe="Ryujinx.exe" |
|
fi |
|
|
|
TARGET_PATH="$INSTALL_DIR/$target_exe" |
|
TARGET_PATH="$(cygpath -w "$TARGET_PATH")" |
|
echo "$TARGET_PATH" |
|
POWERSHELL_CMD="\$s = (New-Object -COM WScript.Shell).CreateShortcut('$SHORTCUT_PATH') |
|
\$s.TargetPath = '$TARGET_PATH' |
|
\$s.IconLocation = '$TARGET_PATH' |
|
\$s.Save()" |
|
powershell.exe -NoProfile -Command "$POWERSHELL_CMD" |
|
print_success "Desktop shortcut created" |
|
fi |
|
} |
|
|
|
cleanup() { |
|
if [[ -n "$TEMP_DIR" && -d "$TEMP_DIR" ]]; then |
|
rm -rf "$TEMP_DIR" |
|
print_info "Cleaned up temporary files" |
|
fi |
|
} |
|
|
|
main() { |
|
print_info "Starting $EMULATOR installation..." |
|
|
|
setup_emulator_vars |
|
detect_os |
|
set_emu_directory |
|
check_existing_installation |
|
get_latest_release_info |
|
download_emu |
|
install_emu |
|
download_keys_and_firmware |
|
create_desktop_shortcut |
|
cleanup |
|
|
|
print_success "$EMULATOR v$VERSION installation completed!" |
|
print_info "Installation directory: $INSTALL_DIR" |
|
|
|
if [[ "$OS" == "linux" ]]; then |
|
if [[ "$EMULATOR" == "ryujinx" ]]; then |
|
print_info "You can run $EMULATOR with: $INSTALL_DIR/Ryujinx" |
|
else |
|
print_info "You can run $EMULATOR with: $INSTALL_DIR/$EMULATOR" |
|
fi |
|
elif [[ "$OS" == "windows" ]]; then |
|
print_info "You can run $EMULATOR from: $INSTALL_DIR" |
|
fi |
|
|
|
if [[ "$SKIP_KEYS" == false ]]; then |
|
print_info "Keys directory: $KEYS_DIR" |
|
print_info "Firmware directory: $NAND_DIR" |
|
fi |
|
} |
|
|
|
trap cleanup EXIT |
|
|
|
main "$@ |