1#!/bin/sh 2 3# Copyright 2013 Arx Libertatis Team (see the AUTHORS file) 4# 5# This file is part of Arx Libertatis. 6# 7# Arx Libertatis is free software: you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation, either version 3 of the License, or 10# (at your option) any later version. 11# 12# Arx Libertatis is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with Arx Libertatis. If not, see <http://www.gnu.org/licenses/>. 19 20########################################################################################## 21# Install script for Arx Fatalis data files to be used with Arx Libertatis 22# Usage: just run the damned script, maybe check --help 23 24# This scripts targets Linux and FreeBSD, but may also work on other UNIX-like systems. 25 26# Is this a multi-thousand-line bas^H^H^HPOSIX shell script? 27# Sure looks like it. 28# Am I mad? 29# Most likely. 30 31# If you want to edit the required files and checksums, scroll to the end. 32 33 34########################################################################################## 35# Colors 36 37disable_color() { 38 red='' ; green='' ; yellow='' ; blue='' ; pink='' ; cyan='' ; white='' 39 dim_red='' ; dim_green='' ; dim_yellow='' ; dim_blue='' ; dim_pink='' 40 dim_cyan='' ; dim_white='' ; reset='' 41} 42disable_color 43if [ -t 1 ] && [ "$(tput colors 2> /dev/null)" != -1 ] ; then 44 45 red="$(printf '\033[1;31m')" 46 green="$(printf '\033[1;32m')" 47 yellow="$(printf '\033[1;33m')" 48 blue="$(printf '\033[1;34m')" 49 pink="$(printf '\033[1;35m')" 50 cyan="$(printf '\033[1;36m')" 51 white="$(printf '\033[1;37m')" 52 53 dim_red="$(printf '\033[0;31m')" 54 dim_green="$(printf '\033[0;32m')" 55 dim_yellow="$(printf '\033[0;33m')" 56 dim_blue="$(printf '\033[0;34m')" 57 dim_pink="$(printf '\033[0;35m')" 58 dim_cyan="$(printf '\033[0;36m')" 59 dim_white="$(printf '\033[0;37m')" 60 61 reset="$(printf '\033[0m')" 62fi 63 64 65########################################################################################## 66# Constants 67 68# Name and download locations for the 1.21 patch 69patch_ver='1.21' 70patch_name="ArxFatalis_${patch_ver}_MULTILANG.exe" 71patch_name_localized="ArxFatalis_${patch_ver}_%s.exe" 72patch_url_path="arxfatalis/patches/${patch_ver}/${patch_name}" 73patch_url_master="http://cdn.bethsoft.com/${patch_url_path}" 74patch_urls="http://arx.vg/${patch_name} ${patch_url_master}" 75patch_urls="$patch_urls http://download.zenimax.com/${patch_url_path}" 76patch_urls="$patch_urls http://web.archive.org/web/${patch_url_master}" 77 78# Name and download locations for the Japanese 1.02j patch 79patch_jp_ver='1.02j' 80patch_jp_name="arx_jpn_patch_${patch_jp_ver}.exe" 81patch_jp_url_master="http://www.capcom.co.jp/pc/arx/patch/${patch_jp_name}" 82patch_jp_urls="http://arx.vg/${patch_jp_name}" # master URL is no longer available 83patch_jp_urls="$patch_jp_urls http://web.archive.org/web/${patch_jp_url_master}" 84 85# Name and store page for the GOG.com download 86gog_names='setup_arx_fatalis.exe' 87gog_url='http://www.gog.com/gamecard/arx_fatalis' 88 89# Store page for the Steam download 90steam_url='http://store.steampowered.com/app/1700/' 91 92# Name and wiki page for the demo download 93demo_names="arx_demo_english.zip arxdemoenglish.zip arx_jpn_demo.exe" 94demo_url='http://arx.vg/Getting_the_game_data#Demo' 95 96bug_tracker_url='http://bugs.arx-libertatis.org/' 97 98cabextract_url='http://www.cabextract.org.uk/' 99innoextract_url='http://constexpr.org/innoextract/' 100 101 102########################################################################################## 103# Standard directories 104 105user_pwd="$PWD" 106user_pwd="${user_pwd%/}" 107platform="$(uname)" 108command="$(basename "$0")" 109scommand="$(printf '%s' "$command" | tr - _)" 110if [ "$platform" = 'Darwin' ] ; then 111 # Mac OS X 112 data_dirs='/Applications' 113 data_home="$HOME/Library/Application Support" 114 config_home="$HOME/Library/Application Support" 115 data_dir_suffixes='ArxLibertatis' 116 user_dir_suffixes='ArxLibertatis' 117 config_dir_suffixes='ArxLibertatis' 118 downloads_dir="$HOME/Downloads" 119else 120 # Linux, FreeBSD, ... 121 data_dirs="${XDG_DATA_DIRS:-"/usr/local/share/:/usr/share/"}:/opt" 122 data_home="${XDG_DATA_HOME:-"$HOME/.local/share"}" 123 config_home="${XDG_CONFIG_HOME:-"$HOME/.config"}" 124 data_dir_suffixes='games/arx:arx' 125 user_dir_suffixes='arx' 126 config_dir_suffixes='arx' 127 [ -f "${config_home}/user-dirs.dirs" ] && . "${config_home}/user-dirs.dirs" 128 downloads_dir="${XDG_DOWNLOAD_DIR:-"$HOME/Downloads"}" 129fi 130downloads_dir="${downloads_dir%/}" 131tempdir="${TMPDIR:-"/tmp"}" 132tempdir="${tempdir%/}" 133[ -d "$tempdir" ] || tempdir="$PWD" 134eval "data_path=\"\$${scommand}_PATH\"" 135[ -z "$data_path" ] && data_path="$arx_PATH" 136 137 138########################################################################################## 139# Helper functions 140 141exec 4>&2 # fd to the original stderr (we redirect output to a log file in some cases) 142logfile='' # log file receiving sdout and stderr 143 144true=0 # Return value / exit status that evaluates to true 145false=1 # Return value / exit status that evaluates to false 146 147# 1 if the script is being run as root, false otherwise 148if [ "$(id -u)" = 0 ] ; then is_root=1 ; else is_root=0 ; fi 149 150# Print one line of text, without escape codes or other shell-specific shenanigans. 151# Seriously, shells, you can't even agree on a consistent implementation of echo? 152# Usage: print <text> 153print() { 154 printf '%s\n' "$1" 155} 156 157puts() { 158 printf '%s' "$1" 159} 160 161disabled_commands=' ' # List of commands that should not be used, even if they exist 162 163# Make `have` return false for a comand 164# Usage: disable_command <command> 165disable_command() { 166 disabled_commands="$disabled_commands$1 " 167} 168 169# Check if a command is available. 170# Usage: have <command> 171# Return: $true if the command is available, $false otherwise 172have() { 173 case "$disabled_commands" in *" $1 "*) return $false ; esac 174 command -v "$1" > /dev/null 2>&1 175} 176 177# Make a path absolute no matter if it is relative or not 178# Usage: abspath <path> 179# Too bad we can't just use readlink -m 180abspath() { 181 case "$1" in 182 /*) print "$1" ;; 183 *) print "$PWD/$1" ;; 184 esac 185} 186 187# Get the canonical representation of an existing path 188# Usage: canonicalize <path> 189# Too bad we can't just use readlink -f 190if have realpath ; then 191 canonicalize() { realpath "$1" ; } 192else if have grealpath ; then 193 canonicalize() { grealpath "$1" ; } 194else if have greadlink ; then 195 canonicalize() { greadlink -f "$1" ; } 196else 197 canonicalize() { 198 _canonicalize_old_pwd="$PWD" 199 _canonicalize_file="$1" 200 while true ; do 201 cd "$(dirname "$_canonicalize_file")" 202 _canonicalize_file="$(basename "$_canonicalize_file")" 203 [ -L "$_canonicalize_file" ] || break; 204 _canonicalize_file="$(readlink "$_canonicalize_file")" 205 done 206 echo "$(pwd -P)/$_canonicalize_file" 207 cd "$_canonicalize_old_pwd" 208 } 209fi ; fi ; fi 210 211cleanup_functions='' # List of functions to be run on exit 212 213# Add a function to ron on exit. 214# Functions are un in the order they are added. 215# Usage: on_exit <code> 216# Cleanup functions will receive one argument: the exit message if any or an empty string. 217on_exit() { 218 [ -z "$cleanup_functions" ] || cleanup_functions=" $cleanup_functions" 219 cleanup_functions="$1$cleanup_functions" 220} 221 222# Run exit runctions. 223cleanup() { 224 _cleanup_functions="$cleanup_functions" ; cleanup_functions='' 225 [ -z "$_cleanup_functions" ] && return 226 eval "for _cleanup_func in $_cleanup_functions ; do \"\$_cleanup_func\" \"\$@\" ; done" 227} 228 229# Register our cleanup handler. 230trap "cleanup" EXIT 231# Some shells don't have their own (non-libc) SIGINT handler, but the EXIT trap 232# won't trigger if there is none! 233trap 'print >&4 ; quit 1' INT 234 235# Run cleanup functions with a possible message and then exit. 236# Usage: quit <status> [<message>] 237quit() { 238 cleanup "$2" 239 exit $1 240} 241 242# Exit with a non-zero status and optionally print a message. 243# Usage: die [<message>...] 244die() { 245 _die_message='' 246 if [ $# -gt 0 ] ; then 247 _die_message="$1" ; shift 248 for _die_arg ; do _die_message="$_die_message $_die_arg" ; done 249 _die_message="$_die_message 250 251If you think this is a bug in the install script 252please report the complete output at 253 $bug_tracker_url" 254 if [ ! -z "$logfile" ] && [ -f "$logfile" ] ; then 255 _die_message="$_die_message 256 257Also attach the contents of 258 $logfile" 259 logfile='' # so that we don't remove it on exit 260 printf "${red}%s${reset}\\n" "$_die_message" >&4 # also print to priginal stdout 261 printf '\n%s\n' 'Preserving log file.' >&4 262 fi 263 264 printf "${red}%s${reset}\\n" "$_die_message" 265 fi 266 quit 1 "$_die_message" 267} 268 269# Escape a string from stdin for use in a whitespace-seperated list. 270# Usage: print <string> | escape_pipe 271escape_pipe() { 272 sed "s:[^a-zA-Z0-9/_.$1]:\\\\&:g" 273} 274 275# Escape a string for use in a whitespace-seperated list. 276# Usage: escape <string> 277escape() { 278 print "$1" | escape_pipe "$2" 279} 280 281# Convert a colon-seperated list into an escaped whitespace-seperated list. 282# Usage: to_list <colon-list> 283to_list() { 284 escape "$1" | sed 's/\\:/ /g' 285} 286 287# Line-based output into a list 288# Usage: ls | lines_to_list 289lines_to_list() { 290 escape_pipe | tr '\n' ' ' 291} 292 293# Check if a whitespace separated list contains a string. 294# Usage: list_contains <list-var> <needle> 295list_contains() { 296 eval "_list_contents=\"\$$1\"" 297 [ -z "$_list_contents" ] && return $false 298 eval "for _list_contains_entry in $_list_contents ; do" \ 299 " [ \"\$_list_contains_entry\" = \"\$2\" ] && return \$true ; done" 300 return $false 301} 302 303# Append a string to a whitespace separated list. 304# Usage: list_append <list-var> <string> [comment] 305# Whitespace seperated lists can be loaded into the argument list using: 306# eval "set -- $var" 307list_append() { 308 _list_entry="$(escape "$2")" 309 eval "_list_contents=\"\$$1\"" 310 if [ -z "$_list_contents" ] 311 then eval "$1=\"\$_list_entry\"" 312 else eval "$1=\"\$_list_contents \$_list_entry\"" 313 fi 314 eval "[ -z \"\$$1__list_count\" ] && $1__list_count=0" 315 eval "_list_count=\$$1__list_count" 316 eval "$1__list_comment_$_list_count=\"\$3\"" 317 eval "$1__list_count=\$((\$$1__list_count + 1))" 318} 319 320# Append one list to another, preserving comments. 321# Usage: list_merge <list-var> <append-list-var> 322list_merge() { 323 eval "_list_append=\"\$$2\"" 324 [ -z "$_list_append" ] && return 325 eval " 326 _list_merge_i=0 327 for _list_merge_entry in $_list_append ; do 328 list_append $1 \"\$_list_merge_entry\" \"\$(list_comment $2 \$_list_merge_i)\" 329 _list_merge_i=\$((\$_list_merge_i + 1)) 330 done 331 " 332} 333 334# Get a comment associated with alist entry 335# Usage: list_comment <list-var> <index> 336list_comment() { 337 eval "print \"\$$1__list_comment_$2\"" 338} 339 340# Set a comment associated with alist entry 341# Usage: list_comment <list-var> <index> <comment> 342set_list_comment() { 343 eval "$1__list_comment_$2=\"\$3\"" 344} 345 346# Append a string to a whitespace separated list if it isn't already in the list. 347# Usage: set_append <list-var> <string> [comment] 348set_append() { 349 if ! list_contains "$1" "$2" ; then 350 list_append "$1" "$2" "$3" 351 fi 352} 353 354# Check if a directory contains a file while ignoring case differences. 355# Usage: icontains <dir> <filename> 356icontains() { 357 [ ! -z "$(find "$1" -mindepth 1 -maxdepth 1 -iname "$2")" ] 358} 359 360# Check if a directory or file is writable or can be created. 361# Usage: is_writable <path> 362is_writable() { 363 [ -w "$1" ] && return $true 364 [ ! -e "$1" ] && is_writable "$(dirname "$1")" 365} 366 367# Create a directory and die with a message on error. 368# Usage: create_dir <path> <type> 369create_dir() { 370 mkdir -p "$1" || die "Could not create $2 directory: $1" 371} 372 373probe_file_dirs='' 374set_append probe_file_dirs "$user_pwd" 375set_append probe_file_dirs "$downloads_dir" 376set_append probe_file_dirs "$HOME" 377set_append probe_file_dirs "$tempdir" 378 379# Find a file in standard directories. 380# Usage: probe_file <command> <filename> [comment] 381# Will call `command <file>` for each file found. 382probe_file() { 383 eval "for _probe_file_d in $probe_file_dirs ; do [ -f \"\$_probe_file_d/\$2\" ] && \$1 \"\$_probe_file_d/\$2\" \"\$3\" && return \$true ; done" 384} 385 386# Find files in standard directories. 387# Usage: probe_file <command> <list> [comment] 388# Will call `command <file>` for each file found. 389probe_files() { 390 [ -z "$2" ] && return $false 391 eval "for _probe_files_file in $2 ; do probe_file \"\$1\" \"\$_probe_files_file\" \"\$3\" && return \$true ; done" 392 return $false 393} 394 395 396########################################################################################## 397# Parse command-line arguments 398 399extract_zip_reqs='' 400list_append extract_zip_reqs 'bsdtar' 'libarchive' 401list_append extract_zip_reqs 'unzip' 402list_append extract_zip_reqs '7za' 403list_append extract_zip_reqs '7z' 'p7zip' 404extract_ms_cab_reqs='' 405list_append extract_ms_cab_reqs 'bsdtar' 'with libarchive 3.1+' 406list_append extract_ms_cab_reqs 'cabextract' "$cabextract_url" 407list_append extract_ms_cab_reqs '7za' 408list_append extract_ms_cab_reqs '7z' 'p7zip' 409extract_installshield_reqs='' 410list_append extract_installshield_reqs 'unshield' 411extract_innosetup_reqs='' 412list_append extract_innosetup_reqs 'innoextract' "$innoextract_url" 413mount_cdrom_reqs='' 414list_append mount_cdrom_reqs 'fuseiso' 415extract_iso_reqs='' 416list_append extract_iso_reqs 'isoinfo' 417list_append extract_iso_reqs 'bsdtar' 'libarchive' 418list_append extract_iso_reqs '7z' 'p7zip' 419extract_cdrom_reqs='' 420list_merge extract_cdrom_reqs mount_cdrom_reqs 421list_merge extract_cdrom_reqs extract_iso_reqs 422download_reqs='' 423list_append download_reqs 'wget' 424list_append download_reqs 'curl' 425list_append download_reqs 'fetch' 'FreeBSD' 426 427printf '%s %s\n' "${white}Welome to the ${green}Arx Fatalis${white} ${patch_ver} data" \ 428 "install script for UNIX-like systems!${reset}" 429 430patchfile='' # Main patch file 431patchfile_jp='' # Japanese patch file 432sourcefile='' # Source file or directory 433datadir='' # Output data directory 434batch=0 # Never wait for user input 435gui=0 # Display a graphical user interface (command-line interface otherwise) 436install=1 # Install new non-patch files 437installed_stuff=0 # Have we already installed anything? 438patch=1 # Install patch files if needed 439probe_patch=1 # Look for patch files in standard locations and download if needed 440redirect_log=1 # Redirect standard output/error output to a log file in GUI mode 441 442# Enable compatiblity with old install-* scripts. 443# Usage: enable_compat_mode <help-flag> <sourcefile> <patchfile> <datadir> 444enable_compat_mode() { 445 print \ 446 "${yellow}Enabling compatibility mode for ${pink}$command${yellow}.${reset} 447 448${dim_yellow}The individual ${dim_pink}install-*${dim_yellow} scripts have been merged. 449Rename this script to something else (like ${dim_pink}arx-install-data${dim_yellow}) to unlock its full power!${reset} 450" >&2 451 batch=1 452 probe_patch=0 453 if [ -z "$1" ] || [ "$1" = '--help' ] || [ "$1" = '-h' ] ; then 454 printf '%s\n\n%s\n' "$5" \ 455 "${yellow}More options are available in the non-compatiblity mode.${reset}" 456 exit $false 457 fi 458 if [ -z "$2" ] ; then install=0 ; else sourcefile="$2" ; fi 459 if [ -z "$3" ] ; then patch=0 ; else patchfile="$3" ; fi 460 if [ -z "$4" ] ; then datadir="$user_pwd" ; else datadir="$4" ; fi 461} 462 463case "$command" in 464 465install-cd) 466[ "$1" = "--no-progress" ] && shift # ignore - not supported 467enable_compat_mode "$1" "$1" "$2" "$3" "\ 468Usage: $command path/to/mount/point/ path/to/ArxFatalis_1.21_MULTILANG.exe [output_dir] 469or $command path/to/cd.iso path/to/ArxFatalis_1.21_MULTILANG.exe [output_dir]" ;; 470 471install-copy) 472enable_compat_mode "$1" "$1" '' "$2" "\ 473Usage: $command path/to/ArxFatalis/ [output_dir]" ;; 474 475install-demo) 476enable_compat_mode "$1" "$1" '' "$2" "\ 477Usage: $command path/to/arx_demo_english.zip [output_dir]" ;; 478 479install-gog) 480[ "$1" = "--no-progress" ] && shift # ignore - not supported 481enable_compat_mode "$1" "$1" '' "$2" "\ 482Usage: $command path/to/setup_arx_fatalis.exe [output_dir]" ;; 483 484install-verify) 485enable_compat_mode "$1" '' '' "$1" "\ 486Usage: $command [directory]" ;; 487 488*) # non-compatibility mode 489 490# Print elements in a list, joined by ' or ' 491# Usage: print_help_or <list-var> [color] 492print_help_or() { 493 _print_help_or_var=$1 494 eval "_print_help_or_list=\"\$$1\"" 495 _print_help_or_color="$2" 496 [ -z "$1" ] && return 497 eval " 498 _print_help_or_i=0 499 for _print_help_or_entry in $_print_help_or_list ; do 500 [ \$_print_help_or_i = 0 ] || puts ' or ' 501 printf '%s%s' \"\$_print_help_or_color\" \"\$_print_help_or_entry\" 502 [ -z \"\$_print_help_or_color\" ] || puts \"\$reset\" 503 _print_help_or_comment=\"\$(list_comment \$_print_help_or_var \$_print_help_or_i)\" 504 [ -z \"\$_print_help_or_comment\" ] || printf ' (%s)' \"\$_print_help_or_comment\" 505 _print_help_or_i=\$((\$_print_help_or_i + 1)) 506 done 507 " 508} 509 510# Print elements in a list, one per line. 511# Usage: print_help_list <prefix-format> <list-var> 512# prefi-format will receive one argument: the list index starting at 1 513print_help_list() { 514 _print_help_list_prefix="$1" 515 _print_help_list_var=$2 516 eval "_print_help_list_list=\"\$$2\"" 517 [ -z "$1" ] && return 518 eval " 519 _print_help_list_i=0 520 for _print_help_list_entry in $_print_help_list_list ; do 521 case \"\$_print_help_list_prefix\" in 522 *%*) printf \"\$_print_help_list_prefix\" \$((\$_print_help_list_i + 1)) ;; 523 *) puts \"\$_print_help_list_prefix\" 524 esac 525 printf \"%s\${reset}\" \"\$_print_help_list_entry\" 526 _print_help_list_comment=\"\$(list_comment \$_print_help_list_var \$_print_help_list_i)\" 527 [ -z \"\$_print_help_list_comment\" ] || printf ' (%s)' \"\$_print_help_list_comment\" 528 printf '\n' 529 _print_help_list_i=\$((\$_print_help_list_i + 1)) 530 done 531 " 532} 533 534# Print help output. 535# Usage: print_help [<error-message>] 536print_help() { 537 [ -z "${1-}" ] || ( printf '%s\n\n' "${red}$1${reset}" ) 538 print " 539${white}Simply start the script without any arguments to select paths interactively: 540 \$ $command${reset} 541 542Usage: $command [--source] source [--patch patchfile] [[--data-dir] datadir] 543 $command [--patch patchfile] [--data-dir datadir] 544 $command --verify [[--data-dir] datadir] 545 546 ${green}-s, --source PATH${reset} Path to the source file or directory 547 ${cyan}-d, --data-dir DIR${reset} Where to install the data 548 ${blue}-p, --patch FILE${reset} Path to the ${patch_ver} patch file 549 --patch-jp FILE Path to the ${patch_jp_ver} Japanese patch file 550 -v, --verify Only verify the files in the data-dir, don't install new ones, 551 except for patch files. 552 -n, --no-patch Don't use a patch file unless explicitly specified. 553 -h, --help Print this message and maybe more 554 -b, --batch Never ask the user questions 555 -g, --gui Show a GUI asking the user what to do 556 Requires ${dim_pink}KDialog${reset}, ${dim_pink}Zenity${reset}, or ${dim_pink}Xmessage${reset}. 557 If none of them are available the script is re-launched 558 in a terminal emulator. 559 -c, --cli Interactively ask the user to select files/directories (no GUI) 560 --no-redirect-log Don't redirect output to a log file when in GUI mode 561 --disable-COMMAND Don't use the given tool, even if it exists. 562 Valid values are unzip, bsdtar, cabextract, isoinfo, 563 fuseiso, fusermount, mount, umount and innoextract, 564 wget, curl, fetch, unshield, kdialog, zenity, Xdialog, 565 qdbus, dcop, x-terminal-emulator, urxvt, gtkterm, aterm, 566 rxvt, gnome-terminal, konsole, xterm, gxmessage, xmessage, 567 md5sum, md5. 568 569--gui is enabled by default if there are no arguments *and* stdin, stdout or stderr is not a terminal 570" 571 [ ! -z "${1-}" ] && exit $false 572 help_innosetup="$(print_help_or extract_innosetup_reqs "$dim_pink")" 573 help_cdrom="$(print_help_or extract_cdrom_reqs "$dim_pink") or root access" 574 help_cab="$(print_help_or extract_ms_cab_reqs "$dim_pink")" 575 help_zip="$(print_help_or extract_zip_reqs "$dim_pink")" 576 help_unshield="$(print_help_or extract_installshield_reqs "$dim_pink")" 577 help_download="$(print_help_or download_reqs "$dim_pink")" 578 help_optpatch="may use the 1.21 patch file and require ${help_innosetup} if not already patched" 579 help_probe_file_dirs=" 580 a) the current working directory (\$PWD): $user_pwd 581 b) the user's downloads directory (\$HOME): $HOME 582 c) the user's home directory (\$XDG_DOWNLOAD_DIR): $downloads_dir 583 d) the temp directory (\$TMPDIR): $tempdir" 584 help_probe_file_dirs_patch="${help_probe_file_dirs} 585 e) the directory containing the source file" 586 help_probed_files="$gog_names $demo_names" 587 print " 588The ${pink}dependencies${reset} required by the ${command} script depend on the source files. 589However, you always need either ${dim_pink}md5sum${reset} or ${dim_pink}md5${reset}. 590 591 592The ${green}source${reset} can be one of many things: 593 594 * ${white}Mounted Arx Fatalis ${green}cdrom${reset} 595 requires: 596 - ${help_cab} 597 - ${help_innosetup} 598 needs the 1.21 patch file 599 600 * ${white}Arx Fatalis cdrom ${green}ISO${white} image / device file${reset} 601 requires: 602 - ${help_cdrom} 603 - ${help_cab} 604 - ${help_innosetup} 605 needs the 1.21 patch file 606 607 * ${white}Arx Fatalis installer from ${green}GOG.com${white}${reset} ($(print_help_or gog_names)) 608 requires: 609 - ${help_innosetup} 610 never uses the 1.21 patch file 611 get it from ${dim_green}${gog_url}${reset} 612 613 * ${green}Installed${white} copy of Arx Fatalis${reset} (for example from ${green}Steam${reset}) 614 ${help_optpatch} 615 get it from ${dim_green}${steam_url}${reset} 616 617 * ${white}Arx Fatalis ${green}demo${white} zip${reset} ($(print_help_or demo_names)) 618 requires: 619 - ${help_zip} 620 - ${help_cab} 621 never uses the 1.21 patch file 622 get it from ${dim_green}${demo_url}${reset} 623 624 * ${white}Extracted Arx Fatalis demo installer${reset} 625 requires: 626 - ${help_cab} 627 never uses the 1.21 patch file 628 629 * ${white}Installed copy of the Arx Fatalis demo${reset} 630 never uses the 1.21 patch file 631 632If no source is specified, these files will be probed: 6331. The following files in${help_probe_file_dirs} 634$(print_help_list " 1.%d ${green}" help_probed_files) 6352. If \$WINEPREFIX is set, any installation in there 6363. Any installation in the default WINEPREFIX (${green}~/.wine${reset}) 6374. Any mounted ${green}cdrom${reset} or ISO file 638 639 640If no ${blue}patch${reset} file is specified, but is needed and 641the --no-patch option wasn't specified specified: 6421. Try to find the following files in${help_probe_file_dirs_patch} 643 1.1. ${blue}${patch_name}${reset} 644 1.2. $(printf "$patch_name_localized" '<LANG>') 645 Where <LANG> is one of EN, ES, FR, GE, IT, RU, 646 depending on the language of the data files. 6472. Downloaded from: 648$(print_help_list " - ${dim_blue}" patch_urls) 649Downloading the patch file requires ${help_download}. 650Extracting the ${patch_ver} patch file requires ${help_innosetup}. 651 652For the Japanese version, if no ${blue}patch-jp${reset} file is specified, 653but is needed and the --no-patch option wasn't specified specified: 6541. Try to find ${blue}${patch_jp_name}${reset} in${help_probe_file_dirs_patch} 6552. Downloaded from: 656$(print_help_list " - ${dim_blue}" patch_jp_urls) 657Downloading the patch file requires ${help_download}. 658Extracting the Japanese patch file requires: 659 - ${help_unshield} 660 - ${help_cab}. 661 662 663If no ${cyan}data-dir${reset} to install into is specified, 664one is automatically selected similarly to how Arx Libertatis would: 665If --verify and --no-patch (and --no-patch) are give, use the first existing 666directory of the following, otherwise, use the first existing writable directory 667or, if none exists, the first directory that can be created: 6681. Any path in \$${scommand}_PATH (for use in wrapper scripts) 6692. \"\${XDG_DATA_DIRS:-\"/usr/local/share/:/usr/share/\"}:/opt\" / \"$data_dir_suffixes\":" 670 i=1 671 eval "set -- $(to_list "$data_dirs")" 672 for prefix in "$@" ; do 673 eval "set -- $(to_list "$data_dir_suffixes")" 674 for suffix ; do 675 printf " 2.%d. ${dim_cyan}%s${reset}\\n" $i "$prefix/$suffix" 676 i=$(($i + 1)) 677 done 678 done 679print "3. \"\${XDG_DATA_HOME:-\"\$HOME/.local/share\"}\" / \"$user_dir_suffixes\"" 680 i=1 681 eval "set -- $(to_list "$user_dir_suffixes")" 682 for suffix ; do 683 printf " 3.%d. ${dim_cyan}%s${reset}\\n" $i "$data_home/$suffix" 684 i=$(($i + 1)) 685 done 686 print 687 exit $true 688} 689 690user_is_sane=1 691if [ ! -t 0 ] || [ ! -t 1 ] || [ ! -t 2 ] ; then 692 [ $# = 0 ] && gui=1 693fi 694while [ $# -gt 0 ] ; do 695 case "$1" in 696 --source=*) sourcefile="${1#--source=}" ; install=1 ;; 697 -s|--source) shift ; sourcefile="$1" ; install=1 ;; 698 --data-dir=*) datadir="${1#--data-dir=}" ;; 699 -d|--data-dir) shift ; datadir="$1" ;; 700 --patch=*) patchfile="${1#--patch=}" ; patch=1 ;; 701 -p|--patch) shift ; patchfile="$1" ; patch=1 ;; 702 --patch-jp=*) patchfile_jp="${1#--patch-jp=}" ; patch=1 ;; 703 --patch-jp) shift ; patchfile_jp="$1" ; patch=1 ;; 704 -v|--verify) install=0 ;; 705 -n|--no-patch) [ -z "$patchfile" ] && [ -z "$patchfile_jp" ] && patch=0 706 probe_patch=0 ;; 707 -b|--batch) batch=1 ;; 708 -g|--gui) gui=1 ;; 709 -c|--cui|--cli) batch=0 ; gui=0 ;; 710 --no-redirect-log) redirect_log=0 ;; 711 --i-am-insane) user_is_sane=0 ;; 712 --disable-*) disable_command "${1#--disable-}" ;; 713 --disable) shift ; disable_command "${1#--disable-}" ;; 714 -h|--help) print_help ;; 715 -*) print_help "Uknown option: $1" ;; 716 *) 717 if [ -z "${sourcefile-}" ] && [ $install = 1 ] ; then sourcefile="$1" 718 else if [ -z "${datadir-}" ] ; then datadir="$1" 719 else print_help "Too many options: $1" ; fi ;fi 720 esac 721 [ -z "${1-}" ] && print_help "Expected more options" 722 shift; 723done 724 725print "See \`${dim_pink}$command --help${reset}\` for available options." 726 727esac 728 729# Make user-provided paths absolute 730[ ! -z "$sourcefile" ] && sourcefile="$(abspath "$sourcefile")" 731[ ! -z "$datadir" ] && datadir="$(abspath "$datadir")" 732[ ! -z "$patchfile" ] && patchfile="$(abspath "$patchfile")" 733 734# Sanity check 735[ $install = 1 ] && [ $batch = 1 ] && [ -z "$sourcefile" ] && [ $user_is_sane = 1 ] \ 736 && die "You have used --batch without providing a source file! 737This would just pick the first source file found, which is a bad idea™. 738If you really want this, add the --i-am-insane option." 739 740 741########################################################################################## 742# User interface abstraction 743 744_dialog_title="Arx Fatalis ${patch_ver} data installer" 745 746# Handle magic environment variable to tell the script that it has been launched 747# in its own terminal and should not try to create a GUI. 748if [ $batch = 0 ] && [ "$_arx_install_data_force_cli" = 1 ] ; then 749 trap '_arx_install_data_force_cli=0 ; quit 1' INT 750 printf "\n${yellow}%s${reset}\n\n" \ 751 'Note: Install KDialog, Zenity or Xdialog for a better GUI' 752 wait_exit() { 753 [ "$_arx_install_data_force_cli" = 1 ] && print 'Press enter to exit...' && read f 754 exit $false 755 } 756 on_exit wait_exit 757 gui=0 758 for var in batch install patch probe_patch sourcefile datadir \ 759 patchfile patchfile_jp disabled_commands; do 760 eval "$var=\"\$_arx_install_data_force_$var\"" 761 done 762fi 763 764# Select the dialog backend to use 765if [ $gui = 1 ] ; then 766 767 # Detect if we are running in a KDE session 768 is_kde=0 769 case "$DESKTOP_SESSION" in *kde*|*KDE*) is_kde=1 ; esac 770 [ -z "$KDE_FULL_SESSION" ] || is_kde=1 771 [ -z "$KDE_SESSION_UID" ] || is_kde=1 772 [ -z "$KDE_SESSION_VERSION" ] || is_kde=1 773 774 # Select the GUI backend, prefer kdialog for KDE sessions, zenity otherwise 775 if [ $is_kde = 1 ] ; then preferred=kdialog ; else preferred=zenity ; fi 776 for backend in $preferred zenity kdialog Xdialog ; do 777 have $backend && gui=$backend && break 778 done 779 780 if [ $gui = 1 ] ; then 781 782 # No dialog backend available 783 # Try opening a graphical terminal and launching the script in there. 784 print 'No GUI dialog backend is available - trying to launch a terminal emulator' 785 term_cmd="$(abspath "$(command -v "$0" 2> /dev/null)")" 786 # Not all terminals accept command arguments in the same way. 787 # Instead of hacking terminal-specific code, use a magic environment 788 # variable to tell the sub-process how to behave. 789 _arx_install_data_force_cli=1 790 export _arx_install_data_force_cli 791 for var in batch install patch probe_patch sourcefile datadir \ 792 patchfile patchfile_jp disabled_commands ; do 793 eval "_arx_install_data_force_$var=\"\$$var\"" 794 eval "export _arx_install_data_force_$var" 795 done 796 if [ $is_kde = 1 ] ; then preferred=konsole ; else preferred=x-terminal-emulator ; fi 797 for backend in x-terminal-emulator $preferred \ 798 aterm urxvt rxvt konsole xterm gnome-terminal 799 do 800 if have $backend ; then 801 $backend -e "$term_cmd" || continue 802 exit $true 803 fi 804 done 805 806 # Hm, that didn't work either - bail 807 message="No GUI dialog backend is available" 808 message="$message - install KDialog, Zenity or Xdialog, or use the --cli option." 809 # Final attempt to let the user know what happened 810 for backend in gxmessage xmessage ; do 811 if have $backend ; then 812 $backend -center -buttons OK "$_dialog_title 813 814$message" 815 break 816 fi 817 done 818 die "$message" 819 820 fi 821 822 # We don't need colors for the UI, but they may cause problems - get rit of them 823 disable_color 824 825 if [ $redirect_log = 1 ] ; then 826 # Redirect all further output into a log file 827 logfile="$(abspath "$(mktemp "$tempdir/arx-install-data.log.XXXXX")")" 828 clean_logfile() { 829 [ -z "$logfile" ] || rm -f "$logfile" 830 } 831 on_exit clean_logfile 832 print "Enabling GUI mode, standard output/error saved to $logfile" 833 print "Use the --cli option for an interactive command-line interface." 834 exec > "$logfile" 2>&1 835 else 836 print "Enabling GUI mode..." 837 print "Use the --cli option for an interactive command-line interface." 838 fi 839 840else 841 842 print "Enabling CLI mode, use the --gui option for a graphical interface." 843 844fi 845 846#----------------------------------------------------------------------------------------# 847# Functions for controlling an asynchronous process via stdin 848 849pipe_file='' 850pipe_pid=0 851 852# Run a command in the background and open a pipe to pass commmands to it 853# Usage: pipe_create <command> [<args>...] 854pipe_create() { 855 856 pipe_destroy 857 858 # Pipe commands via a FIFO or, if that fails, via a regular file 859 pipe_file="$(mktemp -u "$tempdir/arx-install-data.pipe.XXXXX")" 860 if mkfifo -m 600 "$pipe_file" 2> /dev/null ; then 861 # Fast communication via a FIFO 862 cat "$pipe_file" | "$@" & 863 pipe_pid="$!" 864 else 865 # Fallback via regular file, may use polling 866 pipe_file="$(mktemp "$tempdir/arx-install-data.pipe.XXXXX")" 867 [ -z "$pipe_file" ] && return $false 868 tail -f "$pipe_file" | "$@" & 869 pipe_pid="$!" 870 fi 871 872 # Open fd 3 for writing into the pipe 873 exec 3> "$pipe_file" 874 875 return $true 876} 877 878# Kill the program created via pipe_create and cleanup files 879# Usage: pipe_destroy 880pipe_destroy() { 881 882 # Close fd pointing to the pipe 883 exec 3<&- 884 885 # Remove the FIFO or temp file 886 [ -z "$pipe_file" ] || rm -f "$pipe_file" > /dev/null 2>&1 887 pipe_file='' 888 889 # Terminate the remote process 890 [ "$pipe_pid" = 0 ] || kill "$pipe_pid" > /dev/null 2>&1 891 pipe_pid=0 892 893 return $true 894} 895 896# Check if the remote process is running 897# Usage: pipe_exists || print 'oh noes' 898pipe_exists() { 899 [ -z "$pipe_file" ] && return $false 900 [ "$pipe_pid" = 0 ] && return $false 901 kill -s 0 "$pipe_pid" > /dev/null 2>&1 902} 903 904# Send a message to the remote process 905# Usage: pipe_write <command> 906pipe_write() { 907 [ -z "$pipe_file" ] || print "$1" >&3 908} 909 910#----------------------------------------------------------------------------------------# 911# Code for the different GUI/CLI implementations 912# Each implementation exposes dialog_* primitives that are used by generic functions. 913 914case $gui in 915 916#----------------------------------------------------------------------------------------# 917zenity) 918 919# Helper functions 920 921# Run Zenity 922# Usage: zenity_run <title-prefix> <dialog-type> [<args>...] 923zenity_run() { 924 _zenity_run_t="$1" ; shift 925 [ -z "$_zenity_run_t" ] || _zenity_run_t="$_zenity_run_t - " 926 zenity --title "$_zenity_run_t$_dialog_title" "$@" 927} 928 929# Dialog abstraction 930 931# Create the main progress window. 932# Usage: dialog_create 933dialog_create() { 934 pipe_create zenity --title "$_dialog_title$1" --width 450 --progress 935} 936 937# Destroy the main progress window. 938# Usage: dialog_destroy 939dialog_destroy() { 940 pipe_destroy 941} 942 943# Show an error dialog. 944# Usage: dialog_error <message> 945dialog_error() { 946 zenity_run 'Error' --error --no-wrap --text="$1" 947} 948 949# Show a message box. 950# Usage: dialog_message <message> 951dialog_message() { 952 zenity_run 'Status' --info --no-wrap --text="$1" 953} 954 955# Ask a yes/no question. 956# Usage: dialog_ask <question> 957dialog_ask() { 958 zenity_run 'Confirm' --question --no-wrap --ok-label=Yes --cancel-label=No --text="$1" 959} 960 961# Has the user quested to cancel the operation? 962# Usage: dialog_cancelled && print "cancelled" 963dialog_cancelled() { 964 ! pipe_exists 965} 966 967# Set the status text. 968# Usage: dialog_set_text <text> 969dialog_set_text() { 970 pipe_write "#$1" 971} 972 973# Set if the progress bar should continously animate instead of showing the value. 974# Usage: dialog_set_pulsate <enable> 975dialog_set_pulsate() { 976 if [ $1 = 1 ] 977 then pipe_write "pulsate:true" 978 else pipe_write "pulsate:false" 979 fi 980} 981 982# Set the current progress value. 983# Usage: dialog_set_value <percentage> 984dialog_set_value() { 985 pipe_write "$1" 986} 987 988# Select an entry in a list. 989# Usage dialog_select_entry <var> <label> <tag1> <item1> [ <tag2> < item2> ... ] 990dialog_select_entry() { 991 _zenity_select_entry_v="$1" ; shift 992 _zenity_select_entry_t="$1" ; shift 993 _zenity_select_entry_r="$( 994 zenity_run 'Select path' --width 550 --height 300 \ 995 --list --text="$_zenity_select_entry_t" \ 996 --column '#' --column 'Path' --hide-column=1 "$@" --hide-header 997 )" 998 [ -z "$_zenity_select_entry_r" ] && return $false 999 eval "$_zenity_select_entry_v=\"\$_zenity_select_entry_r\"" 1000 return $true 1001} 1002 1003# dialog_select_path does not support the --any flag 1004dialog_select_path_any=0 1005 1006# Let the user select a path. 1007# Usage: dialog_select_path (--file|--dir|--any) <result-var> <label> 1008# Any is only supported if $dialog_select_path_any is 1. 1009dialog_select_path() { 1010 case "$1" in 1011 --any) die 'not implemented' ;; 1012 --file) _zenity_select_path_f='--file-selection' ;; 1013 --dir) _zenity_select_path_f='--file-selection --directory' ;; 1014 esac 1015 _zenity_select_path="$( 1016 eval "zenity_run \"\$3\" $_zenity_select_path_f" 2> /dev/null 1017 )" 1018 [ -z "$_zenity_select_path" ] && return $false 1019 eval "$2=\"\$_zenity_select_path\"" 1020 return $true 1021} 1022 1023dialog_retry() { 1024 zenity_run 'Error' --question --no-wrap --text="$1" \ 1025 --ok-label='Retry' --cancel-label='Ignore' 1026 case $? in 1027 0) dialog_retry_choice='retry' ;; 1028 1) dialog_retry_choice='ignore' ;; 1029 *) dialog_retry_choice='abort' ;; 1030 esac 1031} 1032 1033;; 1034 1035#----------------------------------------------------------------------------------------# 1036kdialog) 1037 1038# Helper functions 1039 1040kdialog_handle='' # dbus/dcop handle for the main progress window 1041 1042# Send a message to the main KDialog instance non-_q variants hide all output 1043kdialog_qdbus_q() { have qdbus && eval "qdbus $kdialog_handle \"\$@\"" 2> /dev/null ; } 1044kdialog_qdbus() { kdialog_qdbus_q "$@" > /dev/null ; } 1045kdialog_dcop_q() { have dcop && eval "dcop $kdialog_handle \"\$@\"" 2> /dev/null ; } 1046kdialog_dcop() { kdialog_dcop_q "$@" > /dev/null ; } 1047kdialog_cmd_q() { kdialog_qdbus_q "$@" || kdialog_dcop_q "$@" ; } 1048kdialog_cmd() { kdialog_cmd_q "$@" > /dev/null ; } 1049 1050# Run KDialog 1051# Usage: kdialog_run <title-prefix> <dialog-type> [<args>...] 1052kdialog_run() { 1053 _kdialog_run_t="$1" ; shift 1054 [ -z "$_kdialog_run_t" ] || _kdialog_run_t="$_kdialog_run_t - " 1055 kdialog --icon arx-libertatis --title "$_kdialog_run_t$_dialog_title" "$@" 1056} 1057 1058# Dialog abstraction 1059 1060# Create the main progress window. 1061# Usage: dialog_create 1062dialog_create() { 1063 dialog_destroy 1064 _kdialog_force_width='WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW' 1065 kdialog_handle="$(kdialog_run '' --progressbar "$_kdialog_force_width" 0)" 1066 [ -z "$kdialog_handle" ] && return $false 1067 kdialog_cmd showCancelButton true 1068 return $true 1069} 1070 1071# Destroy the main progress window. 1072# Usage: dialog_destroy 1073dialog_destroy() { 1074 [ -z "$kdialog_handle" ] && return $true 1075 kdialog_cmd close 1076 kdialog_handle='' 1077} 1078 1079# Show an error dialog. 1080# Usage: dialog_error <message> 1081dialog_error() { 1082 kdialog_run 'Error' --error "$1" > /dev/null 2> /dev/null 1083} 1084 1085# Show a message box. 1086# Usage: dialog_message <message> 1087dialog_message() { 1088 kdialog_run 'Status' --msgbox "$1" > /dev/null 2> /dev/null 1089} 1090 1091# Ask a yes/no question. 1092# Usage: dialog_ask <question> 1093dialog_ask() { 1094 kdialog_run 'Confirm' --warningyesno "$1" > /dev/null 2> /dev/null 1095} 1096 1097# Has the user quested to cancel the operation? 1098# Usage: dialog_cancelled && print "cancelled" 1099dialog_cancelled() { 1100 [ "$(kdialog_cmd_q wasCancelled || print true)" = true ] 1101} 1102 1103# Set the status text. 1104# Usage: dialog_set_text <text> 1105dialog_set_text() { 1106 kdialog_qdbus setLabelText "$1" || kdialog_dcop setLabel "$1" 1107} 1108 1109# Set if the progress bar should continously animate instead of showing the value. 1110# Usage: dialog_set_pulsate <enable> 1111dialog_set_pulsate() { 1112 _kdialog_max=100 1113 [ $1 = 1 ] && _kdialog_max=0 1114 kdialog_qdbus Set "" maximum $_kdialog_max || kdialog_dcop setMaximum $_kdialog_max 1115} 1116 1117# Set the current progress value. 1118# Usage: dialog_set_value <percentage> 1119dialog_set_value() { 1120 kdialog_qdbus Set "" value "$1" || kdialog_dcop setProgress "$1" 1121 [ $1 = 100 ] && kdialog_cmd showCancelButton true 1122} 1123 1124# Select an entry in a list. 1125# Usage dialog_select_entry <var> <label> <tag1> <item1> [ <tag2> < item2> ... ] 1126dialog_select_entry() { 1127 _kdialog_select_entry_v="$1" ; shift 1128 _kdialog_select_entry_t="$1" ; shift 1129 _kdialog_select_entry_w=" " 1130 _kdialog_select_entry_w="$_kdialog_select_entry_w$_kdialog_select_entry_w" 1131 _kdialog_select_entry_r="$( 1132 kdialog_run 'Select path' \ 1133 --menu "$_kdialog_select_entry_t$_kdialog_select_entry_w" "$@" 2> /dev/null 1134 )" 1135 [ -z "$_kdialog_select_entry_r" ] && return $false 1136 eval "$_kdialog_select_entry_v=\"\$_kdialog_select_entry_r\"" 1137 return $true 1138} 1139 1140# dialog_select_path does not support the --any flag 1141dialog_select_path_any=0 1142 1143# Let the user select a path. 1144# Usage: dialog_select_path (--file|--dir|--any) <result-var> <label> 1145# Any is only supported if $dialog_select_path_any is 1. 1146dialog_select_path() { 1147 case "$1" in 1148 --any) die 'not implemented' ;; 1149 --file) _kdialog_select_path_f=--getopenfilename ;; 1150 --dir) _kdialog_select_path_f=--getexistingdirectory ;; 1151 esac 1152 _kdialog_select_path="$( 1153 kdialog_run "$3" $_kdialog_select_path_f "$HOME" 2> /dev/null 1154 )" 1155 [ -z "$_kdialog_select_path" ] && return $false 1156 eval "$2=\"\$_kdialog_select_path\"" 1157 return $true 1158} 1159 1160dialog_retry() { 1161 kdialog_run 'Error' \ 1162 --yes-label 'Retry' --no-label 'Ignore' --cancel-label 'Abort' \ 1163 --warningyesnocancel "$1" 2>&1 1164 case $? in 1165 0) dialog_retry_choice='retry' ;; 1166 1) dialog_retry_choice='ignore' ;; 1167 *) dialog_retry_choice='abort' ;; 1168 esac 1169} 1170 1171;; 1172 1173#----------------------------------------------------------------------------------------# 1174Xdialog) 1175 1176# Helper functions 1177 1178# Run Xdialog 1179# Usage: Xdialog_run <title-prefix> <dialog-type> [<args>...] 1180Xdialog_run() { 1181 _Xdialog_run_t="$1" ; shift 1182 [ -z "$_Xdialog_run_t" ] || _Xdialog_run_t="$_Xdialog_run_t - " 1183 Xdialog --left --title "$_Xdialog_run_t$_dialog_title" "$@" 1184} 1185 1186# Dialog abstraction 1187 1188# Create the main progress window. 1189# Usage: dialog_create 1190dialog_create() { 1191 _Xdialog_width='WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW' 1192 pipe_create Xdialog --left --title "$_dialog_title$1" --gauge "$_Xdialog_width" 0 0 1193} 1194 1195# Destroy the main progress window. 1196# Usage: dialog_destroy 1197dialog_destroy() { 1198 pipe_destroy 1199} 1200 1201# Show an error dialog. 1202# Usage: dialog_error <message> 1203dialog_error() { 1204 dialog_message "$1" # no dedicated error box for Xdialog 1205} 1206 1207# Show a message box. 1208# Usage: dialog_message <message> 1209dialog_message() { 1210 Xdialog_run 'Status' --msgbox "$1" 0 0 1211} 1212 1213# Ask a yes/no question. 1214# Usage: dialog_ask <question> 1215dialog_ask() { 1216 Xdialog_run 'Confirm' --yesno "$1" 0 0 1217} 1218 1219# Has the user quested to cancel the operation? 1220# Usage: dialog_cancelled && print "cancelled" 1221dialog_cancelled() { 1222 ! pipe_exists 1223} 1224 1225# Set the status text. 1226# Usage: dialog_set_text <text> 1227dialog_set_text() { 1228 pipe_write 'XXX' 1229 pipe_write "$1" 1230 pipe_write 'XXX' 1231} 1232 1233# Set if the progress bar should continously animate instead of showing the value. 1234# Usage: dialog_set_pulsate <enable> 1235dialog_set_pulsate() { 1236 true # Pulsate is not supported by Xdialog 1237} 1238 1239# Set the current progress value. 1240# Usage: dialog_set_value <percentage> 1241dialog_set_value() { 1242 pipe_write "$1" 1243} 1244 1245# Select an entry in a list. 1246# Usage dialog_select_entry <var> <label> <tag1> <item1> [ <tag2> < item2> ... ] 1247dialog_select_entry() { 1248 _Xdialog_select_entry_v="$1" ; shift 1249 _Xdialog_select_entry_t="$1" ; shift 1250 _Xdialog_select_entry_r="$( 1251 Xdialog_run 'Select path' \ 1252 --menubox "$_Xdialog_select_entry_t" 20 80 10 "$@" 2>&1 1253 )" 1254 [ -z "$_Xdialog_select_entry_r" ] && return $false 1255 eval "$_Xdialog_select_entry_v=\"\$_Xdialog_select_entry_r\"" 1256 return $true 1257} 1258 1259# dialog_select_path does not support the --any flag 1260dialog_select_path_any=0 1261 1262# Let the user select a path. 1263# Usage: dialog_select_path (--file|--dir|--any) <result-var> <label> 1264# Any is only supported if $dialog_select_path_any is 1. 1265dialog_select_path() { 1266 case "$1" in 1267 --any) die 'not implemented' ;; 1268 --file) _Xdialog_select_path_f=--fselect ;; 1269 --dir) _Xdialog_select_path_f=--dselect ;; 1270 esac 1271 _Xdialog_select_path="$( 1272 Xdialog_run "$3" $_Xdialog_select_path_f "$HOME" 0 0 2>&1 1273 )" 1274 [ -z "$_Xdialog_select_path" ] && return $false 1275 eval "$2=\"\$_Xdialog_select_path\"" 1276 return $true 1277} 1278 1279dialog_retry() { 1280 Xdialog_run 'Error' --ok-label='Retry' --cancel-label='Ignore' --yesno "$1" 0 0 1281 case $? in 1282 0) dialog_retry_choice='retry' ;; 1283 1) dialog_retry_choice='ignore' ;; 1284 *) dialog_retry_choice='abort' ;; 1285 esac 1286} 1287 1288;; 1289 1290#----------------------------------------------------------------------------------------# 12910) # command-line 1292 1293# Dialog abstraction 1294 1295# Create the main progress window. 1296# Usage: dialog_create 1297dialog_create() { 1298 true 1299} 1300 1301# Destroy the main progress window. 1302# Usage: dialog_destroy 1303dialog_destroy() { 1304 true 1305} 1306 1307# Show an error dialog. 1308# Usage: dialog_error <message> 1309dialog_error() { 1310 true # error messages are always printed to stdout 1311} 1312 1313# Show a message box. 1314# Usage: dialog_message <message> 1315dialog_message() { 1316 true 1317} 1318 1319# Ask a yes/no question. 1320# Usage: dialog_ask <question> 1321dialog_ask() { 1322 die 'unimplemented' 1323} 1324 1325# Has the user quested to cancel the operation? 1326# Usage: dialog_cancelled && print "cancelled" 1327dialog_cancelled() { 1328 false # never cancelled SIGINT is not trapped 1329} 1330 1331# Set the status text. 1332# Usage: dialog_set_text <text> 1333dialog_set_text() { 1334 true 1335} 1336 1337# Set if the progress bar should continously animate instead of showing the value. 1338# Usage: dialog_set_pulsate <enable> 1339dialog_set_pulsate() { 1340 true 1341} 1342 1343# Set the current progress value. 1344# Usage: dialog_set_value <percentage> 1345dialog_set_value() { 1346 true 1347} 1348 1349# Select an entry in a list. 1350# Usage dialog_select_entry <var> <label> <tag1> <item1> [ <tag2> < item2> ... ] 1351dialog_select_entry() { 1352 _cli_select_entry_var="$1" ; shift 1353 1354 # Print a list for the user to select from 1355 print "$1:" ; shift 1356 _cli_select_entry_min=$1 1357 _cli_select_entry_max=$1 1358 _cli_select_entry_f=' [default]' 1359 while [ $# -gt 0 ] ; do 1360 _cli_select_entry_i=$1 ; shift 1361 _cli_select_entry_t="$1" ; shift 1362 if [ $_cli_select_entry_i -lt $_cli_select_entry_min ] ; then 1363 _cli_select_entry_min=$_cli_select_entry_i 1364 fi 1365 if [ $_cli_select_entry_i -gt $_cli_select_entry_max ] ; then 1366 _cli_select_entry_max=$_cli_select_entry_i 1367 fi 1368 printf ' %d) %s%s\n' $_cli_select_entry_i \ 1369 "$_cli_select_entry_t" "$_cli_select_entry_f" 1370 _cli_select_entry_f='' 1371 done 1372 1373 # Read a number (or empty string for the first entry) 1374 while true ; do 1375 1376 puts '> #' 1377 read -r _cli_select_entry_r 1378 1379 [ -z "$_cli_select_entry_r" ] && _cli_select_entry_r=1 1380 1381 case "$_cli_select_entry_r" in 1382 'quit') ;; 'q') ;; 'exit') ;; 'abort') ;; 1383 *) 1384 if [ ! "$_cli_select_entry_r" -lt $_cli_select_entry_min ] 2> /dev/null \ 1385 && [ ! "$_cli_select_entry_r" -gt $_cli_select_entry_max ] 2> /dev/null 1386 then 1387 eval "$_cli_select_entry_var=\"\$_cli_select_entry_r\"" 1388 return $true 1389 else 1390 printf "Please enter a number between %d and %d.\n" \ 1391 $_cli_select_entry_min $_cli_select_entry_max 1392 continue 1393 fi 1394 esac 1395 die 1396 1397 done 1398 1399 return $true 1400} 1401 1402# dialog_select_path supports the --any flag 1403dialog_select_path_any=1 1404 1405# Let the user select a path. 1406# Usage: dialog_select_path (--file|--dir|--any) <result-var> <label> 1407# Any is only supported if $dialog_select_path_any is 1. 1408dialog_select_path() { 1409 _cli_select_path_var="$2" 1410 print "$3:" ; shift 1411 puts '> ' 1412 read -r _cli_select_path_r 1413 [ -z "$_cli_select_path_r" ] && return $false 1414 eval "$_cli_select_path_var=\"\$_cli_select_path_r\"" 1415 return $true 1416} 1417 1418dialog_retry() { 1419 printf '\n%s\n' "${red}Error:${reset} $1" 1420 while true ; do 1421 print "Abort / [Retry] / Ignore" 1422 puts '> ' 1423 read -r _cli_dialog_retry_r 1424 case "$_cli_dialog_retry_r" in 1425 a|A|abort|Abort|ABORT) dialog_retry_choice='abort' ; return ;; 1426 ''|r|R|retry|Retry|RETRY) dialog_retry_choice='retry' ; return ;; 1427 i|I|ignore|Ignore|IGNORE) dialog_retry_choice='ignore' ; return ;; 1428 esac 1429 done 1430} 1431 1432esac 1433 1434 1435########################################################################################## 1436# Common user interface implementation 1437 1438# Ask the user if the setup should really be cancelled. 1439handle_cancel() { 1440 _handle_cancel_message="Are you sure you want to exit the Arx Fatalis data installer?" 1441 if [ $installed_stuff = 1 ] ; then 1442 _handle_cancel_message="$_handle_cancel_message 1443 1444Already installed files will not be removed!" 1445 fi 1446 dialog_ask "$_handle_cancel_message" && print 'Aborted by user' && die 1447} 1448 1449# Update the status. 1450# Usage: status (<percent>|--temp) [<message>] 1451_status_text="Initializing..." 1452_status_cur='' 1453_status_pulsate=default 1454status() { 1455 1456 if [ "$1" = '--temp' ] ; then 1457 _status_value=0 1458 _status_temp=1 1459 else 1460 _status_value=$1 1461 _status_temp=0 1462 fi 1463 _status_new="${2:-$_status_text}" 1464 1465 # Handle the cancel and close buttons 1466 if dialog_cancelled ; then 1467 handle_cancel 1468 dialog_create || die "Could not re-create progress window." 1469 _status_cur='' 1470 _status_pulsate=default 1471 fi 1472 1473 # Update the progress text if one was provided 1474 if [ ! "$_status_cur" = "$_status_new" ] ; then 1475 _status_cur="$_status_new" 1476 print "$_status_new" 1477 dialog_set_text "$_status_new" 1478 fi 1479 [ $_status_temp = 0 ] && _status_text="$_status_new" 1480 1481 # Set the maximum progress value 1482 if [ $_status_value = 0 ] ; then _new_status_pulsate=1 ; else _new_status_pulsate=0 ; fi 1483 if [ ! "$_status_pulsate" = $_new_status_pulsate ] ; then 1484 _status_pulsate="$_new_status_pulsate" 1485 dialog_set_pulsate $_status_pulsate 1486 fi 1487 1488 # Update the progress value 1489 [ $_status_pulsate = 0 ] && dialog_set_value $_status_value 1490 1491} 1492 1493# Print an error message and show an error dialog if we have a GUI/ 1494# Usage: error <message> 1495error() { 1496 print "${dim_red}$1${reset}" 1497 dialog_error "$1" 1498} 1499 1500# Let the user select an item from a list or enter a custom one. 1501# In batch mode, select the first one if --first is given, die otherwise. 1502# Usage: user_select_entry (--existing|--writable) (--any|--file|--dir) \ 1503# <list> <result-var> <desc> <desc-color> <list-color> <verb> 1504user_select_entry() { 1505 1506 _user_select_entry_access="$1" 1507 _user_select_entry_t="$2" 1508 _user_select_entry_lname="$3" 1509 eval "_user_select_entry_list=\"\$$_user_select_entry_lname\"" 1510 _user_select_entry_var="$4" 1511 _user_select_entry_desc="$5" 1512 _user_select_entry_color1="$6" 1513 _user_select_entry_color2="$7" 1514 _user_select_entry_verb="$8" 1515 1516 # Select the first element if in batch mode 1517 eval "_user_select_entry_current=\"\$$_user_select_entry_var\"" 1518 if [ $batch = 1 ] || [ ! -z "$_user_select_entry_current" ] ; then 1519 _user_select_entry_='' 1520 eval "set -- $_user_select_entry_list" 1521 for _user_select_entry ; do 1522 _user_select_entry_="$_user_select_entry" 1523 break 1524 done 1525 [ -z "$_user_select_entry_" ] && die "Missing $_user_select_entry_desc!" 1526 eval "$_user_select_entry_var=\"\$_user_select_entry_\"" 1527 return $true 1528 fi 1529 1530 if [ $gui = 0 ] 1531 then print 1532 else status --temp "Select a ${_user_select_entry_desc}" 1533 fi 1534 1535 _user_select_entry_i=1 1536 _user_select_entry_nolist=0 1537 if [ -z "$_user_select_entry_list" ] \ 1538 && [ ! $_user_select_entry_t = --any ] \ 1539 && [ ! $dialog_select_path_any = 1 ] ; then 1540 1541 # No entries detected - direclty promt the user 1542 _user_select_entry_num=1 1543 _user_select_entry_nolist=1 1544 1545 else 1546 1547 _user_select_entry_tlist='' 1548 1549 # Format the list entries in a user friendly way 1550 eval "set -- $_user_select_entry_list" 1551 for _user_select_entry ; do 1552 1553 # Use 'command (file)' if comment available or 'file' otherwise 1554 _user_select_entry_comment="$( 1555 list_comment "$_user_select_entry_lname" "$(($_user_select_entry_i - 1))" 1556 )" 1557 if [ -z "$_user_select_entry_comment" ] ; then 1558 _user_select_entry_label="$_user_select_entry_color2$_user_select_entry$reset" 1559 else 1560 _user_select_entry_label="$_user_select_entry_color2$_user_select_entry_comment$reset" 1561 _user_select_entry_label="$_user_select_entry_label: $_user_select_entry" 1562 fi 1563 1564 # Add the tag and label to the arguments 1565 list_append _user_select_entry_tlist $_user_select_entry_i 1566 list_append _user_select_entry_tlist "$_user_select_entry_label" 1567 1568 # Remeber the file for the tag 1569 eval "_user_select_entry_$_user_select_entry_i=\"\$_user_select_entry\"" 1570 1571 _user_select_entry_i=$(($_user_select_entry_i + 1)) 1572 done 1573 1574 # Add entries for custom files/diectories 1575 if [ $_user_select_entry_t = --any ] && [ $dialog_select_path_any = 1 ] ; then 1576 list_append _user_select_entry_tlist $_user_select_entry_i 1577 list_append _user_select_entry_tlist "Select file or directory to $_user_select_entry_verb.." 1578 else 1579 _user_select_entry_ii=$_user_select_entry_i 1580 if [ $_user_select_entry_t = --any ] || [ $_user_select_entry_t = --file ] ; then 1581 list_append _user_select_entry_tlist $_user_select_entry_ii 1582 list_append _user_select_entry_tlist "Select file to $_user_select_entry_verb..." 1583 _user_select_entry_ii=$(($_user_select_entry_ii + 1)) 1584 fi 1585 if [ $_user_select_entry_t = --any ] || [ $_user_select_entry_t = --dir ] ; then 1586 list_append _user_select_entry_tlist $_user_select_entry_ii 1587 list_append _user_select_entry_tlist "Select directory to $_user_select_entry_verb..." 1588 _user_select_entry_ii=$(($_user_select_entry_ii + 1)) 1589 fi 1590 fi 1591 1592 fi 1593 1594 # Loop until we have selected a value 1595 while true ; do 1596 1597 if [ $_user_select_entry_nolist = 0 ] ; then 1598 1599 # Ask the user to select an entry 1600 eval "set -- $_user_select_entry_tlist" 1601 while true ; do 1602 if dialog_select_entry _user_select_entry_num \ 1603 "${_user_select_entry_color1}Select a ${_user_select_entry_desc}${reset}" "$@" 1604 then 1605 break 1606 else 1607 handle_cancel 1608 fi 1609 done 1610 1611 fi 1612 1613 if [ $_user_select_entry_num -lt $_user_select_entry_i ] ; then 1614 1615 # User selected an entry -- return that 1616 eval "$_user_select_entry_var=\"\$_user_select_entry_$_user_select_entry_num\"" 1617 return $true 1618 1619 fi 1620 1621 # Loop until we have selected a path of the correct type 1622 while true ; do 1623 1624 # Adjust type based on user selection 1625 if [ $_user_select_entry_t = --any ] && [ ! $dialog_select_path_any = 1 ] ; then 1626 if [ $_user_select_entry_num = $_user_select_entry_i ] 1627 then _user_select_entry_type=--file 1628 else _user_select_entry_type=--dir 1629 fi 1630 else 1631 _user_select_entry_type=$_user_select_entry_t 1632 fi 1633 1634 _user_select_entry_c="Select a custom source " 1635 1636 if ! dialog_select_path "$_user_select_entry_type" _user_select_entry_path \ 1637 "${_user_select_entry_color1}Choose a custom ${_user_select_entry_desc}${reset}" \ 1638 "$@" ; then 1639 1640 # User cancelled the dialog - return to the main selection, unless there is none 1641 if [ $_user_select_entry_nolist = 1 ] ; then 1642 handle_cancel 1643 continue # let the user try again 1644 fi 1645 break # return to list selection 1646 1647 fi 1648 1649 _user_select_entry_path="$(abspath "$_user_select_entry_path")" 1650 1651 # The user selected a path, now check if it exists our criteria. 1652 case "$_user_select_entry_access" in 1653 --existing) 1654 if [ ! -e "$_user_select_entry_path" ] ; then 1655 error "$_user_select_entry_path does not exist!" 1656 continue # let the user try again 1657 fi ;; 1658 --writable) 1659 if ! is_writable "$_user_select_entry_path" ; then 1660 error "$_user_select_entry_path is not writable!" 1661 continue # let the user try again 1662 fi ;; 1663 esac 1664 case "$_user_select_entry_t" in 1665 --any) ;; # anything goes 1666 --file) 1667 if [ -e "$_user_select_entry_path" ] && [ -d "$_user_select_entry_path" ] ; then 1668 error "$_user_select_entry_path is is a directory, but we need a file!" 1669 continue 1670 fi ;; 1671 --dir) 1672 if [ -e "$_user_select_entry_path" ] && [ ! -d "$_user_select_entry_path" ] ; then 1673 error "$_user_select_entry_path is is a file, but we need a directory!" 1674 continue 1675 fi ;; 1676 esac 1677 1678 # Everything went better than expected - save the result 1679 eval "$_user_select_entry_var=\"\$_user_select_entry_path\"" 1680 return $true 1681 1682 done 1683 1684 done 1685 1686} 1687 1688# Show error message and close windows on exit 1689ui_cleanup() { 1690 [ -z "$1" ] || dialog_error "$1" 1691 dialog_destroy 1692} 1693on_exit ui_cleanup 1694 1695# Initilize the UI 1696dialog_create || die " 1697Could not create main window. 1698 1699You could try the --cli option. 1700" 1701[ $gui = 0 ] || status --temp "$_status_text" 1702 1703 1704########################################################################################## 1705# Autodetect source file/dir 1706 1707sourcefiles='' # List of source file/directory candidates 1708 1709# Add a source file or directory to the list of candidates. 1710# Usage: found_source_file <file> [comment] 1711# Return: $true if more source files should be probed, $false otherwise. 1712found_source_file() { 1713 set_append sourcefiles "$(canonicalize "$1")" "$2" 1714 if [ $batch = 1 ] ; then return $true ; else return $false ; fi 1715} 1716 1717# Find a match for a case insensitive path containing arx.exe 1718# Usage: probe_wine_path <baseprefix> <ipath> 1719probe_wine_path() { 1720 [ -d "$1" ] || return $false 1721 if [ -z "$2" ] ; then 1722 if icontains "$1" 'arx.exe' ; then 1723 found_source_file "$1" "Wine" 1724 fi 1725 return $false 1726 fi 1727 _wine_path_dir="${2%%\\*}" 1728 if [ "$_wine_path_dir" = "$2" ] 1729 then _wine_path='' 1730 else _wine_path="${2#*\\}" 1731 fi 1732 _wine_paths="$( 1733 find "$1/" -mindepth 1 -maxdepth 1 -iname "$(escape "$_wine_path_dir")" -print \ 1734 | lines_to_list 1735 )" 1736 [ -z "$_wine_paths" ] && return $false 1737 _wine_path_call='probe_wine_path "$_wine_path_prefix" "$_wine_path" && return $true' 1738 eval "for _wine_path_prefix in $_wine_paths ; do $_wine_path_call ; done" 1739} 1740 1741# Find possible source directories from a registry key. 1742# Usage: probe_wine_registry <key> <variable> 1743# Return: $true if more source files should be probed, $false otherwise. 1744probe_wine_registry() { 1745 1746 # This is intentionally implemented without calling wine as we don't want to 1747 # modify the source. 1748 1749 # Get candidate paths from the registry file 1750 _wine_pattern='Software\\\\(Wow6432Node\\\\)?' 1751 _wine_pattern="$_wine_pattern$(print "$1" | sed 's/\\/\\\\/g' | escape_pipe '()|')" 1752 _wine_paths="$( 1753 # The --after is just an arbitrary limit 1754 # We verify the paths by checking for arx.exe, but for effiency we still want 1755 # to avoid false positives. 1756 cat "$_wine_prefix"/*.reg \ 1757 | grep -iPA 20 "^\\[$_wine_pattern\\]" 2> /dev/null \ 1758 | grep -iP "^\"?$2\"?=" 2> /dev/null \ 1759 | sed 's/^[^=]*="\([^"]*\)".*$/\1/;s/\\\(.\)/\1/g' \ 1760 | lines_to_list 1761 )" 1762 1763 # For each candidate, find a case-insensitive match and check if it contains arx.exe 1764 eval "set -- $_wine_paths" 1765 for _wine_path ; do 1766 probe_wine_path "$_wine_prefix/dosdevices" "$_wine_path" && return $true 1767 done 1768 1769 return $false 1770} 1771 1772# Find possible source directories from uninstall registry entries. 1773# Usage: probe_wine_uninstall_info <id> 1774# Return: $true if more source files should be probed, $false otherwise. 1775probe_wine_uninstall_info() { 1776 probe_wine_registry "Microsoft\\Windows\\CurrentVersion\\Uninstall\\$1" \ 1777 'InstallLocation' 1778} 1779 1780# Find possible source directories in a WINEPREFIX. 1781# Usage: probe_wineprefix <wineprefix> 1782# Return: $true if more source files should be probed, $false otherwise. 1783probe_wineprefix() { 1784 1785 [ -d "$1" ] || return $false 1786 [ -e "$1/system.reg" ] || [ -e "$1/user.reg" ] || return $false 1787 _wine_prefix="$1" 1788 1789 # Normal install 1790 probe_wine_registry 'Arkane Studios\Installed Apps\Arx Fatalis' 'Folder' && return $true 1791 1792 # GOG version 1793 probe_wine_registry 'GOG.com\GOGARXFATALIS' 'PATH' && return $true 1794 1795 # Probe uninstall entries - combined for effiency 1796 _wine_uninstall='Microsoft\Windows\CurrentVersion\Uninstall\' 1797 # Steam 1798 _wine_steam='Steam App 1700' 1799 # Original game 1800 _wine_orig='{96443F45-13E2-11D6-AC87-00D0B7A9E540}' 1801 # 1.21 patch 1802 _wine_patch='{171251E0-4EED-4EA1-A46D-3213A226F2B3}_is1' 1803 probe_wine_registry "$_wine_uninstall($_wine_steam|$_wine_orig|$_wine_patch)" \ 1804 'InstallLocation' && return $true 1805 1806} 1807 1808# Check a mounte point if it's an Arx Fatalis cd 1809# Usage: probe_cd <mountpoint> <fstype> 1810# Return: $true if more source files should be probed, $false otherwise 1811probe_cd() { 1812 node="$1" 1813 mountpoint="$2" 1814 fstype="$3" 1815 1816 case "$fstype" in 1817 cd9660) ;; iso9660) ;; udf) ;; *fuseiso) ;; 1818 *) return $true 1819 esac 1820 1821 if [ -d "$mountpoint/bin" ] && icontains "$mountpoint/bin" 'arx.ttf' 2> /dev/null ; then 1822 found_source_file "$mountpoint" "CDROM at $node" && return $true 1823 fi 1824 1825} 1826 1827# Find mounted Arx Fatalis CDs 1828# Usage: probe_cdrom 1829# Return: $true if more source files should be probed, $false otherwise 1830probe_cdrom() { 1831 1832 _probe_cdrom_mountpoints="$( 1833 cat /proc/mounts 2> /dev/null | lines_to_list # Linux - use /proc/mounts 1834 mount -p 2> /dev/null | lines_to_list # Hope that mount has a -p option 1835 )" 1836 1837 eval "set -- $_probe_cdrom_mountpoints" 1838 for _probe_cdrom_line ; do 1839 eval "probe_cd $(escape "$_probe_cdrom_line" ' ' | sed 's/\\\\\040/\\ /g')" # space-separated 1840 eval "probe_cd $(print "$_probe_cdrom_line" | tr '\t' '\n' | lines_to_list)" # tab-separated 1841 done 1842 1843} 1844 1845# Find possible source files/directories 1846# Usage: probe_source_files 1847# If sourcefile is already set, uses that. 1848probe_source_files() { 1849 1850 if [ ! -z "$sourcefile" ] ; then 1851 1852 # Trust the user 1853 found_source_file "$sourcefile" && return $true 1854 1855 # But also allow to refile the dir in non-batch mode if it is a wineprefix 1856 [ -d "$sourcefile" ] && probe_wineprefix "$sourcefile" 1857 1858 [ "$sourcefiles" = "$sourcefile" ] || sourcefile='' 1859 1860 return $true 1861 fi 1862 1863 status 5 "Searching for source files..." 1864 1865 # Find by filename 1866 probe_files found_source_file "$gog_names" 'GOG.com setup' 1867 probe_files found_source_file "$demo_names" 'Demo' 1868 1869 # Find an Arx Fatalis installation in $WINEPREFIX 1870 [ ! -z "${WINEPREFIX-}" ] && probe_wineprefix "$WINEPREFIX" && return $true 1871 1872 # Find an Arx Fatalis installation in ~/.wine 1873 [ ! "${WINEPREFIX-}" = "$HOME/.wine" ] && probe_wineprefix "$HOME/.wine" && return $true 1874 1875 # Find an Arx Fatalis cdrom 1876 probe_cdrom && return $true 1877 1878 # Just in case it will ever be installable under non-Windows systems 1879 steam_source_path="$HOME/.steam/root/SteamApps/common/Arx Fatalis" 1880 [ -d "$steam_source_path" ] && found_source_file "$steam_source_path" 'Steam' 1881 1882} 1883 1884 1885########################################################################################## 1886# Autodetect destination dir 1887 1888datadirs='' # List of destination data directory candidate 1889 1890# Add a destination data directory to the list of candidates. 1891# Usage: found_data_dir <dir> [comment] 1892# Return: $true if more data directories should be probed, $false otherwise. 1893found_data_dir() { 1894 set_append datadirs "$(abspath "$1")" "$2" 1895 if [ $batch = 1 ] ; then return $true ; else return $false ; fi 1896} 1897 1898# Add a destination data directory to the list of candidates if it is writable. 1899# In verify mode, also existing read-only directories are added. 1900# Usage: probe_data_dir <must-exists> <dir> [comment] 1901# Return: $true if more data directories should be probed, $false otherwise. 1902probe_data_dir() { 1903 [ "$1" = 1 ] && [ ! -e "$2" ] && return $false 1904 [ $install = 0 ] && [ ! -e "$2" ] && return $false 1905 if [ $install = 1 ] || [ $patch = 1 ] ; then is_writable "$2" || return $false ; fi 1906 found_data_dir "$2" "$3" 1907} 1908 1909# Find possible destination data directories 1910# Usage: probe_data_dirs 1911# If datadir is already set, uses that. 1912probe_data_dirs() { 1913 1914 if [ ! -z "$datadir" ] ; then 1915 found_data_dir "$datadir" 1916 return $true 1917 fi 1918 1919 status 10 "Searching for destination directories..." 1920 1921 for _probe_data_dirs_existing in 1 0 ; do 1922 1923 # Try paths supplied by wrapper scripts 1924 if [ ! -z "$data_path" ] ; then 1925 eval "set -- $(to_list "$data_path")" 1926 for _probe_data_dirs_path ; do 1927 probe_data_dir $_probe_data_dirs_existing "$_probe_data_dirs_path" "portable" \ 1928 && return $true 1929 done 1930 fi 1931 1932 # Try system paths 1933 eval "set -- $(to_list "$data_dirs")" 1934 for _probe_data_dirs_prefix in "$@" ; do 1935 eval "set -- $(to_list "$data_dir_suffixes")" 1936 for _probe_data_dirs_suffix ; do 1937 probe_data_dir $_probe_data_dirs_existing \ 1938 "$_probe_data_dirs_prefix/$_probe_data_dirs_suffix" "system" \ 1939 && return $true 1940 done 1941 done 1942 1943 # Try user paths 1944 if [ $is_root = 0 ] ; then 1945 eval "set -- $(to_list "$user_dir_suffixes")" 1946 for _probe_data_dirs_suffix ; do 1947 probe_data_dir $_probe_data_dirs_existing \ 1948 "$data_home/$_probe_data_dirs_suffix" "user" \ 1949 && return $true 1950 done 1951 fi 1952 1953 done 1954 1955} 1956 1957 1958########################################################################################## 1959# Extract helpers and other utility abstractions 1960 1961# Calculate the MD5 checksum of a file. 1962# Usage: checksum <result-var> <file> 1963checksum() { 1964 1965 if have md5sum ; then 1966 _checksum_result="$(md5sum -b "$2" | sed 's/ .*//g')" 1967 eval "$1=\"\$_checksum_result\"" 1968 return $true 1969 fi 1970 1971 if have md5 ; then 1972 _checksum_result="$(md5 -q "$2")" 1973 eval "$1=\"\$_checksum_result\"" 1974 return $true 1975 fi 1976 1977 die "You need either md5sum or md5." 1978} 1979 1980have_run() { 1981 eval "_have_run_p=\"\$${1}_reqs\"" 1982 eval "for _have_run_program in $_have_run_p ; do have \$_have_run_program && return $true ; done" 1983 return $false 1984} 1985 1986# Extract an archive file to the current directory. 1987# Usage: extract <file> <types>... 1988have_extract() { 1989 have_run "extract_$1" 1990} 1991extract() { 1992 _extract_file="$1" ; shift 1993 1994 while true ; do 1995 1996 _extract_missing='' 1997 for _extract_type ; do 1998 1999 if "have_extract_${_extract_type}" ; then 2000 "extract_${_extract_type}" "$_extract_file" && return "$true" 2001 else 2002 list_merge _extract_missing extract_${_extract_type}_reqs 2003 fi 2004 2005 done 2006 2007 _extract_msg="${white}Could not extract $(basename "$_extract_file")${reset}" 2008 [ -z "$_extract_missing" ] \ 2009 || _extract_msg="$_extract_msg 2010 2011Please install one or more of the following: 2012$(print_help_list " - $dim_pink" _extract_missing)" 2013 2014 [ $batch = 1 ] && die "Error: $_extract_msg" 2015 2016 dialog_retry "$_extract_msg" 2017 case $dialog_retry_choice in 2018 abort) die "Error extracting files" ;; 2019 retry) continue ;; 2020 ignore) return $true ;; 2021 esac 2022 2023 done 2024 2025} 2026 2027# Extract a .zip file to the current directory. 2028# Usage: extract_zip <zipfile> 2029have_extract_zip() { have_extract zip ; } 2030extract_zip() { 2031 2032 if have bsdtar ; then 2033 printf 'Extracting %s using bsdtar\n' "$1" 2034 bsdtar xvf "$1" 2035 return $? 2036 fi 2037 2038 if have unzip ; then 2039 puts 'unzip: ' 2040 unzip "$1" 2041 return $? 2042 fi 2043 2044 for _extract_zip_sz in 7za 7z ; do 2045 if have $_extract_zip_sz ; then 2046 $_extract_zip_sz x "$1" 2047 return $? 2048 fi 2049 done 2050 2051 die "no program to extract $1" 2052} 2053 2054# Mount a .iso file or CDROM using fuseiso if available or normal mount if root. 2055# Usage: mount_cdrom <cdromfile> <mountpoint> 2056have_mount_cdrom() { 2057 have fuseiso && have fusermount && return $true 2058 have mount && have umount && [ $is_root = 1 ] && return $true 2059 return $false 2060} 2061mount_cdrom() { 2062 2063 if have fuseiso && have fusermount && fuseiso "$1" "$2" ; then 2064 printf 'Mounted %s at %s using fuseiso\n' "$1" "$2" 2065 return $true 2066 fi 2067 2068 if have mount && have umount && [ $is_root = 1 ] && mount -o loop,ro "$1" "$2" ; then 2069 printf 'Mounted %s at %s\n' "$1" "$2" 2070 return $true 2071 fi 2072 2073 die "no program to extract $1" 2074} 2075 2076# Unmount a CDROM that was mounted using mount_cdrom. 2077# Usage: unmount_cdrom <mountpoint> 2078unmount_cdrom() { 2079 have fusermount && fusermount -u "$1" > /dev/null 2>&1 2080 have umount && [ $is_root = 1 ] && umount "$1" > /dev/null 2>&1 2081} 2082 2083# isoinfo wrapper to extract all files to the current directory 2084extract_isoinfo() { 2085 _extract_isoinfo_file="$1" 2086 2087 # Get a list of all files in the ISO image 2088 _extract_isoinfo_files="$( 2089 isoinfo -i "$_extract_isoinfo_file" -J -f | grep ';1$' | lines_to_list 2090 )" 2091 [ -z "$_extract_isoinfo_files" ] && return $false 2092 2093 eval "set -- $_extract_isoinfo_files" 2094 for _extract_isoinfo_e ; do 2095 2096 # Remove leading / and trailing ;1 from filenames 2097 _extract_isoinfo_f="$(print "$_extract_isoinfo_e" | sed 's:^/*::;s:;1$::')" 2098 [ -z "$_extract_isoinfo_f" ] && continue 2099 printf ' - %s\n' "$_extract_isoinfo_f" 2100 2101 # Create subdirectories as needed 2102 _extract_isoinfo_d="$(dirname "$_extract_isoinfo_f")" 2103 [ -z "$_extract_isoinfo_d" ] || mkdir -p "$_extract_isoinfo_d" || return $false 2104 2105 # Extract the file 2106 isoinfo -i "$_extract_isoinfo_file" -J -x "$_extract_isoinfo_e" \ 2107 > "$_extract_isoinfo_f" || return $false 2108 2109 # Don't rely on isoinfo setting a non-zero return code, check that we got something 2110 [ -s "$_extract_isoinfo_f" ] || return $false 2111 2112 done 2113 2114 return $true 2115} 2116 2117# Extract a .iso file or CDROM to the current directory. 2118# Usage: extract_iso <cdromfile> 2119have_extract_iso() { have_extract iso ; } 2120extract_iso() { 2121 2122 if have isoinfo ; then 2123 printf 'Extracting %s using isoinfo\n' "$1" 2124 extract_isoinfo "$1" 2125 return $? 2126 fi 2127 2128 ret=$false 2129 2130 if have bsdtar ; then 2131 printf 'Extracting %s using bsdtar\n' "$1" 2132 bsdtar xvf "$1" 2133 ret="$?" 2134 2135 # Older versions of bsdtar don't always get the names right - fix them 2136 _extrac_cdrom_f="$(find "$PWD" -depth -iname '*;1' | lines_to_list)" 2137 if [ ! -z "$_extrac_cdrom_f" ] ; then 2138 eval "for _extract_iso_f in $_extrac_cdrom_f ; do mv -f \"\$_extract_iso_f\" \"\$(print \"\$_extract_iso_f\" | sed 's:;1$::')\" ; done" 2139 fi 2140 2141 else if have 7z ; then 2142 7z x -tiso "$1" 2143 ret="$?" 2144 fi ; fi 2145 2146 # For some iso files bsdtar just does nothing - at least let the user know 2147 if [ ! -d "$PWD/bin" ] || ! icontains "$PWD/bin" 'arx.ttf' ; then 2148 _extract_iso_err="${yellow}It looks like bsdtar/p7zip didn't do what it was supposed to - this will likely fail!${reset} 2149 2150You might have better luck with ${dim_pink}isoinfo${reset} or ${dim_pink}fuseiso${reset}, or by manually mounting the CD/ISO." 2151 printf '%s\n' "$_extract_iso_err" 2152 [ $batch = 0 ] && dialog_message "$_extract_iso_err" 2153 fi 2154 2155 return $ret 2156} 2157 2158# Extract a microsoft .cab or .exe file to the current directory. 2159# Usage: extract_ms_cab <cabfile> 2160extract_cab_check_bsdtar() { 2161 if have bsdtar ; then 2162 case "$(bsdtar --version)" in 2163 # These versions have bugs that cause corrupted files 2164 '') ;; 2165 *'libarchive 1.'*) ;; 2166 *'libarchive 2.'*) ;; 2167 *'libarchive 3.0') ;; 2168 *'libarchive 3.0.'*) ;; 2169 # Newer versions should work fine 2170 *) 2171 return $true 2172 esac 2173 fi 2174 return $false 2175} 2176have_extract_ms_cab() { 2177 extract_cab_check_bsdtar && return $true 2178 have cabextract || have 7za || have 7z 2179} 2180extract_ms_cab() { 2181 2182 if extract_cab_check_bsdtar ; then 2183 printf 'Extracting %s using bsdtar\n' "$1" 2184 bsdtar xvf "$1" 2185 return $? 2186 fi 2187 2188 if have cabextract ; then 2189 puts 'cabextract: ' 2190 cabextract "$1" 2191 return $? 2192 fi 2193 2194 for _extract_ms_cab_sz in 7za 7z ; do 2195 if have $_extract_ms_cab_sz ; then 2196 $_extract_ms_cab_sz x "$1" 2197 return $? 2198 fi 2199 done 2200 2201 die "no program to extract $1" 2202} 2203 2204# Extract an InstallShield .cab or .exe file to the current directory. 2205# Usage: extract_installshield <cabfile> 2206have_extract_installshield() { have_extract installshield ; } 2207extract_installshield() { 2208 2209 if have unshield ; then 2210 puts 'unshield: ' 2211 unshield x "$1" 2212 return $? 2213 fi 2214 2215 die "no program to extract $1" 2216} 2217 2218# Extract an Inno Setup .exe file to the current directory. 2219# Usage: extract_innosetup <exefile> 2220have_extract_innosetup() { have innoextract ; } 2221innosetup_language='' 2222extract_innosetup() { 2223 2224 if have innoextract ; then 2225 puts 'innoextract: ' 2226 if [ -z "$innosetup_language" ] 2227 then innoextract --color=off "$1" ; return $? 2228 else innoextract --color=off --language="$innosetup_language" "$1" ; return $? 2229 fi 2230 fi 2231 2232 die "no program to extract $1" 2233} 2234 2235# Extract all .cab files in a directory. 2236# Usage: extract_cab_files <sourcedir> <destdir> 2237extract_cab_files() { 2238 2239 _extract_cab_files_i=0 2240 _extract_cab_files_files="$( 2241 find "$1/" -mindepth 1 -type f -iname '*.cab' -print \ 2242 | lines_to_list 2243 )" 2244 eval "set -- $_extract_cab_files_files" 2245 for _extract_cab_files_cabfile ; do 2246 [ -z "$_extract_cab_files_cabfile" ] && continue 2247 2248 while true ; do 2249 _extract_cab_files_cabdir="$sourcedir/cab.$_extract_cab_files_i" 2250 if [ -e "$_extract_cab_files_cabdir" ] ; then 2251 _extract_cab_files_i=$(($_extract_cab_files_i + 1)) 2252 continue 2253 fi 2254 create_dir "$_extract_cab_files_cabdir" "cab #$i work" 2255 break 2256 done 2257 2258 cd "$_extract_cab_files_cabdir" 2259 case "$(basename "$_extract_cab_files_cabfile")" in 2260 data*) extract "$_extract_cab_files_cabfile" installshield ms_cab ;; 2261 *) extract "$_extract_cab_files_cabfile" ms_cab installshield ;; 2262 esac 2263 2264 _extract_cab_files_i=$(($_extract_cab_files_i + 1)) 2265 done 2266 2267} 2268 2269# Download a file. 2270# Usage: download_file <url> <destination> 2271have_download() { have_run download ; } 2272download_impl() { 2273 2274 if have wget ; then 2275 wget -O "$2" "$1" 2276 return $? 2277 fi 2278 2279 if have curl ; then 2280 curl --location --fail -o "$2" "$1" 2281 return $? 2282 fi 2283 2284 if have fetch ; then 2285 fetch -o "$2" "$1" 2286 return $? 2287 fi 2288 2289 die "no program to download $1" 2290} 2291download_file() { 2292 2293 download_impl "$1" "$2" || return $false 2294 2295 # Check that we got something useful 2296 case "$(file --dereference --brief --mime-type "$2" 2> /dev/null)" in 2297 */html*) ;; 2298 */xml*) ;; 2299 *) return $true 2300 esac 2301 2302 rm -f "$2" 2303 return $false; 2304} 2305 2306# Download a file from a list of mirrors. 2307# Usage: download <callback> <name> <names> <urls> <destdir> 2308download() { 2309 _download_callback="$1" 2310 _download_name="$2" 2311 _download_names="$3" 2312 _download_urls="$4" 2313 _download_dest="$5" 2314 2315 while true ; do 2316 2317 probe_files "$_download_callback" "$_download_names" && return $true 2318 2319 _download_missing='' 2320 if have_download ; then 2321 eval "for _download_url in $_download_urls ; do download_file \"\$_download_url\" \"\$_download_dest\" && \$_download_callback \"\$_download_dest\" && return $true ; done" 2322 else 2323 list_merge _download_missing download_reqs 2324 fi 2325 2326 _download_msg="${white}Could not download ${_download_name}${reset}" 2327 [ -z "$_download_missing" ] \ 2328 || _download_msg="$_download_msg 2329 2330Please install one of the following: 2331$(print_help_list " - $dim_pink" _download_missing)" 2332 2333 _download_msg="$_download_msg 2334 2335You can download the file manually from 2336$(print_help_list " - $dim_blue" _download_urls) 2337 2338and put it in one of these locations: 2339$(print_help_list " - $dim_white" probe_file_dirs)" 2340 2341 [ $batch = 1 ] && die "Error: $_download_msg" 2342 2343 dialog_retry "$_download_msg" 2344 case $dialog_retry_choice in 2345 abort) die "Error downloading files" ;; 2346 retry) continue ;; 2347 ignore) return $true ;; 2348 esac 2349 2350 done 2351 2352} 2353 2354 2355########################################################################################## 2356# Unpack source 2357 2358workdir='' 2359cleanup_workdir() { 2360 [ ! -z "$workdir" ] && [ -e "$workdir" ] && rm -rf "$workdir" 2361} 2362# Create the work directory if it doesn't exist. 2363# Also regiter a cleanup function to remove the work directory on exit. 2364# Usage: create_workdir 2365create_workdir() { 2366 [ -z "$workdir" ] || return $true 2367 workdir="$datadir/$command-temp" 2368 cleanup_workdir 2369 on_exit cleanup_workdir 2370 create_dir "$workdir" 'work' 2371} 2372 2373# Extract setup*.cab files from a source directory into $sourcedir/cab.*. 2374# Usage: extract_source_dir <sourcedir> 2375extract_source_dir() { 2376 extract_cab_files "$1" "$sourcedir" 2377} 2378 2379# Extract a source executable into $sourcedir/exe. 2380# Usage: extract_source_exe <sourcefile> 2381extract_source_exe() { 2382 2383 sourcedir_exe="$sourcedir/exe" 2384 create_dir "$sourcedir_exe" 'exe work' 2385 2386 cd "$sourcedir_exe" || die 2387 case "$(basename "$1")" in 2388 arx_jpn_*.exe) extract "$1" ms_cab installshield innosetup ;; # Japanese demo 2389 *) extract "$1" innosetup ms_cab installshield ;; # GOG.com setup 2390 esac 2391 2392 extract_source_dir "$sourcedir_exe" 2393 2394} 2395 2396# Wrap mount_cdrom et al so we can use them with extract() 2397extract_mount_cdrom_reqs='' 2398list_merge extract_mount_cdrom_reqs mount_cdrom_reqs 2399have_extract_mount_cdrom() { have_mount_cdrom ; } 2400extract_mount_cdrom() { 2401 # Ignore the current working directory, always mount to $cdromdir 2402 if mount_cdrom "$1" "$cdromdir" ; then 2403 # Pretent the mountpoint is the original source 2404 sourcefile="$cdromdir" 2405 sourcedir_cdrom="$cdromdir" 2406 return $true 2407 else 2408 return $false 2409 fi 2410} 2411 2412cdromdir='' 2413cleanup_cdrom() { 2414 [ ! -z "$cdromdir" ] && [ -e "$cdromdir" ] && unmount_cdrom "$cdromdir" 2415} 2416# Mount a source CDROM/ISO and adjust $sourcefile or extract it into $sourcedir/cdrom. 2417# Usage: extract_source_cdrom <sourcefile> 2418extract_source_cdrom() { 2419 2420 # Try to mount the cdrom to avoid unneeded copies 2421 # Keep the mount point out of $sourcedir so we don't try to mv files from it 2422 cdromdir="$workdir/cdrom" 2423 cleanup_cdrom 2424 on_exit cleanup_cdrom 2425 create_dir "$cdromdir" 'mount work' 2426 2427 # Otherwise, extract the files from the CDROM if we have the required tools 2428 sourcedir_cdrom="$sourcedir/cdrom" 2429 create_dir "$sourcedir_cdrom" 'cdrom work' 2430 cd "$sourcedir_cdrom" || die 2431 2432 extract "$1" mount_cdrom iso 2433 2434 # Extract any cab files on the CDROM 2435 extract_source_dir "$sourcedir_cdrom" 2436} 2437 2438# Extract a source executable into $sourcedir/zip. 2439# Also extracts contained setup*.cab files into $sourcedir/cab.*. 2440# Usage: extract_source_zip <sourcefile> 2441extract_source_zip() { 2442 2443 sourcedir_zip="$sourcedir/zip" 2444 create_dir "$sourcedir_zip" 'zip work' 2445 2446 cd "$sourcedir_zip" || die 2447 extract "$1" zip 2448 2449 extract_source_dir "$sourcedir_zip" 2450} 2451 2452sourcedir='' 2453# Extract the source file or directory if it hansn't been extracted already. 2454# Usage: extract_source 2455extract_source() { 2456 [ -z "$sourcedir" ] || return $true 2457 2458 status --temp "${white}Extracting source...${reset}" 2459 2460 create_workdir 2461 sourcedir="$workdir/source" 2462 create_dir "$sourcedir" 'source work' 2463 2464 if [ -d "$sourcefile" ] ; then 2465 extract_source_dir "$sourcefile" 2466 else if [ -f "$sourcefile" ] ; then 2467 case "$sourcefile" in 2468 *.zip) extract_source_zip "$sourcefile" ;; 2469 *.exe) extract_source_exe "$sourcefile" ;; 2470 *.iso) extract_source_cdrom "$sourcefile" ;; 2471 *) 2472 case "$(file --dereference --brief --mime-type "$sourcefile" 2> /dev/null)" in 2473 application/zip) extract_source_zip "$sourcefile" ;; 2474 application/x-dosexec) extract_source_exe "$sourcefile" ;; 2475 application/x-iso9660-image) extract_source_cdrom "$sourcefile" ;; 2476 *) die "Unknown source file type: $sourcefile" 2477 esac 2478 esac 2479 else 2480 extract_source_cdrom "$sourcefile" 2481 fi ; fi 2482 2483 print 2484} 2485 2486 2487########################################################################################## 2488# Detect data language 2489 2490find_file_impl() { 2491 _patchable="$1" 2492 2493 # Search for both file.ext and file_default.ext 2494 _file="$(escape "$(basename "$2")")" 2495 _file_d="$(print "$_file" | sed 's/^\(.*\)\(\.[^.]*\)$/\1_default\2/')" 2496 2497 # Prefer files from the patch - if availbale, they are most likely the correct ones 2498 set -- -iname "$_file" -print -o -iname "$_file_d" -print 2499 [ "$_patchable" = 1 ] && [ ! -z "$patchdir" ] && find "$patchdir" "$@" 2500 2501 # Find the file in the source 2502 [ ! -z "$sourcedir" ] && find "$sourcedir" "$@" 2503 [ -d "$sourcefile" ] && find "$sourcefile" "$@" 2504 2505 # Also find the file if it is already in the data directory, but don't ignore case 2506 find "$datadir" -path '*-temp' -prune -o \ 2507 -name "$_file" -print -o -name "$_file_d" -print 2508 2509} 2510# Find a file in patch, source and data directories. 2511# Usage: find_file <is-patchable> <return-list-var> <filename/path> 2512find_file() { 2513 eval "$2=\"\$(find_file_impl \"\$1\" \"\$3\" | lines_to_list)\"" 2514} 2515 2516# Copy/move a file to the data diectory. 2517# Usage: use_file <file> <data-path> 2518# Action taken depends on the source directory. 2519use_file() { 2520 _in="$1" 2521 _out="$datadir/$2" 2522 2523 # Don't change anything on verify-only mode 2524 [ $install = 0 ] && [ $patch = 0 ] && return $true 2525 2526 # Don't try to copy/move a file onto itself 2527 [ "$_in" = "$_out" ] && return $true 2528 2529 # Create directories as needed 2530 _outdir="$(dirname "$_out")" 2531 create_dir "$_outdir" 'output' 2532 2533 # Copy or move the file 2534 if [ "${_in#"$sourcefile"}" = "$_in" ] 2535 then mv -f "$_in" "$_out" || die "Could not move $_in to $_out!" 2536 else cp -f "$_in" "$_out" || die "Could not copy $_in to $_out!" 2537 fi 2538 installed_stuff=1 2539 2540 # Fix permissions 2541 chmod --reference="$_outdir" "$_out" > /dev/null 2>&1 2542 chmod -x "$_out" > /dev/null 2>&1 2543} 2544 2545data_lang='' # Data language/tipe ID 2546data_lang_desc='' # Friendly data language/type label 2547 2548# Detect the data language and type 2549# Usage: detect_data_langauge <callback> 2550# <callback> receives a speech.pak checksum and sets data_lang and data_lang_desc 2551detect_data_langauge() { 2552 callback="$1" 2553 2554 if [ $install = 1 ] 2555 then _detect_data_langauge_status=40 2556 else _detect_data_langauge_status=10 2557 fi 2558 [ $gui = 0 ] || status $_detect_data_langauge_status 'Detecting data language...' 2559 puts "${white}Detecting data language..." 2560 2561 _speech_checksums='' 2562 2563 find_file 0 _speech_files 'speech.pak' 2564 eval "set -- $_speech_files" 2565 for _speech_file ; do 2566 2567 checksum _speech_checksum "$_speech_file" 2568 2569 data_lang='' 2570 "$callback" "$_speech_checksum" 2571 2572 if [ -z "$data_lang" ] ; then 2573 list_append _speech_checksums "$_speech_checksum" 2574 else 2575 use_file "$_speech_file" 'speech.pak' 2576 break 2577 fi 2578 2579 done 2580 2581 if [ -z "$data_lang" ] ; then 2582 printf '\n' 2583 case "$_speech_checksums" in 2584 '') die "speech*.pak not found" ;; 2585 *) die "Unsupported data language - speech*.pak checksum: $_speech_checksums" ;; 2586 esac 2587 fi 2588 2589 printf " ${green}%s${reset}\n\n" "${data_lang_desc}" 2590 2591} 2592 2593 2594########################################################################################## 2595# Get the patch file 2596 2597# Warn if the user-supplied patch file has a suspicious name. 2598# Usage: patch_check_file_name <file> <expected-name-list> 2599patch_check_file_name() { 2600 _user_patch_name="$(basename "$1")" 2601 _patch_check_file_names="$2" 2602 if ! list_contains _patch_check_file_names "$_user_patch_name" ; then 2603 print "${yellow}Warning: unexpected patch file name: %s" "$_user_patch_name" >&2 2604 printf "Expected %s${reset}\n" "$(print_help_or _patch_check_file_names)" >&2 2605 fi 2606} 2607 2608# Find or download a patch file. 2609# Usage: probe_patch_file_impl <callback> <name> <user-supplied-file> \ 2610# <expected-name-list> <url-list> \ 2611# <callback> will be called with the patch file 2612probe_patch_file_impl() { 2613 2614 _patch_found="$1" 2615 _patch_name="$2" 2616 _patch_file="$3" 2617 _patch_names="$4" 2618 _patch_urls="$5" 2619 2620 if [ ! -z "$_patch_file" ] ; then 2621 2622 # Check the filename of user-supplied patchse 2623 patch_check_file_name "$_file" "$_patch_names" 2624 2625 "$_patch_found" "$_patch_file" 2626 return $true 2627 fi 2628 2629 [ $probe_patch = 0 ] && return $true 2630 2631 # Probe local files now so we don't lie abut downloading it 2632 probe_files "$_patch_found" "$_patch_names" && return $true 2633 2634 status --temp "${white}Downloading patch ${blue}${_patch_name}${reset}..." 2635 2636 create_workdir 2637 download "$_patch_found" "$_patch_name" \ 2638 "$_patch_names" "$_patch_urls" "${workdir}/${_patch_name}" \ 2639 && return $true 2640 2641} 2642 2643# Callback for the japanese patch. 2644patch_jp_found() { 2645 patchfile_jp="$(abspath "$1")" 2646 printf "Using Japanese %s patch: ${blue}%s${reset}\n" \ 2647 "$patch_jp_ver" "$patchfile_jp" 2648 return $true 2649} 2650 2651# Callback for the main patch. 2652patch_found() { 2653 patchfile="$(abspath "$1")" 2654 printf "Using %s patch: ${blue}%s${reset}\n" "$patch_ver" "$patchfile" 2655 return $true 2656} 2657 2658# Find the patch file(s) for a specific language. 2659# Usage: probe_patch_file <language> 2660# If patchfile is already set, uses that. 2661probe_patch_file() { 2662 2663 _patch_file_lang='' 2664 case "$1" in 2665 'german') _patch_file_lang='GE' ;; 2666 'english') _patch_file_lang='EN' ;; 2667 'spanish') _patch_file_lang='ES' ;; 2668 'french') _patch_file_lang='FR' ;; 2669 'italian') _patch_file_lang='IT' ;; 2670 'russian') _patch_file_lang='RU' ;; 2671 'japanese') 2672 _patch_jp_names='' 2673 list_append _patch_jp_names "$patch_jp_name" 2674 probe_patch_file_impl patch_jp_found "$patch_jp_name" "$patchfile_jp" \ 2675 "$_patch_jp_names" "$patch_jp_urls" 2676 ;; 2677 esac 2678 _patch_names='' 2679 list_append _patch_names "$patch_name" 2680 if [ ! -z "$_patch_file_lang" ] ; then 2681 list_append _patch_names "$(printf "$patch_name_localized" "$_patch_file_lang")" 2682 fi 2683 2684 probe_patch_file_impl patch_found "$patch_name" "$patchfile" \ 2685 "$_patch_names" "$patch_urls" 2686 2687} 2688 2689patchdir='' # Directory where the patch file(s) are extracted 2690 2691# Extract all patch files for a specific language. 2692# Usage: extract_patch <language> 2693# Does nothing if the patch files are already extracted. 2694extract_patch() { 2695 [ -z "$patchdir" ] || return $true 2696 _extract_patch_lang="$1" 2697 2698 print 2699 2700 # Search for and download the patch files if needed 2701 probe_patch_file "$_extract_patch_lang" 2702 2703 status --temp "${white}Extracting patch...${reset}" 2704 2705 create_workdir 2706 patchdir="$workdir/patch" 2707 create_dir "$patchdir" 'patch work' 2708 2709 # Extract the main patch file 2710 if [ ! -z "$patchfile" ] ; then 2711 _patchdir_main="$patchdir/main" 2712 create_dir "$_patchdir_main" 'main patch work' 2713 cd "$_patchdir_main" 2714 innosetup_language="$_extract_patch_lang" 2715 extract "$patchfile" innosetup 2716 innosetup_language='' 2717 fi 2718 2719 if [ ! -z "$patchfile_jp" ] ; then 2720 2721 # Extract the Japanese patch file 2722 _patchdir_jp="$patchdir/main" 2723 create_dir "$_patchdir_jp" 'jp patch work' 2724 cd "$_patchdir_jp" 2725 extract "$patchfile_jp" ms_cab 2726 2727 # Also extract contained files 2728 extract_cab_files "$_patchdir_jp" "$patchdir" 2729 2730 fi 2731 2732 print 2733 status --temp 2734} 2735 2736 2737########################################################################################## 2738# Copy and verify files 2739 2740checksum_failed=0 # Was there any mismatched checksum or missing file so far? 2741 2742# Handle a required file: find, compare checksum and copy/move if needed. 2743# Usage: required_file <is-patchable> <filepath> <checksums> 2744required_file() { 2745 2746 _patchable="$1" 2747 _name="$2" 2748 _valid="$3" 2749 2750 find_file 1 _files "$_name" 2751 eval "set -- $_files" 2752 _checksums='' 2753 for _file ; do 2754 checksum _checksum "$_file" 2755 2756 if list_contains _valid "$_checksum" ; then 2757 # We found a match - use it 2758 printf ' - %s\n' "$_name" 2759 use_file "$_file" "$_name" 2760 return $true 2761 fi 2762 2763 # Remember mismatched checksums so we can output debug info if none matched 2764 list_append _checksums "$_checksum" 2765 continue 2766 2767 done 2768 2769 # No matching file found! 2770 2771 # If we didn't use the patch yet, fetch it and try again! 2772 if [ $patch = 1 ] && [ $_patchable = 1 ] && [ -z "$patchdir" ] ; then 2773 extract_patch "$data_lang" 2774 if [ ! -z "$patchdir" ] ; then 2775 required_file "$_patchable" "$_name" "$_valid" 2776 return $? 2777 fi 2778 fi 2779 2780 # Let the user know that something is wrong! 2781 if [ -z "$_checksums" ] ; then 2782 printf "${red}Missing ${dim_red}%s${red}!${reset}\n" "$_name" >&2 2783 else 2784 printf "${red}Checksum failed for ${dim_red}%s${reset}:\n" "$_name" >&2 2785 printf " expected: ${dim_red}%s${reset}\n" "$(print_help_or _valid)" >&2 2786 printf " actual: ${dim_red}%s${reset}\n" "$(print_help_or _checksums)" >&2 2787 fi 2788 2789 # Be optimistic, copy the first result even if the checksum doesn't match! 2790 # We will display an error at the end (end exit with $false), but it may still work. 2791 eval "set -- $_files" 2792 for _file ; do 2793 use_file "$_file" "$_name" 2794 break 2795 done 2796 2797 checksum_failed=1 2798 return $false 2799} 2800 2801# Handle an optional file: find and copy/move if it exists. 2802# Usage: optional_file <filepath> 2803optional_file() { 2804 _name="$1" 2805 find_file 1 _files "$_name" 2806 eval "set -- $_files" 2807 for _file ; do 2808 # There is no checksum, just copy the first file 2809 printf ' - %s\n' "$_name" 2810 use_file "$_file" "$_name" 2811 break 2812 done 2813} 2814 2815 2816########################################################################################## 2817# Setup 2818 2819# Select source file / directory 2820if [ $install = 1 ] ; then 2821 probe_source_files 2822 if [ $batch = 0 ] ; then 2823 list_append sourcefiles 'Patch existing install' '' 2824 list_append sourcefiles 'Verify existing install only' '' 2825 fi 2826 user_select_entry --existing --any sourcefiles sourcefile \ 2827 "source file or directory to install from" "$green" "$dim_green" 'install from' 2828 case "$sourcefile" in 2829 'Patch existing install') install=0 ; patch=1 ;; 2830 'Verify existing install only') install=0 ; patch=0 ;; 2831 *) [ -e "$sourcefile" ] || die "Missing source file: $sourcefile" 2832 esac 2833 set_append probe_file_dirs "$(dirname "$sourcefile")" 2834fi 2835 2836# Select destination data directory 2837probe_data_dirs 2838if [ $install = 1 ] ; then 2839 verb='install to' ; access=--writable 2840else if [ $patch = 1 ] ; then 2841 verb='patch' ; access=--existing 2842else 2843 verb='verify' ; access=--existing 2844fi ; fi 2845user_select_entry $access --dir datadirs datadir \ 2846 "data directory to $verb" "$cyan" "$dim_cyan" "$verb" 2847[ -z "$datadir" ] && die "Missing data dir." 2848if [ $install = 1 ] ; then 2849 create_dir "$datadir" 'data' 2850else 2851 [ -d "$datadir" ] || die "Missing data dir: $datadir" 2852fi 2853 2854# Extract source files 2855if [ $install = 1 ] ; then 2856 printf "\nInstalling Arx Fatalis data files \nfrom %s\nto %s\n\n" \ 2857 "${green}$sourcefile${reset}" "${cyan}$datadir${reset}" 2858 extract_source 2859else 2860 printf "\nVerifying Arx Fatalis data files \nin %s\n\n" "${cyan}$datadir${reset}" 2861fi 2862 2863 2864########################################################################################## 2865# Required files 2866 2867# Detect language 2868determine_language() { 2869 speech_checksum="$1" # speech.pak 2870 2871 case "$speech_checksum" in 2872 '4e8f962d8204bcfd79ce6f3226d6d6de') data_lang='english' ;; 2873 '4c3fdb1f702700255924afde49081b6e') data_lang='german' ;; 2874 'ab8a93161688d793a7c78fbefd7d133e') data_lang='german' ;; 2875 '2f88c67ae1537919e69386d27583125b') data_lang='spanish' ;; 2876 '4edf9f8c799190590b4cd52cfa5f91b1') data_lang='french' ;; 2877 '81f05dea47c52d43f01c9b44dd8fe962') data_lang='italian' ;; 2878 '677163bc319cd1e9aa1b53b5fb3e9402') data_lang='russian' ;; 2879 '235b86700fc80b3eb86731d748013a38') data_lang='japanese' ;; 2880 '62ca7b1751c0615ee131a94f0856b389') data_lang='english-demo' ;; 2881 'eeacbd9a845ecc00054934e82e9d7dd3') data_lang='japanese-demo' ;; 2882 esac 2883 2884 case "$data_lang" in 2885 'english') data_lang_desc='English' ;; 2886 'german') data_lang_desc='German' ;; 2887 'spanish') data_lang_desc='Spanish' ;; 2888 'french') data_lang_desc='French' ;; 2889 'italian') data_lang_desc='Italian' ;; 2890 'russian') data_lang_desc='Russian' ;; 2891 'japanese') data_lang_desc='Japanese' ;; 2892 'english-demo') data_lang_desc='English (demo)' ;; 2893 'japanese-demo') data_lang_desc='Japanese (demo)' ;; 2894 esac 2895 2896} 2897detect_data_langauge determine_language 2898 2899if [ $install = 1 ] ; then 2900 progress=50 2901 status $progress "${white}Copying and verifying files...${reset}" 2902 case "$data_lang" in *-demo) increment=8 ;; *) increment=1 ;; esac 2903else 2904 progress=15 2905 status $progress "${white}Verifying files...${reset}" 2906 case "$data_lang" in *-demo) increment=14 ;; *) increment=2 ;; esac 2907fi 2908print " - speech.pak" 2909 2910# Usage: f <is-patchable> <file> <checksums>... 2911f() { 2912 2913 # Update progress bar 2914 progress=$(($progress + $increment)) 2915 status $progress 2916 2917 # Verify & copy file 2918 required_file "$@" 2919} 2920 2921# speech.pak - already copied in detect_data_langauge 2922 2923# loc.pak contains the localized text, so it's different for each language! 2924case "$data_lang" in 2925 german) loc_checksum='31bc35bca48e430e108db1b8bcc2621d' ;; 2926 english) loc_checksum='a47b192493afb5794e2161a62d35b69f' ;; 2927 spanish) loc_checksum='121f99608814a2c9c5857cfadb665553' ;; 2928 french) loc_checksum='f8fc448fea12469ed94f417c313fe5ea' ;; 2929 italian) loc_checksum='a9e162f2916f5737a95bd8c5bd8a979e' ;; 2930 russian) loc_checksum='a131bf2398ee70a9c22a2bbffd9d0d99' ;; 2931 japanese) loc_checksum='9dcb0f5d7a517be4f1d9190419900892' ;; 2932 english-demo) loc_checksum='2ae16d3925c597dca70f960f175def3a' ;; 2933 japanese-demo) loc_checksum='9d84cede805b13fdf7fce856ecc15b19' ;; 2934 *) loc_checksum='' 2935esac 2936if [ ! -z "$loc_checksum" ] ; then 2937 f 1 'loc.pak' "$loc_checksum" 2938fi 2939 2940# misc/arx.ttf is the same for everything except japanese 2941# there are also separate misc/arx_russian.ttf and misc/arx_taiwanese.ttf handled later 2942case "$data_lang" in 2943 japanese*) font_checksum='58eab00842d8adea8d553ae1f66b0c9b' ;; 2944 *) font_checksum='9a95ff96795c034524ba1c2e94ea12c7' ;; 2945esac 2946if [ ! -z "$font_checksum" ] ; then 2947 f 1 'misc/arx.ttf' "$font_checksum" 2948fi 2949 2950case "$data_lang" in 2951 2952 english-demo) 2953 f 0 'data2.pak' 958b78f8f370b06d769843137138c461 2954 f 0 'data.pak' 5d7ba6e6c79ebf7fbb232eaced9e8ad9 2955 f 0 'misc/logo.bmp' aa3dfbd4bc9c863d10a0c5345ae5a4c9 2956 f 0 'sfx.pak' ea1b3e6d6f4906905d4a34f07e9a59ac 2957 ;; 2958 2959 japanese-demo) 2960 f 0 'data2.pak' 958b78f8f370b06d769843137138c461 2961 f 0 'data.pak' 903dfe1878a0cedff3b941fd3aa22ba9 2962 f 0 'misc/logo.bmp' aa3dfbd4bc9c863d10a0c5345ae5a4c9 2963 f 0 'sfx.pak' ea1b3e6d6f4906905d4a34f07e9a59ac 2964 ;; 2965 2966 *) # full game 2967 2968 f 1 'graph/interface/misc/arkane.bmp' afff1099c01ffeb03b9a351f7b5966b6 2969 f 1 'graph/interface/misc/quit1.bmp' 41445d3792a1f8818d950aca47254488 2970 f 1 'graph/obj3d/textures/fixinter_barrel.jpg' 8419274acbff7346c3661b18d6aad6dc 2971 f 1 'graph/obj3d/textures/fixinter_bell.bmp' 5743b9047c9ad65540c318dfcc98123a 2972 f 1 'graph/obj3d/textures/fixinter_metal_door.jpg' f246eff6b19c9c710313b4a4dce96a69 2973 f 1 'graph/obj3d/textures/fixinter_public_notice.bmp' f81394abbb9006ce0950843b7909db33 2974 f 1 'graph/obj3d/textures/item_bread.bmp' 544448f8eedc912aa231a6a04fffb7c5 2975 f 1 'graph/obj3d/textures/item_club.jpg' 7e26c4199ddaca494c8b369294306b0b 2976 f 1 'graph/obj3d/textures/item_long_sword.jpg' 3a6196fe9b7666c7d80d82be06f6de86 2977 f 1 'graph/obj3d/textures/item_mauld_sabre.jpg' 18492c25ebac02f83e2f0ebda61ecb00 2978 f 1 'graph/obj3d/textures/item_mauldsword.jpg' 503a5c2f23668040c675aefdde6dbbe5 2979 f 1 'graph/obj3d/textures/item_mirror.jpg' c0a22b4f7a7a6461da68206e94928637 2980 f 1 'graph/obj3d/textures/item_ring_casting.bmp' 348f9add709bacee08556d1f8cf10f3f 2981 f 1 'graph/obj3d/textures/item_rope.bmp' ff05de281c8b380ee98f6e123d3d51cb 2982 f 1 'graph/obj3d/textures/item_spell_sheet.jpg' 024ccbb520020f92fba5a5a4f0270cea 2983 f 1 'graph/obj3d/textures/item_torch2.jpg' 027951899b4829599ca611010ea3484f 2984 f 1 'graph/obj3d/textures/item_torch.jpg' 9ada166f23ddcb775ac20836e752187e 2985 f 1 'graph/obj3d/textures/item_zohark.bmp' cd206a4027f86c6e57b7710c94049efa 2986 f 1 'graph/obj3d/textures/l7_dwarf_[wood]_board08.jpg' 79ccc81adb7c37b98f40b478ef1fccd4 2987 f 1 'graph/obj3d/textures/l7_dwarf_[wood]_board80.jpg' 691611087b13d38ef02bb9dfd6a2518e 2988 f 1 'graph/obj3d/textures/npc_dog.bmp' 116bd374c14ae8c387a4da1899e1dca7 2989 f 1 'graph/obj3d/textures/npc_pig.bmp' b7a4d0d3d230b2d1470176909004e38b 2990 f 1 'graph/obj3d/textures/npc_pig_dirty.bmp' 76034d8d74056c8a982479d36321c228 2991 f 1 'graph/obj3d/textures/npc_rat_base.bmp' 00c585ec9ebe8006d7ca72993de7b51b 2992 f 1 'graph/obj3d/textures/npc_rat_base_cm.bmp' cae38facbf77db742180b9e58d0eb42f 2993 f 1 'graph/obj3d/textures/npc_worm_body_part1.jpg' 0b220bffaedc89fa663f08d12630c342 2994 f 1 'graph/obj3d/textures/npc_worm_body_part2.bmp' 20797cb78f6393a0fb5405969ba9f805 2995 f 1 'graph/obj3d/textures/[wood]_light_door.jpg' 00d0b018e995e7d013d6e52e92126901 2996 f 1 'misc/arx_russian.ttf' 921561e83786efcd25f92147b60a13db 2997 f 1 'misc/arx_taiwanese.ttf' da59198061cef0761c6b2fca113f76f6 2998 f 1 'misc/logo.avi' 63ed31a4eb3d226c23e58cfaa974d484 2999 f 1 'misc/logo.bmp' afff1099c01ffeb03b9a351f7b5966b6 3000 f 1 'data2.pak' f7e0ce700bf963429ac535ca86f8a7b4 3001 3002 f 0 'sfx.pak' 2efc9a74c517fd1ee9919900cf4091d2 3003 3004 # data.pak is censored in some versions (presumably has less gore) 3005 # At least the original german and italian CDs have the censored version. 3006 # The censored version has different level files and a different 3007 # human_female_villager model. 3008 # There are also minor differences in the scripts, but those are 3009 # overwritten by data2.pak from the 1.21 patch. 3010 data_checksum_original='a91a0b39a046233debbb10b4850e13eb' 3011 data_checksum_censored='a88d239dc7919ab113ff45483cb4ad46' 3012 f 0 'data.pak' "$data_checksum_original $data_checksum_censored" 3013 3014esac 3015 3016# Optional files - we don't need them, but copy them anyway if available 3017optional_file 'manual.pdf' 3018optional_file 'map.pdf' 3019 3020print 3021 3022 3023########################################################################################## 3024# Print a summary 3025 3026if [ $install = 1 ] ; then verb='Installed' ; else verb='Verified' ; fi 3027printf "${white}%s Arx Fatalis %s data: ${green}%s${reset}\n" "$verb" \ 3028 "$patch_ver" "$data_lang_desc" 3029 3030if [ $checksum_failed = 1 ] ; then 3031 [ $gui = 0 ] || status 100 "Error!" 3032 die "There are wrong or missing files!${reset} 3033 3034The game may run fine, or it may fail - good luck!" >&2 3035fi 3036 3037status 100 "${dim_green}All good!${reset}" 3038if [ $install = 1 ] ; then verb='Installation' ; else verb='Verification' ; fi 3039dialog_message "$verb complete: $data_lang_desc 3040 3041Have fun playing Arx Fatalis!" 3042 3043quit $true 3044