/* txd.c V1.2
 *
 * Z code disassembler.
 *
 * No switches, no nothing, just give it a story file name, and you're off.
 * Requires txio.c and tx.h. To reduce some CPU cost define NO_LOW_SCAN, but
 * some low address routines may be skipped.
 *
 * Currently only works for type 3, 4 and 5 games.
 *
 * Usage: txd story-file-name
 *
 * Mark Howell 25 August 1992 howell_ma@movies.enet.dec.com
 *
 * History:
 *    Merge separate disassemblers for each type into one program
 *    Fix logic error in low routine scan
 */

#include "tx.h"

#define MAX_PCS 100

#ifdef __STDC__
static int decode_code (void);
static int decode_inputs (void);
static int decode_opcode (void);
static int decode_operands (const char *, int, int, int);
static int decode_outputs (void);
static int decode_routine (void);
static void decode_operand (int, int);
static void decode_program (void);
static void decode_strings (unsigned long);
static void scan_strings (unsigned long);
static int lookup_string (unsigned long);
static void scan_words (void);
static unsigned long lookup_word (unsigned long);
#else
static int decode_code ();
static int decode_inputs ();
static int decode_opcode ();
static int decode_operands ();
static int decode_outputs ();
static int decode_routine ();
static void decode_operand ();
static void decode_program ();
static void decode_strings ();
static void scan_strings ();
static int lookup_string ();
static void scan_words ();
static unsigned long lookup_word ();
#endif

static unsigned long pctable[MAX_PCS];
static int pcindex = 0;

static unsigned long start_data_pc, end_data_pc;

static decode_t decode;
static opcode_t opcode;

#ifdef __STDC__
int main (int argc, char *argv [])
#else
int main (argc, argv)
int argc;
char *argv [];
#endif
{

    if (argc != 2) {
        fprintf (stderr, "Usage: %s data-file\n", argv [0]);
        exit (EXIT_FAILURE);
    }

    open_story (argv [1]);

    configure (V3, V5);

    load_cache ();

    decode.pc = (unsigned long) h_data_size;
    decode.initial_pc = (unsigned long) h_start_pc;

    printf ("Resident data ends at %lx, program starts at %lx, file ends at %lx\n",
            (unsigned long) h_data_size, (unsigned long) h_start_pc, (unsigned long) h_file_size * story_scaler);

    printf ("\nStarting analysis pass at address %lx\n", decode.pc);

    decode.first_pass = 1;
    decode_program ();

    scan_strings (decode.pc);
    scan_words ();

    printf ("\nEnd of analysis pass, low address = %lx, high address = %lx\n",
            decode.low_address, decode.high_address);
    if (start_data_pc)
        printf ("\nData PC range = %lx to %lx\n", start_data_pc, end_data_pc);

    decode.first_pass = 0;
    decode_program ();

    decode_strings (decode.pc);

    close_story ();

    exit (EXIT_SUCCESS);

    return (0);

}/* main */

/* decode_program - Decode Z code in two passes */

#ifdef __STDC__
static void decode_program (void)
#else
static void decode_program ()
#endif
{
    unsigned long pc, low_pc, high_pc, prev_low_pc, prev_high_pc;
    int flag, vars;

    if (decode.first_pass) {
#if !defined(NO_LOW_SCAN)
        /* Scan for low routines */
        decode.pc = (decode.pc + (story_scaler - 1)) & ~(story_scaler - 1);
        for (pc = decode.pc, flag = 0; pc < (decode.initial_pc - 1) && !flag; pc += story_scaler) {
            decode.pc = pc;
            while (decode.pc < (decode.initial_pc - 1) && (decode_routine () == END_OF_ROUTINE) && pcindex == 0)
                decode.pc = (decode.pc + (story_scaler - 1)) & ~(story_scaler - 1);
            if (decode.pc == (decode.initial_pc - 1)) {
                decode.pc = pc;
                flag = 1;
            }
            pcindex = 0;
        }
        if (!flag)
#endif /* !defined(NO_LOW_SCAN) */
            decode.pc = decode.initial_pc - 1;
        /* Fill in middle routines */
        decode.low_address = decode.pc;
        decode.high_address = decode.pc;
        start_data_pc = 0;
        end_data_pc = 0;
        do {
            prev_low_pc = decode.low_address;
            prev_high_pc = decode.high_address;
            flag = 0;
            pcindex = 0;
            low_pc = decode.low_address;
            high_pc = decode.high_address;
            pc = decode.pc = decode.low_address;
            while (decode.pc <= high_pc) {
                if (start_data_pc == decode.pc)
                    decode.pc = end_data_pc;
                if (decode_routine () != END_OF_ROUTINE) {
                    if (start_data_pc == 0)
                        start_data_pc = decode.pc;
                    flag = 1;
                    end_data_pc = 0;
                    pcindex = 0;
                    pc = (pc + (story_scaler - 1)) & ~(story_scaler - 1);
                    do {
                        pc += story_scaler;
                        vars = (char) read_data_byte (&pc);
                        pc--;
                    } while (vars < 0 || vars > 15);
                    decode.pc = pc;
                } else {
                    if (start_data_pc && end_data_pc == 0)
                        end_data_pc = pc;
                    pc = (decode.pc + (story_scaler - 1)) & ~(story_scaler - 1);
                    if (!flag) {
                        low_pc = decode.low_address;
                        high_pc = decode.high_address;
                    }
                }
            }
            decode.low_address = low_pc;
            decode.high_address = high_pc;
        } while (low_pc < prev_low_pc || high_pc > prev_high_pc);
        /* Scan for high routines */
        pc = decode.pc;
        while (decode_routine () == END_OF_ROUTINE) {
            decode.high_address = pc;
            pc = decode.pc;
        }
    } else {
        printf ("\n[start of code at %lx]\n", decode.low_address);
        for (decode.pc = decode.low_address;
             decode.pc <= decode.high_address; )
            decode_routine ();
        printf ("\n[end of code at %lx]\n", decode.pc);
    }

}/* decode_program */

/* decode_routine - Decode a routine from start address to last instruction */

#ifdef __STDC__
static int decode_routine (void)
#else
static int decode_routine ()
#endif
{
    unsigned long old_pc;
    int vars, status, count, i;

    if (decode.first_pass) {
        old_pc = decode.pc;
        decode.pc = (decode.pc + (story_scaler - 1)) & ~(story_scaler - 1);
        vars = read_data_byte (&decode.pc);
        if (vars >= 0 && vars <= 15) {
            if (h_type == V3 || h_type == V4)
                for (; vars; vars--)
                    read_data_word (&decode.pc);
            if (decode_code () == END_OF_ROUTINE)
                return (END_OF_ROUTINE);
        }
        decode.pc = old_pc;
        if ((status = decode_code ()) != END_OF_ROUTINE) {
            decode.pc = old_pc;
            return (status);
        }
        pctable[pcindex++] = old_pc;
        if (pcindex == MAX_PCS) {
            fprintf (stderr, "\nFatal: too many orphan code fragments\n");
            exit (EXIT_FAILURE);
        }
    } else {
        if (decode.pc == start_data_pc) {
            printf ("\n[start of data at %lx]\n\n", decode.pc);
            count = 0;
            while (decode.pc < end_data_pc) {
                printf ("[%04x]", (unsigned int) read_data_word (&decode.pc));
                if (++count == 13) {
                    printf ("\n");
                    count = 0;
                }
            }
            printf ("\n\n[end of data at %lx]\n", decode.pc);
            decode.pc = end_data_pc;
        }
        for (i = 0; i < pcindex && decode.pc != pctable[i]; i++)
            ;
        if (i == pcindex) {
            decode.pc = (decode.pc + (story_scaler - 1)) & ~(story_scaler - 1);
            vars = read_data_byte (&decode.pc);
            printf ("%soutine %lx, %d local",
                    (decode.pc == decode.initial_pc) ? "\nMain r" : "\nR",
                    (unsigned long) decode.pc - 1,
                    vars);
            if (vars != 1)
                printf ("s");
            if (h_type == V3 || h_type == V4) {
                printf (" (");
                for (; vars; vars--) {
                    printf ("%04x", (unsigned int) read_data_word (&decode.pc));
                    if (vars > 1)
                        printf (",");
                }
                printf (")");
            }
            printf ("\n\n");
        } else
            printf ("\norphan code fragment:\n\n");
        status = decode_code ();
    }

    return (status);

}/* decode_routine */

/* decode_code - grab opcode and determine the class */

#ifdef __STDC__
static int decode_code (void)
#else
static int decode_code ()
#endif
{
    int status;

    decode.high_pc = decode.pc;
    do {
        if (!decode.first_pass)
            printf ("%5lx:  ", decode.pc);
        opcode.opcode = read_data_byte (&decode.pc);
        if (h_type == V5 && opcode.opcode == 0xbe) {
            opcode.opcode = read_data_byte (&decode.pc);
            opcode.class = EXTENDED_OPERAND;
        } else if (opcode.opcode < 0x80)
            opcode.class = TWO_OPERAND;
        else
            if (opcode.opcode < 0xb0)
                opcode.class = ONE_OPERAND;
            else
                if (opcode.opcode < 0xc0)
                    opcode.class = ZERO_OPERAND;
                else
                    opcode.class = VARIABLE_OPERAND;
        status = decode_opcode ();
    } while (status == END_OF_INSTRUCTION);

    return (status);

}/* decode_code */

/* decode_opcode - Check and decode the opcode itself */

#define caseline(opc, text, operands, type1, type2) \
    case opc: return (decode_operands (text, operands, type1, type2))

#ifdef __STDC__
static int decode_opcode (void)
#else
static int decode_opcode ()
#endif
{
    int code;

    code = opcode.opcode;

    switch (opcode.class) {

        case EXTENDED_OPERAND:
            code &= 0x3f;
            switch (code) {
                caseline (0x00, "SAVE",                 0,   STORE,   NONE);
                caseline (0x01, "RESTORE",              0,   STORE,   NONE);
                caseline (0x02, "SHIFT",                2,   STORE,   NONE);

                caseline (0x04, "GRAPHICS",             1,   STORE,   NONE);

                caseline (0x09, "SAVE UNDO",            0,   STORE,   NONE);
                caseline (0x0A, "RESTORE UNDO",         0,   STORE,   NONE);

                default:
                    return (decode_operands ("ILLEGAL", 0, ILLEGAL, NONE));
            }

        case TWO_OPERAND:
            code &= 0x1f;

        case VARIABLE_OPERAND:
            code &= 0x3f;
            switch (code) {

                caseline (0x01, "JE",                  -4,  BRANCH,   NONE);
                caseline (0x02, "JLE",                  2,  BRANCH,   NONE);
                caseline (0x03, "JGE",                  2,  BRANCH,   NONE);
                caseline (0x04, "DEC_CHK",              2,  BRANCH,    VAR);
                caseline (0x05, "INC_CHK",              2,  BRANCH,    VAR);
                caseline (0x06, "COMPARE_POBJ",         2,  BRANCH,   NONE);
                caseline (0x07, "TEST",                 2,  BRANCH,   NONE);
                caseline (0x08, "OR",                   2,   STORE,   NONE);
                caseline (0x09, "AND",                  2,   STORE,   NONE);
                caseline (0x0A, "TEST_ATTR",            2,  BRANCH,   NONE);
                caseline (0x0B, "SET_ATTR",             2,    NONE,   NONE);
                caseline (0x0C, "CLEAR_ATTR",           2,    NONE,   NONE);
                caseline (0x0D, "STORE",                2,    NONE,    VAR);
                caseline (0x0E, "INSERT_OBJ",           2,    NONE,   NONE);
                caseline (0x0F, "LOADW",                2,   STORE,   NONE);
                caseline (0x10, "LOADB",                2,   STORE,   NONE);
                caseline (0x11, "GET_PROP",             2,   STORE,   NONE);
                caseline (0x12, "GET_PROP_ADDR",        2,   STORE,   NONE);
                caseline (0x13, "GET_NEXT_PROP",        2,   STORE,   NONE);
                caseline (0x14, "ADD",                  2,   STORE,   NONE);
                caseline (0x15, "SUB",                  2,   STORE,   NONE);
                caseline (0x16, "MUL",                  2,   STORE,   NONE);
                caseline (0x17, "DIV",                  2,   STORE,   NONE);
                caseline (0x18, "MOD",                  2,   STORE,   NONE);

                caseline (0x20, "CALL",                -4,    CALL,   NONE);
                caseline (0x21, "STOREW",               3,    NONE,   NONE);
                caseline (0x22, "STOREB",               3,    NONE,   NONE);
                caseline (0x23, "PUT_PROP",             3,    NONE,   NONE);

                caseline (0x25, "PRINT_CHAR",           1,   PCHAR,   NONE);
                caseline (0x26, "PRINT_NUM",            1,    NONE,   NONE);
                caseline (0x27, "RANDOM",               1,   STORE,   NONE);
                caseline (0x28, "PUSH",                 1,    NONE,   NONE);
                caseline (0x29, "POP",                  1,    NONE,    VAR);
                caseline (0x2A, "STATUS_SIZE",          1,    NONE,   NONE);
                caseline (0x2B, "SET_WINDOW",           1,    NONE,   NONE);

                caseline (0x33, "SET_PRINT",           -2,    NONE,   NONE);
                caseline (0x34, "#RECORD_MODE",         1,    NONE,   NONE);
                caseline (0x35, "SOUND",               -4,    NONE,   NONE);

                default:
                    switch (h_type) {
                        case V3:
                            switch (code) {
                                caseline (0x24, "READ",                 2,    NONE,   NONE);
                            }
                        case V4:
                            switch (code) {
                                caseline (0x19, "CALL",                 2,    CALL,   NONE);

                                caseline (0x24, "READ",                -4,    NONE,   NONE);

                                caseline (0x2C, "CALL",                -8,    CALL,   NONE);
                                caseline (0x2D, "ERASE_WINDOW",         1,    NONE,   NONE);
                                caseline (0x2E, "ERASE_LINE",           1,    NONE,   NONE);
                                caseline (0x2F, "SET_CURSOR",           2,    NONE,   NONE);

                                caseline (0x31, "VIDEO_ATTR",           1,   VATTR,   NONE);
                                caseline (0x32, "SET_FORMAT",           1,    NONE,   NONE);

                                caseline (0x36, "READ_CHAR",           -3,   STORE,   NONE);
                                caseline (0x37, "SCANW",               -4,   STORE, OBJECT);
                            }
                        case V5:
                            switch (code) {
                                caseline (0x19, "CALL",                 2,    CALL,   NONE);
                                caseline (0x1a, "CALL",                 2,   NCALL,   NONE);
                                caseline (0x1b, "OPCODE 0x1B",          2,    NONE,   NONE);

                                caseline (0x24, "READ",                -4,   STORE,   NONE);

                                caseline (0x2C, "CALL",                -8,    CALL,   NONE);
                                caseline (0x2D, "ERASE_WINDOW",         1,    NONE,   NONE);
                                caseline (0x2E, "ERASE_LINE",           1,    NONE,   NONE);
                                caseline (0x2F, "SET_CURSOR",           2,    NONE,   NONE);

                                caseline (0x31, "VIDEO_ATTR",           1,   VATTR,   NONE);
                                caseline (0x32, "SET_FORMAT",           1,    NONE,   NONE);

                                caseline (0x36, "READ_CHAR",           -3,   STORE,   NONE);
                                caseline (0x37, "SCANW",               -4,   STORE, OBJECT);

                                caseline (0x39, "CALL",                -4,   NCALL,   NONE);
                                caseline (0x3a, "CALL",                -8,   NCALL,   NONE);
                                caseline (0x3b, "OPCODE 0x3B",         -4,    NONE,   NONE);
                                caseline (0x3c, "OPCODE 0x3C",          4,    NONE,   NONE);
                                caseline (0x3d, "OPCODE 0x3D",          3,    NONE,   NONE);
                                caseline (0x3e, "OPCODE 0x3E",         -3,    NONE,   NONE);
                                caseline (0x3f, "CHECK_VA_ARG",         1,  BRANCH,   NONE);
                            }
                    }
                    return (decode_operands ("ILLEGAL", 0, ILLEGAL, NONE));
            }

        case ONE_OPERAND:
            code &= 0x0f;
            switch (code) {
                caseline (0x00, "JZ",                   1,  BRANCH,   NONE);
                caseline (0x01, "GET_SIBLING",          1,   STORE, OBJECT);
                caseline (0x02, "GET_CHILD",            1,   STORE, OBJECT);
                caseline (0x03, "GET_PARENT",           1,   STORE,   NONE);
                caseline (0x04, "GET_PROP_LEN",         1,   STORE,   NONE);
                caseline (0x05, "INC",                  1,    NONE,    VAR);
                caseline (0x06, "DEC",                  1,    NONE,    VAR);
                caseline (0x07, "PRINT_ADDR",           1,    NONE,   NONE);

                caseline (0x09, "REMOVE_OBJ",           1,    NONE,   NONE);
                caseline (0x0A, "PRINT_OBJ",            1,    NONE,   NONE);
                caseline (0x0B, "RET",                  1,  RETURN,   NONE);
                caseline (0x0C, "JUMP",                 1,    JUMP,   NONE);
                caseline (0x0D, "PRINT_PADDR",          1,    NONE,   NONE);
                caseline (0x0E, "LOAD",                 1,   STORE,    VAR);

                default:
                    switch (h_type) {
                        case V3:
                            switch (code) {
                                caseline (0x0F, "NOT",                  1,   STORE,   NONE);
                            }
                        case V4:
                            switch (code) {
                                caseline (0x08, "CALL",                 1,    CALL,   NONE);

                                caseline (0x0F, "NOT",                  1,   STORE,   NONE);
                            }
                        case V5:
                            switch (code) {
                                caseline (0x08, "CALL",                 1,    CALL,   NONE);

                                caseline (0x0F, "CALL",                 1,   NCALL,   NONE);
                            }
                    }
                    return (decode_operands ("ILLEGAL", 0, ILLEGAL, NONE));
            }

        case ZERO_OPERAND:
            code &= 0x0f;
            switch (code) {
                caseline (0x00, "RET           #TRUE",  0,  RETURN,   NONE);
                caseline (0x01, "RET           #FALSE", 0,  RETURN,   NONE);
                caseline (0x02, "PRINT",                0,    NONE,   TEXT);
                caseline (0x03, "PRINT_RET",            0,  RETURN,   TEXT);

                caseline (0x07, "RESTART",              0,    NONE,   NONE);
                caseline (0x08, "RET           (SP)+",  0,  RETURN,   NONE);
                caseline (0x09, "POP",                  0,    NONE,   NONE);
                caseline (0x0A, "QUIT",                 0,    NONE,   NONE);
                caseline (0x0B, "NEW_LINE",             0,    NONE,   NONE);

                caseline (0x0D, "VERIFY",               0,  BRANCH,   NONE);

                default:
                    switch (h_type) {
                        case V3:
                            switch (code) {
                                caseline (0x05, "SAVE",                 0,  BRANCH,   NONE);
                                caseline (0x06, "RESTORE",              0,  BRANCH,   NONE);

                                caseline (0x0C, "SHOW_SCORE",           0,    NONE,   NONE);
                            }
                        case V4:
                            switch (code) {
                                caseline (0x05, "SAVE",                 0,   STORE,   NONE);
                                caseline (0x06, "RESTORE",              0,   STORE,   NONE);
                            }
                        case V5:
                            switch (code) {
                                /* From a bug in Wishbringer V23 */
                                caseline (0x0C, "SHOW_SCORE",           0,    NONE,   NONE);
                            }
                    }
                    return (decode_operands ("ILLEGAL", 0, ILLEGAL, NONE));
            }

        default:
            fprintf (stderr, "\nFatal: bad class (%d)\n", opcode.class);
            exit (EXIT_FAILURE);
    }
    return (0);

}/* decode_opcode */

#undef caseline

/* decode_operands - Decode operands of opcode */

#ifdef __STDC__
static int decode_operands (const char *opcode_name, int input_opers, int type, int special)
#else
static int decode_operands (opcode_name, input_opers, type, special)
const char *opcode_name;
int input_opers;
int type;
int special;
#endif
{
    size_t len;
    int opers, status;

    opcode.type = type;
    opcode.special = special;

    if (opcode.type == ILLEGAL)
        return (BAD_OPCODE);

    if (decode.first_pass) {
        opers = decode_inputs ();
        if (input_opers < 0) {
            if (opers > abs (input_opers))
                return (BAD_OPCODE);
        } else
            if (opers != input_opers)
                return (BAD_OPCODE);
        status = decode_outputs ();
    } else {
        printf (opcode_name);
        for (len = strlen (opcode_name); len < 14; len++)
            printf (" ");
        opers = decode_inputs ();
        if (opers > 0 && ((opcode.type == CALL) || (opcode.type == BRANCH) || (opcode.type == STORE)))
            printf (" -> ");
        status = decode_outputs ();
        printf ("\n");
    }
    if (decode.pc > decode.high_pc)
        decode.high_pc = decode.pc;

    return (status);

}/* decode_operands */

/* decode_inputs - Decode input operands */

#ifdef __STDC__
static int decode_inputs (void)
#else
static int decode_inputs ()
#endif
{
    int modes, addr_mode, maxopers, opers = 0;

    switch (opcode.class) {

        case ONE_OPERAND:
            decode_operand ((opcode.opcode >> 4) & 0x03, 1);
            opers = 1;
            break;

        case TWO_OPERAND:
            decode_operand ((opcode.opcode & 0x40) ? VARIABLE : BYTE_IMMED, 1);
            if (!decode.first_pass)
                printf (",");
            decode_operand ((opcode.opcode & 0x20) ? VARIABLE : BYTE_IMMED, 0);
            opers = 2;
            break;

        case VARIABLE_OPERAND:
        case EXTENDED_OPERAND:
            if ((opcode.opcode & 0x3f) == 0x2c ||
                (opcode.opcode & 0x3f) == 0x3a) {
                modes = read_data_word (&decode.pc);
                maxopers = 8;
            } else {
                modes = read_data_byte (&decode.pc);
                maxopers = 4;
            }
            for (addr_mode = 0, opers = 0;
                 (addr_mode != NO_OPERAND) && maxopers; maxopers--) {
                addr_mode = (modes >> ((maxopers - 1) * 2)) & 0x03;
                if (addr_mode != NO_OPERAND) {
                    if (!decode.first_pass && opers) {
                        if ((opcode.type == CALL || opcode.type == NCALL) && opers == 1)
                            printf (" (");
                        else
                            printf (",");
                    }
                    decode_operand (addr_mode, opers == 0);
                    opers++;
                }
            }
            if (!decode.first_pass && (opcode.type == CALL || opcode.type == NCALL) && opers > 1)
                printf (")");
            break;

        case ZERO_OPERAND:
            if (opcode.special == TEXT) {
                if (decode.first_pass) {
                    while ((short) read_data_word (&decode.pc) >= 0)
                        ;
                } else {
                    printf ("\"");
                    decode_text (&decode.pc);
                    printf ("\"");
                }
            }
            break;

        default:
            fprintf (stderr, "\nFatal: bad class (%d)\n", opcode.class);
            exit (EXIT_FAILURE);
    }

    return (opers);

}/* decode_inputs */

/* decode_operand - Decode one operand */

#ifdef __STDC__
static void decode_operand (int addr_mode, int flag)
#else
static void decode_operand (addr_mode, flag)
int addr_mode;
int flag;
#endif
{
    unsigned long addr = 0;
    int vars, type, special;

    if (flag) {
        type = opcode.type;
        special = opcode.special;
    } else {
        type = NONE;
        special = NONE;
    }
    if (special == VAR)
        addr_mode = VARIABLE;
    switch (addr_mode) {

        case WORD_IMMED:
            addr = (zword_t) read_data_word (&decode.pc);
            break;

        case BYTE_IMMED:
            addr = (zbyte_t) read_data_byte (&decode.pc);
            break;

        case VARIABLE:
            addr = read_data_byte (&decode.pc);
            if (!decode.first_pass) {
                if (addr == 0)
                    printf ("(SP)+");
                else
                    if (addr < 16)
                        printf ("L%02lx", (unsigned long) (addr - 1));
                    else
                        printf ("G%02lx", (unsigned long) (addr - 16));
            }

        case NO_OPERAND:
            return;

        default:
            fprintf (stderr, "\nFatal: bad addressing mode (%d)\n", addr_mode);
            exit (EXIT_FAILURE);
    }
    switch (type) {

        case NCALL:
        case CALL:
            addr *= story_scaler;
            if (!decode.first_pass)
                printf ("%lx", addr);
            if (addr < decode.low_address &&
                addr >= (unsigned long) h_data_size) {
                vars = read_data_byte (&addr);
                if (vars >= 0 && vars <= 15)
                    decode.low_address = addr - 1;
            }
            if (addr > decode.high_address &&
                addr < ((unsigned long) h_file_size * story_scaler)) {
                vars = read_data_byte (&addr);
                if (vars >= 0 && vars <= 15)
                    decode.high_address = addr - 1;
            }
            break;

        case JUMP:
            addr = decode.pc + (short) addr - 2;
            if (!decode.first_pass)
                printf ("%lx", addr);
            if (addr > decode.high_pc)
                decode.high_pc = addr;
            break;

        case PCHAR:
            if (!decode.first_pass)
                if (isprint ((char) addr)) {
                    printf ("\'%c\'", (char) addr);
                    break;
                }

        case VATTR:
            if (!decode.first_pass) {
                switch ((char) addr) {
                    case NORMAL:
                        printf ("NORMAL");
                        break;
                    case REVERSE:
                        printf ("REVERSE");
                        break;
                    case BOLD:
                        printf ("BOLD");
                        break;
                    case BLINK:
                        printf ("BLINK");
                        break;
                    case UNDERSCORE:
                        printf ("UNDERSCORE");
                        break;
                }
                if (addr >= 0 && addr <= 4)
                    break;
            }

        default:
            if (!decode.first_pass)
                if (addr_mode == WORD_IMMED) {
                    printf ("#%04lx", addr);
                    if (special = lookup_string (addr * story_scaler))
                        printf ("[s%d]", special);
                    if (addr = lookup_word (addr)) {
                        printf ("[w\"");
                        decode_text (&addr);
                        printf ("\"]");
                    }
                } else
                    printf ("#%02lx", addr);
    }

}/* decode_operand */

/* decode_outputs - Decode output operand */

#ifdef __STDC__
static int decode_outputs (void)
#else
static int decode_outputs ()
#endif
{
    unsigned long addr;

    switch (opcode.type) {

        case CALL:
        case STORE:
            addr = (zbyte_t) read_data_byte (&decode.pc);
            if (!decode.first_pass) {
                if (addr == 0)
                    printf ("-(SP)");
                else
                    if (addr < 16)
                        printf ("L%02lx", (unsigned long) (addr - 1));
                    else
                        printf ("G%02lx", (unsigned long) (addr - 16));
            }
            if (opcode.special != OBJECT)
                break;
            else
                if (!decode.first_pass)
                    printf (", ");

        case BRANCH:
            addr = (zbyte_t) read_data_byte (&decode.pc);
            if (!decode.first_pass) {
                if (addr & 0x80)
                    printf ("[TRUE]");
                else
                    printf ("[FALSE]");
            }
            addr &= 0x7f;
            if (addr & 0x40)
                addr &= 0x3f;
            else {
                addr = (addr << 8) | (zbyte_t) read_data_byte (&decode.pc);
                if (addr & 0x2000) {
                    addr &= 0x1fff;
                    addr |= ~0x1fff;
                }
            }
            if (addr == 0) {
                if (!decode.first_pass)
                    printf (" RET #FALSE");
            } else if (addr == 1) {
                if (!decode.first_pass)
                    printf (" RET #TRUE");
            } else {
                addr = decode.pc + addr - 2;
                if (!decode.first_pass)
                    printf (" %lx", addr);
                if (addr > decode.high_pc)
                    decode.high_pc = addr;
            }
            break;

        case RETURN:
        case JUMP:
            if (decode.pc > decode.high_pc)
                return (END_OF_ROUTINE);

        case NCALL:
        case NONE:
        case PCHAR:
        case VATTR:
            break;

        default:
            fprintf (stderr, "\nFatal: bad type (%d)\n", opcode.type);
            exit (EXIT_FAILURE);
    }

    return (END_OF_INSTRUCTION);

}/* decode_outputs */

/* decode_strings - Dump text after end of code */

#ifdef __STDC__
static void decode_strings (unsigned long pc)
#else
static void decode_strings (pc)
unsigned long pc;
#endif
{
    int count = 1;

    pc = (pc + (story_scaler - 1)) & ~(story_scaler - 1);
    printf ("\n[start of text at %lx]\n\n", pc);
    while (pc < ((unsigned long) h_file_size * story_scaler)) {
        printf ("%5lx: [s%d] \"", pc, count++);
        decode_text (&pc);
        printf ("\"\n");
        pc = (pc + (story_scaler - 1)) & ~(story_scaler - 1);
    }
    printf ("\n[end of text at %lx]\n\n[End of file]\n", pc);

}/* decode_strings */

typedef struct string_item {
    struct string_item *next;
    unsigned long text_addr;
    int text_num;
} string_item_t;

static string_item_t *strings_base = NULL;

/* scan_strings - build string address table */

#ifdef __STDC__
static void scan_strings (unsigned long pc)
#else
static void scan_strings (pc)
unsigned long pc;
#endif
{
    int count = 1;
    string_item_t *string_item;
    zword_t data;

    pc = (pc + (story_scaler - 1)) & ~(story_scaler - 1);
    while (pc < ((unsigned long) h_file_size * story_scaler)) {
        string_item = (string_item_t *) malloc (sizeof (string_item_t));
        if (string_item == NULL) {
            fprintf (stderr, "Insufficient memory\n");
            exit (EXIT_FAILURE);
        }
        string_item->text_addr = pc;
        string_item->text_num = count++;
        string_item->next = strings_base;
        strings_base = string_item;
        do
            data = (zword_t) read_data_word (&pc);
        while ((data & 0x8000) == 0);
        pc = (pc + (story_scaler - 1)) & ~(story_scaler - 1);
    }

}/* scan_strings */

/* lookup_string - lookup a string address */

#ifdef __STDC__
static int lookup_string (unsigned long addr)
#else
static int lookup_string (addr)
unsigned long addr;
#endif
{
    string_item_t *string_item;

    for (string_item = strings_base; string_item->next != NULL; string_item = string_item->next)
        if (string_item->text_addr == addr)
            return (string_item->text_num);

    return (0);

}/* lookup_string */

typedef struct word_item {
    struct word_item *next;
    unsigned long word_addr;
} word_item_t;

static word_item_t *words_base = NULL;

/* scan_words - build dictionary address table */

#ifdef __STDC__
static void scan_words (void)
#else
static void scan_words ()
#endif
{
    unsigned long offset;
    int separator_count, word_size, word_count, i;
    word_item_t *word_item;

    offset = (unsigned long) h_words_offset;
    separator_count = (int) read_data_byte (&offset);
    offset += separator_count;
    word_size = (int) read_data_byte (&offset);
    word_count = (int) read_data_word (&offset);

    for (i = 0; i < word_count; i++) {
        if ((offset + 4) < (unsigned long) h_data_size)
            if (h_type == V3)
                set_byte (offset + 2, get_byte (offset + 2) | 0x80);
            else
                set_byte (offset + 4, get_byte (offset + 4) | 0x80);
        word_item = (word_item_t *) malloc (sizeof (word_item_t));
        if (word_item == NULL) {
            fprintf (stderr, "Insufficient memory\n");
            exit (EXIT_FAILURE);
        }
        word_item->word_addr = offset;
        word_item->next = words_base;
        words_base = word_item;
        offset += word_size;
    }

}/* scan_words */

/* lookup_word - lookup a word address */

#ifdef __STDC__
static unsigned long lookup_word (unsigned long addr)
#else
static unsigned long lookup_word (addr)
unsigned long addr;
#endif
{
    word_item_t *word_item;

    for (word_item = words_base; word_item->next != NULL; word_item = word_item->next)
        if (word_item->word_addr == addr)
            return (word_item->word_addr);

    return (0);

}/* lookup_word */
