2955 lines
97 KiB
Bash
Executable File
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
|