/* txio.c V1.1
 *
 * I/O routines for Z code disassembler and story file dumper.
 *
 * Mark Howell 26 August 1992 howell_ma@movies.enet.dec.com
 *
 * History:
 *     Add support for old type 3 data files with no file size in header
 */

#include "tx.h"

zbyte_t h_type;
zword_t h_version;
zword_t h_data_size;
zword_t h_start_pc;
zword_t h_words_offset;
zword_t h_objects_offset;
zword_t h_globals_offset;
zword_t h_restart_size;
zword_t h_synonyms_offset;
zword_t h_file_size;
zword_t h_checksum;
zword_t h_function_keys_offset;
zword_t h_alternate_alphabet_offset;

int story_scaler;
int story_shift;
int property_mask;
int property_size_mask;

zbyte_t *datap;

const char *lookup_table[3] = {
    "abcdefghijklmnopqrstuvwxyz",
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
    "  0123456789.,!?_#'\"/\\-:()"
};

typedef struct cache_entry {
    struct cache_entry *flink;
    unsigned int page_number;
    zbyte_t data[PAGE_SIZE];
} cache_entry_t;

#ifdef __STDC__
static cache_entry_t *update_cache (unsigned int page_number);
static int get_story_size (void);
#else
static cache_entry_t *update_cache ();
static int get_story_size ();
#endif

static FILE *gfp = NULL;

static unsigned int cache_pages = 0;
static cache_entry_t *cache = NULL;

static unsigned int current_data_page = 0;
static cache_entry_t *current_data_cachep = NULL;

static unsigned int data_size;

#ifdef __STDC__
void configure (int min_version, int max_version)
#else
void configure (min_version, max_version)
int min_version;
int max_version;
#endif
{
    zbyte_t buffer[PAGE_SIZE];

    assert (sizeof (zheader_t) <= PAGE_SIZE);

    read_page (0, buffer);
    datap = buffer;

    h_type = get_byte (H_TYPE);

    if (h_type < min_version || h_type > max_version || (get_byte (H_CONFIG) & CONFIG_BYTE_SWAPPED)) {
        fprintf (stderr, "\nFatal: wrong game or version\n");
        exit (EXIT_FAILURE);
    }

    if (h_type == V3) {
        story_scaler = 2;
        story_shift = 1;
        property_mask = P3_MAX_PROPERTIES - 1;
        property_size_mask = 0xe0;
    } else if (h_type == V4 || h_type == V5) {
        story_scaler = 4;
        story_shift = 2;
        property_mask = P4_MAX_PROPERTIES - 1;
        property_size_mask = 0x3f;
    } else {
        story_scaler = 8;
        story_shift = 3;
        property_mask = P4_MAX_PROPERTIES - 1;
        property_size_mask = 0x3f;
    }

    h_version = get_word (H_VERSION);
    h_data_size = get_word (H_DATA_SIZE);
    h_start_pc = get_word (H_START_PC);
    h_words_offset = get_word (H_WORDS_OFFSET);
    h_objects_offset = get_word (H_OBJECTS_OFFSET);
    h_globals_offset = get_word (H_GLOBALS_OFFSET);
    h_restart_size = get_word (H_RESTART_SIZE);
    h_synonyms_offset = get_word (H_SYNONYMS_OFFSET);
    h_file_size = get_word (H_FILE_SIZE);
    if (h_file_size == 0)
        h_file_size = get_story_size ();
    h_checksum = get_word (H_CHECKSUM);
    h_function_keys_offset = get_word (H_FUNCTION_KEYS_OFFSET);
    h_alternate_alphabet_offset = get_word (H_ALTERNATE_ALPHABET_OFFSET);

}/* configure */

#ifdef __STDC__
void open_story (char *storyname)
#else
void open_story (storyname)
char *storyname;
#endif
{

    gfp = fopen (storyname, "rb");
    if (gfp == NULL) {
        fprintf (stderr, "Fatal: game file not found");
        exit (EXIT_FAILURE);
    }

}/* open_story */

#ifdef __STDC__
void close_story (void)
#else
void close_story ()
#endif
{

    if (gfp != NULL)
        fclose (gfp);

}/* close_story */

#ifdef __STDC__
void read_page (unsigned int page, void *buffer)
#else
void read_page (page, buffer)
unsigned int page;
zbyte_t *buffer;
#endif
{
    unsigned long file_size;
    unsigned int pages, offset;

    fseek (gfp, (long) page * PAGE_SIZE, SEEK_SET);
    if (fread (buffer, PAGE_SIZE, 1, gfp) != 1) {
        file_size = (unsigned long) h_file_size * story_scaler;
        pages = (unsigned int) ((unsigned long) file_size / PAGE_SIZE);
        offset = (unsigned int) ((unsigned long) file_size & PAGE_MASK);
        if ((unsigned int) page == pages) {
            fseek (gfp, (long) page * PAGE_SIZE, SEEK_SET);
            if (fread (buffer, offset, 1, gfp) == 1)
                return;
        }
        fprintf (stderr, "Fatal: game file read error");
        exit (EXIT_FAILURE);
    }

}/* read_page */

#ifdef __STDC__
void load_cache (void)
#else
void load_cache ()
#endif
{
    unsigned int file_pages, data_pages;
    unsigned int i;
    cache_entry_t *cachep, *lastp = NULL;

    if (h_type == V3) {
        story_scaler = 2;
        story_shift = 1;
    } else {
        story_scaler = 4;
        story_shift = 2;
    }

    file_pages = (h_file_size >> (PAGE_SHIFT - story_shift)) + 1;
    data_pages = (h_data_size + PAGE_MASK) >> PAGE_SHIFT;
    cache_pages = file_pages - data_pages;

    data_size = data_pages * PAGE_SIZE;
    datap = (zbyte_t *) malloc (data_size);
    if (datap == NULL) {
        fprintf (stderr, "Fatal: insufficient memory to play game");
        exit (EXIT_FAILURE);
    }
    for (i = 0; i < data_pages; i++)
        read_page (i, &datap[i * PAGE_SIZE]);

    for (i = 0; i < cache_pages; i++) {
        cachep = (cache_entry_t *) malloc (sizeof (cache_entry_t));
        if (cachep == NULL) {
            cache_pages = i;
            i = 512 + 1;
        } else {
            if (i == 0)
                cache = cachep;
            else
                lastp->flink = cachep;
            cachep->flink = NULL;
            cachep->page_number = data_pages + i;
            lastp = cachep;
            read_page (cachep->page_number, cachep->data);
        }
    }

}/* load_cache */

#ifdef __STDC__
zword_t read_data_word (unsigned long *addr)
#else
zword_t read_data_word (addr)
unsigned long *addr;
#endif
{
    zword_t w;

    w = (zword_t) read_data_byte (addr) << 8;
    w |= (zword_t) read_data_byte (addr);

    return (w);

}/* read_data_word */

#ifdef __STDC__
zbyte_t read_data_byte (unsigned long *addr)
#else
zbyte_t read_data_byte (addr)
unsigned long *addr;
#endif
{
    unsigned int page_number, page_offset;
    zbyte_t value;

    if (*addr < (unsigned long) data_size)
        value = datap[*addr];
    else {
        page_number = (int) (*addr >> PAGE_SHIFT);
        page_offset = (int) *addr & PAGE_MASK;
        if (page_number != current_data_page) {
            current_data_cachep = update_cache (page_number);
        current_data_page = page_number;
        }
        value = current_data_cachep->data[page_offset];
    }
    (*addr)++;

    return (value);

}/* read_data_byte */

#ifdef __STDC__
int decode_text (unsigned long *address)
#else
int decode_text (address)
unsigned long *address;
#endif
{
    int i, synonym_flag = 0, ascii_flag = 0, char_count = 0;
    short data, code, sindex = 0;
    short prefix = 0, saved_prefix = 0;
    unsigned long addr;

    do {
        data = read_data_word (address);
        for (i = 10; i >= 0; i -= 5) {
            code = (data >> i) & 0x1f;
            if (synonym_flag == TRUE) {
                synonym_flag = FALSE;
                addr = (unsigned long) get_word (h_synonyms_offset + sindex + (code * 2)) * 2;
                char_count += decode_text (&addr);
                prefix = saved_prefix;
            } else {
                if (prefix == 3 || ascii_flag == TRUE) {
                    if (ascii_flag == TRUE) {
                        ascii_flag = FALSE;
                        putchar (((prefix & 3) << 5) | code);
                        char_count++;
                        prefix = saved_prefix;
                    } else {
                        ascii_flag = TRUE;
                        prefix = code;
                    }
                } else {
                    if (code >= 6) {
                        if (prefix == 2 && code <= 7) {
                            if (code != 7)
                                prefix++;
                            else {
                                putchar ('\n');
                                char_count++;
                                prefix = saved_prefix;
                            }
                        } else {
                            putchar (lookup_table[prefix][code - 6]);
                            char_count++;
                            prefix = saved_prefix;
                        }
                    } else {
                        if (code == 0) {
                            putchar (' ');
                            char_count++;
                            prefix = saved_prefix;
                        } else {
                            if (code <= 3) {
                                synonym_flag = TRUE;
                                sindex = (code - 1) * 64;
                            } else {
                                code -= 3;
                                if (prefix == 0)
                                    prefix = code;
                                else {
                                    if (code != prefix)
                                        prefix = 0;
                                    saved_prefix = prefix;
                                }
                            }
                        }
                    }
                }
            }
        }
    } while (data >= 0);

    return (char_count);

}/* decode_text */

#ifdef __STDC__
static cache_entry_t *update_cache (unsigned int page_number)
#else
static cache_entry_t *update_cache (page_number)
unsigned int page_number;
#endif
{
    cache_entry_t *cachep, *lastp;

    for (lastp = cache, cachep = cache;
         cachep->flink != NULL &&
         cachep->page_number &&
         cachep->page_number != page_number;
         lastp = cachep, cachep = cachep->flink)
        ;
    if (cachep->page_number != page_number) {
        if (cachep->flink == NULL && cachep->page_number) {
            if (current_data_page == (unsigned int) cachep->page_number)
                current_data_page = 0;
        }
        cachep->page_number = page_number;
        read_page (page_number, cachep->data);
    }
    if (lastp != cache) {
        lastp->flink = cachep->flink;
        cachep->flink = cache;
        cache = cachep;
    }

    return (cachep);

}/* update_cache */

#ifdef __STDC__
static int get_story_size (void)
#else
static int get_story_size ()
#endif
{
    unsigned long file_length;

    /* Seek to end of file to calculate length */

    fseek (gfp, 0L, SEEK_END);
    file_length = (unsigned long) ftell (gfp);
    rewind (gfp);

    /* Calculate length of file in game allocation units */

    file_length = (file_length + (unsigned long) (story_scaler - 1)) / (unsigned long) story_scaler;

    return ((int) file_length);

}/* get_story_size */
