sub ord1($;$) {
my $str = shift;
my $ofs = shift || 0;
return ord(substr($str, $ofs));
}
sub read1(*) {
my $FH = shift;
return ord(getc($FH));
}
sub ord2($;$) {
my $str = shift;
my $ofs = shift || 0;
my $c1 = ord1($str, $ofs);
my $c2 = ord1($str, $ofs + 1);
return ($c2 << 8) | $c1;
}
sub ord2s($;$) {
my $str = shift;
my $ofs = shift || 0;
my $result = ord2($str, $ofs);
$result -= 0x10000 if ($result >= 0x8000);
return $result;
}
sub read2(*) {
my $FH = shift;
my $str;
read($FH, $str, 2);
return ord2($str);
}
sub ord4($;$) {
my $str = shift;
my $ofs = shift || 0;
my $c1 = ord1($str, $ofs);
my $c2 = ord1($str, $ofs + 1);
my $c3 = ord1($str, $ofs + 2);
my $c4 = ord1($str, $ofs + 3);
$c4 -= 256 if ($c4 >= 128); # In case of a signed value
local *HEXL;
open(HEXL, "|/usr/lib/xemacs-21.4.8/i386-debian-linux/hexl");
print HEXL $block;
close HEXL;
}
sub sigend($) {
print "(END REACHED)";
return shift;
}
# Datatype numbers
sub DAT_NUMBER() { return 1; }
sub DAT_OBJECT() { return 2; }
sub DAT_SSTRING() { return 3; }
sub DAT_BASEPTR() { return 4; }
sub DAT_NIL() { return 5; }
sub DAT_CODE() { return 6; }
sub DAT_LIST() { return 7; }
sub DAT_TRUE() { return 8; }
sub DAT_DSTRING() { return 9; }
sub DAT_FNADDR() { return 10; }
sub DAT_TPL() { return 11; }
sub DAT_PROPNUM() { return 13; }
sub DAT_DEMAND() { return 14; }
sub DAT_SYN() { return 15; }
sub DAT_REDIR() { return 16; }
sub DAT_TPL2() { return 17; }
# Print a value, returning the length it occupied
sub printValue($$$) {
my $block = shift;
my $pos = shift;
my $type = shift;
my $lenleft = length($block) - $pos;
print "[";
my $pos = 2;
my $len = ord2($block);
return sigend(length($block)) if ($len > length($block) || $len < 2);
while ($pos < $len) {
my $type = ord1($block, $pos);
$pos++;
$pos += printValue($block, $pos, $type);
print " " if ($pos < $len);
}
print "]";
return $len;
}
# Define operand type values
sub OPBYTE() { return 1; }
sub OPWORD() { return 2; }
sub OPQUAD() { return 3; }
sub OPOBJ() { return 4; }
sub OPFUNC() { return 5; }
sub OPPROP() { return 6; }
sub OPRET() { return 7; }
sub OPLABEL() { return 8; }
sub OPDSTR() { return 9; }
sub OPBIF() { return 10; }
sub OPSSTR() { return 11; }
sub OPLIST() { return 12; }
sub OPSWITCH() { return 13; } # Switch table
sub OPLINE() { return 14; } # Debugger line record
sub OPFRAME() { return 15; } # Local variable frame record
# Print an operand, returning the number of bytes it occupied
sub printOperand($$$) {
my $block = shift;
my $pos = shift;
my $type = shift;
my $lenleft = length($block) - $pos;
my @switches;
my $pos = 0, $len = length($block);
my $numswitches = -1; # Number of switch table entries left,
# -1 means not in switch table
# (It's this way so that switch tables with only a default get
# disassembled correctly.)
while ($pos < $len) {
print $pos, "\t";
# The one case where we're not pointed at an opcode: the end of
# a switch table
if ($numswitches == 0) {
print "default\t-> ";
$pos += printOperand($block, $pos, OPLABEL);
$numswitches = -1;
print "\n", $pos, "\t";
}
if ($#switches >= 0 && $pos == $switches[0]) {
print "Switch table\n";
$numswitches = ord2($block, $pos);
$pos += 2;
shift @switches;
}
else {
my $opc = ord1($block, $pos);
$pos++;
if ($opc >= 1 && $opc <= $#opcodes) {
my @opcdata = @{$opcodes[$opc]};
if ($#opcdata < 0) {
print "Invalid opcode $opc\n";
last;
}
print $opcdata[0], "\t";
for (my $i = 1; $i <= $#opcdata; $i++) {
# Handle operand to switch
if ($opcdata[$i] == OPSWITCH) {
@switches = sort(@switches, $pos + ord2s($block, $pos));
}
if ($#ARGV >= 1 && $ARGV[0] eq '-s') {
unless ($return = do $ARGV[1]) {
warn "couldn't parse file: $@" if $@;
warn "couldn't do $ARGV[1]: $!" unless defined ($return);
warn "couldn't run $ARGV[1]" unless $return;
}
splice(@ARGV, 0, 2);
}
if ($#ARGV != 0) {
die "Usage: untads [-s symbols] file.gam\n";
}
open(GAM, $ARGV[0]) || die("Couldn't find file $ARGV[0]");
# Assume for now that it is a TADS game; skip to the header flags
seek(GAM, 20, SEEK_SET);
$flags = read1(GAM);
$crypt = $flags & 8;
seek(GAM, 48, SEEK_SET);
for (;;) {
$namelen = read1(GAM);
read(GAM, $name, $namelen);
$nextofs = read4(GAM);
$curofs = tell(GAM);
$sectlen = $nextofs - $curofs;
if ($name eq '$EOF') {
print "\$EOF marker\n";
close GAM;
exit(0);
}
print "Section $name: $sectlen bytes ($curofs to $nextofs)\n";
processXSI(GAM, $sectlen) if ($name eq 'XSI');
processOBJ(GAM, $sectlen) if ($name eq 'OBJ');
processFST(GAM, $sectlen) if ($name eq 'FST');
processINH(GAM, $sectlen) if ($name eq 'INH');
processREQ(GAM, $sectlen) if ($name eq 'REQ');
processFMTSTR(GAM, $sectlen) if ($name eq 'FMTSTR');
processCMPD(GAM, $sectlen) if ($name eq 'CMPD');
processSPECWORD(GAM, $sectlen) if ($name eq 'SPECWORD');
processVOC(GAM, $sectlen) if ($name eq 'VOC');