bl791/IFDB / programming /tads2 /utils /t2disasm.pl
bl791's picture
download
raw
21 kB
#!/usr/bin/perl -w
use Fcntl qw(SEEK_SET);
use integer;
# Extract information from within a TADS GAM file
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
return ($c4 << 24) | ($c3 << 16) | ($c2 << 8) | $c1;
}
sub read4(*) {
my $FH = shift;
my $str;
read($FH, $str, 4);
return ord4($str);
}
$xorseed = 0x3f;
$xorinc = 0x40;
sub decode($) {
my $block = shift;
return $block unless ($crypt);
my $len = length($block);
my $mask = $xorseed;
my $maskstr;
for (my $i = 0; $i < $len; $i++) {
$maskstr .= chr($mask);
$mask = ($mask + $xorinc) % 256;
}
return $block ^ $maskstr;
}
sub hexl($) {
my $block = shift;
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;
if ($type == DAT_NUMBER) {
return sigend($lenleft) if ($lenleft < 4);
print ord4($block, $pos);
return 4;
}
elsif ($type == DAT_OBJECT || $type == DAT_FNADDR) {
print "&" if ($type == DAT_FNADDR);
return sigend($lenleft) if ($lenleft < 2);
printObj(ord2($block, $pos));
return 2;
}
elsif ($type == DAT_SSTRING || $type == DAT_DSTRING) {
return sigend($lenleft) if ($lenleft < 2);
my $strlen = ord2($block, $pos);
# Check for valid string length
return sigend($lenleft) if ($strlen > $lenleft || $strlen < 2);
my $delim = "\"";
$delim = "'" if ($type == DAT_SSTRING);
print $delim, substr($block, $pos + 2, $strlen - 2), $delim;
return $strlen;
}
elsif ($type == DAT_NIL) {
print "nil";
return 0;
}
elsif ($type == DAT_LIST) {
return sigend($lenleft) if ($lenleft < 2);
my $listlen = ord2($block, $pos);
# Check for valid list length
return sigend($lenleft) if ($listlen > $lenleft || $listlen < 2);
printList(substr($block, $pos, $listlen));
return $listlen;
}
elsif ($type == DAT_TRUE) {
print "true";
return 0;
}
elsif ($type == DAT_PROPNUM) {
return sigend($lenleft) if ($lenleft < 2);
print "&";
printProp(ord2($block, $pos));
return 2;
}
else {
print "Unexpected data type: $type\n";
return $lenleft;
}
}
sub printList($) {
my $block = shift;
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;
if ($type == OPBYTE) {
return sigend($lenleft) if ($lenleft < 1);
print ord1($block, $pos);
return 1;
}
elsif ($type == OPWORD || $type == OPRET) {
return sigend($lenleft) if ($lenleft < 2);
print ord2s($block, $pos);
return 2;
}
elsif ($type == OPQUAD) {
return printValue($block, $pos, DAT_NUMBER);
}
elsif ($type == OPOBJ || $type == OPFUNC) {
return printValue($block, $pos, DAT_OBJECT);
}
elsif ($type == OPPROP) {
return sigend($lenleft) if ($lenleft < 2);
printProp(ord2($block, $pos));
return 2;
}
elsif ($type == OPLABEL || $type == OPSWITCH) {
return sigend($lenleft) if ($lenleft < 2);
print $pos + ord2s($block, $pos);
return 2;
}
elsif ($type == OPDSTR) {
return printValue($block, $pos, DAT_DSTRING);
}
elsif ($type == OPSSTR) {
return printValue($block, $pos, DAT_SSTRING);
}
elsif ($type == OPBIF) {
return sigend($lenleft) if ($lenleft < 2);
printBuiltin(ord2($block, $pos));
return 2;
}
elsif ($type == OPLIST) {
return printValue($block, $pos, DAT_LIST);
}
elsif ($type == OPLINE) {
return sigend($lenleft) if ($lenleft < 1);
my $linelen = ord1($block, $pos);
return sigend($lenleft) if ($linelen > $lenleft || $linelen < 1);
print "line record (", $linelen, " bytes)";
return $linelen;
}
elsif ($type == OPFRAME) {
return sigend($lenleft) if ($lenleft < 2);
my $framelen = ord2($block, $pos);
return sigend($lenleft) if ($framelen > $lenleft || $framelen < 2);
print "frame record (", $framelen, " bytes)";
return $framelen;
}
else {
print "This shouldn't happen: unknown operand type ", $type, "\n";
die;
}
}
@props = (undef, "doAction", "verb", "noun", "adjective", "preposition",
"article", "plural", "sdesc", "thedesc", "doDefault",
"ioDefault", "ioAction", "location", "value",
"roomAction", "actorAction", "contents", "tpl",
"prepDefault", "verActor", "validDo", "validIo",
"lookAround", "roomCheck", "statusLine", "locationOK",
"isVisible", "cantReach", "isHim", "isHer", "action",
"validDoList", "validIoList", "iobjGen", "dobjGen",
"nilPrep", "rejectMultiDobj", "moveInto", "construct",
"destruct", "validActor", "preferredActor", "isEquivalent",
"adesc", "multisdesc", "tpl2", "anyvalue",
"newNumbered", "unknown", "parseUnknownDobj",
"parseUnknownIobj", "dobjCheck", "iobjCheck", "verbAction",
"disambigDobj", "disambigIobj", "prefixdesc", "isThem");
sub printProp($) {
my $n = shift;
if ($n == 0) {
print "noprop";
}
elsif ($n > 0 && defined $props[$n]) {
print $props[$n];
}
else {
print "prop", $n;
}
}
@objs = ();
sub printObj($) {
my $n = shift;
if ($n == 65535) {
print "nullobj";
}
elsif (defined $objs[$n]) {
print $objs[$n];
}
else {
print "obj", $n;
}
}
sub printBuiltin($) {
my $n = shift;
my @builtins = ("say", "car", "cdr", "length", "randomize", "rand",
"substr", "cvtstr", "cvtnum", "upper", "lower",
"caps", "find", "getarg", "datatype", "setdaemon",
"setfuse", "setversion", "notify", "unnotify",
"yorn", "remfuse", "remdaemon", "incturn", "quit",
"save", "restore", "logging", "input", "setit",
"askfile", "setscore", "firstobj", "nextobj",
"isclass", "restart", "debugTrace", "undo", "defined",
"proptype", "outhide", "runfuses", "rundaemons",
"gettime", "getfuse", "intersect", "inputkey",
"objwords", "addword", "delword", "getwords",
"nocaps", "skipturn", "clearscreen", "firstsc",
"verbinfo", "fopen", "fclose", "fwrite", "fread",
"fseek", "fseekeof", "ftell", "outcapture",
"systemInfo", "morePrompt", "parserSetMe",
"parserGetMe", "reSearch", "reGetGroup", "inputevent",
"timeDelay", "setOutputFilter", "execCommand",
"parserGetObj", "parseNounList", "parserTokenize",
"parserGetTokTypes", "parserDictLookup",
"parserResolveObjects", "parserReplaceCommand",
"exitobj", "inputdialog", "resourceExists");
if ($n < 0 || $n > $#builtins) {
print "invalid builtin $n";
}
else {
print $builtins[$n];
}
}
@opcodes =
([],
["pushnum", OPQUAD],
["pushobj", OPOBJ],
["neg"],
["not"],
["add"],
["sub"],
["mul"],
["div"],
["and"],
["or"],
["eq"],
["ne"],
["gt"],
["ge"],
["lt"],
["le"],
["call", OPBYTE, OPFUNC],
["getp", OPBYTE, OPPROP],
["getpdata", OPBYTE, OPPROP],
["getlcl", OPWORD],
["ptrgetpdata", OPBYTE],
["return", OPRET],
["retval", OPRET],
["enter", OPWORD],
["discard"],
["jmp", OPLABEL],
["jf", OPLABEL],
["pushself"],
["say", OPDSTR],
["builtin", OPBYTE, OPBIF],
["pushstr", OPSSTR],
["pushlst", OPLIST],
["pushnil"],
["pushtrue"],
["pushfn", OPFUNC],
["getpselfdata", OPBYTE, OPPROP],
[],
["ptrcall", OPBYTE],
["ptrinh", OPBYTE],
["ptrgetp", OPBYTE],
["pass", OPPROP],
["exit"],
["abort"],
["askdo"],
["askio", OPOBJ],
["expinh", OPBYTE, OPPROP, OPOBJ],
["expinhptr", OPBYTE, OPOBJ],
["calld", OPBYTE, OPFUNC],
["getpd", OPBYTE, OPPROP],
["builtind", OPBYTE, OPBIF],
["je", OPLABEL],
["jne", OPLABEL],
["jgt", OPLABEL],
["jge", OPLABEL],
["jlt", OPLABEL],
["jle", OPLABEL],
["jnand", OPLABEL],
["jnor", OPLABEL],
["jt", OPLABEL],
["getpself", OPBYTE, OPPROP],
["getpslfd", OPBYTE, OPPROP],
["getpobj", OPBYTE, OPOBJ, OPPROP],
["getpobjd", OPBYTE, OPOBJ, OPPROP],
["index"],
[],
[],
["pushpn", OPPROP],
["jst", OPLABEL],
["jsf", OPLABEL],
["jmpd", OPLABEL],
["inherit", OPBYTE, OPPROP],
["callext", OPBYTE, OPWORD],
["dbgret"],
["cons", OPBYTE],
["switch", OPSWITCH],
["argc"],
["chkargc", OPBYTE],
["line", OPLINE],
["frame", OPFRAME],
["bp", OPLINE],
["getdblcl", OPWORD, OPWORD, OPWORD],
["getpptrself", OPBYTE],
["mod"],
["band"],
["bor"],
["xor"],
["bnot"],
["shl"],
["shr"],
["new"],
["delete"]);
sub disasm($) {
my $block = shift;
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));
}
$pos += printOperand($block, $pos, $opcdata[$i]);
print ", " if ($i < $#opcdata);
}
}
elsif (($opc & 0xc0) == 0xc0) { # Assignment
print "assign\t";
my $ext = 0;
if (($opc & 0x1c) == 0x1c) {
$ext = ord1($block, $pos);
$pos++;
}
if (($opc & 3) == 0) {
print "local ", ord2($block, $pos);
$pos += 2;
} elsif (($opc & 3) == 1) {
print "property ";
printProp(ord2($block, $pos));
$pos += 2;
} elsif (($opc & 3) == 2) {
print "list";
} else {
print "property pointer";
}
print " :=" if (($opc & 0x1c) == 0x00);
print " +=" if (($opc & 0x1c) == 0x04);
print " -=" if (($opc & 0x1c) == 0x08);
print " *=" if (($opc & 0x1c) == 0x0c);
print " /=" if (($opc & 0x1c) == 0x10);
print " ++" if (($opc & 0x1c) == 0x14);
print " --" if (($opc & 0x1c) == 0x18);
print " %=" if ($ext == 1);
print " &=" if ($ext == 2);
print " |=" if ($ext == 3);
print " ^=" if ($ext == 4);
print " <<=" if ($ext == 5);
print " >>=" if ($ext == 6);
if (($opc & 0x1c) == 0x14 || ($opc & 0x1c) == 0x18) {
if ($opc & 0x20) {
print " pre";
}
else {
print " post";
}
}
else {
print " and discard" if ($opc & 0x20);
}
}
else {
print "Invalid opcode ", $opc, "\n";
last;
}
if ($numswitches >= 0) {
print "\t-> ";
$pos += printOperand($block, $pos, OPLABEL);
$numswitches--;
}
print "\n";
}
}
}
sub processXSI(*$) {
my $FH = shift;
$xorseed = read1($FH);
$xorinc = read1($FH);
print " xorseed = ", $xorseed, ", xorinc = ", $xorinc, "\n";
}
sub processOBJ(*$) {
my $FH = shift;
my $len = shift;
while ($len > 0) {
my $type = read1($FH);
my $n = read2($FH);
my $size = read2($FH);
my $use = read2($FH);
print " Object ";
printObj($n);
print ":\n Type $type";
print " (function)" if ($type == 1);
print " (object)" if ($type == 2);
print " (extern)" if ($type == 10);
print "\n Size $size\n";
print " Size used $use\n";
my $block;
read($FH, $block, $use);
$block = decode($block);
if ($type == 1) {
disasm($block);
}
else {
print " Workspace ", ord2($block, 0), "\n";
print " Flags ", ord2($block, 2), "\n";
print " Free ", ord2($block, 8), "\n";
print " Reset ", ord2($block, 10), "\n";
print " Static ", ord2($block, 12), "\n";
print " Superclasses:";
my $n = ord2($block, 4);
for (my $i = 0; $i < $n; $i++) {
print " ";
printObj(ord2($block, 14 + 2 * $i));
}
print "\n";
my $flags = ord2($block, 2);
my $nprop = ord2($block, 6);
my $pos = 14 + 2 * $n;
if ($flags & 2) {
# Skip the index table if present
$pos += 2 * $nprop;
}
for (my $i = 0; $i < $nprop; $i++) {
my $num = ord2($block, $pos);
my $type = ord1($block, $pos + 2);
my $size = ord2($block, $pos + 3);
print " Property ";
printProp($num);
print ":\n";
print " Datatype ", $type, "\n";
print " Size ", $size, "\n";
print " Flags ", ord1($block, $pos + 5), "\n";
if ($type == DAT_CODE) { # code
disasm(substr($block, $pos + 6, $size));
}
elsif ($type == DAT_DEMAND) {
print " implicit contents list\n";
}
elsif ($type == DAT_SYN) { # property synonym
print " synonym to property ";
printProp(ord2($block, $pos + 6));
print "\n";
}
elsif ($type == DAT_REDIR) { # redirection to another object
print " redirection to object ";
printObj(ord2($block, $pos + 6));
print "\n";
}
elsif ($type == DAT_TPL2) { # tpl2
my $i = ord1($block, $pos + 6);
my $intpos = $pos + 7;
for (my $j = 0; $j < $i; $j++) {
print " preposition ";
printObj(ord2($block, $intpos));
print ":\n verIoVerb ";
printProp(ord2($block, $intpos + 2));
print "\n ioVerb ";
printProp(ord2($block, $intpos + 4));
print "\n verDoVerb ";
printProp(ord2($block, $intpos + 6));
print "\n doVerb ";
printProp(ord2($block, $intpos + 8));
print "\n flags ", ord1($block, $intpos + 10), "\n";
$intpos += 16;
}
}
else {
print " ";
printValue($block, $pos + 6, $type);
print "\n";
}
$pos += 6 + $size;
}
}
$len -= $use + 7;
}
}
sub processFST(*$) {
my $FH = shift;
my $len = shift;
while ($len > 0) {
my $type = read1($FH);
my $n = read2($FH);
my $size = read2($FH);
my $use = read2($FH);
my $ofs = read4($FH);
print " Object ";
printObj($n);
print ":\n Type $type";
print " (function)" if ($type == 1);
print " (object)" if ($type == 2);
print " (extern)" if ($type == 10);
print "\n Size $size\n";
print " Size used $use\n";
print " Offset $ofs\n";
$len -= 11;
}
}
sub processINH(*$) {
my $FH = shift;
my $len = shift;
while ($len > 0) {
my $flag = read1($FH);
my $n = read2($FH);
my $loc = read2($FH);
my $ilc = read2($FH);
my $i = read2($FH);
print " Object ";
printObj($n);
print ":\n Flags $flag\n";
print " Loc ";
printObj($loc);
print "\n Ilc ";
printObj($ilc);
print "\n Superclasses:";
for (my $j = 0; $j < $i; $j++) {
print " ";
printObj(read2($FH));
}
print "\n";
$len -= (9 + 2 * $i);
}
}
sub processREQ(*$) {
my $FH = shift;
my $len = shift;
my @reqnames = ("Me", "takeVerb", "strObj", "numObj", "pardon",
"againVerb", "init", "preparse", "parseError",
"cmdPrompt", "parseDisambig", "parseError2",
"parseDefault", "parseAskobj", "preparseCmd",
"parseAskobjActor", "parseErrorParam", "commandAfterRead",
"initRestore", "parseUnknownVerb", "parseNounPhrase",
"postAction", "endCommand", "preCommand",
"parseAskobjIndirect");
foreach $name(@reqnames) {
return if ($len <= 0);
print " $name: "; printObj(read2($FH)); print "\n";
$len -= 2;
}
}
sub processFMTSTR(*$) {
my $FH = shift;
my $len = shift;
my $block;
$len = read2($FH);
read($FH, $block, $len);
$block = decode($block);
my $pos = 0;
while ($pos < $len) {
my $strlen = ord2($block, $pos + 2);
print " ", substr($block, $pos + 4, $strlen - 2), " -> ";
printProp(ord2($block, $pos));
print "\n";
$pos += $strlen + 2;
}
}
sub processCMPD(*$) {
my $FH = shift;
my $len = shift;
my $block;
$len = read2($FH);
read($FH, $block, $len);
$block = decode($block);
my $pos = 0, $i = 0;
while ($pos < $len) {
my $thislen = ord2($block, $pos);
print " " if ($i % 3 == 0);
print substr($block, $pos + 2, $thislen - 2);
print " " if ($i % 3 == 0);
print " => " if ($i % 3 == 1);
print "\n" if ($i % 3 == 2);
$i++;
$pos += $thislen;
}
print "\n" unless ($i % 3 == 0);
}
sub processSPECWORD(*$) {
my $FH = shift;
my $len = shift;
my $block;
$len = read2($FH);
read($FH, $block, $len);
$block = decode($block);
my $pos = 0;
while ($pos < $len) {
$flags = ord1($block, $pos);
$strlen = ord1($block, $pos + 1);
print " ", substr($block, $pos + 2, $strlen),
", flags ", $flags, "\n";
$pos += $strlen + 2;
}
}
sub processVOC(*$) {
my $FH = shift;
my $len = shift;
my $pos = 0;
while ($pos < $len) {
my $len1 = read2($FH);
my $len2 = read2($FH);
my $prpnum = read2($FH);
my $objnum = read2($FH);
my $classflg = read2($FH);
my $block;
read($FH, $block, $len1 + $len2);
$block = decode($block);
print " ", substr($block, 0, $len1);
print " ", substr($block, $len1, $len2) if ($len2 != 0);
print "\n Prpnum: ";
printProp($prpnum);
print "\n Object: ";
printObj($objnum);
print "\n Class flags: $classflg\n";
$pos += 10 + $len1 + $len2;
}
}
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');
seek(GAM, $nextofs, SEEK_SET);
}

Xet Storage Details

Size:
21 kB
·
Xet hash:
a8b09d0cd0194725d79ad5660c56fd823500a792358f8844f82775fa68779b17

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.