RetroPie-Setup/scriptmodules/helpers.sh
Jools Wills 82bbd5b0ec
Merge pull request #3967 from s1eve-mcdichae1/genesis-standalone
fn mkRomDir - create symlink only if 'genesis' not already exist
2024-09-03 20:44:24 +01:00

1745 lines
60 KiB
Bash
Executable file

#!/bin/bash
# This file is part of The RetroPie Project
#
# The RetroPie Project is the legal property of its developers, whose names are
# too numerous to list here. Please refer to the COPYRIGHT.md file distributed with this source.
#
# See the LICENSE.md file at the top-level directory of this distribution and
# at https://raw.githubusercontent.com/RetroPie/RetroPie-Setup/master/LICENSE.md
#
## @file helpers.sh
## @brief RetroPie helpers library
## @copyright GPLv3
## @fn printMsgs()
## @param type style of display to use - dialog, console or heading
## @param message string or array of messages to display
## @brief Prints messages in a variety of ways.
function printMsgs() {
local type="$1"
shift
if [[ "$__nodialog" == "1" && "$type" == "dialog" ]]; then
type="console"
fi
for msg in "$@"; do
[[ "$type" == "dialog" ]] && dialog --backtitle "$__backtitle" --cr-wrap --no-collapse --msgbox "$msg" 20 60 >/dev/tty
[[ "$type" == "console" ]] && echo -e "$msg"
[[ "$type" == "heading" ]] && echo -e "\n= = = = = = = = = = = = = = = = = = = = =\n$msg\n= = = = = = = = = = = = = = = = = = = = =\n"
done
return 0
}
## @fn printHeading()
## @param message string or array of messages to display
## @brief Calls PrintMsgs with "heading" type.
function printHeading() {
printMsgs "heading" "$@"
}
## @fn fatalError()
## @param message string or array of messages to display
## @brief Calls PrintMsgs with "heading" type, and exits immediately.
function fatalError() {
printHeading "Error"
echo -e "$1"
joy2keyStop
exit 1
}
# @fn fnExists()
# @param name name of function to check for
# @brief Checks if function name exists.
# @retval 0 if the function name exists
# @retval 1 if the function name does not exist
function fnExists() {
declare -f "$1" > /dev/null
return $?
}
function ask() {
echo -e -n "$@" '[y/n] ' ; read ans
case "$ans" in
y*|Y*) return 0 ;;
*) return 1 ;;
esac
}
## @fn runCmd()
## @param command command to run
## @brief Calls command and record any non zero return codes for later printing.
## @return whatever the command returns.
function runCmd() {
local ret
"$@"
ret=$?
if [[ "$ret" -ne 0 ]]; then
md_ret_errors+=("Error running '$*' - returned $ret")
fi
return $ret
}
## @fn hasFlag()
## @param string string to search in
## @param flag flag to search for
## @brief Checks for a flag in a string (consisting of space separated flags).
## @retval 0 if the flag was found
## @retval 1 if the flag was not found
function hasFlag() {
local string="$1"
local flag="$2"
[[ -z "$string" || -z "$flag" ]] && return 1
if [[ "$string" =~ (^| )$flag($| ) ]]; then
return 0
else
return 1
fi
}
## @fn isPlatform()
## @param platform
## @brief Test for current platform / platform flags.
function isPlatform() {
local flag="$1"
if hasFlag "${__platform_flags[*]}" "$flag"; then
return 0
fi
return 1
}
## @fn addLineToFile()
## @param line line to add
## @param file file to add line to
## @brief Adds a new line of text to a file.
function addLineToFile() {
if [[ -f "$2" ]]; then
cp -p "$2" "$2.bak"
else
sed -i --follow-symlinks '$a\' "$2"
fi
echo "$1" >> "$2"
}
## @fn editFile()
## @param file file to edit
## @brief Opens an editing dialog for specified file.
function editFile() {
local file="$1"
local cmd=(dialog --backtitle "$__backtitle" --editbox "$file" 22 76)
local choice=$("${cmd[@]}" 2>&1 >/dev/tty)
[[ -n "$choice" ]] && echo "$choice" >"$file"
}
## @fn inputBox()
## @param title title of dialog
## @param text default text
## @param minchars minimum chars to accept
## @brief Opens an inputbox dialog and echoes resulting text. Uses the OSK if installed.
## @details The input dialog has OK/Cancel buttons and can be cancelled by the user.
## The dialog will enforce the minimum number of characters expected, re-prompting the user.
## @retval 0 when the user entered the text and chose the OK button
## @retval != 0 when the user chose the Cancel button
function inputBox() {
local title="$1"
local text="$2"
local minchars="$3"
[[ -z "$minchars" ]] && minchars=0
local params=(--backtitle "$__backtitle" --inputbox "Enter the $title")
local osk="$(rp_getInstallPath joy2key)/osk.py"
if [[ -f "$osk" ]]; then
params+=(--minchars "$minchars")
text=$(python3 "$osk" "${params[@]}" "$text" 2>&1 >/dev/tty) || return $?
else
while true; do
text=$(dialog "${params[@]}" 10 60 "$text" 2>&1 >/dev/tty) || return $?
[[ "${#text}" -ge "$minchars" ]] && break
dialog --msgbox "$title must have at least $minchars characters" 8 60 2>&1 >/dev/tty
done
fi
echo "$text"
}
## @fn hasPackage()
## @param package name of Debian package
## @param version requested version (optional)
## @param comparison type of comparison - defaults to `ge` (greater than or equal) if a version parameter is provided.
## @brief Test for an installed Debian package / package version.
## @retval 0 if the requested package / version was installed
## @retval 1 if the requested package / version was not installed
function hasPackage() {
local pkg="$1"
local req_ver="$2"
local comp="$3"
[[ -z "$comp" ]] && comp="ge"
local ver
local status
# extract the first line only (for cases where both amd64 & i386 versions of a package are installed)
local out=$(dpkg-query -W --showformat='${Status} ${Version}\n' $1 2>/dev/null | head -n1)
if [[ "$?" -eq 0 ]]; then
ver="${out##* }"
status="${out% *}"
fi
local installed=0
[[ "$status" == *"ok installed" ]] && installed=1
# if we are not checking version
if [[ -z "$req_ver" ]]; then
# if the package is installed return true
[[ "$installed" -eq 1 ]] && return 0
else
# if checking version and the package is not installed we need to clear "ver" as it may contain
# the version number of a removed package and give a false positive with compareVersions.
# we still need to do the version check even if not installed due to the varied boolean operators
[[ "$installed" -eq 0 ]] && ver=""
compareVersions "$ver" "$comp" "$req_ver" && return 0
fi
return 1
}
## @fn aptUpdate()
## @brief Calls apt-get update (if it has not been called before).
function aptUpdate() {
if [[ "$__apt_update" != "1" ]]; then
apt-get update --allow-releaseinfo-change
__apt_update="1"
fi
}
## @fn aptInstall()
## @param packages package / space separated list of packages to install
## @brief Calls apt-get install with the packages provided.
function aptInstall() {
aptUpdate
apt-get install -y "$@"
return $?
}
## @fn aptRemove()
## @param packages package / space separated list of packages to install
## @brief Calls apt-get remove with the packages provided.
function aptRemove() {
aptUpdate
apt-get remove -y "$@"
return $?
}
function _mapPackage() {
local pkg="$1"
case "$pkg" in
libraspberrypi-bin)
isPlatform "osmc" && pkg="rbp-userland-osmc"
isPlatform "xbian" && pkg="xbian-package-firmware"
;;
libraspberrypi-dev)
isPlatform "osmc" && pkg="rbp-userland-dev-osmc"
isPlatform "xbian" && pkg="xbian-package-firmware"
;;
mali-fbdev)
isPlatform "vero4k" && pkg=""
;;
# handle our custom package alias LINUX-HEADERS
LINUX-HEADERS)
if isPlatform "rpi"; then
if [[ "$__os_debian_ver" -lt 12 ]]; then
pkg="raspberrypi-kernel-headers"
else
# on RaspiOS bookworm and later, kernel packages are separated by arch and model
isPlatform "rpi0" || isPlatform "rpi1" && pkg="linux-headers-rpi-v6"
if isPlatform "32bit"; then
isPlatform "rpi2" || isPlatform "rpi3" && pkg="linux-headers-rpi-v7"
isPlatform "rpi4" && pkg="linux-headers-rpi-v7l"
else
isPlatform "rpi3" || isPlatform "rpi4" && pkg="linux-headers-rpi-v8"
isPlatform "rpi5" && pkg="linux-headers-rpi-2712"
fi
fi
elif isPlatform "armbian"; then
local branch="$(grep -oP "BRANCH=\K.*" /etc/armbian-release)"
local family="$(grep -oP "LINUXFAMILY=\K.*" /etc/armbian-release)"
pkg="linux-headers-${branch}-${family}"
elif [[ -z "$__os_ubuntu_ver" ]]; then
pkg="linux-headers-$(uname -r)"
else
pkg="linux-headers-generic"
fi
;;
# map libpng-dev to libpng12-dev for Jessie
libpng-dev)
[[ "$__os_debian_ver" -lt 9 ]] && pkg="libpng12-dev"
;;
libsdl1.2-dev)
rp_isEnabled "sdl1" && pkg="RP sdl1 $pkg"
;;
libsdl2-dev)
if rp_isEnabled "sdl2"; then
# check whether to use our own sdl2 - can be disabled to resolve issues with
# mixing custom 64bit sdl2 and os distributed i386 version on multiarch
local own_sdl2=1
# default to off for x11 targets due to issues with dependencies with recent
# Ubuntu (19.04). eg libavdevice58 requiring exactly 2.0.9 sdl2.
isPlatform "x11" && own_sdl2=0
iniConfig " = " '"' "$configdir/all/retropie.cfg"
iniGet "own_sdl2"
if [[ "$ini_value" == "1" ]]; then
own_sdl2=1
elif [[ "$ini_value" == "0" ]]; then
own_sdl2=0
fi
[[ "$own_sdl2" -eq 1 ]] && pkg="RP sdl2 $pkg"
fi
;;
libfreetype6-dev)
[[ "$__os_debian_ver" -gt 10 ]] || compareVersions "$__os_ubuntu_ver" gt 23.04 && pkg="libfreetype-dev"
;;
esac
echo "$pkg"
}
## @fn getDepends()
## @param packages package / space separated list of packages to install
## @brief Installs packages if they are not installed.
## @retval 0 on success
## @retval 1 on failure
function getDepends() {
local own_pkgs=()
local apt_pkgs=()
local all_pkgs=()
local pkg
for pkg in "$@"; do
pkg=($(_mapPackage "$pkg"))
# manage our custom packages (pkg = "RP module_id pkg_name")
if [[ "${pkg[0]}" == "RP" ]]; then
# if removing, check if any version is installed and queue for removal via the custom module
if [[ "$md_mode" == "remove" ]]; then
if hasPackage "${pkg[2]}"; then
own_pkgs+=("${pkg[1]}")
all_pkgs+=("${pkg[2]}(custom)")
fi
else
# if installing check if our version is installed and queue for installing via the custom module
if hasPackage "${pkg[2]}" $(get_pkg_ver_${pkg[1]}) "ne"; then
own_pkgs+=("${pkg[1]}")
all_pkgs+=("${pkg[2]}(custom)")
fi
fi
continue
fi
if [[ "$md_mode" == "remove" ]]; then
# add package to apt_pkgs for removal if installed
if hasPackage "$pkg"; then
apt_pkgs+=("$pkg")
all_pkgs+=("$pkg")
fi
else
# add package to apt_pkgs for installation if not installed
if ! hasPackage "$pkg"; then
apt_pkgs+=("$pkg")
all_pkgs+=("$pkg")
fi
fi
done
# return if no packages required
[[ ${#apt_pkgs[@]} -eq 0 && ${#own_pkgs[@]} -eq 0 ]] && return
# if we are removing, then remove packages, do an autoremove to clean up additional packages and return
if [[ "$md_mode" == "remove" ]]; then
printMsgs "console" "Removing dependencies: ${all_pkgs[*]}"
for pkg in ${own_pkgs[@]}; do
rp_callModule "$pkg" remove
done
apt-get remove --purge -y "${apt_pkgs[@]}"
apt-get autoremove --purge -y
return 0
fi
printMsgs "console" "Did not find needed dependencies: ${all_pkgs[*]}. Trying to install them now."
# install any custom packages
for pkg in ${own_pkgs[@]}; do
rp_callModule "$pkg" _auto_
done
aptInstall --no-install-recommends "${apt_pkgs[@]}"
local failed=()
# check the required packages again rather than return code of apt-get,
# as apt-get might fail for other reasons (eg other half installed packages)
for pkg in ${apt_pkgs[@]}; do
if ! hasPackage "$pkg"; then
# workaround for installing samba in a chroot (fails due to failed smbd service restart)
# we replace the init.d script with an empty script so the install completes
if [[ "$pkg" == "samba" && "$__chroot" -eq 1 ]]; then
mv /etc/init.d/smbd /etc/init.d/smbd.old
echo "#!/bin/sh" >/etc/init.d/smbd
chmod u+x /etc/init.d/smbd
apt-get -f install
mv /etc/init.d/smbd.old /etc/init.d/smbd
else
failed+=("$pkg")
fi
fi
done
if [[ ${#failed[@]} -gt 0 ]]; then
md_ret_errors+=("Could not install package(s): ${failed[*]}.")
return 1
fi
return 0
}
## @fn rpSwap()
## @param command *on* to add swap if needed and *off* to remove later
## @param memory total memory needed (swap added = memory needed - available memory)
## @brief Adds additional swap to the system if needed.
function rpSwap() {
local command=$1
local swapfile="$__swapdir/swap"
case $command in
on)
rpSwap off
local needed=$2
local size=$((needed - __memory_avail))
mkdir -p "$__swapdir/"
if [[ $size -ge 0 ]]; then
echo "Adding $size MB of additional swap"
fallocate -l ${size}M "$swapfile"
chmod 600 "$swapfile"
mkswap "$swapfile"
swapon "$swapfile"
fi
;;
off)
echo "Removing additional swap"
swapoff "$swapfile" 2>/dev/null
rm -f "$swapfile"
;;
esac
}
## @fn gitPullOrClone()
## @param dest destination directory
## @param repo repository to clone or pull from
## @param branch branch to clone or pull from (optional)
## @param commit specific commit to checkout (optional - requires branch to be set)
## @param depth depth parameter for git. (optional)
## @brief Git clones or pulls a repository.
## @details depth parameter will default to 1 (shallow clone) so long as __persistent_repos isn't set.
## A depth parameter of 0 will do a full clone with all history.
function gitPullOrClone() {
local dir="$1"
[[ -z "$dir" ]] && dir="$md_build"
local repo="$2"
local branch="$3"
local commit="$4"
local depth="$5"
# if repo is blank then use the rp_module_repo info
if [[ -z "$repo" && -n "$md_repo_url" ]]; then
repo="$(rp_resolveRepoParam "$md_repo_url")"
branch="$(rp_resolveRepoParam "$md_repo_branch")"
commit="$(rp_resolveRepoParam "$md_repo_commit")"
fi
[[ -z "$repo" ]] && return 1
[[ -z "$branch" ]] && branch="master"
# if no depth is provided default to shallow clone (depth 1)
[[ -z "$depth" ]] && depth=1
# if we are using persistent repos or checking out a specific commit, don't shallow clone
if [[ "$__persistent_repos" -eq 1 || -n "$commit" ]]; then
depth=0
fi
# record the source directory in __mod_info[ID/repo_dir] if not previously set which will be used
# by the packaging functions later to grab repository information
if [[ -z "${__mod_info[$md_id/repo_dir]}" ]]; then
__mod_info[$md_id/repo_dir]="$dir"
fi
if [[ -d "$dir/.git" ]]; then
pushd "$dir" > /dev/null
# if we are using persistent repos, fetch the latest remote changes and clean the source so
# any patches can be re-applied as needed.
if [[ "$__persistent_repos" -eq 1 ]]; then
runCmd git fetch
runCmd git reset --hard
runCmd git clean -f -d
fi
runCmd git checkout "$branch"
# only try to pull if we are on a tracking branch
if [[ -n "$(git config --get branch.$branch.merge)" ]]; then
runCmd git pull --ff-only
runCmd git submodule update --init --recursive
fi
popd > /dev/null
else
local git="git clone --recursive"
if [[ "$depth" -gt 0 ]]; then
git+=" --depth $depth --shallow-submodules"
fi
git+=" --branch $branch"
printMsgs "console" "$git \"$repo\" \"$dir\""
runCmd $git "$repo" "$dir"
fi
if [[ -n "$commit" ]]; then
printMsgs "console" "Winding back $repo->$branch to commit: #$commit"
git -C "$dir" branch -D "$commit" &>/dev/null
runCmd git -C "$dir" checkout -f "$commit" -b "$commit"
fi
branch=$(runCmd git -C "$dir" rev-parse --abbrev-ref HEAD)
commit=$(runCmd git -C "$dir" rev-parse HEAD)
printMsgs "console" "HEAD is now in branch '$branch' at commit '$commit'"
}
# @fn setupDirectories()
# @brief Makes sure some required retropie directories and files are created.
function setupDirectories() {
mkdir -p "$rootdir"
mkUserDir "$datadir"
mkUserDir "$romdir"
mkUserDir "$biosdir"
mkUserDir "$configdir"
mkUserDir "$configdir/all"
# some home folders for configs that modules rely on
mkUserDir "$home/.cache"
mkUserDir "$home/.config"
mkUserDir "$home/.local"
mkUserDir "$home/.local/share"
# make sure we have inifuncs.sh in place and that it is up to date
mkdir -p "$rootdir/lib"
local helper
for helper in inifuncs.sh archivefuncs.sh; do
if [[ ! -f "$rootdir/lib/$helper" || "$rootdir/lib/$helper" -ot "$scriptdir/scriptmodules/$helper" ]]; then
cp --preserve=timestamps "$scriptdir/scriptmodules/$helper" "$rootdir/lib/$helper"
fi
done
# create template for autoconf.cfg and make sure it is owned by $__user
local config="$configdir/all/autoconf.cfg"
if [[ ! -f "$config" ]]; then
echo "# this file can be used to enable/disable retropie autoconfiguration features" >"$config"
fi
chown "$__user":"$__group" "$config"
}
## @fn rmDirExists()
## @param dir directory to remove
## @brief Removes a directory and all contents if it exists.
function rmDirExists() {
if [[ -d "$1" ]]; then
rm -rf "$1"
fi
}
## @fn mkUserDir()
## @param dir directory to create
## @brief Creates a directory owned by the current user.
function mkUserDir() {
mkdir -p "$1"
chown "$__user":"$__group" "$1"
}
## @fn mkRomDir()
## @param dir rom directory to create
## @brief Creates a directory under $romdir owned by the current user.
function mkRomDir() {
mkUserDir "$romdir/$1"
if [[ "$1" == "megadrive" ]]; then
if [[ ! -e "$romdir/genesis" ]]; then
pushd "$romdir"
ln -snf "$1" "genesis"
popd
fi
fi
}
## @fn moveConfigDir()
## @param from source directory
## @param to destination directory
## @brief Moves the contents of a folder and symlinks to the new location.
function moveConfigDir() {
local from="$1"
local to="$2"
# if we are in remove mode - remove the symlink
if [[ "$md_mode" == "remove" ]]; then
[[ -h "$from" ]] && rm -f "$from"
return
fi
mkUserDir "$to"
# move any old configs to the new location
if [[ -d "$from" && ! -h "$from" ]]; then
cp -a "$from/." "$to/"
rm -rf "$from"
fi
ln -snf "$to" "$from"
# set ownership of the actual link to $__user
chown -h "$__user":"$__group" "$from"
}
## @fn moveConfigFile()
## @param from source file
## @param to destination file
## @brief Moves the file and symlinks to the new location.
function moveConfigFile() {
local from="$1"
local to="$2"
# if we are in remove mode - remove the symlink
if [[ "$md_mode" == "remove" && -h "$from" ]]; then
rm -f "$from"
return
fi
# move old file
if [[ -f "$from" && ! -h "$from" ]]; then
mv "$from" "$to"
fi
ln -sf "$to" "$from"
# set ownership of the actual link to $__user
chown -h "$__user":"$__group" "$from"
}
## @fn diffFiles()
## @param file1 file to compare
## @param file2 file to compare
## @brief Compares two files using diff.
## @retval 0 if the files were the same
## @retval 1 if they were not
## @retval >1 an error occurred
function diffFiles() {
diff -q "$1" "$2" >/dev/null
return $?
}
## @fn compareVersions()
## @param version first version to compare
## @param operator operator to use (lt le eq ne ge gt)
## @brief version second version to compare
## @retval 0 if the comparison was true
## @retval 1 if the comparison was false
function compareVersions() {
dpkg --compare-versions "$1" "$2" "$3" >/dev/null
return $?
}
## @fn dirIsEmpty()
## @param path path to directory
## @param files_only set to 1 to ignore sub directories
## @retval 0 if the directory is empty
## @retval 1 if the directory is not empty
function dirIsEmpty() {
if [[ "$2" -eq 1 ]]; then
[[ -z "$(ls -lA1 "$1" | grep "^-")" ]] && return 0
else
[[ -z "$(ls -A "$1")" ]] && return 0
fi
return 1
}
## @fn copyDefaultConfig()
## @param from source file
## @param to destination file
## @brief Copies a default configuration.
## @details Copies from the source file to the destination file if the destination
## file doesn't exist. If the destination is the same nothing is done. If different
## the source is copied to `$destination.rp-dist`.
function copyDefaultConfig() {
local from="$1"
local to="$2"
# if the destination exists, and is different then copy the config as name.rp-dist
if [[ -f "$to" ]]; then
if ! diffFiles "$from" "$to"; then
to+=".rp-dist"
printMsgs "console" "Copying new default configuration to $to"
cp "$from" "$to"
fi
else
printMsgs "console" "Copying default configuration to $to"
cp "$from" "$to"
fi
chown "$__user":"$__group" "$to"
}
## @fn renameModule()
## @param from source file
## @param to destination file
## @brief Renames an existing module.
## @details Renames an existing module, moving it's install folder to the new location
## and changing any references to it in `emulators.cfg`.
function renameModule() {
local from="$1"
local to="$2"
# move from old location and update emulators.cfg
if [[ -d "$rootdir/$md_type/$from" ]]; then
rm -rf "$rootdir/$md_type/$to"
mv "$rootdir/$md_type/$from" "$rootdir/$md_type/$to"
# replace any default = "$from"
sed -i --follow-symlinks "s/\"$from\"/\"$to\"/g" "$configdir"/*/emulators.cfg
# replace any $from = "cmdline"
sed -i --follow-symlinks "s/^$from\([ =]\)/$to\1/g" "$configdir"/*/emulators.cfg
# replace any paths with /$from/
sed -i --follow-symlinks "s|/$from/|/$to/|g" "$configdir"/*/emulators.cfg
fi
}
## @fn addUdevInputRules()
## @brief Creates a udev rule to adjust input device permissions.
## @details Creates a udev rule in `/etc/udev/rules.d/99-input.rules` to
## make everything in `/dev/input` it writable by any user in group `input`.
function addUdevInputRules() {
if [[ ! -f /etc/udev/rules.d/99-input.rules ]]; then
echo 'SUBSYSTEM=="input", GROUP="input", MODE="0660"' > /etc/udev/rules.d/99-input.rules
fi
# remove old 99-evdev.rules
rm -f /etc/udev/rules.d/99-evdev.rules
}
## @fn setBackend()
## @param emulator.cfg key to configure backend for
## @param backend name of the backend to set
## @param force set to 1 to force the change
## @brief Set a backend rendering driver for a module
## @details Set a backend rendering driver for a module - can be currently default, dispmanx or x11.
## This function will only set a backend if
## - It's not already configured, or
## - The 3rd parameter (force) is set to 1
## The emulator.cfg key is usually the module_id but some modules add multiple emulator.cfg entries
## which are all handled separately. A module can use a _backend_set_MODULE function hook which is called
## from the backends module to handle calling setBackend for additional emulator.cfg entries.
## See "fuse" scriptmodule for an example.
function setBackend() {
local config="$configdir/all/backends.cfg"
local id="$1"
local mode="$2"
local force="$3"
iniConfig "=" "\"" "$config"
iniGet "$id"
if [[ "$force" -eq 1 || -z "$ini_value" ]]; then
iniSet "$id" "$mode"
chown "$__user":"$__group" "$config"
fi
}
## @fn getBackend()
## @param emulator.cfg key to get backend for
## @brief Get a backend rendering driver for a module
## @details Get a backend rendering driver for a module
## The function echos the result so the value can be captured using var=$(getBackend "$module_id")
function getBackend() {
local config="$configdir/all/backends.cfg"
local id="$1"
iniConfig " = " '"' "$config"
iniGet "$id"
if [[ -n "$ini_value" ]]; then
# translate old value of 1 as dispmanx for backward compatibility
[[ "$ini_value" == "1" ]] && ini_value="dispmanx"
else
ini_value="default"
fi
echo "$ini_value"
}
## @fn setDispmanx()
## @param module_id name of module to add dispmanx flag for
## @param status initial status of flag (0 or 1)
## @brief Sets a dispmanx flag for a module. This function is deprecated.
## @details Set a dispmanx flag for a module as to whether it should use the
## sdl1 dispmanx backend by default or not (0 for framebuffer, 1 for dispmanx).
## This function is deprecated and instead setBackend should be used.
function setDispmanx() {
isPlatform "dispmanx" || return
setBackend "$1" "dispmanx"
}
## @fn iniFileEditor()
## @param delim ini file delimiter eg. ' = '
## @param quote ini file quoting character eg. '"'
## @param config ini file to edit
## @brief Allows editing of ini files with a user friendly dialog based gui.
## @details Some arrays need to be configured before calling this, which are
## used to display what can be edited and the options available.
##
## The first array is `$ini_titles` which provides the titles for each
## entry..
##
## The second array is `$ini_descs` which contains a help description for each
## entry.
##
## The third array is `$ini_options` which contains multiple space separated
## strings in each element to control how each entry should be managed.
##
## The `$ini_options` array is constructed as follows:
##
## If the first string is `_function_` then the next string should be a function
## name that will handle that entry. The function will be called with a parameter
## `get` or `set`. The function should return the value for get via `echo`
## and should handle any gui functionality when called with `set`. This can be
## used for example to build custom dialogs.
##
## If the first option is anything else, it is assumed to be a key name, followed
## by a control type and a list of parameters.
##
## Control types are:
## * `_id_` map the following values to an id
## * `_string_` allow the value to be inputted by the user
## * `_file_` select from a list of files. The following values are wildcard,
## then file path.
##
## If none of the above, then the rest of the array element should be a list of
## possible values for the key.
##
## Some examples for ini_options:
##
## ini_options=('video_smooth true false')
## Allow setting of the key `video_smooth` with the values of *true* or *false*
##
## ini_options=('aspect_ratio_index _id_ 4:3 16:9 16:10)
## Allow setting of the key `aspect_ratio_index` with the values 0 1 or 2 which
## correspond to the ratios. The user is shown the ratios, but the ini configuration
## is set to the id (4:3 = 0, 16:9 = 1, 16:10 = 2).
##
## ini_options=('_function_ _video_fullscreen_configedit')
## The function `_video_fullscreen_configedit` is called with *get* or *set*
## to manage this entry.
##
## ini_options=("video_shader _file_ *.*p $rootdir/emulators/retroarch/shader")
## The key `video_shader` will be able to be set to a list of files in
## `$rootdir/emulators/retroarch/shader` that match the wildcard `*.*p`
##
## For more examples you can check out the code in supplementary/configedit.sh
function iniFileEditor() {
local delim="$1"
local quote="$2"
local config="$3"
[[ ! -f "$config" ]] && return
iniConfig "$delim" "$quote" "$config"
local sel
local value
local option
local title
while true; do
local options=()
local params=()
local values=()
local keys=()
local i=0
# generate menu from options
for option in "${ini_options[@]}"; do
# split into new array (globbing safe)
read -ra option <<<"$option"
key="${option[0]}"
keys+=("$key")
params+=("${option[*]:1}")
# if the first parameter is _function_ we call the second parameter as a function
# so we can handle some options with a custom menu etc
if [[ "$key" == "_function_" ]]; then
value="$(${option[1]} get)"
else
# get current value
iniGet "$key"
if [[ -n "$ini_value" ]]; then
value="$ini_value"
else
value="unset"
fi
fi
values+=("$value")
# add the matching value to our id in _id_ lists
if [[ "${option[1]}" == "_id_" && "$value" != "unset" ]]; then
value+=" - ${option[value+2]}"
fi
# use custom title if provided
if [[ -n "${ini_titles[i]}" ]]; then
title="${ini_titles[i]}"
else
title="$key"
fi
options+=("$i" "$title ($value)" "${ini_descs[i]}")
((i++))
done
local cmd=(dialog --backtitle "$__backtitle" --default-item "$sel" --item-help --help-button --menu "Please choose the setting to modify in $config" 22 76 16)
sel=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty)
if [[ "${sel[@]:0:4}" == "HELP" ]]; then
printMsgs "dialog" "${sel[@]:5}"
continue
fi
[[ -z "$sel" ]] && break
# if the key is _function_ we handle the option with a custom function
if [[ "${keys[sel]}" == "_function_" ]]; then
"${params[sel]}" set "${values[sel]}"
continue
fi
# process the editing of the option
i=0
options=("U" "unset")
local default=""
# split into new array (globbing safe)
read -ra params <<<"${params[sel]}"
local mode="${params[0]}"
case "$mode" in
_string_)
options+=("E" "Edit (Currently ${values[sel]})")
;;
_file_)
local match="${params[1]}"
local path="${params[*]:2}"
local file
while read file; do
[[ "${values[sel]}" == "$file" ]] && default="$i"
file="${file//$path\//}"
options+=("$i" "$file")
((i++))
done < <(find -L "$path" -type f -name "$match" | sort)
;;
_id_|*)
[[ "$mode" == "_id_" ]] && params=("${params[@]:1}")
for option in "${params[@]}"; do
if [[ "$mode" == "_id_" ]]; then
[[ "${values[sel]}" == "$i" ]] && default="$i"
else
[[ "${values[sel]}" == "$option" ]] && default="$i"
fi
options+=("$i" "$option")
((i++))
done
;;
esac
[[ -z "$default" ]] && default="U"
# display values
cmd=(dialog --backtitle "$__backtitle" --default-item "$default" --menu "Please choose the value for ${keys[sel]}" 22 76 16)
local choice=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty)
# if it is a _string_ type we will open an inputbox dialog to get a manual value
if [[ -z "$choice" ]]; then
continue
elif [[ "$choice" == "E" ]]; then
[[ "${values[sel]}" == "unset" ]] && values[sel]=""
cmd=(dialog --backtitle "$__backtitle" --inputbox "Please enter the value for ${keys[sel]}" 10 60 "${values[sel]}")
value=$("${cmd[@]}" 2>&1 >/dev/tty)
elif [[ "$choice" == "U" ]]; then
value=""
else
if [[ "$mode" == "_id_" ]]; then
value="$choice"
else
# get the actual value from the options array
local index=$((choice*2+3))
if [[ "$mode" == "_file_" ]]; then
value="$path/${options[index]}"
else
value="${options[index]}"
fi
fi
fi
if [[ "$choice" == "U" ]]; then
iniUnset "${keys[sel]}" "$value"
else
iniSet "${keys[sel]}" "$value"
fi
done
}
## @fn setESSystem()
## @param fullname full name of system
## @param name short name of system
## @param path rom path
## @param extension file extensions to show
## @param command command to run
## @param platform name of platform (used by es for scraping)
## @param theme name of theme to use
## @brief Adds a system entry for Emulation Station (to /etc/emulationstation/es_systems.cfg).
function setESSystem() {
local function
for function in $(compgen -A function _add_system_); do
"$function" "$@"
done
}
## @fn ensureSystemretroconfig()
## @param system system to create retroarch.cfg for
## @brief Deprecated - use defaultRAConfig
## @details Creates a default retroarch.cfg for specified system in `$configdir/$system/retroarch.cfg`.
function ensureSystemretroconfig() {
# don't do any config work on module removal
[[ "$md_mode" == "remove" ]] && return
# reset "$md_conf_root" to "$configdir" as defaultRAConfig handles this whereas ensureSystemretroconfig
# expects system to include any subdirectory in the first parameter such as "ports/$system".
local save_conf_root="$md_conf_root"
md_conf_root="$configdir"
defaultRAConfig "$1"
md_conf_root="$save_conf_root"
}
## @fn defaultRAConfig()
## @param system system to create retroarch.cfg for
## @param ... optional key then value parameters to be used in the config
## @brief Creates a default retroarch.cfg for specified system in `$md_root_dir/$system/retroarch.cfg`.
## @details Additional default configuration values can be provided as parameters to the function - eg. "fps_show" "true"
## as two parameters would add a default entry of fps_show = "true" to the default configuration.
## This function uses $md_conf_root as a base, so there is no need to use "ports/$system" for libretro ports as with
## the older ensureSystemretroconfig
function defaultRAConfig() {
# don't do any config work on module removal
[[ "$md_mode" == "remove" ]] && return
local system="$1"
shift
local defaults=("$@")
local config_path="$md_conf_root/$system"
[[ ! -d "$config_path" ]] && mkUserDir "$config_path"
local config="$(mktemp)"
# add the initial comment regarding include order
echo -e "# Settings made here will only override settings in the global retroarch.cfg if placed above the #include line\n" >"$config"
# add the per system default settings
iniConfig " = " '"' "$config"
iniSet "input_remapping_directory" "$config_path"
# add any additional config key / values from function parameters
local key
local value
while read key value; do
[[ -n "$key" ]] && iniSet "$key" "$value"
done <<< "${defaults[@]}"
# include the main retroarch config
echo -e "\n#include \"$configdir/all/retroarch.cfg\"" >>"$config"
copyDefaultConfig "$config" "$config_path/retroarch.cfg"
rm "$config"
}
## @fn setRetroArchCoreOption()
## @param option option to set
## @param value value to set
## @brief Sets a retroarch core option in `$configdir/all/retroarch-core-options.cfg`.
function setRetroArchCoreOption() {
local option="$1"
local value="$2"
iniConfig " = " "\"" "$configdir/all/retroarch-core-options.cfg"
iniGet "$option"
if [[ -z "$ini_value" ]]; then
iniSet "$option" "$value"
fi
chown "$__user":"$__group" "$configdir/all/retroarch-core-options.cfg"
}
## @fn setConfigRoot()
## @param dir directory under $configdir to use
## @brief Sets module config root `$md_conf_root` to subfolder from `$configdir`
## @details This is used for ports that are not actually in scriptmodules/ports
## as they would get the wrong config root otherwise.
function setConfigRoot() {
local dir="$1"
md_conf_root="$configdir"
[[ -n "$dir" ]] && md_conf_root+="/$dir"
mkUserDir "$md_conf_root"
}
## @fn loadModuleConfig()
## @param params space separated list of key=value parameters
## @brief Load the settings for a module.
## @details This allows modules to quickly load some settings from an ini file.
## It can provide a shortcut way to load a set of keys from an ini file into
## variables.
##
## It requires iniConfig to be called first to specify the format and file.
## eg.
##
## iniConfig " = " '"' "$configdir/all/mymodule.cfg"
## eval $(loadModuleConfig \
## 'some_option=1' \
## 'another_option=2'
##
## This would load the keys `some_option` and `another_option` into local
## variables `some_option` and `another_option`. If the keys did not exist
## in mymodule.cfg the variables would be initialised to 1 and 2.
function loadModuleConfig() {
local options=("$@")
local option
local key
local value
for option in "${options[@]}"; do
option=(${option/=/ })
key="${option[0]}"
value="${option[@]:1}"
iniGet "$key"
if [[ -z "$ini_value" ]]; then
iniSet "$key" "$value"
echo "local $key=\"$value\""
else
echo "local $key=\"$ini_value\""
fi
done
}
## @fn applyPatch()
## @param patch filename of patch to apply
## @brief Apply a patch if it has not already been applied to current folder.
## @details This is used for applying patches against upstream code.
## @retval 0 on success
## @retval 1 on failure
function applyPatch() {
local patch="$1"
local patch_applied="${patch##*/}.applied"
if [[ ! -f "$patch_applied" ]]; then
if patch -f -p1 <"$patch"; then
touch "$patch_applied"
printMsgs "console" "Successfully applied patch: $patch"
else
md_ret_errors+=("$md_id patch $patch failed to apply")
return 1
fi
fi
return 0
}
## @fn runCurl()
## @params ... commandline arguments to pass to curl
## @brief Run curl with chosen parameters and handle curl errors
## @details Runs curl with the provided parameters, whilst also capturing the output and extracting
## any error message, which is stored in the global variable __NET_ERRMSG. Function returns the return
## code provided by curl. The environment variable __curl_opts can be set to override default curl
## parameters, eg - timeouts etc.
## @retval curl return value
function runCurl() {
local params=("$@")
# add any user supplied curl opts - timeouts can be overridden as curl uses the last parameters given
[[ -n "$__curl_opts" ]] && params+=($__curl_opts)
local cmd_err
local ret
# get the last non zero exit status (ignoring tee)
set -o pipefail
# set up additional file descriptor for stdin
exec 3>&1
# capture stderr - while passing both stdout and stderr to terminal
# curl like wget outputs the progress meter to stderr, so we will extract the error line later
cmd_err=$(curl "${params[@]}" 2>&1 1>&3 | tee /dev/stderr)
ret="$?"
# remove stdin copy
exec 3>&-
set +o pipefail
# if there was an error, extract it and put in __NET_ERRMSG
if [[ "$ret" -ne 0 ]]; then
# as we also capture the curl progress output, extract the last line which contains the error
__NET_ERRMSG="${cmd_err##*$'\n'}"
else
__NET_ERRMSG=""
fi
return "$ret"
}
## @fn download()
## @param url url of file
## @param dest destination name (optional), use - for stdout
## @brief Download a file
## @details Download a file - if the dest parameter is omitted, the file will be downloaded to the current directory.
## If the destination name is a hyphen (-), then the file will be outputted to stdout, for piping to another command
## or retrieving the contents directly to a variable. If the destination is a folder, extract with the basename from
## the url to the destination folder.
## @retval 0 on success
function download() {
local url="$1"
local dest="$2"
local file="${url##*/}"
# if no destination, get the basename from the url
[[ -z "$dest" ]] && dest="${PWD}/$file"
# if the destination is a folder, download to that with filename from url
[[ -d "$dest" ]] && dest="$dest/$file"
local params=(--location)
if [[ "$dest" == "-" ]]; then
params+=(--silent --no-buffer)
else
printMsgs "console" "Downloading $url to $dest ..."
params+=(-o "$dest")
fi
params+=(--connect-timeout 10 --speed-limit 1 --speed-time 60 --fail)
# add the url
params+=("$url")
local ret
runCurl "${params[@]}"
ret="$?"
# if download failed, remove file, log error and return error code
if [[ "$ret" -ne 0 ]]; then
# remove dest if not set to stdout and exists
[[ "$dest" != "-" && -f "$dest" ]] && rm "$dest"
md_ret_errors+=("URL $url failed to download.\n\n$__NET_ERRMSG")
fi
return "$ret"
}
## @fn downloadAndVerify()
## @param url url of file
## @param dest destination file (optional)
## @brief Download a file and a corresponding .asc signature and verify the contents
## @details Download a file and a corresponding .asc signature and verify the contents.
## The .asc file will be downloaded to verify the file, but will be removed after downloading.
## If the dest parameter is omitted, the file will be downloaded to the current directory
## @retval 0 on success
function downloadAndVerify() {
local url="$1"
local dest="$2"
local file="${url##*/}"
# if no destination, get the basename from the url (supported by GNU basename)
[[ -z "$dest" ]] && dest="${PWD}/$file"
local cmd_out
local ret=1
if download "${url}.asc" "${dest}.asc"; then
if download "$url" "$dest"; then
cmd_out="$(gpg --verify "${dest}.asc" 2>&1)"
ret="$?"
if [[ "$ret" -ne 0 ]]; then
md_ret_errors+=("$dest failed signature check:\n\n$cmd_out")
fi
fi
fi
return "$ret"
}
## @fn downloadAndExtract()
## @param url url of archive
## @param dest destination folder for the archive
## @param optional additional parameters to pass to the decompression tool.
## @brief Download and extract an archive
## @details Download and extract an archive.
## @retval 0 on success
function downloadAndExtract() {
local url="$1"
local dest="$2"
shift 2
local opts=("$@")
local ext="${url##*.}"
local file="${url##*/}"
local temp="$(mktemp -d)"
# download file, removing temporary folder and returning on error
if ! download "$url" "$temp/$file"; then
rm -rf "$temp"
return 1
fi
mkdir -p "$dest"
local ret
case "$ext" in
exe|zip)
runCmd unzip "${opts[@]}" -o "$temp/$file" -d "$dest"
;;
*)
tar -xvf "$temp/$file" -C "$dest" "${opts[@]}"
;;
esac
ret=$?
rm -rf "$temp"
return $ret
}
## @fn ensureFBMode()
## @param res_x width of mode
## @param res_y height of mode
## @brief Add a framebuffer mode to /etc/fb.modes
## @details Useful for adding specific resolutions used by emulators so SDL1 can
## use them and utilise the RPI hardware scaling. Without for example a 320x240
## mode in fb.modes many of the emulators that output to the framebuffer and
## were not set to use the dispmanx SDL1 backend would just show in a small
## area of the screen.
function ensureFBMode() {
[[ ! -f /etc/fb.modes ]] && return
local res_x="$1"
local res_y="$2"
local res="${res_x}x${res_y}"
sed -i --follow-symlinks "/$res mode/,/endmode/d" /etc/fb.modes
cat >> /etc/fb.modes <<_EOF_
# added by RetroPie-Setup - $res mode for emulators
mode "$res"
geometry $res_x $res_y $res_x $res_y 16
timings 0 0 0 0 0 0 0
endmode
_EOF_
}
## @fn joy2keyStart()
## @param left mapping for left
## @param right mapping for right
## @param up mapping for up
## @param down mapping for down
## @param but1 mapping for button 1
## @param but2 mapping for button 2
## @param but3 mapping for button 3
## @param butX mapping for button X ...
## @brief Start joy2key process in background to map joystick presses to keyboard
## @details Arguments are curses capability names or hex values starting with '0x'
## see: http://pubs.opengroup.org/onlinepubs/7908799/xcurses/terminfo.html
function joy2keyStart() {
# don't start on SSH sessions
# (check for bracket in output - ip/name in brackets over a SSH connection)
[[ "$(who -m)" == *\(* ]] && return
local params=("$@")
# if joy2key is installed, run it
if rp_isInstalled "joy2key"; then
"$(rp_getInstallPath joy2key)/joy2key" start "${params[@]}" 2>/dev/null && return 0
fi
return 1
}
## @fn joy2keyStop()
## @brief Stop previously started joy2key process.
function joy2keyStop() {
# if joy2key is installed, stop it
if rp_isInstalled "joy2key"; then
"$(rp_getInstallPath joy2key)/joy2key" stop
fi
}
## @fn getPlatformConfig()
## @param key key to look up
## @brief gets a config from a platforms.cfg ini
## @details gets a config from a platforms.cfg ini first looking in
## `$configdir/all/platforms.cfg` then `$scriptdir/platforms.cfg`
## allowing users to override any parts of `$scriptdir/platforms.cfg`
function getPlatformConfig() {
local key="$1"
local conf
for conf in "$configdir/all/platforms.cfg" "$scriptdir/platforms.cfg"; do
[[ ! -f "$conf" ]] && continue
iniConfig "=" '"' "$conf"
iniGet "$key"
[[ -n "$ini_value" ]] && break
done
# workaround for RetroPie platform
[[ "$key" == "retropie_fullname" ]] && ini_value="RetroPie"
echo "$ini_value"
}
## @fn addSystem()
## @param system system to add
## @brief adds an emulator entry / system
## @param fullname optional fullname for the frontend (if not present in platforms.cfg)
## @param exts optional extensions for the frontend (if not present in platforms.cfg)
## @details Adds a system to one of the frontend launchers
function addSystem() {
local system="$1"
local fullname="$2"
local exts=($3)
local platform="$system"
local theme="$system"
local cmd
local path
# if removing and we don't have an emulators.cfg we can remove the system from the frontends
if [[ "$md_mode" == "remove" ]] && [[ ! -f "$md_conf_root/$system/emulators.cfg" ]]; then
delSystem "$system" "$fullname"
return
fi
# set system / platform / theme for configuration based on data in names field
if [[ "$system" == "ports" ]]; then
cmd="bash %ROM%"
path="$romdir/ports"
else
cmd="$rootdir/supplementary/runcommand/runcommand.sh 0 _SYS_ $system %ROM%"
path="$romdir/$system"
fi
exts+=("$(getPlatformConfig "${system}_exts")")
local temp
temp="$(getPlatformConfig "${system}_theme")"
if [[ -n "$temp" ]]; then
theme="$temp"
else
theme="$system"
fi
temp="$(getPlatformConfig "${system}_platform")"
if [[ -n "$temp" ]]; then
platform="$temp"
else
platform="$system"
fi
temp="$(getPlatformConfig "${system}_fullname")"
[[ -n "$temp" ]] && fullname="$temp"
exts="${exts[*]}"
# add the extensions again as uppercase
exts+=" ${exts^^}"
setESSystem "$fullname" "$system" "$path" "$exts" "$cmd" "$platform" "$theme"
}
## @fn delSystem()
## @param system system to delete
## @brief Deletes a system
## @details deletes a system from all frontends.
function delSystem() {
local system="$1"
local fullname="$2"
local temp
temp="$(getPlatformConfig "${system}_fullname")"
[[ -n "$temp" ]] && fullname="$temp"
local function
for function in $(compgen -A function _del_system_); do
"$function" "$fullname" "$system"
done
}
## @fn addPort()
## @param id id of the module / command
## @param port name of the port
## @param name display name for the launch script
## @param cmd commandline to launch
## @param game rom/game parameter (optional)
## @brief Adds a port to the emulationstation ports menu.
## @details Adds an emulators.cfg entry as with addSystem but also creates a launch script in `$datadir/ports/$name.sh`.
##
## Can also optionally take a game parameter which can be used to create multiple launch
## scripts for different games using the same engine - eg for quake
##
## addPort "lr-tyrquake" "quake" "Quake" "$emudir/retroarch/bin/retroarch -L $md_inst/tyrquake_libretro.so --config $md_conf_root/quake/retroarch.cfg %ROM%" "$romdir/ports/quake/id1/pak0.pak"
## addPort "lr-tyrquake" "quake" "Quake Mission Pack 2 (rogue)" "$emudir/retroarch/bin/retroarch -L $md_inst/tyrquake_libretro.so --config $md_conf_root/quake/retroarch.cfg %ROM%" "$romdir/ports/quake/id1/rogue/pak0.pak"
##
## Would add an entry in $configdir/ports/quake/emulators.cfg for lr-tyrquake (setting it to default if no default set)
## and create a launch script in $romdir/ports for each game.
function addPort() {
local id="$1"
local port="$2"
local file="$romdir/ports/$3.sh"
local cmd="$4"
local game="$5"
# move configurations from old ports location
if [[ -d "$configdir/$port" ]]; then
mv "$configdir/$port" "$md_conf_root/"
fi
# remove the emulator / port
if [[ "$md_mode" == "remove" ]]; then
delEmulator "$id" "$port"
# remove launch script if in remove mode and the ports emulators.cfg is empty
[[ ! -f "$md_conf_root/$port/emulators.cfg" ]] && rm -f "$file"
# if there are no more port launch scripts we can remove ports from emulation station
if [[ "$(find "$romdir/ports" -maxdepth 1 -name "*.sh" | wc -l)" -eq 0 ]]; then
delSystem "ports"
fi
return
fi
mkUserDir "$romdir/ports"
cat >"$file" << _EOF_
#!/bin/bash
"$rootdir/supplementary/runcommand/runcommand.sh" 0 _PORT_ "$port" "$game"
_EOF_
chown "$__user":"$__group" "$file"
chmod +x "$file"
[[ -n "$cmd" ]] && addEmulator 1 "$id" "$port" "$cmd"
addSystem "ports"
}
## @fn addEmulator()
## @param default 1 to make the emulator / command default for the system if no default already set
## @param id unique id of the module / command
## @param name name of the system to add the emulator to
## @param cmd commandline to launch
## @brief Adds a new emulator for a system.
## @details This is the primary function for adding emulators to a system which can be
## switched between via the runcommand launch menu
##
## addEmulator 1 "vice-x64" "c64" "$md_inst/bin/x64 %ROM%"
## addEmulator 0 "vice-xvic" "c64" "$md_inst/bin/xvic %ROM%"
##
## Would add two optional emulators for the c64 - with vice-x64 being the default if no default
## was already set. This adds entries to `$configdir/$system/emulators.cfg` with
##
## id = "cmd"
## default = id
##
## Which are then selectable from runcommand when launching roms
##
## For libretro emulators, cmd needs to only contain the path to the libretro library.
##
## eg. for the lr-fcuemm module
##
## addEmulator 1 "$md_id" "nes" "$md_inst/fceumm_libretro.so"
function addEmulator() {
local default="$1"
local id="$2"
local system="$3"
local cmd="$4"
# check if we are removing the system
if [[ "$md_mode" == "remove" ]]; then
delEmulator "$id" "$system"
return
fi
# automatically add parameters for libretro modules
if [[ "$id" == lr-* && "$cmd" =~ ^"$md_inst"[^[:space:]]*\.so ]]; then
cmd="$emudir/retroarch/bin/retroarch -L $cmd --config $md_conf_root/$system/retroarch.cfg %ROM%"
fi
# create a config folder for the system / port
mkUserDir "$md_conf_root/$system"
# add the emulator to the $conf_dir/emulators.cfg if a commandline exists (not used for some ports)
if [[ -n "$cmd" ]]; then
iniConfig " = " '"' "$md_conf_root/$system/emulators.cfg"
iniSet "$id" "$cmd"
# set a default unless there is one already set
iniGet "default"
if [[ -z "$ini_value" && "$default" -eq 1 ]]; then
iniSet "default" "$id"
fi
chown "$__user":"$__group" "$md_conf_root/$system/emulators.cfg"
fi
}
## @fn delEmulator()
## @param id id of emulator to delete
## @param system system to delete from
## @brief Deletes an emulator entry / system
## @details Delete the entry for the id from `$configdir/$system/emulators.cfg`.
## If there are no more emulators for the system present, it will also
## delete the system entry from the installed frontends.
function delEmulator() {
local id="$1"
local system="$2"
local config="$md_conf_root/$system/emulators.cfg"
# remove from apps list for system
if [[ -f "$config" && -n "$id" ]]; then
# delete emulator entry
iniConfig " = " '"' "$config"
iniDel "$id"
# if it is the default - remove it - runcommand will prompt to select a new default
iniGet "default"
[[ "$ini_value" == "$id" ]] && iniDel "default"
# if we no longer have any entries in the emulators.cfg file we can remove it
grep -q "=" "$config" || rm -f "$config"
fi
}
## @fn patchVendorGraphics()
## @param filename file to patch
## @details replace declared dependencies of old vendor graphics libraries with new names
## Temporary compatibility workaround for legacy software to work on new Raspberry Pi firmwares.
function patchVendorGraphics() {
local filename="$1"
# patchelf is not available on Raspbian Jessie
[[ "$__os_debian_ver" -lt 9 ]] && return
getDepends patchelf
printMsgs "console" "Applying vendor graphics patch: $filename"
patchelf --replace-needed libEGL.so libbrcmEGL.so \
--replace-needed libGLES_CM.so libbrcmGLESv2.so \
--replace-needed libGLESv1_CM.so libbrcmGLESv2.so \
--replace-needed libGLESv2.so libbrcmGLESv2.so \
--replace-needed libOpenVG.so libbrcmOpenVG.so \
--replace-needed libWFC.so libbrcmWFC.so "$filename"
}
## @fn dkmsManager()
## @param mode dkms operation type
## @module_name name of dkms module
## @module_ver version of dkms module
## Helper function to manage DKMS modules installed by RetroPie
function dkmsManager() {
local mode="$1"
local module_name="$2"
local module_ver="$3"
local kernel="$(uname -r)"
local ver
case "$mode" in
install)
if dkms status | grep -q "^$module_name"; then
dkmsManager remove "$module_name" "$module_ver"
fi
ln -sf "$md_inst" "/usr/src/${module_name}-${module_ver}"
dkms install --no-initrd --force -m "$module_name" -v "$module_ver" -k "$kernel"
if ! dkms status "$module_name/$module_ver" -k "$kernel" | grep -q installed; then
# Force building for any kernel that has source/headers
local k_ver
while read k_ver; do
if [[ -d "$(realpath /lib/modules/$k_ver/build)" ]]; then
dkms install --no-initrd --force -m "$module_name/$module_ver" -k "$k_ver"
fi
done < <(ls -r1 /lib/modules)
fi
if ! dkms status "$module_name/$module_ver" | grep -q installed; then
md_ret_errors+=("Failed to install $md_id")
return 1
fi
;;
remove)
for ver in $(dkms status "$module_name" | cut -d"," -f2 | cut -d":" -f1); do
dkms remove -m "$module_name" -v "$ver" --all
rm -f "/usr/src/${module_name}-${ver}"
done
dkmsManager unload "$module_name" "$module_ver"
;;
reload)
dkmsManager unload "$module_name" "$module_ver"
# No reason to load modules in chroot
if [[ "$__chroot" -eq 0 ]]; then
modprobe "$module_name"
fi
;;
unload)
if [[ -n "$(lsmod | grep ${module_name/-/_})" ]]; then
rmmod "$module_name"
fi
;;
esac
}
## @fn getIPAddress()
## @param dev optional specific network device to use for address lookup
## @brief Obtains the current externally routable source IP address of the machine
## @details This function first tries to obtain an external IPv4 route and
## otherwise tries an IPv6 route if the IPv4 route can not be determined.
## If no external route can be determined, nothing will be returned.
## This function uses Google's DNS servers as the external lookup address.
function getIPAddress() {
local dev="$1"
local ip_route
# first try to obtain an external IPv4 route
ip_route=$(ip -4 route get 8.8.8.8 ${dev:+dev $dev} 2>/dev/null)
if [[ -z "$ip_route" ]]; then
# if there is no IPv4 route, try to obtain an IPv6 route instead
ip_route=$(ip -6 route get 2001:4860:4860::8888 ${dev:+dev $dev} 2>/dev/null)
fi
# if an external route was found, report its source address
[[ -n "$ip_route" ]] && grep -oP "src \K[^\s]+" <<< "$ip_route"
}
## @fn isConnected()
## @brief Simple check to see if there is a connection to the Internet.
## @details Uses the getIPAddress function to check if we have a route to the Internet. Also sets
## __NET_ERRMSG with an error message for use in packages / setup to display to the user if not.
## @retval 0 on success
## @retval 1 on failure
function isConnected() {
local ip="$(getIPAddress)"
if [[ -z "$ip" ]]; then
__NET_ERRMSG="Not connected to the Internet"
return 1
fi
return 0
}
## @fn adminRsync()
## @param src src folder on local system - eg "$__tmpdir/stats/"
## @param dest destination folder on remote system - eg "stats/"
## @param params additional rsync parameters - eg --delete
## @brief Rsyncs data to remote host for admin modules
## @details Used to rsync data to our server for admin modules. Default remote
## user is retropie, host is $__binary_host and default port is 22. These can be overridden with
## env vars __upload_user __upload_host and __upload_port
##
## The default parameters for rsync are "-av --delay-updates" - more can be added via the 3rd+ argument
function adminRsync() {
local src="$1"
local dest="$2"
shift 2
local params=("$@")
local remote_user="$__upload_user"
[[ -z "$remote_user" ]] && remote_user="retropie"
local remote_host="$__upload_host"
[[ -z "$remote_host" ]] && remote_host="$__binary_host"
local remote_port="$__upload_port"
[[ -z "$remote_port" ]] && remote_port=22
rsync -av --delay-updates -e "ssh -p $remote_port" "${params[@]}" "$src" "$remote_user@$remote_host:$dest"
}
## @fn signFile()
## @param file path to file to sign
## @brief signs file with $__gpg_signing_key
## @details signs file with $__gpg_signing_key creating corresponding .asc file in the same folder
function signFile() {
local file="$1"
local cmd_out
cmd_out=$(gpg --default-key "$__gpg_signing_key" --detach-sign --armor --yes "$1" 2>&1)
if [[ "$?" -ne 0 ]]; then
md_ret_errors+=("Failed to sign $1\n\n$cmd_out")
return 1
fi
return 0
}