| #!/usr/bin/perl -w | |
| =head1 NAME | |
| detads - decompile TADS 2 .GAM files | |
| =head1 SYNOPSIS | |
| B<detads> [B<-c>] [B<-s> I<file>] I<file> | |
| =head1 DESCRIPTION | |
| detads is a Perl script which (mostly) decompiles TADS 2 game files. | |
| The decompiled TADS 2 source code is printed to stdout. | |
| =head1 OPTIONS | |
| =over 4 | |
| =item B<-c> | |
| Decompile for the more C-like syntax (activated by #pragma C+ in | |
| TADS source). Default is to decompile for the default syntax. | |
| =item B<-s> I<file> | |
| Input a symbols file. The I<file> is a Perl script which can set | |
| several global variables, which control the names given to identifiers. | |
| For example: | |
| $objs[23] = 'redHerring'; | |
| $objs[35] = 'myFunction'; | |
| $props[312] = 'dontAnnoyMe'; | |
| $propArgs[312] = ['actor', 'maxAnnoyance']; | |
| $actions[40] = 'Sneezeat'; | |
| Note that it is not necessary (or desirable) to set names for builtin | |
| properties, objects and functions used directly by the builtin parser, | |
| or action methods (such as verDoSneezeat or doSneezeat in the above | |
| example). | |
| =item I<file> | |
| The TADS game file to be decompiled. | |
| =back | |
| =head1 BUGS | |
| Sometimes, because values don't get popped off the runtime stack in | |
| certain situations, the decompiler outputs errors. For example: | |
| f: function(arg1) { | |
| local local1, local2; | |
| g(arg1 + local1); | |
| // ERROR: nonempty stack | |
| // local1 := 1 | |
| ... | |
| } | |
| This comes from the code: | |
| f: function(arg1) { | |
| local local1 := 1, local2; | |
| g(arg1 + local1); | |
| ... | |
| } | |
| Similarly, output such as: | |
| local3 := 4; | |
| // ERROR: nonempty stack | |
| // local2 := foo; | |
| // local1 := 0; | |
| while (local1 < 10) { | |
| ... | |
| local1++; | |
| } | |
| comes from the code: | |
| for (local1 := 0, local2 := foo, local3 := 4; local1 < 10; local1++) { | |
| ... | |
| } | |
| Objects and functions will almost certainly not be output in the order | |
| they were defined in the original source code. | |
| The decompiler cannot detect `for' statements (as hinted above), or | |
| `modify' or `replace' directives. | |
| =head1 COPYRIGHT AND LICENSE | |
| Copyright 2003 by Daniel Schepler. | |
| This program is free software; you may redistribute it and/or modify | |
| it under the terms of the GNU General Public License, version 2 or | |
| later. | |
| =head1 AUTHOR | |
| Daniel Schepler <schepler@math.berkeley.edu> | |
| =cut | |
| use Fcntl qw(SEEK_SET); | |
| use integer; | |
| 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; | |
| } | |
| # 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 valueStr($$$) { | |
| my $block = shift; | |
| my $pos = shift; | |
| my $type = shift; | |
| if ($type == DAT_NUMBER) { | |
| return (ord4($block, $pos), $pos + 4); | |
| } | |
| elsif ($type == DAT_OBJECT || $type == DAT_FNADDR) { | |
| return (($type == DAT_FNADDR ? "&" : "") . objStr(ord2($block, $pos)), | |
| $pos + 2); | |
| } | |
| elsif ($type == DAT_PROPNUM) { | |
| return ("&" . propStr(ord2($block, $pos)), $pos + 2); | |
| } | |
| elsif ($type == DAT_SSTRING || $type == DAT_DSTRING) { | |
| my ($str, $newpos) = stringStr($block, $pos, | |
| $type == DAT_SSTRING ? "'" : '"'); | |
| return ($str, $newpos); | |
| } | |
| elsif ($type == DAT_LIST) { | |
| my ($str, $newpos) = listStr($block, $pos); | |
| return ($str, $newpos); | |
| } | |
| elsif ($type == DAT_NIL) { | |
| return ("nil", $pos); | |
| } | |
| elsif ($type == DAT_TRUE) { | |
| return ("true", $pos); | |
| } | |
| else { | |
| warn "Unexpected data type: $type"; | |
| return ("", length($block)); | |
| } | |
| } | |
| sub listStr($$) { | |
| my $block = shift; | |
| my $pos = shift; | |
| my $result = "["; | |
| my $len = ord2($block, $pos); | |
| my $endpos = $pos + $len; | |
| my $str; | |
| $pos += 2; | |
| while ($pos < $endpos) { | |
| my $type = ord1($block, $pos); | |
| $pos++; | |
| ($str, $pos) = valueStr($block, $pos, $type); | |
| $result .= $str; | |
| $result .= ", " if $pos < $endpos; | |
| } | |
| $result .= "]"; | |
| return ($result, $endpos); | |
| } | |
| @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 propStr($) { | |
| my $n = shift; | |
| if ($n == 0) { | |
| return "noprop"; | |
| } | |
| elsif ($n > 0 && defined $props[$n]) { | |
| return $props[$n]; | |
| } | |
| else { | |
| return "prop$n"; | |
| } | |
| } | |
| @objs = (); | |
| sub objStr($) { | |
| my $n = shift; | |
| if ($n == 65535) { | |
| return "nullobj"; | |
| } | |
| elsif (defined $objs[$n]) { | |
| return $objs[$n]; | |
| } | |
| else { | |
| return "obj$n"; | |
| } | |
| } | |
| sub builtinStr($) { | |
| 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) { | |
| return "builtin$n"; | |
| } | |
| else { | |
| return $builtins[$n]; | |
| } | |
| } | |
| @propArgs = (undef, undef, undef, undef, undef, undef, | |
| undef, undef, undef, undef, | |
| ['actor', 'prep', 'iobj'], # doDefault | |
| ['actor', 'prep'], # ioDefault | |
| undef, undef, undef, | |
| ['actor', 'verb', 'dobj', 'prep', 'iobj'], # roomAction | |
| ['verb', 'dobj', 'prep', 'iobj'], # actorAction | |
| undef, undef, undef, undef, | |
| ['actor', 'obj', 'seqno'], # validDo | |
| ['actor', 'obj', 'seqno'], # validIo | |
| ['verbosity'], # lookAround | |
| ['verb'], # roomCheck | |
| undef, undef, | |
| ['vantage'], # isVisible | |
| ['actor', 'dolist', 'iolist', 'prep'], # cantReach | |
| undef, undef, | |
| ['actor'], # action | |
| ['actor', 'prep', 'iobj'], # validDoList | |
| ['actor', 'prep', 'dobj'], # validIoList | |
| ['actor', 'verb', 'dobj', 'prep'], # iobjGen | |
| ['actor', 'verb', 'iobj', 'prep'], # dobjGen | |
| ['prep'], # rejectMultiDobj | |
| ['dest'], # moveInto | |
| undef, undef, undef, undef, undef, undef, undef, undef, | |
| ['num'], # anyvalue | |
| ['actor', 'verb', 'num'], # newNumbered | |
| undef, | |
| ['actor', 'prep', 'iobj', 'wordlist'], # parseUnknownDobj | |
| ['actor', 'prep', 'iobj', 'wordlist'], # parseUnknownIobj | |
| ['actor', 'prep', 'iobj', 'prep'], # dobjCheck | |
| ['actor', 'prep', 'iobj', 'prep'], # iobjCheck | |
| ['actor', 'dobj', 'prep', 'iobj'], # verbAction | |
| ['actor', 'prep', 'iobj', 'verprop', 'wordlist', 'objlist', | |
| 'flaglist', 'numberWanted', 'isAmbiguous', 'silent'], | |
| # disambigDobj | |
| ['actor', 'prep', 'dobj', 'verprop', 'wordlist', 'objlist', | |
| 'flaglist', 'numberWanted', 'isAmbiguous', 'silent'], | |
| # disambigIobj | |
| ['show', 'current_index', 'count', 'multi_flags'], # prefixdesc | |
| undef | |
| ); | |
| @funcArgs = (); | |
| sub localStr($$) { | |
| my $num = shift; | |
| my $propNum = shift; # Negative for function | |
| if ($num < 0) { | |
| my $argList = ($propNum < 0 ? $funcArgs[-$propNum] : | |
| $propArgs[$propNum]); | |
| if (defined $argList && -1 - $num <= $#{$argList}) { | |
| return $argList->[-1 - $num]; | |
| } | |
| else { | |
| return "arg" . (-$num); | |
| } | |
| } | |
| else { | |
| return "local$num"; | |
| } | |
| } | |
| sub stringStr($$$) { | |
| my $block = shift; | |
| my $pos = shift; | |
| my $delim = shift; | |
| my $strlen = ord2($block, $pos); | |
| my $str = substr($block, $pos + 2, $strlen - 2); | |
| $str =~ s/$delim/\\$delim/g; | |
| return ($delim . $str . $delim, $pos + $strlen); | |
| } | |
| sub indentStr($) { | |
| my $level = shift; | |
| return ("\t" x ($level / 2)) . (" " x ($level % 2)); | |
| } | |
| sub funcArgs($) { | |
| my $num = shift; | |
| return "" if $num == 0; | |
| my @args = splice(@stack, -$num); | |
| splice(@precstack, -$num); | |
| return "(" . join(", ", reverse(@args)) . ")"; | |
| } | |
| # The possible precedence levels: | |
| # 14: atoms (numbers or symbols) | |
| # 13: . []; function calls | |
| # 12: ++ -- | |
| # 11: unary operators (-, not, ~, delete, new) | |
| # 10: * / % | |
| # 9: + - | |
| # 8: << >> | |
| # 7: comparisons | |
| # 6: & | |
| # 5: ^ | |
| # 4: | | |
| # 3: and | |
| # 2: or | |
| # 1: ?: | |
| # 0: assignments | |
| sub doBinaryOp($$) { | |
| my $op = shift; | |
| my $prec = shift; | |
| my $arg2 = pop(@stack); | |
| $arg2 = "($arg2)" if pop(@precstack) <= $prec; | |
| my $arg1 = pop(@stack); | |
| $arg1 = "($arg1)" if pop(@precstack) < $prec; | |
| push(@stack, "$arg1 $op $arg2"); | |
| push(@precstack, $prec); | |
| } | |
| sub doUnaryOp($$) { | |
| my $op = shift; | |
| my $prec = shift; | |
| my $arg = pop(@stack); | |
| $arg = "($arg)" if pop(@precstack) < $prec; | |
| push(@stack, "$op $arg"); | |
| push(@precstack, $prec); | |
| } | |
| sub checkStack($) { | |
| my $indentLevel = shift; | |
| my $nonempty = 0; | |
| foreach my $elt (@stack) { | |
| $nonempty = 1 unless $elt eq "*SAYEXPR*"; | |
| } | |
| if ($nonempty) { | |
| if ($#lines == -1) { | |
| push(@lines, ""); | |
| push(@labels, -1); | |
| } | |
| $lines[$#lines] .= "\n" . indentStr($indentLevel) . | |
| "// ERROR: nonempty stack"; | |
| while ($#stack >= 0) { | |
| $elt = pop(@stack); | |
| pop(@precstack); | |
| next if $elt eq "*SAYEXPR*"; | |
| $lines[$#lines] .= "\n" . indentStr($indentLevel) . | |
| "// $elt"; | |
| } | |
| } | |
| else { | |
| @stack = (); | |
| } | |
| } | |
| $cmode = 0; | |
| sub decompileBlock { | |
| my $block = shift; | |
| my $propNum = shift; | |
| my $startpos = shift; | |
| my $endpos = shift; | |
| my $breakLabel = shift; | |
| my $continueLabel = shift; | |
| my $indentLevel = shift; | |
| my $pos = $startpos; | |
| my $reachable = 1; | |
| my $linepos = $pos; | |
| while ($pos < $endpos || ($endpos == -1 && | |
| ($reachable || $pos <= $maxLabel))) { | |
| my $opcode = ord1($block, $pos); | |
| my $str; | |
| # Only a few opcodes don't fall through to the next | |
| $reachable = 1; | |
| $pos++; | |
| if ($opcode == 1) { # pushnum | |
| push(@stack, ord4($block, $pos)); | |
| push(@precstack, 14); | |
| $pos += 4; | |
| } | |
| elsif ($opcode == 2) { # pushobj | |
| push(@stack, objStr(ord2($block, $pos))); | |
| push(@precstack, 14); | |
| $pos += 2; | |
| } | |
| elsif ($opcode == 3) { # neg | |
| doUnaryOp("-", 11); | |
| } | |
| elsif ($opcode == 4) { # not | |
| doUnaryOp("not", 11); | |
| } | |
| elsif ($opcode == 5) { # add | |
| doBinaryOp("+", 9); | |
| } | |
| elsif ($opcode == 6) { # sub | |
| doBinaryOp("-", 9); | |
| } | |
| elsif ($opcode == 7) { # mul | |
| doBinaryOp("*", 10); | |
| } | |
| elsif ($opcode == 8) { # div | |
| doBinaryOp("/", 10); | |
| } | |
| elsif ($opcode == 9) { # and | |
| doBinaryOp($cmode ? "&&" : "and", 3); | |
| } | |
| elsif ($opcode == 10) { # or | |
| doBinaryOp($cmode ? "||" : "or", 2); | |
| } | |
| elsif ($opcode == 11) { # eq | |
| doBinaryOp($cmode ? "==" : "=", 7); | |
| } | |
| elsif ($opcode == 12) { # ne | |
| doBinaryOp($cmode ? "!=" : "<>", 7); | |
| } | |
| elsif ($opcode == 13) { # gt | |
| doBinaryOp(">", 7); | |
| } | |
| elsif ($opcode == 14) { # ge | |
| doBinaryOp(">=", 7); | |
| } | |
| elsif ($opcode == 15) { # lt | |
| doBinaryOp("<", 7); | |
| } | |
| elsif ($opcode == 16) { # le | |
| doBinaryOp("<=", 7); | |
| } | |
| elsif ($opcode == 17) { # call | |
| my $obj = objStr(ord2($block, $pos + 1)); | |
| push(@stack, $obj . funcArgs(ord1($block, $pos))); | |
| $stack[$#stack] .= "()" if ord1($block, $pos) == 0; | |
| push(@precstack, 13); | |
| $pos += 3; | |
| } | |
| elsif ($opcode == 18) { # getp | |
| my $prop = propStr(ord2($block, $pos + 1)); | |
| my $obj = pop(@stack); | |
| $obj = "($obj)" if pop(@precstack) < 13; | |
| push(@stack, "$obj.$prop" . funcArgs(ord1($block, $pos))); | |
| push(@precstack, 13); | |
| $pos += 3; | |
| } | |
| elsif ($opcode == 20) { # getlcl | |
| my $num = ord2s($block, $pos); | |
| $pos += 2; | |
| push(@stack, localStr($num, $propNum)); | |
| push(@precstack, 14); | |
| } | |
| elsif ($opcode == 22) { # return | |
| $pos += 2; | |
| $reachable = 0; | |
| # Check to see if it will be the last; if so, don't bother | |
| # printing the statement | |
| if ($endpos != -1 || $pos <= $maxLabel) { | |
| push(@lines, indentStr($indentLevel) . "return;"); | |
| } | |
| # But do make the label available for the top-level | |
| # decompile, in case it's needed | |
| push(@labels, $linepos); | |
| $linepos = $pos; | |
| checkStack($indentLevel); | |
| } | |
| elsif ($opcode == 23) { # retval | |
| $pos += 2; | |
| $reachable = 0; | |
| push(@lines, indentStr($indentLevel) . "return " . | |
| pop(@stack) . ";"); | |
| pop(@precstack); | |
| push(@labels, $linepos); | |
| $linepos = $pos; | |
| checkStack($indentLevel); | |
| } | |
| elsif ($opcode == 25) { # discard | |
| my $expr = pop(@stack); | |
| pop(@precstack); | |
| unless ($expr eq "*SAYEXPR*") { # See "builtin" case below | |
| push(@lines, indentStr($indentLevel) . "$expr;"); | |
| push(@labels, $linepos); | |
| } | |
| $linepos = $pos; | |
| checkStack($indentLevel); | |
| } | |
| elsif ($opcode == 26) { # jmp | |
| my $dest = $pos + ord2s($block, $pos); | |
| $pos += 2; | |
| if ($dest == $breakLabel) { | |
| push(@lines, indentStr($indentLevel) . "break;"); | |
| } | |
| elsif ($dest == $continueLabel) { | |
| push(@lines, indentStr($indentLevel) . "continue;"); | |
| } | |
| else { | |
| push(@lines, indentStr($indentLevel) . "goto label$dest;"); | |
| $labelNeeded{$dest} = 1; | |
| $maxLabel = $dest if $dest > $maxLabel; | |
| } | |
| push(@labels, $linepos); | |
| $linepos = $pos; | |
| checkStack($indentLevel); | |
| } | |
| elsif ($opcode == 27) { # jf | |
| my $dest = $pos + ord2s($block, $pos); | |
| $pos += 2; | |
| if ($dest < $startpos || ($endpos != -1 && $dest > $endpos)) { | |
| # Oops, it's a jump outside this block | |
| push(@lines, indentStr($indentLevel) . "if (not (" . | |
| pop(@stack) . ")) goto label$dest;"); | |
| pop(@precstack); | |
| push(@labels, $linepos); | |
| $labelNeeded{$dest} = 1; | |
| $maxLabel = $dest if $dest > $maxLabel; | |
| $linepos = $pos; | |
| checkStack($indentLevel); | |
| } | |
| elsif (ord1($block, $dest - 3) == 26 && # jmp | |
| ($dest - 2) + ord2s($block, $dest - 2) == $linepos) { | |
| # A while statement | |
| push(@lines, indentStr($indentLevel) . | |
| "while (" . pop(@stack) . ") {"); | |
| pop(@precstack); | |
| push(@labels, $linepos); | |
| decompileBlock($block, $propNum, $pos, $dest - 3, | |
| $dest, $linepos, $indentLevel + 1); | |
| push(@lines, indentStr($indentLevel) . "}"); | |
| push(@labels, $dest - 3); | |
| $pos = $dest; | |
| $linepos = $pos; | |
| checkStack($indentLevel); | |
| } | |
| elsif ($dest > $pos + 3 && # Decompile "if (cond) break;" correctly | |
| ord1($block, $dest - 3) == 26 && | |
| ord2s($block, $dest - 2) > 0 && | |
| ($endpos == -1 || | |
| ($dest - 2) + ord2s($block, $dest - 2) <= $endpos)) { | |
| # An if/else statement -- or ?: expression | |
| my $endElse = $dest - 2 + ord2s($block, $dest - 2); | |
| my $cond = pop(@stack); | |
| my $condprec = pop(@precstack); | |
| if ($linepos == $startpos && $endElse == $endpos && | |
| $#lines >= 0 && $lines[$#lines] eq | |
| (indentStr($indentLevel - 1) . "} else {")) { | |
| # Contract to "else if (...)" -- the contents of | |
| # an else statement shouldn't be a ?: expression | |
| # without even a discard | |
| $lines[$#lines] = indentStr($indentLevel - 1) . | |
| "} else if ($cond) {"; | |
| decompileBlock($block, $propNum, $pos, $dest - 3, | |
| $breakLabel, $continueLabel, | |
| $indentLevel); | |
| checkStack($indentLevel); | |
| push(@lines, indentStr($indentLevel - 1) . "} else {"); | |
| push(@labels, $dest - 3); | |
| decompileBlock($block, $propNum, $dest, $endElse, | |
| $breakLabel, $continueLabel, | |
| $indentLevel); | |
| # The outer decompile will provide the } | |
| $linepos = $endElse; | |
| checkStack($indentLevel); | |
| } | |
| else { | |
| push(@lines, indentStr($indentLevel) . | |
| "if ($cond) {"); | |
| push(@labels, $linepos); | |
| my $oldline = $#lines; | |
| decompileBlock($block, $propNum, $pos, $dest - 3, | |
| $breakLabel, $continueLabel, | |
| $indentLevel + 1); | |
| if ($#lines == $oldline) { | |
| # No output -- assume it's a ?: expression | |
| pop(@lines); # Remove "if" line | |
| pop(@labels); | |
| $cond = "($cond)" if $condprec <= 1; | |
| my $trueexpr = pop(@stack); | |
| $trueexpr = "($trueexpr)" if pop(@precstack) <= 1; | |
| decompileBlock($block, $propNum, $dest, $endElse, | |
| $breakLabel, $continueLabel, | |
| $indentLevel + 1); | |
| my $falseexpr = pop(@stack); | |
| $falseexpr = "($falseexpr)" if pop(@precstack) <= 1; | |
| push(@stack, "$cond ? $trueexpr : $falseexpr"); | |
| push(@precstack, 1); | |
| } | |
| else { | |
| checkStack($indentLevel); | |
| push(@lines, indentStr($indentLevel) . | |
| "} else {"); | |
| push(@labels, $dest - 3); | |
| decompileBlock($block, $propNum, $dest, $endElse, | |
| $breakLabel, $continueLabel, | |
| $indentLevel + 1); | |
| $lines[$#lines] .= "\n" . indentStr($indentLevel) . | |
| "}"; | |
| $linepos = $endElse; | |
| checkStack($indentLevel); | |
| } | |
| } | |
| $pos = $endElse; | |
| } | |
| else { | |
| # A plain if statement | |
| if ($linepos == $startpos && $dest == $endpos && | |
| $#lines >= 0 && $lines[$#lines] eq | |
| (indentStr($indentLevel - 1) . "} else {")) { | |
| # Contract to "else if (...)" | |
| $lines[$#lines] = indentStr($indentLevel - 1) . | |
| "} else if (" . pop(@stack) . ") {"; | |
| pop(@precstack); | |
| checkStack($indentLevel); | |
| decompileBlock($block, $propNum, $pos, $dest, | |
| $breakLabel, $continueLabel, | |
| $indentLevel); | |
| # The outer decompile will provide the } | |
| } | |
| else { | |
| push(@lines, indentStr($indentLevel) . | |
| "if (" . pop(@stack) . ") {"); | |
| pop(@precstack); | |
| push(@labels, $linepos); | |
| checkStack($indentLevel); | |
| decompileBlock($block, $propNum, $pos, $dest, | |
| $breakLabel, $continueLabel, | |
| $indentLevel + 1); | |
| $lines[$#lines] .= "\n" . indentStr($indentLevel) . "}"; | |
| } | |
| $pos = $dest; | |
| $linepos = $pos; | |
| checkStack($indentLevel); | |
| } | |
| } | |
| elsif ($opcode == 28) { # pushself | |
| push(@stack, "self"); | |
| push(@precstack, 14); | |
| } | |
| elsif ($opcode == 29) { # say | |
| ($str, $pos) = stringStr($block, $pos, '"'); | |
| # Try to combine with a previous string ending with | |
| # printing an expression | |
| if ($#lines >= 0 && substr($lines[$#lines], -4) eq '>>";') { | |
| $lines[$#lines] = substr($lines[$#lines], 0, -2) . | |
| substr($str, 1) . ";"; | |
| } | |
| else { | |
| push(@lines, indentStr($indentLevel) . "$str;"); | |
| push(@labels, $linepos); | |
| } | |
| $linepos = $pos; | |
| } | |
| elsif ($opcode == 30) { # builtin | |
| # Special case: say with 2 arguments is used to implement | |
| # "<< expr >>". The pop of the result isn't reliable, so | |
| # push a special token. | |
| if (builtinStr(ord2($block, $pos + 1)) eq "say" && | |
| ord1($block, $pos) == 2) { | |
| my $expr = pop(@stack); | |
| pop(@precstack); | |
| pop(@stack); # Usually nil for second argument | |
| pop(@precstack); | |
| # Try to combine with a previous string | |
| if ($#lines >= 0 && substr($lines[$#lines], -2) eq '";') { | |
| $lines[$#lines] = substr($lines[$#lines], 0, -2) . | |
| "<< $expr >>\";"; | |
| } | |
| else { | |
| push(@lines, indentStr($indentLevel) . | |
| "\"<< $expr >>\";"); | |
| push(@labels, $linepos); | |
| } | |
| push(@stack, "*SAYEXPR*"); | |
| push(@precstack, 14); | |
| } | |
| else { | |
| push(@stack, builtinStr(ord2($block, $pos + 1)) . | |
| funcArgs(ord1($block, $pos))); | |
| $stack[$#stack] .= "()" if ord1($block, $pos) == 0; | |
| push(@precstack, 13); | |
| } | |
| $pos += 3; | |
| } | |
| elsif ($opcode == 31) { # pushstr | |
| my $str; | |
| ($str, $pos) = stringStr($block, $pos, "'"); | |
| push(@stack, $str); | |
| push(@precstack, 14); | |
| } | |
| elsif ($opcode == 32) { # pushlst | |
| my $str; | |
| ($str, $pos) = listStr($block, $pos); | |
| push(@stack, $str); | |
| push(@precstack, 14); | |
| } | |
| elsif ($opcode == 33) { # pushnil | |
| push(@stack, "nil"); | |
| push(@precstack, 14); | |
| } | |
| elsif ($opcode == 34) { # pushtrue | |
| push(@stack, "true"); | |
| push(@precstack, 14); | |
| } | |
| elsif ($opcode == 35) { # pushfn | |
| push(@stack, "&" . objStr(ord2($block, $pos))); | |
| push(@precstack, 14); | |
| $pos += 2; | |
| } | |
| elsif ($opcode == 40) { # ptrgetp | |
| my $prop = pop(@stack); | |
| pop(@precstack); # It needs parentheses in all cases | |
| my $obj = pop(@stack); | |
| $obj = "($obj)" if pop(@precstack) < 13; | |
| push(@stack, "$obj.($prop)" . funcArgs(ord1($block, $pos))); | |
| push(@precstack, 13); | |
| $pos++; | |
| } | |
| elsif ($opcode == 41) { # pass | |
| my $prop = propStr(ord2($block, $pos)); | |
| $pos += 2; | |
| push(@lines, indentStr($indentLevel) . "pass $prop;"); | |
| push(@labels, $linepos); | |
| $linepos = $pos; | |
| checkStack($indentLevel); | |
| } | |
| elsif ($opcode == 42) { # exit | |
| push(@lines, indentStr($indentLevel) . "exit;"); | |
| push(@labels, $linepos); | |
| $linepos = $pos; | |
| checkStack($indentLevel); | |
| } | |
| elsif ($opcode == 43) { # abort | |
| push(@lines, indentStr($indentLevel) . "abort;"); | |
| push(@labels, $linepos); | |
| $linepos = $pos; | |
| checkStack($indentLevel); | |
| } | |
| elsif ($opcode == 44) { # askdo | |
| push(@lines, indentStr($indentLevel) . "askdo;"); | |
| push(@labels, $linepos); | |
| $linepos = $pos; | |
| checkStack($indentLevel); | |
| } | |
| elsif ($opcode == 45) { # askio | |
| push(@lines, indentStr($indentLevel) . "askio(" . | |
| propStr(ord2($block, $pos)) . ");"); | |
| push(@labels, $linepos); | |
| $pos += 2; | |
| $linepos = $pos; | |
| checkStack($indentLevel); | |
| } | |
| elsif ($opcode == 46) { # expinh | |
| push(@stack, "inherited " . objStr(ord2($block, $pos + 3)) . | |
| "." . propStr(ord2($block, $pos + 1)) . | |
| funcArgs(ord1($block, $pos))); | |
| push(@precstack, 13); | |
| $pos += 5; | |
| } | |
| elsif ($opcode == 59) { # jt | |
| # Used to implement a do/while statement | |
| my $dest = $pos + ord2s($block, $pos); | |
| $pos += 2; | |
| if ($dest >= $pos || $dest < $startpos) { | |
| # Oops, it's a jump forward or to before this block... | |
| noline: | |
| push(@lines, indentStr($indentLevel) . "if (" . | |
| pop(@stack) . ") goto label$dest;"); | |
| pop(@precstack); | |
| push(@labels, $linepos); | |
| $labelNeeded{$dest} = 1; | |
| $linepos = $pos; | |
| checkStack($indentLevel); | |
| } | |
| else { | |
| # Search for the beginning | |
| my $line = 0; | |
| $line++ while ($line < $#labels && $labels[$line] < $dest); | |
| goto noline unless $labels[$line] == $dest; | |
| my $cond = pop(@stack); | |
| pop(@precstack); | |
| # Delete everything inside the block, and do it over | |
| splice(@lines, $line); | |
| splice(@labels, $line); | |
| decompileBlock($block, $propNum, $dest, $linepos, | |
| $pos, $linepos, $indentLevel + 1); | |
| $lines[$line] = indentStr($indentLevel) . "do {\n" . | |
| $lines[$line]; | |
| push(@lines, indentStr($indentLevel) . "} while ($cond);"); | |
| push(@labels, $linepos); | |
| $linepos = $pos; | |
| checkStack($indentLevel); | |
| } | |
| } | |
| elsif ($opcode == 60) { # getpself | |
| my $prop = propStr(ord2($block, $pos + 1)); | |
| push(@stack, "self.$prop" . funcArgs(ord1($block, $pos))); | |
| push(@precstack, 13); | |
| $pos += 3; | |
| } | |
| elsif ($opcode == 62) { # getpobj | |
| my $obj = objStr(ord2($block, $pos + 1)); | |
| my $prop = propStr(ord2($block, $pos + 3)); | |
| push(@stack, "$obj.$prop" . funcArgs(ord1($block, $pos))); | |
| push(@precstack, 13); | |
| $pos += 5; | |
| } | |
| elsif ($opcode == 64) { # index | |
| my $arg2 = pop(@stack); | |
| pop(@precstack); | |
| my $arg1 = pop(@stack); | |
| $arg1 = "($arg1)" if pop(@precstack) < 13; | |
| push(@stack, $arg1 . "[" . $arg2 . "]"); | |
| push(@precstack, 13); | |
| } | |
| elsif ($opcode == 67) { # pushpn | |
| push(@stack, "&" . propStr(ord2($block, $pos))); | |
| push(@precstack, 14); | |
| $pos += 2; | |
| } | |
| elsif ($opcode == 68) { # jst | |
| # Used to implement an || operation; call decompileBlock | |
| # recursively to get the other argument | |
| my $dest = $pos + ord2s($block, $pos); | |
| $pos += 2; | |
| decompileBlock($block, $propNum, $pos, $dest, | |
| $breakLabel, $continueLabel, $indentLevel); | |
| doBinaryOp($cmode ? "||" : "or", 2); | |
| $pos = $dest; | |
| } | |
| elsif ($opcode == 69) { # jsf | |
| # Used to implement an && operation; call decompileBlock | |
| # recursively to get the other argument | |
| my $dest = $pos + ord2s($block, $pos); | |
| $pos += 2; | |
| decompileBlock($block, $propNum, $pos, $dest, | |
| $breakLabel, $continueLabel, $indentLevel); | |
| doBinaryOp($cmode ? "&&" : "and", 3); | |
| $pos = $dest; | |
| } | |
| elsif ($opcode == 71) { # inherit | |
| my $prop = propStr(ord2($block, $pos + 1)); | |
| push(@stack, "inherited.$prop" . | |
| funcArgs(ord1($block, $pos))); | |
| push(@precstack, 13); | |
| $pos += 3; | |
| } | |
| elsif ($opcode == 74) { # cons | |
| my @args = splice(@stack, -ord2($block, $pos)); | |
| splice(@precstack, -ord2($block, $pos)); | |
| push(@stack, "[" . join(", ", reverse(@args)) . "]"); | |
| push(@precstack, 14); | |
| $pos += 2; | |
| } | |
| elsif ($opcode == 75) { # switch | |
| my $swtable = $pos + ord2s($block, $pos); | |
| $pos += 2; | |
| my $swlen = ord2($block, $swtable); | |
| my $swpos = $swtable + 2; | |
| my @swcases = (); | |
| my @swdests = (); | |
| for (my $i = 0; $i < $swlen; $i++) { | |
| my $str; | |
| my $swopcode = ord1($block, $swpos); | |
| $swpos++; | |
| if ($swopcode == 1) { # pushint | |
| $str = ord4($block, $swpos); | |
| $swpos += 4; | |
| } | |
| elsif ($swopcode == 2) { # pushobj | |
| $str = objStr(ord2($block, $swpos)); | |
| $swpos += 2; | |
| } | |
| elsif ($swopcode == 31) { # pushstr | |
| ($str, $swpos) = stringStr($block, $swpos, "'"); | |
| } | |
| else { | |
| die "Unimplemented or invalid opcode $swopcode for switch"; | |
| } | |
| push(@swcases, $str); | |
| push(@swdests, $swpos + ord2s($block, $swpos)); | |
| $swpos += 2; | |
| } | |
| push(@swdests, $swpos + ord2s($block, $swpos)); # default | |
| $swpos += 2; | |
| push(@lines, indentStr($indentLevel) . "switch (" . | |
| pop(@stack) . ") {"); | |
| pop(@precstack); | |
| push(@labels, $linepos); | |
| checkStack($indentLevel); | |
| for (my $i = 0; $i <= $#swcases; $i++) { | |
| $lines[$#lines] .= "\n" . indentStr($indentLevel) . | |
| "case $swcases[$i]:"; | |
| if ($swdests[$i + 1] > $swtable) { | |
| decompileBlock($block, $propNum, | |
| $swdests[$i], $swtable - 3, | |
| $swpos, $continueLabel, $indentLevel + 1); | |
| } | |
| elsif ($swdests[$i + 1] > $swdests[$i]) { | |
| decompileBlock($block, $propNum, | |
| $swdests[$i], $swdests[$i + 1], | |
| $swpos, $continueLabel, $indentLevel + 1); | |
| } | |
| checkStack($indentLevel); | |
| } | |
| if ($swdests[$#swdests] < $swtable) { | |
| $lines[$#lines] .= "\n" . indentStr($indentLevel) . | |
| "default:"; | |
| decompileBlock($block, $propNum, | |
| $swdests[$#swdests], $swtable - 3, | |
| $swpos, $continueLabel, $indentLevel + 1); | |
| # $swtable - 3 skips the implicit "break" statement | |
| checkStack($indentLevel); | |
| } | |
| $lines[$#lines] .= "\n" . indentStr($indentLevel) . "}"; | |
| $pos = $swpos; | |
| $linepos = $pos; | |
| } | |
| elsif ($opcode == 76) { # argc | |
| push(@stack, "argc"); | |
| push(@precstack, 14); | |
| } | |
| elsif ($opcode == 83) { # mod | |
| doBinaryOp("%", 10); | |
| } | |
| elsif ($opcode == 84) { # band | |
| doBinaryOp("&", 6); | |
| } | |
| elsif ($opcode == 85) { # bor | |
| doBinaryOp("|", 4); | |
| } | |
| elsif ($opcode == 86) { # xor | |
| doBinaryOp("^", 5); | |
| } | |
| elsif ($opcode == 87) { # bnot | |
| doUnaryOp("~", 11); | |
| } | |
| elsif ($opcode == 88) { # shl | |
| doBinaryOp("<<", 8); | |
| } | |
| elsif ($opcode == 89) { # shr | |
| doBinaryOp(">>", 8); | |
| } | |
| elsif ($opcode == 90) { # new | |
| doUnaryOp("new", 11); | |
| } | |
| elsif ($opcode == 91) { # delete | |
| doUnaryOp("delete", 11); | |
| } | |
| elsif (($opcode & 0xc0) == 0xc0) { # Assignment | |
| my $ext = 0; | |
| if (($opcode & 0x1c) == 0x1c) { | |
| $ext = ord1($block, $pos); | |
| $pos++; | |
| } | |
| my $dest; | |
| if (($opcode & 3) == 0) { | |
| my $num = ord2s($block, $pos); | |
| $pos += 2; | |
| $dest = localStr($num, $propNum); | |
| } | |
| elsif (($opcode & 3) == 1) { | |
| my $prop = propStr(ord2s($block, $pos)); | |
| $pos += 2; | |
| my $obj = pop(@stack); | |
| $obj = "($obj)" if pop(@precstack) < 13; | |
| $dest = "$obj.$prop"; | |
| } | |
| elsif (($opcode & 3) == 2) { | |
| my $index = pop(@stack); | |
| pop(@precstack); | |
| my $list = pop(@stack); | |
| $list = "($list)" if pop(@precstack) < 13; | |
| $dest = $list . "[" . $index . "]"; | |
| } | |
| elsif (($opcode & 3) == 3) { | |
| my $prop = pop(@stack); | |
| pop(@precstack); | |
| my $obj = pop(@stack); | |
| $obj = "($obj)" if pop(@precstack) < 13; | |
| $dest = "$obj.($prop)"; | |
| } | |
| if (($opcode & 0x1c) == 0x14 || ($opcode & 0x1c) == 0x18) { | |
| my $op = (($opcode & 0x1c) == 0x14 ? "++" : "--"); | |
| if ($opcode & 0x20) { | |
| push(@stack, "$op$dest"); | |
| } | |
| else { | |
| push(@stack, "$dest$op"); | |
| } | |
| push(@precstack, 12); | |
| } | |
| else { | |
| my $op; | |
| $op = ($cmode ? "=" : ":=") if ($opcode & 0x1c) == 0x00; | |
| $op = "+=" if ($opcode & 0x1c) == 0x04; | |
| $op = "-=" if ($opcode & 0x1c) == 0x08; | |
| $op = "*=" if ($opcode & 0x1c) == 0x0c; | |
| $op = "/=" if ($opcode & 0x1c) == 0x10; | |
| $op = "%=" if $ext == 1; | |
| $op = "&=" if $ext == 2; | |
| $op = "|=" if $ext == 3; | |
| $op = "^=" if $ext == 4; | |
| $op = "<<=" if $ext == 5; | |
| $op = ">>=" if $ext == 6; | |
| my $val = pop(@stack); | |
| if (substr($val, 0, 10) eq "*NEWLIST* ") { | |
| push(@stack, substr($val, 10)); | |
| # Keep same precedence | |
| } | |
| else { | |
| pop(@precstack); | |
| # Assignment is lowest precedence, and right associative | |
| push(@stack, "$dest $op $val"); | |
| push(@precstack, 0); | |
| } | |
| } | |
| # Mark a list assignment for the later assignment back | |
| # into the appropriate lvalue | |
| if (($opcode & 3) == 2) { | |
| $stack[$#stack] = "*NEWLIST* $stack[$#stack]"; | |
| } | |
| } | |
| else { | |
| print join("\n", @lines), "\n"; | |
| die "Unimplemented or invalid opcode $opcode"; | |
| } | |
| } | |
| push(@labels, $pos) if $endpos == -1; | |
| } | |
| sub decompile($$) { | |
| my $block = shift; | |
| my $propNum = shift; | |
| my $pos = 0; | |
| if (ord1($block, $pos) == 77) { # chkargc | |
| my $numargs = ord1($block, $pos + 1); | |
| unless ($numargs == 0) { | |
| print "("; | |
| for (my $i = 1; $i <= ($numargs & 127); $i++) { | |
| print localStr(-$i, $propNum); | |
| print ", " if $i < ($numargs & 127); | |
| } | |
| print ", " if $numargs > 128; | |
| print "..." if $numargs >= 128; | |
| print ")"; | |
| } | |
| $pos += 2; | |
| } | |
| print " =" if $propNum > 0; | |
| print " {\n"; | |
| if (ord1($block, $pos) == 24) { # enter | |
| my $numlocals = ord2($block, $pos + 1); | |
| unless ($numlocals == 0) { | |
| print indentStr($propNum > 0 ? 2 : 1), "local "; | |
| for (my $i = 1; $i <= $numlocals; $i++) { | |
| print localStr($i, $propNum); | |
| print ", " if $i < $numlocals; | |
| } | |
| print ";\n\n"; | |
| } | |
| $pos += 3; | |
| } | |
| # Preliminary setup | |
| %labelNeeded = (); | |
| @lines = (); | |
| @labels = (); | |
| @stack = (); | |
| @precstack = (); | |
| $maxLabel = 0; | |
| decompileBlock($block, $propNum, $pos, -1, -1, -1, $propNum > 0 ? 2 : 1); | |
| for (my $i = 0; $i <= $#lines; $i++) { | |
| my $label = $labels[$i]; | |
| print "label$label:\n" if $labelNeeded{$label}; | |
| print $lines[$i], "\n"; | |
| } | |
| my $label = $labels[$#lines + 1]; | |
| print "label$label:\n" if $labelNeeded{$label}; | |
| print indentStr($propNum > 0 ? 1 : 0), "}"; | |
| } | |
| sub processXSI(*$) { | |
| my $FH = shift; | |
| $xorseed = read1($FH); | |
| $xorinc = read1($FH); | |
| } | |
| 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 $prop = ord2($block, $pos); | |
| my $str; | |
| ($str, $pos) = stringStr($block, $pos + 2, "'"); | |
| print "formatstring $str ", propStr($prop), ";\n"; | |
| } | |
| print "\n"; | |
| } | |
| sub processCMPD(*$) { | |
| my $FH = shift; | |
| my $len = shift; | |
| my $block; | |
| $len = read2($FH); | |
| read($FH, $block, $len); | |
| $block = decode($block); | |
| my $pos = 0; | |
| my $i = 0; | |
| my $str; | |
| while ($pos < $len) { | |
| print "compoundWord" if $i % 3 == 0; | |
| ($str, $pos) = stringStr($block, $pos, "'"); | |
| print " $str"; | |
| print ";\n" if $i % 3 == 2; | |
| $i++; | |
| } | |
| print "\n"; | |
| } | |
| sub printSpecwords($$$) { | |
| my $block = shift; | |
| my $pos = shift; | |
| my $flags = shift; | |
| my $i = 0, $result = 0;; | |
| print " "; | |
| while (substr($block, $pos, 1) eq $flags) { | |
| print " = " if $i > 0; | |
| $strlen = ord1($block, $pos + 1); | |
| $str = substr($block, $pos + 2, $strlen); | |
| $str =~ s/\'/\\\'/g; | |
| print "'$str'"; | |
| $pos += $strlen + 2; | |
| $result += $strlen + 2; | |
| $i++; | |
| } | |
| return $result; | |
| } | |
| sub processSPECWORD(*$) { | |
| my $FH = shift; | |
| my $len = shift; | |
| my $block; | |
| $len = read2($FH); | |
| read($FH, $block, $len); | |
| $block = decode($block); | |
| my $pos = 0; | |
| print "specialWords\n"; | |
| foreach $c ('O', ',', '.', 'A', 'B', 'X', 'N', 'P', 'I', 'T', | |
| 'M', 'R', 'Y') { | |
| $pos += printSpecwords($block, $pos, $c); | |
| print ",\n" unless $c eq 'Y'; | |
| print ";\n\n" if $c eq 'Y'; | |
| } | |
| } | |
| $actnum = 0; | |
| @actions = (); | |
| sub assignVerb($$$$) { | |
| my $verIoProp = shift; | |
| my $ioProp = shift; | |
| my $verDoProp = shift; | |
| my $doProp = shift; | |
| unless (defined $props[$doProp]) { | |
| my $actname = $actions[$actnum] || ("Action$actnum"); | |
| $actnum++; | |
| $props[$verDoProp] = "verDo$actname"; | |
| $propArgs[$verDoProp] = | |
| ($verIoProp != 0 ? ['actor', 'iobj'] : ['actor']);; | |
| $props[$doProp] = "do$actname"; | |
| $propArgs[$doProp] = $propArgs[$verDoProp]; | |
| if ($verIoProp != 0) { | |
| $props[$verIoProp] = "verIo$actname"; | |
| $propArgs[$verIoProp] = ['actor']; | |
| $props[$ioProp] = "io$actname"; | |
| $propArgs[$ioProp] = ['actor', 'dobj']; | |
| } | |
| } | |
| } | |
| @objblocks = (); | |
| @objtypes = (); | |
| sub preprocessOBJ(*$) { | |
| my $FH = shift; | |
| my $len = shift; | |
| my $block; | |
| read($FH, $block, $len); | |
| # Search for tpl2 properties; and while we're at it, save the | |
| # decoded object data | |
| my $pos = 0; | |
| while ($pos < $len) { | |
| my $type = ord1($block, $pos); | |
| my $n = ord2($block, $pos + 1); | |
| my $sizeuse = ord2($block, $pos + 5); | |
| my $objblock = decode(substr($block, $pos + 7, $sizeuse)); | |
| $objblocks[$n] = $objblock; | |
| $objtypes[$n] = $type; | |
| if ($type == 2) { | |
| my $flags = ord2($objblock, 2); | |
| my $nsc = ord2($objblock, 4); | |
| my $nprop = ord2($objblock, 6); | |
| my $pos = 14 + 2 * $nsc; | |
| $pos += 2 * $nprop if $flags & 2; | |
| for (my $i = 0; $i < $nprop; $i++) { | |
| my $type = ord1($objblock, $pos + 2); | |
| my $size = ord2($objblock, $pos + 3); | |
| if ($type == DAT_TPL2) { | |
| my $num = ord1($objblock, $pos + 6); | |
| my $intpos = $pos + 7; | |
| for (my $j = 0; $j < $num; $j++) { | |
| assignVerb(ord2($objblock, $intpos + 2), | |
| ord2($objblock, $intpos + 4), | |
| ord2($objblock, $intpos + 6), | |
| ord2($objblock, $intpos + 8)); | |
| $intpos += 16; | |
| } | |
| } | |
| $pos += 6 + $size; | |
| } | |
| } | |
| $pos += 7 + $sizeuse; | |
| } | |
| } | |
| sub preprocessREQ(*$) { | |
| my $FH = shift; | |
| my $len = shift; | |
| my @reqnames = ("Me", "takeVerb", "strObj", "numObj", "pardon", | |
| "againVerb", "init", "preparse", "parseError", | |
| "commandPrompt", "parseDisambig", "parseError2", | |
| "parseDefault", "parseAskobj", "preparseCmd", | |
| "parseAskobjActor", "parseErrorParam", "commandAfterRead", | |
| "initRestore", "parseUnknownVerb", "parseNounPhrase", | |
| "postAction", "endCommand", "preCommand", | |
| "parseAskobjIndirect"); | |
| my @reqargs = (undef, undef, undef, undef, undef, undef, undef, | |
| ['cmd'], # preparse | |
| ['num', 'str'], # parseError | |
| ['type'], # commandPrompt | |
| ['nameString', 'objList'], # parseDisambig | |
| ['verb', 'dobj', 'prep', 'iobj'], # parseError2 | |
| ['obj', 'prep'], # parseDefault | |
| ['verb'], # parseAskobj | |
| ['wordList'], # preparseCmd | |
| ['actor', 'verb'], # parseAskobjActor | |
| ['num', 'str'], # parseErrorParam | |
| ['type'], # commandAfterRead | |
| undef, | |
| ['actor', 'wordlist', 'typelist', 'errnum'], | |
| # parseUnknownVerb | |
| ['wordlist', 'typelist', 'currentIndex', | |
| 'complainOnNoMatch', 'isActorCheck'], # parseNounPhrase | |
| ['actor', 'verb', 'dobj', 'prep', 'iobj', 'status'], | |
| # postAction | |
| ['actor', 'verb', 'dobj_list', 'prep', 'iobj', 'status'], | |
| # endCommand | |
| ['actor', 'verb', 'dobj_list', 'prep', 'iobj'], | |
| # preCommand | |
| ['actor', 'verb', 'prep', 'objectList'] | |
| # parseAskobjIndirect | |
| ); | |
| foreach my $i (0..$#reqnames) { | |
| my $name = $reqnames[$i]; | |
| my $args = $reqargs[$i]; | |
| return if $len <= 0; | |
| my $obj = read2($FH); | |
| if ($obj != 65535) { | |
| $objs[$obj] = $name; | |
| $funcArgs[$obj] = $args if defined $args; | |
| } | |
| $len -= 2; | |
| } | |
| } | |
| @vocab = (); | |
| sub preprocessVOC(*$) { | |
| 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); | |
| unless ($classflg & 2) { # Skip if inherited | |
| $block = decode($block); | |
| my $str = substr($block, 0, $len1); | |
| $str .= " " . substr($block, $len1, $len2) if $len2 != 0; | |
| $str =~ s/\'/\\\'/g; | |
| # Construct references as needed | |
| $vocab[$objnum] = [] unless defined $vocab[$objnum]; | |
| $vocab[$objnum]->[$prpnum] = [] | |
| unless defined $vocab[$objnum]->[$prpnum]; | |
| push(@{$vocab[$objnum]->[$prpnum]}, "'$str'"); | |
| } | |
| $pos += 10 + $len1 + $len2; | |
| } | |
| } | |
| sub dumpObj($$) { | |
| my $block = shift; | |
| my $n = shift; | |
| my $flags = ord2($block, 2); | |
| my $nsc = ord2($block, 4); | |
| print "class " if $flags & 1; | |
| print objStr($n), ": "; | |
| for (my $i = 0; $i < $nsc; $i++) { | |
| print ", " if $i > 0; | |
| print objStr(ord2($block, 14 + 2 * $i)); | |
| } | |
| print "object" if $nsc == 0; | |
| print "\n"; | |
| # Dump vocabulary | |
| if (defined $vocab[$n]) { | |
| for (my $i = 0; $i <= $#{$vocab[$n]}; $i++) { | |
| next unless defined $vocab[$n]->[$i]; | |
| print indentStr(1), propStr($i), " = ", | |
| join(" ", @{$vocab[$n]->[$i]}), "\n"; | |
| } | |
| } | |
| my $nprop = ord2($block, 6); | |
| my $pos = 14 + 2 * $nsc; | |
| $pos += 2 * $nprop if $flags & 2; | |
| for (my $i = 0; $i < $nprop; $i++) { | |
| my $num = ord2($block, $pos); | |
| my $type = ord1($block, $pos + 2); | |
| my $size = ord2($block, $pos + 3); | |
| if ($type == DAT_CODE) { | |
| print indentStr(1), propStr($num); | |
| decompile(substr($block, $pos + 6, $size), $num); | |
| print "\n"; | |
| } | |
| elsif ($type == DAT_SYN) { | |
| my $synType = substr(propStr($num), 0, 2); | |
| if ($synType eq 'do' || $synType eq 'io') { | |
| print " ", $synType, "Synonym('", | |
| substr(propStr(ord2($block, $pos + 6)), 2), "') = '", | |
| substr(propStr($num), 2), "'\n"; | |
| } | |
| } | |
| elsif ($type == DAT_REDIR) { | |
| my $synType = substr(propStr($num), 0, 2); | |
| if ($synType eq 'do' || $synType eq 'io') { | |
| print " ", propStr($num), " -> ", | |
| objStr(ord2($block, $pos + 6)), "\n"; | |
| } | |
| } | |
| elsif ($type == DAT_TPL2) { | |
| my $num = ord1($block, $pos + 6); | |
| my $intpos = $pos + 7; | |
| for (my $j = 0; $j < $num; $j++) { | |
| if (ord2($block, $intpos + 2) != 0) { | |
| print " ioAction"; | |
| } | |
| else { | |
| print " doAction"; | |
| } | |
| print "(", objStr(ord2($block, $intpos)), ")" | |
| if ord2($block, $intpos) != 65535; | |
| print " = '"; | |
| # Strip off the 'do' from the doAction property name | |
| print substr(propStr(ord2($block, $intpos + 8)), 2), "'\n"; | |
| $intpos += 16; | |
| } | |
| } | |
| elsif ($type != DAT_DEMAND) { | |
| my ($str) = valueStr($block, $pos + 6, $type); | |
| print " ", propStr($num), " = $str\n"; | |
| } | |
| $pos += 6 + $size; | |
| } | |
| print ";\n"; | |
| } | |
| for (;;) { | |
| 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); | |
| } | |
| elsif ($#ARGV >= 0 && $ARGV[0] eq '-c') { | |
| $cmode = 1; | |
| shift(@ARGV); | |
| } | |
| else { | |
| last; | |
| } | |
| } | |
| if ($#ARGV != 0) { | |
| die "Usage: detads [-s symbols] [-c] 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); | |
| print "#pragma C+;\n\n" if $cmode; | |
| for (;;) { | |
| $namelen = read1(GAM); | |
| read(GAM, $name, $namelen); | |
| $nextofs = read4(GAM); | |
| $curofs = tell(GAM); | |
| $sectlen = $nextofs - $curofs; | |
| last if $name eq '$EOF'; | |
| processXSI(GAM, $sectlen) if $name eq 'XSI'; | |
| preprocessOBJ(GAM, $sectlen) if $name eq 'OBJ'; | |
| preprocessREQ(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'; | |
| preprocessVOC(GAM, $sectlen) if $name eq 'VOC'; | |
| seek(GAM, $nextofs, SEEK_SET); | |
| } | |
| close(GAM); | |
| for ($n = 0; $n <= $#objblocks; $n++) { | |
| next unless defined $objblocks[$n]; | |
| if ($objtypes[$n] == 1) { | |
| print objStr($n), ": function"; | |
| decompile($objblocks[$n], -$n); | |
| print "\n"; | |
| } | |
| elsif ($objtypes[$n] == 2) { | |
| dumpObj($objblocks[$n], $n); | |
| } | |
| else { | |
| die "Unsupported object type $objtypes[$n]"; | |
| } | |
| } | |
Xet Storage Details
- Size:
- 42.9 kB
- Xet hash:
- 66689cb70d5e9532ac607f9d27b3472447713d8abe20cf366fb51d7404342cf1
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.