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; }