summarylogtreecommitdiffstats
path: root/daggerfall-launcher.pl
diff options
context:
space:
mode:
Diffstat (limited to 'daggerfall-launcher.pl')
-rwxr-xr-xdaggerfall-launcher.pl1881
1 files changed, 1881 insertions, 0 deletions
diff --git a/daggerfall-launcher.pl b/daggerfall-launcher.pl
new file mode 100755
index 000000000000..9df29a211d26
--- /dev/null
+++ b/daggerfall-launcher.pl
@@ -0,0 +1,1881 @@
+#!/usr/bin/perl
+use warnings;
+use strict;
+
+#=================================================================================
+# Daggerfall Launcher
+#=================================================================================
+#
+# This launcher should work, but I cannot guarantee that,
+# it was written on Arch Linux and not tried anywhere else
+# if you want to use it on other system, adjust configuration
+# below and keep your fingers crossed!
+#
+# Run it with "--help" option to get help.
+#
+# Remember that you use it at your own risk :-)
+
+#=================================================================================
+# License
+#=================================================================================
+# Copyright (C) 2011 by Andrzej Giniewicz <gginiu@gmail.com>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+#=================================================================================
+# Configuration variables
+#=================================================================================
+my $user_group = 'games';
+my $daggerfall_path = "/usr/share/games/daggerfall";
+my $license = "/usr/share/licenses/daggerfall/license";
+my $dosbox = "/usr/bin/dosbox";
+my $dosbox_config = "dagger.conf";
+my $daggerfall_dir = "DAGGER";
+my $palettes_dir = "palettes";
+my $license_lock = "terms-accepted";
+my $save_backup_dir = "save-backups";
+my $archive_type = ".tar.xz";
+my $archiver_pack = "tar -cJf 'ARCHIVE' *";
+my $archiver_unpack = "tar -xJf 'ARCHIVE'";
+my $mods_dir = "mods";
+my $mod_backup_dir = "modbackup";
+
+#=================================================================================
+# Declarations and description of available functions
+#=================================================================================
+
+# check if terms of use were accepted
+# no arguments
+# returns boolean
+sub terms_accepted;
+
+# get terms of use
+# no arguments
+# returns array of lines
+sub get_terms;
+
+# accept terms of use
+# no arguments
+# no return value
+sub accept_terms;
+
+# run Daggerfall, requires that terms of use are already accepted
+# no arguments
+# no return value
+sub run_daggerfall;
+
+# run sound setup utility
+# no arguments
+# no return value
+sub run_setup;
+
+# run save fixing utility
+# no arguments
+# no return value
+sub run_fixsave;
+
+# run map fixing utility
+# no arguments
+# no return value
+sub run_fixmaps;
+
+# get brightness increase in steps
+# (0 steps = no change; 1 step = multiply gamma by 1.1, 2 steps = multiply gamma by 1.2, etc)
+# no arguments
+# returns number
+sub get_brightness;
+
+# set brightness increase in steps
+# (0 steps = no change; 1 step = multiply gamma by 1.1, 2 steps = multiply gamma by 1.2, etc)
+# takes number of steps
+# no return value
+sub set_brightness;
+
+# get wagon capacity in lbs
+# no arguments
+# returns number
+sub get_wagon_capacity;
+
+# set wagon capacity in lbs
+# takes number representing wagon capacity
+# no return value
+sub set_wagon_capacity;
+
+# check if skill levels above 100 are unlocked
+# no arguments
+# returns boolean
+sub get_high_skills;
+
+# enable or disable skill levels above 100
+# takes boolean, 0 to disable, 1 to enable
+# no return value
+sub set_high_skills;
+
+# get view distance in save stored in given slot
+# takes save slot number (0 to 5)
+# returns view distance (0 to 255)
+sub get_view_distance;
+
+# set view distance in save stored in given slot
+# (in-game this cannot be set higher than 127)
+# takes save slot number (0 to 5) and view distance (0 to 255)
+# no return value
+sub set_view_distance;
+
+# check if cheat mode is enabled
+# no arguments
+# returns boolean
+sub get_cheat_mode;
+
+# enable or disable cheat mode
+# takes boolean, 0 to disable, 1 to enable
+# no return value
+sub set_cheat_mode;
+
+# check if magic item repairs are enabled
+# no arguments
+# returns boolean
+sub get_magic_repair;
+
+# enable or disable magic item repairs
+# takes boolean, 0 to disable, 1 to enable
+# no return value
+sub set_magic_repair;
+
+# get current save names
+# no arguments
+# returns hash from slot numbers (0-5) to save names (strings)
+sub get_current_saves;
+
+# get archived save names
+# no arguments
+# returns hash from slot numbers (0-5) to hashes from names (strings) to array
+# of dates (YYYY_MM_DD_HH_MM_SS) when saves were archived
+sub get_archived_saves;
+
+# archive current save from given slot
+# takes slot number (0-5)
+# no return value
+sub archive_save;
+
+# check if there is save in given slot
+# takes slot number (0-5)
+# returns boolean, 1 for occupied slot, 0 otherwise
+sub is_slot_occupied;
+
+# restore selected save into given slot
+# (possible specifications:
+# (slot numer) -> same as (slot number)-(game name), where (game name) is
+# name of game currently in given slot
+# (slot number)-(game name) -> same as (slot number)-(game name)-(date),
+# where (date) is date of last archived save from given slot with given name
+# (slot number)-(game name)-(date) -> full specification, unpacks save named
+# (game name) archived from slot (slot number) on (date)
+# takes archived save specification (string) and target slot (0-5)
+# no return value
+sub restore_save;
+
+# get list of installed mods
+# no arguments
+# returns array of installed mod names
+sub get_mods;
+
+# get list of enabled mods
+# no arguments
+# returns array of enabled mod names
+sub get_enabled_mods;
+
+# get list of available mod groups
+# no arguments
+# returns array of available mod group names
+sub get_mod_groups;
+
+# get list of mod or group dependencies
+# no arguments
+# returns array of mod and group names
+sub get_direct_mod_dependencies;
+
+# get list of mod or group dependencies (recursively)
+# no arguments
+# returns array of mod names
+sub get_all_mod_dependencies;
+
+# get list of enabled mods requiring given mod
+# takes string (mod name)
+# returns array of mod names
+sub get_mods_requiring;
+
+# enable mod
+# takes string (name of mod or group to enable)
+# no return value
+sub enable_mod;
+
+# disable mod
+# takes string (name of mod to disable)
+# no return value
+sub disable_mod;
+
+# refresh all installed mods to currently installed versions
+# no arguments
+# no return value
+sub refresh_mods;
+
+#=================================================================================
+# Gory details :-)
+#=================================================================================
+
+use File::Copy qw(copy move);
+use File::Find qw(find);
+use File::Path qw(remove_tree);
+use File::Spec::Functions qw(catfile);
+use List::Util qw(min max);
+
+my $gid = getgrnam($user_group);
+
+sub terms_accepted
+{
+ return ( -e catfile($daggerfall_path, $license_lock) );
+}
+
+sub accept_terms
+{
+ my $file = catfile($daggerfall_path, $license_lock);
+ open(FILE, ">$file") or die "Cannot create license lock";
+ close(FILE);
+ chmod 0664, $file;
+ chown -1, $gid, $file;
+}
+
+sub get_terms
+{
+ open(FILE, "<$license") or die "Cannot open license";
+ my @text = <FILE>;
+ close(FILE);
+ return @text;
+}
+
+sub fix_dirs;
+sub fix_dirs
+{
+ my $path = shift;
+ chmod 0775, $path;
+ chown -1, $gid, $path;
+ opendir(DIR, $path) or die "Cannot access target directory";
+ my @files = readdir(DIR);
+ closedir(DIR);
+ @files = grep(!/\./, @files);
+ foreach my $file (@files) {
+ my $full = catfile($path, $file);
+ if ( -d $full) {
+ fix_dirs $full;
+ } else {
+ chmod 0664, $full;
+ chown -1, $gid, $full;
+ }
+ }
+}
+
+sub run_dosbox
+{
+ my ($app, $exit, $no_terms) = @_;
+ $no_terms or terms_accepted or die "Terms of usage not accepted";
+ my $run = catfile($daggerfall_path, $daggerfall_dir, $app);
+ ( -e $run) or die "Cannot find requested application";
+ my $cfg = catfile($daggerfall_path, $dosbox_config);
+ ( -e $cfg) or die "Cannot find dosbox config file";
+ if ($exit) {
+ system($dosbox." ".$run." -exit -conf ".$cfg);
+ } else {
+ system($dosbox." ".$run." -conf ".$cfg);
+ }
+ fix_dirs catfile($daggerfall_path, $daggerfall_dir);
+}
+
+sub run_daggerfall
+{
+ run_dosbox "RUN.BAT", 1, 0;
+}
+
+sub run_setup
+{
+ run_dosbox "SETUP.EXE", 1, 1;
+}
+
+sub run_fixsave
+{
+ run_dosbox "FIXSAVE.EXE", 0, 1;
+}
+
+sub run_fixmaps
+{
+ run_dosbox "FIXMAPS.EXE", 0, 1;
+}
+
+sub get_brightness
+{
+
+ my $pal = catfile($daggerfall_path, $palettes_dir);
+ ( -d $pal ) or return 0;
+
+ my $file = catfile($pal, "now");
+ ( -e $file) or return 0;
+
+ open(FILE, "<$file") or die "Cannot open brighness record";
+ binmode(FILE);
+ my $buffer="";
+ read(FILE, $buffer, 8);
+ close(FILE);
+ return unpack("d", $buffer);
+}
+
+sub set_brightness
+{
+ my $steps = shift;
+ my $gamma = 1+$steps/10;
+
+ my %palettes = (
+ 'ARENA2' => [
+ "MAP.PAL", "ART_PAL.COL", "DANKBMAP.COL", "FMAP_PAL.COL",
+ "NIGHTSKY.COL", "OLDMAP.PAL", "OLDPAL.PAL", "PAL.PAL", "PAL.RAW"
+ ],
+ 'DATA' => [
+ "DAGGER.COL"
+ ]
+ );
+
+ sub edit_palette
+ {
+ my ($source_file, $gamma) = @_;
+ my $palette_size = -s $source_file;
+ my $source;
+ my $target = "";
+ open(FILE, "<$source_file") or die "cannot open $source_file";
+ binmode(FILE);
+ if ($palette_size == 768) {
+ read(FILE, $source, 768);
+ } elsif ($palette_size == 776) {
+ read(FILE, $target, 8);
+ read(FILE, $source, 768);
+ } else {
+ close(FILE);
+ die "$source_file is unknown palette format\n";
+ }
+ close(FILE);
+ $target eq "\x08\x03\x00\x00\x23\xb1\x00\x00"
+ || $target eq ""
+ || die "$source_file is unknown palette format\n";
+ sub transform {
+ my ($c, $g) = @_;
+ return max(0,min(int(255*(0.385/($g-0.5)+0.23)*($c/255)**(1/$g)+0.5),255));
+ }
+ my @source_data = unpack("C*", $source);
+ my @target_data;
+ foreach my $byte (@source_data) {
+ push(@target_data, (transform $byte, $gamma));
+ }
+ $target = $target . pack("C*", @target_data);
+ open(FILE, ">$source_file") or die "cannot write $source_file";
+ binmode(FILE);
+ print FILE $target;
+ close(FILE);
+ }
+
+ (-d catfile($daggerfall_path, $daggerfall_dir)) or die "Cannot find Daggerfall directory";
+
+ my $source_dir = catfile($daggerfall_path, $palettes_dir);
+ if ( ! -d $source_dir ) {
+ mkdir $source_dir or die "Cannot create palettes directory";
+ chmod 0775, $source_dir;
+ chown -1, $gid, $source_dir;
+ }
+
+ foreach my $dir (keys %palettes) {
+ my $target_dir = catfile($daggerfall_path, $daggerfall_dir, $dir);
+
+ foreach my $palette (@{$palettes{$dir}}) {
+ if ( ! -e catfile($source_dir, $palette) ) {
+ copy(
+ catfile($target_dir, $palette),
+ catfile($source_dir, $palette)
+ ) or die "Cannot copy palette file";
+ chmod 0664, catfile($source_dir, $palette);
+ chown -1, $gid, catfile($source_dir, $palette);
+ }
+ copy(
+ catfile($source_dir, $palette),
+ catfile($target_dir, $palette)
+ ) or die "Cannot copy palette file";
+ chmod 0664, catfile($target_dir, $palette);
+ chown -1, $gid, catfile($target_dir, $palette);
+ edit_palette(catfile($target_dir, $palette), $gamma);
+ }
+ }
+
+ my $file = catfile($source_dir, "now");
+ open(FILE, ">$file") or die "Cannot save brighness record";
+ binmode(FILE);
+ print FILE pack("d", $steps);
+ close(FILE);
+}
+
+sub get_wagon_capacity
+{
+ my $fall = catfile($daggerfall_path, $daggerfall_dir, "FALL.EXE");
+ ( -e $fall ) or die "Cannot find FALL.EXE";
+ ( -s $fall == 1864183 ) or die "Wrong FALL.EXE length";
+ open(FILE, "<$fall") or die "cannot open FALL.EXE";
+ binmode(FILE);
+ seek(FILE, 917011, 0);
+ my $buffer="";
+ read(FILE, $buffer, 2);
+ close(FILE);
+ my @bytes = unpack("C*", $buffer);
+ return $bytes[0]/4+$bytes[1]*64;
+}
+
+sub set_wagon_capacity
+{
+ my $val = shift;
+ my $len = length $val;
+ my $rep = int((5-$len)/2);
+ my $out = " "x$rep . "/" . " "x$rep . $val;
+ if (length $out == 5) { $out = $out." " };
+ my $high = int($val/64);
+ my $low = 4*$val-256*$high;
+ (length $out == 6) and
+ ($low >= 0) and
+ ($low <= 255) and
+ ($high >= 0) and
+ ($high <= 255)
+ or die "Bad value $val.";
+ my $bytes = pack("C*", ($low, $high));
+ my $fall = catfile($daggerfall_path, $daggerfall_dir, "FALL.EXE");
+ ( -e $fall ) or die "Cannot find FALL.EXE";
+ ( -s $fall == 1864183 ) or die "Wrong FALL.EXE length";
+ open(FILE, "<$fall") or die "cannot open FALL.EXE";
+ binmode(FILE);
+ my $buffer;
+ read(FILE, $buffer, 917011);
+ $buffer = $buffer.$bytes;
+ seek(FILE, 2, 1);
+ read(FILE, $buffer,854164,917013);
+ $buffer = $buffer.$out;
+ seek(FILE, 6, 1);
+ read(FILE, $buffer,93000,1771183);
+ close(FILE);
+ open(FILE, ">$fall") or die "cannot write FALL.EXE";
+ binmode(FILE);
+ print FILE $buffer;
+ close(FILE);
+}
+
+sub get_high_skills
+{
+ my $fall = catfile($daggerfall_path, $daggerfall_dir, "FALL.EXE");
+ ( -e $fall ) or die "Cannot find FALL.EXE";
+ ( -s $fall == 1864183 ) or die "Wrong FALL.EXE length";
+ open(FILE, "<$fall") or die "cannot open FALL.EXE";
+ binmode(FILE);
+ seek(FILE, 556836, 0);
+ my $buffer="";
+ read(FILE, $buffer, 1);
+ close(FILE);
+ return ($buffer eq "\xc8");
+}
+
+sub set_high_skills
+{
+ my $enable = shift;
+ my $fall = catfile($daggerfall_path, $daggerfall_dir, "FALL.EXE");
+ ( -e $fall ) or die "Cannot find FALL.EXE";
+ ( -s $fall == 1864183 ) or die "Wrong FALL.EXE length";
+ open(FILE, "<$fall") or die "cannot open FALL.EXE";
+ binmode(FILE);
+ my $buffer;
+ read(FILE, $buffer, 1864183);
+ close(FILE);
+ if ($enable) {
+ substr($buffer, 556836, 2, "\xc8\x72");
+ substr($buffer, 558213, 2, "\xc8\x77");
+ substr($buffer, 558234, 2, "\xc8\x76");
+ substr($buffer, 558253, 1, "\xc8");
+ substr($buffer, 558320, 2, "\xc8\x76");
+ substr($buffer, 558342, 1, "\xc8");
+ substr($buffer, 558833, 1, "\xc8");
+ substr($buffer, 557953, 1, "\x7f");
+ } else {
+ substr($buffer, 556836, 2, "\x64\x7c");
+ substr($buffer, 558213, 2, "\x64\x7f");
+ substr($buffer, 558234, 2, "\x64\x7e");
+ substr($buffer, 558253, 1, "\x64");
+ substr($buffer, 558320, 2, "\x64\x7e");
+ substr($buffer, 558342, 1, "\x64");
+ substr($buffer, 558833, 1, "\x64");
+ substr($buffer, 557953, 1, "\x64");
+ }
+ open(FILE, ">$fall") or die "cannot write FALL.EXE";
+ binmode(FILE);
+ print FILE $buffer;
+ close(FILE);
+}
+
+sub find_distance
+{
+ my $slot = shift;
+ my $file = catfile($daggerfall_path,$daggerfall_dir,"SAVE".$slot,"SAVETREE.DAT");
+ open(FILE, "<$file") or die "cannot open save from slot $slot";
+ my $buffer;
+ my $step;
+ my $ans;
+ binmode(FILE);
+ seek(FILE, 19, 1);
+ read(FILE, $buffer, 4);
+ $step = unpack("L", $buffer);
+ seek(FILE, $step, 1);
+ read(FILE, $buffer, 4);
+ $step = unpack("L", $buffer);
+ while ($step > 0) {
+ read(FILE, $buffer, 1);
+ if (unpack("C", $buffer) == 23) {
+ seek(FILE, 71, 1);
+ $ans = tell FILE;
+ close(FILE);
+ return $ans;
+ } else {
+ seek(FILE, $step-1, 1);
+ }
+ read(FILE, $buffer, 4);
+ $step = unpack("L", $buffer);
+ }
+ close(FILE);
+ die "No settings record found";
+}
+
+sub get_view_distance
+{
+ my $slot = shift;
+ my $place = find_distance $slot;
+ my $file = catfile($daggerfall_path,$daggerfall_dir,"SAVE".$slot,"SAVETREE.DAT");
+ open(FILE, "<$file") or die "cannot open save from slot $slot";
+ binmode(FILE);
+ seek(FILE, $place, 1);
+ my $buffer;
+ read(FILE, $buffer, 1);
+ close(FILE);
+ return unpack("C", $buffer);
+}
+
+sub set_view_distance
+{
+ my ($slot, $value) = @_;
+ my $place = find_distance $slot;
+ ($value>0) && ($value < 266) || die "Wrong distance value";
+ my $file = catfile($daggerfall_path,$daggerfall_dir,"SAVE".$slot,"SAVETREE.DAT");
+ my $buffer;
+ open(FILE, "<$file") or die "cannot open save from slot $slot";
+ binmode(FILE);
+ read(FILE, $buffer, -s $file);
+ close(FILE);
+ substr($buffer, $place, 1, pack("C", $value));
+ open(FILE, ">$file") or die "cannot write save from slot $slot";
+ binmode(FILE);
+ print FILE $buffer;
+ close(FILE);
+}
+
+sub get_label
+{
+ my $label = shift;
+ $label =~ tr/[A-Z]/[a-z]/;
+ my $file = catfile($daggerfall_path,$daggerfall_dir,"Z.CFG");
+ open(FILE, "<$file") or die "cannot open config file";
+ while (<FILE>) {
+ my $line = $_;
+ $line =~ tr/[A-Z]/[a-z]/;
+ $line =~ s/\s+//g;
+ if ($line =~ /^$label/) {
+ close(FILE);
+ return ($line =~ /1$/);
+ }
+ }
+ close(FILE);
+ return 0;
+}
+
+sub set_label
+{
+ my ($label, $value) = @_;
+ $label =~ tr/[A-Z]/[a-z]/;
+ my $file = catfile($daggerfall_path,$daggerfall_dir,"Z.CFG");
+ open(FILE, "<$file") or die "cannot open config file";
+ my @lines = <FILE>;
+ close(FILE);
+ open(FILE, ">$file") or die "cannot write to config file";
+ my $found = 0;
+ foreach my $line (@lines) {
+ my $copy = $line;
+ $copy =~ tr/[A-Z]/[a-z]/;
+ $copy =~ s/\s+//g;
+ if ($copy =~ /^$label/) {
+ $found = 1;
+ print FILE $label." ".$value."\r\n";
+ } else {
+ print FILE $line;
+ }
+ }
+ if (! $found) {
+ print FILE $label." ".$value."\r\n";
+ }
+ close(FILE);
+}
+
+sub get_cheat_mode
+{
+ return get_label "cheatmode";
+}
+
+sub set_cheat_mode
+{
+ my $val = shift;
+ if ($val) {
+ set_label "cheatmode", 1;
+ } else {
+ set_label "cheatmode", 0;
+ }
+}
+
+sub get_magic_repair
+{
+ return get_label "magicrepair";
+}
+
+sub set_magic_repair
+{
+ my $val = shift;
+ if ($val) {
+ set_label "magicrepair", 1;
+ } else {
+ set_label "magicrepair", 0;
+ }
+}
+
+sub is_slot_occupied
+{
+ my $slot = shift;
+ return ( -e catfile($daggerfall_path,$daggerfall_dir,"SAVE".$slot,"SAVENAME.TXT"))
+}
+
+sub get_save_name
+{
+ my $slot = shift;
+ ( is_slot_occupied $slot ) or die "Slot empty";
+ my $file = catfile($daggerfall_path,$daggerfall_dir,"SAVE".$slot,"SAVENAME.TXT");
+ my $name = "";
+ open(FILE, "<$file") or die "Cannot open save file";
+ binmode(FILE);
+ read(FILE,$name,32);
+ $name =~ s/\x00.*//;
+ return $name
+}
+
+sub get_current_saves
+{
+ my %saves = ();
+ foreach my $slot (0..5) {
+ if (is_slot_occupied $slot) {
+ $saves{$slot} = get_save_name $slot
+ }
+ }
+ return %saves;
+}
+
+sub get_archived_saves
+{
+ my %saves = ();
+ my $dir = catfile($daggerfall_path, $save_backup_dir);
+ ( -d $dir) or return %saves;
+ opendir(DIR, $dir) or die "Cannot access save backup directory";
+ my @files = readdir(DIR);
+ closedir(DIR);
+ foreach my $file (@files) {
+ if ( $file !~ /^\./) {
+ $file =~ s/$archive_type$//;
+ my @struct = split /-/, $file;
+ my $slot = $struct[0];
+ my $date = $struct[-1];
+ my $name = join('-',@struct[1..($#struct-1)]);
+ if ( ! exists $saves{$slot} ) {
+ $saves{$slot} = {};
+ }
+ if ( ! exists $saves{$slot}{$name} ) {
+ $saves{$slot}{$name} = [];
+ }
+ push($saves{$slot}{$name}, $date);
+ }
+ }
+ return %saves;
+}
+
+sub archive_save
+{
+ my $slot = shift;
+
+ my $dir = catfile($daggerfall_path, $save_backup_dir);
+ if ( ! -d $dir) {
+ mkdir $dir or die "Cannot create save backup directory";
+ chmod 0775, $dir;
+ chown -1, $gid, $dir;
+ }
+
+ my $save_path = catfile($daggerfall_path,$daggerfall_dir,"SAVE".$slot);
+
+ my $file = catfile($save_path,"SAVENAME.TXT");
+ ( -e $file ) or die "No save in slot $slot";
+ my $name;
+ open(FILE, "<$file") or die "Cannot open save file";
+ binmode(FILE);
+ read(FILE,$name,32);
+ $name =~ s/\x00.*//g;
+
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
+ my $date = sprintf("%4d_%02d_%02d_%02d_%02d_%02d", ($year+1900), ($mon+1), $mday, $hour, $min, $sec);
+
+ my $archive = catfile($daggerfall_path, $save_backup_dir, $slot."-".$name."-".$date.$archive_type);
+
+ my $call = $archiver_pack;
+ $call =~ s/ARCHIVE/$archive/;
+
+ chdir($save_path);
+ system($call);
+ chmod 0664, $archive;
+ chown -1, $gid, $archive;
+}
+
+sub expand_save_name
+{
+ my $which = shift;
+ if ($which =~ /^[0-5]$/) {
+ my $file = catfile($daggerfall_path,$daggerfall_dir,"SAVE".$which, "SAVENAME.TXT");
+ ( -e $file) or return "";
+ my $name;
+ open(FILE, "<$file") or return "";
+ binmode(FILE);
+ read(FILE,$name,32);
+ $name =~ s/\x00.*//g;
+ $which = $which."-".$name;
+ }
+ if ($which !~ /[0-9][0-9][0-9][0-9]_[0-9][0-9]_[0-9][0-9]_[0-9][0-9]_[0-9][0-9]_[0-9][0-9]$/) {
+ my $dir = catfile($daggerfall_path, $save_backup_dir);
+ opendir(DIR, $dir) or return "";
+ my @files = readdir(DIR);
+ closedir(DIR);
+ @files = sort grep(/^$which/, @files);
+ ($#files > 0) or return "";
+ my $last = $files[-1];
+ $last =~ s/$archive_type$//;
+ $last =~ s/^$which//;
+ $which = $which.$last;
+ }
+ ( -e catfile($daggerfall_path, $save_backup_dir, $which.$archive_type) ) or return "";
+ return $which
+}
+
+sub restore_save
+{
+ my ($which, $where) = @_;
+
+ my $target = catfile($daggerfall_path,$daggerfall_dir,"SAVE".$where);
+ ( -d $target) or die "No save directory for slot $where";
+
+ $which = expand_save_name $which;
+ $which or die "No stored save meets requirements";
+ $which = $which.$archive_type;
+ my $source = catfile($daggerfall_path, $save_backup_dir, $which);
+
+ my $call = $archiver_unpack;
+ $call =~ s/ARCHIVE/$source/;
+
+ opendir(DIR, $target) or die "Cannot access target directory";
+ my @files = readdir(DIR);
+ closedir(DIR);
+ ( $#files == 1) or remove_tree($target, {keep_root => 1} ) or die "Cannot cleanup target directory";
+ chdir($target) or die "Cannot access target directory";
+ system($call);
+
+ fix_dirs $target;
+}
+
+sub is_mod
+{
+ my $mod = shift;
+ return ( -d catfile($daggerfall_path, $mods_dir, $mod) );
+}
+
+sub has_patch
+{
+ my $mod = shift;
+ ( is_mod $mod) or return 0;
+ return ( -e catfile($daggerfall_path, $mods_dir, $mod.".patch") );
+}
+
+sub was_mod
+{
+ my $mod = shift;
+ ( ! is_mod $mod) or return 0;
+ return ( -e catfile($daggerfall_path, $mods_dir, $mod.".enabled") );
+}
+
+sub is_group
+{
+ my $mod = shift;
+ return (( -e catfile($daggerfall_path, $mods_dir, $mod.".extends")) and ( ! -d catfile($daggerfall_path, $mods_dir, $mod)))
+}
+
+sub is_mod_enabled;
+sub is_mod_enabled
+{
+ my $mod = shift;
+ if (is_mod $mod) {
+ return ( -e catfile($daggerfall_path, $mods_dir, $mod.".enabled"))
+ } elsif (was_mod $mod) {
+ return 1
+ } elsif (is_group $mod) {
+ my $file = catfile($daggerfall_path, $mods_dir, $mod.".extends");
+ open(FILE, "<$file") or die "Cannot access mods group";
+ my @mods = <FILE>;
+ close(FILE);
+ foreach my $file (@mods) {
+ $file =~ s/\r|\n//g;
+ (is_mod_enabled $file) or return 0;
+ }
+ return 1;
+ } else {
+ die "Value $mod does not point to mod or group"
+ }
+}
+
+sub get_mods
+{
+ my @mods = ();
+ my $dir = catfile($daggerfall_path, $mods_dir);
+ ( -d $dir ) or return @mods;
+ opendir(DIR, $dir) or die "Cannot access mods directory";
+ my @files = readdir(DIR);
+ closedir(DIR);
+ @files = grep(!/^\./,@files);
+ @files = grep(!/\.enabled$/,@files);
+ @files = grep(!/\.patch$/,@files);
+ @mods = grep(!/\.extends$/,@files);
+ return sort @mods;
+}
+
+sub get_enabled_mods
+{
+ my @mods = ();
+ my $dir = catfile($daggerfall_path, $mods_dir);
+ ( -d $dir ) or return @mods;
+ opendir(DIR, $dir) or die "Cannot access mods directory";
+ my @files = readdir(DIR);
+ closedir(DIR);
+ @files = grep(/enabled$/,@files);
+ foreach my $mod (@files) {
+ $mod =~ s/.enabled$//;
+ if (( is_mod $mod ) or (was_mod $mod)) {
+ push(@mods, $mod)
+ }
+ }
+ return sort @mods;
+}
+
+sub get_mod_groups
+{
+ my @mods = get_mods;
+ my @groups = ();
+ my $dir = catfile($daggerfall_path, $mods_dir);
+ ( -d $dir ) or return @mods;
+ opendir(DIR, $dir) or die "Cannot access mods directory";
+ my @files = readdir(DIR);
+ closedir(DIR);
+ foreach my $modex (grep(/\.extends$/,@files)) {
+ my $mod = $modex;
+ $mod =~ s/.extends$//;
+ my @temp = grep(/^$mod$/,@mods);
+ if ($#temp) { push(@groups, $mod) }
+ }
+ return sort @groups;
+}
+
+sub get_mod_dependencies;
+sub get_mod_dependencies
+{
+ my ($mod, $rec) = @_;
+ my @deps = ();
+ if (was_mod $mod) {
+ my $dir = catfile($daggerfall_path, $mod_backup_dir);
+ find sub {
+ my $file = $File::Find::name;
+ ( ! -d $file) or return;
+ ($file =~ /$mod$/) or return;
+ $file =~ s/^$dir.//;
+ ($file !~ /^FALL\.EXE/) or return;
+ my $temp = $file;
+ $file =~ s/-[0-9]*-$mod$//;
+ $temp =~ s/.*-([0-9]*)-$mod$/$1/;
+ $temp or return;
+ $temp = $temp - 1;
+ find sub {
+ my $test = $File::Find::name;
+ ( ! -d $test) or return;
+ $test =~ s/^$dir.//;
+ ($test =~ /^$file-$temp/) or return;
+ ($test !~ /orig$/) or return;
+ $test =~ s/^$file-$temp-//;
+
+ my @temp = grep(/^$test$/, @deps);
+ if ($#temp==-1) {
+ push(@deps, $test);
+ }
+
+ ( $rec ) or return;
+
+ my @recdeps = get_mod_dependencies $test, $rec;
+ foreach my $file (@recdeps) {
+ my @temp = grep(/^$file$/, @deps);
+ if ($#temp==-1) {
+ push(@deps, $file);
+ }
+ }
+ }, $dir;
+ }, $dir;
+ return sort @deps;
+ }
+ (is_mod $mod) or (is_group $mod) or die "Value $mod does not point to mod or group";
+ my $file = catfile($daggerfall_path, $mods_dir, $mod.".extends");
+ ( -e $file ) or return @deps;
+ open(FILE, "<$file") or die "Cannot access mods group";
+ my @mods = <FILE>;
+ close(FILE);
+ foreach my $file (@mods) {
+ $file =~ s/\r|\n//g;
+ my @temp = grep(/^$file$/, @deps);
+ if ($#temp==-1) {
+ push(@deps, $file);
+ }
+ if ( ($rec) and (-e catfile($daggerfall_path, $mods_dir, $file.".extends") )) {
+ my @recdeps = get_mod_dependencies $file, $rec;
+ foreach my $file (@recdeps) {
+ my @temp = grep(/^$file$/, @deps);
+ if ($#temp==-1) {
+ push(@deps, $file);
+ }
+ }
+ }
+ }
+ return sort @deps;
+}
+
+sub get_direct_mod_dependencies
+{
+ my $mod = shift;
+ return get_mod_dependencies $mod, 0;
+}
+
+sub get_all_mod_dependencies
+{
+ my $mod = shift;
+ return get_mod_dependencies $mod, 1;
+}
+
+sub get_mods_requiring
+{
+ my $mod = shift;
+ (is_mod $mod) or (was_mod $mod) or die "Value $mod does not represent mod";
+ my @mods = get_enabled_mods;
+ my @result = ();
+ foreach my $name (@mods) {
+ my @deps = get_all_mod_dependencies $name;
+ my @temp = grep(/^$mod$/, @deps);
+ if ($#temp != -1) {
+ my @temp = grep(/^$name$/, @result);
+ if ($#temp == -1) {
+ push(@result, $name);
+ }
+ }
+ }
+ return @result;
+}
+
+sub is_mod_conflicting
+{
+ my $mod = shift;
+ ( is_mod $mod ) or die "$mod is not a mod";
+ ( ! is_mod_enabled $mod ) or return 0;
+ my @possible_conflicts = ();
+ my $dir = catfile($daggerfall_path, $mod_backup_dir);
+ ( -d $dir ) or return @possible_conflicts;
+ my $moddir = catfile($daggerfall_path, $mods_dir, $mod);
+ find sub {
+ my $file = $File::Find::name;
+ ( ! -d $file ) or return;
+ $file =~ s/^$moddir.//;
+ my @file_conflicts = ();
+ find sub {
+ my $backup = $File::Find::name;
+ ( ! -d $backup) or return;
+ $backup =~ s/^$dir.//;
+ if ($backup =~ /^$file/) {
+ $backup =~ s/^$_-//;
+ ($backup !~ /0-orig/) or return;
+ my @parts = split(/-/, $backup);
+ $backup = join('-',@parts);
+ my $slot = $parts[0];
+ while (! (is_mod($backup) or was_mod($backup))) {
+ $slot = $parts[0];
+ shift @parts;
+ $backup = join('-',@parts);
+ }
+ ($backup ne $mod) or return;
+ my @temp = grep(/^$slot-backup$/,@file_conflicts);
+ if ($#temp==-1) {
+ push(@file_conflicts,"$slot-$backup");
+ }
+ }
+ }, $dir;
+ (@file_conflicts) or return;
+ my $conflict = (sort @file_conflicts)[-1];
+ my @temp = split(/-/,$conflict);
+ shift @temp;
+ $conflict = join('-',@temp);
+ @temp = grep(/^$conflict$/,@possible_conflicts);
+ if ($#temp == -1) {
+ push(@possible_conflicts, $conflict);
+ }
+ }, $moddir;
+ my @conflicts = ();
+ my @deps = get_direct_mod_dependencies $mod;
+ foreach my $conflict (@possible_conflicts) {
+ my @temp = grep(/^$conflict$/, @deps);
+ if ($#temp == -1) {
+ push(@conflicts, $conflict);
+ }
+ }
+ return sort @conflicts;
+}
+
+sub get_file_in_mods_count
+{
+ my $file = shift;
+ my $dir = catfile($daggerfall_path, $mod_backup_dir);
+ ( -d $dir ) or return 0;
+ my @backups = ();
+ find sub {
+ my $backup = $File::Find::name;
+ $backup =~ s/^$dir.//;
+ ( $backup eq $dir ) or push(@backups, $backup);
+ }, $dir;
+ @backups = grep(/^$file/, @backups);
+ return (1+$#backups);
+}
+
+sub rm
+{
+ my $file = shift;
+ ( -e $file ) or return;
+ ( ! -d $file ) or return;
+ unlink @{[$file]} or die "Cannot delete file";
+}
+
+sub dir_empty
+{
+ my $dir = shift;
+ ( -d $dir ) or return 0;
+ opendir(DIR, $dir);
+ my @files = readdir(DIR);
+ closedir(DIR);
+ return ($#files == 1);
+}
+
+sub enable_patch
+{
+ my $mod = shift;
+ my $file = catfile($daggerfall_path, $mods_dir, $mod.".patch");
+ open(FILE, "<$file") or die "Cannot open patch file";
+ my @lines = <FILE>;
+ close(FILE);
+ $file = catfile($daggerfall_path, $mod_backup_dir, "FALL.EXE-$mod");
+ open(FILE, ">$file") or die "Cannot create FALL.EXE backup";
+ my $fall = catfile($daggerfall_path, $daggerfall_dir, "FALL.EXE");
+ open(FALL, "<$fall") or die "Cannot open FALL.EXE file";
+ binmode(FALL);
+ my $buffer;
+ read(FALL, $buffer, 1864183);
+ foreach my $part (@lines) {
+ $part =~ s/\r|\n//g;
+ my @data = split(/\ /, $part);
+ my $offset = $data[0];
+ shift @data;
+ my $length = $data[0];
+ shift @data;
+ my $out = pack("C*", @data);
+ seek FALL, $offset, 0;
+ my $buf;
+ read FALL, $buf, $length;
+ my @orig = unpack("C*", $buf);
+ my $origval = join(" ", @orig);
+ print FILE "$offset $length $origval\n";
+ substr($buffer, $offset, $length, $out);
+ }
+ close(FILE);
+ close(FALL);
+ open(FALL, ">$fall") or die "Cannot open FALL.EXE file";
+ binmode(FALL);
+ print FALL $buffer;
+ close(FALL);
+}
+
+sub disable_patch
+{
+ my $mod = shift;
+ my $file = catfile($daggerfall_path, $mod_backup_dir, "FALL.EXE-$mod");
+ open(FILE, "<$file") or die "Cannot read FALL.EXE backup";
+ my @lines = <FILE>;
+ close(FILE);
+ my $fall = catfile($daggerfall_path, $daggerfall_dir, "FALL.EXE");
+ open(FALL, "<$fall") or die "Cannot open FALL.EXE file";
+ binmode(FALL);
+ my $buffer;
+ read(FALL, $buffer, 1864183);
+ close(FALL);
+ foreach my $part (@lines) {
+ $part =~ s/\r|\n//g;
+ my @data = split(/\ /, $part);
+ my $offset = $data[0];
+ shift @data;
+ my $length = $data[0];
+ shift @data;
+ my $out = pack("C*", @data);
+ substr($buffer, $offset, $length, $out);
+ }
+ open(FALL, ">$fall") or die "Cannot open FALL.EXE file";
+ binmode(FALL);
+ print FALL $buffer;
+ close(FALL);
+ rm $file;
+}
+
+sub enable_mod
+{
+ my $mod = shift;
+ (is_mod $mod) or (is_group $mod) or die "Value $mod does not point to mod or group";
+ (! is_mod_enabled $mod) or return;
+ my @deps = get_direct_mod_dependencies $mod;
+ foreach my $dep (@deps) {
+ (is_mod_enabled $dep) or enable_mod $dep;
+ }
+ ( is_mod $mod ) or return;
+ my $dir = catfile($daggerfall_path, $mod_backup_dir);
+ if ( ! -d $dir ) {
+ mkdir $dir or die "Cannot create mod backup directory";
+ chmod 0775, $dir;
+ chown -1, $gid, $dir;
+ }
+ my @conflicts = is_mod_conflicting $mod;
+ ( ! @conflicts ) or die "Mod is conflicting with: ".join(' ', @conflicts);
+ my $moddir = catfile($daggerfall_path, $mods_dir, $mod);
+ find sub {
+ my $source = $File::Find::name;
+ my $file = $source;
+ ($file ne $moddir) or return;
+ $file =~ s/^$moddir.//;
+ ( $file !~ /^FALL.EXE$/ ) or die "Bad mod, FALL.EXE can be modded only trough patches";
+ my $target = catfile($daggerfall_path, $daggerfall_dir, $file);
+ my $backup = catfile($dir, $file);
+ if ( -d $source ) {
+ if ( ! -d $target ) {
+ mkdir $target or die "Cannot create directory";
+ chmod 0775, $target;
+ chown -1, $gid, $target;
+ }
+ if ( ! -d $backup ) {
+ mkdir $backup or die "Cannot create directory";
+ chmod 0775, $backup;
+ chown -1, $gid, $backup;
+ }
+ } else {
+ if ( ! -e $target ) {
+ copy($source, $target) or die "Cannot copy file";
+ chmod 0664, $target;
+ chown -1, $gid, $target;
+ open(FILE, ">$backup-0-$mod") or die "Cannot create file";
+ close(FILE);
+ chmod 0664, "$backup-0-$mod";
+ chown -1, $gid, "$backup-0-$mod";
+ } else {
+ my $id = get_file_in_mods_count $file;
+ if ($id == 0) {
+ open(FILE, ">$backup-0-orig") or die "Cannot create file";
+ close(FILE);
+ chmod 0664, "$backup-0-orig";
+ chown -1, $gid, "$backup-0-orig";
+ $id = 1;
+ }
+ copy($target, "$backup-$id-$mod") or die "Cannot copy file";
+ chmod 0664, "$backup-$id-$mod";
+ chown -1, $gid, "$backup-$id-$mod";
+ copy($source, $target) or die "Cannot copy file";
+ chmod 0664, $target;
+ chown -1, $gid, $target;
+ }
+ }
+ }, $moddir;
+ if ( has_patch $mod ) {
+ enable_patch $mod;
+ }
+ open(FILE, ">$moddir.enabled") or die "Cannot create file";
+ close(FILE);
+ chmod 0664, "$moddir.enabled";
+ chown -1, $gid, "$moddir.enabled";
+}
+
+sub disable_mod
+{
+ my $mod = shift;
+ ( is_mod $mod ) or ( was_mod $mod) or die "Value $mod does not represent mod";
+ ( was_mod $mod ) or ( is_mod_enabled $mod ) or return;
+ my @temp = get_mods_requiring $mod;
+ my $count = $#temp+1;
+ ( ! $count ) or die "There are $count mods requiring $mod, cannot disable";
+ my $dir = catfile($daggerfall_path, $mod_backup_dir);
+ find sub {
+ my $file = $File::Find::name;
+ $file =~ s/^$dir.//;
+ ($file ne $dir) or return;
+ ($file =~ /$mod$/) or return;
+ my $id = $file;
+ $file =~ s/-[0-9]*-$mod$//;
+ if ($file =~ /FALL.EXE/) {
+ disable_patch $mod;
+ } else {
+ $id =~ s/^$file-//;
+ $id =~ s/-$mod$//;
+ my $source = catfile($dir, "$file-$id-$mod");
+ my $target = catfile($daggerfall_path, $daggerfall_dir, $file);
+ if ($id == 0) {
+ rm($source);
+ rm($target);
+ } else {
+ move($source, $target) or die "Cannot restore backup";
+ chmod 0664, $target;
+ chown -1, $gid, $target;
+ if ($id == 1) {
+ rm(catfile($dir, "$file-0-orig"));
+ }
+ }
+ }
+ }, $dir;
+ my @to_remove = ();
+ find { no_chdir => 1, wanted => sub {
+ my $file = $File::Find::name;
+ ($file ne $dir) or return;
+ (-d $file) or return;
+ (dir_empty $file) or return;
+ push(@to_remove, $file);
+ $file =~ s/^$dir.//;
+ $file = catfile($daggerfall_path, $daggerfall_dir, $file);
+ (dir_empty $file) or return;
+ push(@to_remove, $file);
+ }}, $dir;
+ foreach my $file (@to_remove) {
+ remove_tree($file) or die "Cannot remove leftover directory";
+ }
+ rm(catfile($daggerfall_path, $mods_dir, "$mod.enabled"));
+}
+
+sub refresh_mods
+{
+ my @mods = get_enabled_mods;
+ my @enabled_mods = @mods;
+ while ($#enabled_mods >= 0) {
+ foreach my $mod (@enabled_mods) {
+ my @temp = get_mods_requiring $mod;
+ my $count = $#temp+1;
+ if ( ! $count ) {
+ disable_mod $mod;
+ }
+ }
+ @enabled_mods = get_enabled_mods;
+ }
+ foreach my $mod (@mods) {
+ if (is_mod $mod) {
+ enable_mod $mod
+ }
+ }
+}
+
+#=================================================================================
+# Command line interface, options parsing
+#=================================================================================
+
+use Getopt::Long;
+
+my $opt_run_daggerfall=1;
+my $opt_force_run_daggerfall=0;
+my $opt_help=0;
+my $opt_accept_terms=0;
+my $opt_run_setup=0;
+my $opt_run_fixsave=0;
+my $opt_run_fixmaps=0;
+my $opt_get_brightness=0;
+my $opt_set_brightness="";
+my $opt_get_wagon_capacity=0;
+my $opt_set_wagon_capacity="";
+my $opt_get_high_skills=0;
+my $opt_set_high_skills="";
+my $opt_get_view_distance="";
+my %opt_set_view_distance=();
+my $opt_get_cheat_mode=0;
+my $opt_set_cheat_mode="";
+my $opt_get_magic_repair=0;
+my $opt_set_magic_repair="";
+my $opt_list_saves=0;
+my $opt_list_archived_saves=0;
+my $opt_archive_save="";
+my $opt_archive_all_saves=0;
+my %opt_restore_save=();
+my $opt_list_mods=0;
+my $opt_enable_mod="";
+my $opt_disable_mod="";
+my $opt_refresh_mods=0;
+my $die_early=0;
+
+Getopt::Long::Configure('pass_through');
+GetOptions (
+ 'help' => \$opt_help,
+ 'accept-terms' => \$opt_accept_terms,
+ 'run-daggerfall' => \$opt_force_run_daggerfall,
+ 'run-setup' => \$opt_run_setup,
+ 'run-fixsave' => \$opt_run_fixsave,
+ 'run-fixmaps' => \$opt_run_fixmaps,
+ 'get-brightness' => \$opt_get_brightness,
+ 'set-brightness=f' => \$opt_set_brightness,
+ 'get-wagon-capacity' => \$opt_get_wagon_capacity,
+ 'set-wagon-capacity=i' => \$opt_set_wagon_capacity,
+ 'get-high-skills' => \$opt_get_high_skills,
+ 'set-high-skills=s' => \$opt_set_high_skills,
+ 'get-view-distance=i' => \$opt_get_view_distance,
+ 'set-view-distance=i' => \%opt_set_view_distance,
+ 'get-cheat-mode' => \$opt_get_cheat_mode,
+ 'set-cheat-mode=s' => \$opt_set_cheat_mode,
+ 'get-magic-repair' => \$opt_get_magic_repair,
+ 'set-magic-repair=s' => \$opt_set_magic_repair,
+ 'list-saves' => \$opt_list_saves,
+ 'list-archived-saves' => \$opt_list_archived_saves,
+ 'archive-save=i' => \$opt_archive_save,
+ 'archive-all-saves' => \$opt_archive_all_saves,
+ 'restore-save=i' => \%opt_restore_save,
+ 'list-mods' => \$opt_list_mods,
+ 'enable-mod=s' => \$opt_enable_mod,
+ 'disable-mod=s' => \$opt_disable_mod,
+ 'refresh-mods' => \$opt_refresh_mods
+);
+
+if ($opt_set_brightness ne "") {
+ if ($opt_set_brightness < 0) {
+ print "Bad value for --set-brightness ($opt_set_brightness)\n";
+ $opt_help = 1;
+ }
+}
+
+if ($opt_set_wagon_capacity ne "") {
+ if (($opt_set_wagon_capacity <= 0) or ($opt_set_wagon_capacity >= 16384)) {
+ print "Bad value for --set-wagon-capacity ($opt_set_wagon_capacity)\n";
+ $opt_help = 1;
+ }
+}
+
+if ($opt_set_high_skills ne "") {
+ if ($opt_set_high_skills !~ /on|off/) {
+ print "Bad value for --set-high-skills ($opt_set_high_skills)\n";
+ $opt_help = 1;
+ }
+}
+
+if ($opt_get_view_distance ne "") {
+ if (($opt_get_view_distance < 0) or ($opt_get_view_distance > 5)) {
+ print "Bad value for --get-view-distance ($opt_get_view_distance)\n";
+ $opt_help = 1;
+ }
+ if ( ! is_slot_occupied $opt_get_view_distance ) {
+ print "No save in slot $opt_get_view_distance\n";
+ $die_early = 1;
+ }
+}
+
+foreach my $key (keys %opt_set_view_distance) {
+ my $val = $opt_set_view_distance{$key};
+ if ($key !~ /[0-5]/) {
+ print "Bad slot value for --set-view-distance ($key)\n";
+ $opt_help = 1;
+ }
+ if (($val < 0) or ($val > 255)) {
+ print "Bad view distance value for --set-view-distance $key ($val)\n";
+ $opt_help = 1;
+ }
+ if ( ! is_slot_occupied $key ) {
+ print "No save in slot $key\n";
+ $die_early = 1;
+ }
+}
+
+if ($opt_set_cheat_mode ne "") {
+ if ($opt_set_cheat_mode !~ /on|off/) {
+ print "Bad value for --set-cheat-mode ($opt_set_cheat_mode)\n";
+ $opt_help = 1;
+ }
+}
+
+if ($opt_set_magic_repair ne "") {
+ if ($opt_set_magic_repair !~ /on|off/) {
+ print "Bad value for --set-magic-repair ($opt_set_magic_repair)\n";
+ $opt_help = 1;
+ }
+}
+
+if ($opt_archive_save ne "") {
+ if (($opt_archive_save < 0) or ($opt_archive_save > 5)) {
+ print "Bad value for --archive-save ($opt_archive_save)\n";
+ $opt_help = 1;
+ }
+ if ( ! is_slot_occupied $opt_archive_save ) {
+ print "No save in slot $opt_archive_save\n";
+ $die_early = 1;
+ }
+}
+
+my %conflict_vals = ();
+foreach my $key (keys %opt_restore_save) {
+ my $val = $opt_restore_save{$key};
+ if ( (expand_save_name $key) eq "" ) {
+ print "No save matching $key\n"
+ }
+ if (($val < 0) or ($val > 5)) {
+ print "Bad slot targets for --restore-save $key ($val)\n";
+ $opt_help = 1;
+ }
+ if (exists $conflict_vals{$val}) {
+ print "Conflicting slot targets\n";
+ $die_early = 1;
+ } else {
+ $conflict_vals{$val} = 1;
+ }
+}
+
+if ($opt_enable_mod ne "") {
+ if ((! is_mod $opt_enable_mod) and (! is_group $opt_enable_mod)) {
+ print "Bad mod name for --enable-mod ($opt_enable_mod)\n";
+ $opt_help = 1;
+ } elsif ((is_mod $opt_enable_mod) and (is_mod_enabled $opt_enable_mod)) {
+ print "Mod \"$opt_enable_mod\" already enabled\n";
+ $die_early = 1;
+ } elsif (is_mod $opt_enable_mod) {
+ my @conflicts = is_mod_conflicting $opt_enable_mod;
+ if (@conflicts) {
+ print "Mod \"$opt_enable_mod\" is conflicting with: ".join(' ', @conflicts)."\n";
+ $die_early = 1;
+ }
+ }
+}
+
+if ($opt_disable_mod ne "") {
+ if (! is_mod $opt_disable_mod) {
+ print "Bad mod name for --disable-mod ($opt_disable_mod)\n";
+ $opt_help = 1;
+ } elsif ( ! is_mod_enabled $opt_disable_mod) {
+ print "Mod \"$opt_disable_mod\" not enabled\n";
+ $die_early = 1;
+ }
+}
+
+if ($#ARGV >= 0) {
+ foreach my $arg (@ARGV) {
+ print "Unknown option: $arg\n";
+ }
+ $opt_help = 1;
+}
+
+#=================================================================================
+# Command line interface, commands
+#=================================================================================
+
+if ($opt_help) {
+ print
+"
+The Elder Scrolls II: Daggerfall launcher
+
+usage:
+ daggerfall [options]
+
+available conflicting options:
+
+ --run-setup run the sound setup utility
+ --run-fixsave run fixsave, the save game fixing utility
+ --run-fixmaps run fixmaps, the map fixing utility
+ --run-daggerfall when any option is specified Daggerfall will
+ not be started by launcher. This options
+ forces start of game when all other tasks
+ are finished
+
+ --get-brightness returns current palette brightness
+ 0 means original, 1 means multiply gamma by 1.1,
+ 2 means multiply gamma by 1.2, etc.
+ --set-brightness=<val> sets brightness,
+ accept any non-negative number
+ (reasonable values are between 0 and 10)
+
+ --get-wagon-capacity returns current wagon capacity (in lbs)
+ --set-wagon-capacity=<val> sets current wagon capacity,
+ accepts values between 1 to 16384
+
+ --get-high-skills checks if skill levels above 100 are unlocked
+ --set-high-skills=<val> unlocks/locks skill levels above 100,
+ accepts two values - on and off
+
+ --get-cheat-mode checks if cheat mode is enabled
+ --set-cheat-mode=<val> enables/disables cheat mode
+ accepts two values - on and off
+
+ --get-magic-repair checks if repairing of magical items is enabled
+ --set-magic-repair=<val> enables/disabled reparis of magical items
+
+ --get-view-distance=<slot> returns view distance set in given slot,
+ accepts slot number, from 0 to 5
+ --set-view-distance <slot>=<val> sets view distance in given slot,
+ accepts slot number, from 0 to 5 and
+ value, from 0 to 255
+
+ --list-saves list current saves
+ --list-archived-saves list archived saves
+ --archive-save=<slot> archive game from given slot,
+ accepts slot number, from 0 to 5
+ --archive-all-saves archives all saves
+ --restore-save <val>=<slot> restores given archived save into requested slot,
+ accepts save description and target slot,
+ the save description is in form
+ <slot>-<name>-<time stamp>,
+ where if only slot given, current name for
+ that slot is assumed, and if time stamp is
+ not given, latest available is assumed,
+ e.g. \"4\" is valid shortcut to any
+ archived game from slot 4, and \"4-name\"
+ is valid for any game from slot 4 with
+ given name.
+
+ --list-mods lists all mods and groups. Marks which
+ mods/groups are enabled, lists any enabled
+ but no longer installed mods
+ --enable-mod=<val> enabled given mod or group, taking care of
+ dependencies
+ --disable-mod=<val> disables given mod, checks for dependencies
+ --refresh-mods updates all enabled mods to latest installed
+ versions
+
+ --accept-terms accept Daggerfall terms of use
+
+ --help display this help message
+";
+ exit;
+}
+
+if ($die_early) {
+ exit
+}
+
+if ($opt_accept_terms) {
+ $opt_run_daggerfall=0;
+ accept_terms;
+}
+
+if ($opt_run_setup) {
+ $opt_run_daggerfall=0;
+ run_setup;
+}
+
+if ($opt_run_fixsave) {
+ $opt_run_daggerfall=0;
+ run_fixsave;
+}
+
+if ($opt_run_fixmaps) {
+ $opt_run_daggerfall=0;
+ run_fixmaps;
+}
+
+if ($opt_get_brightness) {
+ $opt_run_daggerfall=0;
+ my $value = get_brightness;
+ print "Current palette brightness: $value\n";
+}
+
+if ($opt_set_brightness ne "") {
+ $opt_run_daggerfall=0;
+ set_brightness $opt_set_brightness;
+}
+
+if ($opt_get_wagon_capacity) {
+ $opt_run_daggerfall=0;
+ my $value = get_wagon_capacity;
+ print "Current wagon capacity: $value lbs\n";
+}
+
+if ($opt_set_wagon_capacity ne "") {
+ $opt_run_daggerfall=0;
+ set_wagon_capacity $opt_set_wagon_capacity;
+}
+
+if ($opt_get_high_skills) {
+ $opt_run_daggerfall=0;
+ if (get_high_skills) {
+ print "High skills are enabled\n";
+ } else {
+ print "High skills are disabled\n";
+ }
+}
+
+if ($opt_set_high_skills ne "") {
+ $opt_run_daggerfall=0;
+ set_high_skills ($opt_set_high_skills =~ /on/);
+}
+
+if ($opt_get_view_distance ne "") {
+ $opt_run_daggerfall=0;
+ my $value = get_view_distance $opt_get_view_distance;
+ print "View distance for save $opt_get_view_distance is $value.\n"
+}
+
+foreach my $key (keys %opt_set_view_distance) {
+ $opt_run_daggerfall=0;
+ my $val = $opt_set_view_distance{$key};
+ set_view_distance $key, $val;
+}
+
+if ($opt_get_cheat_mode) {
+ $opt_run_daggerfall=0;
+ if (get_cheat_mode) {
+ print "Cheat mode codes are enabled\n";
+ } else {
+ print "Cheat mode codes are disabled\n";
+ }
+}
+
+if ($opt_set_cheat_mode ne "") {
+ $opt_run_daggerfall=0;
+ set_cheat_mode ($opt_set_cheat_mode =~ /on/);
+}
+
+if ($opt_get_magic_repair) {
+ $opt_run_daggerfall=0;
+ if (get_magic_repair) {
+ print "Magic repairs are enabled\n";
+ } else {
+ print "Magic repairs are disabled\n";
+ }
+}
+
+if ($opt_set_magic_repair ne "") {
+ $opt_run_daggerfall=0;
+ set_magic_repair ($opt_set_magic_repair =~ /on/);
+}
+
+if ($opt_list_saves) {
+ $opt_run_daggerfall=0;
+ my %saves = get_current_saves;
+ my @slots = sort keys %saves;
+ if ($#slots == -1) {
+ print "No saves found\n";
+ } else {
+ foreach my $slot (@slots) {
+ print "Save in slot $slot: $saves{$slot}\n"
+ }
+ }
+}
+
+if ($opt_list_archived_saves) {
+ $opt_run_daggerfall=0;
+ my %saves = get_archived_saves;
+ my @slots = sort keys %saves;
+ if ($#slots == -1) {
+ print "No saves found\n";
+ } else {
+ foreach my $slot (sort keys %saves) {
+ print "Archived saves from slot $slot\n\n";
+ foreach my $name (sort keys $saves{$slot}) {
+ print " saves named $name\n\n";
+ foreach my $date (sort @{$saves{$slot}{$name}}) {
+ $date =~ s/_/./;
+ $date =~ s/_/./;
+ $date =~ s/_/, /;
+ $date =~ s/_/:/;
+ $date =~ s/_/:/;
+ print " from ", $date, "\n";
+ }
+ print "\n";
+ }
+ }
+ }
+}
+
+if ($opt_archive_save ne "") {
+ $opt_run_daggerfall=0;
+ archive_save $opt_archive_save;
+ print "Archived save from slot $opt_archive_save\n";
+}
+
+if ($opt_archive_all_saves) {
+ $opt_run_daggerfall=0;
+ my $found = 0;
+ foreach my $slot (0..5) {
+ if ( is_slot_occupied $slot ) {
+ $found = 1;
+ archive_save $slot;
+ print "Archived save from slot $slot\n";
+ }
+ }
+ $found or print "All save slots are empty\n"
+}
+
+foreach my $key (keys %opt_restore_save) {
+ $opt_run_daggerfall=0;
+ my $val = $opt_restore_save{$key};
+ my $proceed = 1;
+ if ( is_slot_occupied $val ) {
+ my $ans = "";
+ until ($ans =~ /yes|no/) {
+ print "You will overwrite existing save in slot $val, overwrite? (yes/no) ";
+ $ans = <>;
+ if ($ans !~ /yes|no/) {
+ print "Please answer with \"yes\" or \"no\"\n";
+ }
+ }
+ if ($ans =~ /no/) {
+ $proceed = 0;
+ }
+ }
+ if ($proceed) {
+ my $full = expand_save_name $key;
+ restore_save $key, $val;
+ print "Restored save $full into slot $val\n";
+ }
+}
+
+if ($opt_list_mods) {
+ $opt_run_daggerfall=0;
+ my $any = 0;
+ my @mods = get_mods;
+ if ( $#mods >= 0 ) {
+ $any = 1;
+ print "Installed mods:\n\n";
+ foreach my $mod (@mods) {
+ print " $mod";
+ if (is_mod_enabled $mod) {
+ print " (enabled)"
+ }
+ print "\n";
+ }
+ print "\n";
+ }
+ my @groups = get_mod_groups;
+ if ( $#groups >= 0 ) {
+ $any = 1;
+ print "Installed groups:\n\n";
+ foreach my $group (@groups) {
+ print " $group";
+ if (is_mod_enabled $group) {
+ print " (enabled)"
+ }
+ print "\n";
+ }
+ print "\n";
+ }
+ my @missing = ();
+ my @enabled = get_enabled_mods;
+ foreach my $mod (@enabled) {
+ my @temp = grep(/^$mod$/, @mods);
+ if ($#temp == -1) {
+ push(@missing, $mod)
+ }
+ }
+ if ( $#missing >= 0) {
+ $any = 1;
+ print "Enabled mods, no longer installed:\n\n";
+ foreach my $mod (@missing) {
+ print " $mod\n";
+ }
+ print "\n";
+ }
+ if (! $any) {
+ print "No mods found\n";
+ }
+}
+
+if ($opt_enable_mod ne "") {
+ $opt_run_daggerfall=0;
+ enable_mod $opt_enable_mod;
+ if (is_mod $opt_enable_mod) {
+ print "Enabled mod \"$opt_enable_mod\"\n"
+ } else {
+ print "Enabled group \"$opt_enable_mod\"\n"
+ }
+}
+
+if ($opt_disable_mod ne "") {
+ $opt_run_daggerfall=0;
+ disable_mod $opt_disable_mod;
+ if (is_mod $opt_disable_mod) {
+ print "Disabled mod \"$opt_disable_mod\"\n"
+ } else {
+ print "Disabled group \"$opt_disable_mod\"\n"
+ }
+}
+
+if ($opt_refresh_mods) {
+ $opt_run_daggerfall=0;
+ refresh_mods;
+ print "Refreshed enabled mods to latest installed version\n"
+}
+
+$opt_run_daggerfall or $opt_force_run_daggerfall or exit;
+
+if ( ! terms_accepted ) {
+ foreach (get_terms) { print $_ }
+ my $ans = 0;
+ until ($ans =~ /yes|no/) {
+ print "Do you accept the license? (yes/no) ";
+ $ans = <>;
+ ($ans =~ /yes|no/) or print "Please answer with \"yes\" or \"no\"\n";
+ }
+ if ($ans =~ /yes/) {
+ accept_terms
+ } else {
+ print "You should uninstall Daggerfall at once!\n";
+ exit
+ }
+}
+
+run_daggerfall;
+