For more information or to contact the author, visit http://www.exmsft.com/~benrg/if-decompilers/.
The command-line syntax is:
reform storyfile.z5 [-s symfile.reform] [-t] [-x] > output-file
The -s option specifies a "symbol file", the format of which is described below. If you don't specify -s, then Reform will look for a symbol file named "x.reform", where "x.zn" is the name of the story file you mentioned on the command line.
If you include -t, then Reform will output a symbol file template. Otherwise, it will output decompiled quasi-Inform source code. There are no options to suppress certain parts of the decompilation, but it wouldn't be hard to add them.
The generated symbol file template contains some cross-reference information to make it easier to choose names for things: each attribute comes with a list of the objects which have that attribute when the game begins, each action with a list of all the grammar lines which produce it, and routines which are mentioned in object properties are preceded by a comment listing those properties.
It's perfectly okay to use -s and -t together. It can be useful to create the symbol file in stages: for example, first giving names to some attributes, then generating a new template file in which those attributes will appear in the grammar lines next to actions.
Reform has various internal heuristics to guess the names and types of various things, so you'll often get semi-readable output even without a symbol file.
If you include -x on the command line, then Reform will put the hex addresses of all constants in the generated source code, which is useful for hacking game files to eliminate annoying inventory limits, random events, and whatnot.
The symbol file format is a ripoff of the format used in Allen Garvin's "ztool". They are not compatible, but the Reform distribution includes converted versions of nearly all of his files.
A symbol file is a list of directives, each of which must be on a separate line. The line begins with the name of the directive. Most directives take arguments, which are separated by spaces from the name and each other. If you want to include spaces in an argument, you can surround it by quotes. (This is probably never useful.)
Comments begin with ! and continue to the end of the line, as in Inform.
MD5 C44B2CD392B505C123FADE5DFE1E1BE0
Gives the MD5 hash of the story file to which this symbol file is supposed to refer. This must be exactly 32 hex digits. Reform will abort with an error if the MD5 of the story file doesn't match this.
In the case of a blorb file, this is the hash of the embedded story file, not of the whole blorb. If the story file gives its own length in the header, only that much of the file is hashed.
There can be more than one MD5 directive; Reform will accept any story file which matches any of them.
CodeArea 0x1234 0x5678 StringArea 0x5678 0x9ABC
Tells Reform that the specified byte address range contains z-code which it should try to disassemble, or strings which it should try to decode. The low address is inclusive, and the high address is exclusive. (This is also the way TXD does it.)
If there are no CodeArea or StringArea directives, Reform will try to find the code and data itself. It's not as good at this as TXD is.
FalseEnd 0x1234 1
Reform assumes that a z-code routine ends after the last reachable instruction, but sometimes this doesn't work, because routines have unreachable code. FalseEnd takes a routine address and a "number of false ends" -- i.e. the number of times it looks like the routine is ending, but actually isn't.
Usually, Reform will tell you when you need this. For example, if you see the following in the output:
! Code appears to end here. If the strings below look like ! garbage, try adding this directive to the symbol file: ! FalseEnd 0x2d8cc 1
and the strings do look like garbage, add the directive and decompile again. You may have to repeat this process several times. TXD has better heuristics and doesn't need this babying; maybe someday Reform won't either.
Object 117 babel_fish_dispenser Attribute 30 drinkable Action 81 Hitchhike
Gives a name to an object, attribute, or action.
Global 80 Have_Headache:bool
Gives a name and type to a global variable. The type is optional. The legal types are:
? | an unknown type (same as leaving off the type) |
object | an object number |
property | a property number |
attribute | an attribute number |
attribute0 | an attribute number or 0 (otherwise 0 prints as the name of attribute #0). |
routine | a routine packed address |
string | a string packed address |
char | a ZSCII character |
unicode | a Unicode character |
int | a number (currently always printed as 0-65535) |
bool | a boolean value ("true" or "false") |
dictword | a dictionary word |
action | an action |
adjective | an Infocom adjective number |
verbnum | an Infocom verb number |
thing | an Inform "thing" (an object, a routine, or a string, depending on address) |
^arraytype | the address of an array with the given type |
For information on array types, see the Array directive below.
Enum typename FLAG1=128 FLAG2=64 FOO=5 BAR=6
This defines a new enumerated type named typename, which you can use just like the other value types listed above. A value of an enumerated type prints as some combination of the constants you specify. For example, the number 128 will print as FLAG1, 197 will print as (FLAG1|FLAG2|FOO), and so on.
The constants will show up in the generated source code.
You can use the new type anywhere in the symbol file, even before the Enum directive.
LastGlobal 220
This specifies the highest global variable number used in the program. To save memory, Zilch didn't pad the global variable table with zeroes to give it 240 entries, so without this directive you'll end up with spurious nonzero "globals" which are never used.
Array 0x1234 foo:array(20*(int,routine))
Gives a name and type to an array living at the given address. For each Array directive, Reform will dump the (initial) contents of the array in the story file as an Inform Array.
Array types look like this:
array(int,char) | An array of two words, of types int and char |
array(20*int) | An array of 20 words of type int |
array(20*(int,char)) | An array of 40 words, with types alternating between int and char |
array(*(int,char)) | An array of unknown length, with types alternating between int and char |
array(int,n*(int,char)) | An integer, followed by that many int and char values. (If the initial word is 10, there are a total of 21 words in the array.) |
table(int,char) | An alternate syntax for array(int,n*(int,char)) |
array(20*(int,~char)) | An array of 20 three-byte records (60 bytes total), each record being an int word followed by a char byte |
array(~int,~int,n*(dictword,~int,~int)) | The parse array for the @read Z-machine instruction |
Any of the types from the Global section are legal as element types here, including array pointers: if you write array(10*^table(string)) it means an array of ten elements, each the address of a table of strings. If there is no separate Array directive for those addresses, Reform will dump their contents "inline", like Allen Garvin's ztool.
The following aliases are also available (these are particularly convenient for Property directives):
objbytes | array(*~object) |
objwords | array(*object) |
things | array(*thing) |
routines | array(*routine) |
strings | array(*string) |
dictwords | array(*dictword) |
adjbytes | array(*~adjective) |
pseudo | array(*(dictword,routine)) (found in Infocom story files) |
GlobalArray 0x12 foo:array(20*(int,routine))
Infocom story files tend to have global variables which point to static tables and are never reassigned during play. GlobalArray makes these look more like Inform arrays. For example, if global 0x12 contains 0x3456 at startup, then the GlobalArray directive above is approximately equivalent to
Global 0x12 foo:^array(20*(int,routine)) Array 0x3456 foo:array(20*(int,routine))
except that the value of the global isn't printed separately (it would be "Global foo = foo;").
Property 59 aft_to:exit
Gives a name and type to an object property. The legal types are all those listed under Global, plus all those listed under Array, plus these two additional types:
exit | a ZIL "exit" property |
bzexit | a variant "exit" format that I've only seen in Beyond Zork |
"Exit" properties are described in "Learning ZIL", and are one of:
Reform displays the first three of these as just a room, routine, or string (a la Inform), and the last two roughly in the ZIL format.
Routine 0x8e58 PrintContents obj:object itm nextitem first last
Gives a name and type to a routine and its parameters/locals. The allowed types are those listed in the Global directive.
PrintRoutine 0x1234 the
This is supposed to specify that a routine should be disassembled as "print (the)foo" instead of "the(foo)", but I can't remember if it works properly.
Word laser-as laser-assisted
Gives the full version of a truncated dictionary word.
Zilch Inform5 Inform6Specifies the compiler which produced the story file. Usually Reform can auto-guess this.