#!/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);
}