UT2004-Linux/install-ut2004.sh
2026-06-15 21:09:42 -06:00

2955 lines
97 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Unreal Tournament 2004 Linux Installer
#
# shellcheck source-path=SCRIPTDIR
# ARGBASH_SET_INDENT([ ])
# ARG_OPTIONAL_SINGLE([destination],[d],[Install directory. Will be created if it doesn't exist.],[${XDG_DATA_HOME:-${HOME}/.local/share}/OldUnreal/UT2004])
# ARG_OPTIONAL_SINGLE([ui-mode],[],[UI library to use during install.],[auto])
# ARG_TYPE_GROUP_SET([uimode],[MODE],[ui-mode],[auto,kdialog,zenity,none])
# ARG_OPTIONAL_SINGLE([application-entry],[],[Action to take when installing the XDG Application Entry.],[prompt])
# ARG_OPTIONAL_SINGLE([desktop-shortcut],[],[Action to take when installing a desktop shortcut.],[prompt])
# ARG_TYPE_GROUP_SET([entryhandlingmode],[ACTION],[application-entry,desktop-shortcut],[install,prompt,skip])
# ARG_OPTIONAL_BOOLEAN([unrealed],[e],[Install UnrealEd (Windows, umu-launcher recommended).],[])
# ARG_OPTIONAL_BOOLEAN([keep-installer-files],[k],[Keep ISO and Patch files.],[])
# ARG_HELP([Install Unreal Tournament 2004])
# ARG_VERSION_AUTO([1.2.1],['OldUnreal <https://oldunreal.com>'])
# DEFINE_SCRIPT_DIR([_SCRIPT_DIR])
# ARGBASH_GO()
# needed because of Argbash --> m4_ignore([
### START OF CODE GENERATED BY Argbash v2.11.0 one line above ###
# Argbash is a bash code generator used to get arguments parsing right.
# Argbash is FREE SOFTWARE, see https://argbash.dev for more info
die() {
local _ret="${2:-1}"
test "${_PRINT_HELP:-no}" = yes && print_help >&2
echo "$1" >&2
exit "${_ret}"
}
# validators
uimode() {
local _allowed=("auto" "kdialog" "zenity" "none") _seeking="$1"
for element in "${_allowed[@]}"; do
test "$element" = "$_seeking" && echo "$element" && return 0
done
die "Value '$_seeking' (of argument '$2') doesn't match the list of allowed values: 'auto', 'kdialog', 'zenity' and 'none'" 4
}
entryhandlingmode() {
local _allowed=("install" "prompt" "skip") _seeking="$1"
for element in "${_allowed[@]}"; do
test "$element" = "$_seeking" && echo "$element" && return 0
done
die "Value '$_seeking' (of argument '$2') doesn't match the list of allowed values: 'install', 'prompt' and 'skip'" 4
}
begins_with_short_option() {
local first_option all_short_options='dekhv'
first_option="${1:0:1}"
test "$all_short_options" = "${all_short_options/$first_option/}" && return 1 || return 0
}
# THE DEFAULTS INITIALIZATION - OPTIONALS
_arg_destination="${XDG_DATA_HOME:-${HOME}/.local/share}/OldUnreal/UT2004"
_arg_ui_mode="auto"
_arg_application_entry="prompt"
_arg_desktop_shortcut="prompt"
_arg_unrealed="off"
_arg_keep_installer_files="off"
print_help() {
printf '%s\n' "Install Unreal Tournament 2004"
printf 'Usage: %s [-d|--destination <arg>] [--ui-mode <MODE>] [--application-entry <ACTION>] [--desktop-shortcut <ACTION>] [-e|--(no-)unrealed] [-k|--(no-)keep-installer-files] [-h|--help] [-v|--version]\n' "$0"
printf '\t%s\n' "-d, --destination: Install directory. Will be created if it doesn't exist. (default: '${XDG_DATA_HOME:-${HOME}/.local/share}/OldUnreal/UT2004')"
printf '\t%s\n' "--ui-mode: UI library to use during install.. Can be one of: 'auto', 'kdialog', 'zenity' and 'none' (default: 'auto')"
printf '\t%s\n' "--application-entry: Action to take when installing the XDG Application Entry.. Can be one of: 'install', 'prompt' and 'skip' (default: 'prompt')"
printf '\t%s\n' "--desktop-shortcut: Action to take when installing a desktop shortcut.. Can be one of: 'install', 'prompt' and 'skip' (default: 'prompt')"
printf '\t%s\n' "-e, --unrealed, --no-unrealed: Install UnrealEd (Windows, umu-launcher recommended). (off by default)"
printf '\t%s\n' "-k, --keep-installer-files, --no-keep-installer-files: Keep ISO and Patch files. (off by default)"
printf '\t%s\n' "-h, --help: Prints help"
printf '\t%s\n' "-v, --version: Prints version"
}
parse_commandline() {
local _key
while test $# -gt 0; do
_key="$1"
case "$_key" in
-d | --destination)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_destination="$2"
shift
;;
--destination=*)
_arg_destination="${_key##--destination=}"
;;
-d*)
_arg_destination="${_key##-d}"
;;
--ui-mode)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_ui_mode="$(uimode "$2" "ui-mode")" || exit 1
shift
;;
--ui-mode=*)
_arg_ui_mode="$(uimode "${_key##--ui-mode=}" "ui-mode")" || exit 1
;;
--application-entry)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_application_entry="$(entryhandlingmode "$2" "application-entry")" || exit 1
shift
;;
--application-entry=*)
_arg_application_entry="$(entryhandlingmode "${_key##--application-entry=}" "application-entry")" || exit 1
;;
--desktop-shortcut)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_desktop_shortcut="$(entryhandlingmode "$2" "desktop-shortcut")" || exit 1
shift
;;
--desktop-shortcut=*)
_arg_desktop_shortcut="$(entryhandlingmode "${_key##--desktop-shortcut=}" "desktop-shortcut")" || exit 1
;;
-e | --no-unrealed | --unrealed)
_arg_unrealed="on"
test "${1:0:5}" = "--no-" && _arg_unrealed="off"
;;
-e*)
_arg_unrealed="on"
_next="${_key##-e}"
if test -n "$_next" -a "$_next" != "$_key"; then
{ begins_with_short_option "$_next" && shift && set -- "-e" "-${_next}" "$@"; } || die "The short option '$_key' can't be decomposed to ${_key:0:2} and -${_key:2}, because ${_key:0:2} doesn't accept value and '-${_key:2:1}' doesn't correspond to a short option."
fi
;;
-k | --no-keep-installer-files | --keep-installer-files)
_arg_keep_installer_files="on"
test "${1:0:5}" = "--no-" && _arg_keep_installer_files="off"
;;
-k*)
_arg_keep_installer_files="on"
_next="${_key##-k}"
if test -n "$_next" -a "$_next" != "$_key"; then
{ begins_with_short_option "$_next" && shift && set -- "-k" "-${_next}" "$@"; } || die "The short option '$_key' can't be decomposed to ${_key:0:2} and -${_key:2}, because ${_key:0:2} doesn't accept value and '-${_key:2:1}' doesn't correspond to a short option."
fi
;;
-h | --help)
print_help
exit 0
;;
-h*)
print_help
exit 0
;;
-v | --version)
printf '%s %s\n\n%s\n%s\n' "install-ut2004.sh" "1.2.1" 'Install Unreal Tournament 2004' 'OldUnreal <https://oldunreal.com>'
exit 0
;;
-v*)
printf '%s %s\n\n%s\n%s\n' "install-ut2004.sh" "1.2.1" 'Install Unreal Tournament 2004' 'OldUnreal <https://oldunreal.com>'
exit 0
;;
*)
_PRINT_HELP=yes die "FATAL ERROR: Got an unexpected argument '$1'" 1
;;
esac
shift
done
}
parse_commandline "$@"
# OTHER STUFF GENERATED BY Argbash
_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" || {
echo "Couldn't determine the script's running directory, which probably matters, bailing out" >&2
exit 2
}
# Validation of values
### END OF CODE GENERATED BY Argbash (sortof) ### ])
# [ <-- needed because of Argbash
# Enable Bash Strict Mode
set -euo pipefail
installer::entrypoint() {
# Defining Installation Parameters
local PRODUCT_NAME="Unreal Tournament 2004"
local PRODUCT_SHORTNAME="UT2004"
local PRODUCT_KEYWORDS=("UT2004" "UT2K4")
local PRODUCT_URLSCHEME="ut2004"
local MAIN_BINARY_NAME="UT2004"
# Import Library files
# shellcheck shell=bash
declare -A ansi_colornum=(
[black]=0
[red]=1
[green]=2
[yellow]=3
[blue]=4
[magenta]=5
[cyan]=6
[white]=7
)
declare -A ansi_stylenum=(
[reset]=0
[bright]=1
[dim]=2
[italic]=3
[underline]=4
[flash]=5
[highlight]=7
[normal]=22
)
# UI Mode auto-detection
if [[ -z "${_arg_ui_mode:-}" ]] || [[ "${_arg_ui_mode:-}" == "auto" ]]; then
if ! { [[ -n "${DISPLAY:-}" ]] || [[ -n "${WAYLAND_DISPLAY:-}" ]]; } ||
{ [[ -n "${SSH_CLIENT:-}" ]] || [[ -n "${SSH_TTY:-}" ]]; }; then
# If we do not have a display, or we are in a SSH session, fallback to text mode
_arg_ui_mode="none"
elif { [[ "${XDG_CURRENT_DESKTOP:-}" == "KDE" ]] && command -v kdialog &>/dev/null && command -v busctl &>/dev/null; }; then
# If we are on KDE, and kdialog + busctl is available, use kdialog
_arg_ui_mode="kdialog"
elif command -v zenity &>/dev/null; then
# Use Zenity if available
_arg_ui_mode="zenity"
elif command -v kdialog &>/dev/null && command -v busctl &>/dev/null; then
# If kdialog is available, but zenity is not (outside of KDE)
_arg_ui_mode="kdialog"
else
# If nothing is available, only run in text mode
_arg_ui_mode="none"
fi
fi
# Set-up fd6 as an alternate STDOUT for when we are doing operations with pipes, but
# still want to output progress
exec 6>&1
local PROGRESS_MIN_REFRESH=0.1
ansi::styled() {
ALLOW_ESCAPES="off"
if [[ "${1:-}" == "-e" ]]; then
ALLOW_ESCAPES="on"
shift
fi
local TEXT_TO_PRINT="${1:-}"
local STYLE="${2:--1}"
local FORE_COLOR="${3:--1}"
local BACK_COLOR="${4:--1}"
declare -a CODES RESETCODES
if [[ "${STYLE}" -eq 22 ]] || [[ "${STYLE}" -eq -1 ]]; then
RESETCODES=("$(printf "\033[%sm" "22")" "${RESETCODES[@]}")
else
CODES=("${CODES[@]}" "$(printf "\033[%sm" "${STYLE}")")
fi
if [[ "${BACK_COLOR}" -eq -1 ]]; then
RESETCODES=("$(printf "\033[%sm" "49")" "${RESETCODES[@]}")
else
CODES=("${CODES[@]}" "$(printf "\033[%sm" "$((BACK_COLOR + 40))")")
fi
if [[ "${FORE_COLOR}" -eq -1 ]]; then
RESETCODES=("$(printf "\033[%sm" "39")" "${RESETCODES[@]}")
else
CODES=("${CODES[@]}" "$(printf "\033[%sm" "$((FORE_COLOR + 30))")")
fi
local rc
for rc in "${RESETCODES[@]}"; do
echo -en "$rc"
done
local c
for c in "${CODES[@]}"; do
echo -en "$c"
done
if [[ "${ALLOW_ESCAPES}" == "on" ]]; then
echo -en "${TEXT_TO_PRINT}"
else
echo -n "${TEXT_TO_PRINT}"
fi
echo -en "\033[m"
}
ansi::banner() {
local TOPEDGE BOTTOMEDGE
TOPEDGE="┏━${1//?/━}━┓"
BOTTOMEDGE="┗━${1//?/━}━┛"
printf "%s\n" "$(ansi::styled "${TOPEDGE}" "${ansi_stylenum[bright]}")"
printf "%s\n" "$(ansi::styled "${1}" "${ansi_stylenum[bright]}")"
printf "%s\n" "$(ansi::styled "${BOTTOMEDGE}" "${ansi_stylenum[bright]}")"
}
term::error() {
echo -e "$(ansi::styled "Error:" "${ansi_stylenum[bright]}" "${ansi_colornum[red]}") $*" 1>&2
}
term::yesno() {
local QUESTION="${1:-}"
local DEFAULT="${2:-N}"
local DEFAULT_PROMPT="[yN]"
if [[ "${DEFAULT}" =~ ^[Yy]$ ]]; then
DEFAULT_PROMPT="[Yn]"
fi
# In case the user was impatient and typed a whole bunch of crap in their terminal while waiting for a download
# clear the STDIN buffer
if test -t 0; then
local discard
read -r -n 1000000 -t 0.001 discard || true
if [[ -n "${discard:-}" ]]; then
echo
fi
fi
read -p "$(ansi::styled "?" "${ansi_stylenum[bright]}" "${ansi_colornum[green]}") ${QUESTION} $(ansi::styled "${DEFAULT_PROMPT}" "${ansi_stylenum[bright]}") " -n 1 -r
echo
if [[ "${DEFAULT}" =~ ^[Nn]$ ]]; then
if [[ ! "${REPLY}" =~ ^[Yy]$ ]]; then
return 1
fi
elif [[ "${DEFAULT}" =~ ^[Yy]$ ]]; then
if [[ "${REPLY}" =~ ^[Nn]$ ]]; then
return 1
fi
fi
return 0
}
local CURRENT_STEP_NAME=""
local STEP_PROGRESS_SPINNER_CHARS=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
local STEP_PROGRESS_SPINNER_CURRENT=0
term::step::new() {
CURRENT_STEP_NAME="$*"
echo -en " $(ansi::styled "[ ]" "${ansi_stylenum[bright]}") $*"
}
term::step::replace() {
CURRENT_STEP_NAME="$*"
echo -en "\r\033[K $(ansi::styled "[ ]" "${ansi_stylenum[bright]}") $*"
}
term::step::progress() {
local STEP_PROGRESS_TEXT=""
if [[ -n "${1:-}" ]]; then
STEP_PROGRESS_TEXT="$(ansi::styled " ($*)" "${ansi_stylenum[dim]}")"
fi
echo -en "\r $(ansi::styled "[" "${ansi_stylenum[bright]}") $(ansi::styled "${STEP_PROGRESS_SPINNER_CHARS[$STEP_PROGRESS_SPINNER_CURRENT]}" "${ansi_stylenum[dim]}") $(ansi::styled "]" "${ansi_stylenum[bright]}") ${CURRENT_STEP_NAME}${STEP_PROGRESS_TEXT}\033[K"
STEP_PROGRESS_SPINNER_CURRENT=$((STEP_PROGRESS_SPINNER_CURRENT + 1))
if [[ "${STEP_PROGRESS_SPINNER_CURRENT}" -ge "${#STEP_PROGRESS_SPINNER_CHARS[@]}" ]]; then
STEP_PROGRESS_SPINNER_CURRENT=0
fi
}
term::step::complete() {
echo -e "\r\033[K $(ansi::styled "[" "${ansi_stylenum[bright]}") $(ansi::styled "✓" "${ansi_stylenum[bright]}" "${ansi_colornum[green]}") $(ansi::styled "]" "${ansi_stylenum[bright]}") ${CURRENT_STEP_NAME}"
}
term::step::failed() {
echo -e "\r\033[K $(ansi::styled "[" "${ansi_stylenum[bright]}") $(ansi::styled "✗" "${ansi_stylenum[bright]}" "${ansi_colornum[red]}") $(ansi::styled "]" "${ansi_stylenum[bright]}") ${CURRENT_STEP_NAME}"
}
term::step::skipped() {
local STEP_SKIPPED_REASON=""
if [[ -n "${1:-}" ]]; then
STEP_SKIPPED_REASON="$(ansi::styled " ($*)" "${ansi_stylenum[dim]}")"
fi
echo -e "\r\033[K $(ansi::styled "[" "${ansi_stylenum[bright]}") $(ansi::styled "-" "${ansi_stylenum[dim]}") $(ansi::styled "]" "${ansi_stylenum[bright]}") ${CURRENT_STEP_NAME}${STEP_SKIPPED_REASON}"
}
term::step::failed_with_error() {
term::step::failed
echo
local ERROR_TEXT="$*"
term::error "${ERROR_TEXT}" 1>&2
if [[ "${_arg_ui_mode:-none}" == "kdialog" ]]; then
kdialog --title "Error" \
--error "${ERROR_TEXT}" 2>/dev/null
elif [[ "${_arg_ui_mode:-none}" == "zenity" ]]; then
zenity --error --text="${ERROR_TEXT}" --width=350 2>/dev/null
fi
}
xdgdirs::get_user_dir() {
local USER_DIR_NAME="${1}"
if command -v xdg-user-dir &>/dev/null; then
local USER_DIR_RETURNED
USER_DIR_RETURNED=$(xdg-user-dir "${USER_DIR_NAME}")
if [[ -d "${USER_DIR_RETURNED}" ]]; then
echo "${USER_DIR_RETURNED}"
fi
return 0
fi
local USER_DIR_VAR_NAME="XDG_${USER_DIR_NAME}_DIR"
if [[ -n "${!USER_DIR_VAR_NAME:-}" ]] && [[ -d "${!USER_DIR_VAR_NAME}" ]]; then
echo "${!USER_DIR_VAR_NAME}"
fi
}
helper::progress::run_with_progress() {
local LAST_UPDATE=""
local PROGRESS_MESSAGE="${1}"
shift
while IFS= read -r UPDATE; do
term::step::progress "${PROGRESS_MESSAGE}" >&6
LAST_UPDATE="${UPDATE}"
done < <(helper::progress::make_consistant "$@")
echo "${LAST_UPDATE}"
}
# This function throttles the output to avoid buffering, while also making sure the last update
# is repeated at a set frequency for the throbber to work
helper::progress::make_consistant() {
local LAST_UPDATE=""
local CURRENT_UPDATE=""
exec 3< <(helper::run_as_proc_group "$@")
local PROC_SUB_PID=$!
trap '[ -n "${PROC_SUB_PID:-}" ] && { kill -- "${PROC_SUB_PID}"; }' EXIT
local CAN_WRITE="y"
trap 'CAN_WRITE="n"' SIGPIPE
while kill -0 "${PROC_SUB_PID}" 2>/dev/null; do
while IFS= read -r -u 3 -t 0.001 CURRENT_UPDATE; do
LAST_UPDATE="${CURRENT_UPDATE}"
done
if [[ "${CAN_WRITE}" == "y" ]]; then
echo "${LAST_UPDATE}" 2>/dev/null
sleep "${PROGRESS_MIN_REFRESH}"
else
break
fi
done
trap - SIGPIPE EXIT
local EXIT_CODE=1
if [[ "${CAN_WRITE}" == "y" ]]; then
# Final drain of remaining data
while IFS= read -r -u 3 CURRENT_UPDATE; do
LAST_UPDATE="${CURRENT_UPDATE}"
done
echo "${LAST_UPDATE}"
else
if kill -0 "${PROC_SUB_PID}" 2>/dev/null; then
# Explicitly kill if the loop exited due to CAN_WRITE="n"
kill -TERM -- "${PROC_SUB_PID}"
fi
return 1
fi
wait "${PROC_SUB_PID}"
EXIT_CODE=$?
exec 3<&-
return "${EXIT_CODE}"
}
helper::run_as_proc_group() {
set -m
{ "$@"; } &
set +m
local PROC_SUB_PID=$!
trap 'trap - EXIT; [[ -n "${PROC_SUB_PID:-}" ]] && { kill -- -"${PROC_SUB_PID}"; wait "${PROC_SUB_PID}"; return $?; }' EXIT
wait "${PROC_SUB_PID}"
local EXIT_CODE=$?
trap - EXIT
return $?
}
helper::string::unshift::next_value() {
__helper::string::unshift "${1:-}" "${2:-}" "v"
}
helper::string::unshift::remainder() {
__helper::string::unshift "${1:-}" "${2:-}" "r"
}
__helper::string::unshift() {
local VAR_CURRENT_VALUE="${1:-}"
local SEPARATOR="${2:-|}"
local RETURN="${3}"
local SHIFTED_VALUE
local REMAINING_VALUE
if [[ "${VAR_CURRENT_VALUE}" == *"${SEPARATOR}"* ]]; then
SHIFTED_VALUE="${VAR_CURRENT_VALUE%%"${SEPARATOR}"*}"
REMAINING_VALUE="${VAR_CURRENT_VALUE#*"${SEPARATOR}"}"
else
SHIFTED_VALUE="${VAR_CURRENT_VALUE}"
REMAINING_VALUE=""
fi
if [[ "${RETURN}" == "r" ]]; then
echo "${REMAINING_VALUE}"
return 0
fi
echo "${SHIFTED_VALUE}"
}
# shellcheck shell=bash
local ARCHITECTURE_SUFFIX ARCHITECTURE_BINARY_SUFFIX UE_SYSTEM_FOLDER_SUFFIX UEED_SYSTEM_FOLDER_SUFFIX
local DETECTED_ARCHITECTURE
DETECTED_ARCHITECTURE=$(uname -m)
case "${DETECTED_ARCHITECTURE}" in
x86_64 | amd64)
case "${PRODUCT_SHORTNAME}" in
UT2004)
ARCHITECTURE_SUFFIX='amd64'
ARCHITECTURE_BINARY_SUFFIX=''
UE_SYSTEM_FOLDER_SUFFIX=''
UEED_SYSTEM_FOLDER_SUFFIX=''
;;
UnrealTournament)
ARCHITECTURE_SUFFIX='amd64'
ARCHITECTURE_BINARY_SUFFIX='-amd64'
UE_SYSTEM_FOLDER_SUFFIX='64'
UEED_SYSTEM_FOLDER_SUFFIX=''
;;
*)
ARCHITECTURE_SUFFIX='amd64'
ARCHITECTURE_BINARY_SUFFIX='-amd64'
UE_SYSTEM_FOLDER_SUFFIX='64'
UEED_SYSTEM_FOLDER_SUFFIX='64'
;;
esac
;;
aarch64)
case "${PRODUCT_SHORTNAME}" in
UT2004)
ARCHITECTURE_SUFFIX='arm64'
ARCHITECTURE_BINARY_SUFFIX=''
UE_SYSTEM_FOLDER_SUFFIX='ARM64'
UEED_SYSTEM_FOLDER_SUFFIX=''
;;
UnrealTournament)
ARCHITECTURE_SUFFIX='arm64'
ARCHITECTURE_BINARY_SUFFIX='-arm64'
UE_SYSTEM_FOLDER_SUFFIX='ARM64'
UEED_SYSTEM_FOLDER_SUFFIX=''
;;
*)
ARCHITECTURE_SUFFIX='arm64'
ARCHITECTURE_BINARY_SUFFIX='-arm64'
UE_SYSTEM_FOLDER_SUFFIX='ARM64'
UEED_SYSTEM_FOLDER_SUFFIX='64'
;;
esac
;;
i386 | i686)
case "${PRODUCT_SHORTNAME}" in
UT2004)
ARCHITECTURE_SUFFIX='NOT_SUPPORTED'
ARCHITECTURE_BINARY_SUFFIX=''
UE_SYSTEM_FOLDER_SUFFIX=''
UEED_SYSTEM_FOLDER_SUFFIX=''
;;
*)
ARCHITECTURE_SUFFIX='x86'
ARCHITECTURE_BINARY_SUFFIX='-x86'
UE_SYSTEM_FOLDER_SUFFIX=''
UEED_SYSTEM_FOLDER_SUFFIX=''
;;
esac
;;
ppc64le)
case "${PRODUCT_SHORTNAME}" in
UT2004)
ARCHITECTURE_SUFFIX='powerpc64le'
ARCHITECTURE_BINARY_SUFFIX='-powerpc64le'
UE_SYSTEM_FOLDER_SUFFIX='PPC64LE'
UEED_SYSTEM_FOLDER_SUFFIX='NOT_SUPPORTED'
;;
*)
ARCHITECTURE_SUFFIX='NOT_SUPPORTED'
ARCHITECTURE_BINARY_SUFFIX=''
UE_SYSTEM_FOLDER_SUFFIX=''
UEED_SYSTEM_FOLDER_SUFFIX=''
;;
esac
;;
*)
ARCHITECTURE_SUFFIX='NOT_SUPPORTED'
ARCHITECTURE_BINARY_SUFFIX=''
UE_SYSTEM_FOLDER_SUFFIX=''
UEED_SYSTEM_FOLDER_SUFFIX=''
;;
esac
# shellcheck shell=bash
# Downloader
local DOWNLOADER_TIMEOUT=15
local DOWNLOADER_API_BIN=""
local DOWNLOADER_API_TYPE=""
local DOWNLOADER_DL_BIN=""
local DOWNLOADER_DL_TYPE=""
local DOWNLOADER_USER_AGENT="OldUnreal-${PRODUCT_SHORTNAME}-Linux-Installer/1.2.1"
# For archive.org links, aria2c will be instructed to open multiple connections at the same time
local ARIA2C_ARCHIVEORG_CONNECTIONS="${OLDUNREAL_ARCHIVEORG_ARIA2C_CONNECTIONS:-4}"
# Check which command should be used for API calls
if command -v "curl" &>/dev/null; then
DOWNLOADER_API_BIN="curl"
DOWNLOADER_API_TYPE="curl"
elif command -v "wget" &>/dev/null; then
DOWNLOADER_API_BIN="wget"
DOWNLOADER_API_TYPE="wget"
# Check if provided wget version is Wget2... Thanks Fedora :(
if [[ "$(wget --version)" =~ " Wget2 " ]]; then
DOWNLOADER_API_TYPE="wget2"
fi
elif command -v "wget2" &>/dev/null; then
DOWNLOADER_API_BIN="wget2"
DOWNLOADER_API_TYPE="wget2"
fi
# Check which command should be used for downloads
if command -v "aria2c" &>/dev/null; then
DOWNLOADER_DL_BIN="aria2c"
DOWNLOADER_DL_TYPE="aria2c"
else
DOWNLOADER_DL_BIN="${DOWNLOADER_API_BIN}"
DOWNLOADER_DL_TYPE="${DOWNLOADER_API_TYPE}"
fi
downloader::download_file() {
if [[ -z "${1:-}" ]] || [[ -z "${2:-}" ]]; then
return 1
fi
local CURRENT_DOWNLOAD_URL_SET REMAINING_DOWNLOAD_URL_SETS
CURRENT_DOWNLOAD_URL_SET=$(helper::string::unshift::next_value "${1}" ";;")
REMAINING_DOWNLOAD_URL_SETS=$(helper::string::unshift::remainder "${1}" ";;")
local DOWNLOAD_PATH="${2}"
local IS_RETRY="${3:-no}"
__downloader::download_file "${CURRENT_DOWNLOAD_URL_SET}" "${DOWNLOAD_PATH}" "${IS_RETRY}" || downloader::download_file "${REMAINING_DOWNLOAD_URL_SETS}" "${DOWNLOAD_PATH}" "yes"
}
downloader::fetch_json() {
local ENDPOINT_URL="${1:-}"
if [[ -z "${ENDPOINT_URL}" ]]; then
return 1
fi
local IS_SPECIAL_URL
IS_SPECIAL_URL=$(__downloader::identify_special_host "${ENDPOINT_URL}")
local RESOLVED_GITHUB_TOKEN=""
if [[ "${IS_SPECIAL_URL}" == "github.com" ]]; then
RESOLVED_GITHUB_TOKEN="${GITHUB_TOKEN:-${GH_TOKEN:-}}"
fi
if [[ "${DOWNLOADER_API_TYPE}" == "curl" ]]; then
local ADDITIONAL_ARGS=()
if [[ "${IS_SPECIAL_URL}" == "github.com" ]] && [[ -n "${RESOLVED_GITHUB_TOKEN}" ]]; then
ADDITIONAL_ARGS+=("--header" "Authorization: Bearer ${RESOLVED_GITHUB_TOKEN}")
fi
"${DOWNLOADER_API_BIN}" -Ls "${ADDITIONAL_ARGS[@]}" \
--compressed --connect-timeout "${DOWNLOADER_TIMEOUT}" \
--user-agent "${DOWNLOADER_USER_AGENT}" \
"${ENDPOINT_URL}" 2>/dev/null
elif [[ "${DOWNLOADER_API_TYPE}" == "wget" ]] || [[ "${DOWNLOADER_API_TYPE}" == "wget2" ]]; then
local ADDITIONAL_ARGS=()
if [[ "${IS_SPECIAL_URL}" == "github.com" ]] && [[ -n "${RESOLVED_GITHUB_TOKEN}" ]]; then
ADDITIONAL_ARGS+=("--header=Authorization: Bearer ${RESOLVED_GITHUB_TOKEN}")
fi
"${DOWNLOADER_API_BIN}" -q "${ADDITIONAL_ARGS[@]}" \
--timeout="${DOWNLOADER_TIMEOUT}" \
--user-agent="${DOWNLOADER_USER_AGENT}" \
"${ENDPOINT_URL}" -O - -o /dev/null 2>/dev/null
fi
}
downloader::build_download_source_definition() {
local SEPARATOR=";;"
local SOURCE_DEF=""
if [[ $# -eq 0 ]]; then
return 1
fi
while [[ $# -gt 0 ]]; do
local SOURCES_GROUP_VAR_NAME="${1}"
shift
if [[ -z "${SOURCES_GROUP_VAR_NAME}" ]]; then
return 1
fi
local SOURCES_GROUP=()
local SOURCES_GROUP_VAR_REF="${SOURCES_GROUP_VAR_NAME}[@]"
SOURCES_GROUP=("${!SOURCES_GROUP_VAR_REF}")
if [[ "${#SOURCES_GROUP[@]}" -gt 0 ]]; then
while IFS= read -r SHUFFLED_ITEM; do
if [[ -n "${SOURCE_DEF}" ]]; then
SOURCE_DEF="${SOURCE_DEF}${SEPARATOR}"
fi
SOURCE_DEF="${SOURCE_DEF}${SHUFFLED_ITEM}"
done < <(shuf -e "${SOURCES_GROUP[@]}")
fi
done
echo "${SOURCE_DEF}"
}
downloader::compute_sha256sum() {
local FILEPATH="${1:-}"
if [[ -z "${FILEPATH}" ]] || [[ ! -f "${FILEPATH}" ]]; then
return 1
fi
if command -v sha256sum &>/dev/null; then
sha256sum "${FILEPATH}" | cut -f1 -d' '
elif command -v shasum &>/dev/null; then
shasum -a 256 "${FILEPATH}" | cut -f1 -d' '
fi
}
__downloader::download_file() {
local CURRENT_DOWNLOAD_URL_SET="${1:-}"
local DOWNLOAD_PATH="${2:-}"
local IS_RETRY="${3:-no}"
if [[ -z "${CURRENT_DOWNLOAD_URL_SET}" ]] || [[ -z "${DOWNLOAD_PATH}" ]]; then
return 1
fi
local DOWNLOAD_URL DOWNLOAD_EXPECTED_SIZE DOWNLOAD_EXPECTED_HASH
DOWNLOAD_URL=$(helper::string::unshift::next_value "${CURRENT_DOWNLOAD_URL_SET}")
CURRENT_DOWNLOAD_URL_SET=$(helper::string::unshift::remainder "${CURRENT_DOWNLOAD_URL_SET}")
DOWNLOAD_EXPECTED_SIZE=$(helper::string::unshift::next_value "${CURRENT_DOWNLOAD_URL_SET}")
CURRENT_DOWNLOAD_URL_SET=$(helper::string::unshift::remainder "${CURRENT_DOWNLOAD_URL_SET}")
DOWNLOAD_EXPECTED_HASH=$(helper::string::unshift::next_value "${CURRENT_DOWNLOAD_URL_SET}")
local DOWNLOAD_FILE="${DOWNLOAD_PATH##*/}"
local TARGET_STEP_NAME="Download ${DOWNLOAD_FILE}"
if [[ "${IS_RETRY}" == "no" ]]; then
if [[ "${CURRENT_STEP_NAME}" != "${TARGET_STEP_NAME}" ]]; then
term::step::new "${TARGET_STEP_NAME}"
fi
term::step::progress "Starting..."
else
term::step::new "${TARGET_STEP_NAME} (retry)"
fi
local DOWNLOAD_PROGRESS
local KDIALOG_DBUS_ADDRESS=()
if [[ "${_arg_ui_mode:-none}" == "kdialog" ]]; then
read -ra KDIALOG_DBUS_ADDRESS < <(kdialog --title "${CURRENT_STEP_NAME}" --progressbar "Starting download..." 0 2>/dev/null)
fi
helper::progress::make_consistant __downloader::download_file_with_progress "${DOWNLOAD_URL}" "${DOWNLOAD_PATH}" | while IFS= read -r DOWNLOAD_PROGRESS; do
if [[ -z "${DOWNLOAD_PROGRESS}" ]]; then
term::step::progress "Starting..." >&6
if [[ "${_arg_ui_mode:-none}" == "kdialog" ]]; then
busctl --user call "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "setLabelText" s "Starting Download..." 2>/dev/null || break
busctl --user set-property "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "maximum" i 0 2>/dev/null || break
fi
continue
fi
local TOTAL_SIZE CURRENT_PROGRESS RECEIVED SPEED
DOWNLOAD_PROGRESS=$(helper::string::unshift::remainder "${DOWNLOAD_PROGRESS}")
TOTAL_SIZE=$(helper::string::unshift::next_value "${DOWNLOAD_PROGRESS}")
DOWNLOAD_PROGRESS=$(helper::string::unshift::remainder "${DOWNLOAD_PROGRESS}")
CURRENT_PROGRESS=$(helper::string::unshift::next_value "${DOWNLOAD_PROGRESS}")
DOWNLOAD_PROGRESS=$(helper::string::unshift::remainder "${DOWNLOAD_PROGRESS}")
RECEIVED=$(helper::string::unshift::next_value "${DOWNLOAD_PROGRESS}")
DOWNLOAD_PROGRESS=$(helper::string::unshift::remainder "${DOWNLOAD_PROGRESS}")
SPEED=$(helper::string::unshift::next_value "${DOWNLOAD_PROGRESS}")
term::step::progress "${CURRENT_PROGRESS}% @ ${SPEED}/s" >&6
local DIALOG_TEXT="Downloading ${DOWNLOAD_FILE}\n${RECEIVED}"
if [[ -n "${TOTAL_SIZE:-}" ]]; then
DIALOG_TEXT="${DIALOG_TEXT} of ${TOTAL_SIZE} (${CURRENT_PROGRESS}%)"
else
DIALOG_TEXT="${DIALOG_TEXT} downloaded (${CURRENT_PROGRESS}%)"
fi
DIALOG_TEXT="${DIALOG_TEXT}\nSpeed : ${SPEED}/s"
if [[ "${_arg_ui_mode:-none}" == "kdialog" ]]; then
local ESCAPED_DIALOG_TEXT
ESCAPED_DIALOG_TEXT="$(echo -e "${DIALOG_TEXT}")"
busctl --user set-property "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "maximum" i 100 2>/dev/null || break
busctl --user set-property "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "value" i "${CURRENT_PROGRESS}" 2>/dev/null || break
busctl --user call "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "setLabelText" s "${ESCAPED_DIALOG_TEXT}" 2>/dev/null || break
# Check for cancellation
local WAS_CANCELLED
WAS_CANCELLED="$(busctl --user call "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "wasCancelled" 2>/dev/null || echo "b true")"
if [[ "${WAS_CANCELLED}" == "b true" ]]; then
break
fi
elif [[ "${_arg_ui_mode:-none}" == "zenity" ]]; then
echo "${CURRENT_PROGRESS}"
echo "# ${DIALOG_TEXT}"
fi
done | {
if [[ "${_arg_ui_mode:-none}" == "zenity" ]]; then
zenity --progress --percentage=0 --text="Starting download..." --time-remaining --auto-close 2>/dev/null
else
cat - >/dev/null
fi
}
local DOWNLOAD_STATUS="${PIPESTATUS[0]}"
if [[ "${DOWNLOAD_STATUS}" -ne 0 ]]; then
if [[ "${_arg_ui_mode:-none}" == "kdialog" ]]; then
busctl --user call "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "close" 2>/dev/null || true
fi
term::step::failed
return "${DOWNLOAD_STATUS}"
fi
if [[ "${_arg_ui_mode:-none}" == "kdialog" ]]; then
busctl --user call "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "close" 2>/dev/null || true
fi
local DL_RESULT_FILESIZE DL_RESULT_HASH
if [[ -n "${DOWNLOAD_EXPECTED_SIZE:-}" ]]; then
DL_RESULT_FILESIZE=$(stat --format=%s "${DOWNLOAD_PATH}") || {
term::step::failed
return 1
}
if [[ "${DL_RESULT_FILESIZE}" -ne "${DOWNLOAD_EXPECTED_SIZE}" ]]; then
term::step::failed
return 1
fi
fi
if [[ -n "${DOWNLOAD_EXPECTED_HASH:-}" ]]; then
DL_RESULT_HASH=$(helper::progress::run_with_progress "Verifying file" downloader::compute_sha256sum "${DOWNLOAD_PATH}")
if [[ -n "${DL_RESULT_HASH}" ]] && [[ "${DL_RESULT_HASH}" != "${DOWNLOAD_EXPECTED_HASH}" ]]; then
term::step::failed
return 1
fi
fi
term::step::complete
}
# This function parses the output of the various downloaders to report status information
__downloader::download_file_with_progress() {
local DOWNLOAD_URL="${1:-}"
local DOWNLOAD_PATH="${2:-}"
if [[ -z "${DOWNLOAD_URL}" ]] || [[ -z "${DOWNLOAD_PATH}" ]]; then
return 1
fi
local DOWNLOAD_FILE="${DOWNLOAD_PATH##*/}"
local TRANSFER_PROGRESS=""
local IS_SPECIAL_URL
IS_SPECIAL_URL=$(__downloader::identify_special_host "${DOWNLOAD_URL}")
local RESOLVED_GITHUB_TOKEN=""
if [[ "${IS_SPECIAL_URL}" == "github.com" ]]; then
RESOLVED_GITHUB_TOKEN="${GITHUB_TOKEN:-${GH_TOKEN:-}}"
fi
if [[ "${DOWNLOADER_DL_TYPE}" == "aria2c" ]]; then
local ARIA2C_CAPTURE_REGEX='^\[#[a-z0-9]+\s+([1-9][0-9.]*(B|KiB|MiB|GiB|TiB))/([1-9][0-9.]*(B|KiB|MiB|GiB|TiB))\(([0-9]+)%\)\s+CN:[0-9]+\s+DL:([1-9][0-9.]*(B|KiB|MiB|GiB|TiB))\s+ETA:.+\]$'
local ARIA2C_DOWNLOAD_DIRECTORY="${DOWNLOAD_PATH%/*}"
local ARIA2C_DOWNLOAD_FILE="${DOWNLOAD_PATH##*/}"
if [[ "${IS_SPECIAL_URL}" == "github.com" ]] && [[ -n "${RESOLVED_GITHUB_TOKEN}" ]]; then
ADDITIONAL_ARGS+=("--header=Authorization: Bearer ${RESOLVED_GITHUB_TOKEN}")
elif [[ "${IS_SPECIAL_URL}" == "archive.org" ]]; then
ADDITIONAL_ARGS+=("-x" "${ARIA2C_ARCHIVEORG_CONNECTIONS}")
fi
stdbuf -o0 -- "${DOWNLOADER_DL_BIN}" --no-conf=true --allow-overwrite=true --remove-control-file=true \
--daemon=false --enable-color=false --stop-with-process="$$" \
--truncate-console-readout=false --console-log-level=warn --summary-interval=0 \
--connect-timeout="${DOWNLOADER_TIMEOUT}" \
--user-agent="${DOWNLOADER_USER_AGENT}" \
--dir="${ARIA2C_DOWNLOAD_DIRECTORY}" --out="${ARIA2C_DOWNLOAD_FILE}" \
"${ADDITIONAL_ARGS[@]}" \
"${DOWNLOAD_URL}" |
stdbuf -oL -- tr $'\r' $'\n' |
while IFS= read -r TRANSFER_PROGRESS; do
if [[ "${TRANSFER_PROGRESS}" =~ ${ARIA2C_CAPTURE_REGEX} ]]; then
echo "${DOWNLOAD_FILE}|${BASH_REMATCH[3]}|${BASH_REMATCH[5]}|${BASH_REMATCH[1]}|${BASH_REMATCH[6]}"
fi
done
elif [[ "${DOWNLOADER_DL_TYPE}" == "curl" ]]; then
# % Total % Received % Xfered AvgSpdDown AvgSpdUp Time Total Time Spent Time Left Current Speed
local CURL_CAPTURE_REGEX='^\s*[0-9]+\s+([1-9][0-9.]*[kKMGT]?)\s+([0-9]+)\s+([0-9.]+[kKMGT]?)\s+[0-9]+\s+[0-9.]+[kKMGT]?\s+[0-9.]+[kKMGT]?\s+[0-9.]+[kKMGT]?\s+[-0-9:]+\s+[-0-9:]+\s+[-0-9:]+\s+([0-9.]+[kKMGT]?)\s*$'
local ADDITIONAL_ARGS=()
if [[ "${IS_SPECIAL_URL}" == "github.com" ]] && [[ -n "${RESOLVED_GITHUB_TOKEN}" ]]; then
ADDITIONAL_ARGS+=("--header" "Authorization: Bearer ${RESOLVED_GITHUB_TOKEN}")
fi
"${DOWNLOADER_DL_BIN}" -LN --progress-meter "${ADDITIONAL_ARGS[@]}" \
--connect-timeout "${DOWNLOADER_TIMEOUT}" \
--user-agent "${DOWNLOADER_USER_AGENT}" \
"${DOWNLOAD_URL}" -o "${DOWNLOAD_PATH}" 2>&1 |
stdbuf -oL -- tr $'\r' $'\n' |
while IFS= read -r TRANSFER_PROGRESS; do
if [[ "${TRANSFER_PROGRESS}" =~ ${CURL_CAPTURE_REGEX} ]]; then
echo "${DOWNLOAD_FILE}|${BASH_REMATCH[1]}|${BASH_REMATCH[2]}|${BASH_REMATCH[3]}|${BASH_REMATCH[4]}"
fi
done
return "${PIPESTATUS[0]}"
elif [[ "${DOWNLOADER_DL_TYPE}" == "wget" ]]; then
local WGET_LENGTH_CAPTURE_REGEX='^Length:\s+[0-9]+\s+\((.*)\).*$'
local WGET_CAPTURE_REGEX='^\s*([0-9.]+[BKMG])\s+[. ]+\s+([0-9]+)%\s+([0-9.]+[BKMG])\s+.*$'
local TOTAL_SIZE
local ADDITIONAL_ARGS=()
if [[ "${IS_SPECIAL_URL}" == "github.com" ]] && [[ -n "${RESOLVED_GITHUB_TOKEN}" ]]; then
ADDITIONAL_ARGS+=("--header=Authorization: Bearer ${RESOLVED_GITHUB_TOKEN}")
fi
"${DOWNLOADER_DL_BIN}" --progress=dot "${ADDITIONAL_ARGS[@]}" \
--timeout="${DOWNLOADER_TIMEOUT}" \
--user-agent="${DOWNLOADER_USER_AGENT}" \
"${DOWNLOAD_URL}" -O "${DOWNLOAD_PATH}" -o - 2>&1 |
while IFS= read -r TRANSFER_PROGRESS; do
if [[ "${TRANSFER_PROGRESS}" =~ ${WGET_LENGTH_CAPTURE_REGEX} ]]; then
TOTAL_SIZE="${BASH_REMATCH[1]}"
elif [[ "${TRANSFER_PROGRESS}" =~ ${WGET_CAPTURE_REGEX} ]]; then
echo "${DOWNLOAD_FILE}|${TOTAL_SIZE}|${BASH_REMATCH[2]}|${BASH_REMATCH[1]}|${BASH_REMATCH[3]}"
fi
done
return "${PIPESTATUS[0]}"
elif [[ "${DOWNLOADER_DL_TYPE}" == "wget2" ]]; then
local WGET2_CAPTURE_REGEX='^\[1G.+\s+([0-9]+)%\s+\[.+\]\s+([0-9.]+[BKMG])\s+([0-9.]+[BKMG])(B?/s)?\s*$'
local ADDITIONAL_ARGS=()
if [[ "${IS_SPECIAL_URL}" == "github.com" ]] && [[ -n "${RESOLVED_GITHUB_TOKEN}" ]]; then
ADDITIONAL_ARGS+=("--header=Authorization: Bearer ${RESOLVED_GITHUB_TOKEN}")
fi
"${DOWNLOADER_DL_BIN}" --progress=bar --force-progress "${ADDITIONAL_ARGS[@]}" \
--timeout="${DOWNLOADER_TIMEOUT}" \
--user-agent="${DOWNLOADER_USER_AGENT}" \
"${DOWNLOAD_URL}" -O "${DOWNLOAD_PATH}" 2>&1 |
stdbuf -oL -- tr $'\r\033' $'\n\n' |
while IFS= read -r TRANSFER_PROGRESS; do
if [[ "${TRANSFER_PROGRESS}" =~ ${WGET2_CAPTURE_REGEX} ]]; then
echo "${DOWNLOAD_FILE}||${BASH_REMATCH[1]}|${BASH_REMATCH[2]}|${BASH_REMATCH[3]}"
fi
done
return "${PIPESTATUS[0]}"
fi
}
__downloader::identify_special_host() {
local URL="${1:-}"
if [[ "${URL}" =~ ^https://(github\.com|.+\.github\.com|.+\.githubusercontent\.com)/ ]]; then
echo "github.com"
elif [[ "${URL}" =~ ^https://(archive\.org|.+\.archive\.org)/ ]]; then
echo "archive.org"
fi
}
# shellcheck shell=bash
local UNARCHIVER_BIN=""
if command -v "7z" &>/dev/null; then
UNARCHIVER_BIN="7z"
elif command -v "7zz" &>/dev/null; then
UNARCHIVER_BIN="7zz"
fi
unarchiver::unarchive_file() {
local ITEM_NAME="${1:-}"
local ARCHIVE_PATH="${2:-}"
local TARGET_PATH="${3:-}"
local IGNORE_PATTERNS_VAR_NAME="${4:-}"
local IGNORE_PATTERNS_RECURSIVE_VAR_NAME="${5:-}"
if [[ -z "${ARCHIVE_PATH}" ]] || [[ -z "${TARGET_PATH}" ]]; then
return 1
fi
if [[ ! -f "${ARCHIVE_PATH}" ]]; then
return 1
fi
term::step::new "Extract ${ITEM_NAME}"
local IS_TARBALL="n"
if __unarchiver::is_tarball "${ARCHIVE_PATH}"; then
IS_TARBALL="y"
fi
local UNARCHIVE_PROGRESS
local KDIALOG_DBUS_ADDRESS=()
if [[ "${_arg_ui_mode:-none}" == "kdialog" ]]; then
read -ra KDIALOG_DBUS_ADDRESS < <(kdialog --title "${CURRENT_STEP_NAME}" --progressbar "Extracting ${ITEM_NAME}..." 0 2>/dev/null)
busctl --user call "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "showCancelButton" b "false" 2>/dev/null || true
fi
helper::progress::make_consistant __unarchiver::unarchive_file_with_progress "${ARCHIVE_PATH}" "${TARGET_PATH}" "${IGNORE_PATTERNS_VAR_NAME}" "${IGNORE_PATTERNS_RECURSIVE_VAR_NAME}" |
while IFS= read -r UNARCHIVE_PROGRESS; do
if [[ -z "${UNARCHIVE_PROGRESS}" ]]; then
term::step::progress "" >&6
if [[ "${_arg_ui_mode:-none}" == "kdialog" ]]; then
busctl --user set-property "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "maximum" i 0 2>/dev/null || true
fi
continue
fi
local PROGRESS_TEXT="${UNARCHIVE_PROGRESS}"
if [[ "${IS_TARBALL}" == "n" ]]; then
PROGRESS_TEXT="${PROGRESS_TEXT}%"
fi
term::step::progress "${PROGRESS_TEXT}" >&6
local DIALOG_TEXT="Extracting ${ITEM_NAME} (${PROGRESS_TEXT})"
if [[ "${_arg_ui_mode:-none}" == "kdialog" ]]; then
local ESCAPED_DIALOG_TEXT
ESCAPED_DIALOG_TEXT="$(echo -e "${DIALOG_TEXT}")"
if [[ "${IS_TARBALL}" == "n" ]]; then
busctl --user set-property "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "maximum" i 100 2>/dev/null || true
busctl --user set-property "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "value" i "${UNARCHIVE_PROGRESS}" 2>/dev/null || true
fi
busctl --user call "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "setLabelText" s "${ESCAPED_DIALOG_TEXT}" 2>/dev/null || true
elif [[ "${_arg_ui_mode:-none}" == "zenity" ]]; then
if [[ "${IS_TARBALL}" == "n" ]]; then
echo "${UNARCHIVE_PROGRESS}"
fi
echo "# ${DIALOG_TEXT}"
fi
done | {
if [[ "${_arg_ui_mode:-none}" == "zenity" ]]; then
if [[ "${IS_TARBALL}" == "n" ]]; then
zenity --progress --percentage=0 --text="Extracting ${ITEM_NAME}..." --no-cancel --time-remaining --auto-close 2>/dev/null
else
zenity --progress --pulsate --text="Extracting ${ITEM_NAME}..." --no-cancel --auto-close 2>/dev/null
fi
else
cat - >/dev/null
fi
}
local UNARCHIVE_STATUS="${PIPESTATUS[0]}"
if [[ "${UNARCHIVE_STATUS}" -ne 0 ]]; then
if [[ "${_arg_ui_mode:-none}" == "kdialog" ]]; then
busctl --user call "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "close" 2>/dev/null || true
fi
term::step::failed
return "${UNARCHIVE_STATUS}"
fi
if [[ "${_arg_ui_mode:-none}" == "kdialog" ]]; then
busctl --user call "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "close" 2>/dev/null || true
fi
term::step::complete
}
local SEVENZ_VERSION_MAJOR="0"
local SEVENZ_VERSION_MINOR="0"
local SEVENZ_REQUIRE_SNLD="no"
__unarchiver::read_7z_version() {
if [[ "${SEVENZ_VERSION_MAJOR}" -gt 0 ]]; then
return 0
fi
local SEZENV_VERSION_REGEX='^7-Zip( \[[0-9a-zA-Z_-]\]+)? ([0-9]+)(\.([0-9]+)| |:)'
local SEVENZ_OUTPUT_LINE
while IFS= read -r SEVENZ_OUTPUT_LINE; do
if [[ "${SEVENZ_VERSION_MAJOR}" -gt 0 ]]; then
continue
fi
if [[ "${SEVENZ_OUTPUT_LINE}" =~ ${SEZENV_VERSION_REGEX} ]]; then
SEVENZ_VERSION_MAJOR="${BASH_REMATCH[2]}"
SEVENZ_VERSION_MINOR="${BASH_REMATCH[4]:-0}"
if [[ "${SEVENZ_VERSION_MAJOR}" -gt 25 ]]; then
SEVENZ_REQUIRE_SNLD="yes"
elif [[ "${SEVENZ_VERSION_MAJOR}" -eq 25 ]] && [[ "${SEVENZ_VERSION_MINOR}" -ge 1 ]]; then
SEVENZ_REQUIRE_SNLD="yes"
fi
fi
done < <("${UNARCHIVER_BIN}" 2>&1)
}
__unarchiver::is_tarball() {
local ARCHIVE_PATH="${1:-}"
local ARCHIVE_FILENAME="${ARCHIVE_PATH##*/}"
case "${ARCHIVE_FILENAME}" in
*.tar.gz | *.tgz)
return 0
;;
*.tar.bz2 | *.tbz)
return 0
;;
*.tar.xz | *.txz)
return 0
;;
*.tar.zst | *.tar.zstd)
return 0
;;
esac
return 1
}
__unarchiver::unarchive_file_with_progress() {
local ARCHIVE_PATH="${1:-}"
local TARGET_PATH="${2:-}"
local IGNORE_PATTERNS_VAR_NAME="${3:-}"
local IGNORE_PATTERNS_RECURSIVE_VAR_NAME="${4:-}"
if [[ -z "${ARCHIVE_PATH}" ]] || [[ -z "${TARGET_PATH}" ]]; then
return 1
fi
local IS_TARBALL="n"
if __unarchiver::is_tarball "${ARCHIVE_PATH}"; then
IS_TARBALL="y"
fi
local UNARCHIVE_PROGRESS
if [[ "${IS_TARBALL}" == "y" ]]; then
local TAR_REGEX='^tar:\s+r:\s+[0-9]+\s+\((.+)\)$'
__unarchiver::unarchive_file::call_tar \
"${ARCHIVE_PATH}" "${TARGET_PATH}" \
"${IGNORE_PATTERNS_VAR_NAME}" "${IGNORE_PATTERNS_RECURSIVE_VAR_NAME}" |
while IFS= read -r UNARCHIVE_PROGRESS; do
if [[ "${UNARCHIVE_PROGRESS}" =~ ${TAR_REGEX} ]]; then
echo "${BASH_REMATCH[1]}"
else
echo "${UNARCHIVE_PROGRESS}" 1>&2
fi
done
return "${PIPESTATUS[0]}"
else
local SEVENZ_REGEX='^\s*([0-9]+)%'
__unarchiver::unarchive_file::call_7z \
"${ARCHIVE_PATH}" "${TARGET_PATH}" \
"${IGNORE_PATTERNS_VAR_NAME}" "${IGNORE_PATTERNS_RECURSIVE_VAR_NAME}" |
stdbuf -oL -- tr $'\b\r' $'\n\n' |
while IFS= read -r UNARCHIVE_PROGRESS; do
if [[ "${UNARCHIVE_PROGRESS}" =~ ${SEVENZ_REGEX} ]]; then
echo "${BASH_REMATCH[1]}"
fi
done
return "${PIPESTATUS[0]}"
fi
}
__unarchiver::unarchive_file::call_7z() {
local ARCHIVE_PATH="${1:-}"
local TARGET_PATH="${2:-}"
if [[ -z "${ARCHIVE_PATH}" ]] || [[ -z "${TARGET_PATH}" ]]; then
return 1
fi
local IGNORE_PATTERNS=()
local IGNORE_PATTERNS_VAR_NAME="${3:-}"
if [[ -n "${IGNORE_PATTERNS_VAR_NAME}" ]]; then
local IGNORE_PATTERN_VAR_REF="${IGNORE_PATTERNS_VAR_NAME}[@]"
IGNORE_PATTERNS=("${!IGNORE_PATTERN_VAR_REF}")
fi
local IGNORE_PATTERNS_RECURSIVE=()
local IGNORE_PATTERNS_RECURSIVE_VAR_NAME="${4:-}"
if [[ -n "${IGNORE_PATTERNS_RECURSIVE_VAR_NAME}" ]]; then
local IGNORE_PATTERN_RECURSIVE_VAR_REF="${IGNORE_PATTERNS_RECURSIVE_VAR_NAME}[@]"
IGNORE_PATTERNS_RECURSIVE=("${!IGNORE_PATTERN_RECURSIVE_VAR_REF}")
fi
__unarchiver::read_7z_version
local SEZENZ_ARGS=()
SEZENZ_ARGS=("x" "${ARCHIVE_PATH}" "-y" "-bsp1" "-bso0" "-bse2" "-aoa" "-o${TARGET_PATH}")
if [[ "${SEVENZ_REQUIRE_SNLD}" == "yes" ]]; then
SEZENZ_ARGS+=("-snld")
else
SEZENZ_ARGS+=("-snl")
fi
local IGNORE
for IGNORE in "${IGNORE_PATTERNS[@]}"; do
SEZENZ_ARGS+=("-x!${IGNORE}")
done
for IGNORE in "${IGNORE_PATTERNS_RECURSIVE[@]}"; do
SEZENZ_ARGS+=("-xr!${IGNORE}")
done
"${UNARCHIVER_BIN}" "${SEZENZ_ARGS[@]}"
}
__unarchiver::unarchive_file::call_tar() {
local ARCHIVE_PATH="${1:-}"
local TARGET_PATH="${2:-}"
if [[ -z "${ARCHIVE_PATH}" ]] || [[ -z "${TARGET_PATH}" ]]; then
return 1
fi
local IGNORE_PATTERNS=()
local IGNORE_PATTERNS_VAR_NAME="${3:-}"
if [[ -n "${IGNORE_PATTERNS_VAR_NAME}" ]]; then
local IGNORE_PATTERN_VAR_REF="${IGNORE_PATTERNS_VAR_NAME}[@]"
IGNORE_PATTERNS=("${!IGNORE_PATTERN_VAR_REF}")
fi
local IGNORE_PATTERNS_RECURSIVE=()
local IGNORE_PATTERNS_RECURSIVE_VAR_NAME="${4:-}"
if [[ -n "${IGNORE_PATTERNS_RECURSIVE_VAR_NAME}" ]]; then
local IGNORE_PATTERN_RECURSIVE_VAR_REF="${IGNORE_PATTERNS_RECURSIVE_VAR_NAME}[@]"
IGNORE_PATTERNS_RECURSIVE=("${!IGNORE_PATTERN_RECURSIVE_VAR_REF}")
fi
local TAR_ARGS=()
TAR_ARGS=("xf" "${ARCHIVE_PATH}" "--checkpoint=10" '--checkpoint-action=echo=%{r}T' "--overwrite" "-C" "${TARGET_PATH}")
local IGNORE
for IGNORE in "${IGNORE_PATTERNS[@]}"; do
TAR_ARGS+=("--exclude=${IGNORE}")
done
for IGNORE in "${IGNORE_PATTERNS_RECURSIVE[@]}"; do
TAR_ARGS+=("--exclude=${IGNORE}")
done
tar "${TAR_ARGS[@]}" 2>&1
}
# Patch Metadata Download Step
PATCH_METADATA_URL="https://api.github.com/repos/OldUnreal/UT2004Patches/releases"
# Download Steps
# shellcheck disable=SC2034 # Used dynamically below
local TITLE_PRIMARY_DOWNLOAD_SOURCES=(
"https://files.oldunreal.net/UT2004.ISO|2995322880|43e9182ae20bcbc0f6f4588fee6c1b336c261f1465403118a1973b09b1a22541"
"https://files2.oldunreal.net/UT2004.ISO|2995322880|43e9182ae20bcbc0f6f4588fee6c1b336c261f1465403118a1973b09b1a22541"
"https://files3.oldunreal.net/UT2004.ISO|2995322880|43e9182ae20bcbc0f6f4588fee6c1b336c261f1465403118a1973b09b1a22541"
)
# shellcheck disable=SC2034 # Used dynamically below
local TITLE_BACKUP_DOWNLOAD_SOURCES=(
"https://archive.org/download/ut-2004/UT2004.ISO|3751510016|7ae95242aa23d5e31b353811e1e920a4377fa53cf728b8edcb17006b7f3c4e97"
)
# Build Download sources
declare -A DOWNLOADS_SOURCE_LIST=(
[game]="$(downloader::build_download_source_definition "TITLE_PRIMARY_DOWNLOAD_SOURCES" "TITLE_BACKUP_DOWNLOAD_SOURCES")"
)
declare -A DOWNLOADS_FILENAME_LIST=(
[game]="UT2004.iso"
)
# Game Data Unpacking
# shellcheck disable=SC2034 # Used dynamically
local UNPACK_IGNORE_PATTERNS=(
# Old Setup Files
'AutoRunData'
'Disk1/layout.bin'
'Disk1/Setup.*'
'Disk1/setup.*'
'SoNow'
'*.*'
)
# shellcheck shell=bash
step::welcome_banner() {
ansi::banner "OldUnreal ${PRODUCT_NAME} Linux Installer"
echo
if [[ "${_arg_ui_mode:-none}" != "none" ]]; then
echo -e "$(ansi::styled "Installer is running in GUI mode. If no window is displayed," "${ansi_stylenum[dim]}")"
echo -e "$(ansi::styled "type " "${ansi_stylenum[dim]}")CTRL+C$(ansi::styled " to kill the installer, and start it with the" "${ansi_stylenum[dim]}")"
echo -e "--ui-mode=none$(ansi::styled " argument." "${ansi_stylenum[dim]}")"
echo
fi
}
step::welcome_banner
# shellcheck shell=bash
step::check_dependencies() {
term::step::new "Checking Dependencies"
if [[ "${ARCHITECTURE_SUFFIX}" == 'NOT_SUPPORTED' ]]; then
term::step::failed_with_error "CPU Architecture ${DETECTED_ARCHITECTURE} is not currently supported."
return 1
fi
if [[ "${_arg_unrealed}" == "on" ]] && [[ "${UEED_SYSTEM_FOLDER_SUFFIX}" == 'NOT_SUPPORTED' ]]; then
term::step::failed_with_error "CPU Architecture ${DETECTED_ARCHITECTURE} is not currently supported for UnrealEd. Remove the -e flag."
return 1
fi
local MISSING_DEPS=()
local MISSING_DEPS_RHEL=()
local MISSING_DEPS_DEB=()
local MISSING_DEPS_ARCH=()
local MISSING_DEPS_OPENSUSE=()
local MISSING_DEPS_BREW=()
local UI_MODE_DEPS_MET="yes"
# Check UI Mode Dependencies
if [[ "${_arg_ui_mode:-none}" == "kdialog" ]]; then
term::step::progress "kdialog"
if ! command -v "kdialog" &>/dev/null; then
MISSING_DEPS+=("kdialog")
MISSING_DEPS_RHEL+=("kdialog")
MISSING_DEPS_DEB+=("kdialog")
MISSING_DEPS_ARCH+=("kdialog")
MISSING_DEPS_OPENSUSE+=("kdialog")
UI_MODE_DEPS_MET="no"
fi
term::step::progress "busctl"
if ! command -v "busctl" &>/dev/null; then
MISSING_DEPS+=("systemd")
MISSING_DEPS_RHEL+=("systemd")
MISSING_DEPS_DEB+=("systemd")
MISSING_DEPS_ARCH+=("systemd")
MISSING_DEPS_OPENSUSE+=("systemd")
UI_MODE_DEPS_MET="no"
fi
elif [[ "${_arg_ui_mode:-none}" == "zenity" ]]; then
term::step::progress "zenity"
if ! command -v "zenity" &>/dev/null; then
MISSING_DEPS+=("zenity")
MISSING_DEPS_RHEL+=("zenity")
MISSING_DEPS_DEB+=("zenity")
MISSING_DEPS_ARCH+=("zenity")
MISSING_DEPS_OPENSUSE+=("zenity")
MISSING_DEPS_BREW+=("zenity")
UI_MODE_DEPS_MET="no"
fi
fi
# Check Downloaders
term::step::progress "curl"
if ! command -v "curl" &>/dev/null &&
! command -v "wget" &>/dev/null &&
! command -v "wget2" &>/dev/null; then
MISSING_DEPS+=("curl (or wget)")
MISSING_DEPS_RHEL+=("curl")
MISSING_DEPS_DEB+=("curl")
MISSING_DEPS_ARCH+=("curl")
MISSING_DEPS_OPENSUSE+=("curl")
MISSING_DEPS_BREW+=("curl")
fi
# Check Archivers
term::step::progress "tar"
if ! command -v "tar" &>/dev/null; then
MISSING_DEPS+=("tar")
MISSING_DEPS_RHEL+=("tar")
MISSING_DEPS_DEB+=("tar")
MISSING_DEPS_ARCH+=("tar")
MISSING_DEPS_OPENSUSE+=("tar")
fi
term::step::progress "7zip"
if ! command -v "7z" &>/dev/null &&
! command -v "7zz" &>/dev/null; then
MISSING_DEPS+=("7zip, 7zip [Debian > bookworm], p7zip-full [Debian <= bookworm], or 7zip-standalone-all [Fedora/RHEL]")
MISSING_DEPS_RHEL+=("7zip-standalone-all")
MISSING_DEPS_DEB+=("p7zip-full")
MISSING_DEPS_ARCH+=("7zip")
MISSING_DEPS_OPENSUSE+=("7zip")
MISSING_DEPS_BREW+=("7zip")
fi
# Check jq
term::step::progress "jq"
if ! command -v "jq" &>/dev/null; then
MISSING_DEPS+=("jq")
MISSING_DEPS_RHEL+=("jq")
MISSING_DEPS_DEB+=("jq")
MISSING_DEPS_ARCH+=("jq")
MISSING_DEPS_OPENSUSE+=("jq")
MISSING_DEPS_BREW+=("jq")
fi
if [[ "${PRODUCT_SHORTNAME}" == "UT2004" ]]; then
# Check unshield
term::step::progress "unshield"
if ! command -v "unshield" &>/dev/null; then
# Can it be downloaded?
case "${ARCHITECTURE_SUFFIX}" in
amd64 | arm64)
# shellcheck disable=SC2034 # May not be used in all installers
DOWNLOADS_SOURCE_LIST[unshield]="https://raw.githubusercontent.com/OldUnreal/FullGameInstallers/master/Linux/deps/unshield-${ARCHITECTURE_SUFFIX}||"
DOWNLOADS_FILENAME_LIST[unshield]="unshield"
;;
*)
MISSING_DEPS+=("unshield")
MISSING_DEPS_RHEL+=("unshield")
MISSING_DEPS_DEB+=("unshield")
MISSING_DEPS_ARCH+=("unshield")
MISSING_DEPS_OPENSUSE+=("unshield")
MISSING_DEPS_BREW+=("unshield")
;;
esac
fi
fi
if [[ "${UI_MODE_DEPS_MET}" == "no" ]]; then
_arg_ui_mode="none"
fi
if [[ "${#MISSING_DEPS[@]}" -gt 0 ]]; then
local DISTRO_DERIVATIVE=""
local DISTRO_PKG_INSTALL_CMD=""
if command -v "pacman" &>/dev/null; then
DISTRO_DERIVATIVE="Arch"
DISTRO_PKG_INSTALL_CMD="sudo pacman -S ${MISSING_DEPS_ARCH[*]}"
elif command -v "dnf" &>/dev/null; then
DISTRO_DERIVATIVE="Fedora/RHEL"
DISTRO_PKG_INSTALL_CMD="sudo dnf install ${MISSING_DEPS_RHEL[*]}"
elif command -v "apt" &>/dev/null; then
DISTRO_DERIVATIVE="Debian"
DISTRO_PKG_INSTALL_CMD="sudo apt install ${MISSING_DEPS_DEB[*]}"
elif command -v "zypper" &>/dev/null; then
DISTRO_DERIVATIVE="OpenSUSE"
DISTRO_PKG_INSTALL_CMD="sudo zypper install ${MISSING_DEPS_OPENSUSE[*]}"
fi
local ERROR_TEXT="Missing required dependencies.\n\nYour system is missing dependencies that are required by this installer.\nPlease install the following required packages:"
local PKG
for PKG in "${MISSING_DEPS[@]}"; do
ERROR_TEXT="${ERROR_TEXT}\n - ${PKG}"
done
if [[ -n "${DISTRO_DERIVATIVE}" ]]; then
ERROR_TEXT="${ERROR_TEXT}\n\nOn ${DISTRO_DERIVATIVE} or derivatives, you should be able to install"
ERROR_TEXT="${ERROR_TEXT}\nthe required package(s) using the following command:\n"
ERROR_TEXT="${ERROR_TEXT}\n ${DISTRO_PKG_INSTALL_CMD}"
fi
if command -v "brew" &>/dev/null && [[ "${#MISSING_DEPS_BREW[@]}" -gt 0 ]]; then
local BREW_INSTALL_CMD="brew install"
for PKG in "${MISSING_DEPS_BREW[@]}"; do
BREW_INSTALL_CMD="${BREW_INSTALL_CMD} ${PKG}"
done
ERROR_TEXT="${ERROR_TEXT}\n\nYou appear to have brew installed. Some of the required packages are available\n"
ERROR_TEXT="${ERROR_TEXT}\non brew. You can install them using the following command:\n"
ERROR_TEXT="${ERROR_TEXT}\n ${BREW_INSTALL_CMD}"
fi
term::step::failed_with_error "${ERROR_TEXT}"
if [[ "${UI_MODE_DEPS_MET}" == "no" ]]; then
echo 1>&2
echo "You do not have the required dependencies for the selected UI mode." 1>&2
echo -e "Please relaunch using the $(ansi::styled "--ui-mode=none" "${ansi_stylenum[bright]}") argument," 1>&2
echo "or install the required dependencies." 1>&2
fi
return 1
else
term::step::complete
fi
}
step::check_dependencies
# shellcheck shell=bash
local DESTINATION_HOMIFIED=""
# shellcheck disable=SC2034 # Used in some installers
local IS_DESTINATION_CASE_SENSITIVE_FS=""
step::check_destination() {
term::step::new "Checking Destination Folder"
if [[ -z "${_arg_destination:-}" ]]; then
term::step::failed_with_error "No destination folder set. Aborting installation."
return 78
fi
if [[ -d "${_arg_destination}" ]] && [[ ! -w "${_arg_destination}" ]]; then
term::step::failed_with_error "Destination folder not writable by user. Aborting installation."
return 77 #E_PERM
elif [[ ! -d "${_arg_destination}" ]]; then
# Create folder if it doesn't exist
mkdir -p "${_arg_destination}" &>/dev/null || {
term::step::failed_with_error "User does not have permission to create destination folder. Aborting installation."
return 77 #E_PERM
}
fi
_arg_destination="$(realpath "${_arg_destination}")"
if [[ ! -d "${_arg_destination%/}/Installer" ]]; then
# Create the installation folder
mkdir -p "${_arg_destination%/}/Installer" &>/dev/null || {
term::step::failed_with_error "User does not have permission to create destination folder. Aborting installation."
return 77 #E_PERM
}
fi
if [[ ! -w "${_arg_destination%/}/Installer" ]]; then
term::step::failed_with_error "The ./Installer subfolder of the destination cannot be written by this user."
return 77 #E_PERM
fi
# Check Case-Sensitivity
{
if [[ -f "${_arg_destination%/}/Installer/.check_casesensitive" ]]; then
rm -f "${_arg_destination%/}/Installer/.check_casesensitive"
fi
if [[ ! -f "${_arg_destination%/}/Installer/.Check_caseSensitive" ]]; then
touch "${_arg_destination%/}/Installer/.Check_caseSensitive"
fi
if [[ -f "${_arg_destination%/}/Installer/.check_casesensitive" ]]; then
# shellcheck disable=SC2034 # Used in some installers
IS_DESTINATION_CASE_SENSITIVE_FS="no"
else
# shellcheck disable=SC2034 # Used in some installers
IS_DESTINATION_CASE_SENSITIVE_FS="yes"
fi
rm -f "${_arg_destination%/}/Installer/.Check_caseSensitive"
} || {
term::step::failed_with_error "Unable to determine case sensitivity. Aborting installation."
return 77 #E_PERM
}
DESTINATION_HOMIFIED="${_arg_destination}"
{ [[ "${DESTINATION_HOMIFIED}" =~ ^"${HOME}"(/|$) ]] && DESTINATION_HOMIFIED="~${_arg_destination#"${HOME}"}"; } || true
term::step::complete
}
step::check_destination
# shellcheck shell=bash
local EPIC_TOS_URL="https://legal.epicgames.com/en-US/epicgames/tos"
step::eula() {
if [[ "${_arg_ui_mode:-none}" == "none" ]]; then
__step::eula::text
return 0
fi
term::step::new "Terms of Service"
local DIALOG_TEXT="The Epic Games Terms of Service apply to the use and distribution of this game,
and they supersede any other end user agreements that may accompany the game.
You may read the Terms of Service at this URL:
${EPIC_TOS_URL}"
local DIALOG_ARGS=()
if [[ "${_arg_ui_mode:-none}" == "kdialog" ]]; then
DIALOG_ARGS=(
kdialog
--title
"Terms of Service"
--yesno
"${DIALOG_TEXT}"
)
elif [[ "${_arg_ui_mode:-none}" == "zenity" ]]; then
DIALOG_ARGS=(
zenity
--text-info
"Terms of Service"
"--checkbox=I agree to Epic Games Terms of Service"
--width=450
--height=400
)
fi
if ! echo "${DIALOG_TEXT}" | "${DIALOG_ARGS[@]}" &>/dev/null; then
term::step::failed_with_error "Installation Aborted." 1>&2
return 1
fi
term::step::complete
}
__step::eula::text() {
echo
echo -e "The $(ansi::styled "Epic Games Terms of Service" "${ansi_stylenum[bright]}") apply to the use and distribution of this game,"
echo "and they supersede any other end user agreements that may accompany the game."
echo
echo "You may read the Terms of Service at this URL:"
echo " $(ansi::styled "${EPIC_TOS_URL}" "${ansi_stylenum[underline]}")"
echo
if ! term::yesno "Do you agree to the Terms of Service?"; then
echo
term::step::new "Terms of Service"
term::step::failed_with_error "Installation Aborted." 1>&2
return 1
fi
echo
term::step::new "Terms of Service"
term::step::complete
}
step::eula
# shellcheck shell=bash
local PATCH_METADATA_JSON
step::read_patch_meta_from_github() {
local PATCH_FOR_OS="${1:-linux}"
local PATCH_FOR_REASON="${2:-}"
local STEP_NAME="Fetch Patch Info from GitHub"
if [[ -n "${PATCH_FOR_REASON}" ]]; then
STEP_NAME="${STEP_NAME} (${PATCH_FOR_REASON})"
fi
term::step::new "${STEP_NAME}"
if [[ -z "${PATCH_METADATA_URL:-}" ]]; then
term::step::failed_with_error "Implementation error, PATCH_METADATA_URL not set."
return 1
fi
if [[ -z "${PATCH_METADATA_JSON:-}" ]]; then
term::step::progress "Downloading Metadata"
{ PATCH_METADATA_JSON=$(downloader::fetch_json "${PATCH_METADATA_URL}"); } ||
{
term::step::failed_with_error "Failed to read patch metadata from GitHub. Installation aborted."
return 1
}
fi
if ! type "step::read_patch_meta_from_github::metadata_filter" &>/dev/null; then
step::read_patch_meta_from_github::metadata_filter() {
local PATCH_OS_NAME="${1:-}"
# shellcheck disable=SC2034 # present by convention
local PATCH_TARGET_ARCHITECTURE="${2:-}"
if [[ "${PATCH_OS_NAME}" == "windows" ]]; then
echo "-${PATCH_OS_NAME}(-.+)?.zip"
return 0
fi
echo "-${PATCH_OS_NAME}"
}
fi
local IS_GITHUB_RELEASES_ARRAY="no"
{ IS_GITHUB_RELEASES_ARRAY=$(echo "${PATCH_METADATA_JSON}" | jq -r 'if (. | type) == "array" then "yes" else "no" end'); } || {
term::step::failed_with_error "Couldn't determine if we received a single release, or an array of releases. Installation aborted."
return 1
}
local JQ_FILTER=".assets[]"
if [[ "${IS_GITHUB_RELEASES_ARRAY}" == "yes" ]]; then
JQ_FILTER=".[0].assets[]"
fi
local METADATA_FILTER
METADATA_FILTER=$(step::read_patch_meta_from_github::metadata_filter "${PATCH_FOR_OS}" "${ARCHITECTURE_SUFFIX}")
local JQ_FILTER
{ JQ_FILTER+=' | select(.browser_download_url | ascii_downcase | test("'"${METADATA_FILTER}"'"))'; } ||
{
term::step::failed_with_error "Implementation error, step::read_patch_meta_from_github::metadata_filter runtime error."
return 1
}
local PATCH_DOWNLOAD_URL
{ PATCH_DOWNLOAD_URL=$(echo "${PATCH_METADATA_JSON}" | jq -r "[ ${JQ_FILTER} ] | .[0].browser_download_url"); } ||
{
term::step::failed_with_error "Implementation error, step::read_patch_meta_from_github::metadata_filter runtime error."
return 1
}
if [[ -z "${PATCH_DOWNLOAD_URL:-}" ]]; then
term::step::failed_with_error "Couldn't determine which patch to download. Installation aborted."
return 1
fi
PATCH_FILENAME="${PATCH_DOWNLOAD_URL##*/}"
PATCH_FILENAME="${PATCH_FILENAME%%\?*}"
local PATCH_DOWNLOAD_SIZE
{ PATCH_DOWNLOAD_SIZE=$(echo "${PATCH_METADATA_JSON}" | jq -r "[ ${JQ_FILTER} ] | .[0].size"); } ||
{
term::step::failed_with_error "Couldn't determine patch size. Installation aborted."
return 1
}
DOWNLOADS_SOURCE_LIST[patch_${PATCH_FOR_OS}]="${PATCH_DOWNLOAD_URL}|${PATCH_DOWNLOAD_SIZE}|"
DOWNLOADS_FILENAME_LIST[patch_${PATCH_FOR_OS}]="${PATCH_FILENAME}"
term::step::complete
}
step::read_patch_meta_from_github
if [[ "${_arg_unrealed}" == "on" ]]; then
step::read_patch_meta_from_github "windows" "UnrealEd"
fi
# shellcheck shell=bash
step::download_files() {
local DOWNLOAD_SOURCE_KEY
for DOWNLOAD_SOURCE_KEY in "${!DOWNLOADS_SOURCE_LIST[@]}"; do
local DOWNLOAD_URL_SETS="${DOWNLOADS_SOURCE_LIST[${DOWNLOAD_SOURCE_KEY}]}"
local DOWNLOAD_FILENAME="${DOWNLOADS_FILENAME_LIST[${DOWNLOAD_SOURCE_KEY}]}"
local IS_DOWNLOAD_SKIPPED="no"
DOWNLOAD_PATH="${_arg_destination%/}/Installer/${DOWNLOAD_FILENAME}"
local TARGET_STEP_NAME="Download ${DOWNLOAD_FILENAME}"
# Allow for the .iso and patch files to be alongside the script
local POSSIBLE_EXISTING_FILES_TO_CHECK=(
"${DOWNLOAD_PATH}"
"${_SCRIPT_DIR}/${DOWNLOAD_FILENAME}"
)
term::step::new "${TARGET_STEP_NAME}"
for POSSIBLE_EXISTING_FILE in "${POSSIBLE_EXISTING_FILES_TO_CHECK[@]}"; do
# If the file exist and isn't a partial download
if [[ -f "${POSSIBLE_EXISTING_FILE}" ]] && [[ ! -f "${POSSIBLE_EXISTING_FILE}.aria2" ]]; then
local EXISTING_FILESIZE EXISTING_HASH
EXISTING_FILESIZE=$(stat --format=%s "${POSSIBLE_EXISTING_FILE}")
EXISTING_HASH=$(helper::progress::run_with_progress "Verifying existing file" downloader::compute_sha256sum "${POSSIBLE_EXISTING_FILE}")
local REMAINING_DOWNLOAD_URL_SETS="${DOWNLOAD_URL_SETS}"
while true; do
# We looped through all the possible values, we can break out
if [[ -z "${REMAINING_DOWNLOAD_URL_SETS}" ]]; then
break
fi
CURRENT_DOWNLOAD_URL_SET=$(helper::string::unshift::next_value "${REMAINING_DOWNLOAD_URL_SETS}" ";;")
REMAINING_DOWNLOAD_URL_SETS=$(helper::string::unshift::remainder "${REMAINING_DOWNLOAD_URL_SETS}" ";;")
local DOWNLOAD_URL DOWNLOAD_SIZE DOWNLOAD_HASH
DOWNLOAD_URL=$(helper::string::unshift::next_value "${CURRENT_DOWNLOAD_URL_SET}")
CURRENT_DOWNLOAD_URL_SET=$(helper::string::unshift::remainder "${CURRENT_DOWNLOAD_URL_SET}")
DOWNLOAD_SIZE=$(helper::string::unshift::next_value "${CURRENT_DOWNLOAD_URL_SET}")
CURRENT_DOWNLOAD_URL_SET=$(helper::string::unshift::remainder "${CURRENT_DOWNLOAD_URL_SET}")
DOWNLOAD_HASH=$(helper::string::unshift::next_value "${CURRENT_DOWNLOAD_URL_SET}")
if [[ -n "${DOWNLOAD_SIZE}" ]] && [[ "${EXISTING_FILESIZE}" -ne "${DOWNLOAD_SIZE}" ]]; then
continue
fi
if [[ -n "${DOWNLOAD_HASH}" ]] && [[ "${EXISTING_HASH}" != "${DOWNLOAD_HASH}" ]]; then
continue
fi
IS_DOWNLOAD_SKIPPED="yes"
break
done
if [[ "${IS_DOWNLOAD_SKIPPED}" == "yes" ]]; then
if [[ "${POSSIBLE_EXISTING_FILE}" != "${DOWNLOAD_PATH}" ]]; then
ln -s "${POSSIBLE_EXISTING_FILE}" "${DOWNLOAD_PATH}"
fi
break
fi
fi
done
if [[ "${IS_DOWNLOAD_SKIPPED}" == "yes" ]]; then
term::step::skipped "SKIPPED: File already exists"
continue
fi
downloader::download_file "${DOWNLOAD_URL_SETS}" "${DOWNLOAD_PATH}"
done
}
step::download_files
# shellcheck shell=bash
step::unarchive_generic() {
local ITEM_NAME="${1:-}"
local ARCHIVE_PATH="${2:-}"
local TARGET_PATH="${3:-}"
local IGNORE_PATTERNS_VAR_NAME="${4:-}"
local IGNORE_PATTERNS_RECURSIVE_VAR_NAME="${5:-}"
if [[ -z "${ITEM_NAME}" ]] || [[ -z "${ARCHIVE_PATH}" ]] || [[ -z "${TARGET_PATH}" ]]; then
return 1
fi
if [[ ! -d "${TARGET_PATH}" ]]; then
# Create the installation folder
mkdir -p "${TARGET_PATH}" &>/dev/null || {
term::step::failed_with_error "User does not have permission to create staging folder. Aborting installation."
return 77 #E_PERM
}
fi
unarchiver::unarchive_file "${ITEM_NAME}" "${ARCHIVE_PATH}" "${TARGET_PATH}" "${IGNORE_PATTERNS_VAR_NAME}" "${IGNORE_PATTERNS_RECURSIVE_VAR_NAME}"
}
step::unarchive_generic "Game Files" "${_arg_destination%/}/Installer/${DOWNLOADS_FILENAME_LIST[game]}" "${_arg_destination%/}/Installer/.staging" "UNPACK_IGNORE_PATTERNS"
# shellcheck shell=bash
step::ut2004_unpack_cabs() {
term::step::new "Unpack Install CABs"
local STAGING_PATH="${_arg_destination%/}/Installer/.staging"
local CABS_PATH="${STAGING_PATH}/Cabs"
local TARGET_PATH="${STAGING_PATH}/Data"
if [[ -d "${CABS_PATH}" ]]; then
rm -rf "${CABS_PATH}" &>/dev/null || {
term::step::failed_with_error "User does not have permission to create staging folder. Aborting installation."
return 77 #E_PERM
}
fi
# Create the cabs folder
mkdir -p "${CABS_PATH}" &>/dev/null || {
term::step::failed_with_error "User does not have permission to create staging folder. Aborting installation."
return 77 #E_PERM
}
{
local DISK_FOLDER
for DISK_FOLDER in "${STAGING_PATH}"/Disk*; do
if [[ ! -d "${DISK_FOLDER}" ]]; then
continue
fi
local DISK_CAB
for DISK_CAB in "${DISK_FOLDER}"/*.cab; do
if [[ ! -f "${DISK_CAB}" ]]; then
continue
fi
local DISK_CAB_BASE="${DISK_CAB##*/}"
ln -fs "${DISK_CAB}" "${CABS_PATH}/${DISK_CAB_BASE}"
done
for DISK_CAB in "${DISK_FOLDER}"/*.hdr; do
if [[ ! -f "${DISK_CAB}" ]]; then
continue
fi
local DISK_CAB_BASE="${DISK_CAB##*/}"
ln -fs "${DISK_CAB}" "${CABS_PATH}/${DISK_CAB_BASE}"
done
done
} || {
term::step::failed_with_error "Failed to prepare cabs for unpacking. Aborting installation."
return 1
}
if [[ ! -d "${TARGET_PATH}" ]]; then
# Create the installation folder
mkdir -p "${TARGET_PATH}" &>/dev/null || {
term::step::failed_with_error "User does not have permission to create staging folder. Aborting installation."
return 77 #E_PERM
}
fi
local KDIALOG_DBUS_ADDRESS=()
if [[ "${_arg_ui_mode:-none}" == "kdialog" ]]; then
read -ra KDIALOG_DBUS_ADDRESS < <(kdialog --title "${CURRENT_STEP_NAME}" --progressbar "Unpacking install CABs..." 0 2>/dev/null)
busctl --user call "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "showCancelButton" b "false" 2>/dev/null || true
fi
helper::progress::make_consistant __step::ut2004_unpack_cabs::run "${CABS_PATH}/data1.cab" "${TARGET_PATH}" | while IFS= read -r UNPACK_PROGRESS; do
if [[ -z "${UNPACK_PROGRESS}" ]]; then
term::step::progress "" >&6
if [[ "${_arg_ui_mode:-none}" == "kdialog" ]]; then
busctl --user set-property "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "maximum" i 0 2>/dev/null || true
fi
continue
fi
local TOTAL_FILES PERCENT CURRENT_FILE_INDEX FILE_NAME
TOTAL_FILES=$(helper::string::unshift::next_value "${UNPACK_PROGRESS}")
UNPACK_PROGRESS=$(helper::string::unshift::remainder "${UNPACK_PROGRESS}")
PERCENT=$(helper::string::unshift::next_value "${UNPACK_PROGRESS}")
UNPACK_PROGRESS=$(helper::string::unshift::remainder "${UNPACK_PROGRESS}")
CURRENT_FILE_INDEX=$(helper::string::unshift::next_value "${UNPACK_PROGRESS}")
UNPACK_PROGRESS=$(helper::string::unshift::remainder "${UNPACK_PROGRESS}")
FILE_NAME=$(helper::string::unshift::next_value "${UNPACK_PROGRESS}")
UNPACK_PROGRESS=$(helper::string::unshift::remainder "${UNPACK_PROGRESS}")
term::step::progress "${CURRENT_FILE_INDEX} of ${TOTAL_FILES} - ${FILE_NAME}" >&6
local DIALOG_TEXT="Unpacking ${FILE_NAME} (${CURRENT_FILE_INDEX} of ${TOTAL_FILES})"
if [[ "${_arg_ui_mode:-none}" == "kdialog" ]]; then
local ESCAPED_DIALOG_TEXT
ESCAPED_DIALOG_TEXT="$(echo -e "${DIALOG_TEXT}")"
busctl --user set-property "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "maximum" i "${TOTAL_FILES}" 2>/dev/null || true
busctl --user set-property "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "value" i "${CURRENT_FILE_INDEX}" 2>/dev/null || true
busctl --user call "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "setLabelText" s "${ESCAPED_DIALOG_TEXT}" 2>/dev/null || true
elif [[ "${_arg_ui_mode:-none}" == "zenity" ]]; then
echo "${PERCENT}"
echo "# ${DIALOG_TEXT}"
fi
done | {
if [[ "${_arg_ui_mode:-none}" == "zenity" ]]; then
zenity --progress --percentage=0 --text="Unpacking maps..." --no-cancel --time-remaining --auto-close 2>/dev/null
else
cat - >/dev/null
fi
} || {
if [[ "${_arg_ui_mode:-none}" == "kdialog" ]]; then
busctl --user call "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "close" 2>/dev/null || true
fi
term::step::failed_with_error "Failed to unpack CABs. Installation aborted."
return 1
}
if [[ "${_arg_ui_mode:-none}" == "kdialog" ]]; then
busctl --user call "${KDIALOG_DBUS_ADDRESS[@]}" "org.kde.kdialog.ProgressDialog" "close" 2>/dev/null || true
fi
rm -rf "${CABS_PATH}" &>/dev/null || {
term::step::failed_with_error "User does not have permission to remove temporary staging folder for CABs. Aborting installation."
return 77 #E_PERM
}
term::step::complete
}
__step::ut2004_unpack_cabs::run() {
local CAB_FILE="${1:-}"
local WRITE_TO_PATH="${2:-}"
local UNSHIELD_BIN="unshield"
if [[ "${DOWNLOADS_FILENAME_LIST[unshield]:-}" == "unshield" ]]; then
UNSHIELD_BIN="${_arg_destination%/}/Installer/unshield"
if [[ ! -f "${UNSHIELD_BIN}" ]]; then
term::error "unshield binary not present"
return 1
fi
chmod +x "${UNSHIELD_BIN}"
fi
local RAW_TOTAL_FILES_TO_EXTRACT TOTAL_FILES_TO_EXTRACT
RAW_TOTAL_FILES_TO_EXTRACT=$("${UNSHIELD_BIN}" l "${CAB_FILE}" | tail -n1)
local LINE_MATCH_REGEX='^\s*([0-9]+)'
if [[ "${RAW_TOTAL_FILES_TO_EXTRACT}" =~ ${LINE_MATCH_REGEX} ]]; then
TOTAL_FILES_TO_EXTRACT="${BASH_REMATCH[1]}"
fi
local CURRENT_FILE_INDEX=0
local EXTRACTING_MATCH_REGEX='^\s*extracting:\s+(.+)$'
"${UNSHIELD_BIN}" -d "${WRITE_TO_PATH}" x "${CAB_FILE}" |
while IFS= read -r UNPACK_PROGRESS; do
if [[ "${UNPACK_PROGRESS}" =~ ${EXTRACTING_MATCH_REGEX} ]]; then
local CURRENT_FILE="${BASH_REMATCH[1]##*/}"
local PERCENT_PROGRESS="$(((CURRENT_FILE_INDEX * 100) / TOTAL_FILES_TO_EXTRACT))"
CURRENT_FILE_INDEX=$((CURRENT_FILE_INDEX + 1))
echo "${TOTAL_FILES_TO_EXTRACT}|${PERCENT_PROGRESS}|${CURRENT_FILE_INDEX}|${CURRENT_FILE}"
fi
done
}
step::ut2004_unpack_cabs
# shellcheck shell=bash
step::ut2004_install_files() {
term::step::new "Install Files"
local STAGING_DATA_FOLDER="${_arg_destination%/}/Installer/.staging/Data"
local FOLDERS_AND_TARGETS=(
'All_Animations|Animations'
'All_Benchmark|Benchmark'
'All_ForceFeedback|ForceFeedback'
'All_Help|Help'
'All_KarmaData|KarmaData'
'All_Maps|Maps'
'All_Music|Music'
'All_StaticMeshes|StaticMeshes'
'All_Textures|Textures'
'All_Web|Web'
'All_UT2004.EXE|System'
'English_Manual|Manual'
'English_Sounds_Speech_System_Help|'
)
# Backup file doesn't have US_License file group
if [[ -d "${STAGING_DATA_FOLDER}/US_License.int" ]]; then
FOLDERS_AND_TARGETS+=('US_License.int|System')
fi
# Let's first remove any files which are not required due to us targeting Linux
rm -f "${STAGING_DATA_FOLDER}/All_UT2004.EXE/"*.exe 2>/dev/null || true
rm -f "${STAGING_DATA_FOLDER}/English_Sounds_Speech_System_Help/System/"*.bat 2>/dev/null || true
rm -f "${STAGING_DATA_FOLDER}/English_Sounds_Speech_System_Help/System/"*.dll 2>/dev/null || true
rm -f "${STAGING_DATA_FOLDER}/English_Sounds_Speech_System_Help/System/"*.exe 2>/dev/null || true
# Check if we are istalling on top of a Steam install of UT2004, and rename the lower-case Maps folder is that's the case
if [[ -d "${_arg_destination%/}/maps" ]] && [[ ! -d "${_arg_destination%/}/Maps" ]]; then
mv "${_arg_destination%/}/maps" "${_arg_destination%/}/Maps"
fi
local FOLDER_PAIR
for FOLDER_PAIR in "${FOLDERS_AND_TARGETS[@]}"; do
local FOLDER_SOURCE="${FOLDER_PAIR%|*}"
local FOLDER_TARGET="${FOLDER_PAIR#*|}"
local RESOLVED_TARGET="${_arg_destination}"
term::step::progress "${FOLDER_SOURCE}"
if [[ -n "${FOLDER_TARGET}" ]]; then
mkdir -p "${_arg_destination%/}/${FOLDER_TARGET}" 2>/dev/null || {
term::step::failed_with_error "User does not have permission to create the target folder (${FOLDER_TARGET}). Aborting installation."
return 77 #E_PERM
}
RESOLVED_TARGET="${_arg_destination%/}/${FOLDER_TARGET}"
fi
cp -rf "${STAGING_DATA_FOLDER}/${FOLDER_SOURCE}/"* "${RESOLVED_TARGET}/" || {
term::step::failed_with_error "Failed to copy files to target folder (${FOLDER_TARGET}). Aborting installation."
return 77 #E_PERM
}
done
term::step::progress "Removing extra files"
rm -rf "${_arg_destination%/}/Installer/.staging" || {
term::step::failed_with_error "Failed to remove staging files. Aborting installation."
return 77 #E_PERM
}
term::step::complete
}
step::ut2004_install_files
if [[ "${_arg_unrealed}" == "on" ]]; then
step::unarchive_generic "Latest Patch (UnrealEd)" "${_arg_destination%/}/Installer/${DOWNLOADS_FILENAME_LIST[patch_windows]}" "${_arg_destination%/}"
fi
step::unarchive_generic "Latest Patch" "${_arg_destination%/}/Installer/${DOWNLOADS_FILENAME_LIST[patch_linux]}" "${_arg_destination%/}"
# shellcheck shell=bash
step::ut2004_special_fixes() {
term::step::new "Apply UT2004 Specific Fixes"
local SYSTEM_FOLDER="${_arg_destination%/}/System${UE_SYSTEM_FOLDER_SUFFIX}"
if [[ "${IS_DESTINATION_CASE_SENSITIVE_FS}" == "yes" ]]; then
# Remove files with different casing than the patch payload
local WRONG_CASING
local COMMON_WRONG_CASINGS=(
"Bonuspack.u"
"Gui2K4.u"
"Gameplay.u"
"Ipdrv.u"
"Skaarjpack.u"
"StreamLineFX.u"
"UT2K4Assault.u"
"UT2K4AssaultFull.u"
"XVoting.u"
"xWebAdmin.u"
)
for WRONG_CASING in "${COMMON_WRONG_CASINGS[@]}"; do
if [[ -f "${SYSTEM_FOLDER}/${WRONG_CASING}" ]]; then
rm -f "${SYSTEM_FOLDER}/${WRONG_CASING}"
fi
if [[ -f "${_arg_destination%/}/System/${WRONG_CASING}" ]]; then
rm -f "${_arg_destination%/}/System/${WRONG_CASING}"
fi
done
fi
# Remove provided libopenal if provided by the system
if [[ -f "${SYSTEM_FOLDER}/libopenal.so.1" ]] && [[ -n "$(step::ut2004_special_fixes::find_library libopenal.so.1)" ]]; then
rm -f "${SYSTEM_FOLDER}/libopenal.so.1" "${SYSTEM_FOLDER}/libopenal.so.1."*
fi
# Remove provided libSDL3 if provided by the system
if [[ -f "${SYSTEM_FOLDER}/libSDL3.so.0" ]] && [[ -n "$(step::ut2004_special_fixes::find_library libSDL3.so.0)" ]]; then
rm -f "${SYSTEM_FOLDER}/libSDL3.so.0" "${SYSTEM_FOLDER}/libSDL3.so.0."*
fi
# libomp fixes
local SYSTEM_PROVIDED_LIBOMP_PATH=""
local LIBOMP_REQUIRES_SYMLINK="no"
SYSTEM_PROVIDED_LIBOMP_PATH=$(step::ut2004_special_fixes::find_library "libomp.so.5")
if [[ -z "${SYSTEM_PROVIDED_LIBOMP_PATH}" ]]; then
SYSTEM_PROVIDED_LIBOMP_PATH=$(step::ut2004_special_fixes::find_library "libomp.so")
if [[ -n "${SYSTEM_PROVIDED_LIBOMP_PATH}" ]]; then
LIBOMP_REQUIRES_SYMLINK="yes"
fi
fi
# Remove provided libomp.so.5 is one is provided by the system
if [[ -f "${SYSTEM_FOLDER}/libomp.so.5" ]] && [[ -n "${SYSTEM_PROVIDED_LIBOMP_PATH}" ]]; then
rm -f "${SYSTEM_FOLDER}/libomp.so.5" "${SYSTEM_FOLDER}/libomp.so.5."*
fi
if [[ "${LIBOMP_REQUIRES_SYMLINK}" == "yes" ]]; then
ln -s "${SYSTEM_PROVIDED_LIBOMP_PATH}" "${SYSTEM_FOLDER}/libomp.so.5"
fi
step::ut2004_special_fixes::replace_line_in_file "${HOME}/.ut2004/System/UT2004.ini" "MainMenuClass=GUI2K4.UT2K4MainMenu" "MainMenuClass=GUI2K4.UT2K4MainMenuWS"
step::ut2004_special_fixes::replace_line_in_file "${SYSTEM_FOLDER}/UT2004.ini" "MainMenuClass=GUI2K4.UT2K4MainMenu" "MainMenuClass=GUI2K4.UT2K4MainMenuWS"
term::step::complete
}
# To avoid having a dependency on 'sed', this is being done manually
step::ut2004_special_fixes::replace_line_in_file() {
local FILENAME="${1:-}"
local LINE_TO_REPLACE="${2:-}"
local REPLACEMENT_LINE="${3:-}"
if [[ -z "${FILENAME}" ]] || [[ -z "${LINE_TO_REPLACE}" ]] || [[ -z "${REPLACEMENT_LINE}" ]]; then
term::step::failed_with_error "ASSERT FAILED. Missing required arg in step::ut2004_special_fixes::replace_line_in_file"
return 1
fi
if [[ ! -f "${FILENAME}" ]]; then
return 0
fi
local FILE_CONTENTS FILE_LINE
FILE_CONTENTS="$(cat "${FILENAME}")"
local FILE_NEW_CONTENTS=""
local IS_CONTENT_FOUND="no"
while IFS= read -r FILE_LINE; do
if [[ "${FILE_LINE}" == "${LINE_TO_REPLACE}" ]]; then
IS_CONTENT_FOUND="yes"
FILE_NEW_CONTENTS="${FILE_NEW_CONTENTS}"$'\n'"${REPLACEMENT_LINE}"
else
FILE_NEW_CONTENTS="${FILE_NEW_CONTENTS}"$'\n'"${FILE_LINE}"
fi
done <<<"${FILE_CONTENTS}"
if [[ "${IS_CONTENT_FOUND}" == "yes" ]]; then
echo "${FILE_NEW_CONTENTS}" >"${FILENAME}"
fi
}
step::ut2004_special_fixes::find_library() {
local LIBRARY_NAME="${1:-}"
if [[ -z "${LIBRARY_NAME}" ]]; then
term::step::failed_with_error "ASSERT FAILED. Missing required arg in step::ut2004_special_fixes::find_library"
return 1
fi
if command -v "ldconfig" &>/dev/null; then
local LIB_PATH
LIB_PATH=$( (ldconfig -p | grep -F "${LIBRARY_NAME}" | head -n 1) || true)
if [ -z "${LIB_PATH}" ]; then
return 0
fi
LIB_PATH="${LIB_PATH##*=>}"
shopt -s extglob
LIB_PATH="${LIB_PATH##+([[:space:]])}"
shopt -u extglob
if [ -f "${LIB_PATH}" ]; then
echo "${LIB_PATH}"
fi
return 0
fi
local SEARCH_PATHS=("/usr/lib/${DETECTED_ARCHITECTURE}-linux-gnu" "/usr/lib64" "/usr/local/lib" "/lib/${DETECTED_ARCHITECTURE}-linux-gnu")
local CURRENT_PATH
for CURRENT_PATH in "${SEARCH_PATHS[@]}"; do
if [[ -f "${CURRENT_PATH}/${LIBRARY_NAME}" ]]; then
echo "${LIBRARY_NAME}"
break
fi
done
}
step::ut2004_special_fixes
# shellcheck shell=bash
step::create_unrealed_launcher() {
term::step::new "Create UnrealEd Launch Script"
local UNREALED_WINE_PREFIX_PATH="${_arg_destination%/}/.wine-unrealed"
local UNREALED_LAUNCH_SCRIPT_PATH="${_arg_destination%/}/System${UEED_SYSTEM_FOLDER_SUFFIX:-}/UnrealEd-launch-linux.sh"
mkdir -p "${UNREALED_WINE_PREFIX_PATH}"
step::create_unrealed_launcher::write "${UNREALED_LAUNCH_SCRIPT_PATH}" || {
term::step::failed_with_error "Failed to create UnrealEd launch script. Installation aborted."
return 1
}
chmod +x "${UNREALED_LAUNCH_SCRIPT_PATH}" || {
term::step::failed_with_error "Failed to set UnrealEd launch script as executable. Installation aborted."
return 1
}
term::step::complete
}
step::create_unrealed_launcher::write() {
local FILEPATH="${1:-}"
{
cat <<"EOFLAUNCHER"
#!/usr/bin/env bash
#
# UnrealEd Linux Launcher
#
# shellcheck source-path=SCRIPTDIR
# ARGBASH_SET_INDENT([ ])
# ARG_OPTIONAL_SINGLE([mode],[],[Informs the script on which Windows compatibility tool to use to launch UnrealEd],[auto])
# ARG_OPTIONAL_SINGLE([wine],[],[Path to wine (when using --mode=wine)],[])
# ARG_OPTIONAL_SINGLE([proton],[],[Path to proton compatibility tool (when using --mode=proton)],[])
# ARG_TYPE_GROUP_SET([mode],[MODE],[mode],[auto,umu-launcher,proton,wine])
# ARG_HELP([Launch UnrealEd])
# ARG_VERSION_AUTO([1.0],['OldUnreal <https://oldunreal.com>'])
# DEFINE_SCRIPT_DIR([_SCRIPT_DIR])
# ARGBASH_GO()
# needed because of Argbash --> m4_ignore([
### START OF CODE GENERATED BY Argbash v2.11.0 one line above ###
# Argbash is a bash code generator used to get arguments parsing right.
# Argbash is FREE SOFTWARE, see https://argbash.dev for more info
die() {
local _ret="${2:-1}"
test "${_PRINT_HELP:-no}" = yes && print_help >&2
echo "$1" >&2
exit "${_ret}"
}
# validators
mode() {
local _allowed=("auto" "umu-launcher" "proton" "wine") _seeking="$1"
for element in "${_allowed[@]}"; do
test "$element" = "$_seeking" && echo "$element" && return 0
done
die "Value '$_seeking' (of argument '$2') doesn't match the list of allowed values: 'auto', 'umu-launcher', 'proton' and 'wine'" 4
}
begins_with_short_option() {
local first_option all_short_options='hv'
first_option="${1:0:1}"
test "$all_short_options" = "${all_short_options/$first_option/}" && return 1 || return 0
}
# THE DEFAULTS INITIALIZATION - OPTIONALS
_arg_mode="auto"
_arg_wine=
_arg_proton=
print_help() {
printf '%s\n' "Launch UnrealEd"
printf 'Usage: %s [--mode <MODE>] [--wine <arg>] [--proton <arg>] [-h|--help] [-v|--version]\n' "$0"
printf '\t%s\n' "--mode: Informs the script on which Windows compatibility tool to use to launch UnrealEd. Can be one of: 'auto', 'umu-launcher', 'proton' and 'wine' (default: 'auto')"
printf '\t%s\n' "--wine: Path to wine (when using --mode=wine) (no default)"
printf '\t%s\n' "--proton: Path to proton compatibility tool (when using --mode=proton) (no default)"
printf '\t%s\n' "-h, --help: Prints help"
printf '\t%s\n' "-v, --version: Prints version"
}
parse_commandline() {
local _key
while test $# -gt 0; do
_key="$1"
case "$_key" in
--mode)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_mode="$(mode "$2" "mode")" || exit 1
shift
;;
--mode=*)
_arg_mode="$(mode "${_key##--mode=}" "mode")" || exit 1
;;
--wine)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_wine="$2"
shift
;;
--wine=*)
_arg_wine="${_key##--wine=}"
;;
--proton)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_proton="$2"
shift
;;
--proton=*)
_arg_proton="${_key##--proton=}"
;;
-h | --help)
print_help
exit 0
;;
-h*)
print_help
exit 0
;;
-v | --version)
printf '%s %s\n\n%s\n%s\n' "unrealed-launch-linux.sh" "1.0" 'Launch UnrealEd' 'OldUnreal <https://oldunreal.com>'
exit 0
;;
-v*)
printf '%s %s\n\n%s\n%s\n' "unrealed-launch-linux.sh" "1.0" 'Launch UnrealEd' 'OldUnreal <https://oldunreal.com>'
exit 0
;;
*)
_PRINT_HELP=yes die "FATAL ERROR: Got an unexpected argument '$1'" 1
;;
esac
shift
done
}
parse_commandline "$@"
# OTHER STUFF GENERATED BY Argbash
_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" || {
echo "Couldn't determine the script's running directory, which probably matters, bailing out" >&2
exit 2
}
# Validation of values
### END OF CODE GENERATED BY Argbash (sortof) ### ])
# [ <-- needed because of Argbash
# Enable Bash Strict Mode
set -euo pipefail
unrealed::launcher() {
declare -A ansi_colornum=(
[black]=0
[red]=1
[green]=2
[yellow]=3
[blue]=4
[magenta]=5
[cyan]=6
[white]=7
)
declare -A ansi_stylenum=(
[reset]=0
[bright]=1
[dim]=2
[italic]=3
[underline]=4
[flash]=5
[highlight]=7
[normal]=22
)
ansi::styled() {
ALLOW_ESCAPES="off"
if [[ "${1:-}" == "-e" ]]; then
ALLOW_ESCAPES="on"
shift
fi
local TEXT_TO_PRINT="${1:-}"
local STYLE="${2:--1}"
local FORE_COLOR="${3:--1}"
local BACK_COLOR="${4:--1}"
declare -a CODES RESETCODES
if [[ "${STYLE}" -eq 22 ]] || [[ "${STYLE}" -eq -1 ]]; then
RESETCODES=("$(printf "\033[%sm" "22")" "${RESETCODES[@]}")
else
CODES=("${CODES[@]}" "$(printf "\033[%sm" "${STYLE}")")
fi
if [[ "${BACK_COLOR}" -eq -1 ]]; then
RESETCODES=("$(printf "\033[%sm" "49")" "${RESETCODES[@]}")
else
CODES=("${CODES[@]}" "$(printf "\033[%sm" "$((BACK_COLOR + 40))")")
fi
if [[ "${FORE_COLOR}" -eq -1 ]]; then
RESETCODES=("$(printf "\033[%sm" "39")" "${RESETCODES[@]}")
else
CODES=("${CODES[@]}" "$(printf "\033[%sm" "$((FORE_COLOR + 30))")")
fi
local rc
for rc in "${RESETCODES[@]}"; do
echo -en "$rc"
done
local c
for c in "${CODES[@]}"; do
echo -en "$c"
done
if [[ "${ALLOW_ESCAPES}" == "on" ]]; then
echo -en "${TEXT_TO_PRINT}"
else
echo -n "${TEXT_TO_PRINT}"
fi
echo -en "\033[m"
}
local OU_INSTALL_PATH
OU_INSTALL_PATH="$(cd "$(dirname "${_SCRIPT_DIR}")" && pwd)" || {
echo "Couldn't determine game installation directory." >&2
exit 2
}
local PREFERRED_WINEPREFIX="${WINEPREFIX:-${OU_INSTALL_PATH}/.wine-unrealed}"
unrealed::warning() {
echo -e "$(ansi::styled "Warning:" "${ansi_stylenum[bright]}" "${ansi_colornum[yellow]}") $*" 1>&2
}
unrealed::error() {
echo -e "$(ansi::styled "Error:" "${ansi_stylenum[bright]}" "${ansi_colornum[red]}") $*" 1>&2
}
local DEFAULT_PROTON="${HOME}/.steam/steam/steamapps/common/Proton - Experimental/proton"
if [[ "${_arg_mode}" == "auto" ]]; then
if [[ -n "${_arg_proton:-}" ]]; then
_arg_mode="proton"
elif [[ -n "${_arg_wine:-}" ]]; then
_arg_mode="wine"
fi
if command -v umu-run &>/dev/null; then
_arg_mode="umu-launcher"
elif [[ -x "${DEFAULT_PROTON}" ]]; then
_arg_mode="proton"
_arg_proton="${DEFAULT_PROTON}"
elif command -v wine &>/dev/null; then
_arg_mode="wine"
_arg_wine="wine"
fi
fi
if [[ "${_arg_mode}" == "umu-launcher" ]]; then
if ! command -v umu-run &>/dev/null; then
unrealed::error "Unable to find umu-run in PATH."
return 1
fi
local PREFERRED_PROTON="${_arg_proton:-${PROTONPATH:-}}"
if [[ -z "${PREFERRED_PROTON}" ]]; then
if [[ -x "/usr/share/steam/compatibilitytools.d/proton-ge-custom/proton" ]]; then
PREFERRED_PROTON="/usr/share/steam/compatibilitytools.d/proton-ge-custom"
else
PREFERRED_PROTON="GE-Proton"
fi
fi
cd "${OU_INSTALL_PATH}"
WINEPREFIX="${PREFERRED_WINEPREFIX}" \
GAMEID=umu-default \
STORE=none \
PROTONPATH="${PREFERRED_PROTON}" \
exec umu-run "${_SCRIPT_DIR}/UnrealEd.exe"
elif [[ "${_arg_mode}" == "proton" ]]; then
if ! command -v "${_arg_proton}" &>/dev/null; then
unrealed::error "--proton option is invalid."
return 1
fi
cd "${OU_INSTALL_PATH}"
STEAM_COMPAT_CLIENT_INSTALL_PATH="${STEAM_COMPAT_CLIENT_INSTALL_PATH:-~/.steam/steam}" \
STEAM_COMPAT_DATA_PATH="${PREFERRED_WINEPREFIX}" \
exec "${_arg_proton}" run "${_SCRIPT_DIR}/UnrealEd.exe"
elif [[ "${_arg_mode}" == "wine" ]]; then
if ! command -v "${_arg_wine}" &>/dev/null; then
unrealed::error "--wine option is invalid."
return 1
fi
unrealed::warning "Wine is untested, OldUnreal recommends running UnrealEd via Proton or UMU"
cd "${OU_INSTALL_PATH}"
WINEPREFIX="${PREFERRED_WINEPREFIX}" \
exec "${_arg_wine}" "${_SCRIPT_DIR}/UnrealEd.exe"
fi
}
unrealed::launcher
# ] <-- needed because of Argbash
EOFLAUNCHER
} >"${FILEPATH}"
}
if [[ "${_arg_unrealed}" == "on" ]]; then
step::create_unrealed_launcher
fi
# shellcheck shell=bash
step::xdg_desktop_entry() {
local STEP_NAME="${1:-}"
local STEP_PROMPT="${2:-}"
local DESKTOP_ENTRY_PATH="${3:-}"
local DESKTOP_ENTRY_PATH_UNREALED="${4:-}"
local APPLICATION_ENTRY_HANDLING_MODE="${5:-}"
local SKIP_IF_ENTRY_FOLDER_DOESNT_EXIST="${6:-no}"
if [[ -z "${STEP_NAME}" ]] ||
[[ -z "${STEP_PROMPT}" ]] ||
[[ -z "${DESKTOP_ENTRY_PATH}" ]] ||
[[ -z "${DESKTOP_ENTRY_PATH_UNREALED}" ]] ||
[[ -z "${APPLICATION_ENTRY_HANDLING_MODE}" ]]; then
return 1
fi
if [[ "${APPLICATION_ENTRY_HANDLING_MODE:-skip}" == "skip" ]]; then
term::step::new "${STEP_NAME}"
term::step::skipped "SKIPPED: User opted out"
return 0
fi
if [[ "${SKIP_IF_ENTRY_FOLDER_DOESNT_EXIST}" == "yes" ]]; then
local DESKTOP_ENTRY_FOLDER="${DESKTOP_ENTRY_PATH%/*}"
if [[ ! -d "${DESKTOP_ENTRY_FOLDER}" ]]; then
term::step::new "${STEP_NAME}"
term::step::skipped "SKIPPED: Not Available"
return 0
fi
fi
# Prompt before showing step name in text mode
if [[ "${_arg_ui_mode:-none}" == "none" ]] && [[ "${APPLICATION_ENTRY_HANDLING_MODE}" == "prompt" ]]; then
echo
if ! term::yesno "${STEP_PROMPT}"; then
echo
term::step::new "${STEP_NAME}"
term::step::skipped "SKIPPED: User opted out"
return 0
fi
echo
fi
term::step::new "${STEP_NAME}"
if [[ "${_arg_ui_mode:-none}" != "none" ]] && [[ "${APPLICATION_ENTRY_HANDLING_MODE}" == "prompt" ]]; then
local DIALOG_ARGS=()
if [[ "${_arg_ui_mode:-none}" == "kdialog" ]]; then
DIALOG_ARGS=(
kdialog
--yesno
"${STEP_PROMPT}"
)
elif [[ "${_arg_ui_mode:-none}" == "zenity" ]]; then
DIALOG_ARGS=(
zenity
--question
"--text=${STEP_PROMPT}"
)
fi
if ! "${DIALOG_ARGS[@]}" &>/dev/null; then
term::step::skipped "SKIPPED: User opted out"
return 0
fi
fi
__step::xdg_desktop_entry::create "${DESKTOP_ENTRY_PATH}" || {
term::step::failed_with_error "User does not have permission to create .desktop file."
return 77 #E_PERM
}
if [[ "${_arg_unrealed}" == "on" ]]; then
__step::xdg_desktop_entry::unrealed::create "${DESKTOP_ENTRY_PATH_UNREALED}" || {
term::step::failed_with_error "User does not have permission to create .desktop file."
return 77 #E_PERM
}
fi
term::step::complete
}
step::xdg_desktop_entry::xdg_dir() {
local STEP_NAME="${1:-}"
local STEP_PROMPT="${2:-}"
local XDG_DIR_NAME="${3:-}"
local DESKTOP_ENTRY_PATH="${4:-}"
local DESKTOP_ENTRY_PATH_UNREALED="${5:-}"
local APPLICATION_ENTRY_HANDLING_MODE="${6:-}"
if [[ -z "${STEP_NAME}" ]] ||
[[ -z "${STEP_PROMPT}" ]] ||
[[ -z "${XDG_DIR_NAME}" ]] ||
[[ -z "${DESKTOP_ENTRY_PATH}" ]] ||
[[ -z "${DESKTOP_ENTRY_PATH_UNREALED}" ]] ||
[[ -z "${APPLICATION_ENTRY_HANDLING_MODE}" ]]; then
return 1
fi
local XDG_DIR_PATH
XDG_DIR_PATH=$(xdgdirs::get_user_dir "${XDG_DIR_NAME}")
if [[ -z "${XDG_DIR_PATH}" ]]; then
term::step::new "${STEP_NAME}"
term::step::skipped "SKIPPED: Not Available"
return 0
fi
step::xdg_desktop_entry \
"${STEP_NAME}" \
"${STEP_PROMPT}" \
"${XDG_DIR_PATH}/${DESKTOP_ENTRY_PATH}" \
"${XDG_DIR_PATH}/${DESKTOP_ENTRY_PATH_UNREALED}" \
"${APPLICATION_ENTRY_HANDLING_MODE}" \
"yes"
}
__step::xdg_desktop_entry::create() {
local DESKTOP_ENTRY_PATH="${1:-}"
if [[ -z "${DESKTOP_ENTRY_PATH}" ]]; then
return 1
fi
local DESKTOP_ENTRY_FOLDER="${DESKTOP_ENTRY_PATH%/*}"
local SOURCE_ICON="${_arg_destination%/}/${PRODUCT_ICONPATH:-Help/Unreal.ico}"
local ICON_NAME
ICON_NAME=$(__step::xdg_desktop_entry::get_icon_name "${SOURCE_ICON}" "OldUnreal_${PRODUCT_SHORTNAME}" || echo "${SOURCE_ICON}")
# Create destination folder if it doesn't exist
if [[ ! -d "${DESKTOP_ENTRY_FOLDER}" ]]; then
mkdir -p "${DESKTOP_ENTRY_FOLDER}" 2>/dev/null
fi
{
echo "[Desktop Entry]"
echo "Name=${PRODUCT_NAME}"
if [[ -n "${PRODUCT_KEYWORDS[*]:-}" ]]; then
echo -n "Keywords="
local KEYWORD
for KEYWORD in "${PRODUCT_KEYWORDS[@]}"; do
echo -n "${KEYWORD};"
done
echo
fi
if [[ -n "${PRODUCT_URLSCHEME:-}" ]]; then
echo "Exec="'"'"${_arg_destination%/}/System${UE_SYSTEM_FOLDER_SUFFIX:-}/${MAIN_BINARY_NAME}${ARCHITECTURE_BINARY_SUFFIX}"'"' %u
echo "MimeType=x-scheme-handler/${PRODUCT_URLSCHEME};"
else
echo "Exec="'"'"${_arg_destination%/}/System${UE_SYSTEM_FOLDER_SUFFIX:-}/${MAIN_BINARY_NAME}${ARCHITECTURE_BINARY_SUFFIX}"'"'
fi
echo "Icon=${ICON_NAME}"
echo "Terminal=false"
echo "Type=Application"
echo "Categories=Game;"
} >"${DESKTOP_ENTRY_PATH}"
}
__step::xdg_desktop_entry::unrealed::create() {
local DESKTOP_ENTRY_PATH="${1:-}"
if [[ -z "${DESKTOP_ENTRY_PATH}" ]]; then
return 1
fi
local DESKTOP_ENTRY_FOLDER="${DESKTOP_ENTRY_PATH%/*}"
local SOURCE_ICON="${_arg_destination%/}/${PRODUCT_ICONPATH:-Help/UnrealEd.ico}"
local ICON_NAME
ICON_NAME=$(__step::xdg_desktop_entry::get_icon_name "${SOURCE_ICON}" "OldUnreal_${PRODUCT_SHORTNAME}_UnrealEd" || echo "${SOURCE_ICON}")
# Create destination folder if it doesn't exist
if [[ ! -d "${DESKTOP_ENTRY_FOLDER}" ]]; then
mkdir -p "${DESKTOP_ENTRY_FOLDER}" 2>/dev/null
fi
{
echo "[Desktop Entry]"
echo "Name=UnrealEd for ${PRODUCT_NAME}"
echo "Exec="'"'"${_arg_destination%/}/System${UEED_SYSTEM_FOLDER_SUFFIX:-}/UnrealEd-launch-linux.sh"'"'
echo "Icon=${ICON_NAME}"
echo "Terminal=false"
echo "Type=Application"
echo "Categories=Game;"
} >"${DESKTOP_ENTRY_PATH}"
}
# Automatically create png files from .ico for DEs that don't support Windows icons
declare -A STEP_XDG_DESKTOP_ENTRY_ICONS=()
__step::xdg_desktop_entry::get_icon_name() {
local ICON_PATH="${1:-}"
local TARGET_ICON_NAME="${2:-}"
if [[ -n "${STEP_XDG_DESKTOP_ENTRY_ICONS[${ICON_PATH}]:-}" ]]; then
echo "${STEP_XDG_DESKTOP_ENTRY_ICONS[${ICON_PATH}]}"
return 0
fi
if [[ ! -f "${ICON_PATH}" ]]; then
return 1
fi
if ! command -v magick &>/dev/null; then
STEP_XDG_DESKTOP_ENTRY_ICONS[${ICON_PATH}]="${ICON_PATH}"
echo "${ICON_PATH}"
return 0
fi
local XDG_UNREAL_ICONS_PATH="${XDG_DATA_HOME:-${HOME}/.local/share}/icons/hicolor"
local IS_48PX_FOUND="no"
local IS_32PX_FOUND="no"
local INDIVIDUAL_ICON_INFO
local REGEX_ICON_INFO='^([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)$'
local ICON_INFORMATION
ICON_INFORMATION=$(magick identify -format "%p %h %z %k\n" "${ICON_PATH}" | sort -k3nr -k2nr -k4nr)
while IFS= read -r INDIVIDUAL_ICON_INFO; do
if [[ "${INDIVIDUAL_ICON_INFO}" =~ ${REGEX_ICON_INFO} ]]; then
local ICON_INDEX="${BASH_REMATCH[1]}"
local ICON_SIZE="${BASH_REMATCH[2]}"
case "${ICON_SIZE}" in
48)
if [[ "${IS_48PX_FOUND}" == "yes" ]]; then
continue
fi
IS_48PX_FOUND="yes"
mkdir -p "${XDG_UNREAL_ICONS_PATH}/48x48/apps"
magick -quiet "${ICON_PATH}[${ICON_INDEX}]" "${XDG_UNREAL_ICONS_PATH}/48x48/apps/${TARGET_ICON_NAME}.png" >/dev/null
STEP_XDG_DESKTOP_ENTRY_ICONS[${ICON_PATH}]="${TARGET_ICON_NAME}"
;;
32)
if [[ "${IS_32PX_FOUND}" == "yes" ]]; then
continue
fi
IS_32PX_FOUND="yes"
mkdir -p "${XDG_UNREAL_ICONS_PATH}/32x32/apps"
magick -quiet "${ICON_PATH}[${ICON_INDEX}]" "${XDG_UNREAL_ICONS_PATH}/32x32/apps/${TARGET_ICON_NAME}.png" >/dev/null
STEP_XDG_DESKTOP_ENTRY_ICONS[${ICON_PATH}]="${TARGET_ICON_NAME}"
;;
esac
fi
done <<<"${ICON_INFORMATION}"
if [[ -n "${STEP_XDG_DESKTOP_ENTRY_ICONS[${ICON_PATH}]:-}" ]]; then
echo "${STEP_XDG_DESKTOP_ENTRY_ICONS[${ICON_PATH}]}"
else
STEP_XDG_DESKTOP_ENTRY_ICONS[${ICON_PATH}]="${ICON_PATH}"
echo "${ICON_PATH}"
fi
}
step::xdg_desktop_entry \
"Application Menu Entry" \
"Do you want to create an application menu entry?" \
"${XDG_DATA_HOME:-${HOME}/.local/share}/applications/OldUnreal-${PRODUCT_SHORTNAME}.desktop" \
"${XDG_DATA_HOME:-${HOME}/.local/share}/applications/OldUnreal-${PRODUCT_SHORTNAME}-UnrealEd.desktop" \
"${_arg_application_entry}"
step::xdg_desktop_entry::xdg_dir \
"Desktop Shortcut" \
"Do you want to create a shortcut on your desktop?" \
"DESKTOP" \
"${PRODUCT_SHORTNAME}.desktop" \
"UnrealEd-${PRODUCT_SHORTNAME}.desktop" \
"${_arg_desktop_shortcut}"
# shellcheck shell=bash
step::notify_install_finished() {
# Remove installation files
if [[ "${_arg_keep_installer_files:-off}" != "on" ]] && [[ -d "${_arg_destination%/}/Installer" ]]; then
rm -rf "${_arg_destination%/}/Installer"
fi
echo
echo -e "$(ansi::styled "${PRODUCT_NAME}" "${ansi_stylenum[bright]}" "${ansi_colornum[green]}") $(ansi::styled "is now installed!" "" "${ansi_colornum[green]}")"
echo
echo "You can launch the game by running:"
echo " ${DESTINATION_HOMIFIED}/System${UE_SYSTEM_FOLDER_SUFFIX:-}/${MAIN_BINARY_NAME}${ARCHITECTURE_BINARY_SUFFIX}"
local DIALOG_TEXT="""
${PRODUCT_NAME} is now installed!
You can launch the game by running:
${DESTINATION_HOMIFIED}/System${UE_SYSTEM_FOLDER_SUFFIX:-}/${MAIN_BINARY_NAME}${ARCHITECTURE_BINARY_SUFFIX}
"""
if [[ "${_arg_ui_mode:-none}" == "none" ]]; then
return 0
elif [[ "${_arg_ui_mode:-none}" == "kdialog" ]]; then
DIALOG_ARGS=(
kdialog
--msgbox
"${DIALOG_TEXT}"
)
elif [[ "${_arg_ui_mode:-none}" == "zenity" ]]; then
DIALOG_ARGS=(
zenity
--info
--width=400
"--text=${DIALOG_TEXT}"
)
fi
"${DIALOG_ARGS[@]}" &>/dev/null || true
}
step::notify_install_finished
}
installer::entrypoint
# ] <-- needed because of Argbash