ECL-TOOL
========

http://gbc.zorbus.net
joonas.hirvonen@pp.inet.fi

Updated 06-Aug-2020


ECL*.DAX files in Gold Box games are bytecode scripts for various game mechanics and plot. 
They for example contain the text, encounters, treasure, dialog options and so on.

For an introduction to ECL files, see this thread on the FRUA forums:
http://ua.reonis.com/index.php?topic=2519.0

ECL-Tool is a browser for these files and it can be also used to edit the commands and parameters.


Following games and versions are supported (basically the latest from GOG.com):

- Pool of Radiance                  1.3
- Curse of the Azure Bonds          1.3
- Secret of the Silver Blades       1.3
- Pools of Darkness                 1.1
- Champions of Krynn                1.2
- Death Knights of Krynn            1.1
- The Dark Queen of Krynn           1.1
- Gateway to the Savage Frontier    1.2
- Treasures of the Savage Frontier  1.0   
- Countdown to Doomsday             1.0
- Matrix Cubed                      1.0


THANKS
======

- This would not be possible without Simeon Pilgrim's decrypting / reprogramming of Curse of the Azure Bonds.
  https://github.com/simeonpilgrim/coab

- Stephen S. Lee for figuring out so much of ECL flags and addresses.

- Also thanks to marainein, Ishad Nha and others from the FRUA forums for analysing the ECL files already years ago.

- Bil Simser's Gold Box Explorer (https://github.com/bsimser/Gold-Box-Explorer). Can also view ECL file contents.


ABOUT THE ECL-FILES
===================

ECL files contain also data (string tables, number tables) and the command structure varies from game to game which
makes it very diffictult to parse the file perfectly. As some of the commands are only 1 byte of length, they may
easily be mistaken for a command when they in fact are data from a table. So there are most certainly parsing mistakes.

In some cases it's almost impossible to distinguish code and data so I've added a possibility to force certain
byte offsets to be handled as data. You can edit these offsets by clicking the DATA OFFSETS button.
Each game has its own text file listing the offsets, located under "Gold Box Companion\Games\xx. Game\ECL-Tool\" folders.

Because these forced data offsets, it's important to use the GOG.com versions of the games, otherwise the files
may be parsed wrong.

Stephen S. Lee's FAQs for Pool of Radiance / Curse of the Azure Bonds have lists of the commands, flags etc. info:
https://gamefaqs.gamespot.com/pc/564785-pool-of-radiance/faqs/73869
https://gamefaqs.gamespot.com/pc/564786-curse-of-the-azure-bonds/faqs/78365

Check out this if you want to know how the commands work internally (Simeon's reprogrammed Curse of the Azure Bonds):
https://github.com/simeonpilgrim/coab/blob/master/engine/ovr003.cs


ABOUT GOLD BOX DAX-FILES AND FILE NUMBERS
=========================================

In a folder of a typical Gold Box game you'll see files like MON1CHA.DAX, GEO4.DAX, ECL7.DAX and so on. 

DAX files are containers for many kind of data. 

When the Gold Box games were made, many people were still playing through floppy disks without installing the game
on a hard disk. The numbers in the DAX-files are disk numbers (or disk sides even?).

An ECL file can only use data (monsters, items etc.) from the same disk's other DAX files even if it's installed on
a hard disk. 

Some of the later Gold Box games just use 1 number so the ECL files can reference to any of the monsters or items.

You can use the MonsterMod and ItemMod tools to see what monster or item is put to what DAX file.

Alternatively, to see just a list of monsters or items and their id numbers on certain DAX file, 
click on the MONSTERS or ITEMS button.


ABOUT MAP EVENTS
================

Maps in Gold Box games are GEO*.DAX files which also have a possible event number per map cell. In most cases, 
this is a direct index for an ON GOTO command which jumps to a certain event code in the ECL file. 

Again, see this thread:
http://ua.reonis.com/index.php?topic=2519.0

ECL-Tool tries to find the ON GOTO command from the code and comment the corresponding code blocks with a short
comment. The ON GOTO command in most cases seem to be under the "search location" or "initial entry" code blocks 
but that may vary and in some cases there might be several sets of ON GOTOs. 
So don't be surprised if ECL-Tool marks the wrong ON GOTO for map events.

If you use Gold Box Companion, you can see the map event number if you right click on the map until events are shown.
Note that GBC does not show the event number if it's 0.


USAGE
=====

- From the Games-listbox, select the wanted game.
  - Set the game folder if not already set.
- From the DAX-files listbox, select a wanted DAX-file.
- In the bottom listbox on the left, the contents of the DAX-file are listed. Select wanted ECL script.
- You should now see the script listed.
- If there are "GEO xx" buttons visible under contents listbox, you can click them to change the map.


ECL VIEW
========

  file offset
   |        memory address                   parameter 1
   |         |      command byte              | parameter 2
   |         |       |   command name         |  |       parameter n
   |         |       |    |                   |  |        |
   $0016     $9914   09   SAVE                0  <$6E7B>  ...

- All offsets and addresses are in hexadecimal format ($xxxx).
- The command byte is also in hexadecimal but without the $-sign.
- Some command names may be wrong as the commands change from game to game.
  Unknown commands are listed with the name "???" but are still parsed.
- Pointer parameters are listed as <$xxxx> (parameter id = $01).
- Number parameters are in decimal (parameter id = $00 or $02).
- The "6-bit" strings are decoded (parameter id = $80).
- String pointers are listed as {$xxxx} (parameter id = $81).

- If the script uses the GETTABLE function, the address will me marked as "// table".

- Monsters, items and treasure is listed.

- Unknown or unneeded data is marked as "// possible data ...".

- Debug-tab has a debug log which shows information on how the script was parsed.
  At the bottom of the log you'll find code code block addresses that were not called by 
  GOTO/GOSUB/ON GOTO/ON GOSUB-commands and vice versa, addresses from GOTO/GOSUB-commands
  that are not parsed as code. There seem to be several code blocks that are not called
  by still appear in the games.

- Hex dump show the file in hex. Editing is not possible.

Apart from the two byte id in front of the ECL file, ECL-Tool lists every byte of the file. 
If some part of the file has unrecognized commands, it is still shown as a hex dump.


BROWSING
========

- CONTROL + RIGHT ARROW = follow a GOTO/GOSUB/ON GOTO/ON GOSUB address.

- CONTROL + LEFT ARROW = return to previous location if you followed a GOTO/GOSUB/...

- ALT + RIGHT ARROW on a row with a flag or memory address to jump to corresponding flag / address list index.

- F1 = jump to the line where there's an ON GOTO command which is used for the map events (if found).

- F2 / mouse doubleclick = edit command parameters.

- CONTROL + F2 = edit command.

- CONTROL + F = search the contents of the current tab. F3 to search again.

- ALT + F = global search. Search for a keyword through all scripts and shows results in the "Global search results" tab.
  Double click or press enter on a line starting with "###" to jump to the script.

- CONTROL + J = jump to offset/address. Only works on the command list tab.

- F11 / F12 to decrease/increase font size.

- CONTROL + DELETE = marks the first byte offset of the current command as data (saves to the offsets file). 

- F5 ... F9 = change active page (ECL / Debug / Hex dump / Global search / Keyboard shortcuts).

When you click on a command, the edit field on the bottom shows the byte code for the command. 
Changing the values here does nothing.


DESCRIPTIONS button opens a text file where you can give a title to a certain ECL script. 
Most of the scripts are already titled based on the ID numbers vs map GEO locations.


COMMENTS opens a text file where you can add comments to flags / memory addresses. The comments will be shown in the scripts.
You can use ":number" to limit the comment to certain script file number.

  Examples:

    $2C90
    // redraw first-person view

    $4A00:20
    // slums: 255 = got message from entering map

    $4A00:21
    // sokal keep: 255 = destroyed elf skeleton


GET FLAGS processes all scripts of the current game and builds a list of referenced flags and memory addresses to the
"Flags" and "Addresses" tabs. Double click or press enter on a line starting with "###" to jump to the script.


TO CLIPBOARD copies contents of the current tab to clipboard as text.


MAKING CHANGES
==============

ECL-Tool is not a compiler. If you make changes, they're written directly to the proper offsets in the file. The script
is not compiled in any way. You can insert code but only by replacing existing code.

ECL-Tool makes a backup of the ECL file before you make changes to it. The original can be easily restored just by clicking
the RESTORE button. Backups of the modified DAX-files are stored in the game's folder in the DAX_BACKUPS-folder
or GLB_BACKUPS in the case of The Dark Queen of Krynn.

Editing command parameters is safer and easier than editing the command itself.

At least Pool of Radiance seems to save the current ECL into the save game file. So if you edit a ECL script that
is currently active in a save game, the changes won't be seen. Instead you have to leave the map and come back for
the changed ECL to load. Or edit any ECL you're not currently in.


EDITING COMMAND PARAMETERS
==========================

To edit command's parameters, click the command line active and either doubleclick it, click the EDIT PARAMETERS button 
or press F2. 

A new window will open, listing the parameters. Add "$" before hexadecimal values. Strings can also be edited but can not
exceed the original length.

If the game crashes or does not function as planned, use the RESTORE button.


EDITING COMMANDS
================

To edit commands, click the command line active and click the EDIT COMMAND button or press CONTROL + F2.

A new window will open, listing the byte structure of the command. Command and parameter references are shown.

When editing the bytes do NOT add "$" before them. All values are considered to be in hexadecimal.

If your edited command is shorter than the original, pad the remaining bytes with some 1-byte command like
DELAY ($3A) or EXIT ($00).

If the game crashes or does not function as planned, use the RESTORE button.


USING DAXBUILDER TO EDIT ECL-FILES
==================================

You can also use DAXBuilder to extract the contents of a ECL*.DAX file and then use a hex editor to change the file directly and 
then rebuild the DAX with DAXBuilder.

You need to know the right offset in the file (the first value in the ECL list) and the command / parameter structure.
Again, pad the remaining bytes with the EXIT-command ($00).


CHANGING COMMAND STRUCTURE
==========================

Command name and parameter amounts can be changed by clicking CONFIGURE COMMANDS. This can also be used to get
a list of the available commands for the current game.

The fields are:
- parameter amount
- parameter number where the number of additional parameters is stored (0 if no extra parameters)
- command name
- checkbox, checked = ends command block (e.g. EXIT, RETURN, GOTO or ON GOTO)

After you click SAVE CHANGES, click on a wanted ECL on the main window's listbox to reparse it. You can keep the configure window
open.


SOME COMMAND EXAMPLES
=====================


Pool of Radiance, ECL2.DAX, 20:

$17EF     $B0ED   0B   LOAD MONSTER        8  2  8                              // ogre x 2
$17F6     $B0F4   0B   LOAD MONSTER        31  4  31                            // troll x 4

The 1. parameter indicates what monster to load and the 2. is the monster count. The 3. tells what combat icon to use which 
in most cases is the same as the monster id number.


-----


Pools of Darkness, ECL1.DAX, 16:

$1461     $945F   27   TREASURE            0  0  0  3  250  250  250  
                                                                                // 250 = cleric scroll 3 spells
                                                                                // 250 = cleric scroll 3 spells
                                                                                // 250 = cleric scroll 3 spells

The 4. parameter indicates that 3 item bytes follow. In this case, 3 similar cleric scroll items.


-----


Pool of Radiance, ECL3.DAX, 0:

$0FD8     $A8D6   12   PRINTCLEAR          "THE SHOP SPECIALIZES IN ARMS AND ARMOR. 'CAN I SHOW YOU OUR 
                                           WARES?'"
$100E     $A90C   02   GOSUB               <$AE5D>  
$1012     $A910   25   ON GOTO             <$6E79>  2  
                                           <$A91C>                              // 0
                                           <$AE6D>                              // 1

$101E     $A91C   1C   CLEARMONSTERS       
$101F     $A91D   27   TREASURE            0  0  0  0  0  0  0  53  
                                                                                // shield
                                                                                // plate mail
                                                                                // banded mail
                                                                                // splint mail
                                                                                .. more items

Example how a shop works. In the older games the TREASURE command has 8 parameters and last one is for 
item record id. One record id can contain several items. In this case the whole shop inventory.



========================================================================================================================



Script variable types and command list taken from Stephen S. Lee's Pool of Radiance FAQ:
https://gamefaqs.gamespot.com/pc/564785-pool-of-radiance/faqs/73869



Script Variable Types
---------------------

All script commands that take a variable must explicitly declare its type.

0x00 8-bit integer value
     This is usually unsigned, and underflows below 0 or overflows above 255
     appropriately.

     The Gold Box games have multiple ways of handling signed 8-bit integers:
     * THAC0 and Armor Class are stored as (60 - number).
     * Sometimes, 128 is treated as 0.
     * Sometimes, the standard C logic for signed 8-bit integers is used.

     These are often stored as a 16-bit, which is why the save files have a lot
     of flags interleaved with 0x00.

0x01 16-bit little-endian address pointing to 8-bit integer value

0x02 16-bit unsigned integer value, little-endian

0x80 String value
     * The first byte has the number of subsequent encoded bytes.
     * The subsequent bytes hold 6-bit values as follows:
       encoded bits: abcdefgh | ijklmnop | qrstuvwx
       decoded bits: abcdef | ghijkl | mnopqr | stuvwx
     * 0 is not a null terminator; it is ignored.
     * 1 to 26 represent the letters A-Z (ASCII code minus 64).
     * 32 to 63 use the standard ASCII encoding.
     A comparison of a string value will decompress and decode it.

0x81 String reference
     Unlike string values, this points to a standard C string (ASCII-encoded
     and null-terminated).


Script Commands
---------------

All ECL script commands are a single byte.  The names for the commands are the
same given by debug mode.

A <var> can be either a value or an address; for the latter, the value stored
there will be retrieved (dereferenced).

0x00 EXIT
     Stops execution and returns control to the player.

0x01 GOTO <address>
     Continues execution at <address>.

0x02 GOSUB <address>
     Continues execution at <address>, and pushes the current bytecode address
     to the stack.

0x03 COMPARE <var1> <var2>
     Compares <var1> and <var2> for a subsequent IF command.

0x04 ADD <var1> <var2> <address>
     The result <var1> + <var2> is placed in <address>.

0x05 SUBTRACT <var1> <var2> <address>
     The result of <var2> - <var1> is placed in <address>.  Yes, the
     subtrahend comes first.

0x06 DIVIDE <var1> <var2> <address>
     The result of <var1> / <var2> is placed in <address>.  This is integer
     division, with the dividend coming first.  I don't think the remainder is
     stored anywhere, unlike in later Gold Box games.

0x07 MULTIPLY <var1> <var2> <address>
     Stores <var1> * <var2> in <address>.

0x08 RANDOM <var> <address>
     Generates a random integer from 0 to <var>, inclusive, and stores it in
     <address>.  The random number generator isn't robust.

0x09 SAVE <var> <address>
     Stores <var> in <address>.

0x0A LOAD CHARACTER <var>
     * If <var> is less than 128:
       Loads the statistics for the indicated character, where 0 is first in
       the marching order, and (party size - 1) is the last.  If <var> is at
       least equal to party size, monsters from LOAD MONSTER are loaded.
       References to $6B00 to $6C1B are now references for the appropriate
       statistics.  As a reference and not a copy, SAVE or SAVE TABLE actually
       change the character or monster.
     * If <var> is 128 or more:
       Monster (<var> - 128) will be physically placed on the party's side in
       the next combat.  If $6B00 and $6C00 are subsequently set to 0, this
       instead removes the monster; Pool of Radiance doesn't have the DUMP
       command all other Gold Box games have.

0x0B LOAD MONSTER <monsterID> <count> <graphicID>
     Adds <count> copies of monster <monsterID> to the monster list.
     <graphicID>, taken from CPIC?.DAX, is often equal to <monsterID>.

0x0C SETUP MONSTER <monsterID> <distance> <graphicID>
     Starts a monster encounter with the primary monster <monsterID>,
     <distance> (which can be 0-2) squares away in the first-person view.

0x0D APPROACH
     The monsters from SETUP MONSTER close distance by 1 square.  If distance
     is 0, this does nothing.

0x0E PICTURE <pictureID>
     * If <pictureID> is 255:
       Ends the graphical display and resumes the normal first-person view.
     * If <pictureID> is defined in BODY?.DAX:
       Displays a composite head and body graphic, using $6DE1 as the head in
       HEAD?.DAX and <pictureID> as the body in BODY?.DAX.
     * Otherwise:
       Displays the specified picture from PIC?.DAX in the first-person view.
       If there is more than one such picture, this displays an animation
       cycling through all such pictures.

0x0F INPUT NUMBER <maxDigits> <address>
     Asks the player to input a number with <maxDigits> maximum digits, and
     places it in <address>.

0x10 INPUT STRING <maxLength> <address>
     Asks the player to input a string with <maxLength> maximum characters, and
     places it at <address>.

0x11 PRINT <string>
     Prints <string> to the text box.

0x12 PRINTCLEAR <string>
     As above, but clears the text box first.

0x13 RETURN
     Returns from a GOSUB, with the next command executed the one after the
     most recent GOSUB pushed to the stack.

0x14 COMPARE AND <val1> <val2> <val3> <val4>
     Compares <val1> to <val2> and <val3> to <val4> for a subsequent IF
     command.

0x15 VERTICAL MENU <address> <message> <count> <stringVarargs>
     Displays <message> as a prompt in the text box, with the player able to
     select from the <count> options listed as <stringVarargs>.  The chosen
     option is placed in <address>, with 0 the first option and (<count> - 1)
     the last.

0x16 IF =
0x17 IF <>
0x18 IF <
0x19 IF >
0x1A IF <=
0x1B IF >=
     All six IF commands will skip the next command unless the most recent:
     * COMPARE has a true comparisons
     * COMPARE AND has both comparisons true
     * GETTABLE has a true comparison to 0
     * AND has a true comparison to 0
     * FIND ITEM found a character carrying the appropriate item
     This means bit flags are checked with AND followed by an IF <>.

0x1C CLEARMONSTERS
     Removes all monsters from the LOAD MONSTER list.

0x1D PARTYSTRENGTH <address>
     Calculates Party Strength using the calculation given in the Party
     Strength section, and places it in <address>.

0x1E CHECKPARTY <attributeAddress> <effectID> <unknown> <address1>
                <unknown> <address2>
     * If attributeAddress is not 0 and effectID is 0:
       Checks all characters for the values in <attributeAddress> ($6B00 to
       $6C1B).  Then <address1> will hold the highest value and <address2> will
       hold the lowest.
     * If attributeAddress is 0 and effectID is not 0:
       Checks all characters for the given <effectID>.  Then <address2> will
       hold 0 if no characters have the effect, or 1 if at least one character
       does.

0x1F undefined

0x20 NEWECL <script>
     Loads the indicated ECL script.

0x21 LOAD FILES <mapID> <unknown> <unknown>

0x22 PARTY SURPRISE <address1> <address2>
     Initializes <address1> and <address2> for a subsequent SURPRISE command.
     <address1> is the roll on a d6 for the monsters to surprise the party.
     <address2> is the roll on a d6 for the party to surprise the monsters.
     These are both initialized to 2, the standard values; rolls must be equal
     to or less than this to succeed.  If different values are wanted for the
     party, they can be modified before the SURPRISE command.

0x23 SURPRISE <address1> <address2> <var1> <var2>
     Rolls for surprise, with the addresses expected to be initialized by the
     PARTY SURPRISE command.
     <var1> is the monsters' modifier to surprise added to <address1>.
     <var2> is the monsters' modifier to be surprised subtracted from
       <address2>.

     The end result is placed in $6DCB:
       0 Neither side surprised
       1 Monsters surprise party
       2 Party surprises monsters
       3 Monsters and party surprise each other

0x24 COMBAT
     * Usually:
       Starts combat with the list of monsters from LOAD MONSTER.

       Combat can be modified by the following:
       * All monsters have Morale of $6DC6.
       * If $6DCB is 1 or 2, the surprising side may get surprise actions.
       * If $6E70 is not 0, it acts as a signed 8-bit bonus to all monsters'
         THAC0.
       * If $6E71 is not 0, it acts as a signed 8-bit bonus to all
         characters' THAC0 and damage.
       * If $6E72 is not 0, it acts as a signed 8-bit bonus to characters'
         movement if they are humans, half-elves, or elves.
       * The TREASURE command will load additional items that will show up as
         loot if the party wins.
       * If $6DE3 is 1, the monsters will not drop the items they are carrying.

       After combat:
       * $6DC7 is set to 0 if the party won, or 129 if it ran away.
       * $6DC8 is set to the number of monsters the party killed.
       * $6E33 is set to 1 if the party attacked allies during the fight.
       * $6DCB, $6DE3, $6E70, $6E71, and $6E72 are all reset to 0.

     * If $6DE2 is 1:
       The party instead enters a temple.  $6DE2 is set back to 0 when done.

     * If $6E6C is 1:
       The party instead enters a shop.  $6E6C is set back to 0 when done.
       Available items for sale are what was loaded with the TREASURE command.
       Purchase prices are multiplied by ($6E6D / 16).  Prices are unsigned
       16-bit integers and will overflow if they exceed 65535.

0x25 ON GOTO <var> <count> <addressVarargs>
     * If <var> is less than <count>, this acts as a GOTO command with the
       corresponding address from <addressVarargs>.
     * If <var> is equal to or greater than <count>, execution falls through to
       the next statement.

0x26 ON GOSUB <var> <count> <addressVarargs>
     Same as ON GOTO, except a GOSUB is executed.

0x27 TREASURE <copper> <silver> <electrum> <gold> <platinum>
              <gems> <jewelry> <treasureID>
     This adds all the indicated treasure for the next COMBAT.
     If treasure ID is:
       * 0 to 127: all the treasure from that list is added.
       * 128 to 254: <treasureID> minus 128 random magical items are added.
       * 255: no items are added.

0x28 ROB <isWholeParty> <percentMoney> <itemChance>
     Takes money and items away.
     * If <isWholeParty> is 0, affects only the chosen character;
     * If <isWholeParty> is 1, affects all characters.

     All affected characters always lose <percentMoney>% money.

     For <itemChance>, consider every item:
     * If item is less than 2.5 pounds, <itemChance>% chance of loss
     * If item is 2.5 to 25.5 pounds, (<itemChance> - 50)% chance of loss
     * If item is more than 25.5 pounds, (<itemChance> - 90)% chance of loss
     You can't lose a medium item if <itemChance> is below 50, and you can't
     lose a heavy item if <itemChance> is below 90.

0x29 ENCOUNTER MENU <monsterID> <distance> <pictureID> <address>
                    <combat> <wait> <flee> <advance> <parley>
                    <message0> <message1> <message2>
                    <movement1> <movement2>
     Starts an encounter with the monster with <monsterID> and <pictureID>,
     starting <distance> squares away.  The messages displayed in the text box
     are for the given distance from the party; if no message is defined, the
     message for lower distance is used.

     <combat>, <wait>, <flee>, <advance>, <parley> all define what happens if
     you choose that action:
       0 combat
       1 do whatever you chose
       2 monsters flee (unless you attacked them)
       3 monsters advance
       4 parley
     Your party does not "F"lee unless you choose to do so; success depends on
     the movement of your slowest party member compared to <movement1> and
     <movement2>.  (Those are usually identical; I'm not sure what the
     difference is.)

     If the party successfully advanced, the menu is repeated.

     The resolution is placed in <address>:
       0 monsters flee
       1 combat begins
       2 party flees
       3 parley

0x2A GETTABLE <address1> <var> <address2>
     Retrieves the value from address <address1 + var> and places it in
     <address2>.  Equivalent to retrieving a value from an array.

0x2B HORIZONTAL MENU <address> <count> <stringVarargs>
     Similar to VERTICAL MENU, but the message to the player must be printed
     beforehand, and the string choices are displayed on the bottom menu bar.
     The player can additionally select an option with the first letter of the
     string.

     Scripted "Press <enter>/<return> to continue" prompts use this in earlier
     Gold Box games.  "P" will work for these prompts; an address must still be
     supplied, which can cause bugs.

0x2C PARLAY [sic] <haughty> <sly> <nice> <meek> <abusive> <address>
     Gives the player the option to choose a parley attitude; the value
     defined for the chosen option is placed in <address>.

0x2D CALL <address>
     Executes and returns from the machine code (not bytecode) at the indicated
     address.

0x2E DAMAGE <var1> <dice> <sides> <bonus> <var2>
     This does damage equal to <dice>d<sides> + <bonus>.
     If <var1> is from 1 to 127:
       This executes <var1> physical attacks on random characters, with a
       THAC0 of (60 - <var2>).
     If <var1> is 128 or higher:
       Who gets damaged depends on <var1> bit 64:
       * If set, the entire party takes damage.
       * If not set, but bit 1 is, a random character takes damage.
       * Otherwise, the currently chosen character takes damage.

       The saving throw depends on <var1> bit 32:
       * If set, the damage always hits.
       * If not set, damage can be avoided with a saving throw defined in the
         lower 7 bits of <var2>:
           0 saving throw vs. paralyzation, poison or death
           1 saving throw vs. petrification or polymorph
           2 saving throw vs. rod, staff or wand
           3 saving throw vs. breath weapon
           4 saving throw vs. spell

0x2F AND <var1> <var2> <address>
     The result of a bitwise AND on <var1> and <var2> is placed in <address>.
     This is used to read bit flags.

0x30 OR <var1> <var2> <address>
     The result of a bitwise OR on <var1> and <var2> is placed in <address>.
     This is used to set bit flags, though ADD is also used.

0x31 SPRITE OFF

0x32 FIND ITEM <itemID>
     This checks all party members for an item with the given <itemID> for a
     subsequent IF.

0x33 PRINT RETURN
     Prints a single blank line to the text box.

0x34 ECL CLOCK <var> <timeunit>
     Not used in Pool of Radiance.

0x35 SAVE TABLE <var1> <address> <var2>
     Reverse of GETTABLE; places <var1> in <address + var2>.  Equivalent to
     storing a value in an array.

0x36 ADD NPC <monsterID> <morale>
     Adds the given monster to your party, with the indicated <morale>.

0x37 LOAD PIECES <walldefID> <unknown> <unknown>

0x38 PROGRAM <program>
     Runs a special routine depending on <program>:
       0 training hall (using the class bitmask in $6DA8)
       8 win game
       9 camp

0x39 WHO <message>
     Shows <message> and has the player select a party member, who also gets
     LOAD CHARACTER executed.

0x3A DELAY

0x3B SPELL <spellID> <address1> <address2>
     Searches for a character with the given spell:
     * if no such character exists: <address1> will hold 255;
     * if such a character does exist:
       <address1> will hold the index of the spell slot;
       <address2> will hold the index of the character;
       also, a LOAD CHARACTER <address2> command is executed.

0x3C PROTECTION <address>
     Prints a runic phrase from <address>.

0x3D CLEAR BOX

