/*
 * infodump V7/3
 *
 * Infocom data file dumper, etc. for V1 to V8 games.
 * Works on everything I have, except for parsing information in V6 games.
 *
 * The most useful options are; -i to display the header information, such as
 * game version, serial number and release; -t to display the object tree which
 * shows where all objects start and which item is in which room; -g to show
 * the sentence grammar acceptable to the game; -d to see all the recognised
 * words.
 *
 * Required files:
 *    showhead.c - show header information
 *    showdict.c - show dictionary and abbreviations
 *    showobj.c  - show objects
 *    showverb.c - show verb grammar
 *    txio.c     - I/O support
 *    tx.h       - Include file required by everything above
 *    getopt.c   - The standard getopt function
 *
 * Usage: infodump [options...] story-file [story-file...]
 *     -i   show game information in header (default)
 *     -a   show abbreviations
 *     -m   show data file map
 *     -o   show objects
 *     -t   show object tree
 *     -g   show verb grammar
 *     -d n show dictionary (n = columns)
 *     -a   all of the above
 *     -w n display width (0 = no wrap)
 *
 * Mark Howell 28 August 1992 howell_ma@movies.enet.dec.com
 *
 * History:
 *    Fix verb table display for later V4 and V5 games
 *    Fix property list
 *    Add verb table
 *    Add support for V1 and V2 games
 *    Improve output
 *    Improve verb formatting
 *    Rewrite and add map
 *    Add globals address and V6 start PC
 *    Fix lint warnings and some miscellaneous bugs
 *    Add support for V7 and V8 games
 *    Fix Inform grammar tables
 *    Fix Inform adjectives table
 *    Add support for Inform 6 (helped by Matthew T. Russotto)
 *    Add header flag for "timed input"
 *    Add header extension table and Unicode table
 *    Add Inform and user symbol table support
 */
#include <getopt.h>
#include "tx.h"

#ifdef __STDC__
#ifndef HAS_GETOPT
extern int getopt (int, char *[], const char *);
#endif
extern void show_header (void);
extern void show_abbreviations (void);
extern void show_dictionary (int);
extern void show_objects (int);
extern void show_tree (void);
extern void show_verbs (int);
#else
extern int getopt ();
extern void show_header ();
extern void show_abbreviations ();
extern void show_dictionary ();
extern void show_objects ();
extern void show_tree ();
extern void show_verbs ();
#endif

#ifdef __STDC__
static void show_help (const char *);
static void process_story (const char *, int *, int, int);
static void fix_dictionary (void);
static void show_map (void);
#else
static void show_help ();
static void process_story ();
static void fix_dictionary ();
static void show_map ();
#endif

/* Options */

#define OPTION_A 0
#define OPTION_I 1
#define OPTION_O 2
#define OPTION_T 3
#define OPTION_G 4
#define OPTION_D 5
#define OPTION_M 6
#define MAXOPT   7

#ifndef HAS_GETOPT
/* getopt linkages */

extern int optind;
extern const char *optarg;
#endif

/*
 * main
 *
 * Process command line arguments and process each story file.
 */

#ifdef __STDC__
int main (int argc, char *argv[])
#else
int main (argc, argv)
int argc;
char *argv[];
#endif
{
    int c, f, i, errflg = 0;
    int columns, options[MAXOPT];
    int symbolic;

/* Clear all options */

    for (i = 0; i < MAXOPT; i++)
	options[i] = 0;
    columns = 0;
    symbolic = 0;

    /* Parse the options */

    while ((c = getopt (argc, argv, "hafiotgmdsc:w:u:")) != EOF) {
	switch (c) {
	    case 'f':
		for (i = 0; i < MAXOPT; i++)
		    options[i] = 1;
		break;
	    case 'a':
		options[OPTION_A] = 1;
		break;
	    case 'i':
		options[OPTION_I] = 1;
		break;
	    case 'o':
		options[OPTION_O] = 1;
		break;
	    case 't':
		options[OPTION_T] = 1;
		break;
	    case 'g':
		options[OPTION_G] = 1;
		break;
	    case 'm':
		options[OPTION_M] = 1;
		break;
	    case 'd':
		options[OPTION_D] = 1;
		break;
	    case 's':
		symbolic = 1;
		break;
	    case 'c':
		columns = atoi (optarg);
		break;
	    case 'w':
		tx_set_width (atoi (optarg));
		break;
	    case 'u':
	    	symbolic = 1;
		init_symbols (optarg);
		break;
	    case 'h':
	    case '?':
	    default:
		errflg++;
	}
    }

    /* Display usage if unknown flag or no story file */

    if (errflg || optind >= argc) {
	show_help (argv[0]);
	exit (EXIT_FAILURE);
    }

    /* If no options then force header option information on */

    for (f = 0, i = 0; i < MAXOPT; i++)
	f += options[i];
    if (f == 0)
	options[OPTION_I] = 1;

    /* Process any story files on the command line */

    for (; optind < argc; optind++)
	process_story (argv[optind], options, columns, symbolic);

    exit (EXIT_SUCCESS);

    return (0);

}/* main */

/*
 * show_help
 */

#ifdef __STDC__
static void show_help (const char *program)
#else
static void show_help (program)
const char *program;
#endif
{

    (void) fprintf (stderr, "usage: %s [options...] story-file [story-file...]\n\n", program);
    (void) fprintf (stderr, "INFODUMP version 7/3 - display Infocom story file information. By Mark Howell\n");
    (void) fprintf (stderr, "Works with V1 to V8 Infocom games.\n\n");
    (void) fprintf (stderr, "\t-i   show game information in header (default)\n");
    (void) fprintf (stderr, "\t-a   show abbreviations\n");
    (void) fprintf (stderr, "\t-m   show data file map\n");
    (void) fprintf (stderr, "\t-o   show objects\n");
    (void) fprintf (stderr, "\t-t   show object tree\n");
    (void) fprintf (stderr, "\t-g   show verb grammar\n");
    (void) fprintf (stderr, "\t-d   show dictionary\n");
    (void) fprintf (stderr, "\t-f   full report (all of the above)\n");
    (void) fprintf (stderr, "\t-c n number of columns for dictionary display\n");
    (void) fprintf (stderr, "\t-w n display width (0 = no wrap)\n");
    (void) fprintf (stderr, "\t-s Display Inform symbolic names in object and grammar displays\n");
    (void) fprintf (stderr, "\t-u <file> Display symbols from file in object and grammar displays (implies -s)\n");

}/* show_help */

/*
 * process_story
 *
 * Load the story and display all parts of the data file requested.
 */

#ifdef __STDC__
static void process_story (const char *name, int *options, int columns, int symbolic)
#else
static void process_story (name, options, columns, symbolic)
const char *name;
int *options;
int columns;
int symbolic;
#endif
{

    tx_printf ("\nStory file is %s\n", name);

    open_story (name);

    configure (V1, V8);

    load_cache ();

    fix_dictionary ();

    if (options[OPTION_I])
	show_header ();

    if (options[OPTION_M])
	show_map ();

    if (options[OPTION_A])
	show_abbreviations ();

    if (options[OPTION_O])
	show_objects (symbolic);

    if (options[OPTION_T])
	show_tree ();

    if (options[OPTION_G])
	show_verbs (symbolic);

    if (options[OPTION_D])
	show_dictionary (columns);

    close_story ();

}/* process_story */

/*
 * fix_dictionary
 *
 * Fix the end of text flag for each word in the dictionary. Some older games
 * are missing the end of text flag on some words. All the words are fixed up
 * so that they can be printed.
 */

#ifdef __STDC__
static void fix_dictionary (void)
#else
static void fix_dictionary ()
#endif
{
    unsigned long address;
    int separator_count, word_size, word_count, i;

    address = header.dictionary;
    separator_count = read_data_byte (&address);
    address += separator_count;
    word_size = read_data_byte (&address);
    word_count = read_data_word (&address);

    for (i = 1; i <= word_count; i++) {

	/* Check that the word is in non-paged memory before writing */

	if ((address + 4) < (unsigned long) header.resident_size)
	    if ((unsigned int) header.version <= V3)
		set_byte (address + 2, (unsigned int) get_byte (address + 2) | 0x80);
	    else
		set_byte (address + 4, (unsigned int) get_byte (address + 4) | 0x80);

	address += word_size;
    }

}/* fix_dictionary */

#ifdef __STDC__
extern void configure_dictionary
    (unsigned int *, unsigned long *, unsigned long *);
extern void configure_abbreviations
    (unsigned int *, unsigned long *, unsigned long *, unsigned long *,
     unsigned long *);
extern void configure_object_tables
    (unsigned int *, unsigned long *, unsigned long *, unsigned long *,
     unsigned long *);
#else
extern void configure_dictionary ();
extern void configure_abbreviations ();
extern void configure_object_tables ();
#endif

#ifdef __STDC__
static int compare_area (const void *, const void *);
#else
static int compare_area ();
#endif

#define MAX_AREA 20

#define set_area(index, base_addr, end_addr, name_string) { \
    if (index == MAX_AREA) {                                \
	fprintf (stderr, "Area space exhausted!\n");        \
	exit (EXIT_FAILURE);                                \
    }                                                       \
    areas[index].base = base_addr;                          \
    areas[index].end = end_addr;                            \
    areas[index].name = name_string;                        \
    index++;                                                \
}

typedef struct area_s {
    unsigned long base;
    unsigned long end;
    const char *name;
} area_t;

/*
 * show_map
 *
 * Show the map of the data area. This is done by calling the configure routine
 * for each area. Each area is then sorted in ascending order and displayed.
 */

#ifdef __STDC__
static void show_map (void)
#else
static void show_map ()
#endif
{
    unsigned int abbr_count;
    unsigned long abbr_table_base, abbr_table_end, abbr_data_base, abbr_data_end;
    unsigned int word_count;
    unsigned long word_table_base, word_table_end;
    unsigned int obj_count;
    unsigned long obj_table_base, obj_table_end, obj_data_base, obj_data_end;
    unsigned int verb_count, action_count, verb_type, prep_type;
    unsigned int parse_count;
    unsigned long verb_table_base, verb_data_base;
    unsigned long action_table_base, preact_table_base;
    unsigned long prep_table_base, prep_table_end;
    unsigned int ext_table_size;
    unsigned long ext_table_base, ext_table_end;
    unsigned long unicode_table_base, unicode_table_end;
    unsigned short inform_version;
    unsigned long class_numbers_base, class_numbers_end;
    unsigned long property_names_base, property_names_end;
    unsigned long attr_names_base, attr_names_end;
    area_t areas[MAX_AREA];
    int i, area;

    /* Configure areas */

    area = 0;

    set_area (area, 0, 63, "Story file header");

    ext_table_base = header.mouse_table;
    if (ext_table_base) {
    	ext_table_size = get_word(ext_table_base);
    	ext_table_end = ext_table_base + 2 + ext_table_size * 2 - 1;
	set_area (area, ext_table_base, ext_table_end, "Header extension table");
	if (ext_table_size > 2) {
	    unicode_table_base = get_word(ext_table_base + 6);
	    if (unicode_table_base) {
		unicode_table_end = unicode_table_base + get_byte(unicode_table_base)*2; 
		set_area (area, unicode_table_base, unicode_table_end, "Unicode table");
	    }
	}
    }
    
    configure_abbreviations (&abbr_count, &abbr_table_base, &abbr_table_end,
			     &abbr_data_base, &abbr_data_end);

    if (abbr_count) {
	set_area (area, abbr_table_base, abbr_table_end, "Abbreviation pointer table");
	set_area (area, abbr_data_base, abbr_data_end, "Abbreviation data");
    }

    configure_dictionary (&word_count, &word_table_base, &word_table_end);

    set_area (area, word_table_base, word_table_end, "Dictionary");

    configure_object_tables (&obj_count, &obj_table_base, &obj_table_end,
			     &obj_data_base, &obj_data_end);

    set_area (area, obj_table_base, obj_table_end, "Object table");
    set_area (area, obj_data_base, obj_data_end, "Property data");

    configure_parse_tables (&verb_count, &action_count, &parse_count, &verb_type, &prep_type,
			    &verb_table_base, &verb_data_base,
			    &action_table_base, &preact_table_base,
			    &prep_table_base, &prep_table_end);

    if ((verb_count > 0) && (verb_type != infocom6_grammar)) {
	set_area (area, verb_table_base, verb_data_base - 1, "Grammar pointer table");
	set_area (area, verb_data_base, action_table_base - 1, "Grammar data");
	set_area (area, action_table_base, preact_table_base - 1, "Action routine table");
	if (verb_type < inform_gv2) {
		set_area (area, preact_table_base, prep_table_base - 1, (verb_type >= inform5_grammar) ? "Parsing routine table" : "Pre-action routine table");
		set_area (area, prep_table_base, prep_table_end, "Preposition table");
	}
    }
    else if (verb_count > 0) {
	set_area (area, verb_table_base, verb_table_base + 8 * verb_count - 1, "Verb grammar table");
	set_area (area, verb_data_base, prep_table_base - 1, "Grammar entries");
	set_area (area, action_table_base, preact_table_base - 1, "Action routine table");
	set_area (area, preact_table_base, preact_table_base + action_count * 2 - 1, "Pre-action routine table");
    }
    
    configure_inform_tables(obj_data_end, &inform_version, &class_numbers_base, &class_numbers_end,
    			    &property_names_base, &property_names_end, &attr_names_base, &attr_names_end);
   
    if (inform_version >= INFORM_6) {
    	set_area(area, class_numbers_base, class_numbers_end, "Class Prototype Object Numbers");
    	set_area(area, property_names_base, property_names_end, "Property Names Table");
	if (inform_version >= INFORM_610) {
    		set_area(area, attr_names_base, attr_names_end, "Attribute Names Table");
	}
    }

    set_area (area, (unsigned long) header.globals,
	      (unsigned long) header.globals + (240 * 2) - 1,
	      "Global variables");

    set_area (area, (unsigned long) header.resident_size,
	      (unsigned long) file_size - 1,
	      "Paged memory");

    if (header.alphabet)
	set_area (area, (unsigned long) header.alphabet,
		  (unsigned long) header.alphabet + (26 * 3) - 1,
		  "Alphabet");

    /* Sort areas */

    qsort (areas, (size_t) area, sizeof (area_t), compare_area);

    /* Print area map */

    tx_printf ("\n    **** Story file map ****\n\n");

    tx_printf (" Base    End   Size\n");
    for (i = 0; i < area; i++) {
	if (i && (areas[i].base - 1) > areas[i - 1].end)
	    tx_printf ("%5lx  %5lx  %5lx\n",
		       (unsigned long) (areas[i - 1].end + 1), (unsigned long) (areas[i].base - 1),
		       (unsigned long) ((areas[i].base - 1) - (areas[i - 1].end + 1) + 1));
	tx_printf ("%5lx  %5lx  %5lx  %s\n",
		   (unsigned long) areas[i].base, (unsigned long) areas[i].end,
		   (unsigned long) (areas[i].end - areas[i].base + 1),
		   areas[i].name);
    }

}/* show_map */

/*
 * compare_area
 *
 * Compare two areas and sort by ascending value
 */

#ifdef __STDC__
static int compare_area (const void *a, const void *b)
#else
static int compare_area (a, b)
const void *a;
const void *b;
#endif
{
    long diff;

    diff = ((const area_t *) a)->base - ((const area_t *) b)->base;

    if (diff < 0)
	return (-1);
    else if (diff > 0)
	return (1);

    return (0);

}/* compare_area */
