summarylogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.SRCINFO42
-rw-r--r--HMISET.CFG12
-rw-r--r--PKGBUILD62
-rw-r--r--RUN.BAT3
-rw-r--r--Z.CFG13
-rwxr-xr-xdagger.conf33
-rw-r--r--dagger.install35
-rwxr-xr-xdaggerfall-launcher.pl1881
-rw-r--r--license38
-rw-r--r--maps.patchbin0 -> 402 bytes
-rwxr-xr-xunpk.py519
11 files changed, 2638 insertions, 0 deletions
diff --git a/.SRCINFO b/.SRCINFO
new file mode 100644
index 000000000000..27cc4afca0fb
--- /dev/null
+++ b/.SRCINFO
@@ -0,0 +1,42 @@
+pkgbase = daggerfall
+ pkgdesc = The Elder Scrolls II: Daggerfall
+ pkgver = 1.07.213
+ pkgrel = 12
+ url = http://www.elderscrolls.com/daggerfall/
+ install = dagger.install
+ arch = any
+ license = custom:daggerfall
+ makedepends = python2
+ makedepends = unzip
+ makedepends = bsdiff
+ depends = dosbox
+ noextract = addquest.zip
+ noextract = fixsa175.zip
+ options = emptydirs
+ source = http://cms.elderscrolls.com/sites/default/files/tes/extras/DFInstall.zip
+ source = http://www.uesp.net/dagger/files/addquest.zip
+ source = http://www.uesp.net/dagger/files/fixsa175.zip
+ source = http://download.narechk.net/dos32a-912-bin.zip
+ source = license
+ source = daggerfall-launcher.pl
+ source = RUN.BAT
+ source = Z.CFG
+ source = HMISET.CFG
+ source = unpk.py
+ source = maps.patch
+ source = dagger.conf
+ md5sums = 3cdd09a5696c2b94c58b85488be7cba2
+ md5sums = 1074c3e593375542e8e45256c6f9ada4
+ md5sums = e5647c7acba32973eb6b2fba621ae536
+ md5sums = f37ae16b8eab499edb6b0de0099827ca
+ md5sums = 2923e8e848462a4a05aa6b5473cd82b5
+ md5sums = 0e54a1e6032573d6b6ca60637dc43202
+ md5sums = dd4b858a32c6e80aaa025c57496958fe
+ md5sums = eae2f2244cc23bc1f266438ca4d9b2ce
+ md5sums = bd94604036a62217617fd28092c9d956
+ md5sums = 5cfc8231715eefd6b574b15e39d6a26c
+ md5sums = 4e773bc05d54c36a53c7aec2c2d2f538
+ md5sums = 36e4de3a8b1030bbf12c6badd23da2ec
+
+pkgname = daggerfall
+
diff --git a/HMISET.CFG b/HMISET.CFG
new file mode 100644
index 000000000000..c7b08cd92cb1
--- /dev/null
+++ b/HMISET.CFG
@@ -0,0 +1,12 @@
+
+[DIGITAL]
+DeviceName = Sound Blaster 16/AWE32
+DeviceIRQ = 7
+DeviceDMA = 1
+DevicePort = 0x220
+DeviceID = 0xe016
+
+[MIDI]
+DeviceName = MPU-401
+DevicePort = 0x330
+DeviceID = 0xa001
diff --git a/PKGBUILD b/PKGBUILD
new file mode 100644
index 000000000000..4c421eb6f8a0
--- /dev/null
+++ b/PKGBUILD
@@ -0,0 +1,62 @@
+# Maintainer: Andrzej Giniewicz <gginiu@gmail.com>
+pkgname=daggerfall
+pkgver=1.07.213
+pkgrel=12
+pkgdesc="The Elder Scrolls II: Daggerfall"
+arch=('any')
+url="http://www.elderscrolls.com/daggerfall/"
+license=('custom:daggerfall')
+depends=("dosbox")
+makedepends=("python2" "unzip" "bsdiff")
+options=(emptydirs)
+install="dagger.install"
+source=(http://cms.elderscrolls.com/sites/default/files/tes/extras/DFInstall.zip
+ http://www.uesp.net/dagger/files/addquest.zip
+ http://www.uesp.net/dagger/files/fixsa175.zip
+ http://download.narechk.net/dos32a-912-bin.zip
+ license daggerfall-launcher.pl RUN.BAT Z.CFG HMISET.CFG unpk.py maps.patch
+ dagger.conf)
+noextract=(addquest.zip fixsa175.zip)
+md5sums=('3cdd09a5696c2b94c58b85488be7cba2'
+ '1074c3e593375542e8e45256c6f9ada4'
+ 'e5647c7acba32973eb6b2fba621ae536'
+ 'f37ae16b8eab499edb6b0de0099827ca'
+ '2923e8e848462a4a05aa6b5473cd82b5'
+ '0e54a1e6032573d6b6ca60637dc43202'
+ 'dd4b858a32c6e80aaa025c57496958fe'
+ 'eae2f2244cc23bc1f266438ca4d9b2ce'
+ 'bd94604036a62217617fd28092c9d956'
+ '5cfc8231715eefd6b574b15e39d6a26c'
+ '4e773bc05d54c36a53c7aec2c2d2f538'
+ '36e4de3a8b1030bbf12c6badd23da2ec')
+
+package() {
+ cd "$srcdir"
+ _target="${pkgdir}"/usr/share/games/daggerfall
+ install -d -m775 "$_target"
+ cp -rf "$srcdir/DFCD/DAGGER" "$_target"
+ chmod 775 "$_target/DAGGER"
+ chmod 775 "$_target/DAGGER/ARENA2"
+ cp -rf "$srcdir/DFCD/DATA" "$_target/DAGGER"
+ install "$srcdir/DFCD/INSTALL.EXE" "$_target/DAGGER/INSTALL.EXE"
+ install "$srcdir/DFCD/INSTALL.PIF" "$_target/DAGGER/INSTALL.PIF"
+ for i in {0..5}; do install -d "$_target/DAGGER/SAVE$i"; done
+ mv "$_target/DAGGER/ARENA2/faction.txt" "$_target/DAGGER/ARENA2/FACTION.TXT"
+ install "$srcdir/RUN.BAT" "$_target/DAGGER/RUN.BAT"
+ install "$srcdir/Z.CFG" "$_target/DAGGER/Z.CFG"
+ install "$srcdir/HMISET.CFG" "$_target/DAGGER/HMISET.CFG"
+ python2 unpk.py "$srcdir/DFCD/DAGGER/ARENA2/PACKED.DAT" "$_target/DAGGER"
+ rm "$_target/DAGGER/ARENA2/PACKED.DAT"
+ unzip -o "$srcdir/addquest.zip" -d "$_target/DAGGER/ARENA2"
+ rm "$_target/DAGGER/ARENA2/readme.txt"
+ _offset=$((`grep -Ubo --binary-files=text 'start of data' "$srcdir/DAGGER/DAG213.EXE" | head -1 | sed 's/:.*//g'`+13))
+ python2 unpk.py "$srcdir/DAGGER/DAG213.EXE" "$_target/DAGGER" ${_offset}
+ unzip -o "$srcdir/fixsa175.zip" -d "$_target/DAGGER"
+ bspatch "$_target/DAGGER/ARENA2/MAPS.BSA" "$_target/MAPS.BSA" "$srcdir/maps.patch"
+ mv "$_target/MAPS.BSA" "$_target/DAGGER/ARENA2/MAPS.BSA"
+ install "$srcdir/binw/dos32a.exe" "$_target/DAGGER/DOS32A.EXE"
+ install "$srcdir/dagger.conf" "$_target/dagger.conf"
+ install -D -m644 "$srcdir/license" "$pkgdir/usr/share/licenses/daggerfall/license"
+ install -D -m754 "$srcdir/daggerfall-launcher.pl" "$pkgdir/usr/bin/daggerfall"
+}
+
diff --git a/RUN.BAT b/RUN.BAT
new file mode 100644
index 000000000000..e2720fc47343
--- /dev/null
+++ b/RUN.BAT
@@ -0,0 +1,3 @@
+@ECHO OFF
+@DOS32A FALL.EXE Z.CFG
+
diff --git a/Z.CFG b/Z.CFG
new file mode 100644
index 000000000000..b5fc9e516c06
--- /dev/null
+++ b/Z.CFG
@@ -0,0 +1,13 @@
+type dfall_huge
+path c:\arena2\
+pathcd c:\arena2\
+fadecolor 0
+mapfile d
+rendergame 1
+user 1
+startMap 179
+region 17
+helmet 0
+maxSpeed 200
+controls betaplyr.dat
+maps mapsave.sav
diff --git a/dagger.conf b/dagger.conf
new file mode 100755
index 000000000000..0d5e908806c7
--- /dev/null
+++ b/dagger.conf
@@ -0,0 +1,33 @@
+[sdl]
+fullscreen=false
+fulldouble=true
+fullresolution=1152x720
+windowresolution=960x720
+waitonerror=false
+output=opengl
+
+[dosbox]
+memsize=32
+
+[render]
+aspect=true
+scaler=normal3x
+
+[cpu]
+core=dynamic
+cycles=max
+
+[mixer]
+blocksize=2048
+prebuffer=10
+
+[midi]
+mididevice=alsa
+midiconfig=128:0
+
+[sblaster]
+oplmode=opl3
+
+[speaker]
+pcspeaker=false
+
diff --git a/dagger.install b/dagger.install
new file mode 100644
index 000000000000..75ca072be232
--- /dev/null
+++ b/dagger.install
@@ -0,0 +1,35 @@
+
+pre_install() {
+ getent group games > /dev/null || /usr/sbin/groupadd -g 50 games
+}
+
+post_install() {
+ echo "You must read and accept the Daggerfall terms of use"
+ echo " /usr/share/licenses/daggerfall/license"
+ echo ""
+ echo "To accept license run daggerfall from command line"
+ echo "and answer \"yes\" when asked, or issue command"
+ echo " daggerfall --accept-terms"
+ echo ""
+ echo "You should tweak dosbox settings to match your needs"
+ echo " /usr/share/games/daggerfall/dagger.conf"
+ echo ""
+ echo "To play, make sure you are in games group and use command"
+ echo " daggerfall"
+ echo ""
+ echo "To grab/ungrab mouse in window mode press CTRL+F10"
+ echo "To switch fullscreen press ALT+ENTER"
+ echo ""
+ echo "To see more commands, use"
+ echo " daggerfall --help"
+ chgrp -R games /usr/share/games/daggerfall
+ chmod -R g+rw /usr/share/games/daggerfall
+ chgrp games /usr/bin/daggerfall
+ chmod g+rx /usr/bin/daggerfall
+ /bin/true
+}
+
+post_upgrade() {
+ post_install
+}
+
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;
+
diff --git a/license b/license
new file mode 100644
index 000000000000..8105f949090d
--- /dev/null
+++ b/license
@@ -0,0 +1,38 @@
+Terms Of Use For DAGGERFALL
+
+THE ELDER SCROLLS II: DAGGERFALL
+
+IMPORTANT - PLEASE READ CAREFULLY BEFORE INSTALLING THE ELDER SCROLLS II:
+DAGGERFALL™ ("THIS PRODUCT").
+
+THIS IS A LEGAL DOCUMENT STATING THE TERMS AND CONDITIONS GOVERNING INSTALLATION
+AND USE OF THIS PRODUCT. BY INSTALLING OR USING THIS PRODUCT, YOU AGREE TO THE
+TERMS STATED HEREIN BY BETHESDA SOFTWORKS.
+
+IF YOU DO NOT AGREE, DO NOT INSTALL.
+
+1. You have a non-exclusive, non-transferable license and right to use this
+Product for your own personal use and enjoyment. This Product is not provided
+for any non-personal, commercial purpose. All rights not expressly granted to
+you herein are hereby reserved by Bethesda Softworks.
+
+2. As between you and Bethesda Softworks, all rights, title and interest in and
+to the Product, and all worldwide intellectual property rights that are embodied
+in, related to, or represented by the Product, are and at all times shall remain
+the sole and exclusive property of Bethesda Softworks.
+
+3. This Product is provided "as is." Bethesda Softworks makes no representation,
+warranty or covenant of any kind as to merchantability or fitness for a
+particular purpose or use, and disclaims any liability with respect thereto. In
+no event shall Bethesda Softworks, its affiliates, or their respective officers,
+directors, employees or agents be liable in any way to you or to any third party
+for any damage whatsoever that may result from use of this Product or its
+installation.
+
+4. Neither Bethesda Softworks nor any of its affiliates will provide any
+technical or customer support with respect to this Product or its use by you or
+any third party.
+
+5. You acknowledge that Bethesda Softworks owns any and all trademark, copyright
+and other proprietary rights to this Product.
+
diff --git a/maps.patch b/maps.patch
new file mode 100644
index 000000000000..e3b34d063bbe
--- /dev/null
+++ b/maps.patch
Binary files differ
diff --git a/unpk.py b/unpk.py
new file mode 100755
index 000000000000..a6445324c01f
--- /dev/null
+++ b/unpk.py
@@ -0,0 +1,519 @@
+import sys, os, struct, itertools, array
+
+# class PKStream is based on information from Ben Rudiak-Gould:
+# http://groups.google.com/group/comp.compression/msg/48ea9de6d71a575b
+# and implementation of Douglas Kane:
+# http://groups.google.com/group/comp.compression/msg/aa014556d706c525
+# Archive extraction code was highly influenced by Gavin Claytons Daggerfall Jukebox:
+# http://www.dfworkshop.net/?page_id=61
+
+def file_bytestream(f):
+ while True:
+ temp = f.read(1)
+ if not temp: raise StopIteration
+ yield struct.unpack('B',temp)[0]
+
+class PKStream(object):
+ def __init__(self, source):
+ if isinstance(source, file):
+ self.bytestream = file_bytestream(source)
+ elif hasattr(source, "next"):
+ self.bytestream = source
+ elif hasattr(source, "__iter__"):
+ self.bytestream = source.__iter__()
+ else:
+ raise TypeError("Expected file or iterable object, got %s" % type(source))
+ self.bits_read = 0
+ self.prefixed_literals = self.read(8)
+ assert(self.prefixed_literals==0 or self.prefixed_literals==1)
+ self.dict_bytes = self.read(8)
+ assert(self.dict_bytes==4 or self.dict_bytes==5 or self.dict_bytes==6)
+ self.dict_size = 2**(self.dict_bytes+6)
+ self.dictionary = array.array('B',itertools.repeat(0,self.dict_size))
+ self.current_key = 0
+
+ def read_byte(self):
+ self.bits_read = 8
+ self.last_byte = self.bytestream.next()
+
+ def read(self, n):
+ if self.bits_read==0:
+ self.read_byte()
+ if n<=self.bits_read:
+ temp = (self.last_byte >> (8-self.bits_read))&(0xff>>(8-n))
+ self.bits_read -= n
+ return temp
+ else:
+ shift = 8-self.bits_read
+ res = [self.last_byte>>shift]
+ n -= self.bits_read
+ self.read_byte()
+ while n>8:
+ res[-1] = res[-1] | ((self.last_byte << (8-shift)) & 0xff)
+ res.append(self.last_byte >> shift)
+ self.read_byte()
+ n-=8
+ self.bits_read = (8-n)
+ if n>shift:
+ res[-1] = res[-1] | ((self.last_byte << (8-shift)) & 0xff)
+ res.append((self.last_byte>>shift)&(0xff>>(8-n+shift)))
+ else:
+ res[-1] = res[-1] | (self.last_byte << (8-shift)) & (0xff >> (shift-n))
+ temp = 0
+ for i,v in enumerate(res):
+ temp += v<<(i*8)
+ return temp
+
+ def read_rev(self,bits):
+ value = self.read(bits)
+ temp = 0
+ for i in xrange(bits):
+ temp = temp << 1
+ temp = temp | value & 0x0001
+ value = value >> 1
+ return temp
+
+ def decode_literal(self):
+ if self.prefixed_literals:
+ temp - self.read_rev(4)
+ if temp==0xf: #1111
+ return 0x20
+ if temp==0xe: #1110
+ if self.read(1): #11101
+ return 0x45
+ #11100
+ return 0x61
+ if temp==0xd: #1101
+ if self.read(1): #11011
+ return 0x65
+ #11010
+ return 0x69
+ if temp==0xc: #1100
+ if self.read(1): #11001
+ return 0x6c
+ #11000
+ return 0x6e
+ if temp==0xb: #1011
+ if self.read(1): #10111
+ return 0x6f
+ #10110
+ return 0x72
+ if temp==0xa: #1010x
+ return 0x74-self.read(1)
+ if temp==0x9: #1001
+ if self.read(1): #10011
+ return 0x75
+ if self.read(1): #100101
+ return 0x2d
+ #100100
+ return 0x31
+ if temp==0x8: #1000
+ temp = self.read_rev(2)
+ if temp==0x3: #100011
+ return 0x41
+ if temp==0x2: #100010
+ return 0x43
+ if temp==0x1: #100001
+ return 0x44
+ #100000
+ return 0x49
+ if temp==0x7: #0111
+ temp = self.read_rev(2)
+ if temp==0x3: #011111
+ return 0x4c
+ if temp==0x2: #011110
+ return 0x4e
+ if temp==0x1: #011101
+ return 0x4f
+ #011100
+ return 0x52
+ if temp==0x6: #0110
+ if self.read(1): #01101x
+ return 0x54-self.read(1)
+ #01100x
+ return 0x63-self.read(1)
+ if temp==0x5: #0101
+ temp = self.read_rev(2)
+ if temp==0x3: #010111
+ return 0x64
+ #0101xx
+ return 0x68-temp
+ if temp==0x4: #0100
+ if self.read(1): #01001
+ if self.read(1): #010011
+ return 0x6d
+ #010010
+ return 0x70
+ #01000
+ if self.read(1): #010001
+ if self.read(1): #0100011
+ return 0x0a
+ #0100010
+ return 0x0d
+ #010000x
+ return 0x29-self.read(1)
+ if temp==0x3: #0011
+ temp = self.read_rev(3)
+ if temp==0x7: #0011111
+ return 0x2c
+ if temp==0x6: #0011110
+ return 0x2e
+ if temp==0x5: #0011101
+ return 0x30
+ if temp==0x0: #0011000
+ return 0x37
+ #0011xxx
+ return 0x36-temp
+ if temp==0x2: #0010
+ temp = self.read_rev(3)
+ if temp==0x7: #0010111
+ return 0x38
+ if temp==0x6: #0010110
+ return 0x3d
+ if temp==0x5: #0010101
+ return 0x42
+ if temp==0x4: #0010100
+ return 0x46
+ if temp==0x3: #0010011
+ return 0x4d
+ if temp==0x2: #0010010
+ return 0x50
+ if temp==0x1: #0010001
+ return 0x55
+ #0010000
+ return 0x6b
+ if temp==0x1: #0001
+ temp = self.read_rev(3)
+ if temp==0x7: #0001111
+ return 0x77
+ if temp==0x6: #0001110
+ if self.read(1): #00011100
+ return 0x09
+ #00011101
+ return 0x22
+ if temp==0x5: #0001101
+ if self.read(1): #00011011
+ return 0x27
+ #00011010
+ return 0x2a
+ if temp==0x4: #0001100
+ if self.read(1): #00011001
+ return 0x2f
+ #00011000
+ return 0x36
+ if temp==0x3: #0001011x
+ return 0x3a-self.read(1)
+ if temp==0x2: #0001010x
+ return 0x48-self.read(1)
+ if temp==0x1: #0001001
+ if self.read(1): #00010011
+ return 0x57
+ #00010010
+ return 0x5b
+ if self.read(1): #00010001
+ return 0x5f
+ #00010000
+ return 0x76
+ #0000
+ temp = self.read_rev(3)
+ if temp==0x7: #0000111x
+ return 0x79-self.read(1)
+ if temp==0x6: #0000110
+ temp = self.read_rev(2)
+ if temp==0x3: #000011011
+ return 0x2b
+ if temp==0x2: #000011010
+ return 0x3e
+ if temp==0x1: #000011001
+ return 0x4b
+ #000011000
+ return 0x56
+ if temp==0x5: #0000101
+ temp = self.read_rev(2)
+ if temp==0x3: #000010111
+ return 0x58
+ if temp==0x2: #000010110
+ return 0x59
+ if temp==0x1: #000010101
+ return 0x5d
+ if self.read(1): #0000101001
+ return 0x21
+ #0000101000
+ return 0x24
+ if temp==0x4: #0000100
+ if self.read(1): #00001001
+ temp = self.read_rev(2)
+ if temp==0x3: #0000100111
+ return 0x26
+ if temp==0x2: #0000100110
+ return 0x71
+ if temp==0x1: #0000100101
+ return 0x7a
+ if self.read(1): #00001001001
+ return 0x00
+ #00001001000
+ return 0x3c
+ temp = self.read_rev(3)
+ if temp==0x7: #00001000111
+ return 0x3f
+ if temp==0x6: #00001000110
+ return 0x4a
+ if temp==0x5: #00001000101
+ return 0x51
+ if temp==0x4: #00001000100
+ return 0x5a
+ if temp==0x3: #00001000011
+ return 0x5c
+ if temp==0x2: #00001000010
+ return 0x6a
+ if temp==0x1: #00001000001
+ return 0x7b
+ #00001000000
+ return 0x7c
+ if temp==0x3: #0000011
+ temp = self.read_rev(5)
+ if temp>=0x18:
+ return 0x20-temp
+ if temp>=0x16:
+ return 0x22-temp
+ if temp>=0x0a:
+ return 0x23-temp
+ if temp>=0x05:
+ return 0x24-temp
+ if temp==0x04:
+ return 0x23
+ if temp==0x03:
+ return 0x25
+ if temp==0x02:
+ return 0x3b
+ if temp==0x01:
+ return 0x40
+ return 0x5e
+ if temp==0x2: #0000010
+ temp = self.read_rev(5)
+ if temp==0x1f:
+ return 0x60
+ if temp>=0x1c:
+ return 0x9b-temp
+ return 0xcb-temp
+ if temp==0x1: #0000001
+ temp = self.read_rev(5)
+ if temp>=0x0c:
+ return 0xeb-temp
+ if temp==0x0b:
+ return 0xe1
+ if temp==0x0a:
+ return 0xe5
+ if temp==0x09:
+ return 0xe9
+ if temp==0x08:
+ return 0xee
+ if temp>=0x05:
+ return 0xf9-temp
+ if temp==0x04:
+ if self.read(1):
+ return 0x1a
+ return 0x80
+ if temp==0x03:
+ return 0x82-self.read(1)
+ if temp==0x02:
+ return 0x84-self.read(1)
+ if temp==0x01:
+ return 0x86-self.read(1)
+ return 0x88-self.read(1)
+ #0000000
+ temp = self.read_rev(6)
+ if temp>=0x19:
+ return 0xc8-temp
+ if temp==0x18:
+ return 0xe0
+ if temp>=0x15:
+ return 0xf9-temp
+ if temp>=0x12:
+ return 0xfa-temp
+ if temp>=0x0e:
+ return 0xfb-temp
+ if temp>=0x0b:
+ return 0xfc-temp
+ return 0xff-temp
+ return self.read(8)
+ def decode_copy_length(self):
+ temp = self.read_rev(2)
+ if temp==0x1: #01
+ if self.read(1): #011
+ return 5
+ #010
+ if self.read(1): #0101
+ return 6
+ #0100
+ return 7
+ if temp==0x2: # 10
+ if self.read(1): #101
+ return 2
+ #100
+ return 4
+ if temp==0x3: #11
+ return 3
+ #00
+ if self.read(1): #001
+ if self.read(1): #0011
+ return 8
+ #0010
+ if self.read(1): #00101
+ return 9
+ #00100x
+ return 10 + self.read(1)
+ #000
+ if self.read(1): #0001
+ if self.read(1): #00011xx
+ return 12+self.read(2)
+ #00010xxx
+ return 16+self.read(3)
+ #0000
+ temp = self.read_rev(2)
+ if temp==0x3: #000011xxxx
+ return 24+self.read(4)
+ if temp==0x2: #000010xxxxx
+ return 40+self.read(5)
+ if temp==0x1: #000001xxxxxx
+ return 72+self.read(6)
+ #000000
+ if self.read(1): #0000001xxxxxxx
+ return 136+self.read(7)
+ #0000000
+ return 264+self.read(8)
+
+ def calc_offset(self, high, low) :
+ return (high << low) | self.read(low)
+
+ def decode_copy_offset(self, low):
+ temp = self.read(2)
+ if temp==0x3: #11
+ return self.calc_offset(0x00, low)
+ if temp==0x1: #10
+ if self.read(1): # 101
+ if self.read(1): #1011
+ return self.calc_offset(0x01, low)
+ #1010
+ return self.calc_offset(0x02, low)
+ #100
+ return self.calc_offset(0x06-self.read_rev(2), low)
+ if temp==0x2: #01
+ temp = self.read_rev(4)
+ if temp: # 01xxxx
+ return self.calc_offset(0x16-temp, low)
+ # 010000
+ return self.calc_offset(0x17-self.read(1), low)
+ #00
+ if self.read(1):
+ return self.calc_offset(0x27-self.read_rev(4), low)
+ #000
+ if self.read(1):
+ return self.calc_offset(0x2f-self.read_rev(3), low)
+ #0000
+ return self.calc_offset(0x3f-self.read_rev(4), low)
+
+ def get_next_token(self):
+ temp = self.read(1)
+ if temp==0:
+ return (0, self.decode_literal(),0,0)
+ length = self.decode_copy_length()
+ if length==519: # end of stream
+ return (-1,0,0,0)
+ if length==2:
+ low = 2
+ else:
+ low = self.dict_bytes
+ return (1, 0, length, self.decode_copy_offset(low))
+
+ def decode(self):
+ tktype = 0
+ apBuffer = array.array('B')
+ while tktype>=0:
+ (tktype, literal, length, offset) = self.get_next_token()
+ if tktype==0:
+ apBuffer.append(literal)
+ self.dictionary[self.current_key] = literal
+ self.current_key += 1
+ if self.current_key == self.dict_size:
+ self.current_key = 0
+ elif tktype==1:
+ start = (self.current_key-1-offset)%self.dict_size
+ ind = start
+ nexti = self.current_key
+ copies = 0
+ while copies<length:
+ copies += 1
+ apBuffer.append(self.dictionary[ind])
+ self.dictionary[nexti] = self.dictionary[ind]
+ nexti += 1
+ ind += 1
+ if ind==self.current_key:
+ ind = start
+ if ind==self.dict_size:
+ ind = 0
+ if nexti == self.dict_size:
+ nexti = 0
+ self.current_key = nexti
+ return apBuffer
+
+def unpack_file(f, out, length):
+ g = open(out, "wb")
+ lenout = 0
+ while lenout<length:
+ f.seek(36,1)
+ stream = PKStream(f).decode()
+ lenout += len(stream)
+ print (lenout*100/length),"%\x0d"
+ stream.write(g)
+ g.close()
+
+def unpack_header(f, at, names, offset):
+ f.seek(offset+at)
+ length, = struct.unpack('I', f.read(4))
+ to = names[struct.unpack('I',f.read(4))[0]]
+ name = f.read(13).strip('\x00')
+ start, = struct.unpack('I', f.read(4))
+ out = os.path.join(to, name)
+ f.seek(offset+start)
+ print "\"%s\" (length: %s bytes)"%(out,length)
+ unpack_file(f, out, length)
+ print "File \"%s\" unpacked\n"%out
+
+def unpack_archive(archive, directory, offset=0):
+ f = open(archive, "rb")
+ f.seek(offset)
+ start,end = struct.unpack('II', f.read(8))
+ nfiles = (end-start)/25
+ if not os.path.exists(directory):
+ os.mkdir(directory)
+ ndirs = 0
+ f.seek(offset+start+4)
+ for i in xrange(nfiles):
+ ndirs=max(ndirs, struct.unpack('I',f.read(4))[0])
+ f.seek(21,1)
+ names = []
+ f.seek(offset+end)
+ for i in xrange(ndirs+1):
+ name = f.read(60).strip('\x00').replace("\\",os.path.sep)
+ if name!=".":
+ to = os.path.join(directory,name)
+ else:
+ to = directory
+ if not os.path.exists(to):
+ os.mkdir(to)
+ names.append(to)
+ print "Found %s files in archive %s at offset %s.\n"%(nfiles,archive,offset)
+ for i in xrange(nfiles):
+ print "Extracting file %s of %s,"%(i+1,nfiles),
+ unpack_header(f, start+i*25, names, offset)
+ f.close()
+
+if __name__ == "__main__":
+ if len(sys.argv)==3:
+ unpack_archive(*map(os.path.expandvars,map(os.path.expanduser,sys.argv[1:])))
+ elif len(sys.argv)==4:
+ unpack_archive(*map(os.path.expandvars,map(os.path.expanduser,sys.argv[1:3])),offset=int(sys.argv[3]))
+ else:
+ print "usage: python2 unpk.py <archive> <target directory> [offset]"
+