Skip to content

Commit

Permalink
Add utilities that can manage the resource archives.
Browse files Browse the repository at this point in the history
  • Loading branch information
the3dfxdude committed Jun 30, 2017
1 parent 29d4f23 commit 4fdaca2
Show file tree
Hide file tree
Showing 8 changed files with 764 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ SUBDIRS = data include src po
doc_DATA = COPYING README

# these are included recursively by dist
EXTRA_DIST = doc packaging
EXTRA_DIST = doc packaging tools
3 changes: 3 additions & 0 deletions tools/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This directory contains development tools that may be useful for managing
the resource archives. To get more information on the format type of each
resource file, see the development section of the wiki at 7kfans.com.
268 changes: 268 additions & 0 deletions tools/dbf.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
#
# Seven Kingdoms: Ancient Adversaries
#
# Copyright 1997,1998 Enlight Software Ltd.
# Copyright 2017 Jesse Allen
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#

package dbf;

use warnings;
use strict;

sub trim {
$_[0] =~ s/^\s+//;
$_[0] =~ s/\s+$//;
return $_[0];
}

sub read_file {
my $class;
my $dbf_file;
my $fh;
my $buf;
my %header;
my $end;
my @fields;
my @records;
my $rec_packing;
my $success;

($class, $dbf_file) = @_;

$success = 0;
if (!open($fh, '<', $dbf_file)) {
#print "Error: Cannot open $dbf_file\n";
goto OUT;
}
if (read($fh, $buf, 32) != 32) {
#print "Error: Corrupt file\n";
goto OUT;
}
($header{dbf_id},
$header{year},
$header{month},
$header{day},
$header{records},
$header{hsize},
$header{rsize},
) = unpack('ccccLSS', $buf);
$rec_packing = 'C'; # record status indicator, included in record's array
while (!eof($fh)) {
my ($name, $type, $reserved0, $len);
if (read($fh, $buf, 32) != 32) {
#print "Error: Corrupt file\n";
goto OUT;
}
($end) = unpack('C', $buf);
if ($end == hex('0D')) {
seek($fh, $header{hsize}, 0);
last;
}
($name, $type, $reserved0, $len) = unpack('Z11aLC', $buf);
if ($type eq 'C' || $type eq 'N') {
# even though field type 'C' could be extracted with
# pack indicator of 'A', this would mess up the handling
# of the PTR type fields
$rec_packing .= "a$len";
} else {
print "Error: Unhandled record type '$type'\n";
goto OUT;
}
push(@fields, [$name, $type, $reserved0, $len]);
}
while (!eof($fh)) {
my @record;
my $bytes = read($fh, $buf, $header{rsize});
($end) = unpack('C', $buf);
if ($end == hex('1A')) {
last;
}
if ($bytes != $header{rsize}) {
#print "Error: Corrupt file\n";
goto OUT;
}
@record = unpack($rec_packing, $buf);
push(@records, \@record);
}
$success = 1;
OUT:
close($fh);
if ($success) {
my $dbf = {
header => \%header,
fields => \@fields,
records => \@records,
};
bless($dbf, $class);
return $dbf;
}
return undef;
}

sub write_file {
my $dbf;
my $dbf_file;
my $fh;
my $buf;
my $packing;
($dbf, $dbf_file) = @_;
if (!open($fh, '>', $dbf_file)) {
print "Error: Cannot write $dbf_file\n";
close($fh);
return;
}
print $fh pack('ccccLSSx20',
$dbf->{header}{dbf_id},
$dbf->{header}{year},
$dbf->{header}{month},
$dbf->{header}{day},
$dbf->{header}{records},
$dbf->{header}{hsize},
$dbf->{header}{rsize},
);
$packing = 'C'; # record status indicator
for (my $i = 0; $i < @{$dbf->{fields}}; $i++) {
my $field;
$field = $dbf->{fields}[$i];
print $fh pack('Z11aLCx15', @$field);
if ($field->[1] eq 'C' || $field->[1] eq 'N') {
$packing .= "a$field->[3]";
}
}
print $fh pack('C', hex('0D'));
for (my $i = 0; $i < @{$dbf->{records}}; $i++) {
my $record;
$record = $dbf->{records}[$i];
print $fh pack($packing, @$record);
}
print $fh pack('C', hex('1A'));
close($fh);
return;
}

# return the index of $field_name
sub get_field {
my $dbf;
my $field_name;
($dbf, $field_name) = @_;

for (my $i = 0; $i < @{$dbf->{fields}}; $i++) {
my $field;
my $name;
$field = $dbf->{fields}[$i];
$name = $field->[0];
print "$name eq $field_name\n";
if ($name eq $field_name) {
return $i;
}
}
return -1;
}

# returns the field record length
sub get_field_len {
my $dbf;
my $field;
($dbf, $field) = @_;
if (!defined($dbf->{fields}[$field])) {
return 0;
}
return $dbf->{fields}[$field][3];
}

# returns the number of fields in a record
sub get_field_names {
my $dbf;
my @names;
($dbf) = @_;
@names = map {$_->[0]} @{$dbf->{fields}};
return \@names;
}

# return array representing the values in the record
sub get_record {
my $dbf;
my $index;
my $record;
my @values;
($dbf, $index) = @_;
if (!defined($dbf->{records}[$index])) {
return undef;
}
# check for deleted record, just in case.
if ($dbf->{records}[$index][0] == hex('2A')) {
return undef;
}
$record = $dbf->{records}[$index];
@values = @{$record}[1..$#$record];
return \@values;
}

# return the number of records
sub get_records {
my $dbf;
($dbf) = @_;
return $dbf->{header}{records};
}

# return the value of a record field
# $record and $field are indexes
# values are generally treated as strings, without whitespace trimming
sub get_value {
my $dbf;
my $record;
my $field;
my $record_field_idx;
my $value;
($dbf, $record, $field) = @_;
$record_field_idx = $field+1; # adjusted due to record status indicator
if (!defined($dbf->{records}[$record])) {
return undef;
}
# check for deleted record, just in case.
if ($dbf->{records}[$record][0] == hex('2A')) {
return undef;
}
if (!defined($dbf->{records}[$record][$record_field_idx])) {
return undef;
}
$value = $dbf->{records}[$record][$record_field_idx];
return $value;
}

# sets the value of a record field
# the user must properly format $value to the expected convention
sub set_value {
my $dbf;
my $record;
my $field;
my $value;
my $record_field_idx;
($dbf, $record, $field, $value) = @_;
$record_field_idx = $field+1; # adjusted due to record status indicator
if (!defined($dbf->{records}[$record])) {
return 0;
}
if (!defined($dbf->{records}[$record][$record_field_idx])) {
return 0;
}
$dbf->{records}[$record][$record_field_idx] = $value;
return 1;
}

1;
68 changes: 68 additions & 0 deletions tools/dbfdump
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/perl
#
# Seven Kingdoms: Ancient Adversaries
#
# Copyright 1997,1998 Enlight Software Ltd.
# Copyright 2017 Jesse Allen
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#

use warnings;
use strict;

use FindBin;
use lib $FindBin::Bin;

use List::Util qw(min max);
use dbf;

if (!@ARGV) {
print "Usage: $0 input.dbf\n";
exit 0;
}
my ($dbf_file) = @ARGV;

my ($dbf) = dbf->read_file($dbf_file);

# used for write testing
#$dbf->write_file("$dbf_file.new");

my $records = $dbf->get_records();
my $fields = $dbf->get_field_names();
my @ptr_fields;
my $format = "%6s:";
for (my $i = 0; $i < @$fields; $i++) {
my $len = max($dbf->get_field_len($i), length($fields->[$i]));
if ($fields->[$i] =~ /PTR/) {
$format .= " %-${len}s";
push(@ptr_fields, $i);
} else {
$format .= " %-${len}s";
}
}
$format .= "\n";
printf($format, "RECORD", @$fields);
#print "Format string: $format\n";
for (my $i = 0; $i < $records; $i++) {
my $record = $dbf->get_record($i);
for (my $j = 0; $j < @ptr_fields; $j++) {
my $val = unpack('L', $record->[$ptr_fields[$j]]);
$record->[$ptr_fields[$j]] = $val;
}
printf($format, $i, @$record);
}

exit 0;
Loading

0 comments on commit 4fdaca2

Please sign in to comment.