#!/bin/bash run_game() { # Initialize variables emulator="" system="" manual_mode=false # Parse options for system, emulator, and manual mode while getopts ":e:s:m" opt; do case ${opt} in e) emulator=$OPTARG # Emulator provided via -e ;; s) system=$OPTARG # System provided via -s ;; m) manual_mode=true # Manual mode enabled via -m log i "Run game: manual mode enabled" ;; \?) echo "Usage: $0 start [-e emulator] [-s system] [-m] game" exit 1 ;; esac done shift $((OPTIND - 1)) # Check for game argument if [[ -z "$1" ]]; then log e "Game path is required." log i "Usage: $0 start [-e emulator] [-s system] [-m] game" exit 1 fi game=$1 if [[ -d "$game" ]]; then log d "$(basename "$game") is a directory, parsing it like a \"directory as a file\"" game="$game/$(basename "$game")" log d "Actual file is in \"$game\"" fi game_basename="./$(basename "$game")" # Step 1: System Recognition if [[ -z "$system" ]]; then # Automatically detect system from game path system=$(echo "$game" | grep -oP '(?<=roms/)[^/]+') if [[ -z "$system" ]]; then log e "Failed to detect system from game path." exit 1 fi fi # Step 2: Emulator Definition if [[ -n "$emulator" ]]; then log d "Emulator provided via command-line: $emulator" elif [[ "$manual_mode" = true ]]; then log d "Manual mode: showing Zenity emulator selection" emulator=$(show_zenity_emulator_list "$system") if [[ -z "$emulator" ]]; then log e "No emulator selected in manual mode." exit 1 fi else log d "Automatically searching for an emulator for system: $system" # Check for in the game block in gamelist.xml altemulator=$(xmllint --recover --xpath "string(//game[path='$game_basename']/altemulator)" "$rdhome/ES-DE/gamelists/$system/gamelist.xml" 2>/dev/null) if [[ -n "$altemulator" ]]; then log d "Found for game: $altemulator" emulator=$(xmllint --recover --xpath "string(//command[@label=\"$altemulator\"])" "$es_systems" 2>/dev/null) else # if no altemulator is found we search if a global one is set log d "No altemulator found in the game entry, searching for alternativeEmulator to check if a global emulator is set for the system $system" alternative_emulator=$(xmllint --recover --xpath 'string(//alternativeEmulator/label)' "$rdhome/ES-DE/gamelists/$system/gamelist.xml" 2>/dev/null) log d "Alternate emulator found in header: $alternative_emulator" emulator=$(xmllint --recover --xpath "string(//system[platform='$system']/command[@label=\"$alternative_emulator\"])" "$es_systems" 2>/dev/null) fi # Fallback to first available emulator in es_systems.xml if no found if [[ -z "$emulator" ]]; then log d "No alternate emulator found, using first available emulator in es_systems.xml" emulator=$(xmllint --recover --xpath "string(//system[name='$system']/command[1])" "$es_systems") fi if [[ -z "$emulator" ]]; then log e "No valid emulator found for system: $system" exit 1 fi fi # Step 3: Construct and Run the Command log i "-------------------------------------------" log i " RetroDECK is now booting the game" log i " Game path: \"$game\"" log i " Recognized system: $system" log i " Given emulator: $emulator" log i "-------------------------------------------" # Now pass the final constructed command to substitute_placeholders function final_command=$(substitute_placeholders "$emulator") # Log and execute the command log i "Launching game with command: $final_command" eval "$final_command" } # Assume this function handles showing the Zenity list of emulators for manual mode show_zenity_emulator_list() { local system="$1" # Example logic to retrieve and show Zenity list of emulators for the system # This would extract available emulators for the system from es_systems.xml and show a Zenity dialog emulators=$(xmllint --xpath "//system[name='$system']/command/@label" "$es_systems" | sed 's/ label=/\n/g' | sed 's/\"//g' | grep -o '[^ ]*') zenity --list --title="Select Emulator" --column="Emulators" $emulators } # Function to extract commands from es_systems.xml and present them in Zenity find_system_commands() { local system_name=$system # Use xmllint to extract the system commands from the XML system_section=$(xmllint --xpath "//system[name='$system_name']" "$es_systems" 2>/dev/null) if [ -z "$system_section" ]; then log e "System not found: $system_name" exit 1 fi # Extract commands and labels commands=$(echo "$system_section" | xmllint --xpath "//command" - 2>/dev/null) # Prepare Zenity command list command_list=() while IFS= read -r line; do label=$(echo "$line" | sed -n 's/.*label="\([^"]*\)".*/\1/p') command=$(echo "$line" | sed -n 's/.*]*>\(.*\)<\/command>.*/\1/p') # Substitute placeholders in the command command=$(substitute_placeholders "$command") # Add label and command to Zenity list (label first, command second) command_list+=("$label" "$command") done <<< "$commands" # Check if there's only one command if [ ${#command_list[@]} -eq 2 ]; then log d "Only one command found for $system_name, running it directly: ${command_list[1]}" selected_command="${command_list[1]}" else # Show the list with Zenity and return the **command** (second column) selected selected_command=$(zenity --list \ --title="Select an emulator for $system_name" \ --column="Emulator" --column="Hidden Command" "${command_list[@]}" \ --width=800 --height=400 --print-column=2 --hide-column=2) fi echo "$selected_command" } # Function to substitute placeholders in the command substitute_placeholders() { local cmd="$1" log d "Substitute placeholder: working on $cmd" game=$(echo "$game" | sed "s/'/'\\\\''/g") # escaping internal ' local rom_path="$game" local rom_dir=$(dirname "$rom_path") # Strip all file extensions from the base name local base_name=$(basename "$rom_path") base_name="${base_name%%.*}" local file_name=$(basename "$rom_path") local rom_raw="$rom_path" local rom_dir_raw="$rom_dir" local es_path="" local emulator_path="" # Manually replace %EMULATOR_*% placeholders while [[ "$cmd" =~ (%EMULATOR_[A-Z0-9_]+%) ]]; do placeholder="${BASH_REMATCH[1]}" emulator_path=$(replace_emulator_placeholder "$placeholder") cmd="${cmd//$placeholder/$emulator_path}" done # Substitute %BASENAME% and other placeholders cmd="${cmd//"%BASENAME%"/"'$base_name'"}" cmd="${cmd//"%FILENAME%"/"'$file_name'"}" cmd="${cmd//"%ROMRAW%"/"'$rom_raw'"}" cmd="${cmd//"%ROMPATH%"/"'$rom_dir'"}" cmd="${cmd//"%ENABLESHORTCUTS%"/""}" cmd="${cmd//"%EMULATOR_OS-SHELL%"/"/bin/sh"}" # Ensure paths are quoted correctly cmd="${cmd//"%ROM%"/"'$rom_path'"}" cmd="${cmd//"%GAMEDIR%"/"'$rom_dir'"}" cmd="${cmd//"%GAMEDIRRAW%"/"'$rom_dir_raw'"}" cmd="${cmd//"%CORE_RETROARCH%"/"$ra_cores_path"}" log d "Command after placeholders substitutions: $cmd" # Now handle %INJECT% after %BASENAME% has been substituted cmd=$(handle_inject_placeholder "$cmd") echo "$cmd" } # Function to replace %EMULATOR_SOMETHING% with the actual path of the emulator replace_emulator_placeholder() { local placeholder=$1 # Extract emulator name from placeholder without changing case local emulator_name="${placeholder//"%EMULATOR_"/}" # Extract emulator name after %EMULATOR_ emulator_name="${emulator_name//"%"/}" # Remove the trailing % # Use the find_emulator function to get the emulator path using the correct casing local emulator_exec=$(find_emulator "$emulator_name") if [[ -z "$emulator_exec" ]]; then log e "Emulator '$emulator_name' not found." exit 1 fi echo "$emulator_exec" } # Function to handle the %INJECT% placeholder handle_inject_placeholder() { local cmd="$1" local rom_dir=$(dirname "$game") # Get the ROM directory based on the game path # Find and process all occurrences of %INJECT%='something'.extension while [[ "$cmd" =~ (%INJECT%=\'([^\']+)\')(.[^ ]+)? ]]; do inject_file="${BASH_REMATCH[2]}" # Extract the quoted file name extension="${BASH_REMATCH[3]}" # Extract the extension (if any) inject_file_full_path="$rom_dir/$inject_file$extension" # Form the full path log d "Found inject part: %INJECT%='$inject_file'$extension" # Check if the file exists if [[ -f "$inject_file_full_path" ]]; then # Read the content of the file and replace newlines with spaces inject_content=$(cat "$inject_file_full_path" | tr '\n' ' ') log i "File \"$inject_file_full_path\" found. Replacing %INJECT% with content." # Escape special characters in the inject part for the replacement escaped_inject_part=$(printf '%s' "%INJECT%='$inject_file'$extension" | sed 's/[]\/$*.^[]/\\&/g') # Replace the entire %INJECT%=...'something'.extension part with the file content cmd=$(echo "$cmd" | sed "s|$escaped_inject_part|$inject_content|g") log d "Replaced cmd: $cmd" else log e "File \"$inject_file_full_path\" not found. Removing %INJECT% placeholder." # Use sed to remove the entire %INJECT%=...'something'.extension escaped_inject_part=$(printf '%s' "%INJECT%='$inject_file'$extension" | sed 's/[]\/$*.^[]/\\&/g') cmd=$(echo "$cmd" | sed "s|$escaped_inject_part||g") log d "sedded cmd: $cmd" fi done log d "Returning the command with injected content: $cmd" echo "$cmd" } # Function to get the first available emulator in the list get_first_emulator() { local system_name=$system system_section=$(xmllint --xpath "//system[name='$system_name']" "$es_systems" 2>/dev/null) if [ -z "$system_section" ]; then log e "System not found: $system_name" exit 1 fi # Extract the first command and use it as the selected emulator first_command=$(echo "$system_section" | xmllint --xpath "string(//command[1])" - 2>/dev/null) if [[ -n "$first_command" ]]; then # Substitute placeholders in the command first_command=$(substitute_placeholders "$first_command") log d "Automatically selected the first emulator: $first_command" echo "$first_command" else log e "No command found for the system: $system_name" return 1 fi } find_emulator() { local emulator_name="$1" found_path="" # Search the es_find_rules.xml file for the emulator emulator_section=$(xmllint --xpath "//emulator[@name='$emulator_name']" "$es_find_rules" 2>/dev/null) if [ -z "$emulator_section" ]; then log e "Find emulator: emulator not found: $emulator_name" return 1 fi # Search systempath entries while IFS= read -r line; do command_path=$(echo "$line" | sed -n 's/.*\(.*\)<\/entry>.*/\1/p') if [ -x "$(command -v $command_path)" ]; then found_path=$command_path break fi done <<< "$(echo "$emulator_section" | xmllint --xpath "//rule[@type='systempath']/entry" - 2>/dev/null)" # If not found, search staticpath entries if [ -z "$found_path" ]; then while IFS= read -r line; do command_path=$(eval echo "$line" | sed -n 's/.*\(.*\)<\/entry>.*/\1/p') if [ -x "$command_path" ]; then found_path=$command_path break fi done <<< "$(echo "$emulator_section" | xmllint --xpath "//rule[@type='staticpath']/entry" - 2>/dev/null)" fi if [ -z "$found_path" ]; then log e "Find emulator: no valid path found for emulator: $emulator_name" return 1 else log d "Find emulator: found emulator \"$found_path\"" echo "$found_path" return 0 fi } # Function to find the emulator name from the label in es_systems.xml find_emulator_name_from_label() { local label="$1" # Search for the emulator matching the label in the es_systems.xml file extracted_emulator_name=$(xmllint --recover --xpath "string(//system[name='$system']/command[@label='$label']/text())" "$es_systems" 2>/dev/null | sed 's/%//g' | sed 's/EMULATOR_//g' | cut -d' ' -f1) log d "Found emulator from label: $extracted_emulator_name" emulator_command=$(find_emulator "$extracted_emulator_name") if [[ -n "$emulator_command" ]]; then echo "$emulator_command" else log e "Found emulator from label: emulator name not found for label: $label" return 1 fi }