#!/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 <http://www.gnu.org/licenses/>. ########################################################################################## # 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 <text> 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 <command> disable_command() { disabled_commands="$disabled_commands$1 " } # Check if a command is available. # Usage: have <command> # 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 <path> # 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 <path> # 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 <code> # 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 <status> [<message>] quit() { cleanup "$2" exit $1 } # Exit with a non-zero status and optionally print a message. # Usage: die [<message>...] 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 <string> | escape_pipe escape_pipe() { sed "s:[^a-zA-Z0-9/_.$1]:\\\\&:g" } # Escape a string for use in a whitespace-separated list. # Usage: escape <string> escape() { print "$1" | escape_pipe "$2" } # Convert a colon-separated list into an escaped whitespace-separated list. # Usage: to_list <colon-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-var> <needle> 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 <list-var> <string> [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-var> <append-list-var> 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-var> <index> list_comment() { eval "print \"\$$1__list_comment_$2\"" } # Set a comment associated with alist entry # Usage: list_comment <list-var> <index> <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 <list-var> <string> [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 <dir> <filename> 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 <path> 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 <path> <type> 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 <command> <filename> [comment] # Will call `command <file>` 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 <command> <list> [comment] # Will call `command <file>` 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 <help-flag> <sourcefile> <patchfile> <datadir> 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 <list-var> [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 <prefix-format> <list-var> # 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 [<error-message>] 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" '<LANG>') Where <LANG> 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 <command> [<args>...] 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 <command> 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 <title-prefix> <dialog-type> [<args>...] 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 <message> dialog_error() { zenity_run 'Error' --error --no-wrap --text="$1" } # Show a message box. # Usage: dialog_message <message> dialog_message() { zenity_run 'Status' --info --no-wrap --text="$1" } # Ask a yes/no question. # Usage: dialog_ask <question> 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 <text> dialog_set_text() { pipe_write "#$1" } # Set if the progress bar should continuously animate instead of showing the value. # Usage: dialog_set_pulsate <enable> 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 <percentage> dialog_set_value() { pipe_write "$1" } # Select an entry in a list. # Usage dialog_select_entry <var> <label> <tag1> <item1> [ <tag2> < item2> ... ] dialog_select_entry() { _zenity_select_entry_v="$1" ; shift _zenity_select_entry_t="$1" ; shift _zenity_select_entry_r="$( zenity_run 'Select path' --width 550 --height 300 \ --list --text="$_zenity_select_entry_t" \ --column '#' --column 'Path' --hide-column=1 "$@" --hide-header )" [ -z "$_zenity_select_entry_r" ] && return $false eval "$_zenity_select_entry_v=\"\$_zenity_select_entry_r\"" return $true } # dialog_select_path does not support the --any flag dialog_select_path_any=0 # Let the user select a path. # Usage: dialog_select_path (--file|--dir|--any) <result-var> <label> # Any is only supported if $dialog_select_path_any is 1. dialog_select_path() { case "$1" in --any) die 'not implemented' ;; --file) _zenity_select_path_f='--file-selection' ;; --dir) _zenity_select_path_f='--file-selection --directory' ;; esac _zenity_select_path="$( eval "zenity_run \"\$3\" $_zenity_select_path_f" 2> /dev/null )" [ -z "$_zenity_select_path" ] && return $false eval "$2=\"\$_zenity_select_path\"" return $true } dialog_retry() { zenity_run 'Error' --question --no-wrap --text="$1" \ --ok-label='Retry' --cancel-label='Ignore' case $? in 0) dialog_retry_choice='retry' ;; 1) dialog_retry_choice='ignore' ;; *) dialog_retry_choice='abort' ;; esac } ;; #----------------------------------------------------------------------------------------# kdialog) # Helper functions kdialog_handle='' # dbus/dcop handle for the main progress window # Send a message to the main KDialog instance non-_q variants hide all output kdialog_qdbus_q() { have qdbus && eval "qdbus $kdialog_handle \"\$@\"" 2> /dev/null ; } kdialog_qdbus() { kdialog_qdbus_q "$@" > /dev/null ; } kdialog_dcop_q() { have dcop && eval "dcop $kdialog_handle \"\$@\"" 2> /dev/null ; } kdialog_dcop() { kdialog_dcop_q "$@" > /dev/null ; } kdialog_cmd_q() { kdialog_qdbus_q "$@" || kdialog_dcop_q "$@" ; } kdialog_cmd() { kdialog_cmd_q "$@" > /dev/null ; } # Run KDialog # Usage: kdialog_run <title-prefix> <dialog-type> [<args>...] kdialog_run() { _kdialog_run_t="$1" ; shift [ -z "$_kdialog_run_t" ] || _kdialog_run_t="$_kdialog_run_t - " kdialog --icon arx-libertatis --title "$_kdialog_run_t$_dialog_title" "$@" } # Dialog abstraction # Create the main progress window. # Usage: dialog_create dialog_create() { dialog_destroy _kdialog_force_width='WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW' kdialog_handle="$(kdialog_run '' --progressbar "$_kdialog_force_width" 0)" [ -z "$kdialog_handle" ] && return $false kdialog_cmd showCancelButton true return $true } # Destroy the main progress window. # Usage: dialog_destroy dialog_destroy() { [ -z "$kdialog_handle" ] && return $true kdialog_cmd close kdialog_handle='' } # Show an error dialog. # Usage: dialog_error <message> dialog_error() { kdialog_run 'Error' --error "$1" > /dev/null 2> /dev/null } # Show a message box. # Usage: dialog_message <message> dialog_message() { kdialog_run 'Status' --msgbox "$1" > /dev/null 2> /dev/null } # Ask a yes/no question. # Usage: dialog_ask <question> dialog_ask() { kdialog_run 'Confirm' --warningyesno "$1" > /dev/null 2> /dev/null } # Has the user quested to cancel the operation? # Usage: dialog_cancelled && print "cancelled" dialog_cancelled() { [ "$(kdialog_cmd_q wasCancelled || print true)" = true ] } # Set the status text. # Usage: dialog_set_text <text> dialog_set_text() { kdialog_qdbus setLabelText "$1" || kdialog_dcop setLabel "$1" } # Set if the progress bar should continuously animate instead of showing the value. # Usage: dialog_set_pulsate <enable> dialog_set_pulsate() { _kdialog_max=100 [ $1 = 1 ] && _kdialog_max=0 kdialog_qdbus Set "" maximum $_kdialog_max || kdialog_dcop setMaximum $_kdialog_max } # Set the current progress value. # Usage: dialog_set_value <percentage> dialog_set_value() { kdialog_qdbus Set "" value "$1" || kdialog_dcop setProgress "$1" [ $1 = 100 ] && kdialog_cmd showCancelButton true } # Select an entry in a list. # Usage dialog_select_entry <var> <label> <tag1> <item1> [ <tag2> < item2> ... ] dialog_select_entry() { _kdialog_select_entry_v="$1" ; shift _kdialog_select_entry_t="$1" ; shift _kdialog_select_entry_w=" " _kdialog_select_entry_w="$_kdialog_select_entry_w$_kdialog_select_entry_w" _kdialog_select_entry_r="$( kdialog_run 'Select path' \ --menu "$_kdialog_select_entry_t$_kdialog_select_entry_w" "$@" 2> /dev/null )" [ -z "$_kdialog_select_entry_r" ] && return $false eval "$_kdialog_select_entry_v=\"\$_kdialog_select_entry_r\"" return $true } # dialog_select_path does not support the --any flag dialog_select_path_any=0 # Let the user select a path. # Usage: dialog_select_path (--file|--dir|--any) <result-var> <label> # Any is only supported if $dialog_select_path_any is 1. dialog_select_path() { case "$1" in --any) die 'not implemented' ;; --file) _kdialog_select_path_f=--getopenfilename ;; --dir) _kdialog_select_path_f=--getexistingdirectory ;; esac _kdialog_select_path="$( kdialog_run "$3" $_kdialog_select_path_f "$HOME" 2> /dev/null )" [ -z "$_kdialog_select_path" ] && return $false eval "$2=\"\$_kdialog_select_path\"" return $true } dialog_retry() { kdialog_run 'Error' \ --yes-label 'Retry' --no-label 'Ignore' --cancel-label 'Abort' \ --warningyesnocancel "$1" 2>&1 case $? in 0) dialog_retry_choice='retry' ;; 1) dialog_retry_choice='ignore' ;; *) dialog_retry_choice='abort' ;; esac } ;; #----------------------------------------------------------------------------------------# Xdialog) # Helper functions # Run Xdialog # Usage: Xdialog_run <title-prefix> <dialog-type> [<args>...] Xdialog_run() { _Xdialog_run_t="$1" ; shift [ -z "$_Xdialog_run_t" ] || _Xdialog_run_t="$_Xdialog_run_t - " Xdialog --left --title "$_Xdialog_run_t$_dialog_title" "$@" } # Dialog abstraction # Create the main progress window. # Usage: dialog_create dialog_create() { _Xdialog_width='WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW' pipe_create Xdialog --left --title "$_dialog_title" --gauge "$_Xdialog_width" 0 0 } # Destroy the main progress window. # Usage: dialog_destroy dialog_destroy() { pipe_destroy } # Show an error dialog. # Usage: dialog_error <message> dialog_error() { dialog_message "$1" # no dedicated error box for Xdialog } # Show a message box. # Usage: dialog_message <message> dialog_message() { Xdialog_run 'Status' --msgbox "$1" 0 0 } # Ask a yes/no question. # Usage: dialog_ask <question> dialog_ask() { Xdialog_run 'Confirm' --yesno "$1" 0 0 } # 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 <text> dialog_set_text() { pipe_write 'XXX' pipe_write "$1" pipe_write 'XXX' } # Set if the progress bar should continuously animate instead of showing the value. # Usage: dialog_set_pulsate <enable> dialog_set_pulsate() { true # Pulsate is not supported by Xdialog } # Set the current progress value. # Usage: dialog_set_value <percentage> dialog_set_value() { pipe_write "$1" } # Select an entry in a list. # Usage dialog_select_entry <var> <label> <tag1> <item1> [ <tag2> < item2> ... ] dialog_select_entry() { _Xdialog_select_entry_v="$1" ; shift _Xdialog_select_entry_t="$1" ; shift _Xdialog_select_entry_r="$( Xdialog_run 'Select path' \ --menubox "$_Xdialog_select_entry_t" 20 80 10 "$@" 2>&1 )" [ -z "$_Xdialog_select_entry_r" ] && return $false eval "$_Xdialog_select_entry_v=\"\$_Xdialog_select_entry_r\"" return $true } # dialog_select_path does not support the --any flag dialog_select_path_any=0 # Let the user select a path. # Usage: dialog_select_path (--file|--dir|--any) <result-var> <label> # Any is only supported if $dialog_select_path_any is 1. dialog_select_path() { case "$1" in --any) die 'not implemented' ;; --file) _Xdialog_select_path_f=--fselect ;; --dir) _Xdialog_select_path_f=--dselect ;; esac _Xdialog_select_path="$( Xdialog_run "$3" $_Xdialog_select_path_f "$HOME" 0 0 2>&1 )" [ -z "$_Xdialog_select_path" ] && return $false eval "$2=\"\$_Xdialog_select_path\"" return $true } dialog_retry() { Xdialog_run 'Error' --ok-label='Retry' --cancel-label='Ignore' --yesno "$1" 0 0 case $? in 0) dialog_retry_choice='retry' ;; 1) dialog_retry_choice='ignore' ;; *) dialog_retry_choice='abort' ;; esac } ;; #----------------------------------------------------------------------------------------# 0) # command-line # Dialog abstraction # Create the main progress window. # Usage: dialog_create dialog_create() { true } # Destroy the main progress window. # Usage: dialog_destroy dialog_destroy() { true } # Show an error dialog. # Usage: dialog_error <message> dialog_error() { true # error messages are always printed to stdout } # Show a message box. # Usage: dialog_message <message> dialog_message() { true } # Ask a yes/no question. # Usage: dialog_ask <question> dialog_ask() { die 'unimplemented' } # Has the user quested to cancel the operation? # Usage: dialog_cancelled && print "cancelled" dialog_cancelled() { false # never cancelled SIGINT is not trapped } # Set the status text. # Usage: dialog_set_text <text> dialog_set_text() { true } # Set if the progress bar should continuously animate instead of showing the value. # Usage: dialog_set_pulsate <enable> dialog_set_pulsate() { true } # Set the current progress value. # Usage: dialog_set_value <percentage> dialog_set_value() { true } # Select an entry in a list. # Usage dialog_select_entry <var> <label> <tag1> <item1> [ <tag2> < item2> ... ] dialog_select_entry() { _cli_select_entry_var="$1" ; shift # Print a list for the user to select from print "$1:" ; shift _cli_select_entry_min=$1 _cli_select_entry_max=$1 _cli_select_entry_f=' [default]' while [ $# -gt 0 ] ; do _cli_select_entry_i=$1 ; shift _cli_select_entry_t="$1" ; shift if [ $_cli_select_entry_i -lt $_cli_select_entry_min ] ; then _cli_select_entry_min=$_cli_select_entry_i fi if [ $_cli_select_entry_i -gt $_cli_select_entry_max ] ; then _cli_select_entry_max=$_cli_select_entry_i fi printf ' %d) %s%s\n' $_cli_select_entry_i \ "$_cli_select_entry_t" "$_cli_select_entry_f" _cli_select_entry_f='' done # Read a number (or empty string for the first entry) while true ; do puts '> #' read -r _cli_select_entry_r [ -z "$_cli_select_entry_r" ] && _cli_select_entry_r=1 case "$_cli_select_entry_r" in 'quit') ;; 'q') ;; 'exit') ;; 'abort') ;; *) if [ ! "$_cli_select_entry_r" -lt $_cli_select_entry_min ] 2> /dev/null \ && [ ! "$_cli_select_entry_r" -gt $_cli_select_entry_max ] 2> /dev/null then eval "$_cli_select_entry_var=\"\$_cli_select_entry_r\"" return $true else printf "Please enter a number between %d and %d.\n" \ $_cli_select_entry_min $_cli_select_entry_max continue fi esac die done return $true } # dialog_select_path supports the --any flag dialog_select_path_any=1 # Let the user select a path. # Usage: dialog_select_path (--file|--dir|--any) <result-var> <label> # Any is only supported if $dialog_select_path_any is 1. dialog_select_path() { _cli_select_path_var="$2" print "$3:" ; shift puts '> ' read -r _cli_select_path_r [ -z "$_cli_select_path_r" ] && return $false eval "$_cli_select_path_var=\"\$_cli_select_path_r\"" return $true } dialog_retry() { printf '\n%s\n' "${red}Error:${reset} $1" while true ; do print "Abort / [Retry] / Ignore" puts '> ' read -r _cli_dialog_retry_r case "$_cli_dialog_retry_r" in a|A|abort|Abort|ABORT) dialog_retry_choice='abort' ; return ;; ''|r|R|retry|Retry|RETRY) dialog_retry_choice='retry' ; return ;; i|I|ignore|Ignore|IGNORE) dialog_retry_choice='ignore' ; return ;; esac done } esac ########################################################################################## # Common user interface implementation # Ask the user if the setup should really be cancelled. handle_cancel() { _handle_cancel_message="Are you sure you want to exit the Arx Fatalis data installer?" if [ $installed_stuff = 1 ] ; then _handle_cancel_message="$_handle_cancel_message Already installed files will not be removed!" fi if [ $installed_stuff = 1 ] || [ $selected_stuff = 1 ] ; then dialog_ask "$_handle_cancel_message" || return $true fi print 'Aborted by user' && die } # Update the status. # Usage: status (<percent>|--temp) [<message>] _status_text="Initializing..." _status_cur='' _status_pulsate=default status() { if [ "$1" = '--temp' ] ; then _status_value=0 _status_temp=1 else _status_value=$1 _status_temp=0 fi _status_new="${2:-$_status_text}" # Handle the cancel and close buttons if dialog_cancelled ; then handle_cancel dialog_create || die "Could not re-create progress window." _status_cur='' _status_pulsate=default fi # Update the progress text if one was provided if [ ! "$_status_cur" = "$_status_new" ] ; then _status_cur="$_status_new" print "$_status_new" dialog_set_text "$_status_new" fi [ $_status_temp = 0 ] && _status_text="$_status_new" # Set the maximum progress value if [ $_status_value = 0 ] ; then _new_status_pulsate=1 ; else _new_status_pulsate=0 ; fi if [ ! "$_status_pulsate" = $_new_status_pulsate ] ; then _status_pulsate="$_new_status_pulsate" dialog_set_pulsate $_status_pulsate fi # Update the progress value [ $_status_pulsate = 0 ] && dialog_set_value $_status_value } # Print an error message and show an error dialog if we have a GUI/ # Usage: error <message> error() { print "${dim_red}$1${reset}" dialog_error "$1" } # Let the user select an item from a list or enter a custom one. # In batch mode, select the first one if --first is given, die otherwise. # Usage: user_select_entry (--existing|--writable) (--any|--file|--dir) \ # <list> <result-var> <desc> <desc-color> <list-color> <verb> user_select_entry() { _user_select_entry_access="$1" _user_select_entry_t="$2" _user_select_entry_lname="$3" eval "_user_select_entry_list=\"\$$_user_select_entry_lname\"" _user_select_entry_var="$4" _user_select_entry_desc="$5" _user_select_entry_color1="$6" _user_select_entry_color2="$7" _user_select_entry_verb="$8" # Select the first element if in batch mode eval "_user_select_entry_current=\"\$$_user_select_entry_var\"" if [ $batch = 1 ] || [ -n "$_user_select_entry_current" ] ; then _user_select_entry_='' eval "set -- $_user_select_entry_list" for _user_select_entry ; do _user_select_entry_="$_user_select_entry" break done [ -z "$_user_select_entry_" ] && die "Missing $_user_select_entry_desc!" eval "$_user_select_entry_var=\"\$_user_select_entry_\"" return $true fi if [ $gui = 0 ] then print else status --temp "Select a ${_user_select_entry_desc}" fi _user_select_entry_i=1 _user_select_entry_nolist=0 if [ -z "$_user_select_entry_list" ] \ && [ ! $_user_select_entry_t = --any ] \ && [ ! $dialog_select_path_any = 1 ] ; then # No entries detected - directly prompt the user _user_select_entry_num=1 _user_select_entry_nolist=1 else _user_select_entry_tlist='' # Format the list entries in a user friendly way eval "set -- $_user_select_entry_list" for _user_select_entry ; do # Use 'command (file)' if comment available or 'file' otherwise _user_select_entry_comment="$( list_comment "$_user_select_entry_lname" "$((_user_select_entry_i - 1))" )" if [ -z "$_user_select_entry_comment" ] ; then _user_select_entry_label="$_user_select_entry_color2$_user_select_entry$reset" else _user_select_entry_label="$_user_select_entry_color2$_user_select_entry_comment$reset" _user_select_entry_label="$_user_select_entry_label: $_user_select_entry" fi # Add the tag and label to the arguments list_append _user_select_entry_tlist $_user_select_entry_i list_append _user_select_entry_tlist "$_user_select_entry_label" # Remember the file for the tag eval "_user_select_entry_$_user_select_entry_i=\"\$_user_select_entry\"" _user_select_entry_i=$((_user_select_entry_i + 1)) done # Add entries for custom files/diectories if [ $_user_select_entry_t = --any ] && [ $dialog_select_path_any = 1 ] ; then list_append _user_select_entry_tlist $_user_select_entry_i list_append _user_select_entry_tlist "Select file or directory to $_user_select_entry_verb.." else _user_select_entry_ii=$_user_select_entry_i if [ $_user_select_entry_t = --any ] || [ $_user_select_entry_t = --file ] ; then list_append _user_select_entry_tlist $_user_select_entry_ii list_append _user_select_entry_tlist "Select file to $_user_select_entry_verb..." _user_select_entry_ii=$((_user_select_entry_ii + 1)) fi if [ $_user_select_entry_t = --any ] || [ $_user_select_entry_t = --dir ] ; then list_append _user_select_entry_tlist $_user_select_entry_ii list_append _user_select_entry_tlist "Select directory to $_user_select_entry_verb..." _user_select_entry_ii=$((_user_select_entry_ii + 1)) fi fi fi # Loop until we have selected a value while true ; do if [ $_user_select_entry_nolist = 0 ] ; then # Ask the user to select an entry eval "set -- $_user_select_entry_tlist" while true ; do if dialog_select_entry _user_select_entry_num \ "${_user_select_entry_color1}Select a ${_user_select_entry_desc}${reset}" "$@" then break else handle_cancel fi done fi if [ $_user_select_entry_num -lt $_user_select_entry_i ] ; then # User selected an entry -- return that eval "$_user_select_entry_var=\"\$_user_select_entry_$_user_select_entry_num\"" selected_stuff=1 return $true fi # Loop until we have selected a path of the correct type while true ; do # Adjust type based on user selection if [ $_user_select_entry_t = --any ] && [ ! $dialog_select_path_any = 1 ] ; then if [ $_user_select_entry_num = $_user_select_entry_i ] then _user_select_entry_type=--file else _user_select_entry_type=--dir fi else _user_select_entry_type=$_user_select_entry_t fi _user_select_entry_c="Select a custom source " if ! dialog_select_path "$_user_select_entry_type" _user_select_entry_path \ "${_user_select_entry_color1}Choose a custom ${_user_select_entry_desc}${reset}" \ "$@" ; then # User cancelled the dialog - return to the main selection, unless there is none if [ $_user_select_entry_nolist = 1 ] ; then handle_cancel continue # let the user try again fi break # return to list selection fi _user_select_entry_path="$(abspath "$_user_select_entry_path")" # The user selected a path, now check if it meets our criteria. case "$_user_select_entry_access" in --existing) if [ ! -e "$_user_select_entry_path" ] ; then error "$_user_select_entry_path does not exist!" continue # let the user try again fi ;; --writable) if ! is_writable "$_user_select_entry_path" ; then error "$_user_select_entry_path is not writable!" continue # let the user try again fi ;; esac case "$_user_select_entry_t" in --any) ;; # anything goes --file) if [ -e "$_user_select_entry_path" ] && [ -d "$_user_select_entry_path" ] ; then error "$_user_select_entry_path is is a directory, but we need a file!" continue fi ;; --dir) if [ -e "$_user_select_entry_path" ] && [ ! -d "$_user_select_entry_path" ] ; then error "$_user_select_entry_path is is a file, but we need a directory!" continue fi ;; esac # Everything went better than expected - save the result eval "$_user_select_entry_var=\"\$_user_select_entry_path\"" selected_stuff=1 return $true done done } # Show error message and close windows on exit ui_cleanup() { [ -z "$1" ] || dialog_error "$1" dialog_destroy } on_exit ui_cleanup # Initialize the UI dialog_create || die " Could not create main window. You could try the --cli option. " [ $gui = 0 ] || status --temp "$_status_text" ########################################################################################## # Autodetect source file/dir sourcefiles='' # List of source file/directory candidates # Add a source file or directory to the list of candidates. # Usage: found_source_file <file> [comment] # Return: $true if more source files should be probed, $false otherwise. found_source_file() { set_append sourcefiles "$(canonicalize "$1")" "$2" if [ $batch = 1 ] ; then return $true ; else return $false ; fi } # Get a subsection in a .vdf file # Usage: cat config | steam_get_section <section> steam_get_section() { # Abuse indentation to find the section end grep -Pzoi "\\n(\\s*)\"$1\"\\n\\s+\\{(\\n.*)*?\\n\\1}" } # Get a subsection in a .vdf file - and everything after it # Usage: cat config | steam_get_section_tail <section> steam_get_section_tail() { # Abuse indentation to find the section end grep -Pzoi "\\n\\s*\"$1\"\\n\\s+\\{(\\n.*)*" } # Remove backtick escapes # Usage: echo <escaped_string> | unescape steam_unescape() { sed 's/\\\(.\)/\1/g' } # Get an entry in a .vdf file, or the first one if there are multiple # Usage: cat config | steam_get_entry <key> steam_get_entry() { grep -Pi "^\\s*\"$1\"\s*\".*\"\\s*$" \ | head -1 \ | sed 's/^[^"]*"[^"]*"[^"]*"\(.*\)"[^"]*$/\1/' \ | steam_unescape } # Get the app information section for an appid # Usage: steam_get_appinfo <config_file> <appid> steam_get_appinfo() { _steam_config="$1" _steam_appid="$2" _steam_appinfo="$( steam_get_section 'Software' < "$_steam_config" \ | steam_get_section 'Valve' \ | steam_get_section 'Steam' \ | steam_get_section 'apps' \ | steam_get_section "$_steam_appid" )" # Fallback if the tree structure changed [ -z "$_steam_appinfo" ] && _steam_appinfo="$( steam_get_section "$_steam_appid" < "$_steam_config" )" # Fallback if the indentation changed [ -z "$_steam_appinfo" ] && _steam_appinfo="$( steam_get_section_tail "$_steam_appid" < "$_steam_config" )" print "$_steam_appinfo" } # Get a config entry for an appid # Usage: steam_get_app_config <config_file> <appid> steam_get_installdir() { steam_get_appinfo "$1" "$2" | steam_get_entry 'installdir' } # Find possible source directories from a Steam library # Usage: probe_steam_library <steamlibrary> # Return: $true if more source files should be probed, $false otherwise. probe_steam_library() { _steam_library="$1" for _steam_appdir in "$_steam_library/SteamApps" "$_steam_library/steamapps" ; do _steam_installdir="$_steam_appdir/common/$steam_install_dir" if [ -d "$_steam_installdir" ] ; then found_source_file "$_steam_installdir" "Steam" && return $true fi done return $false } # Find possible source directories from a Steam installation # Usage: probe_steam_root <steamroot> # Return: $true if more source files should be probed, $false otherwise. probe_steam_root() { _steam_root="$1" _steam_config="$_steam_root/config/config.vdf" if [ -f "$_steam_config" ] ; then # Strategy 1: Read install directory from Steam config file _steam_installdir="$(steam_get_installdir "$_steam_config" "$steam_appid")" if [ -d "$_steam_installdir" ] ; then found_source_file "$_steam_installdir" "Steam" && return $true fi # Strategy 2: Look for known install directory in steam libraries _steam_libraries="$( sed -n '/BaseInstallFolder/s/^[^\"]*\"[^\"]*\"[^\"]*\"\([^\"]*\)\".*/\1/p' \ < "$_steam_config" \ | sed 's:[^a-zA-Z0-9/_.\n]:\\&:g' \ | lines_to_list )" _steam_library_call='probe_steam_library "$_steam_library" && return $true' eval "for _steam_library in $_steam_libraries ; do $_steam_library_call ; done" fi # Strategy 3: Look for known install directory in the default library probe_steam_library "$_steam_root" && return $true return $false } # Find possible source directories from Steam # Usage: probe_steam # Return: $true if more source files should be probed, $false otherwise. probe_steam() { _steam_roots='' for _steam_root in "$HOME/.steam/root" "$HOME/.steam/steam" "$data_home/Steam" ; do [ -d "$_steam_root" ] || continue set_append _steam_roots "$(canonicalize "$_steam_root")" done _steam_root_call='probe_steam_root "$_steam_root" && return $true' [ -z "$_steam_roots" ] && return $false eval "for _steam_root in $_steam_roots ; do $_steam_root_call ; done" return $false } # Find a match for a case insensitive path containing arx.exe # Usage: probe_wine_path <baseprefix> <ipath> probe_wine_path() { [ -d "$1" ] || return $false if [ -z "$2" ] ; then if icontains "$1" 'arx.exe' ; then found_source_file "$1" "Wine" fi return $false fi _wine_path_dir="${2%%\\*}" if [ "$_wine_path_dir" = "$2" ] then _wine_path='' else _wine_path="${2#*\\}" fi _wine_paths="$( find "$1/" -mindepth 1 -maxdepth 1 -iname "$(escape "$_wine_path_dir")" -print \ | lines_to_list )" [ -z "$_wine_paths" ] && return $false _wine_path_call='probe_wine_path "$_wine_path_prefix" "$_wine_path" && return $true' eval "for _wine_path_prefix in $_wine_paths ; do $_wine_path_call ; done" return $false } # Find possible source directories from a registry key. # Usage: probe_wine_registry <key> <variable> # Return: $true if more source files should be probed, $false otherwise. probe_wine_registry() { # This is intentionally implemented without calling wine as we don't want to # modify the source. # Get candidate paths from the registry file _wine_pattern='Software\\\\(Wow6432Node\\\\)?' _wine_pattern="$_wine_pattern$(print "$1" | sed 's/\\/\\\\/g' | escape_pipe '()|')" _wine_paths="$( # The --after is just an arbitrary limit # We verify the paths by checking for arx.exe, but for efficiency we still want # to avoid false positives. cat "$_wine_prefix"/*.reg \ | grep -iPA 20 "^\\[$_wine_pattern\\]" 2> /dev/null \ | grep -iP "^\"?$2\"?=" 2> /dev/null \ | sed 's/^[^=]*="\([^"]*\)".*$/\1/;s/\\\(.\)/\1/g' \ | lines_to_list )" # For each candidate, find a case-insensitive match and check if it contains arx.exe eval "set -- $_wine_paths" for _wine_path ; do probe_wine_path "$_wine_prefix/dosdevices" "$_wine_path" && return $true done return $false } # Find possible source directories from uninstall registry entries. # Usage: probe_wine_uninstall_info <id> # Return: $true if more source files should be probed, $false otherwise. probe_wine_uninstall_info() { probe_wine_registry "Microsoft\\Windows\\CurrentVersion\\Uninstall\\$1" \ 'InstallLocation' } # Find possible source directories in a WINEPREFIX. # Usage: probe_wineprefix <wineprefix> # Return: $true if more source files should be probed, $false otherwise. probe_wineprefix() { [ -d "$1" ] || return $false [ -e "$1/system.reg" ] || [ -e "$1/user.reg" ] || return $false _wine_prefix="$1" # Normal install probe_wine_registry 'Arkane Studios\Installed Apps\Arx Fatalis' 'Folder' && return $true # GOG version probe_wine_registry 'GOG.com\GOGARXFATALIS' 'PATH' && return $true # Probe uninstall entries - combined for efficiency _wine_uninstall='Microsoft\Windows\CurrentVersion\Uninstall\' # Steam _wine_steam="Steam App $steam_appid" # Original game _wine_orig='{96443F45-13E2-11D6-AC87-00D0B7A9E540}' # 1.21 patch _wine_patch='{171251E0-4EED-4EA1-A46D-3213A226F2B3}_is1' probe_wine_registry "$_wine_uninstall($_wine_steam|$_wine_orig|$_wine_patch)" \ 'InstallLocation' && return $true } # Check a mounte point if it's an Arx Fatalis cd # Usage: probe_cd <mountpoint> <fstype> # Return: $true if more source files should be probed, $false otherwise probe_cd() { node="$1" mountpoint="$2" fstype="$3" case "$fstype" in cd9660) ;; iso9660) ;; udf) ;; *fuseiso) ;; *) return $true esac if [ -d "$mountpoint/bin" ] && icontains "$mountpoint/bin" 'arx.ttf' 2> /dev/null ; then found_source_file "$mountpoint" "CDROM at $node" && return $true fi } # Find mounted Arx Fatalis CDs # Usage: probe_cdrom # Return: $true if more source files should be probed, $false otherwise probe_cdrom() { _probe_cdrom_mountpoints="$( [ -f /proc/mounts ] && lines_to_list < /proc/mounts # Linux - use /proc/mounts mount -p 2> /dev/null | lines_to_list # Hope that mount has a -p option )" eval "set -- $_probe_cdrom_mountpoints" for _probe_cdrom_line ; do eval "probe_cd $(escape "$_probe_cdrom_line" ' ' | sed 's/\\\\\040/\\ /g')" # space-separated eval "probe_cd $(print "$_probe_cdrom_line" | tr '\t' '\n' | lines_to_list)" # tab-separated done } # Find possible source files/directories # Usage: probe_source_files # If sourcefile is already set, uses that. probe_source_files() { if [ -n "$sourcefile" ] ; then # Trust the user found_source_file "$sourcefile" && return $true # But also allow to refile the dir in non-batch mode if it is a wineprefix [ -d "$sourcefile" ] && probe_wineprefix "$sourcefile" [ "$sourcefiles" = "$sourcefile" ] || sourcefile='' return $true fi status 5 "Searching for source files..." # Find by filename probe_files found_source_file "$gog_names" 'GOG.com setup' probe_files found_source_file "$demo_names" 'Demo' probe_steam && return $true # Find an Arx Fatalis installation in $WINEPREFIX [ -n "${WINEPREFIX-}" ] && probe_wineprefix "$WINEPREFIX" && return $true # Find an Arx Fatalis installation in ~/.wine [ ! "${WINEPREFIX-}" = "$HOME/.wine" ] && probe_wineprefix "$HOME/.wine" && return $true # Find an Arx Fatalis cdrom probe_cdrom && return $true # Just in case it will ever be installable under non-Windows systems steam_source_path="$HOME/.steam/root/SteamApps/common/Arx Fatalis" [ -d "$steam_source_path" ] && found_source_file "$steam_source_path" 'Steam' } ########################################################################################## # Autodetect destination dir datadirs='' # List of destination data directory candidate # Add a destination data directory to the list of candidates. # Usage: found_data_dir <dir> [comment] # Return: $true if more data directories should be probed, $false otherwise. found_data_dir() { set_append datadirs "$(abspath "$1")" "$2" if [ $batch = 1 ] ; then return $true ; else return $false ; fi } # Add a destination data directory to the list of candidates if it is writable. # In verify mode, also existing read-only directories are added. # Usage: probe_data_dir <must-exists> <dir> [comment] # Return: $true if more data directories should be probed, $false otherwise. probe_data_dir() { [ "$1" = 1 ] && [ ! -e "$2" ] && return $false [ $install = 0 ] && [ ! -e "$2" ] && return $false if [ $install = 1 ] || [ $patch = 1 ] ; then is_writable "$2" || return $false ; fi found_data_dir "$2" "$3" } # Find possible destination data directories # Usage: probe_data_dirs # If datadir is already set, uses that. probe_data_dirs() { if [ -n "$datadir" ] ; then found_data_dir "$datadir" return $true fi status 10 "Searching for destination directories..." for _probe_data_dirs_existing in 1 0 ; do # Try paths supplied by wrapper scripts if [ -n "$data_path" ] ; then eval "set -- $(to_list "$data_path")" for _probe_data_dirs_path ; do if [ -f "$_probe_data_dirs_path/data" ] ; then eval "set -- $(lines_to_list < "$_probe_data_dirs_path/data")" for _probe_data_dirs_line ; do case "$_probe_data_dirs_line" in /*) ;; *) _probe_data_dirs_line="$( canonicalize "$_probe_data_dirs_path/$_probe_data_dirs_line" )" esac probe_data_dir $_probe_data_dirs_existing "$_probe_data_dirs_line" \ "portable" && return $true done elif [ -d "$_probe_data_dirs_path/data" ] ; then probe_data_dir $_probe_data_dirs_existing "$_probe_data_dirs_path/data" \ "portable" && return $true else probe_data_dir $_probe_data_dirs_existing "$_probe_data_dirs_path" "portable" \ && return $true fi done fi # Try system paths eval "set -- $(to_list "$data_dirs")" for _probe_data_dirs_prefix in "$@" ; do eval "set -- $(to_list "$data_dir_suffixes")" for _probe_data_dirs_suffix ; do _probe_data_dirs_force=$_probe_data_dirs_existing [ -e "$_probe_data_dirs_prefix/$_probe_data_dirs_suffix" ] && \ _probe_data_dirs_force=0 probe_data_dir $_probe_data_dirs_force \ "$_probe_data_dirs_prefix/$_probe_data_dirs_suffix/data" "system" \ && return $true probe_data_dir $_probe_data_dirs_existing \ "$_probe_data_dirs_prefix/$_probe_data_dirs_suffix" "system" \ && return $true done done # Try user paths if [ $is_root = 0 ] ; then eval "set -- $(to_list "$user_dir_suffixes")" for _probe_data_dirs_suffix ; do _probe_data_dirs_force=$_probe_data_dirs_existing [ -e "$data_home/$_probe_data_dirs_suffix" ] && \ _probe_data_dirs_force=0 probe_data_dir $_probe_data_dirs_force \ "$data_home/$_probe_data_dirs_suffix/data" "user" \ && return $true [ $install = 0 ] && probe_data_dir $_probe_data_dirs_existing \ "$data_home/$_probe_data_dirs_suffix" "user" \ && return $true done fi done } ########################################################################################## # Extract helpers and other utility abstractions # Calculate the MD5 checksum of a file. # Usage: checksum <result-var> <file> checksum() { if have md5sum ; then _checksum_result="$(md5sum -b "$2" | sed 's/ .*//g')" eval "$1=\"\$_checksum_result\"" return $true fi if have md5 ; then _checksum_result="$(md5 -q "$2")" eval "$1=\"\$_checksum_result\"" return $true fi die "You need either md5sum or md5." } have_run() { eval "_have_run_p=\"\$${1}_reqs\"" eval "for _have_run_program in $_have_run_p ; do have \$_have_run_program && return $true ; done" return $false } # Extract an archive file to the current directory. # Usage: extract <file> <types>... have_extract() { have_run "extract_$1" } extract() { _extract_file="$1" ; shift while true ; do _extract_missing='' for _extract_type ; do if "have_extract_${_extract_type}" ; then "extract_${_extract_type}" "$_extract_file" && return "$true" else list_merge _extract_missing extract_${_extract_type}_reqs fi done _extract_msg="${white}Could not extract $(basename "$_extract_file")${reset}" [ -z "$_extract_missing" ] \ || _extract_msg="$_extract_msg Please install one or more of the following: $(print_help_list " - $dim_pink" _extract_missing)" [ $batch = 1 ] && die "Error: $_extract_msg" dialog_retry "$_extract_msg" case $dialog_retry_choice in abort) die "Error extracting files" ;; retry) continue ;; ignore) return $true ;; esac done } # Extract a .zip file to the current directory. # Usage: extract_zip <zipfile> have_extract_zip() { have_extract zip ; } extract_zip() { if have bsdtar ; then printf 'Extracting %s using bsdtar\n' "$1" bsdtar xvf "$1" return $? fi if have unzip ; then puts 'unzip: ' unzip "$1" return $? fi for _extract_zip_sz in 7za 7z ; do if have $_extract_zip_sz ; then $_extract_zip_sz x "$1" return $? fi done die "no program to extract $1" } # Mount a .iso file or CDROM using fuseiso if available or normal mount if root. # Usage: mount_cdrom <cdromfile> <mountpoint> have_mount_cdrom() { have fuseiso && have fusermount && return $true have mount && have umount && [ $is_root = 1 ] && return $true return $false } mount_cdrom() { if have fuseiso && have fusermount && fuseiso "$1" "$2" ; then printf 'Mounted %s at %s using fuseiso\n' "$1" "$2" return $true fi if have mount && have umount && [ $is_root = 1 ] && mount -o loop,ro "$1" "$2" ; then printf 'Mounted %s at %s\n' "$1" "$2" return $true fi die "no program to extract $1" } # Unmount a CDROM that was mounted using mount_cdrom. # Usage: unmount_cdrom <mountpoint> unmount_cdrom() { have fusermount && fusermount -u "$1" > /dev/null 2>&1 have umount && [ $is_root = 1 ] && umount "$1" > /dev/null 2>&1 } # isoinfo wrapper to extract all files to the current directory extract_isoinfo() { _extract_isoinfo_file="$1" # Get a list of all files in the ISO image _extract_isoinfo_files="$( isoinfo -i "$_extract_isoinfo_file" -J -f | grep ';1$' | lines_to_list )" [ -z "$_extract_isoinfo_files" ] && return $false eval "set -- $_extract_isoinfo_files" for _extract_isoinfo_e ; do # Remove leading / and trailing ;1 from filenames _extract_isoinfo_f="$(print "$_extract_isoinfo_e" | sed 's:^/*::;s:;1$::')" [ -z "$_extract_isoinfo_f" ] && continue printf ' - %s\n' "$_extract_isoinfo_f" # Create subdirectories as needed _extract_isoinfo_d="$(dirname "$_extract_isoinfo_f")" [ -z "$_extract_isoinfo_d" ] || mkdir -p "$_extract_isoinfo_d" || return $false # Extract the file isoinfo -i "$_extract_isoinfo_file" -J -x "$_extract_isoinfo_e" \ > "$_extract_isoinfo_f" || return $false # Don't rely on isoinfo setting a non-zero return code, check that we got something [ -s "$_extract_isoinfo_f" ] || return $false done return $true } # Extract a .iso file or CDROM to the current directory. # Usage: extract_iso <cdromfile> have_extract_iso() { have_extract iso ; } extract_iso() { if have isoinfo ; then printf 'Extracting %s using isoinfo\n' "$1" extract_isoinfo "$1" return $? fi ret=$false if have bsdtar ; then printf 'Extracting %s using bsdtar\n' "$1" bsdtar xvf "$1" ret="$?" # Older versions of bsdtar don't always get the names right - fix them _extrac_cdrom_f="$(find "$PWD" -depth -iname '*;1' | lines_to_list)" if [ -n "$_extrac_cdrom_f" ] ; then eval "for _extract_iso_f in $_extrac_cdrom_f ; do mv -f \"\$_extract_iso_f\" \"\$(print \"\$_extract_iso_f\" | sed 's:;1$::')\" ; done" fi elif have 7z ; then 7z x -tiso "$1" ret="$?" fi # For some iso files bsdtar just does nothing - at least let the user know if [ ! -d "$PWD/bin" ] || ! icontains "$PWD/bin" 'arx.ttf' ; then _extract_iso_err="${yellow}It looks like bsdtar/p7zip didn't do what it was supposed to - this will likely fail!${reset} You might have better luck with ${dim_pink}isoinfo${reset} or ${dim_pink}fuseiso${reset}, or by manually mounting the CD/ISO." printf '%s\n' "$_extract_iso_err" [ $batch = 0 ] && dialog_message "$_extract_iso_err" fi return $ret } # Extract a microsoft .cab or .exe file to the current directory. # Usage: extract_ms_cab <cabfile> extract_cab_check_bsdtar() { if have bsdtar ; then case "$(bsdtar --version)" in # These versions have bugs that cause corrupted files '') ;; *'libarchive 1.'*) ;; *'libarchive 2.'*) ;; *'libarchive 3.0') ;; *'libarchive 3.0.'*) ;; # Newer versions should work fine *) return $true esac fi return $false } have_extract_ms_cab() { extract_cab_check_bsdtar && return $true have cabextract || have 7za || have 7z } extract_ms_cab() { if extract_cab_check_bsdtar ; then printf 'Extracting %s using bsdtar\n' "$1" bsdtar xvf "$1" return $? fi if have cabextract ; then puts 'cabextract: ' cabextract "$1" return $? fi for _extract_ms_cab_sz in 7za 7z ; do if have $_extract_ms_cab_sz ; then $_extract_ms_cab_sz x "$1" return $? fi done die "no program to extract $1" } # Extract an InstallShield .cab or .exe file to the current directory. # Usage: extract_installshield <cabfile> have_extract_installshield() { have_extract installshield ; } extract_installshield() { if have unshield ; then puts 'unshield: ' unshield x "$1" return $? fi die "no program to extract $1" } # Extract a RAR .rar or .exe file to the current directory. # Usage: extract_rar <rarfile> have_extract_rar() { have_extract rar ; } extract_rar() { if have unrar ; then puts 'unrar: ' unrar x -y -o "$1" return $? fi die "no program to extract $1" } # Extract an ACE .ace or .exe file to the current directory. # Usage: extract_ace <acefile> have_extract_ace() { have_extract ace ; } extract_ace() { if have unace ; then puts 'unace ' unace x -y -o "$1" return $? fi die "no program to extract $1" } # Extract an Inno Setup .exe file to the current directory. # Usage: extract_innosetup <exefile> have_extract_innosetup() { have innoextract ; } innosetup_language='' extract_innosetup() { if have innoextract ; then puts 'innoextract: ' if [ -z "$innosetup_language" ] then innoextract --color=off "$1" ; return $? else innoextract --color=off --language="$innosetup_language" "$1" ; return $? fi fi die "no program to extract $1" } # Extract all .cab files in a directory. # Usage: extract_cab_files <sourcedir> <destdir> extract_cab_files() { _extract_cab_files_i=0 _extract_cab_files_files="$( find "$1/" -mindepth 1 -type f -iname '*.cab' -print \ | lines_to_list )" eval "set -- $_extract_cab_files_files" for _extract_cab_files_cabfile ; do [ -z "$_extract_cab_files_cabfile" ] && continue while true ; do _extract_cab_files_cabdir="$sourcedir/cab.$_extract_cab_files_i" if [ -e "$_extract_cab_files_cabdir" ] ; then _extract_cab_files_i=$((_extract_cab_files_i + 1)) continue fi create_dir "$_extract_cab_files_cabdir" "cab #$i work" break done cd "$_extract_cab_files_cabdir" case "$(basename "$_extract_cab_files_cabfile")" in data*) extract "$_extract_cab_files_cabfile" installshield ms_cab ;; *) extract "$_extract_cab_files_cabfile" ms_cab installshield ;; esac _extract_cab_files_i=$((_extract_cab_files_i + 1)) done } # Download a file. # Usage: download_file <url> <destination> have_download() { have_run download ; } download_impl() { if have wget ; then wget -O "$2" "$1" return $? fi if have curl ; then curl --location --fail -o "$2" "$1" return $? fi if have fetch ; then fetch -o "$2" "$1" return $? fi die "no program to download $1" } download_file() { download_impl "$1" "$2" || return $false # Check that we got something useful case "$(file --dereference --brief --mime-type "$2" 2> /dev/null)" in */html*) ;; */xml*) ;; *) return $true esac rm -f "$2" return $false; } # Download a file from a list of mirrors. # Usage: download <callback> <name> <names> <urls> <destdir> download() { _download_callback="$1" _download_name="$2" _download_names="$3" _download_urls="$4" _download_dest="$5" while true ; do probe_files "$_download_callback" "$_download_names" && return $true _download_missing='' if have_download ; then eval "for _download_url in $_download_urls ; do download_file \"\$_download_url\" \"\$_download_dest\" && \$_download_callback \"\$_download_dest\" && return $true ; done" else list_merge _download_missing download_reqs fi _download_msg="${white}Could not download ${_download_name}${reset}" [ -z "$_download_missing" ] \ || _download_msg="$_download_msg Please install one of the following: $(print_help_list " - $dim_pink" _download_missing)" _download_msg="$_download_msg You can download the file manually from $(print_help_list " - $dim_blue" _download_urls) and put it in one of these locations: $(print_help_list " - $dim_white" probe_file_dirs)" [ $batch = 1 ] && die "Error: $_download_msg" dialog_retry "$_download_msg" case $dialog_retry_choice in abort) die "Error downloading files" ;; retry) continue ;; ignore) return $true ;; esac done } ########################################################################################## # Unpack source workdir='' cleanup_workdir() { [ -n "$workdir" ] && [ -e "$workdir" ] && rm -rf "$workdir" } # Create the work directory if it doesn't exist. # Also register a cleanup function to remove the work directory on exit. # Usage: create_workdir create_workdir() { [ -z "$workdir" ] || return $true selected_stuff=1 workdir="$datadir/$command-temp" cleanup_workdir on_exit cleanup_workdir create_dir "$workdir" 'work' } # Extract setup*.cab files from a source directory into $sourcedir/cab.*. # Usage: extract_source_dir <sourcedir> extract_source_dir() { extract_cab_files "$1" "$sourcedir" } # Extract a source executable into $sourcedir/exe. # Usage: extract_source_exe <sourcefile> extract_source_exe() { sourcedir_exe="$sourcedir/exe" create_dir "$sourcedir_exe" 'exe work' recurse=1 cd "$sourcedir_exe" || die case "$(basename "$1")" in arx_demo_english.exe) extract "$1" rar ace ms_cab installshield innosetup ;; # English demo arx_demo_german.exe) extract "$1" ace rar ms_cab installshield innosetup ;; # German demo arx_jpn_*.exe) extract "$1" ms_cab installshield innosetup rar ace ;; # Japanese demo setup_arx_fatalis*.exe) recurse=0 # Ignore DirectX .cab files in 1.22 GOG.com installers extract "$1" innosetup ms_cab installshield rar ace ;; # GOG.com setup *) extract "$1" innosetup ms_cab installshield rar ace ;; # GOG.com setup esac [ $recurse = 1 ] && extract_source_dir "$sourcedir_exe" } # Wrap mount_cdrom et al so we can use them with extract() extract_mount_cdrom_reqs='' list_merge extract_mount_cdrom_reqs mount_cdrom_reqs have_extract_mount_cdrom() { have_mount_cdrom ; } extract_mount_cdrom() { # Ignore the current working directory, always mount to $cdromdir if mount_cdrom "$1" "$cdromdir" ; then # Pretent the mountpoint is the original source sourcefile="$cdromdir" sourcedir_cdrom="$cdromdir" return $true else return $false fi } cdromdir='' cleanup_cdrom() { [ -n "$cdromdir" ] && [ -e "$cdromdir" ] && unmount_cdrom "$cdromdir" } # Mount a source CDROM/ISO and adjust $sourcefile or extract it into $sourcedir/cdrom. # Usage: extract_source_cdrom <sourcefile> extract_source_cdrom() { # Try to mount the cdrom to avoid unneeded copies # Keep the mount point out of $sourcedir so we don't try to mv files from it cdromdir="$workdir/cdrom" cleanup_cdrom on_exit cleanup_cdrom create_dir "$cdromdir" 'mount work' # Otherwise, extract the files from the CDROM if we have the required tools sourcedir_cdrom="$sourcedir/cdrom" create_dir "$sourcedir_cdrom" 'cdrom work' cd "$sourcedir_cdrom" || die extract "$1" mount_cdrom iso # Extract any cab files on the CDROM extract_source_dir "$sourcedir_cdrom" } # Extract a source executable into $sourcedir/zip. # Also extracts contained setup*.cab files into $sourcedir/cab.*. # Usage: extract_source_zip <sourcefile> extract_source_zip() { sourcedir_zip="$sourcedir/zip" create_dir "$sourcedir_zip" 'zip work' cd "$sourcedir_zip" || die extract "$1" zip extract_source_dir "$sourcedir_zip" } sourcedir='' # Extract the source file or directory if it hansn't been extracted already. # Usage: extract_source extract_source() { [ -z "$sourcedir" ] || return $true status --temp "${white}Extracting source...${reset}" create_workdir sourcedir="$workdir/source" create_dir "$sourcedir" 'source work' if [ -d "$sourcefile" ] ; then extract_source_dir "$sourcefile" elif [ -f "$sourcefile" ] ; then case "$sourcefile" in *.zip) extract_source_zip "$sourcefile" ;; *.exe) extract_source_exe "$sourcefile" ;; *.iso) extract_source_cdrom "$sourcefile" ;; *) case "$(file --dereference --brief --mime-type "$sourcefile" 2> /dev/null)" in application/zip) extract_source_zip "$sourcefile" ;; application/x-dosexec) extract_source_exe "$sourcefile" ;; application/x-iso9660-image) extract_source_cdrom "$sourcefile" ;; *) die "Unknown source file type: $sourcefile" esac esac else extract_source_cdrom "$sourcefile" fi print } ########################################################################################## # Detect data language find_file_impl() { _patchable="$1" # Search for both file.ext and file_default.ext _file="$(escape "$(basename "$2")")" _file_d="$(print "$_file" | sed 's/^\(.*\)\(\.[^.]*\)$/\1_default\2/')" # Prefer files from the patch - if available, they are most likely the correct ones [ "$_patchable" = 1 ] && [ -n "$patchdir" ] \ && find "$patchdir" \( -name "$_file" -o -name "$_file_d" \) -print # Find the file in the source [ -n "$sourcedir" ] && find "$sourcedir" -iname "$_file" -print [ -d "$sourcefile" ] && find "$sourcefile" -iname "$_file" -print # Find the _default.pak variant only after the .pak variant # This is required to detect the correct language from Steam installs # as the english version is always present as speech_default.pak and loc_default.pak [ -n "$sourcedir" ] && find "$sourcedir" -iname "$_file_d" -print [ -d "$sourcefile" ] && find "$sourcefile" -iname "$_file_d" -print # Also find the file if it is already in the data directory, but don't ignore case find "$datadir" -path '*-temp' -prune \( -name "$_file" -o -name "$_file_d" \) -print } # Find a file in patch, source and data directories. # Usage: find_file <is-patchable> <return-list-var> <filename/path> find_file() { eval "$2=\"\$(find_file_impl \"\$1\" \"\$3\" | lines_to_list)\"" } # Copy/move a file to the data diectory. # Usage: use_file <file> <data-path> # Action taken depends on the source directory. use_file() { _in="$1" _out="$datadir/$2" # Don't change anything on verify-only mode [ $install = 0 ] && [ $patch = 0 ] && return $true # Don't try to copy/move a file onto itself [ "$_in" = "$_out" ] && return $true # Create directories as needed _outdir="$(dirname "$_out")" create_dir "$_outdir" 'output' # Copy or move the file if [ "${_in#"$sourcefile"}" = "$_in" ] then mv -f "$_in" "$_out" || die "Could not move $_in to $_out!" else cp -f "$_in" "$_out" || die "Could not copy $_in to $_out!" fi installed_stuff=1 # Fix permissions chmod --reference="$_outdir" "$_out" > /dev/null 2>&1 chmod -x "$_out" > /dev/null 2>&1 } data_lang='' # Data language/type ID data_lang_desc='' # Friendly data language/type label # Detect the data language and type # Usage: detect_data_langauge <callback> # <callback> receives a speech.pak checksum and sets data_lang and data_lang_desc detect_data_langauge() { callback="$1" # TODO Add support for installing multiple languages at once if [ $install = 1 ] then _detect_data_langauge_status=40 else _detect_data_langauge_status=10 fi [ $gui = 0 ] || status $_detect_data_langauge_status 'Detecting data language...' puts "${white}Detecting data language..." _speech_checksums='' find_file 0 _speech_files 'speech.pak' eval "set -- $_speech_files" for _speech_file ; do checksum _speech_checksum "$_speech_file" data_lang='' "$callback" "$_speech_checksum" if [ -z "$data_lang" ] ; then list_append _speech_checksums "$_speech_checksum" else use_file "$_speech_file" 'speech.pak' break fi done if [ -z "$data_lang" ] ; then printf '\n' case "$_speech_checksums" in '') die "speech*.pak not found" ;; *) die "Unsupported data language - speech*.pak checksum: $_speech_checksums" ;; esac fi printf " ${green}%s${reset}\n\n" "${data_lang_desc}" } ########################################################################################## # Get the patch file # Warn if the user-supplied patch file has a suspicious name. # Usage: patch_check_file_name <file> <expected-name-list> patch_check_file_name() { _user_patch_name="$(basename "$1")" _patch_check_file_names="$2" if ! list_contains _patch_check_file_names "$_user_patch_name" ; then printf "${yellow}Warning: unexpected patch file name: %s\n" "$_user_patch_name" >&2 printf "Expected %s${reset}\n" "$(print_help_or _patch_check_file_names)" >&2 fi } # Find or download a patch file. # Usage: probe_patch_file_impl <callback> <name> <user-supplied-file> \ # <expected-name-list> <url-list> \ # <callback> will be called with the patch file probe_patch_file_impl() { _patch_found="$1" _patch_name="$2" _patch_file="$3" _patch_names="$4" _patch_urls="$5" if [ -n "$_patch_file" ] ; then # Check the filename of user-supplied patchse patch_check_file_name "$_patch_file" "$_patch_names" "$_patch_found" "$_patch_file" return $true fi [ $probe_patch = 0 ] && return $true # Probe local files now so we don't lie abut downloading it probe_files "$_patch_found" "$_patch_names" && return $true status --temp "${white}Downloading patch ${blue}${_patch_name}${reset}..." create_workdir download "$_patch_found" "$_patch_name" \ "$_patch_names" "$_patch_urls" "${workdir}/${_patch_name}" \ && return $true } # Callback for the japanese patch. patch_jp_found() { patchfile_jp="$(abspath "$1")" printf "Using Japanese %s patch: ${blue}%s${reset}\n" \ "$patch_jp_ver" "$patchfile_jp" return $true } # Callback for the main patch. patch_found() { patchfile="$(abspath "$1")" printf "Using %s patch: ${blue}%s${reset}\n" "$patch_ver" "$patchfile" return $true } # Find the patch file(s) for a specific language. # Usage: probe_patch_file <language> # If patchfile is already set, uses that. probe_patch_file() { _patch_file_lang='' case "$1" in 'german') _patch_file_lang='GE' ;; 'english') _patch_file_lang='EN' ;; 'spanish') _patch_file_lang='ES' ;; 'french') _patch_file_lang='FR' ;; 'italian') _patch_file_lang='IT' ;; 'russian') _patch_file_lang='RU' ;; 'japanese') _patch_jp_names='' list_append _patch_jp_names "$patch_jp_name" probe_patch_file_impl patch_jp_found "$patch_jp_name" "$patchfile_jp" \ "$_patch_jp_names" "$patch_jp_urls" ;; esac _patch_names='' list_append _patch_names "$patch_name" if [ -n "$_patch_file_lang" ] ; then list_append _patch_names "$(printf "$patch_name_localized" "$_patch_file_lang")" fi probe_patch_file_impl patch_found "$patch_name" "$patchfile" \ "$_patch_names" "$patch_urls" } patchdir='' # Directory where the patch file(s) are extracted # Extract all patch files for a specific language. # Usage: extract_patch <language> # Does nothing if the patch files are already extracted. extract_patch() { [ -z "$patchdir" ] || return $true _extract_patch_lang="$1" print # Search for and download the patch files if needed probe_patch_file "$_extract_patch_lang" status --temp "${white}Extracting patch...${reset}" create_workdir patchdir="$workdir/patch" create_dir "$patchdir" 'patch work' # Extract the main patch file if [ -n "$patchfile" ] ; then _patchdir_main="$patchdir/main" create_dir "$_patchdir_main" 'main patch work' cd "$_patchdir_main" innosetup_language="$_extract_patch_lang" extract "$patchfile" innosetup innosetup_language='' fi if [ -n "$patchfile_jp" ] ; then # Extract the Japanese patch file _patchdir_jp="$patchdir/main" create_dir "$_patchdir_jp" 'jp patch work' cd "$_patchdir_jp" extract "$patchfile_jp" ms_cab # Also extract contained files extract_cab_files "$_patchdir_jp" "$patchdir" fi print status --temp } ########################################################################################## # Copy and verify files checksum_failed=0 # Was there any mismatched checksum or missing file so far? # Handle a required file: find, compare checksum and copy/move if needed. # Usage: required_file <is-patchable> <filepath> <checksums> required_file() { _patchable="$1" _name="$2" _valid="$3" find_file 1 _files "$_name" eval "set -- $_files" _checksums='' for _file ; do checksum _checksum "$_file" if list_contains _valid "$_checksum" ; then # We found a match - use it printf ' - %s\n' "$_name" use_file "$_file" "$_name" return $true fi # Remember mismatched checksums so we can output debug info if none matched list_append _checksums "$_checksum" continue done # No matching file found! # If we didn't use the patch yet, fetch it and try again! if [ $patch = 1 ] && [ $_patchable = 1 ] && [ -z "$patchdir" ] ; then extract_patch "$data_lang" if [ -n "$patchdir" ] ; then required_file "$_patchable" "$_name" "$_valid" return $? fi fi # Let the user know that something is wrong! if [ -z "$_checksums" ] ; then printf "${red}Missing ${dim_red}%s${red}!${reset}\n" "$_name" >&2 else printf "${red}Checksum failed for ${dim_red}%s${reset}:\n" "$_name" >&2 printf " expected: ${dim_red}%s${reset}\n" "$(print_help_or _valid)" >&2 printf " actual: ${dim_red}%s${reset}\n" "$(print_help_or _checksums)" >&2 fi # Be optimistic, copy the first result even if the checksum doesn't match! # We will display an error at the end (end exit with $false), but it may still work. eval "set -- $_files" for _file ; do use_file "$_file" "$_name" break done checksum_failed=1 return $false } # Handle an optional file: find and copy/move if it exists. # Usage: optional_file <filepath> optional_file() { _name="$1" find_file 1 _files "$_name" eval "set -- $_files" for _file ; do # There is no checksum, just copy the first file printf ' - %s\n' "$_name" use_file "$_file" "$_name" break done } ########################################################################################## # Setup # Select source file / directory if [ $install = 1 ] ; then probe_source_files if [ $batch = 0 ] ; then list_append sourcefiles 'Patch existing install' '' list_append sourcefiles 'Verify existing install only' '' fi user_select_entry --existing --any sourcefiles sourcefile \ "source file or directory to install from" "$green" "$dim_green" 'install from' case "$sourcefile" in 'Patch existing install') install=0 ; patch=1 ;; 'Verify existing install only') install=0 ; patch=0 ;; *) [ -e "$sourcefile" ] || die "Missing source file: $sourcefile" esac set_append probe_file_dirs "$(dirname "$sourcefile")" fi # Select destination data directory probe_data_dirs if [ $install = 1 ] ; then verb='install to' ; access=--writable elif [ $patch = 1 ] ; then verb='patch' ; access=--existing else verb='verify' ; access=--existing fi user_select_entry $access --dir datadirs datadir \ "data directory to $verb" "$cyan" "$dim_cyan" "$verb" [ -z "$datadir" ] && die "Missing data dir." if [ $install = 1 ] ; then create_dir "$datadir" 'data' else [ -d "$datadir" ] || die "Missing data dir: $datadir" fi # Extract source files if [ $install = 1 ] ; then printf "\nInstalling Arx Fatalis data files \nfrom %s\nto %s\n\n" \ "${green}$sourcefile${reset}" "${cyan}$datadir${reset}" extract_source else printf "\nVerifying Arx Fatalis data files \nin %s\n\n" "${cyan}$datadir${reset}" fi ########################################################################################## # Required files # Detect language determine_language() { speech_checksum="$1" # speech.pak case "$speech_checksum" in '4e8f962d8204bcfd79ce6f3226d6d6de') data_lang='english' ;; '4c3fdb1f702700255924afde49081b6e') data_lang='german' ;; 'ab8a93161688d793a7c78fbefd7d133e') data_lang='german' ;; '2f88c67ae1537919e69386d27583125b') data_lang='spanish' ;; '4edf9f8c799190590b4cd52cfa5f91b1') data_lang='french' ;; '81f05dea47c52d43f01c9b44dd8fe962') data_lang='italian' ;; '677163bc319cd1e9aa1b53b5fb3e9402') data_lang='russian' ;; '5df8ba0d4ec58bd43d04307eb4c06d86') data_lang='russian' ;; '235b86700fc80b3eb86731d748013a38') data_lang='japanese' ;; '62ca7b1751c0615ee131a94f0856b389') data_lang='english-demo' ;; '09038e43508232c44537c162f9e3ecde') data_lang='french-demo' ;; 'a424fcfc46dd4f11b04030efac15a668') data_lang='german-demo' ;; 'eeacbd9a845ecc00054934e82e9d7dd3') data_lang='japanese-demo' ;; esac case "$data_lang" in 'english') data_lang_desc='English' ;; 'german') data_lang_desc='German' ;; 'spanish') data_lang_desc='Spanish' ;; 'french') data_lang_desc='French' ;; 'italian') data_lang_desc='Italian' ;; 'russian') data_lang_desc='Russian' ;; 'japanese') data_lang_desc='Japanese' ;; 'english-demo') data_lang_desc='English (demo)' ;; 'french-demo') data_lang_desc='French (demo)' ;; 'german-demo') data_lang_desc='German (demo)' ;; 'japanese-demo') data_lang_desc='Japanese (demo)' ;; esac } detect_data_langauge determine_language if [ $install = 1 ] ; then progress=50 status $progress "${white}Copying and verifying files...${reset}" case "$data_lang" in *-demo) increment=8 ;; *) increment=1 ;; esac else progress=15 status $progress "${white}Verifying files...${reset}" case "$data_lang" in *-demo) increment=14 ;; *) increment=2 ;; esac fi print " - speech.pak" # Usage: f <is-patchable> <file> <checksums>... f() { # Update progress bar progress=$((progress + increment)) status $progress # Verify & copy file required_file "$@" } # speech.pak - already copied in detect_data_langauge # loc.pak contains the localized text, so it's different for each language! case "$data_lang" in german) loc_checksum='31bc35bca48e430e108db1b8bcc2621d' ;; english) loc_checksum='a47b192493afb5794e2161a62d35b69f' ;; spanish) loc_checksum='121f99608814a2c9c5857cfadb665553' ;; french) loc_checksum='f8fc448fea12469ed94f417c313fe5ea' ;; italian) loc_checksum='a9e162f2916f5737a95bd8c5bd8a979e' ;; russian) loc_checksum='a131bf2398ee70a9c22a2bbffd9d0d99' ;; japanese) loc_checksum='9dcb0f5d7a517be4f1d9190419900892' ;; english-demo) loc_checksum='2ae16d3925c597dca70f960f175def3a' ;; french-demo) loc_checksum='4a8ac68341d4758a32d9cd04955b115e' ;; german-demo) loc_checksum='87accec0658aa109a3efa8b41aab61df' ;; japanese-demo) loc_checksum='9d84cede805b13fdf7fce856ecc15b19' ;; *) loc_checksum='' esac if [ -n "$loc_checksum" ] ; then f 1 'loc.pak' "$loc_checksum" fi # misc/arx.ttf is the same for everything except Japanese (and Russian for version 1.22) # there are also separate misc/arx_russian.ttf and misc/arx_taiwanese.ttf handled later font_checksum_default='9a95ff96795c034524ba1c2e94ea12c7' font_checksum_japanese='58eab00842d8adea8d553ae1f66b0c9b' font_checksum_russian='921561e83786efcd25f92147b60a13db' f 1 'misc/arx.ttf' "$font_checksum_default $font_checksum_japanese $font_checksum_russian" case "$data_lang" in english-demo) f 0 'data2.pak' 958b78f8f370b06d769843137138c461 f 0 'data.pak' 5d7ba6e6c79ebf7fbb232eaced9e8ad9 f 0 'misc/logo.bmp' aa3dfbd4bc9c863d10a0c5345ae5a4c9 f 0 'sfx.pak' ea1b3e6d6f4906905d4a34f07e9a59ac ;; french-demo) f 0 'data2.pak' 8dc1d1b3e85d4a41ae320aa3fa9c649a f 0 'data.pak' 5d7ba6e6c79ebf7fbb232eaced9e8ad9 f 0 'misc/logo.bmp' aa3dfbd4bc9c863d10a0c5345ae5a4c9 f 0 'sfx.pak' ea1b3e6d6f4906905d4a34f07e9a59ac ;; german-demo) f 0 'data2.pak' 143ba491a357263a2dfad9936a66eeb6 f 0 'data.pak' 5d7ba6e6c79ebf7fbb232eaced9e8ad9 f 0 'misc/logo.bmp' aa3dfbd4bc9c863d10a0c5345ae5a4c9 f 0 'sfx.pak' ea1b3e6d6f4906905d4a34f07e9a59ac ;; japanese-demo) f 0 'data2.pak' 958b78f8f370b06d769843137138c461 f 0 'data.pak' 903dfe1878a0cedff3b941fd3aa22ba9 f 0 'misc/logo.bmp' aa3dfbd4bc9c863d10a0c5345ae5a4c9 f 0 'sfx.pak' ea1b3e6d6f4906905d4a34f07e9a59ac ;; *) # full game f 1 'graph/interface/misc/arkane.bmp' afff1099c01ffeb03b9a351f7b5966b6 f 1 'graph/interface/misc/quit1.bmp' 41445d3792a1f8818d950aca47254488 f 1 'graph/obj3d/textures/fixinter_barrel.jpg' 8419274acbff7346c3661b18d6aad6dc f 1 'graph/obj3d/textures/fixinter_bell.bmp' 5743b9047c9ad65540c318dfcc98123a f 1 'graph/obj3d/textures/fixinter_metal_door.jpg' f246eff6b19c9c710313b4a4dce96a69 f 1 'graph/obj3d/textures/fixinter_public_notice.bmp' f81394abbb9006ce0950843b7909db33 f 1 'graph/obj3d/textures/item_bread.bmp' 544448f8eedc912aa231a6a04fffb7c5 f 1 'graph/obj3d/textures/item_club.jpg' 7e26c4199ddaca494c8b369294306b0b f 1 'graph/obj3d/textures/item_long_sword.jpg' 3a6196fe9b7666c7d80d82be06f6de86 f 1 'graph/obj3d/textures/item_mauld_sabre.jpg' 18492c25ebac02f83e2f0ebda61ecb00 f 1 'graph/obj3d/textures/item_mauldsword.jpg' 503a5c2f23668040c675aefdde6dbbe5 f 1 'graph/obj3d/textures/item_mirror.jpg' c0a22b4f7a7a6461da68206e94928637 f 1 'graph/obj3d/textures/item_ring_casting.bmp' 348f9add709bacee08556d1f8cf10f3f f 1 'graph/obj3d/textures/item_rope.bmp' ff05de281c8b380ee98f6e123d3d51cb f 1 'graph/obj3d/textures/item_spell_sheet.jpg' 024ccbb520020f92fba5a5a4f0270cea f 1 'graph/obj3d/textures/item_torch2.jpg' 027951899b4829599ca611010ea3484f f 1 'graph/obj3d/textures/item_torch.jpg' 9ada166f23ddcb775ac20836e752187e f 1 'graph/obj3d/textures/item_zohark.bmp' cd206a4027f86c6e57b7710c94049efa f 1 'graph/obj3d/textures/l7_dwarf_[wood]_board08.jpg' 79ccc81adb7c37b98f40b478ef1fccd4 f 1 'graph/obj3d/textures/l7_dwarf_[wood]_board80.jpg' 691611087b13d38ef02bb9dfd6a2518e f 1 'graph/obj3d/textures/npc_dog.bmp' 116bd374c14ae8c387a4da1899e1dca7 f 1 'graph/obj3d/textures/npc_pig.bmp' b7a4d0d3d230b2d1470176909004e38b f 1 'graph/obj3d/textures/npc_pig_dirty.bmp' 76034d8d74056c8a982479d36321c228 f 1 'graph/obj3d/textures/npc_rat_base.bmp' 00c585ec9ebe8006d7ca72993de7b51b f 1 'graph/obj3d/textures/npc_rat_base_cm.bmp' cae38facbf77db742180b9e58d0eb42f f 1 'graph/obj3d/textures/npc_worm_body_part1.jpg' 0b220bffaedc89fa663f08d12630c342 f 1 'graph/obj3d/textures/npc_worm_body_part2.bmp' 20797cb78f6393a0fb5405969ba9f805 f 1 'graph/obj3d/textures/[wood]_light_door.jpg' 00d0b018e995e7d013d6e52e92126901 f 1 'misc/logo.avi' 63ed31a4eb3d226c23e58cfaa974d484 f 1 'misc/logo.bmp' afff1099c01ffeb03b9a351f7b5966b6 f 1 'data2.pak' f7e0ce700bf963429ac535ca86f8a7b4 f 0 'sfx.pak' 2efc9a74c517fd1ee9919900cf4091d2 # data.pak is censored in some versions (presumably has less gore) # At least the original german and italian CDs have the censored version. # The censored version has different level files and a different # human_female_villager model. # There are also minor differences in the scripts, but those are # overwritten by data2.pak from the 1.21 patch. # A third data.pak variant can be found on the original French Arx Fatalis CD: # It is almost identical to the preceding censored version, but has different level # files for level 1 (.llf only) and 3 (.llf and .fts). data_checksum_original='a91a0b39a046233debbb10b4850e13eb' data_checksum_censored='a88d239dc7919ab113ff45483cb4ad46' data_checksum_frenchcd='7ae3632eef92700cd6c5e143aa0fe67b' data_checksum_russiancd='b297ab9ae41a593b13cbdd0ecaf1f999' f 0 'data.pak' "$data_checksum_original $data_checksum_censored $data_checksum_frenchcd $data_checksum_russiancd" esac # Optional files - we don't need them, but copy them anyway if available optional_file 'manual.pdf' optional_file 'map.pdf' optional_file 'arx_handbuch.pdf' # Not in version 1.22 (except for Russian for arx_russian.ttf and arx_taiwanese.ttf) optional_file "misc/arx_default.ttf" optional_file "misc/arx_russian.ttf" optional_file "misc/arx_taiwanese.ttf" # Only in version 1.22 optional_file "misc/arx_base.ttf" optional_file "localisation\presence.ini" optional_file "localisation\snd_armor.ini" optional_file "localisation\snd_material.ini" optional_file "localisation\snd_other.ini" optional_file "localisation\snd_step.ini" optional_file "localisation\snd_weapon.ini" optional_file "localisation\ucredits_chinese.txt" optional_file "localisation\ucredits_deutsch.txt" optional_file "localisation\ucredits_english.txt" optional_file "localisation\ucredits_francais.txt" optional_file "localisation\ucredits_italiano.txt" optional_file "localisation\ucredits_russian.txt" optional_file "localisation\utext_francais.ini" print ########################################################################################## # Print a summary if [ $install = 1 ] ; then verb='Installed' ; else verb='Verified' ; fi printf "${white}%s Arx Fatalis %s data: ${green}%s${reset}\n" "$verb" \ "$patch_ver" "$data_lang_desc" if [ $checksum_failed = 1 ] ; then [ $gui = 0 ] || status 100 "Error!" die "There are wrong or missing files!${reset} The game may run fine, or it may fail - good luck!" >&2 fi status 100 "${dim_green}All good!${reset}" if [ $install = 1 ] ; then verb='Installation' ; else verb='Verification' ; fi dialog_message "$verb complete: $data_lang_desc Have fun playing Arx Fatalis!" quit $true