#!/bin/sh
# Copyright 2013-2022 Arx Libertatis Team (see the AUTHORS file)
#
# This file is part of Arx Libertatis.
#
# Arx Libertatis is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Arx Libertatis is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Arx Libertatis. If not, see .
##########################################################################################
# Install script for Arx Fatalis data files to be used with Arx Libertatis
# Usage: just run the damned script, maybe check --help
# This scripts targets Linux and FreeBSD, but may also work on other UNIX-like systems.
# Is this a multi-thousand-line bas^H^H^HPOSIX shell script?
# Sure looks like it.
# Am I mad?
# Most likely.
# If you want to edit the required files and checksums, scroll to the end.
##########################################################################################
# Colors
disable_color() {
red='' ; green='' ; yellow='' ; blue='' ; pink='' ; cyan='' ; white=''
dim_red='' ; dim_green='' ; dim_yellow='' ; dim_blue='' ; dim_pink=''
dim_cyan='' ; dim_white='' ; reset=''
}
disable_color
if [ -z "${NO_COLOR:-1}" ] && [ -t 1 ] && [ "$(tput colors 2> /dev/null)" != -1 ] ; then
red="$(printf '\033[1;31m')"
green="$(printf '\033[1;32m')"
yellow="$(printf '\033[1;33m')"
blue="$(printf '\033[1;34m')"
pink="$(printf '\033[1;35m')"
cyan="$(printf '\033[1;36m')"
white="$(printf '\033[1;37m')"
dim_red="$(printf '\033[0;31m')"
dim_green="$(printf '\033[0;32m')"
dim_yellow="$(printf '\033[0;33m')"
dim_blue="$(printf '\033[0;34m')"
dim_pink="$(printf '\033[0;35m')"
dim_cyan="$(printf '\033[0;36m')"
dim_white="$(printf '\033[0;37m')"
reset="$(printf '\033[m')"
fi
##########################################################################################
# Constants
# Name and download locations for the 1.21 patch
patch_ver='1.21'
patch_name="ArxFatalis_${patch_ver}_MULTILANG.exe"
patch_name_localized="ArxFatalis_${patch_ver}_%s.exe"
patch_url_path="arxfatalis/patches/${patch_ver}/${patch_name}"
patch_url_master="https://cdn.bethsoft.com/${patch_url_path}"
patch_urls="https://arx.vg/${patch_name} ${patch_url_master}"
patch_urls="$patch_urls https://web.archive.org/web/${patch_url_master}"
# Name and download locations for the Japanese 1.02j patch
patch_jp_ver='1.02j'
patch_jp_name="arx_jpn_patch_${patch_jp_ver}.exe"
patch_jp_url_master="http://www.capcom.co.jp/pc/arx/patch/${patch_jp_name}"
patch_jp_urls="https://arx.vg/${patch_jp_name}" # master URL is no longer available
patch_jp_urls="$patch_jp_urls https://web.archive.org/web/${patch_jp_url_master}"
# Name and store page for the GOG.com download
gog_names='"setup_arx_fatalis.exe" "setup_arx_fatalis_2.0.0.7.exe" "setup_arx_fatalis_1.21_(21994).exe" "setup_arx_fatalis_1.22_(38577).exe" "setup_arx_fatalis_1.22_(french)_(38577).exe" "setup_arx_fatalis_1.22_(german)_(38577).exe" "setup_arx_fatalis_1.22_(italian)_(38577).exe" "setup_arx_fatalis_1.22_(russian)_(38577).exe" "setup_arx_fatalis_1.22_(spanish)_(38577).exe"'
gog_url='https://www.gog.com/gamecard/arx_fatalis'
# Store page for the Steam download
steam_appid='1700'
steam_url="https://store.steampowered.com/app/$steam_appid/Arx_Fatalis/"
steam_install_dir="Arx Fatalis"
# Name and wiki page for the demo download
demo_names="arx_demo_english.zip arxdemoenglish.zip arx_demo_english.exe"
demo_names="$demo_names arx_fatalis_demo_fr.zip arx_demo_german.exe arx_jpn_demo.exe"
demo_url='https://arx.vg/demo-data'
bug_report_url='https://arx.vg/bug'
cabextract_url='https://cabextract.org.uk/'
innoextract_url='https://constexpr.org/innoextract/'
##########################################################################################
# Standard directories
user_pwd="$PWD"
user_pwd="${user_pwd%/}"
platform="$(uname)"
command="$(basename "$0")"
scommand="$(printf '%s' "$command" | tr - _)"
if [ "$platform" = 'Darwin' ] ; then
# macOS
data_dirs='/Applications'
data_home="$HOME/Library/Application Support"
config_home="$HOME/Library/Application Support"
data_dir_suffixes='ArxLibertatis'
user_dir_suffixes='ArxLibertatis'
config_dir_suffixes='ArxLibertatis'
downloads_dir="$HOME/Downloads"
else
# Linux, FreeBSD, ...
data_dirs="${XDG_DATA_DIRS:-"/usr/local/share/:/usr/share/"}:/opt"
data_home="${XDG_DATA_HOME:-"$HOME/.local/share"}"
config_home="${XDG_CONFIG_HOME:-"$HOME/.config"}"
data_dir_suffixes='games/arx:arx'
user_dir_suffixes='arx'
config_dir_suffixes='arx'
[ -f "${config_home}/user-dirs.dirs" ] && . "${config_home}/user-dirs.dirs"
downloads_dir="${XDG_DOWNLOAD_DIR:-"$HOME/Downloads"}"
fi
downloads_dir="${downloads_dir%/}"
tempdir="${TMPDIR:-"/tmp"}"
tempdir="${tempdir%/}"
[ -d "$tempdir" ] || tempdir="$PWD"
eval "data_path=\"\$${scommand}_PATH\""
[ -z "$data_path" ] && data_path="$arx_PATH"
##########################################################################################
# Helper functions
exec 4>&2 # fd to the original stderr (we redirect output to a log file in some cases)
logfile='' # log file receiving sdout and stderr
true=0 # Return value / exit status that evaluates to true
false=1 # Return value / exit status that evaluates to false
# 1 if the script is being run as root, false otherwise
if [ "$(id -u)" = 0 ] ; then is_root=1 ; else is_root=0 ; fi
# Print one line of text, without escape codes or other shell-specific shenanigans.
# Seriously, shells, you can't even agree on a consistent implementation of echo?
# Usage: print
print() {
printf '%s\n' "$1"
}
puts() {
printf '%s' "$1"
}
disabled_commands=' ' # List of commands that should not be used, even if they exist
# Make `have` return false for a command
# Usage: disable_command
disable_command() {
disabled_commands="$disabled_commands$1 "
}
# Check if a command is available.
# Usage: have
# Return: $true if the command is available, $false otherwise
have() {
case "$disabled_commands" in *" $1 "*) return $false ; esac
command -v "$1" > /dev/null 2>&1
}
# Make a path absolute no matter if it is relative or not
# Usage: abspath
# Too bad we can't just use readlink -m
abspath() {
case "$1" in
/*) print "$1" ;;
*) print "$PWD/$1" ;;
esac
}
# Get the canonical representation of an existing path
# Usage: canonicalize
# Too bad we can't just use readlink -f
if have realpath ; then
canonicalize() { realpath "$1" ; }
elif have grealpath ; then
canonicalize() { grealpath "$1" ; }
elif have greadlink ; then
canonicalize() { greadlink -f "$1" ; }
else
canonicalize() {
_canonicalize_old_pwd="$PWD"
_canonicalize_file="$1"
while true ; do
cd "$(dirname "$_canonicalize_file")"
_canonicalize_file="$(basename "$_canonicalize_file")"
[ -L "$_canonicalize_file" ] || break;
_canonicalize_file="$(readlink "$_canonicalize_file")"
done
echo "$(pwd -P)/$_canonicalize_file"
cd "$_canonicalize_old_pwd"
}
fi
cleanup_functions='' # List of functions to be run on exit
# Add a function to run on exit.
# Functions are run in the order they are added.
# Usage: on_exit
# Cleanup functions will receive one argument: the exit message if any or an empty string.
on_exit() {
[ -z "$cleanup_functions" ] || cleanup_functions=" $cleanup_functions"
cleanup_functions="$1$cleanup_functions"
}
# Run exit runctions.
cleanup() {
_cleanup_functions="$cleanup_functions" ; cleanup_functions=''
[ -z "$_cleanup_functions" ] && return
eval "for _cleanup_func in $_cleanup_functions ; do \"\$_cleanup_func\" \"\$@\" ; done"
}
# Register our cleanup handler.
trap "cleanup" EXIT
# Some shells don't have their own (non-libc) SIGINT handler, but the EXIT trap
# won't trigger if there is none!
trap 'print >&4 ; quit 1' INT
# Run cleanup functions with a possible message and then exit.
# Usage: quit []
quit() {
cleanup "$2"
exit $1
}
# Exit with a non-zero status and optionally print a message.
# Usage: die [...]
die() {
_die_message=''
if [ $# -gt 0 ] ; then
_die_message="$1" ; shift
for _die_arg ; do _die_message="$_die_message $_die_arg" ; done
_die_message="$_die_message
If you think this is a bug in the install script
please report the complete output at
$bug_report_url"
if [ -n "$logfile" ] && [ -f "$logfile" ] ; then
_die_message="$_die_message
Also attach the contents of
$logfile"
logfile='' # so that we don't remove it on exit
printf "${red}%s${reset}\\n" "$_die_message" >&4 # also print to priginal stdout
printf '\n%s\n' 'Preserving log file.' >&4
fi
printf "${red}%s${reset}\\n" "$_die_message"
fi
quit 1 "$_die_message"
}
# Escape a string from stdin for use in a whitespace-separated list.
# Usage: print | escape_pipe
escape_pipe() {
sed "s:[^a-zA-Z0-9/_.$1]:\\\\&:g"
}
# Escape a string for use in a whitespace-separated list.
# Usage: escape
escape() {
print "$1" | escape_pipe "$2"
}
# Convert a colon-separated list into an escaped whitespace-separated list.
# Usage: to_list
to_list() {
escape "$1" | sed 's/\\:/ /g'
}
# Line-based output into a list
# Usage: ls | lines_to_list
lines_to_list() {
escape_pipe | tr '\n' ' '
}
# Check if a whitespace separated list contains a string.
# Usage: list_contains
list_contains() {
eval "_list_contents=\"\$$1\""
[ -z "$_list_contents" ] && return $false
eval "for _list_contains_entry in $_list_contents ; do" \
" [ \"\$_list_contains_entry\" = \"\$2\" ] && return \$true ; done"
return $false
}
# Append a string to a whitespace separated list.
# Usage: list_append [comment]
# Whitespace separated lists can be loaded into the argument list using:
# eval "set -- $var"
list_append() {
_list_entry="$(escape "$2")"
eval "_list_contents=\"\$$1\""
if [ -z "$_list_contents" ]
then eval "$1=\"\$_list_entry\""
else eval "$1=\"\$_list_contents \$_list_entry\""
fi
eval "[ -z \"\$$1__list_count\" ] && $1__list_count=0"
eval "_list_count=\$$1__list_count"
eval "$1__list_comment_$_list_count=\"\$3\""
eval "$1__list_count=\$(($1__list_count + 1))"
}
# Append one list to another, preserving comments.
# Usage: list_merge
list_merge() {
eval "_list_append=\"\$$2\""
[ -z "$_list_append" ] && return
eval "
_list_merge_i=0
for _list_merge_entry in $_list_append ; do
list_append $1 \"\$_list_merge_entry\" \"\$(list_comment $2 \$_list_merge_i)\"
_list_merge_i=\$((_list_merge_i + 1))
done
"
}
# Get a comment associated with alist entry
# Usage: list_comment
list_comment() {
eval "print \"\$$1__list_comment_$2\""
}
# Set a comment associated with alist entry
# Usage: list_comment
set_list_comment() {
eval "$1__list_comment_$2=\"\$3\""
}
# Append a string to a whitespace separated list if it isn't already in the list.
# Usage: set_append [comment]
set_append() {
if ! list_contains "$1" "$2" ; then
list_append "$1" "$2" "$3"
fi
}
# Check if a directory contains a file while ignoring case differences.
# Usage: icontains
icontains() {
[ -n "$(find "$1" -mindepth 1 -maxdepth 1 -iname "$2")" ]
}
# Check if a directory or file is writable or can be created.
# Usage: is_writable
is_writable() {
[ -w "$1" ] && return $true
[ ! -e "$1" ] && is_writable "$(dirname "$1")"
}
# Create a directory and die with a message on error.
# Usage: create_dir
create_dir() {
mkdir -p "$1" || die "Could not create $2 directory: $1"
}
probe_file_dirs=''
set_append probe_file_dirs "$user_pwd"
set_append probe_file_dirs "$downloads_dir"
set_append probe_file_dirs "$HOME"
set_append probe_file_dirs "$tempdir"
# Find a file in standard directories.
# Usage: probe_file [comment]
# Will call `command ` for each file found.
probe_file() {
eval "for _probe_file_d in $probe_file_dirs ; do [ -f \"\$_probe_file_d/\$2\" ] && \$1 \"\$_probe_file_d/\$2\" \"\$3\" && return \$true ; done"
}
# Find files in standard directories.
# Usage: probe_file [comment]
# Will call `command ` for each file found.
probe_files() {
[ -z "$2" ] && return $false
eval "for _probe_files_file in $2 ; do probe_file \"\$1\" \"\$_probe_files_file\" \"\$3\" && return \$true ; done"
return $false
}
##########################################################################################
# Parse command-line arguments
extract_zip_reqs=''
list_append extract_zip_reqs 'bsdtar' 'libarchive'
list_append extract_zip_reqs 'unzip'
list_append extract_zip_reqs '7za'
list_append extract_zip_reqs '7z' 'p7zip'
extract_ms_cab_reqs=''
list_append extract_ms_cab_reqs 'bsdtar' 'with libarchive 3.1+'
list_append extract_ms_cab_reqs 'cabextract' "$cabextract_url"
list_append extract_ms_cab_reqs '7za'
list_append extract_ms_cab_reqs '7z' 'p7zip'
extract_installshield_reqs=''
list_append extract_installshield_reqs 'unshield'
extract_rar_reqs=''
list_append extract_rar_reqs 'unrar'
extract_ace_reqs=''
list_append extract_ace_reqs 'unace'
extract_innosetup_reqs=''
list_append extract_innosetup_reqs 'innoextract' "$innoextract_url"
mount_cdrom_reqs=''
list_append mount_cdrom_reqs 'fuseiso'
extract_iso_reqs=''
list_append extract_iso_reqs 'isoinfo'
list_append extract_iso_reqs 'bsdtar' 'libarchive'
list_append extract_iso_reqs '7z' 'p7zip'
extract_cdrom_reqs=''
list_merge extract_cdrom_reqs mount_cdrom_reqs
list_merge extract_cdrom_reqs extract_iso_reqs
download_reqs=''
list_append download_reqs 'wget'
list_append download_reqs 'curl'
list_append download_reqs 'fetch' 'FreeBSD'
printf '%s %s\n' "${white}Welome to the ${green}Arx Fatalis${white} 1.21/1.22 data" \
"install script for UNIX-like systems!${reset}"
patchfile='' # Main patch file
patchfile_jp='' # Japanese patch file
sourcefile='' # Source file or directory
datadir='' # Output data directory
batch=0 # Never wait for user input
gui=0 # Display a graphical user interface (command-line interface otherwise)
install=1 # Install new non-patch files
selected_stuff=0 # Has the user made a selection
installed_stuff=0 # Have we already installed anything?
patch=1 # Install patch files if needed
probe_patch=1 # Look for patch files in standard locations and download if needed
redirect_log=1 # Redirect standard output/error output to a log file in GUI mode
# Enable compatibility with old install-* scripts.
# Usage: enable_compat_mode
enable_compat_mode() {
print \
"${yellow}Enabling compatibility mode for ${pink}$command${yellow}.${reset}
${dim_yellow}The individual ${dim_pink}install-*${dim_yellow} scripts have been merged.
Rename this script to something else (like ${dim_pink}arx-install-data${dim_yellow}) to unlock its full power!${reset}
" >&2
batch=1
probe_patch=0
if [ -z "$1" ] || [ "$1" = '--help' ] || [ "$1" = '-h' ] ; then
printf '%s\n\n%s\n' "$5" \
"${yellow}More options are available in the non-compatibility mode.${reset}"
exit $false
fi
if [ -z "$2" ] ; then install=0 ; else sourcefile="$2" ; fi
if [ -z "$3" ] ; then patch=0 ; else patchfile="$3" ; fi
if [ -z "$4" ] ; then datadir="$user_pwd" ; else datadir="$4" ; fi
}
case "$command" in
install-cd)
[ "$1" = "--no-progress" ] && shift # ignore - not supported
enable_compat_mode "$1" "$1" "$2" "$3" "\
Usage: $command path/to/mount/point/ path/to/ArxFatalis_1.21_MULTILANG.exe [output_dir]
or $command path/to/cd.iso path/to/ArxFatalis_1.21_MULTILANG.exe [output_dir]" ;;
install-copy)
enable_compat_mode "$1" "$1" '' "$2" "\
Usage: $command path/to/ArxFatalis/ [output_dir]" ;;
install-demo)
enable_compat_mode "$1" "$1" '' "$2" "\
Usage: $command path/to/arx_demo_english.zip [output_dir]" ;;
install-gog)
[ "$1" = "--no-progress" ] && shift # ignore - not supported
enable_compat_mode "$1" "$1" '' "$2" "\
Usage: $command path/to/setup_arx_fatalis.exe [output_dir]" ;;
install-verify)
enable_compat_mode "$1" '' '' "$1" "\
Usage: $command [directory]" ;;
*) # non-compatibility mode
# Print elements in a list, joined by ' or '
# Usage: print_help_or [color]
print_help_or() {
_print_help_or_var=$1
eval "_print_help_or_list=\"\$$1\""
_print_help_or_color="$2"
[ -z "$1" ] && return
eval "
_print_help_or_i=0
for _print_help_or_entry in $_print_help_or_list ; do
[ \$_print_help_or_i = 0 ] || puts ' or '
printf '%s%s' \"\$_print_help_or_color\" \"\$_print_help_or_entry\"
[ -z \"\$_print_help_or_color\" ] || puts \"\$reset\"
_print_help_or_comment=\"\$(list_comment \$_print_help_or_var \$_print_help_or_i)\"
[ -z \"\$_print_help_or_comment\" ] || printf ' (%s)' \"\$_print_help_or_comment\"
_print_help_or_i=\$((_print_help_or_i + 1))
done
"
}
# Print elements in a list, one per line.
# Usage: print_help_list
# prefi-format will receive one argument: the list index starting at 1
print_help_list() {
_print_help_list_prefix="$1"
_print_help_list_var=$2
eval "_print_help_list_list=\"\$$2\""
[ -z "$1" ] && return
eval "
_print_help_list_i=0
for _print_help_list_entry in $_print_help_list_list ; do
case \"\$_print_help_list_prefix\" in
*%*) printf \"\$_print_help_list_prefix\" \$((_print_help_list_i + 1)) ;;
*) puts \"\$_print_help_list_prefix\"
esac
printf \"%s\${reset}\" \"\$_print_help_list_entry\"
_print_help_list_comment=\"\$(list_comment \$_print_help_list_var \$_print_help_list_i)\"
[ -z \"\$_print_help_list_comment\" ] || printf ' (%s)' \"\$_print_help_list_comment\"
printf '\n'
_print_help_list_i=\$((_print_help_list_i + 1))
done
"
}
# Print help output.
# Usage: print_help []
print_help() {
[ -z "${1-}" ] || ( printf '%s\n\n' "${red}$1${reset}" )
print "
${white}Start the script without any arguments to select paths interactively:
\$ $command${reset}
Usage: $command [--source] source [--patch patchfile] [[--data-dir] datadir]
$command [--patch patchfile] [--data-dir datadir]
$command --verify [[--data-dir] datadir]
${green}-s, --source PATH${reset} Path to the source file or directory
${cyan}-d, --data-dir DIR${reset} Where to install the data
${blue}-p, --patch FILE${reset} Path to the ${patch_ver} patch file
--patch-jp FILE Path to the ${patch_jp_ver} Japanese patch file
-v, --verify Only verify the files in the data-dir, don't install new ones,
except for patch files.
-n, --no-patch Don't use a patch file unless explicitly specified.
-h, --help Print this message and maybe more
-b, --batch Never ask the user questions
-g, --gui Show a GUI asking the user what to do
Requires ${dim_pink}KDialog${reset}, ${dim_pink}Zenity${reset}, or ${dim_pink}Xdialog${reset}.
If none of them are available the script is re-launched
in a terminal emulator.
-c, --cli Interactively ask the user to select files/directories (no GUI)
--no-redirect-log Don't redirect output to a log file when in GUI mode
--disable-COMMAND Don't use the given tool, even if it exists.
Valid values are 7z, 7za, aterm, bsdtar, cabextract, curl,
dcop, fetch, fuseiso, fusermount, gnome-terminal, greadlink,
grealpath, gtkterm, gxmessage, innoextract, isoinfo, kdialog,
konsole, md5, md5sum, mount, qdbus, realpath, rxvt, umount,
unace, unrar, unshield, unzip, urxvt, wget, Xdialog,
xmessage, xterm, x-terminal-emulator, zenity.
--gui is enabled by default if there are no arguments *and* stdin, stdout or stderr is not a terminal
"
[ -n "${1-}" ] && exit $false
help_innosetup="$(print_help_or extract_innosetup_reqs "$dim_pink")"
help_cdrom="$(print_help_or extract_cdrom_reqs "$dim_pink") or root access"
help_cab="$(print_help_or extract_ms_cab_reqs "$dim_pink")"
help_zip="$(print_help_or extract_zip_reqs "$dim_pink")"
help_unshield="$(print_help_or extract_installshield_reqs "$dim_pink")"
help_rar="$(print_help_or extract_rar_reqs "$dim_pink")"
help_ace="$(print_help_or extract_ace_reqs "$dim_pink")"
help_download="$(print_help_or download_reqs "$dim_pink")"
help_optpatch="may use the 1.21 patch file and require ${help_innosetup} if not already patched"
help_probe_file_dirs="
a) the current working directory (\$PWD): $user_pwd
b) the user's downloads directory (\$XDG_DOWNLOAD_DIR): $downloads_dir
c) the user's home directory (\$HOME): $HOME
d) the temp directory (\$TMPDIR): $tempdir"
help_probe_file_dirs_patch="${help_probe_file_dirs}
e) the directory containing the source file"
help_probed_files="$gog_names $demo_names"
print "
The ${pink}dependencies${reset} required by the ${command} script depend on the source files.
However, you always need either ${dim_pink}md5sum${reset} or ${dim_pink}md5${reset}.
The ${green}source${reset} can be one of many things:
* ${white}Mounted Arx Fatalis ${green}cdrom${reset}
requires:
- ${help_cab}
- ${help_innosetup}
needs the 1.21 patch file
* ${white}Arx Fatalis cdrom ${green}ISO${white} image / device file${reset}
requires:
- ${help_cdrom}
- ${help_cab}
- ${help_innosetup}
needs the 1.21 patch file
* ${white}Arx Fatalis installer from ${green}GOG.com${white}${reset} ($(print_help_or gog_names))
requires:
- ${help_innosetup}
never uses the 1.21 patch file
get it from ${dim_green}${gog_url}${reset}
* ${green}Installed${white} copy of Arx Fatalis${reset} (for example from ${green}Steam${reset})
${help_optpatch}
get it from ${dim_green}${steam_url}${reset}
* ${white}Arx Fatalis ${green}demo${reset} (one of the following)
$(print_help_list " - " demo_names)
requires:
- ${help_zip} [english/french .zip]
- ${help_cab}
- ${help_rar} [english .exe]
- ${help_ace} [german .exe]
- ${help_unshield} [japanese .exe]
never uses the 1.21 patch file
get it from ${dim_green}${demo_url}${reset}
* ${white}Extracted Arx Fatalis demo installer${reset}
requires:
- ${help_cab}
never uses the 1.21 patch file
* ${white}Installed copy of the Arx Fatalis demo${reset}
never uses the 1.21 patch file
If no source is specified, these files will be probed:
1. The following files in${help_probe_file_dirs}
$(print_help_list " 1.%d ${green}" help_probed_files)
2. The user's ${green}Steam${reset} library, if available
3. If \$WINEPREFIX is set, any installation in there
4. Any installation in the default WINEPREFIX (${green}~/.wine${reset})
5. Any mounted ${green}cdrom${reset} or ISO file
If no ${blue}patch${reset} file is specified, but is needed and
the --no-patch option wasn't specified:
1. Try to find the following files in${help_probe_file_dirs_patch}
1.1. ${blue}${patch_name}${reset}
1.2. $(printf "$patch_name_localized" '')
Where is one of EN, ES, FR, GE, IT, RU,
depending on the language of the data files.
2. Downloaded from:
$(print_help_list " - ${dim_blue}" patch_urls)
Downloading the patch file requires ${help_download}.
Extracting the ${patch_ver} patch file requires ${help_innosetup}.
For the Japanese version, if no ${blue}patch-jp${reset} file is specified,
but is needed and the --no-patch option wasn't specified specified:
1. Try to find ${blue}${patch_jp_name}${reset} in${help_probe_file_dirs_patch}
2. Downloaded from:
$(print_help_list " - ${dim_blue}" patch_jp_urls)
Downloading the patch file requires ${help_download}.
Extracting the Japanese patch file requires:
- ${help_unshield}
- ${help_cab}.
If no ${cyan}data-dir${reset} to install into is specified,
one is automatically selected similarly to how Arx Libertatis would:
If --verify and --no-patch are give, use the first existing directory
of the following, otherwise, use the first existing writable directory
or, if none exists, the first directory that can be created:
1. Any path in \$${scommand}_PATH or \$arx_PATH (for use in wrapper scripts)
2. \"\${XDG_DATA_DIRS:-\"/usr/local/share/:/usr/share/\"}:/opt\" / \"$data_dir_suffixes\":"
i=1
eval "set -- $(to_list "$data_dirs")"
for prefix in "$@" ; do
eval "set -- $(to_list "$data_dir_suffixes")"
for suffix ; do
printf " 2.%d. ${dim_cyan}%s${reset}\\n" $i "$prefix/$suffix"
i=$((i + 1))
done
done
print "3. \"\${XDG_DATA_HOME:-\"\$HOME/.local/share\"}\" / \"$user_dir_suffixes\""
i=1
eval "set -- $(to_list "$user_dir_suffixes")"
for suffix ; do
printf " 3.%d. ${dim_cyan}%s${reset}\\n" $i "$data_home/$suffix"
i=$((i + 1))
done
print
exit $true
}
user_is_sane=1
if [ ! -t 0 ] || [ ! -t 1 ] || [ ! -t 2 ] ; then
[ $# = 0 ] && gui=1
fi
while [ $# -gt 0 ] ; do
case "$1" in
--source=*) sourcefile="${1#--source=}" ; install=1 ;;
-s|--source) shift ; sourcefile="$1" ; install=1 ;;
--data-dir=*) datadir="${1#--data-dir=}" ;;
-d|--data-dir) shift ; datadir="$1" ;;
--patch=*) patchfile="${1#--patch=}" ; patch=1 ;;
-p|--patch) shift ; patchfile="$1" ; patch=1 ;;
--patch-jp=*) patchfile_jp="${1#--patch-jp=}" ; patch=1 ;;
--patch-jp) shift ; patchfile_jp="$1" ; patch=1 ;;
-v|--verify) install=0 patch=0 ;;
-n|--no-patch) [ -z "$patchfile" ] && [ -z "$patchfile_jp" ] && patch=0
probe_patch=0 ;;
-b|--batch) batch=1 ;;
-g|--gui) gui=1 ;;
-c|--cui|--cli) batch=0 ; gui=0 ;;
--no-redirect-log) redirect_log=0 ;;
--i-am-insane) user_is_sane=0 ;;
--disable-*) disable_command "${1#--disable-}" ;;
--disable) shift ; disable_command "${1#--disable-}" ;;
-h|--help) print_help ;;
-*) print_help "Unknown option: $1" ;;
*)
if [ -z "${sourcefile-}" ] && [ $install = 1 ] ; then sourcefile="$1"
elif [ -z "${datadir-}" ] ; then datadir="$1"
else print_help "Too many options: $1" ; fi
esac
[ -z "${1-}" ] && print_help "Expected more options"
shift;
done
print "See \`${dim_pink}$command --help${reset}\` for available options."
esac
# Make user-provided paths absolute
[ -n "$sourcefile" ] && sourcefile="$(abspath "$sourcefile")"
[ -n "$datadir" ] && datadir="$(abspath "$datadir")"
[ -n "$patchfile" ] && patchfile="$(abspath "$patchfile")"
# Sanity check
[ $install = 1 ] && [ $batch = 1 ] && [ -z "$sourcefile" ] && [ $user_is_sane = 1 ] \
&& die "You have used --batch without providing a source file!
This would just pick the first source file found, which is a bad idea™.
If you really want this, add the --i-am-insane option."
##########################################################################################
# User interface abstraction
_dialog_title="Arx Fatalis ${patch_ver} data installer"
# Handle magic environment variable to tell the script that it has been launched
# in its own terminal and should not try to create a GUI.
if [ $batch = 0 ] && [ "$_arx_install_data_force_cli" = 1 ] ; then
trap '_arx_install_data_force_cli=0 ; quit 1' INT
printf "\n${yellow}%s${reset}\n\n" \
'Note: Install KDialog, Zenity or Xdialog for a better GUI'
wait_exit() {
[ "$_arx_install_data_force_cli" = 1 ] && print 'Press enter to exit...' && read f
exit $false
}
on_exit wait_exit
gui=0
for var in batch install patch probe_patch sourcefile datadir \
patchfile patchfile_jp disabled_commands; do
eval "$var=\"\$_arx_install_data_force_$var\""
done
fi
# Select the dialog backend to use
if [ $gui = 1 ] ; then
# Detect if we are running in a KDE session
is_kde=0
case "$DESKTOP_SESSION" in *kde*|*KDE*) is_kde=1 ; esac
[ -z "$KDE_FULL_SESSION" ] || is_kde=1
[ -z "$KDE_SESSION_UID" ] || is_kde=1
[ -z "$KDE_SESSION_VERSION" ] || is_kde=1
# Select the GUI backend, prefer kdialog for KDE sessions, zenity otherwise
if [ $is_kde = 1 ] ; then preferred=kdialog ; else preferred=zenity ; fi
for backend in $preferred zenity kdialog Xdialog ; do
have $backend && gui=$backend && break
done
if [ $gui = 1 ] ; then
# No dialog backend available
# Try opening a graphical terminal and launching the script in there.
print 'No GUI dialog backend is available - trying to launch a terminal emulator'
term_cmd="$(abspath "$(command -v "$0" 2> /dev/null)")"
# Not all terminals accept command arguments in the same way.
# Instead of hacking terminal-specific code, use a magic environment
# variable to tell the sub-process how to behave.
_arx_install_data_force_cli=1
export _arx_install_data_force_cli
for var in batch install patch probe_patch sourcefile datadir \
patchfile patchfile_jp disabled_commands ; do
eval "_arx_install_data_force_$var=\"\$$var\""
eval "export _arx_install_data_force_$var"
done
if [ $is_kde = 1 ] ; then preferred=konsole ; else preferred=x-terminal-emulator ; fi
for backend in x-terminal-emulator $preferred \
aterm urxvt rxvt konsole xterm gnome-terminal
do
if have $backend ; then
$backend -e "$term_cmd" || continue
exit $true
fi
done
# Hm, that didn't work either - bail
message="No GUI dialog backend is available"
message="$message - install KDialog, Zenity or Xdialog, or use the --cli option."
# Final attempt to let the user know what happened
for backend in gxmessage xmessage ; do
if have $backend ; then
$backend -center -buttons OK "$_dialog_title
$message"
break
fi
done
die "$message"
fi
# We don't need colors for the UI, but they may cause problems - get rid of them
disable_color
if [ $redirect_log = 1 ] ; then
# Redirect all further output into a log file
logfile="$(abspath "$(mktemp "$tempdir/arx-install-data.log.XXXXX")")"
clean_logfile() {
[ -z "$logfile" ] || rm -f "$logfile"
}
on_exit clean_logfile
print "Enabling GUI mode, standard output/error saved to $logfile"
print "Use the --cli option for an interactive command-line interface."
exec > "$logfile" 2>&1
else
print "Enabling GUI mode..."
print "Use the --cli option for an interactive command-line interface."
fi
elif [ ! "$_arx_install_data_force_cli" = 1 ] ; then
print "Enabling CLI mode, use the --gui option for a graphical interface."
fi
#----------------------------------------------------------------------------------------#
# Functions for controlling an asynchronous process via stdin
pipe_file=''
pipe_pid=0
# Run a command in the background and open a pipe to pass commands to it
# Usage: pipe_create [...]
pipe_create() {
pipe_destroy
# Pipe commands via a FIFO or, if that fails, via a regular file
pipe_file="$(mktemp -u "$tempdir/arx-install-data.pipe.XXXXX")"
if mkfifo -m 600 "$pipe_file" 2> /dev/null ; then
# Fast communication via a FIFO
"$@" < "$pipe_file" &
pipe_pid="$!"
else
# Fallback via regular file, may use polling
pipe_file="$(mktemp "$tempdir/arx-install-data.pipe.XXXXX")"
[ -z "$pipe_file" ] && return $false
tail -f "$pipe_file" | "$@" &
pipe_pid="$!"
fi
# Open fd 3 for writing into the pipe
exec 3> "$pipe_file"
return $true
}
# Kill the program created via pipe_create and cleanup files
# Usage: pipe_destroy
pipe_destroy() {
# Close fd pointing to the pipe
exec 3<&-
# Remove the FIFO or temp file
[ -z "$pipe_file" ] || rm -f "$pipe_file" > /dev/null 2>&1
pipe_file=''
# Terminate the remote process
[ "$pipe_pid" = 0 ] || kill "$pipe_pid" > /dev/null 2>&1
pipe_pid=0
return $true
}
# Check if the remote process is running
# Usage: pipe_exists || print 'oh noes'
pipe_exists() {
[ -z "$pipe_file" ] && return $false
[ "$pipe_pid" = 0 ] && return $false
kill -s 0 "$pipe_pid" > /dev/null 2>&1
}
# Send a message to the remote process
# Usage: pipe_write
pipe_write() {
[ -z "$pipe_file" ] || print "$1" >&3
}
#----------------------------------------------------------------------------------------#
# Code for the different GUI/CLI implementations
# Each implementation exposes dialog_* primitives that are used by generic functions.
case $gui in
#----------------------------------------------------------------------------------------#
zenity)
# Helper functions
# Run Zenity
# Usage: zenity_run [...]
zenity_run() {
_zenity_run_t="$1" ; shift
[ -z "$_zenity_run_t" ] || _zenity_run_t="$_zenity_run_t - "
zenity --title "$_zenity_run_t$_dialog_title" "$@"
}
# Dialog abstraction
# Create the main progress window.
# Usage: dialog_create
dialog_create() {
pipe_create zenity --title "$_dialog_title" --width 450 --progress
}
# Destroy the main progress window.
# Usage: dialog_destroy
dialog_destroy() {
pipe_destroy
}
# Show an error dialog.
# Usage: dialog_error
dialog_error() {
zenity_run 'Error' --error --no-wrap --text="$1"
}
# Show a message box.
# Usage: dialog_message
dialog_message() {
zenity_run 'Status' --info --no-wrap --text="$1"
}
# Ask a yes/no question.
# Usage: dialog_ask
dialog_ask() {
zenity_run 'Confirm' --question --no-wrap --ok-label=Yes --cancel-label=No --text="$1"
}
# Has the user quested to cancel the operation?
# Usage: dialog_cancelled && print "cancelled"
dialog_cancelled() {
! pipe_exists
}
# Set the status text.
# Usage: dialog_set_text
dialog_set_text() {
pipe_write "#$1"
}
# Set if the progress bar should continuously animate instead of showing the value.
# Usage: dialog_set_pulsate
dialog_set_pulsate() {
if [ $1 = 1 ]
then pipe_write "pulsate:true"
else pipe_write "pulsate:false"
fi
}
# Set the current progress value.
# Usage: dialog_set_value
dialog_set_value() {
pipe_write "$1"
}
# Select an entry in a list.
# Usage dialog_select_entry