For more information or to contact the author, visit

Running Reform

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.

Symbol file reference

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)
objectan object number
propertya property number
attributean attribute number
attribute0   an attribute number or 0 (otherwise 0 prints as the name of attribute #0).
routinea routine packed address
stringa string packed address
chara ZSCII character
unicodea Unicode character
inta number (currently always printed as 0-65535)
boola boolean value ("true" or "false")
dictworda dictionary word
actionan action
adjectivean Infocom adjective number
verbnuman Infocom verb number
thingan Inform "thing" (an object, a routine, or a string, depending on address)
^arraytypethe 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):

dictwords   array(*dictword)
pseudoarray(*(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:

exita 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.

Specifies the compiler which produced the story file. Usually Reform can auto-guess this.