/*
 *  wad.c - WAD handling code
 *
 *  Written by:
 *   Brendon Wyber, Raphael Quinet (original Deu source),
 *  Extended by:
 *   Andreas Dehmel
 *
 *  This file is part of armDeu, the portable WAD utility (the name derives
 *  from its initial port to the ARM-based RISC OS). armDeu is released under
 *  the GNU Public License (GPL) in the hope that it proves useful. Please
 *  note there is NO WARRANTY. For more information read the file License
 *  included in this release.
 */


/* the includes */
#include "deu.h"
#include "main.h"
#ifdef __riscos__
#include <kernel.h>
#endif

/* fastlz compression library */
#include <fastlz.h>



/* Set the size entry of a lump to this value to mark it as eliminated */
#define ELIMINATED_SIZE		-1



/* compression / decompression structures */
static fastlz_compress_context_t *compCtx = NULL;
static fastlz_decompfile_context_t *decompCtx = NULL;




typedef struct lump_list_s {
  union {int i[2]; char c[8];} name;
  int number;
  MasterDirectory *entry;
} lump_list_t;



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

/* Size of buffer to use when building WADs */
long buffersize = 0x100000;
int eliminate = 0;
int compressOut = 0;
int verbose = 0;
char *lumpPattern = NULL;
int outputMode = OUTPUT_WAD_DATA;
int ignoreFirstBytes = 0;
int expandMultipleLumps = 0;
int maxOutputLines = 0;	/* unlimited */
static const char *MainWadName = NULL;
/* These two are set when normalizing the pattern */
static char *normalPattern;
static int *ranges;

static const char PWADname[] = "PWAD";
static const char IWADname[] = "IWAD";
static const char LUMPname[] = "LUMP";



/* This is what compressed WADs are recognized by: Bytes 12-23 in WAD */
#define COMPRESS_STRG_ZARQUON	"!COMPRESS00!"


/*#define ELIMINATE_UNUSED_LUMPS*/



/*
 *  The following section was experimental code that didn't work quite
 *  right...
 */

#ifdef ELIMINATE_UNUSED_LUMPS

#define NUMSPRITES	138

/* Used for sprite reference when locating unused lumps (138 entries) */
static char *sprnames[NUMSPRITES] = {
    "TROO","SHTG","PUNG","PISG","PISF","SHTF","SHT2","CHGG","CHGF","MISG",
    "MISF","SAWG","PLSG","PLSF","BFGG","BFGF","BLUD","PUFF","BAL1","BAL2",
    "PLSS","PLSE","MISL","BFS1","BFE1","BFE2","TFOG","IFOG","PLAY","POSS",
    "SPOS","VILE","FIRE","FATB","FBXP","SKEL","MANF","FATT","CPOS","SARG",
    "HEAD","BAL7","BOSS","BOS2","SKUL","SPID","BSPI","APLS","APBX","CYBR",
    "PAIN","SSWV","KEEN","BBRN","BOSF","ARM1","ARM2","BAR1","BEXP","FCAN",
    "BON1","BON2","BKEY","RKEY","YKEY","BSKU","RSKU","YSKU","STIM","MEDI",
    "SOUL","PINV","PSTR","PINS","MEGA","SUIT","PMAP","PVIS","CLIP","AMMO",
    "ROCK","BROK","CELL","CELP","SHEL","SBOX","BPAK","BFUG","MGUN","CSAW",
    "LAUN","PLAS","SHOT","SGN2","COLU","SMT2","GOR1","POL2","POL5","POL4",
    "POL3","POL1","POL6","GOR2","GOR3","GOR4","GOR5","SMIT","COL1","COL2",
    "COL3","COL4","CAND","CBRA","COL6","TRE1","TRE2","ELEC","CEYE","FSKU",
    "COL5","TBLU","TGRN","TRED","SMBT","SMGT","SMRT","HDB1","HDB2","HDB3",
    "HDB4","HDB5","HDB6","POB1","POB2","BRS1","TLMP","TLP2"
};

#define NUMMOBJS	137

/* Doom things -> doomdnums */
static int doomdnums[NUMMOBJS] = {
  -1, 3004, 9, 64, -1, 66, -1, -1,
  67, -1, 65, 3001, 3002, 58, 3005, 3003,
  -1, 69, 3006, 7, 68, 16, 71, 84,
  72, 88, 89, 87, -1, -1, 2035, -1,
  -1, -1, -1, -1, -1, -1, -1, -1,
  -1, 14, -1, 2018, 2019, 2014, 2015, 5,
  13, 6, 39, 38, 40, 2011, 2012, 2013,
  2022, 2023, 2024, 2025, 2026, 2045, 83, 2007,
  2048, 2010, 2046, 2047, 17, 2008, 2049, 8,
  2006, 2002, 2005, 2003, 2004, 2001, 82, 85,
  86, 2028, 30, 31, 32, 33, 37, 36,
  41, 42, 43, 44, 45, 46, 55, 56,
  57, 47, 48, 34, 35, 49, 50, 51,
  52, 53, 59, 60, 61, 62, 63, 22,
  15, 18, 21, 23, 20, 19, 10, 12,
  28, 24, 27, 29, 25, 26, 54, 70,
  73, 74, 75, 76, 77, 78, 79, 80,
  81
};




/*
   support functions for spritename matching
 */

static void QuickSortSprites(unsigned int *s, int from, int to)
{
  while (from < to)
  {
    int i, j;
    unsigned int h;

    j = (from+to)/2;
    h = s[j]; s[j] = s[from]; s[from] = h;
    j=from;
    for (i=from+1; i<=to; i++)
    {
      if (s[i] < s[from])
      {
        j++;
        h = s[j]; s[j] = s[i]; s[i] = h;
      }
    }
    h = s[j]; s[j] = s[from]; s[from] = h;

    if ((j-from) < (to-j))
    {
      QuickSortSprites(s, from, j-1);
      from = j+1;
    }
    else
    {
      QuickSortSprites(s, j+1, to);
      to = j-1;
    }
  }
}


static int LookupSpritePrefix(unsigned int *s, unsigned int pref)
{
  int pos, step, count;

  pos = (NUMSPRITES+1)/2; step = (pos+1)/2; count = pos<<1;

  while (count != 0)
  {
    if (s[pos] == pref) return pos;
    if (s[pos] < pref)
    {
      pos += step; if (pos >= NUMSPRITES) pos = NUMSPRITES-1;
    }
    else
    {
      pos -= step; if (pos < 0) pos = 0;
    }
    step = (step+1)/2;
    count >>= 1;
  }
  return -1;
}


/*
   checks lumps against spritenames
 */

static void ScanForSpriteNames(lump_list_t *lumps, int lnum)
{
  unsigned int snames[NUMSPRITES];
  int i, j;
  char *b;

  printf("Check for spritenames...\n");

  for (i=0; i<NUMSPRITES; i++)
  {
    b = sprnames[i];
    snames[i] = (b[0]<<24) | (b[1]<<16) | (b[2]<<8) | b[3];
  }

  QuickSortSprites(snames, 0, NUMSPRITES-1);

  /* Mark all lumps that are a sprite name prefix as used */
  for (i=0; i<lnum; i++)
  {
    j = LookupSpritePrefix(snames, (unsigned int)lumps[i].name.i[0]);
    if (j >= 0) lumps[i].number = 1;
  }
}



/*
   look up a lump with a given name
 */

static int LookupLumpName(lump_list_t *lumps, int lnum, char *name)
{
  union {char c[8]; int i[2];} nm;
  int pos, step, count;

  nm.i[0] = 0; nm.i[1] = 0;
  for (pos=0; (pos<8) && (name[pos] != 0); pos++)
  {
    nm.c[(pos&4) + ((3-pos)&3)] = toupper(name[pos]);
  }

  pos = (lnum+1)/2; step = (pos+1)/2; count = pos<<1;

  while (count != 0)
  {
    if ((lumps[pos].name.i[0] == nm.i[0]) && (lumps[pos].name.i[1] == nm.i[1]))
    {
      /*int i;
      printf("pos %d (", pos);
      for (i=0; (i<8) && (lumps[pos].name.c[(i&4) + ((3-i)&3)] != 0); i++)
        printf("%c", lumps[pos].name.c[(i&4) + ((3-i)&3)]);
      printf(")\n");*/
      return pos;
    }
    if ((lumps[pos].name.i[0] < nm.i[0]) || ((lumps[pos].name.i[0] == nm.i[0]) && (lumps[pos].name.i[1] < nm.i[1])))
    {
      pos += step; if (pos >= lnum) pos = lnum-1;
    }
    else
    {
      pos -= step; if (pos < 0) pos = 0;
    }
    step = (step+1)/2;
    count >>= 1;
  }
  return -1;
}


/*
   read all level data that contains lump names and mark lumps that are referenced
 */

static void ScanForLumpNames(lump_list_t *lumps, int lnum)
{
  MasterDirectory *cur;
  int size;
  char *b, *buffer;
  int bufsize;
  int objs, n;
  int level, lvlump;
  char lvname[8];
  int lvobj, lvcheck[] = {OBJ_THINGS, OBJ_SIDEDEFS, OBJ_SECTORS, -1};

  /* Number not needed anymore here, so use it to indicate reference */
  for (n=0; n<lnum; n++)
  {
    lumps[n].number = 0;
    /*b=lumps[n].name.c;
    for (objs=0; (objs<8) && (b[(objs&4) + ((3-objs)&3)] != 0); objs++)
      printf("%c", b[(objs&4) + ((3-objs)&3)]);
    printf("\n");*/
  }

  ScanForSpriteNames(lumps, lnum);

  n = LookupLumpName(lumps, lnum, "PNAMES");
  if (n <= 0)
  {
    fprintf(stderr, "No PNAMES? What the fuck???\n"); return;
  }
  cur = lumps[n].entry;
  bufsize = cur->dir.size;
  buffer = (char*)safe_malloc(bufsize);
  BasicWadSeek(cur->wadfile, cur->dir.start);
  fread(buffer, 1, cur->dir.size, cur->wadfile->fileinfo);
  b = buffer+4;
  objs = (cur->dir.size - 4) / 8;
  while (objs > 0)
  {
    n = LookupLumpName(lumps, lnum, b);
    if (n >= 0) lumps[n].number = 1;
    objs--; b+=8;
  }

  for (level=0; level<72; level++)
  {
    if (level < 36)
    {
      sprintf(lvname, "E%dM%d", level/9 + 1, level%9 + 1);
    }
    else
    {
      if (level < 45)
      {
        sprintf(lvname, "MAP0%d", level-35);
      }
      else
      {
        sprintf(lvname, "MAP%d", level-35);
      }
    }
    lvlump = LookupLumpName(lumps, lnum, lvname);
    if (lvlump >= 0)
    {
      printf("Check Level %s\n", lvname);
      for (lvobj=0; lvcheck[lvobj] >= 0; lvobj++)
      {
        /* Position to correct level object */
        cur = lumps[lvlump].entry;
        for (objs=0; objs < lvcheck[lvobj]; objs++)
        {
           cur = cur->next;
        }
        size = cur->dir.size;

        BasicWadSeek( cur->wadfile, cur->dir.start);

        if (cur->dir.size > bufsize)
        {
          free(buffer);
          buffer = (char*)safe_malloc(cur->dir.size);
          bufsize = cur->dir.size;
        }
        fread(buffer, 1, cur->dir.size, cur->wadfile->fileinfo);
        b = buffer;

        if (lvcheck[lvobj] == OBJ_THINGS)
        {
          objs = cur->dir.size / 10;
          while (objs > 0)
          {
            printf("thing %d (%x)\n", ((short*)b)[3], ((short*)b)[3]);
            objs--; b += 10;
          }
        }
        else if (lvcheck[lvobj] == OBJ_SIDEDEFS)	/* SIDEDEFS */
        {
          objs = cur->dir.size / 30;
          while (objs > 0)
          {
            n = LookupLumpName(lumps, lnum, b+4);
            if (n >= 0) lumps[n].number = 1;
            n = LookupLumpName(lumps, lnum, b+12);
            if (n >= 0) lumps[n].number = 1;
            n = LookupLumpName(lumps, lnum, b+20);
            if (n >= 0) lumps[n].number = 1;
            objs--; b += 30;
          }
        }
        else if (lvcheck[lvobj] == OBJ_SECTORS)	/* SECTORS */
        {
          objs = cur->dir.size / 26;
          while (objs > 0)
          {
            n = LookupLumpName(lumps, lnum, b+4);
            if (n >= 0) lumps[n].number = 1;
            n = LookupLumpName(lumps, lnum, b+12);
            if (n >= 0) lumps[n].number = 1;
            objs--; b += 26;
          }
        }
      }
    }
  }

  free(buffer);

  for (objs=0; objs<lnum; objs++)
  {
    if (lumps[objs].number == 0)
    {
      printf("void: "); b = lumps[objs].name.c;
      for (n=0; (n<8) && (b[(n&4) + ((3-n)&3)] != 0); n++)
      {
        printf("%c", b[(n&4) + ((3-n)&3)]);
      }
      printf("\n");
    }
  }
}

#endif





/*
   returns 1 if the name is a Doom 1 level name, 2 if it's a Doom 2 level name,
   0 otherwise
 */

static int IsLevelName(const char *s)
{
  if ((s[0] == 'M') && (s[1] == 'A') && (s[2] == 'P') && (s[3] >= '0') && (s[3] <= '9') && (s[4] >= '0') && (s[4] <= '9') && (s[5] == '\0'))
    return 2;

  if ((s[0] == 'E') && (s[1] >= '0') && (s[1] <= '9') && (s[2] == 'M') && (s[3] >= '0') && (s[3] <= '9') && (s[4] == '\0'))
    return 1;

  return 0;
}


/*
   Quicksort lump_list by name
*/

#define SWAP_LUMPS(x, y) \
   hi = lumps[x].name.i[0]; lumps[x].name.i[0] = lumps[y].name.i[0]; lumps[y].name.i[0] = hi;\
   hi = lumps[x].name.i[1]; lumps[x].name.i[1] = lumps[y].name.i[1]; lumps[y].name.i[1] = hi;\
   hi = lumps[x].number; lumps[x].number = lumps[y].number; lumps[y].number = hi; \
   hp = lumps[x].entry; lumps[x].entry = lumps[y].entry; lumps[y].entry = hp;

static void QuickSortLumps(lump_list_t *lumps, int from, int to)
{
   while (from < to)
   {
      int i, j;
      int hi;
      MasterDirectory *hp;

      j = (from + to) / 2;
      SWAP_LUMPS(from, j);
      j = from;

      for (i=from+1; i<=to; i++)
      {
         if ((lumps[i].name.i[0] < lumps[from].name.i[0]) || ((lumps[i].name.i[0] == lumps[from].name.i[0]) && (lumps[i].name.i[1] < lumps[from].name.i[1])))
         {
            j++;
            SWAP_LUMPS(i, j);
         }
      }
      SWAP_LUMPS(from, j);

      if ((j - from) < (to - j))
      {
         QuickSortLumps(lumps, from, j-1);
         from = j+1;
      }
      else
      {
         QuickSortLumps(lumps, j+1, to);
         to = j-1;
      }
   }
}






/*
   eliminate multiple lumps from a WAD directory
*/

static void EliminateLumps(MasterDirectory *root)
{
   MasterDirectory *thisp;
   WadFileInfo *wad;
   lump_list_t *lumps;
   int n, i, j, k, store;
   int lnum, skip;
   char *b;

   wad = root->wadfile;
   lumps = (lump_list_t*)safe_malloc((wad->dirsize)*sizeof(lump_list_t));

   thisp = root; lnum = 0; skip = 0;
   for (n=0; n < wad->dirsize; n++)
   {
      if (skip == 0)
      {
         b = thisp->dir.name;
         /* Levels are treated differently */
         if (IsLevelName(b) != 0)
         {
            skip = 10;
         }
         /* BUT keep the level names in there (for later reference) */
         lumps[lnum].name.i[0] = 0; lumps[lnum].name.i[1] = 0;
         for (i=0; (i<8) && (b[i] != 0); i++)
         {
            /* Reverse byte order for endianness */
            lumps[lnum].name.c[(i&4) + ((3-i)&3)] = toupper(b[i]);
         }
         lumps[lnum].entry = thisp; lumps[lnum].number = lnum;
         lnum++;
      }
      else
      {
         /*thisp->dir.name[7] = 0; printf("(%s)\n", thisp->dir.name);*/
         skip--;
      }
      thisp = thisp->next;
   }

   QuickSortLumps(lumps, 0, lnum - 1);

   /*for (n=0; n<lnum; n++)
   {
      for (i=0; i<8; i++)
      {
         if ((j = lumps[n].name.c[(i&4) + ((3-i)&3)]) == 0) break;
         printf("%c", j);
      }
      printf(" -- ");
      b = lumps[n].entry->dir.name;
      for (i=0; i<8; i++)
      {
         if ((j = b[i]) == 0) break;
         printf("%c", j);
      }
      printf("\n");
   }*/

   /* Now eliminate multiple lumps */

   printf("[%d data lumps]\n", lnum);

   skip = 0; n = 0; store = 0;

   while (n < lnum-1)
   {
      for (i=n+1; i<lnum; i++)
      {
         if ((lumps[n].name.i[0] != lumps[i].name.i[0]) || (lumps[n].name.i[1] != lumps[i].name.i[1])) break;
      }
      /* lumps n...i-1 identical */
      if (n < i-1)
      {
         /*printf("%d to %d\n", n, i);*/
         k = n;
         /* Find last of these lumps */
         for (j=n+1; j<i; j++)
         {
            if (lumps[j].number > lumps[k].number) k = j;
         }
         memcpy(lumps+store, lumps+k, sizeof(lump_list_t));
         store++;
         /* Now remove these lumps */
         for (j=n; j<i; j++)
         {
            if (j != k)
            {
               /*for (l=0; (l<8) && (lumps[j].name.c[(l&4) + ((3-l)&3)] != 0); l++)
                  printf("%c", lumps[j].name.c[(l&4) + ((3-l)&3)]);
               printf(" removed ", j);*/

               skip++;
               lumps[j].entry->dir.size = ELIMINATED_SIZE;
               lumps[j].entry->dir.name[0] = 0;

               /*b = lumps[j].entry->dir.name;
               for (l=0; (l<8) && (b[l] != 0); l++) printf("%c", b[l]);
               printf("\n");*/

            }
         }
         n = i;
      }
      else
      {
        memcpy(lumps+store, lumps+n, sizeof(lump_list_t));
        store++; n++;
      }
   }
   if (n == lnum-1)
   {
     memcpy(lumps+store, lumps+n, sizeof(lump_list_t));
     store++;
   }

   lnum = store;

   printf("[Found %d duplicates]\n", skip);

#ifdef ELIMINATE_UNUSED_LUMPS
   ScanForLumpNames(lumps, lnum);
#endif

   free(lumps);
}





/* Special lump region */
const static char markSpriteStart[] = "SS_START";
const static char markSpriteEnd[] = "SS_END";
const static char markFlatStart[] = "FF_START";
const static char markFlatEnd[] = "FF_END";
const static char markPatchStart[] = "PP_START";
const static char markPatchEnd[] = "PP_END";

/* pointers to the last lumps in special lump regions */
static MasterDirectory *spriteTailPtr = NULL;
static MasterDirectory *flatTailPtr = NULL;
static MasterDirectory *patchTailPtr = NULL;




/* returns 1 if the lumpname is a region-identifier */
static int RegionCompare(const char *name, const char *region)
{
   if ((strncasecmp(name, region, 8) == 0) || (strncasecmp(name, region+1, 7) == 0))
   {
      return 1;
   }
   return 0;
}


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

void OpenMainWad( const char *filename)
{
   MasterDirectory *lastp, *newp;
   long n;
   WadFileInfo *wad;
   int regionDesc;

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

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

   /* check if registered version */
#if 0	/* Naughty, naughty... */
   if (FindMasterDir( MasterDir, "E2M1") == NULL)
   {
      printf( "   *-------------------------------------------------*\n");
      printf( "   | Warning: this is the shareware version of DOOM. |\n");
      printf( "   |   You won't be allowed to save your changes.    |\n");
      printf( "   |     PLEASE REGISTER YOUR COPY OF THE GAME.      |\n");
      printf( "   *-------------------------------------------------*\n");
      Registered = FALSE; /* If you remove this, bad things will happen to you... */
   }
   else
#endif
      Registered = TRUE;

   if (eliminate) EliminateLumps(MasterDir);

   /* Get tail-pointers of the various sprite area types */
   lastp = NULL; newp = MasterDir; regionDesc = 0;
   while (newp != NULL)
   {
      char *name = newp->dir.name;

      if (RegionCompare(name, markSpriteStart) != 0)
      {
         regionDesc = 1;
      }
      else if (RegionCompare(name, markSpriteEnd) != 0)
      {
         if (regionDesc == 1) spriteTailPtr = lastp;
         regionDesc = 0;
      }
      else if (RegionCompare(name, markFlatStart) != 0)
      {
         regionDesc = 2;
      }
      else if (RegionCompare(name, markFlatEnd) != 0)
      {
         if (regionDesc == 2) flatTailPtr = lastp;
         regionDesc = 0;
      }
      else if (RegionCompare(name, markPatchStart) != 0)
      {
         regionDesc = 3;
      }
      else if (RegionCompare(name, markPatchEnd) != 0)
      {
         if (regionDesc == 3) patchTailPtr = lastp;
         regionDesc = 3;
      }
      lastp = newp;
      newp = newp->next;
   }
}




static const char *levelData[] = {
  "THINGS",
  "LINEDEFS",
  "SIDEDEFS",
  "VERTEXES",
  "SEGS",
  "SSECTORS",
  "NODES",
  "SECTORS",
  "REJECT",
  "BLOCKMAP"
};


static MasterDirectory *AllocRegionLump(const char *name, WadFileInfo *wad)
{
   MasterDirectory *mdir;

   mdir = (MasterDirectory*)safe_malloc(sizeof(MasterDirectory));
   memset(mdir->dir.name, 0, 8);
   strncpy(mdir->dir.name, name, 8);
   mdir->dir.start = 0; mdir->dir.size = 0; mdir->wadfile = wad; mdir->next = NULL;

   return mdir;
}


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

void OpenPatchWad( const char *filename)
{
   WadFileInfo *wad;
   MasterDirectory *mdir=NULL;
   int n, l;
   char entryname[9];
   int lumptype;
   MasterDirectory *mdirTail;
   int regionDesc;

   /* 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 */
   printf( "Loading patch WAD file: %s...\n", filename);
   wad = BasicWadOpen( filename);
   if (strncmp( wad->type, PWADname, 4))
   {
      printf( "\"%s\" is not a patch WAD file\n", filename);
      exit(-1);
   }

   /* Find the tail of the MasterDirectory */
   mdirTail = MasterDir;
   while (mdirTail->next) mdirTail = mdirTail->next;

   /* alter the master directory */
   l = 0; regionDesc = 0;
   for (n = 0; n < wad->dirsize; n++)
   {
      memset(entryname, 0, 9);
      strncpy( entryname, wad->directory[ n].name, 8);

      if (l == 0)
      {
         char *name;

         name = wad->directory[n].name;
         lumptype = IsLevelName(name);

	 mdir = FindMasterDir( MasterDir, name);

         /* In case a region doesn't exist create a new one (start and end markers) */
         if (RegionCompare(name, markSpriteStart) != 0)
         {
            regionDesc = 1;
            if (spriteTailPtr == NULL)
            {
               spriteTailPtr = AllocRegionLump(markSpriteStart, wad);
               mdir = AllocRegionLump(markSpriteEnd, wad);
               spriteTailPtr->next = mdir;
               mdirTail->next = spriteTailPtr; mdirTail = mdir;
            }
            continue;
         }
         else if (RegionCompare(name, markSpriteEnd) != 0)
         {
            regionDesc = 0;
            continue;
         }
         else if (RegionCompare(name, markFlatStart) != 0)
         {
            regionDesc = 2;
            if (flatTailPtr == NULL)
            {
               flatTailPtr = AllocRegionLump(markFlatStart, wad);
               mdir = AllocRegionLump(markFlatEnd, wad);
               flatTailPtr->next = mdir;
               mdirTail->next = flatTailPtr; mdirTail = mdir;
            }
            continue;
         }
         else if (RegionCompare(name, markFlatEnd) != 0)
         {
            regionDesc = 0;
            continue;
         }
         else if (RegionCompare(name, markPatchStart) != 0)
         {
            regionDesc = 3;
            if (patchTailPtr == NULL)
            {
               patchTailPtr = AllocRegionLump(markPatchStart, wad);
               mdir = AllocRegionLump(markPatchEnd, wad);
               patchTailPtr->next = mdir;
               mdirTail->next = patchTailPtr; mdirTail = mdir;
            }
            continue;
         }
         else if (RegionCompare(name, markPatchEnd) != 0)
         {
            regionDesc = 0;
            continue;
         }

	 /* if this entry is not in the master directory, then add it */
	 if (mdir == NULL)
	 {
	    if (verbose) printf( "   [Adding new entry %s]\n", entryname);
	    mdir = (MasterDirectory*)safe_malloc( sizeof(MasterDirectory));
	    mdir->next = NULL;
	    if (regionDesc == 1)
	    {
	       if (spriteTailPtr == NULL)
	       {
	          spriteTailPtr = mdir; mdirTail->next = mdir; mdirTail = mdir;
	       }
	       else
	       {
	          if ((mdir->next = spriteTailPtr->next) == NULL) mdirTail = mdir;
	          spriteTailPtr->next = mdir; spriteTailPtr = mdir;
	       }
	    }
	    else if (regionDesc == 2)
	    {
	       if (flatTailPtr == NULL)
	       {
	          flatTailPtr = mdir; mdirTail->next = mdir; mdirTail = mdir;
	       }
	       else
	       {
	          if ((mdir->next = flatTailPtr->next) == NULL) mdirTail = mdir;
	          flatTailPtr->next = mdir; flatTailPtr = mdir;
	       }
	    }
	    else if (regionDesc == 3)
	    {
	       if (patchTailPtr == NULL)
	       {
	          patchTailPtr = mdir; mdirTail->next = mdir; mdirTail = mdir;
	       }
	       else
	       {
	          if ((mdir->next = patchTailPtr->next) == NULL) mdirTail = mdir;
	          patchTailPtr->next = mdir; patchTailPtr = mdir;
	       }
	    }
	    else
	    {
	       mdirTail->next = mdir; mdirTail = mdir;
	       /* Add 10 new entries for level data*/
	       if (lumptype != 0)
	       {
	          MasterDirectory *oldMdir = mdir;

	          for (l=0; l<10; l++)
	          {
	             mdir->next = (MasterDirectory*)safe_malloc(sizeof(MasterDirectory));
	             mdir = mdir->next;
	             strncpy(mdir->dir.name, levelData[l], 8);
	             mdir->next = NULL;
	          }
	          mdirTail = mdir;
	          mdir = oldMdir;
	          l = 10;
	       }
	    }
	 }
	 /* if this is a level, then copy this entry and the next 10 */
	 /* This had to be changed (mostly for Doom2!) Originally it just checked */
	 /* whether the name had the format "E?M?", i.e. not even whether ? is a number */
	 else if (lumptype == 2)
	 {
	    /* Doom2 level */
	    if (verbose) printf( "   [Updating level %s]\n", entryname);
	    /* 10 is also used in doom1; see DOOM2SPEC */
	    l = 10;
	 }
	 else if (lumptype == 1)
	 {
	    if (verbose) printf( "   [Updating level %s]\n", entryname);
	    l = 10;
	 }
	 else
	 {
	    if (verbose) 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))
	 {
	    printf( "\"%s\" is not an understandable PWAD file (error with %s)\n", filename, entryname);
	    exit(-1);
	 }
	 l--;
      }
      mdir->wadfile = wad;
      memcpy( &(mdir->dir), &(wad->directory[ n]), sizeof(Directory));
   }
}



void OpenLumpFile(const char *filename, const char *lumpname)
{
   WadFileInfo *curw, *prevw;
   long filesize;
   MasterDirectory *mdir, *mdTail;

   /* copied from BasicWadOpen */
   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 = (WadFileInfo*)safe_malloc( sizeof(WadFileInfo));
      if (prevw == NULL)
	 WadFileList = curw;
      else
	 prevw->next = curw;
      curw->next = NULL;
      curw->filename = (char*)filename;
   }

   if ((curw->fileinfo = fopen(curw->filename, "rb")) == NULL)
   {
     fprintf(stderr, "Error opening \"%s\"\n", curw->filename);
     exit(-1);
   }
   fseek(curw->fileinfo, 0, SEEK_END); filesize = ftell(curw->fileinfo);
   /* Lumps are always opened on demand */
   fclose(curw->fileinfo); curw->fileinfo = NULL;

   curw->dirsize = 1;
   curw->dirstart = -1;
   curw->compressType = COMPRESS_TYPE_NONE;
   strncpy(curw->type, LUMPname, 4);
   curw->filesize = (size_t)filesize;
   curw->directory = (Directory*)safe_malloc(sizeof(Directory));
   curw->directory->start = 0;
   curw->directory->size = filesize;

   if (lumpname == NULL)
   {
      const char *b, *leaf;
      char *d;
      int length;

      b = filename; leaf = b;
      while (*b != '\0')
      {
#ifdef __riscos
         if ((*b == '.') || (*b == ':')) leaf = b+1;
#else
         if (*b == '/') leaf = b+1;
#endif
         b++;
      }
      b = leaf;
      while (*b != '\0')
      {
#ifdef __riscos
         if (*b == '/') break;
#else
         if (*b == '.') break;
#endif
         b++;
      }
      length = b - leaf;
      if (length > 8) length = 8;
      memset(curw->directory->name, 0, 8);
      b = leaf; d = curw->directory->name;
      /* Make sure lump names are always upper case! */
      while ((*b != 0) && (length > 0))
      {
        *d = toupper(*b);
        d++; b++; length--;
      }
   }
   else
   {
      strncpy(curw->directory->name, lumpname, 8);
   }

   mdir = FindMasterDir(MasterDir, curw->directory->name);
   if (mdir == NULL)
   {
      if (verbose) printf("   [Adding new entry %s]\n", curw->directory->name);
      mdir = (MasterDirectory*)safe_malloc(sizeof(MasterDirectory));
      mdir->next = NULL;
      mdTail = MasterDir;
      while (mdTail->next != NULL) mdTail = mdTail->next;
      mdTail->next = mdir;
   }
   mdir->wadfile = curw;
   memcpy(&(mdir->dir), curw->directory, sizeof(Directory));
}



/*
   close all the wad files, deallocating the WAD file structures
*/

void CloseWadFiles(void)
{
   WadFileInfo *curw, *nextw;
   MasterDirectory *curd, *nextd;

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

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



/*
   forget unused patch wad files
*/

void CloseUnusedWadFiles(void)
{
   WadFileInfo *curw, *prevw;
   MasterDirectory *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);
	 free( curw->directory);
	 free( curw);
      }
      curw = prevw->next;
   }
}



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

WadFileInfo *BasicWadOpen( const char *filename)
{
   WadFileInfo *curw, *prevw;
   char compID[12];
   int i, j;
   long value;

   /* 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 = (WadFileInfo*)safe_malloc( sizeof(WadFileInfo));
      if (prevw == NULL)
	 WadFileList = curw;
      else
	 prevw->next = curw;
      curw->next = NULL;
      curw->filename = (char*)filename;
   }

   /* open the file */
   if ((curw->fileinfo = fopen( filename, "rb")) == NULL)
   {
      printf( "error opening \"%s\"\n", filename);
      exit(-1);
   }

   /* read in the WAD directory info */
   BasicWadRead( curw, curw->type, 4);
   if (strncmp( curw->type, IWADname, 4) && strncmp( curw->type, PWADname, 4))
   {
      printf( "\"%s\" is not a valid WAD file\n", filename);
      exit(-1);
   }
   BasicWadRead( curw, &value, 4);
   curw->dirsize = LONG(value);
   BasicWadRead( curw, &value, 4);
   curw->dirstart = LONG(value);
   BasicWadRead( curw, compID, 12);
   curw->compressType = COMPRESS_TYPE_NONE;
   if (strncmp(compID, COMPRESS_STRG_ZARQUON, 12) == 0)
      curw->compressType = COMPRESS_TYPE_ZARQUON;

   /* read in the WAD directory itself */
   curw->directory = (Directory*)safe_malloc( sizeof(Directory) * curw->dirsize);
   BasicWadSeek( curw, curw->dirstart);
   BasicWadRead( curw, curw->directory, sizeof(Directory) * curw->dirsize);

   /* Clean out lump names */
   for (i=0; i<curw->dirsize; i++)
   {
      curw->directory[i].start = LONG(curw->directory[i].start);
      curw->directory[i].size = LONG(curw->directory[i].size);
      for (j=0; j<8; j++)
      {
        if (curw->directory[i].name[j] == 0) break;
      }
      while (++j < 8) curw->directory[i].name[j] = 0;
   }

   /* Now get the size */
   fseek( curw->fileinfo, 0, SEEK_END);
   curw->filesize = ftell(curw->fileinfo);

   /* all done */
   return curw;
}



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

void BasicWadRead( WadFileInfo *wadfile, void *addr, long size)
{
   /*printf("%p, %d, %d\n", addr, size);*/
   if (fread( addr, 1, size, wadfile->fileinfo) != size)
   {
      printf( "error reading from \"%s\" (1)\n", wadfile->filename);
      exit(-1);
   }
}



/*
   go to offset of wad file with error checking
*/

void BasicWadSeek( WadFileInfo *wadfile, long offset)
{
   if (fseek( wadfile->fileinfo, offset, 0))
   {
      printf( "error reading from \"%s\" (2)\n", wadfile->filename);
      exit(-1);
   }
}



/*
   find an entry in the master directory
*/

MasterDirectory *FindMasterDir( MasterDirectory *from, const char *name)
{
   /* When building a PWAD: don't try to replace anything, just store! */
   /* This is very important for dirty PWADs (AliensTC, AOD, mansion, ... */
   if (WadFileList->fileinfo == NULL) return NULL;

   while (from)
   {
      if (from->dir.size != ELIMINATED_SIZE)
      {
         if (!strncmp( from->dir.name, name, 8)) break;
      }

      from = from->next;
   }
   return from;
}



/*
   list the master directory
*/

void ListMasterDirectory( FILE *file)
{
   char dataname[ 9];
   MasterDirectory *dir;
   char key;
   int 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) && (maxOutputLines > 0) && (lines++ > maxOutputLines))
      {
	 lines = 0;
         printf( "[Q to abort, any other key to continue]");
	 key = fgetc(stdin);
	 printf( "\r                                       \r");
	 if (key == 'Q' || key == 'q')
	    break;
      }
   }
}



/*
   list the directory of a file
*/

void ListFileDirectory( FILE *file, WadFileInfo *wad)
{
   char dataname[ 9];
   char key;
   int lines = 5;
   long 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) && (maxOutputLines > 0) && (lines++ > maxOutputLines))
      {
	 lines = 0;
	 printf( "[Q to abort, any other key to continue]");
	 key = fgetc(stdin);
	 printf( "\r                                       \r");
	 if (key == 'Q' || key == 'q')
	    break;
      }
   }
}



/*
 *  Finding lump entries pointing to the same data.
 */

typedef struct lumpdir_s {
  MasterDirectory *entry;
  long sameas;
  long offset;
} lumpdir_t;


static int total_lumps;


#define SWAP_DIR_LUMPS(x, y) \
  aux = ldir[x].entry; ldir[x].entry = ldir[y].entry; ldir[y].entry = aux;

static void QuicksortLumpDir(lumpdir_t *ldir, int from, int to)
{
  while (from < to)
  {
    int i, j;
    MasterDirectory *aux;

    j = (from + to) / 2;
    SWAP_DIR_LUMPS(from, j);
    j = from;
    for (i=from+1; i<=to; i++)
    {
      if ((ldir[i].entry->wadfile < ldir[from].entry->wadfile) || ((ldir[i].entry->wadfile == ldir[from].entry->wadfile) && (ldir[i].entry->dir.start < ldir[from].entry->dir.start)))
      {
        j++;
        SWAP_DIR_LUMPS(i, j);
      }
    }
    SWAP_DIR_LUMPS(from, j);

    if ((j - from) < (to - j))
    {
      QuicksortLumpDir(ldir, from, j-1);
      from = j+1;
    }
    else
    {
      QuicksortLumpDir(ldir, j+1, to);
      to = j-1;
    }
  }
}


static int FindInLumpDir(lumpdir_t *ldir, MasterDirectory *mdir)
{
  int pos, step, count;

  if (ldir == NULL) return -1;

  pos = (total_lumps+1) >> 1; if (pos >= total_lumps) pos = total_lumps;
  step = (pos+1) >> 1; count = total_lumps << 1;
  while (count != 0)
  {
    if (ldir[pos].entry->wadfile == mdir->wadfile)
    {
      if (ldir[pos].entry->dir.start == mdir->dir.start) /* found one */
      {
        if (ldir[pos].sameas == -1) return pos;
        return ldir[pos].sameas;
      }
      if (ldir[pos].entry->dir.start < mdir->dir.start)
      {
        pos += step; if (pos >= total_lumps) pos = total_lumps-1;
      }
      else
      {
        pos -= step; if (pos < 0) pos = 0;
      }
    }
    else
    {
      if (ldir[pos].entry->wadfile < mdir->wadfile)
      {
        pos += step; if (pos >= total_lumps) pos = total_lumps-1;
      }
      else
      {
        pos -= step; if (pos < 0) pos = 0;
      }
    }
    step = (step+1) >> 1;
    count >>= 1;
  }
  return -1;
}


static lumpdir_t *CreateDirLookup(void)
{
  int number;
  MasterDirectory *cur;
  lumpdir_t *ldir;

  for (number=0, cur=MasterDir; cur != NULL; number++, cur = cur->next) ;

  if ((ldir = (lumpdir_t*)malloc(number * sizeof(lumpdir_t))) == NULL)
  {
    fprintf(stderr, "Unable to claim memory for lump dir lookup\n");
    return NULL;
  }
  total_lumps = number;
  for (number=0, cur=MasterDir; cur != NULL; number++, cur = cur->next)
  {
    ldir[number].entry = cur;
    ldir[number].sameas = -1;
    ldir[number].offset = -1;
  }

  QuicksortLumpDir(ldir, 0, total_lumps-1);

  number = 0;
  while (number < total_lumps)
  {
    int i=number+1;

    for (; i<total_lumps; i++)
    {
      if ((ldir[i].entry->wadfile != ldir[number].entry->wadfile) || (ldir[i].entry->dir.start != ldir[number].entry->dir.start)) break;
      ldir[i].sameas = number;
    }
    number = i;
  }

  /*for (number=0; number<total_lumps; number++)
  {
    printf("%8d: %8p %8ld sameas %8d\n", number, ldir[number].entry->wadfile, ldir[number].entry->dir.start, ldir[number].sameas);
  }
  for (number=0; number<total_lumps; number++)
  {
    printf("%4d: %4d\n", number, FindInLumpDir(ldir, ldir[number].entry));
  }
  exit(0);*/

  return ldir;
}


/* Append data in a buffer to the main WAD buffer */
static void MemoryToBuffer(void *buffer, void *mem, int size, long *bptr, FILE *file)
{
   int h, memOff;

   /*printf("bptr %ld, size %d |\n", *bptr, size);*/
   memOff = 0;
   while (size > 0)
   {
      if (*bptr == buffersize)
      {
         fwrite(buffer, 1, buffersize, file);
         *bptr = 0;
      }
      h = (buffersize - (*bptr)); if (h > size) h = size;
      memcpy(((char*)buffer) + (*bptr), ((char*)mem) + memOff, h);
      (*bptr) += h; memOff += h;
      size -= h;
   }
}

/* Append data in the file to the main WAD buffer */
static void FileToBuffer(void *buffer, FILE *infile, int size, long *bptr, FILE *outfile)
{
   int h;

   /*printf("bptr %ld, size %d |\n", *bptr, size);*/
   while (size > 0)
   {
      if (*bptr == buffersize)
      {
         fwrite(buffer, 1, buffersize, outfile);
         *bptr = 0;
      }
      h = (buffersize - (*bptr)); if (h > size) h = size;
      if (fread(((char*)buffer) + (*bptr), 1, h, infile) != h)
      {
         fprintf(stderr, "error reading from file (3)\n"); exit(-1);
      }
      (*bptr) += h;
      size -= h;
   }
}



static void ReportBadLump(MasterDirectory *cur)
{
   char entryname[9];
   int i;

   for (i=0; i<8; i++)
   {
      unsigned char c = (unsigned char)(cur->dir.name[i]);

      if ((c == 0) || (c >= 32))
	 entryname[i] = cur->dir.name[i];
      else
	 entryname[i] = '.';
   }
   entryname[8] = 0;
   cur->dir.size = ELIMINATED_SIZE;
   fprintf(stderr, "Warning, bad lump \"%s\", skipping...\n", entryname);
}



static void EnsureDecompContext(MasterDirectory *cur)
{
  if (decompCtx == NULL)
  {
    decompCtx = (fastlz_decompfile_context_t*)safe_malloc(sizeof(fastlz_decompfile_context_t));
    fastlz_decompress_init(&(decompCtx->basectx));
    decompCtx->basectx.fastlz_refill = fastlz_file_refill;
    decompCtx->basectx.ioBuffSize = 0x10000;
  }
  else
  {
    fastlz_decompress_reset(&(decompCtx->basectx));
  }
  decompCtx->file = cur->wadfile->fileinfo;
}


/*
   build a new wad file from master dictionary
*/

void BuildNewMainWad( const char *filename, Bool patchonly)
{
   FILE *file;
   long counter=12;
   MasterDirectory *cur;
   long size, h, totalSize;
   long dirstart;
   long dirnum;
   char *buffer;
   long bptr;
   unsigned long wadsize;
   char entryname[9];
   int levellumps;
   Bool elimlevel=0;
   char *outputName;
   int isLump;
   lumpdir_t *ldir = NULL;
   int identno;
   int identicalLumps = 0;

   if ((compressOut != 0) && (ignoreFirstBytes != 0))
   {
     printf("Warning: you can't ignore lump bytes when outputting compressed data!\n");
     ignoreFirstBytes = 0;
   }
   /* open the file and store signatures */
   if (outputMode == OUTPUT_RAW_DATA)
     outputName = "raw binary";
   else
   {
     if (ignoreFirstBytes != 0)
     {
       printf("Warning: you can't ignore lump bytes when outputting WAD data!\n");
       ignoreFirstBytes = 0;
     }
     if (patchonly)
       outputName = "Patch Wad";
     else
       outputName = "Main Wad";
   }
   printf( "Building a%s compound %s file \"%s\".\n", (compressOut == 0) ? "" : " compressed", outputName, filename);
#if 0
   if (FindMasterDir( MasterDir, "E2M4") == NULL)
   {
      printf( "You were warned: you are not allowed to do this.\n");
      exit(-1);
   }
#endif
   if ((file = fopen( filename, "wb")) == NULL)
   {
      printf( "unable to open file \"%s\"\n", filename);
      exit(-1);
   }

#ifdef __riscos__
   if (outputMode != OUTPUT_RAW_DATA)
   {
      _kernel_osfile_block block;

      block.load = 0x16c;
      _kernel_osfile(18, filename, &block);
   }
#endif

   while ((buffer = (char*)malloc((buffersize+3)&~3)) == NULL) buffersize >>= 1;
   bptr = 0; totalSize = 0;

   if (outputMode == OUTPUT_RAW_DATA)
   {
     counter = 0;
   }
   else
   {
     if ((patchonly) || (lumpPattern != NULL))
       WriteBytes( file, PWADname, 4);
     else
       WriteBytes( file, IWADname, 4);
     WriteBytes( file, &counter, 4L);      /* put true value in later */
     WriteBytes( file, &counter, 4L);      /* put true value in later */

     if (compressOut == 0) counter = 12;
     else
     {
       WriteBytes( file, COMPRESS_STRG_ZARQUON, 12);
       counter = 24;
     }
   }
   if (lumpPattern != NULL)
   {
     if ((normalPattern = NormalizePattern(lumpPattern, &ranges)) == NULL)
     {
       fprintf(stderr, "Couldn't normalize pattern!\n");
       exit(-1);
     }
   }

   if (expandMultipleLumps == 0) ldir = CreateDirLookup();

   entryname[8] = '\0'; levellumps = 0;
   /* output the directory data chuncks */
   for (cur = MasterDir; cur; cur = cur->next)
   {
      cur->offset = counter;
      if (patchonly && cur->wadfile == WadFileList) continue;
      if (levellumps != 0)
      {
         if (elimlevel) cur->dir.size = ELIMINATED_SIZE;
         levellumps--;
      }
      else if (lumpPattern != NULL)
      {
         strncpy(entryname, cur->dir.name, 8);
         if (MatchPattern(entryname, normalPattern, ranges) == 0)
         {
           cur->dir.size = ELIMINATED_SIZE; elimlevel = TRUE;
         }
         else
         {
           elimlevel = FALSE;
         }
         /* Don't access level-lumps directly */
         if (IsLevelName(entryname) != 0) levellumps = 10;
      }
      /* Was it an eliminated duplicate? */
      if (cur->dir.size == ELIMINATED_SIZE) continue;
      if ((size = cur->dir.size) <= 0) continue;

      if (ldir != NULL)
      {
        identno = FindInLumpDir(ldir, cur);
        if ((identno != -1) && (ldir[identno].offset != -1))
        {
          identicalLumps++;
          continue;
        }
        ldir[identno].offset = counter;
      }

      /*
       *  Check lump consistency stage 1: start offset.
       *  The rest has to be done somewhere else because of compression.
       */
      wadsize = (unsigned long)(cur->wadfile->filesize);
      if ((unsigned long)(cur->dir.start) > wadsize)
      {
         ReportBadLump(cur);
	 continue;
      }

      /* In case of lump open it first */
      isLump = strncasecmp(cur->wadfile->type, LUMPname, 4);
      if (isLump == 0)
      {
         cur->wadfile->fileinfo = fopen(cur->wadfile->filename, "rb");
      }

      BasicWadSeek( cur->wadfile, cur->dir.start);

      /* Simple check for lump size in uncompressed WADs */
      if (cur->wadfile->compressType == COMPRESS_TYPE_NONE)
      {
        if ((unsigned long)(cur->dir.start+size) > wadsize)
        {
           ReportBadLump(cur);
           continue;
        }
      }

      totalSize += size;
      /*CopyBytes( file, cur->wadfile->fileinfo, size);*/
      /* The original WADs have everything on word-aligned addresses! This probably */
      /* prevents problems especially on RISC machines, so make sure we do the same */
      /*while ((size & 3) != 0) {fputc(0, file); size++;}*/

      if (compressOut == 0)
      {
	 if (ignoreFirstBytes != 0)
	 {
	   if ((size -= ignoreFirstBytes) <= 0) continue;
	 }
         if (cur->wadfile->compressType == COMPRESS_TYPE_NONE)
         {
	    if (ignoreFirstBytes != 0)
	    {
	       fseek(cur->wadfile->fileinfo, ignoreFirstBytes, SEEK_SET);
	    }
            /* Use a huge buffer for this to avoid screwing the harddisc on big WADs */
            FileToBuffer(buffer, cur->wadfile->fileinfo, size, &bptr, file);
         }
         else
         {
            unsigned char *outBuff;

            outBuff = (unsigned char*)safe_malloc(cur->dir.size);

            EnsureDecompContext(cur);
            fastlz_decompress_block(&(decompCtx->basectx), outBuff, cur->dir.size);

            MemoryToBuffer(buffer, outBuff + ignoreFirstBytes, size, &bptr, file);
            free(outBuff);
         }
         counter += ((size + 3) & ~3);
      }
      else
      {
         if (cur->wadfile->compressType == COMPRESS_TYPE_NONE)
         {
            unsigned char *inBuff, *outBuff;
            int outSize;

            if (compCtx == NULL)
            {
              compCtx =
                (fastlz_compress_context_t*)safe_malloc(sizeof(fastlz_compress_context_t));
              fastlz_compress_init(compCtx);
              compCtx->backreference = (128<<compressOut);
            }
            inBuff = (unsigned char*)safe_malloc(size);
            fread(inBuff, 1, size, cur->wadfile->fileinfo);
            /*printf("size <%d>\n", size);*/

            outBuff = fastlz_compress_block(compCtx, inBuff, size, (unsigned int*)&outSize);
            MemoryToBuffer(buffer, (char*)outBuff, outSize, &bptr, file);
            counter += (outSize + 3) & ~3;
            free(outBuff);
            free(inBuff);
         }
         else	/* just copy the lump verbatim */
         {
            unsigned int fpos, lumpSize;
            fpos = ftell(cur->wadfile->fileinfo);
            EnsureDecompContext(cur);
            fastlz_decompress_read_properties(&(decompCtx->basectx), &lumpSize, NULL);
            fseek(cur->wadfile->fileinfo, fpos, SEEK_SET);
            FileToBuffer(buffer, cur->wadfile->fileinfo, lumpSize, &bptr, file);
            counter += (lumpSize + 3) & ~3;
         }
      }

      if (isLump == 0)
      {
         fclose(cur->wadfile->fileinfo); cur->wadfile->fileinfo = NULL;
      }

      if (outputMode == OUTPUT_WAD_DATA)
      {
        /* buffersize is a multiple of words, so we can't overflow here. */
        while ((bptr & 3) != 0) buffer[bptr++] = 0;
      }
      if (compressOut == 0)
      {
         printf( "Size: %ldK\r", counter / 1024);
      }
      else
      {
         printf( "Size: %5ldk | %5ldk\r", totalSize / 1024, counter / 1024);
      }
      fflush(stdout);
   }

   /* Flush buffer */
   if (bptr != 0)
   {
      fwrite(buffer, 1, bptr, file);
   }
   free(buffer);

   if (outputMode == OUTPUT_WAD_DATA)
   {
     /* output the directory */
     dirstart = counter;
     dirnum = 0;
     for (cur = MasterDir; cur; cur = cur->next)
     {
       if (patchonly && cur->wadfile == WadFileList) continue;
       /* Check for eliminated lump here too! */
       if (cur->dir.size == ELIMINATED_SIZE) continue;
       if (dirnum % 100 == 0)
	 printf( "Outputting directory %04ld...\r", dirnum);
       identno = FindInLumpDir(ldir, cur);
       if (identno >= 0)
       {
         h = ldir[identno].offset;
         if (h < 0) h = 0;
       }
       else
       {
         if ((cur->dir.start) || (strncasecmp(cur->wadfile->type, LUMPname, 4) == 0))
         {
           h = cur->offset;
         }
         else
         {
           h = cur->dir.start;
         }
       }
       h = LONG(h);
       WriteBytes( file, &h, 4L);
       /* The size written here doesn't have to be word-aligned even though the */
       /* WAD chunks are! */
       h = LONG(cur->dir.size);
       WriteBytes( file, &h, 4L);
       WriteBytes( file, &(cur->dir.name), 8L);
       dirnum++;
     }

     /* fix up the number of entries and directory start information */
     if (fseek( file, 4L, 0))
     {
       printf( "error writing to file\n");
       exit(-1);
     }
     h = LONG(dirnum);
     WriteBytes( file, &h, 4L);
     h = LONG(dirstart);
     WriteBytes( file, &h, 4L);
   }

   /* close the file */
   if (compressOut == 0)
   {
      printf( "                            \n");
   }
   else
   {
      totalSize >>= 8; counter >>= 8;
      if (totalSize == 0) h = 1;
      else h = (100*counter) / totalSize;
      printf("Output WAD compressed to %ld%%\n", h);
   }
   fclose( file);

   if (identicalLumps != 0)
   {
     printf("%d identical lumps\n", identicalLumps);
   }

   if (ldir != NULL) free(ldir);
}



/*
   output bytes to a binary file with error checking
*/

void WriteBytes( FILE *file, const void *addr, long size)
{
   if (! Registered)
      return;
   while (size > 0x8000)
   {
      if (fwrite( addr, 1, 0x8000, file) != 0x8000)
      {
	 printf( "error writing to file\n");
	 exit(-1);
      }
      addr = (char *)addr + 0x8000;
      size -= 0x8000;
   }
   if (fwrite( addr, 1, size, file) != size)
   {
      printf( "error writing to file\n");
      exit(-1);
   }
}



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

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

   if (! Registered)
      return;
   data = safe_malloc( 0x8000 + 2);
   while (size > 0x8000)
   {
      if (fread( data, 1, 0x8000, source) != 0x8000)
      {
	 printf( "error reading from file (4)\n");
	 exit(-1);
      }
      if (fwrite( data, 1, 0x8000, dest) != 0x8000)
      {
	 printf( "error writing to file\n");
	 exit(-1);
      }
      size -= 0x8000;
   }
   if (fread( data, 1, size, source) != size)
   {
      printf( "error reading from file (5)\n");
      exit(-1);
   }
   if (fwrite( data, 1, size, dest) != size)
   {
      printf( "error writing to file\n");
      exit(-1);
   }
   free( data);
}



/*
   check if a file exists and is readable
*/

Bool Exists( const char *filename)
{
   FILE *test;

   if ((test = fopen( filename, "rb")) == NULL)
      return FALSE;
   fclose( test);
   return TRUE;
}



/*
   dump a directory entry in hex
*/

void DumpDirectoryEntry( FILE *file, const char *entryname)
{
   MasterDirectory *entry;
   char dataname[ 9];
   char key;
   int lines = 5;
   long n, c, i;
   unsigned char buf[16];


   c = 0;
   entry = MasterDir;
   while (entry)
   {
      if (!strncasecmp( 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 = 0;
	 i = -1;
	 for (c = 0; c < entry->dir.size; c += i)
	 {
	    fprintf( file, "%04lx: ", n);
	    for (i = 0; i < 16; i++)
	    {
	       BasicWadRead( entry->wadfile, &(buf[ i]), 1);
	       fprintf( file, " %02X", buf[ i]);
	       n++;
	    }
	    fprintf( file, "   ");
	    for (i = 0; i < 16; i++)
	    {
	       if (buf[ i] >= 32)
		  fprintf( file, "%c", buf[ i]);
	       else
		  fprintf( file, " ");
	    }
	    fprintf( file, "\n");
	    if ((file == stdout) && (maxOutputLines > 0) && (lines++ > maxOutputLines))
	    {
	       lines = 0;
	       printf( "[%ld%% - Q to abort, S to skip this entry, any other key to continue]", n * 100 / entry->dir.size);
	       key = fgetc(stdin);
	       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, const char *entryname)
{
   MasterDirectory *entry;
   long    counter;
   long    size;

   for (entry = MasterDir; entry; entry = entry->next)
      if (!strncasecmp( entry->dir.name, entryname, 8))
	 break;
   if (entry)
   {
      WriteBytes( file, PWADname, 4L);     /* PWAD file */
      counter = 1L;
      WriteBytes( file, &counter, 4L);   /* 1 entry */
      counter = 12L;
      WriteBytes( file, &counter, 4L);
      counter = 28L;
      WriteBytes( file, &counter, 4L);
      size = entry->dir.size;
      WriteBytes( file, &size, 4L);
      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, const char *entryname)
{
   MasterDirectory *entry;

   for (entry = MasterDir; entry; entry = entry->next)
      if (!strncasecmp( 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, const char *entryname)
{
   long    counter;
   long    size;
   char    name8[ 8];

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



void ListWadMaps(void)
{
  MasterDirectory *mdir;

  printf("Maps contained in WAD(s):\n");
  for (mdir = MasterDir; mdir != NULL; mdir = mdir->next)
  {
    if (IsLevelName(mdir->dir.name))
    {
      printf("%s\n", mdir->dir.name);
    }
  }
}

/* end of file */
