/*
 *  fileio.c
 *
 *  Load a normal file, or ZIP/GZ archive into ROM buffer.
 *  Returns loaded ROM size (zero if an error occured).
 *  
 *  Copyright Eke-Eke (2007-2014), based on original work from Softdev (2006)
 *
 *  Redistribution and use of this code or any derivative works are permitted
 *  provided that the following conditions are met:
 *
 *   - Redistributions may not be sold, nor may they be used in a commercial
 *     product or activity.
 *
 *   - Redistributions that are modified from the original source must include the
 *     complete source code, including the source code for all components used by a
 *     binary built from the modified sources. However, as a special exception, the
 *     source code distributed need not include anything that is normally distributed
 *     (in either source or binary form) with the major components (compiler, kernel,
 *     and so on) of the operating system on which the executable runs, unless that
 *     component itself accompanies the executable.
 *
 *   - Redistributions must reproduce the above copyright notice, this list of
 *     conditions and the following disclaimer in the documentation and/or other
 *     materials provided with the distribution.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************************/

#include "shared.h"
#include "file_load.h"
#include "gui.h"

/*
 * Zip file header definition
 */
typedef struct
{
  unsigned int zipid __attribute__ ((__packed__));  // 0x04034b50
  unsigned short zipversion __attribute__ ((__packed__));
  unsigned short zipflags __attribute__ ((__packed__));
  unsigned short compressionMethod __attribute__ ((__packed__));
  unsigned short lastmodtime __attribute__ ((__packed__));
  unsigned short lastmoddate __attribute__ ((__packed__));
  unsigned int crc32 __attribute__ ((__packed__));
  unsigned int compressedSize __attribute__ ((__packed__));
  unsigned int uncompressedSize __attribute__ ((__packed__));
  unsigned short filenameLength __attribute__ ((__packed__));
  unsigned short extraDataLength __attribute__ ((__packed__));
} PKZIPHEADER;


/*
 * Zip files are stored little endian
 * Support functions for short and int types
 */
static inline u32 FLIP32 (u32 b)
{
  unsigned int c;
  c = (b & 0xff000000) >> 24;
  c |= (b & 0xff0000) >> 8;
  c |= (b & 0xff00) << 8;
  c |= (b & 0xff) << 24;
  return c;
}

static inline u16 FLIP16 (u16 b)
{
  u16 c;
  c = (b & 0xff00) >> 8;
  c |= (b & 0xff) << 8;
  return c;
}

int load_archive(char *filename, unsigned char *buffer, int maxsize, char *extension)
{
  int size = 0;
  char in[CHUNKSIZE];
  char msg[64];
  char type[16];

  /* Open file */
  FILE *fd = fopen(filename, "rb");

  /* Autodetect needed System ROM files */
  if (filename == CD_BIOS_US)
  {
    sprintf(type,"CD BIOS (USA)");
  }
  else if (filename == CD_BIOS_EU)
  {
    sprintf(type,"CD BIOS (PAL)");
  }
  else if (filename == CD_BIOS_JP)
  {
    sprintf(type,"CD BIOS (JAP)");
  }
  else if (filename == AR_ROM)
  {
    sprintf(type,"Action Replay");
  }
  else if (filename == GG_ROM)
  {
    sprintf(type,"Game Genie");
  }
  else if (filename == SK_ROM)
  {
    sprintf(type,"S&K (2MB ROM)");
  }
  else if (filename == SK_UPMEM)
  {
    sprintf(type,"S2&K (256K ROM)");
  }
  else if ((filename == MS_BIOS_US) || (filename == MS_BIOS_EU) || (filename == MS_BIOS_JP) || (filename == GG_BIOS) || (filename == MD_BIOS))
  {
    /* Mega Drive / Genesis, Master System & Game Gear BIOS are optional so we disable error messages */
    SILENT = 1;
  }
  else
  {
    sprintf(type,"file");
  }

  if (!fd)
  {
    sprintf(msg,"Unable to open %s", type);
    GUI_WaitPrompt("Error", msg);
    SILENT = 0;
    return 0;
  }

  /* Read first chunk */
  fread(in, CHUNKSIZE, 1, fd);

  /* Detect Zip file */
  if (memcmp(in, "PK", 2) == 0)
  {
    /* Inflate buffer */
    char out[CHUNKSIZE];

    /* PKZip header pointer */
    PKZIPHEADER *pkzip = (PKZIPHEADER *) in;

    /* Retrieve uncompressed ROM size */
    size = FLIP32(pkzip->uncompressedSize);

    /* Check ROM size */
    if (size > MAXROMSIZE)
    {
      fclose(fd);
      GUI_WaitPrompt("Error","File is too large");
      SILENT = 0;
      return 0;
    }
    else if (size > maxsize)
    {
      size = maxsize;
    }

    sprintf (msg, "Unzipping %d bytes ...", size);
    GUI_MsgBoxUpdate("Information",msg);

    /* Initialize zip stream */
    z_stream zs;
    memset (&zs, 0, sizeof (z_stream));
    zs.zalloc = Z_NULL;
    zs.zfree = Z_NULL;
    zs.opaque = Z_NULL;
    zs.avail_in = 0;
    zs.next_in = Z_NULL;
    int res = inflateInit2(&zs, -MAX_WBITS);
    if (res != Z_OK)
    {
      fclose(fd);
      sprintf(msg,"Unable to unzip %s", type);
      GUI_WaitPrompt("Error",msg);
      SILENT = 0;
      return 0;
    }

    /* Compressed filename offset */
    int offset = sizeof (PKZIPHEADER) + FLIP16(pkzip->filenameLength);
    if (extension)
    {
      memcpy(extension, &in[offset - 3], 3);
      extension[3] = 0;
    }

    /* Initial Zip buffer offset */
    offset += FLIP16(pkzip->extraDataLength);
    zs.next_in = (Bytef *)&in[offset];

    /* Initial Zip remaining chunk size */
    zs.avail_in = CHUNKSIZE - offset;

    /* Initialize output size */
    size = 0;

    /* Start unzipping file */
    do
    {
      /* Inflate data until output buffer is empty */
      do
      {
        zs.avail_out = CHUNKSIZE;
        zs.next_out = (Bytef *) out;
        res = inflate(&zs, Z_NO_FLUSH);

        if (res == Z_MEM_ERROR)
        {
          inflateEnd(&zs);
          fclose(fd);
          sprintf(msg,"Unable to unzip %s", type);
          GUI_WaitPrompt("Error",msg);
          SILENT = 0;
          return 0;
        }

        offset = CHUNKSIZE - zs.avail_out;

        if ((size + offset) > maxsize)
        {
          offset = maxsize - size;
        }

        if (offset)
        {
          memcpy(buffer, out, offset);
          buffer += offset;
          size += offset;
        }
      }
      while ((zs.avail_out == 0) && (size < maxsize));

      /* Read next chunk of zipped data */
      fread(in, CHUNKSIZE, 1, fd);
      zs.next_in = (Bytef *)&in[0];
      zs.avail_in = CHUNKSIZE;
    }
    while ((res != Z_STREAM_END) && (size < maxsize));
    inflateEnd (&zs);
  }
  else
  {
    /* Get file size */
    fseek(fd, 0, SEEK_END);
    size = ftell(fd);
    fseek(fd, 0, SEEK_SET);

    /* Check ROM size */
    if (size > MAXROMSIZE)
    {
      fclose(fd);
      GUI_WaitPrompt("Error","File is too large");
      SILENT = 0;
      return 0;
    }
    else if (size > maxsize)
    {
      size = maxsize;
    }

    sprintf((char *)msg,"Loading %d bytes ...", size);
    GUI_MsgBoxUpdate("Information", (char *)msg);

    /* filename extension */
    if (extension)
    {
      memcpy(extension, &filename[strlen(filename) - 3], 3);
      extension[3] = 0;
    }

    /* Read into buffer */
    int left = size;
    while (left > CHUNKSIZE)
    {
      fread(buffer, CHUNKSIZE, 1, fd);
      buffer += CHUNKSIZE;
      left -= CHUNKSIZE;
    }

    /* Read remaining bytes */
    fread(buffer, left, 1, fd);
  }

  /* Close file */
  fclose(fd);

  /* Return loaded ROM size */
  SILENT = 0;
  return size;
}
