

/*
 * fileio.c
 *
 * File manipulation routines. Should be generic.
 *
 */

#include "ztypes.h"

/* Static data */

static FILE *gfp = NULL; /* Game file pointer */
static FILE *sfp = NULL; /* Script file pointer */
static FILE *rfp = NULL; /* Record file pointer */
static char save_name[FILENAME_MAX + 1] = "";
static char script_name[FILENAME_MAX + 1] = "";
static char record_name[FILENAME_MAX + 1] = "";

static int undo_valid = FALSE;
static zword_t undo_stack[STACK_SIZE];

static int script_file_valid = FALSE;

#ifdef __STDC__
static int save_restore (const char *, int, z_globals *);
#else
static int save_restore ();
#endif

/*
 * open_story
 *
 * Open game file for read.
 *
 */

#ifdef __STDC__
void open_story (const char *storyname, z_globals *gl)
#else
void open_story (storyname, gl)
const char *storyname;
z_globals *gl;
#endif
{

    gfp = fopen (storyname, "rb");
    if (gfp == NULL)
        fatal ("Game file not found", gl);

}/* open_story */

/*
 * close_story
 *
 * Close game file if open.
 *
 */

#ifdef __STDC__
void close_story (z_globals *gl)
#else
void close_story (gl)
z_globals *gl;
#endif
{

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

}/* close_story */

/*
 * get_story_size
 *
 * Calculate the size of the game file. Only used for very old games that do not
 * have the game file size in the header.
 *
 */

#ifdef __STDC__
unsigned int get_story_size (z_globals *gl)
#else
unsigned int get_story_size (gl)
z_globals *gl;
#endif
{
    unsigned long file_length;

    /* Read whole file to calculate file size */

    rewind (gfp);
    for (file_length = 0; fgetc (gfp) != EOF; file_length++)
        ;
    rewind (gfp);

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

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

    return ((unsigned int) file_length);

}/* get_story_size */

/*
 * read_page
 *
 * Read one game file page.
 *
 */

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

    /* Seek to start of page */

    fseek (gfp, (long) page * PAGE_SIZE, SEEK_SET);

    /* Read the page */

    if (fread (buffer, PAGE_SIZE, 1, gfp) != 1) {

        /* Read failed. Are we in the last page? */

        file_size = (unsigned long) gl->h_file_size * gl->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) {

            /* Read partial page if this is the last page in the game file */

            fseek (gfp, (long) page * PAGE_SIZE, SEEK_SET);
            if (fread (buffer, offset, 1, gfp) == 1)
                return;
        }
        fatal ("Game file read error", gl);
    }

}/* read_page */

/*
 * verify
 *
 * Verify game ($verify verb). Add all bytes in game file except for bytes in
 * the game file header.
 *
 */

#ifdef __STDC__
void verify (z_globals *gl)
#else
void verify (gl)
z_globals *gl;
#endif
{
    unsigned long file_size;
    unsigned int pages, offset;
    unsigned int start, end, i, j;
    zword_t checksum = 0;
    zbyte_t buffer[PAGE_SIZE];

    /* Print version banner */

    if (gl->h_type < V4) {
        write_string ("ZIP Interpreter ", gl);
        print_number (get_byte (H_INTERPRETER), gl);
        write_string (", Version ", gl);
        write_char (get_byte (H_INTERPRETER_VERSION), gl);
        write_string (".", gl);
        new_line (gl);
    }

    /* Calculate game file dimensions */

    file_size = (unsigned long) gl->h_file_size * gl->story_scaler;
    pages = (unsigned int) ((unsigned long) file_size / PAGE_SIZE);
    offset = (unsigned int) file_size & PAGE_MASK;

    /* Sum all bytes in game file, except header bytes */

    for (i = 0; i <= pages; i++) {
        read_page (i, buffer, gl);
        start = (i == 0) ? 64 : 0;
        end = (i == pages) ? offset : PAGE_SIZE;
        for (j = start; j < end; j++)
            checksum += buffer[j];
    }

    /* Make a conditional jump based on whether the checksum is equal */

    conditional_jump (checksum == gl->h_checksum, gl);

}/* verify */

/*
 * save
 *
 * Save game state to disk. Returns:
 *     0 = save failed
 *     1 = save succeeded
 *
 */

#ifdef __STDC__
int save (z_globals *gl)
#else
int save (gl)
z_globals *gl;
#endif
{
    char new_save_name[FILENAME_MAX + 1];
    int status = 1;

    /* Get the file name */

    if (get_file_name (new_save_name, save_name, GAME_SAVE, gl) == 0) {

        /* Do a save operation */

        if (save_restore (new_save_name, GAME_SAVE, gl) == 0) {

            /* Cleanup file */

            file_cleanup (new_save_name, GAME_SAVE, gl);

            /* Save the new name as the default file name */

            strcpy (save_name, new_save_name);

            /* Indicate success */

            status = 0;

        }

    }

    /* Return result of save to Z-code */

    if (gl->h_type < V4)
        conditional_jump (status == 0, gl);
    else
        store_operand ((status == 0) ? 1 : 0, gl);

    return (status);

}/* save */

/*
 * restore
 *
 * Restore game state from disk. Returns:
 *     0 = restore failed
 *     2 = restore succeeded
 *
 */

#ifdef __STDC__
int restore ( z_globals *gl)
#else
int restore (gl)
z_globals *gl;
#endif
{
    char new_save_name[FILENAME_MAX + 1];
    int status = 1;

    /* Get the file name */

    if (get_file_name (new_save_name, save_name, GAME_RESTORE, gl) == 0) {

        /* Do the restore operation */

        if (save_restore (new_save_name, GAME_RESTORE, gl) == 0) {

            /* Reset the status region (this is just for Seastalker) */

            if (gl->h_type < V4) {
                set_status_size (0, gl);
                blank_status_line (gl);
            }

            /* Cleanup file */

            file_cleanup (new_save_name, GAME_SAVE, gl);

            /* Save the new name as the default file name */

            strcpy (save_name, new_save_name);

            /* Indicate success */

            status = 0;

        }

    }

    /* Return result of save to Z-code */

    if (gl->h_type < V4)
        conditional_jump (status == 0, gl);
    else
        store_operand ((status == 0) ? 2 : 0, gl);

    return (status);

}/* restore */

/*
 * undo_save
 *
 * Save the current Z machine state in memory for a future undo. Returns:
 *    -1 = feature unavailable
 *     0 = save failed
 *     1 = save succeeded
 *
 */

#ifdef __STDC__
void undo_save (z_globals *gl)
#else
void undo_save (gl)
z_globals *gl;
#endif
{

    /* Check if undo is available first */

    if ((gl->undo_datap) != NULL) {

        /* Save the undo data and return success */

        save_restore (NULL, UNDO_SAVE, gl);

        undo_valid = TRUE;

        store_operand (1, gl);

    } else 

        /* If no memory for data area then say undo is not available */

        store_operand ((zword_t) -1, gl);

}/* undo_save */

/*
 * undo_restore
 *
 * Restore the current Z machine state from memory. Returns:
 *    -1 = feature unavailable
 *     0 = restore failed
 *     2 = restore succeeded
 *
 */

#ifdef __STDC__
void undo_restore (z_globals *gl)
#else
void undo_restore (gl)
z_globals *gl;
#endif
{

    /* Check if undo is available first */

    if ((gl->undo_datap) != NULL) {

        /* If no undo save done then return an error */

        if (undo_valid == TRUE) {

            /* Restore the undo data and return success */

            save_restore (NULL, UNDO_RESTORE, gl);

            store_operand (2, gl);

        } else

            store_operand (0, gl);

    } else 

        /* If no memory for data area then say undo is not available */

        store_operand ((zword_t) -1, gl);

}/* undo_restore */

/*
 * save_restore
 *
 * Common save and restore code. Just save or restore the game stack and the
 * writeable data area.
 *
 */

#ifdef __STDC__
static int save_restore (const char *file_name, int flag, z_globals *gl)
#else
static int save_restore (file_name, flag, gl)
const char *file_name;
int flag;
z_globals *gl;
#endif
{
    FILE *tfp = NULL;
    int scripting_flag = 0, status = 0;

    /* Open the save file and disable scripting */

    if (flag == GAME_SAVE || flag == GAME_RESTORE) {
        if ((tfp = fopen (file_name, (flag == GAME_SAVE) ? "wb" : "rb")) == NULL) {
            output_line ("Cannot open SAVE file", gl);
            return (1);
        }
        scripting_flag = get_word (H_FLAGS) & SCRIPTING_FLAG;
        set_word (H_FLAGS, get_word (H_FLAGS) & (~SCRIPTING_FLAG));
    }

    /* Push PC, FP, version and store SP in special location */

    gl->stack[--(gl->sp)] = (zword_t) ((gl->pc) / PAGE_SIZE);
    gl->stack[--(gl->sp)] = (zword_t) ((gl->pc) % PAGE_SIZE);
    gl->stack[--(gl->sp)] = (gl->fp);
    gl->stack[--(gl->sp)] = gl->h_version;
    gl->stack[0] = (gl->sp);

    /* Save or restore stack */

    if (flag == GAME_SAVE) {
        if (status == 0 && fwrite (gl->stack, sizeof (gl->stack), 1, tfp) != 1)
            status = 1;
    } else if (flag == GAME_RESTORE) {
        if (status == 0 && fread (gl->stack, sizeof (gl->stack), 1, tfp) != 1)
            status = 1;
    } else if (flag == UNDO_SAVE) {
        memmove (undo_stack, gl->stack, sizeof (gl->stack));
    } else /* if (flag == UNDO_RESTORE) */
        memmove (gl->stack, undo_stack, sizeof (gl->stack));

    /* Restore SP, check version, restore FP and PC */

    (gl->sp) = gl->stack[0];
    if (gl->stack[(gl->sp)++] != gl->h_version)
        fatal ("Wrong game or version", gl);
    (gl->fp) = gl->stack[(gl->sp)++];
    (gl->pc) = gl->stack[(gl->sp)++];
    (gl->pc) += (unsigned long) gl->stack[(gl->sp)++] * PAGE_SIZE;

    /* Save or restore writeable game data area */

    if (flag == GAME_SAVE) {
        if (status == 0 && fwrite ((gl->datap), gl->h_restart_size, 1, tfp) != 1)
            status = 1;
    } else if (flag == GAME_RESTORE) {
        if (status == 0 && fread ((gl->datap), gl->h_restart_size, 1, tfp) != 1)
            status = 1;
    } else if (flag == UNDO_SAVE) {
        memmove ((gl->undo_datap), (gl->datap), gl->h_restart_size);
    } else /* if (flag == UNDO_RESTORE) */
        memmove ((gl->datap), (gl->undo_datap), gl->h_restart_size);

    /* Close the save file and restore scripting */

    if (flag == GAME_SAVE || flag == GAME_RESTORE) {
        fclose (tfp);
        if (scripting_flag)
            set_word (H_FLAGS, get_word (H_FLAGS) | SCRIPTING_FLAG);
    }

    /* Handle read or write errors */

    if (status) {
        if (flag == GAME_SAVE) {
            output_line ("Write to SAVE file failed", gl);
            remove (file_name);
        } else {
            output_line ("Read from SAVE file failed", gl);
        }
    }

    return (status);

}/* save_restore */

/*
 * open_script
 *
 * Open the scripting file.
 *
 */

#ifdef __STDC__
void open_script (z_globals *gl)
#else
void open_script (gl)
z_globals *gl;
#endif
{
    char new_script_name[FILENAME_MAX + 1];

    /* Open scripting file if closed */

    if ((gl->scripting) == OFF) {

        if (script_file_valid == TRUE) {

            sfp = fopen (script_name, "a");

            /* Turn on scripting if open succeeded */

            if (sfp != NULL)
                (gl->scripting) = ON;
            else
                output_line ("Script file open failed", gl);

        } else {

            /* Get scripting file name and record it */

            if (get_file_name (new_script_name, script_name, GAME_SCRIPT, gl) == 0) {

                /* Open scripting file */

                sfp = fopen (new_script_name, "w");

                /* Turn on scripting if open succeeded */

                if (sfp != NULL) {

                    script_file_valid = TRUE;

                    /* Make file name the default name */

                    strcpy (script_name, new_script_name);

                    /* Turn on scripting */

                    (gl->scripting) = ON;
            
                } else

                    output_line ("Script file create failed", gl);

            }

        }

    }

    /* Set the scripting flag in the game file flags */

    if ((gl->scripting) == ON)
        set_word (H_FLAGS, get_word (H_FLAGS) | SCRIPTING_FLAG);
    else
        set_word (H_FLAGS, get_word (H_FLAGS) & (~SCRIPTING_FLAG));

}/* open_script */

/*
 * close_script
 *
 * Close the scripting file.
 *
 */

#ifdef __STDC__
void close_script (z_globals *gl)
#else
void close_script (gl)
z_globals *gl;
#endif
{

    /* Close scripting file if open */

    if ((gl->scripting) == ON) {

        fclose (sfp);
#if 0
        /* Cleanup */

        file_cleanup (script_name, GAME_SCRIPT);
#endif
        /* Turn off scripting */

        (gl->scripting) = OFF;

    }

    /* Set the scripting flag in the game file flags */

    if ((gl->scripting) == OFF)
        set_word (H_FLAGS, get_word (H_FLAGS) & (~SCRIPTING_FLAG));
    else
        set_word (H_FLAGS, get_word (H_FLAGS) | SCRIPTING_FLAG);

}/* close_script */

/*
 * script_char
 *
 * Write one character to scripting file.
 *
 * Check the state of the scripting flag first. Older games only set the
 * scripting flag in the game flags instead of calling the set_print_modes
 * function. This is because they expect a physically attached printer that
 * doesn't need opening like a file.
 */

#ifdef __STDC__
void script_char (int c, z_globals *gl)
#else
void script_char (c, gl)
int c;
z_globals *gl;
#endif
{

    /* Check the state of the scripting flag in the game flags. If it is on
       then check to see if the scripting file is open as well */

    if ((get_word (H_FLAGS) & SCRIPTING_FLAG) != 0 && (gl->scripting) == OFF)
        open_script (gl);

    /* Check the state of the scripting flag in the game flags. If it is off
       then check to see if the scripting file is closed as well */

    if ((get_word (H_FLAGS) & SCRIPTING_FLAG) == 0 && (gl->scripting) == ON)
        close_script (gl);

    /* If scripting file is open, we are in the text window and the character is
       printable then write the character */

    if ((gl->scripting) == ON && (gl->scripting_disable) == OFF && (c == '\n' || (isprint (c))))
        putc (c, sfp);

}/* script_char */

/*
 * script_string
 *
 * Write a string to the scripting file.
 *
 */

#ifdef __STDC__
void script_string (const char *s, z_globals *gl)
#else
void script_string (s, gl)
const char *s;
z_globals *gl;
#endif
{

    /* Write string */

    while (*s)
        script_char (*s++, gl);

}/* script_string */

/*
 * script_line
 *
 * Write a string followed by a new line to the scripting file.
 *
 */

#ifdef __STDC__
void script_line (const char *s, z_globals *gl)
#else
void script_line (s, gl)
const char *s;
z_globals *gl;
#endif
{

    /* Write string */

    script_string (s, gl);

    /* Write new line */

    script_new_line (gl);

}/* script_line */

/*
 * script_new_line
 *
 * Write a new line to the scripting file.
 *
 */

#ifdef __STDC__
void script_new_line (z_globals *gl)
#else
void script_new_line (gl)
z_globals *gl;
#endif
{

    script_char ('\n', gl);

}/* script_new_line */

/*
 * open_record
 *
 * Turn on recording of all input to an output file.
 *
 */

#ifdef __STDC__
void open_record (z_globals *gl)
#else
void open_record (gl)
z_globals *gl;
#endif
{
    char new_record_name[FILENAME_MAX + 1];

    /* If recording or playback is already on then complain */

    if ((gl->recording) == ON || (gl->replaying) == ON) {

        output_line ("Recording or playback are already active.", gl);

    } else {

        /* Get recording file name */

        if (get_file_name (new_record_name, record_name, GAME_RECORD, gl) == 0) {

            /* Open recording file */

            rfp = fopen (new_record_name, "w");

            /* Turn on recording if open succeeded */

            if (rfp != NULL) {

                /* Make file name the default name */

                strcpy (record_name, new_record_name);

                /* Set recording on */

                (gl->recording) = ON;

            } else

                output_line ("Record file create failed", gl);

        }

    }

}/* open_record */

/*
 * record_line
 *
 * Write a string followed by a new line to the recording file.
 *
 */

#ifdef __STDC__
void record_line (const char *s, z_globals *gl)
#else
void record_line (s, gl)
const char *s;
z_globals *gl;
#endif
{

    if ((gl->recording) == ON && (gl->replaying) == OFF) {

        /* Write string */

        fprintf (rfp, "%s\n", s);

    }

}/* record_line */

/*
 * record_key
 *
 * Write a key followed by a new line to the recording file.
 *
 */

#ifdef __STDC__
void record_key (int c, z_globals *gl)
#else
void record_key (c, gl)
int c;
z_globals *gl;
#endif
{

    if ((gl->recording) == ON && (gl->replaying) == OFF) {

        /* Write the key */

        fprintf (rfp, "<%0o>\n", c);

    }

}/* record_key */

/*
 * close_record
 *
 * Turn off recording of all input to an output file.
 *
 */

#ifdef __STDC__
void close_record (z_globals *gl)
#else
void close_record (gl)
z_globals *gl;
#endif
{

    /* Close recording file */

    if (rfp != NULL) {
        fclose (rfp);
        rfp = NULL;

        /* Cleanup */

        if ((gl->recording) == ON)
            file_cleanup (record_name, GAME_RECORD, gl);
        else /* ((gl->replaying) == ON) */
            file_cleanup (record_name, GAME_PLAYBACK, gl);
    }

    /* Set recording and replaying off */

    (gl->recording) = OFF;
    (gl->replaying) = OFF;

}/* close_record */

/*
 * open_playback
 *
 * Take input from command file instead of keyboard.
 *
 */

#ifdef __STDC__
void open_playback (int arg, z_globals *gl)
#else
void open_playback (arg, gl)
int arg;
z_globals *gl;
#endif
{
    char new_record_name[FILENAME_MAX + 1];

    /* If recording or replaying is already on then complain */

    if ((gl->recording) == ON || (gl->replaying) == ON) {

        output_line ("Recording or replaying is already active.", gl);

    } else {

        /* Get recording file name */

        if (get_file_name (new_record_name, record_name, GAME_PLAYBACK, gl) == 0) {

            /* Open recording file */

            rfp = fopen (new_record_name, "r");

            /* Turn on recording if open succeeded */

            if (rfp != NULL) {

                /* Make file name the default name */

                strcpy (record_name, new_record_name);

                /* Set replaying on */

                (gl->replaying) = ON;

            } else

                output_line ("Record file open failed", gl);

        }

    }

}/* open_playback */

/*
 * playback_line
 *
 * Get a line of input from the command file.
 *
 */

#ifdef __STDC__
int playback_line (int buflen, char *buffer, int *read_size, z_globals *gl)
#else
int playback_line (buflen, buffer, read_size, gl)
int buflen;
char *buffer;
int *read_size;
z_globals *gl;
#endif
{
    char *cp;

    if ((gl->recording) == ON || (gl->replaying) == OFF)
        return (-1);

    if (fgets (buffer, buflen, rfp) == NULL) {
        close_record (gl);
        return (-1);
    } else {
        cp = strrchr (buffer, '\n');
        if (cp != NULL)
            *cp = '\0';
        *read_size = strlen (buffer);
        output_line (buffer, gl);
    }

    return ('\n');

}/* playback_line */

/*
 * playback_key
 *
 * Get a key from the command file.
 *
 */

#ifdef __STDC__
int playback_key (z_globals *gl)
#else
int playback_key (gl)
z_globals *gl;
#endif
{
    int c;

    if ((gl->recording) == ON || (gl->replaying) == OFF)
        return (-1);

    if (fscanf (rfp, "<%o>\n", &c) == EOF) {
        close_record (gl);
        c = -1;
    }

    return (c);

}/* playback_key */




