/* file.c
 *
 *  ``pinfocom'' -- a portable Infocom Inc. data file interpreter.
 *  Copyright (C) 1987-1992  InfoTaskForce
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; see the file COPYING.  If not, write to the
 *  Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * $Header: RCS/file.c,v 3.0 1992/10/21 16:56:19 pds Stab $
 */

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include "infocom.h"

#ifdef NEED_ERRNO
extern int errno;
#endif

static const char *fname_lst[] = {FNAME_LST,0};
static const char *fext_lst[] = {"",FEXT_LST,0};

static FILE *game_file;
static char gname[MAXPATHLEN + 1];

char sname[MAXPATHLEN + 1];

#ifdef SCRIPT_FILE
char script_fn[MAXPATHLEN+1] = SCRIPT_FILE;
#else
char script_fn[MAXPATHLEN+1];
#endif


void
f_error A3(int, erno, const char *, err, const char *, arg1)
{
    char buf[256];
    char *bp;

    sprintf(buf, err, arg1);
    bp = buf + strlen(buf);

    if (erno)
    {
#ifndef NO_STRERROR
        extern char *strerror P((int));
        char *cp = strerror(erno);

        sprintf(bp, ": %o: %s", erno, cp == NULL ? "<unknown>" : cp);
#else
        sprintf(bp, ": %o", erno);
#endif
    }

    if (gflags.game_state == NOT_INIT)
    {
        fputs(buf, stderr);
        putc('\n', stderr);
        putc('\n', stderr);
    }
    else
    {
        scr_putmesg(buf, 1);
    }
}

static void
assign A2(header_t*, head, const header_t*, buffer)
{
    const byte  *ptr;
    int         i;

    /*
     * Process the raw header data in "buffer" and put
     * it into the appropriate fields in "head". This
     * processing is required because of the way different
     * machines internally represent 'words'.
     */
    ptr = (const byte *)buffer;

    head->z_version       = Z_TO_BYTE_I(ptr);
    head->flags_1         = Z_TO_BYTE_I(ptr);
    head->release         = Z_TO_WORD_I(ptr);
    head->resident_bytes  = Z_TO_WORD_I(ptr);
    head->game_o          = Z_TO_WORD_I(ptr);
    head->vocab_o         = Z_TO_WORD_I(ptr);
    head->object_o        = Z_TO_WORD_I(ptr);
    head->variable_o      = Z_TO_WORD_I(ptr);
    head->save_bytes      = Z_TO_WORD_I(ptr);
    head->flags_2         = Z_TO_WORD_I(ptr);
    for (i = 0; i < 6; ++i)
        head->serial_no[i] = Z_TO_BYTE_I(ptr);
    head->common_word_o   = Z_TO_WORD_I(ptr);
    head->verify_length   = Z_TO_WORD_I(ptr);
    head->verify_checksum = Z_TO_WORD_I(ptr);
    for (i = 0; i < 8; ++i)
        head->padding1[i] = Z_TO_WORD_I(ptr);
    head->fkey_o          = Z_TO_WORD_I(ptr);
    for (i = 0; i < 2; ++i)
        head->padding2[i] = Z_TO_WORD_I(ptr);
    head->alphabet_o      = Z_TO_WORD_I(ptr);
    for (i = 0; i < 5; ++i)
        head->padding3[i] = Z_TO_WORD_I(ptr);
}

/*
 * Function:    read_header()
 *
 * Description:
 *      This function reads in the game data file's header info.  We
 *      only do this between scr_setup() and scr_begin(), so just use
 *      normal printf()'s if we find an error, and just exit if we
 *      can't continue.
 *
 * Notes:
 *      This routine does not read the data-file header directly into
 *      a header structure because certain machines like the VAX
 *      11/780 store integers in a different way to machines based on
 *      processors like the 68000 (a 68000 stores the high byte first,
 *      while a VAX stores the low byte first).  Consequently, if the
 *      header is read directly into a structure, the integer values
 *      are interpreted differently by the two machines.
 */
void
read_header A1(header_t*, head)
{
    extern void exit P((int));
    header_t  buffer;

    if (fseek(game_file, 0L, 0) < 0)
    {
        f_error(errno, "Failed to seek to beginning of file `%s'", gname);
        exit(1);
    }

    if (fread(&buffer, sizeof(header_t), 1, game_file) != 1)
    {
        f_error(errno, "Failed to read header of `%s'", gname);
        exit(1);
    }

    assign(head, &buffer);

    if (head->flags_1 & 0x01)
    {
        f_error(0, "Invalid header in file `%s'", gname);
        exit(1);
    }
}

/*
 * Open a file for reading; if the parameter is NULL then look through
 * the name,extension list pairs to try to find one there.  Return a
 * pointer to it, whatever it is.
 */
const char *
open_file A1(const char*, filename)
{
    const char *fn = gname;
    char *endp = gname;

    if (filename != NULL)
    {
        const char **exp;

        strcpy(gname, filename);
        endp = &gname[strlen(gname)];

        if ((game_file = fopen(gname, "rb")) != NULL)
        {
            int len;

            for (exp=fext_lst; *exp != NULL; ++exp)
            {
                if (((len = strlen(*exp)) != 0) && !strcmp(endp - len, *exp))
                    break;
            }

            if (*exp != NULL)
                endp -= len;

            goto done;
        }

        for (exp = fext_lst; *exp != NULL; ++exp)
        {
            strcpy(endp, *exp);
            if ((game_file = fopen(gname, "rb")) != NULL)
                goto done;
        }

        f_error(errno, "Cannot open file `%s'", filename);
        fn = NULL;
    }
    else
    {
        const char **nmp;

        for (nmp = fname_lst; *nmp != NULL; ++nmp)
        {
            const char **exp;

            strcpy(gname, *nmp);
            endp = &gname[strlen(gname)];

            for (exp = fext_lst; *exp != NULL; ++exp)
            {
                strcpy(endp, *exp);
                if ((game_file = fopen(gname, "rb")) != NULL)
                    goto done;
            }
        }

        f_error(0, "Could not find a game to play!", NULL);
        fn = NULL;
    }

 done:
    *endp = '\0';

    strcpy(sname, gname);
#ifdef SAVE_EXT
    strcat(sname, SAVE_EXT);
#endif
#ifdef SCRIPT_EXT
    sprintf(script_fn, "%s%s", gname, SCRIPT_EXT);
#endif

    return (fn);
}

void
close_file()
{
    if (fclose(game_file))
        scr_putline("Cannot Close Game File");
}

void
load_page A3(word, block, word, num_blocks, byte*, ptr)
{
    extern file_t   file_info;

    long found;
    long offset;
    long num_bytes;

    /*
     * Read "num_block" blocks from Game File, starting with block
     * "block", into the location pointed to by "ptr".
     */
    offset = (long)block * BLOCK_SIZE;
    num_bytes = (long)num_blocks * BLOCK_SIZE;

    if (fseek(game_file, offset, 0) < 0)
    {
        f_error(errno, "Failed to seek to required offset in `%s'", gname);
        quit();
    }
    else if ((found = fread(ptr, 1, num_bytes, game_file)) < num_bytes)
    {
        /*
         * Check if this is the last block: some games (notably
         * MS-DOS) don't have the full last block on the disk.  If
         * this isn't the last block, print an error.  Otherwise, zero
         * out the rest of the page.
         */
        if ((found / BLOCK_SIZE != num_blocks - 1)
            || (block + num_blocks - 1 != file_info.pages))
        {
            f_error(errno, "Failed to load required blocks in `%s'", gname);
            quit();
        }

        for (ptr += found; found < num_bytes; ++found, ++ptr)
            *ptr = '\0';
    }
}

void
save()
{
    extern byte     *base_ptr;
    extern word     save_blocks;
    extern byte     *end_res_p;
    extern word     *stack;
    extern word     *stack_base;
    extern word     *stack_var_ptr;
    extern word     pc_page;
    extern word     pc_offset;

    FILE    *fp;
    int     ret = 0;

    /*
     * We save the the program counter, the stack offset, the
     * stack_var offset, the stack itself, and finally the resident
     * impure storage.  This overwrites the lowest 8 bytes of the
     * stack; hopefully those aren't being used...
     */
    if ((fp = scr_open_sf(MAXPATHLEN+1, (char *)sname, SF_SAVE)) == NULL)
    {
        if (errno != 0)
            f_error(errno, "Cannot open save file `%s'", sname);
    }
    else
    {
        word *sp;

        sp = (word *)end_res_p;
        *(sp++) = pc_page;
        *(sp++) = pc_offset;
        *(sp++) = stack_base - stack;
        *sp     = stack_base - stack_var_ptr;

        if ((fwrite(end_res_p, sizeof(byte), STACK_SIZE, fp) != STACK_SIZE)
            || (fwrite(base_ptr, BLOCK_SIZE, save_blocks, fp) != save_blocks))
        {
            f_error(errno, "Cannot write save file `%s'", sname);
            fclose(fp);
        }
        else
        {
            scr_close_sf(sname, fp, SF_SAVE);
            ret = 1;
        }
    }

    ret_value(ret);
}

/*
 * Check to see if a restored game is the same as our current game:
 * note that everything except the serial number, verify length, and
 * verify checksum must be identical.  If they are copy over the new
 * verify length and checksum so $verify will still work...
 */
static Bool
check A1(header_t*, info)
{
#define CMP(_1,_2,_f)   ((_1)->_f == (_2)->_f)

    extern header_t   data_head;

    Bool good = 0;

    if (CMP(info, &data_head, z_version)
        && CMP(info, &data_head, release)
        && CMP(info, &data_head, resident_bytes)
        && CMP(info, &data_head, game_o)
        && CMP(info, &data_head, vocab_o)
        && CMP(info, &data_head, object_o)
        && CMP(info, &data_head, variable_o)
        && CMP(info, &data_head, save_bytes)
        && CMP(info, &data_head, common_word_o)
        && CMP(info, &data_head, fkey_o)
        && CMP(info, &data_head, alphabet_o))
    {
        data_head.flags_1 = info->flags_1;
        data_head.verify_length = info->verify_length;
        data_head.verify_checksum = info->verify_checksum;

        good = 1;
    }

    return (good);
}

void
restore()
{
    extern byte     *base_ptr;
    extern word     save_blocks;
    extern byte     *end_res_p;
    extern word     *stack;
    extern word     *stack_base;
    extern word     *stack_var_ptr;
    extern word     pc_page;
    extern word     pc_offset;

    FILE    *fp;
    int     ret = 0;
    int     len = MAXPATHLEN+1;

    errno = 0;

    /*
     * If we're restoring a game specified on the command line, print
     * a message about it...
     */
    if (gflags.game_state != PLAY_GAME)
    {
        char buf[MAXPATHLEN+30];

        sprintf(buf, "Restoring saved game `%s' ...", sname);
        if (gflags.game_state == NOT_INIT)
            puts(buf);
        else
            scr_putline(buf);
        len = 0;
    }

    /*
     * Ask the user for a saved game filename to restore and open it;
     * if that fails then print an error.
     */
    if ((fp = scr_open_sf(len, (char *)sname, SF_RESTORE)) == NULL)
    {
        if (errno != 0)
            f_error(errno, "Opening saved file ", sname);
        ret = -1 + (gflags.game_state == INIT_GAME);
    }
    /*
     * From here on we're destroying the current game data, so if the
     * restore fails here we have no choice but to restart the game
     * from scratch.  Keep the scripting bit set correctly tho...
     */
    else
    {
        Bool ok;
        Bool scripting;

        scripting = F2_IS_SET(B_SCRIPTING);

        ok = ((fread(end_res_p, sizeof(byte), STACK_SIZE, fp)==STACK_SIZE)
              && (fread(base_ptr, BLOCK_SIZE, save_blocks, fp)==save_blocks));

        if (scripting)
            F2_SETB(B_SCRIPTING);
        else
            F2_RESETB(B_SCRIPTING);

        if (!ok)
            f_error(errno, "Error reading saved game file `%s'", sname);
        else
        {
            header_t    test;

            assign(&test, (header_t *)base_ptr);
            if (!check(&test))
                f_error(0, "Invalid saved game file `%s'", sname);
            else
            {
                word        *sp;

                sp = (word *)end_res_p;
                pc_page         = *(sp++);
                pc_offset       = *(sp++);
                stack           = stack_base - *(sp++);
                stack_var_ptr   = stack_base - *sp;
                fix_pc();
                ret = 1;
            }
        }

        if (ret == 1)
            scr_close_sf(sname, fp, SF_RESTORE);
        else
            fclose(fp);
    }

    if (gflags.game_state != NOT_INIT)
    {
        if (!ret)
        {
            strcpy(sname, gflags.filenm);
#ifdef SAVE_EXT
            strcat(sname, SAVE_EXT);
#endif
            scr_putline("Restarting...");
            restart();
        }
        else
            ret_value(ret > 0);
    }
}