/*----------------------------------------------------------------------------*
 | This file is part of DEU (Doom Editing Utilities), created by the DEU team:|
 | Raphael Quinet, Brendon Wyber, Ted Vessenes and others.  See README.1ST or |
 | the "about" dialog box for full credits.                                   |
 |                                                                            |
 | DEU is an open project: if you think that you can contribute, please join  |
 | the DEU team.  You will be credited for any code (or ideas) included in    |
 | the next version of the program.                                           |
 |                                                                            |
 | If you want to make any modifications and re-distribute them on your own,  |
 | you must follow the conditions of the DEU license.  Read the file LICENSE  |
 | in this directory or README.1ST in the top directory.  If do not have a    |
 | copy of these files, you can request them from any member of the DEU team, |
 | or by mail: Raphael Quinet, Rue des Martyrs 9, B-4550 Nandrin (Belgium).   |
 |                                                                            |
 | This program comes with absolutely no warranty.  Use it at your own risks! |
 *----------------------------------------------------------------------------*

 D_WADS.C - WAD loading and saving routines

*/

/* the includes */
#include "deu.h"
#include "d_main.h"
#include "d_misc.h"
#include "d_config.h"
#include "d_wads.h"

/* global variables */
WadPtr  WadFileList = NULL;      /* linked list of wad files */
MDirPtr MasterDir   = NULL;      /* the master directory */


/*
   Open the main wad file, read in its directory and create the
   master directory.
*/

void OpenMainWad(char *filename)
{
  MDirPtr lastp, newp;
  UInt16  n;
  WadPtr  wad;

  /* open the wad file */
  printf("\nLoading main WAD file: %s...\n", filename);
  wad = BasicWadOpen(filename);
  if (strncmp(wad->type, "IWAD", 4))
    ProgError("\"%s\" is not the main WAD file", filename);

  /* create the master directory */
  lastp = NULL;
  for (n = 0; n < wad->dirsize; n++)
    {
      newp = (MDirPtr) GetMemory(sizeof(struct MasterDirectory));
      newp->next = NULL;
      newp->wadfile = wad;
      memcpy(&(newp->dir), &(wad->directory[n]), sizeof(struct Directory));
      if (MasterDir)
        lastp->next = newp;
      else
        MasterDir = newp;
      lastp = newp;
    }

  /* check version of the game */
  if (FindMasterDir(MasterDir, "E2M1") != NULL)
    DoomVersion = 1;
  else if (FindMasterDir(MasterDir, "MAP28") != NULL)
    DoomVersion = 2;
  else
    {
      printf("   *--------------------------------------------*\n");
      printf("   | Warning: you are using a SHAREWARE game.   |\n");
      printf("   | You won't be allowed to save your changes. |\n");
      printf("   | PLEASE REGISTER YOUR COPY OF THE GAME.     |\n");
      printf("   *--------------------------------------------*\n");
      /* If you change the next line, bad things will happen to you... */
      DoomVersion = 0;
    }
  if (FindMasterDir(MasterDir, "M_HTIC") != NULL)
    DoomVersion += 16;
}


/* Note from R.Q.:
    This function should be re-written so that it doesn't use the master
    directory, which will disappear from DEU.
    Some parts of the code to update a directory may be transferred to the
    BuildCompoundWad function.
*/

/*
   Open a patch wad file, read in its directory and alter the master
   directory.
*/

void OpenPatchWad(char *filename)
{
  WadPtr  wad;
  MDirPtr mdir;
  UInt16  n;
  Int16   l;
  char    entryname[9];

#ifdef DEU_DOS
  strupr(filename);
#endif

  /* ignore the file if it doesn't exist */
  if (! Exists(filename))
    {
      printf("Warning: patch WAD file \"%s\" doesn't exist.  Ignored.\n", filename);
      return;
    }

  /* open the wad file */
  if (Config.verbose == TRUE)
    printf("Loading patch WAD file: %s...\n", filename);
  wad = BasicWadOpen(filename);
  /* Check if the file is a PWAD or DWAD (Hi Bernt!) */
  if (strncmp(wad->type, "PWAD", 4) && strncmp(wad->type, "DWAD", 4))
    ProgError("\"%s\" is not a patch WAD file", filename);

  /* alter the master directory */
  /*! This should be changed - RQ */
  l = 0;
  for (n = 0; n < wad->dirsize; n++)
    {
      strncpy(entryname, wad->directory[n].name, 8);
      entryname[8] = '\0';
      if (l == 0)
        {
          mdir = FindMasterDir(MasterDir, wad->directory[n].name);
          /* if this entry is not in the master directory, then add it */
          if (mdir == NULL)
            {
              printf("   [Adding new entry %s]\n", entryname);
              mdir = MasterDir;
              while (mdir->next)
                mdir = mdir->next;
              mdir->next = (MDirPtr) GetMemory(sizeof(struct MasterDirectory));
              mdir = mdir->next;
              mdir->next = NULL;
            }
          /* if this is a level, then copy this entry and the next 10 */
          else if ((wad->directory[n].name[0] == 'E' && wad->directory[n].name[2] == 'M' && wad->directory[n].name[4] == '\0')
                || (wad->directory[n].name[0] == 'M' && wad->directory[n].name[1] == 'A' && wad->directory[n].name[2] == 'P' && wad->directory[n].name[5] == '\0'))
            {
              printf("   [Updating level %s]\n", entryname);
              l = 10;
            }
          else
            printf("   [Updating entry %s]\n", entryname);
        }
      else
        {
          mdir = mdir->next;
          /* the level data should replace an existing level */
          if (mdir == NULL || strncmp(mdir->dir.name, wad->directory[n].name, 8))
            ProgError("\\%s\" is not an understandable PWAD file (error with %s)", filename, entryname);
          l--;
        }
      mdir->wadfile = wad;
      memcpy(&(mdir->dir), &(wad->directory[n]), sizeof(struct Directory));
    }
}



/*
   Close all the wad files, deallocating the WAD file structures.
*/

void CloseWadFiles(void)
{
  WadPtr curw, nextw;
  MDirPtr curd, nextd;

  /* close the wad files */
  curw = WadFileList;
  WadFileList = NULL;
  while (curw)
    {
      nextw = curw->next;
      fclose(curw->fileinfo);
      FreeMemory(curw->directory);
      FreeMemory(curw);
      curw = nextw;
    }

  /* delete the master directory */
  curd = MasterDir;
  MasterDir = NULL;
  while (curd)
    {
      nextd = curd->next;
      FreeMemory(curd);
      curd = nextd;
    }
}



/*
   Forget unused patch wad files.
*/

void CloseUnusedWadFiles(void)
{
  WadPtr curw, prevw;
  MDirPtr mdir;

  prevw = NULL;
  curw = WadFileList;
  while (curw)
    {
      /* check if the wad file is used by a directory entry */
      mdir = MasterDir;
      while (mdir && mdir->wadfile != curw)
        mdir = mdir->next;
      if (mdir)
        prevw = curw;
      else
        {
          /* if this wad file is never used, close it */
          if (prevw)
            prevw->next = curw->next;
          else
            WadFileList = curw->next;
          fclose(curw->fileinfo);
          FreeMemory(curw->directory);
          FreeMemory(curw);
        }
      curw = prevw->next;
    }
}



/*
   Basic opening of WAD file and creation of node in Wad linked list.
*/

WadPtr BasicWadOpen(char *filename)
{
  WadPtr curw, prevw;
#ifdef FAT_ENDIAN
  int    i;
#endif

  /* find the wad file in the wad file list */
  prevw = WadFileList;
  if (prevw)
    {
      curw = prevw->next;
      while (curw && strcmp(filename, curw->filename))
        {
          prevw = curw;
          curw = prevw->next;
        }
   }
  else
    curw = NULL;

  /* if this entry doesn't exist, add it to the WadFileList */
  if (curw == NULL)
    {
      curw = (WadPtr) GetMemory(sizeof(struct WadFileInfo));
      if (prevw == NULL)
        WadFileList = curw;
      else
        prevw->next = curw;
      curw->next = NULL;
      curw->filename = filename;
    }

  /* open the file */
  if ((curw->fileinfo = fopen(filename, "rb")) == NULL)
    {
      if (!prevw)
        {
          WadFileList = NULL;
        }
      else
        {
          prevw->next = curw->next;
        }
      FreeMemory(curw);
      ProgError("error opening \"%s\"", filename);
    }
  /* read in the WAD directory info */
  BasicWadRead(curw, curw->type, 4L);
  /* check if the file is the IWAD or a PWAD */
  if (strncmp(curw->type + 1, "WAD", 3))
    ProgError("\"%s\" is not a valid WAD file", filename);
  WadReadInt32(curw, &curw->dirsize);
  WadReadInt32(curw, &curw->dirstart);

  /* read in the WAD directory itself */
  curw->directory = (DirPtr) GetMemory(sizeof(struct Directory) * curw->dirsize);
  BasicWadSeek(curw, curw->dirstart);
  BasicWadRead(curw, curw->directory, sizeof(struct Directory) * curw->dirsize);
#ifdef FAT_ENDIAN
  /* swap all integers read from the directory */
  for (i = 0; i < curw->dirsize; i++)
    {
      curw->directory[i].start = (Int32) SwapInt32(curw->directory[i].start);
      curw->directory[i].size = (Int32) SwapInt32(curw->directory[i].size);
    }
#endif

  /* all done */
  return curw;
}



/*
   Read bytes from a file and store it into an address with error checking.
*/

void BasicWadRead(WadPtr wadfile, void huge *addr, long size)
{
  if (fread(addr, 1, size, wadfile->fileinfo) != size)
    ProgError("error reading from \"%s\"", wadfile->filename);
}



/*
   Go to offset of wad file with error checking.
*/

void BasicWadSeek(WadPtr wadfile, long offset)
{
  if (fseek(wadfile->fileinfo, offset, 0))
    ProgError("error reading from \"%s\"", wadfile->filename);
}



/*
   Find an entry in the master directory.
*/

MDirPtr FindMasterDir(MDirPtr from, char *name)
{
  while (from)
    {
      if (!strncmp(from->dir.name, name, 8))
        break;
      from = from->next;
    }
  return from;
}


/*
   List the master directory.
*/

void ListMasterDirectory(FILE *file)
{
  char    dataname[9];
  MDirPtr dir;
  char    key;
  Int16   lines = 3;

  dataname[8] = '\0';
  fprintf(file, "The Master Directory\n");
  fprintf(file, "====================\n\n");
  fprintf(file, "NAME____  FILE________________  SIZE__  START____\n");
  for (dir = MasterDir; dir; dir = dir->next)
    {
      strncpy(dataname, dir->dir.name, 8);
      fprintf(file, "%-8s  %-20s  %6ld  x%08lx\n", dataname, dir->wadfile->filename, dir->dir.size, dir->dir.start);
      if (file == stdout && lines++ > 21)
        {
          lines = 0;
          printf("[Q to abort, any other key to continue]");
          key = getchar();
          printf("\r                                       \r");
          if (key == 'Q' || key == 'q')
            break;
        }
    }
}


/*
   List the directory of a file.
*/

void ListFileDirectory(FILE *file, WadPtr wad)
{
  char   dataname[9];
  char   key;
  Int16  lines = 5;
  UInt32 n;

  dataname[8] = '\0';
  fprintf(file, "WAD File Directory\n");
  fprintf(file, "==================\n\n");
  fprintf(file, "Wad File: %s\n\n", wad->filename);
  fprintf(file, "NAME____  SIZE__  START____  END______\n");
  for (n = 0; n < wad->dirsize; n++)
    {
      strncpy(dataname, wad->directory[n].name, 8);
      fprintf(file, "%-8s  %6ld  x%08lx  x%08lx\n", dataname, wad->directory[n].size, wad->directory[n].start, wad->directory[n].size + wad->directory[n].start - 1);
      if (file == stdout && lines++ > 21)
        {
          lines = 0;
          printf("[Q to abort, any other key to continue]");
          key = getchar();
          printf("\r                                       \r");
          if (key == 'Q' || key == 'q')
            break;
        }
    }
}


/* Note from R.Q. (Jan 95):
    The following function should be replaced by a new BuildCompoundWad
    function, which takes two arguments: the new file name and a list of
    WadPtrs.  BuildCompoundWad will create a new file by copying all entries
    from the WAD files given in the WadPtr list.  If the list contains the
    WadPtr for the main WAD (IWAD), then a new IWAD will be rebuilt.
    This new funtion will not use the master directory, so the entries will
    have to be re-ordered according to the IWAD directory, with the _first_
    WadPtr in the list taking precedence if some entries are duplicated.
*/

/*
   Build a new wad file from master directory.
*/

void BuildNewMainWad(char *filename, Bool patchonly)
{
  FILE   *file;
  UInt32  counter = 12;
  MDirPtr cur;
  UInt32  size;
  UInt32  dirstart;
  UInt32  dirnum;

  /* open the file and store signatures */
  if (patchonly)
    printf("Building a compound Patch Wad file \"%s\".\n", filename);
  else
    printf("Building a new Main Wad file \"%s\" (size approx 10000K)\n", filename);
  if (FindMasterDir(MasterDir, "E2M4") == NULL && FindMasterDir(MasterDir, "MAP24") == NULL)
    ProgError("You were warned: you are not allowed to do this.");
  if ((file = fopen(filename, "wb")) == NULL)
    ProgError("unable to open file \"%s\"", filename);
  if (patchonly)
    WriteBytes(file, "PWAD", 4);
  else
    WriteBytes(file, "IWAD", 4);
  WriteInt32(file, &counter);      /* put true value in later */
  WriteInt32(file, &counter);      /* put true value in later */

  /* output the directory data chuncks */
  for (cur = MasterDir; cur; cur = cur->next)
    {
      if (patchonly && cur->wadfile == WadFileList)
        continue;
      size = cur->dir.size;
      counter += size;
      BasicWadSeek(cur->wadfile, cur->dir.start);
      CopyBytes(file, cur->wadfile->fileinfo, size);
      printf("Size: %ldK\r", counter / 1024);
    }

  /* output the directory */
  dirstart = counter;
  counter = 12;
  dirnum = 0;
  for (cur = MasterDir; cur; cur = cur->next)
    {
      if (patchonly && cur->wadfile == WadFileList)
        continue;
      if (dirnum % 100 == 0)
        printf("Outputting directory %04ld...\r", dirnum);
      if (cur->dir.start)
        WriteInt32(file, &counter);
      else
        WriteInt32(file, &(cur->dir.start));
      WriteInt32(file, &(cur->dir.size));
      WriteBytes(file, &(cur->dir.name), 8L);
      counter += cur->dir.size;
      dirnum++;
    }

  /* fix up the number of entries and directory start information */
  if (fseek(file, 4L, 0))
    ProgError("error writing to file");
  WriteInt32(file, &dirnum);
  WriteInt32(file, &dirstart);

  /* close the file */
  printf("                            \r");
  fclose(file);
}


/*
   Output bytes to a binary file with error checking.
*/

void WriteBytes(FILE *file, void huge *addr, long size)
{
  if (DoomVersion < 1)
    return;
  while (size > 0x8000)
    {
      if (fwrite(addr, 1, 0x8000, file) != 0x8000)
        ProgError("error writing to file");
      addr = (char huge *)addr + 0x8000;
      size -= 0x8000;
    }
  if (fwrite(addr, 1, size, file) != size)
    ProgError("error writing to file");
}



/*
   Copy bytes from a binary file to another with error checking.
*/

void CopyBytes(FILE *dest, FILE *source, long size)
{
  void huge *data;

  data = GetFarMemory(0x8000 + 2);
  while (size > 0x8000)
    {
      if (DoomVersion > 0)
        if (fread(data, 1, 0x8000, source) != 0x8000)
          ProgError("error reading from file");
      if (fwrite(data, 1, 0x8000, dest) != 0x8000)
        ProgError("error writing to file");
      size -= 0x8000;
    }
  if (DoomVersion > 0)
    if (fread(data, 1, size, source) != size)
      ProgError("error reading from file");
  if (fwrite(data, 1, size, dest) != size)
    ProgError("error writing to file");
  FreeFarMemory(data);
}


/*
   Dump a directory entry in hex.
*/

void DumpDirectoryEntry(FILE *file, char *entryname)
{
  MDirPtr entry;
  char    dataname[9];
  char    key;
  int     lines = 5;
  UInt32  n, c, i;
  UInt8   buf[16];

  c = 0L;
  entry = MasterDir;
  while (entry)
    {
      if (!strnicmp(entry->dir.name, entryname, 8))
        {
          strncpy(dataname, entry->dir.name, 8);
          dataname[8] = '\0';
          fprintf(file, "Contents of entry %s (size = %ld bytes):\n", dataname, entry->dir.size);
          BasicWadSeek(entry->wadfile, entry->dir.start);
          n = 0L;
          for (c = 0L; c < entry->dir.size; c += 16L)
            {
              fprintf(file, "%04lX: ", n);
              for (i = 0L; i < 16L; i++)
                {
                  BasicWadRead(entry->wadfile, &(buf[i]), 1);
                  fprintf(file, " %02X", buf[i]);
                  n++;
                }
              fprintf(file, "   ");
              for (i = 0L; i < 16L; i++)
                {
                  if (buf[i] >= ' ')
                    fprintf(file, "%c", buf[i]);
                  else
                    fprintf(file, " ");
                }
              fprintf(file, "\n");
              if (file == stdout && lines++ > 21)
                {
                  lines = 0;
                  printf("[%ld%% - Q to abort, S to skip this entry, any other key to continue]", n * 100 / entry->dir.size);
                  key = getchar();
                  printf("\r                                                                    \r");
                  if (key == 'S' || key == 's')
                    break;
                  if (key == 'Q' || key == 'q')
                    return;
                }
            }
        }
      entry = entry->next;
    }
  if (! c)
    {
      printf("[Entry not in master directory]\n");
      return;
    }
}


/*
   Save a directory entry to disk.
*/

void SaveDirectoryEntry(FILE *file, char *entryname)
{
  MDirPtr entry;
  UInt32  counter;
  UInt32  size;

  for (entry = MasterDir; entry; entry = entry->next)
    if (!strnicmp(entry->dir.name, entryname, 8))
      break;
  if (entry)
    {
      WriteBytes(file, "PWAD", 4L);     /* PWAD file */
      counter = 1L;
      WriteInt32(file, &counter);       /* 1 entry */
      counter = 12L;
      WriteInt32(file, &counter);
      counter = 28L;
      WriteInt32(file, &counter);
      size = entry->dir.size;
      WriteInt32(file, &size);
      WriteBytes(file, &(entry->dir.name), 8L);
      BasicWadSeek(entry->wadfile, entry->dir.start);
      CopyBytes(file, entry->wadfile->fileinfo, size);
    }
  else
    {
      printf("[Entry not in master directory]\n");
      return;
    }
}


/*
   Save a directory entry to disk, without a PWAD header.
*/

void SaveEntryToRawFile(FILE *file, char *entryname)
{
  MDirPtr entry;

  for (entry = MasterDir; entry; entry = entry->next)
    if (!strnicmp(entry->dir.name, entryname, 8))
      break;
  if (entry)
    {
      BasicWadSeek(entry->wadfile, entry->dir.start);
      CopyBytes(file, entry->wadfile->fileinfo, entry->dir.size);
    }
  else
    {
      printf("[Entry not in master directory]\n");
      return;
    }
}


/*
   Encapsulate a raw file in a PWAD file.
*/

void SaveEntryFromRawFile(FILE *file, FILE *raw, char *entryname)
{
  UInt32 counter;
  Int32  size;
  char   name8[8];

  for (counter = 0L; counter < 8L; counter++)
    name8[counter] = '\0';
  strncpy(name8, entryname, 8);
  WriteBytes(file, "PWAD", 4L);     /* PWAD file */
  counter = 1L;
  WriteInt32(file, &counter);       /* 1 entry */
  counter = 12L;
  WriteInt32(file, &counter);
  counter = 28L;
  WriteInt32(file, &counter);
  if (fseek(raw, 0L, SEEK_END) != 0)
    ProgError("error reading from raw file");
  size = ftell(raw);
  if (size < 0L)
    ProgError("error reading from raw file");
  if (fseek(raw, 0L, SEEK_SET) != 0)
    ProgError("error reading from raw file");
  WriteInt32(file, (UInt32 *) &size);
  WriteBytes(file, name8, 8L);
  CopyBytes(file, raw, size);
}



#ifdef FAT_ENDIAN
UInt16 SwapInt16(UInt16 x)
{
  return ((x<<8) & 0xff00) | ((x>>8) & 0x00ff);
}


UInt32 SwapInt32(UInt32 x)
{
  return (((x << 24) & 0xff000000)
          | ((x<< 8) & 0x00ff0000)
          | ((x>> 8) & 0x0000ff00)
          | ((x>>24) & 0x000000ff));
}


void WadReadInt16(WadPtr wadfile, UInt16 huge *x)
{
  BasicWadRead(wadfile, x, 2L);
  *x = SwapInt16(*x);
}


void WadReadInt32(WadPtr wadfile, UInt32 huge *x)
{
  BasicWadRead(wadfile, x, 4L);
  *x = SwapInt32(*x);
}


void WriteInt16(FILE *file, UInt16 huge *x)
{
  UInt16 n;

  n = SwapInt16(*x);
  WriteBytes(file, &n, 2L);
}


void WriteInt32(FILE *file, UInt32 huge *x)
{
  UInt32 n;

  n = SwapInt32(*x);
  WriteBytes(file, &n, 4L);
}
#endif /* FAT_ENDIAN */

/* end of file */
