[misc.misc] Infocom

linhart@cream.rutgers.edu (Mike Threepoint) (03/17/91)

Hello sailor!

Introducing my latest hack...  

Having solved every Infocom adventure game except the 5 I don't have,
what else was there to do but hack them?

This program extracts and translates the vocabulary list from the
Infocom adventure game data files bundles with the interpreters.
The data files should be the same format on any system.  The program
should compile with only minor tweaks on any ANSI C compiler, but
for UNIX you'll want to remove the `swab()' calls to swap the byte
sex.

Enjoy...

<-- snip, snip
/* vocab.c -- A data dumper
 * Copyleft (c) 1991 by Mike Threepoint.  All rights reversed.
 * Release 1 / Serial number 910310
 *
 * This program dumps the vocabulary list encoded in a standard Infocom(tm)
 * adventure game data file.  Having solved all the Infocom games I have,
 * there's little left to do but hack them.
 *
 * Go ahead, reassure yourself you've seen every last Encyclopedia Frobizzica
 * entry, learned every spell, and know all the magic wand's F-words.
 * Discover obscure synonyms (like calling the Zorkian elvish sword Glamdring
 * and the dragon Smaug) and trivia about the game's internal operations
 * (like the internal `intnum' noun in several games, used when you type a
 * number).
 *
 * I doubt Infocom's employees will complain, either of them.  Alas, Infocom.
 * I wore a black armband when you went under.  If only you'd stayed solvent.
 * (At least till I could buy Sherlock and Arthur!  Can't purchase them
 * anywhere anymore...)
 *
 * Email correspondence to linhart@remus.rutgers.edu.
 *
 * Disclaimer:  This program works for me, at the moment.  I've never seen
 *              any Infocom source code(*), and nobody at 55 CambridgePark
 *              Drive told me any technical details.  I'm just an independent
 *              public domain software author.  If I-Need-Magic sues, I'll
 *              cheerfully turn over all zero profits I made on this program.
 *
 * (* Well, maybe one function.  I noticed the Beyond Zork MS-DOS interpreter
 *    was in MSC, so I mailed them a MSC function to get the screen size from
 *    the BIOS instead of the stupid SETUP.EXE method, so the interpreter
 *    could figure out when my VGA was in 50 line mode.  Some time later, a
 *    new text game was released, with VisiClues.  I started it in 50-line
 *    mode, but the screen was reset to 25-line CGA color mode.  And then the
 *    text ran off the bottom of the screen and scrolled as if it were still
 *    50 lines long.  I'd mail another helpful letter, but it's too late now.)
 */

#define MAXCOL 79

#if !defined(__STDC__) && !defined(__TURBOC__)
#error This is an ANSI-complaint.  It looks like you're not ANSI-compliant.
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>

#define S_BLANK         1
#define S_MACRO1        2
#define S_MACRO2        3
#define S_MACRO3        4
#define S_CAPS          5
#define S_PUNC          6
#define S_FILLER        6
#define S_OFF           7

struct bits {
        unsigned ch3 : 5;
        unsigned ch2 : 5;
        unsigned ch1 : 5;
        unsigned     : 1;
};

/* the 5-bit character set */
const char err_chars[7] = {
        /* null thrown in for string handling */
        '\0',
        /* special codes above */
        ' ', '1', '2', '3', '^', '@',
        /* followed by: */
};

typedef const char alfabet[26];

alfabet
lower = {
        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
        'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
        },
upper = {
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
        'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
        },
punct = {
        '$', ' ',
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
        '.', ',', '!', '?', '_', '#', '\'', '\"', '/', '\\', '-', ':', '(', ')'
        };

struct info_header {
        char            type;
        char            qqc;
        unsigned        release;
        unsigned        qqc3;
        unsigned        qqc4;
        unsigned        vocab_offset;
        unsigned        off6;
        unsigned        qqc7;
        unsigned        qqc8;
        unsigned        nul9;
        char            rev_date[6];
        unsigned        macro_offset;
        unsigned        qqc14;
        unsigned        qqc15;
        unsigned        rest[17];
};

FILE    *infile;
short   column = 0;
char    numbers = 1,
        bits = 0,
        wide = 1,
        did_file = 0;


void
newline ( void )
{
        putchar('\n');
        column = 0;
}

char *
bits_to_bstr ( struct bits *chars )
{
        static char buf[4] = {0, 0, 0, 0};

        buf[0] = chars->ch1 + 1;
        buf[1] = chars->ch2 + 1;
        buf[2] = chars->ch3 + 1;

        return buf;
}

char *
bstr_to_str ( char *s )
{
        int             len = strlen(s);
        static char     new[MAXCOL+1];
        unsigned        newlen = 0;

        while (s[len-1] == S_FILLER)
                s[--len] = '\0';

        while (*s) {
                switch (*s) {
                        case S_MACRO1:
                        case S_MACRO2:
                        case S_MACRO3:
                                if (*s == S_MACRO1 && *(s+1) == S_CAPS) {
                                        s++;
                                        break;
                                }

                                /* shouldn't appear in vocabulary list */
                                new [ newlen++ ] = err_chars[*s];
                                break;
                        case S_CAPS:
                                if (*(s+1) >= S_OFF)
                                        new [ newlen++ ] = upper[*(++s) - S_OFF];
                                else
                                        new [ newlen++ ] = err_chars[S_CAPS];
                                break;
                        case S_PUNC:
                                if (*(s+1) >= S_OFF)
                                        new [ newlen++ ] = punct[*(++s) - S_OFF];
                                else
                                        new [ newlen++ ] = err_chars[S_PUNC];
                                break;
                        case S_BLANK:
                                new [ newlen++ ] = ' ';
                                break;
                        default:
                                new [ newlen++ ] = lower[*s - S_OFF];
                }
                s++;
        }

        new [ newlen ] = '\0';

        return new;
}

void
disp_ch ( char x )
{
        putchar(x);
        column++;
}

void
disp_str ( char *fmt, ... )
{
        va_list         argptr;
        static char     buf[16];
        short           len;

        va_start(argptr, fmt);
        vsprintf(buf, fmt, argptr);
        va_end(argptr);

        len = strlen(buf);
        if (column + len > MAXCOL)
                newline();
        printf(buf);
        column += len;
}

void
error ( char *fmt, ... )
{
        va_list         argptr;

        fprintf(stderr, "Error: ");

        va_start(argptr, fmt);
        vfprintf(stderr, fmt, argptr);
        va_end(argptr);

        exit(1);
}

void
disp_bits ( char c )
{
        disp_str(" %d%d%d%d%d%d%d%d",
                 !!(c & 0x80),
                 !!(c & 0x40),
                 !!(c & 0x20),
                 !!(c & 0x10),
                 !!(c & 0x08),
                 !!(c & 0x04),
                 !!(c & 0x02),
                 !!(c & 0x01));
}

void
frob_file ( const char *filename )
{
        register unsigned       count = 0;
        unsigned                words;
        unsigned                i;
        unsigned char           ch, n;
        struct bits             word[3];
        char                    buf[10];
        int                     entry_width;
        struct info_header      header;
        unsigned long           pos;

        if((infile = fopen(filename, "rb")) == NULL)
                error("Can't open \"%s\".\n", filename);

        printf("%s:\n", filename);

        fread(&header, sizeof(header), 1, infile);
        swab((char *)&header.release, (char *)&i, 2);
        printf("Release %u, updated %.6s\n", i, &header.rev_date[0]);

        swab((char *)&header.vocab_offset, (char *)&i, 2);
#ifdef DEBUG
        printf("Vocabulary table offset: %04X\n", i);
#endif

        if (fseek(infile, pos = i, SEEK_SET) != 0)
                error("Can't seek offset %04X.\n", pos);

        /* skip leading info */
        if (fread(&ch, sizeof(ch), 1, infile) < 1)
                error("Can't read character at offset %04X.\n", ftell(infile));

        if (fseek(infile, pos = ch, SEEK_CUR) != 0)
                error("Can't skip %ld characters from offset %04X.\n", pos, ftell(infile));

        fread(&ch, sizeof(ch), 1, infile);
        /* 0x07 for 6 letters, 0x09 for 9 letters */
        n = (ch < 9) ? 6 : 9;

        if (fread(&i, sizeof(i), 1, infile) < 1)
                error("Can't read word at offset %ld.\n", ftell(infile));
        swab((char *)&i, (char *)&words, 2);
#ifdef DEBUG
        printf("Vocabulary entries: %d\n", words);
#else
        if (!numbers)
                printf("%d vocabulary entries\n", words);
#endif

        entry_width = n;
        n /= 3;

        if (numbers)
                entry_width += 5;
        if (bits)
                entry_width += (wide ? 2 : 8) * 3 + 3;

        while ( count < words ) {
                if (fread(buf, sizeof(struct bits), n, infile) < n)
                        error("Can't read vocabulary word at offset %ld.\n", ftell(infile));

                swab(buf, (char *)&word, sizeof(struct bits) * n);

                ++count;
                if (numbers)
                        disp_str("%04d ", count);

                strcpy(buf, bits_to_bstr(&word[0]));
                strcat(buf, bits_to_bstr(&word[1]));
                if (n >= 3)
                        strcat(buf, bits_to_bstr(&word[2]));
                disp_str(n >= 3 ? "%-9s" : "%-6s", bstr_to_str(buf));

                if (fread(buf, sizeof(char), 3, infile) < 3)
                        error("Can't read vocabulary flags at offset %ld.\n", ftell(infile));

                if (bits)
                    if (wide)
                           disp_str("  %02x %02x %02x", buf[1], buf[2], buf[3]);
                    else {
                           disp_ch(' ');
                           disp_bits(buf[1]);
                           disp_bits(buf[2]);
                           disp_bits(buf[3]);
                    }

                if (wide && column + entry_width + 2 < MAXCOL)
                        disp_str("  ");
                else
                        newline();
        }

        if (column)
                newline();
}

#ifndef LINT
const char sccsid[] = "@(#) " __FILE__ " by Mike Threepoint compiled " __DATE__;
#endif

void
info ( void )
{
        puts("Display a vocabulary list of an Infocom(tm) adventure game data file.\n"
             "\n"
             "Usage:  vocab [/#] [/1] [/b] file...\n"
             "\n"
             "\t/#   toggle numbers\n"
             "\t/1   toggle one-column list\n"
             "\t/b   toggle display of additional info encoded with each entry\n"
             "\n");
        exit(0);
}

void
parse ( char *parm )
{
        if (*parm == '-' || *parm == '/')
                switch (tolower(*++parm)) {
                        case '#':
                        case 'n':
                                numbers = !numbers;
                                break;
                        case '1':
                                wide = !wide;
                                break;
                        case 'b':
                                bits = !bits;
                                break;
                        case 'h':
                        case '?':
                                info();
                }
        else {
                if (did_file) newline();
                frob_file(parm);
                did_file = 1;
        }
}

int
main ( const unsigned argc, char *argv[] )
{
        if (argc > 1) {
                register count;

                if (strcmp(argv[1], "?") == 0)
                        info();

                for (count = 1; count < argc; count++)
                        parse(argv[count]);

                if (did_file) return 0;
        }

        info();
        return 1;
}