diff options
author | Andrzej Giniewicz | 2015-07-07 21:05:43 +0200 |
---|---|---|
committer | Andrzej Giniewicz | 2015-07-07 21:05:43 +0200 |
commit | e9222aa68ef940105d79344fdac86bd7fa2ef274 (patch) | |
tree | fa3a4d9c45b6efc977ce54b2415143763ce61d77 | |
download | aur-e9222aa68ef940105d79344fdac86bd7fa2ef274.tar.gz |
Initial import
-rw-r--r-- | .SRCINFO | 42 | ||||
-rw-r--r-- | HMISET.CFG | 12 | ||||
-rw-r--r-- | PKGBUILD | 62 | ||||
-rw-r--r-- | RUN.BAT | 3 | ||||
-rw-r--r-- | Z.CFG | 13 | ||||
-rwxr-xr-x | dagger.conf | 33 | ||||
-rw-r--r-- | dagger.install | 35 | ||||
-rwxr-xr-x | daggerfall-launcher.pl | 1881 | ||||
-rw-r--r-- | license | 38 | ||||
-rw-r--r-- | maps.patch | bin | 0 -> 402 bytes | |||
-rwxr-xr-x | unpk.py | 519 |
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 Binary files differnew file mode 100644 index 000000000000..e3b34d063bbe --- /dev/null +++ b/maps.patch 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]" + |