Descript is an ACS decompiler for Hexen. It is a DOS command-line program, and given three parameters (the wad file, output file, and map number), will create the ACS script.
- Filename: descrp14.zip
Descript is a freeware utility.
DESCRIPT is a fully fledged and user-friendly HEXEN script decompiler, that generates highly readable source code from any compiled script. This may take the form of a self contained object file, or the BEHAVIOR resource in a main or patch WAD file.
Being able to decompile the existing scripts inside HEXEN is a real boon for anyone wishing to design their own levels for the game, as it allows them to learn the ACS language and experiment freely, without having to do a lot of work up front. Since the source files that DESCRIPT produces can be readily recompiled, it's very easy to try out modifications to the existing levels, and find out what works, and what doesn't.
To use DESCRIPT to decompile an object file, or the first BEHAVIOR resource in a WAD, type:
DESCRIPT <objectfile> <sourcefile>
For example, to decompile the first script in HEXEN.WAD, into a source file called HEXLEV1.ACS, type:
DESCRIPT HEXEN.WAD HEXLEV1.ACS
If you have a multi-level WAD, such as HEXEN.WAD, you can extract any script into a source file, by directly specifying the map number on the end of the line:
DESCRIPT <wadfile> <sourcefile> <map>
For example, to decompile level MAP08 in HEXEN.WAD, into file HEXLEV8.ACS, type:
DESCRIPT HEXEN.WAD HEXLEV8.ACS 8
You can find out what levels are present in a WAD file by typing:
DESCRIPT <wadfile> /l
DESCRIPT contains on-line help, accessed by just typing its name with no parameters, or by specifying the /? or ? switch. Additional debugging info can be printed out by adding the /v switch, which includes a list of all the strings and scripts in the file.
The source files generated by DESCRIPT are designed to be highly readable: not only are all string parameters decoded, but also any variables passed as string parameters are automatically shown as type "str" (unless they are script arguments), and any strings they are assigned to are also shown. Most of the enumerated types (in header file defs.acs) are also fully decoded, again with variable assignments correctly shown. Even the return values from functions such as gametype() and gameskill() are decoded, when used directly in conditional/switch statements.
The net result is that the generated script files are probably fairly close in appearence to those that Raven used when making the game. However, like all compiled languages, some information in the source is not translated into the object file, with the result that it is permanently lost. For ACS, this applies to:
1) Variable names. DESCRIPT now names variables according to their usage, so for example, a variable used as a "thing ID" will be called "tid". This makes the code even more readable, especially where parameters are being passed into scripts. The full list of decoded types are:
tid - thing ID on map tag - sector tag on map line - line identification poly - polyobject identification thing - thing type (assignments will be decoded) str - all strings (assignments will be decoded) spare - the variable is not used anywhere var - default type, if not decoded
Whenever DESCRIPT names a variable, it prefixes it with "world" for world-scope variables, "map" for map scope variables, "a" for arguments and nothing for local variables. In all cases, it adds a suffix number, which is the number of the variable in the object code. Note that all map and script scope variables are listed in ascending numerical order, even if not all are used. For example, if only the 4th map variable is actually used, the first three will be displayed as "mapspare0" thru "mapspare2". If this was not done, the recompiled script would differ from the original.
2) Variables declared but not used. Unless a script or map variable is numerically below one that is used, or forms a script parameter, it will not show up on the decompiled source. This is because only variable usage is compiled; variable declarations are not.
3) Strings assigned to a variable but not used. This case is slightly different as the string WILL be compiled into the object code, but DESCRIPT will not reproduce it because the variable is never passed to anything that it recognises as taking a string parameter. This is because strings are stored as HANDLES, with a numerical parameter listing an index of strings stored at the end of the script. The only difference between a string and a number is where it is used.
3) User defined macros. If the user originally used his own macros (using the #define directive), these will not be reproduced, as they are not present in the object code.
4) Comments and spacing. These are not compiled, so do not show up.
DESCRIPT now has the capability to display extended definitions for even greater readability, but because these are non-standard, you also need an extended definitions file. This can be generated with the /g option, and has a fixed name, DSEDEFS.ACS (DeScript Extended DEFinitionS). Then, when decompiling scripts, use the /u option to use extended definitions. The extended definitions currently provided are:
Line Specials - The setlinespecial() function takes in a special number as its second parameter, which is the same number as the compiled code uses when calling one of these functions directly. Since DESCRIPT already decompiles these names, it is a simple matter to decode them in this context, too. All the names are prefixed by "LS_", to distinguish them from the actual special function names. Note that there are a few, usch as UsePuzzleItem, which are not listed in SPECIALS.ACS, as they are not suitable for being called directly from within scripts (or it is pointless to do so).
Sector Sounds - The special Sector_ChangeSound takes in a parameter defining the sound to be selected, as listed in the technical specs. DESCRIPT will decode this, prefixing it with type "SS_".
Keys and Locks - Two specials, Door_LockedRaise and ACS_LockedExecute take in a parameter specifying the key number that the player must hold for the action to take place. These are listed in the technical specs, and prefixed by type "KEY_".
Puzzle Items - The first parameter of UsePuzzleItem() is decoded to a sensible name, as listed in the HEXEN technical specs. However, since this special is unlikely to crop up, because it is not in SPECIALS.ACS, you may think that it is pointless decoding this parameter. Actually, this is quite useful, as DESCRIPT also decodes the parms when passed to setlinespecial().
If DESCRIPT finds a setlinespecial() function, with a numerical parameter for the special type, it decodes all subsequent numerical parameters as if they were being passed to the special function directly. This extends readability still further, especially as this is the only place that some specials, such as UsePuzzleItem(), are used.
Note that you only need to generate the DSEDEFS.ACS file once, as it will be available to all subsequent decompilations. You can generate it at the same time as doing a decompilation, or on its own, and you don't have to specify the /u option at the same time. The generation of this file takes place before DESCRIPT opens any other files, so it will not affect operation in any other way (though it will slow down execution slightly). Note that you should check that the version of DESCRIPT you are using matches that displayed in the DSEDEFS.ACS file, and re-generate it if it does not.
If you want a disassembly of the object module, rather than a decompilation, use the /a switch. Thus, to get an assembly listing of MAP30 in HEXEN.WAD, into file HEXLEV30.DIS, type:
DESCRIPT HEXEN.WAD HEXLEV30.DIS 30 /a
If you don't like the nesting being shown with the brackets at the inner nesting level, you can use the /n option, where the brackets will be shown at the outer nesting level.
You can prevent DESCRIPT from displaying context sensitive variable names with the /c option, and from decoding variable assignments to enumerated types with the /e option. If you don't want assignments to text strings being displayed, use the /s option. All these are useful if you find that false information is being displayed due to the same variables being used for different purposes in the script.
1) DESCRIPT is only guaranteed to work on code produced using the ACC compiler by Ben Gokey of Raven Software. It relies on the compiler following a fixed set of rules, both in the compiled code and the layout of the ACS resource (it expects all the scripts to be sequentially arranged, followed by the strings, and finally the internal directory). Many changes which would still result in correctly executing code will be fatal for DESCRIPT, as its rules would be violated. This is not a design deficiency in DESCRIPT; several high level statements can only be reproduced because the low level opcodes appear in an exact sequence.
2) DESCRIPT relies on good programming practices having being used in the original scripts. For example, if a variable is used as both a string and an integer, this may result in DESCRIPT displaying false strings, and could result in the code recompiling differently (if the string indexing changes). The enumerated types and context-sensitive variable naming could also cause a problem if the same variable is used for different purposes in different parts of the script, though the code will always recompile the same. If you find this a problem, you can disable variable assignments to enumerated types with the /e option, and context-sensitive variable names with the /c option. If strings assignments are being falsely displayed, you can turn them off with the /s option, though beware that this will seriously affect how the code will recompile. Note that use of the /s or /e switches will also disable the relevant context-sensitive variable names, as they stop variables from being assigned the appropriate types.
3) There appears to be a minor "feature" in the ACC compiler. If you create two nested "do" loops without brackets, and put a "continue" statement in the inner statement, the compiler will actually make this jump to the outer loop. This doesn't result in corrupted source (in fact DESCRIPT fixes it), but the decompiled source will compile differently to the original.
4) In a few rare cases, two different source code fragments will compile to identical object code, with the result that DESCRIPT will choose one of the source representations. In all the cases I've found so far, the two source code fragments mean the same thing. A classic example is if you bury an if() statement inside a "do" loop, and put a "continue" statement inside the if(), with nothing following it. DESCRIPT will generate an "else" statement instead of the "continue", but they mean the same thing in this context. Comparing the two object modules show the code is identical.
5) Like all software (except the truly trivial), DESCRIPT cannot be guaranteed to be bug free, but it has been rigorously tested. I've thrown all sorts of wierd and wonderful constructs at it, many of them meaningless such as switch() statements with expressions or "default" statements before the first "case". I've also tried many combinations of nested loops and conditionals, and different combinations of pre- and post- increment/decrement operators (which are especially difficult to decompile). For what it's worth, I've also decompiled all the original HEXEN scripts, and checked that they recompiled into object modules which were identical to the originals.
6) If you attempt to decompile a level that does not exist in the WAD, DESCRIPT will report an error. However, if you specify the /o option, you can specify an "order number" which is the ORDER of the level in the WAD, and DESCRIPT will always decompile a level, if it can find one. If the order number is greater than the number of levels in the file, the last level will be decompiled. Also note that when just listing levels, DESCRIPT does NOT look for BEHAVIOR resources - it just looks for level names. Thus you can list out levels in DOOM files without a problem, but you will get an error as soon as you attempt to decompile a script.
7) Descript can simply dump its output to the console (screen), though this cannot be piped or redirected. For object files, or the first level in a WAD, simply miss out the output (source) file name. To decompile a higher WAD level, simply call the output file "con", and you will get the same result.
8) The ACC compiler appears to get upset at the unary minus operator cropping up in expressions containing a multiplication or division. Thus the expression "var0 = var1 * -5" would not compile, but "var0 = var1 * (-5)" would. Thus DESCRIPT will put brackets around unary operators in this situation. The modulus (%) operator is also handled in the same manner.
9) ACC does not like variable type "str" as a script argument (probably because this is a stupid idea, anyway). Therefore, DESCRIPT will always display argumants as type "int", but if an argument is used as a string, its NAME will be "astr".