/* Emacs style mode select   -*- C++ -*-
 *-----------------------------------------------------------------------------
 *
 *
 *  PrBoom a Doom port merged with LxDoom and LSDLDoom
 *  based on BOOM, a modified and improved DOOM engine
 *  Copyright (C) 1999 by
 *  id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman
 *  Copyright (C) 1999-2000 by
 *  Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze
 *
 *  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; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 *  02111-1307, USA.
 *
 * DESCRIPTION:
 *     Dehacked file support
 *     New for the TeamTNT "Boom" engine
 *
 * Author: Ty Halderman, TeamTNT
 *
 *--------------------------------------------------------------------*/

// killough 5/2/98: fixed headers, removed rendunant external declarations:
#include "doomdef.h"
#include "doomtype.h"
#include "doomstat.h"
#include "sounds.h"
#include "info.h"
#include "m_cheat.h"
#include "p_inter.h"
#include "g_game.h"
#include "d_think.h"
#include "w_wad.h"

#include <stdio.h> /* sscanf */
#include "rockmacros.h"

#define TRUE 1
#define FALSE 0

char* strlwr(char* str)
{
   char* p;
   for (p=str; *p; p++) *p = tolower(*p);
   return str;
}

// killough 10/98: new functions, to allow processing DEH files in-memory
// (e.g. from wads)

typedef struct {
    const byte *inp; // Pointer to string
    size_t size;     // Bytes remaining in string
    int fd;          // Current file descriptor 
} DEHFILE;

// killough 10/98: emulate IO whether input really comes from a file or not

char *dehfgets(char *buf, size_t n, DEHFILE *fp)
{
   char *p;

   if (fp->fd >= 0)
   {                                               // If this is a real file,
      int r = read_line(fp->fd, buf, (unsigned)n); // return regular line read
      return (r > 0) ? buf : NULL;
   }

   n = MIN(fp->size, n);

   if (n == 0 || *fp->inp == '\0')   // If no more characters
      return NULL;

   p = buf;

   while (n-- > 0)
   {
      unsigned char c = *fp->inp++;
      fp->size--;

      if ( c == '\n' )
         break;

      if ( c != '\r' )
         *p++ = c;
   }

   *p = '\0';

   return buf;                       // Return buffer pointer
}

int dehfeof(DEHFILE *fp)
{
   if (fp->fd >= 0)
   {
      off_t size   = filesize(fp->fd);
      off_t offset = lseek(fp->fd, 0, SEEK_CUR);
      return (size <= 0 || offset < 0 || offset >= size) ? 1 : 0;
   }

   return (fp->size <= 0 || *fp->inp == '\0') ? 1 : 0;
}

int dehfgetc(DEHFILE *fp)
{
   if (fp->fd >= 0)
   {
      unsigned char c;
      if (read(fp->fd, &c, 1) == 1)
         return (unsigned int)c;
   }
   else if (fp->size > 0)
   {
      return fp->size--, *fp->inp++;
   }

   return EOF;
}


// variables used in other routines
boolean deh_pars = FALSE; // in wi_stuff to allow pars in modified games

// #include "d_deh.h" -- we don't do that here but we declare the
// variables.  This externalizes everything that there is a string
// set for in the language files.  See d_deh.h for detailed comments,
// original English values etc.  These are set to the macro values,
// which are set by D_ENGLSH.H or D_FRENCH.H(etc).  BEX files are a
// better way of changing these strings globally by language.

// ====================================================================
// Any of these can be changed using the bex extensions
#include "dstrings.h"  // to get the initial values
/* cph - const's
 *     - removed redundant "can't XXX in a netgame" strings.
 */
const char *s_D_DEVSTR    = D_DEVSTR;
const char *s_D_CDROM     = D_CDROM;
const char *s_PRESSKEY    = PRESSKEY;
const char *s_PRESSYN     = PRESSYN;
const char *s_QUITMSG     = QUITMSG;
const char *s_QSAVESPOT   = QSAVESPOT; // PRESSKEY;
const char *s_SAVEDEAD    = SAVEDEAD;  // PRESSKEY; // remove duplicate y/n
const char *s_QSPROMPT    = QSPROMPT;  // PRESSYN;
const char *s_QLPROMPT    = QLPROMPT;  // PRESSYN;
const char *s_NEWGAME     = NEWGAME;   // PRESSKEY;
const char *s_RESTARTLEVEL= RESTARTLEVEL; // PRESSYN;
const char *s_NIGHTMARE   = NIGHTMARE; // PRESSYN;
const char *s_SWSTRING    = SWSTRING;  // PRESSKEY;
const char *s_MSGOFF      = MSGOFF;
const char *s_MSGON       = MSGON;
const char *s_NETEND      = NETEND;    // PRESSKEY;
const char *s_ENDGAME     = ENDGAME;   // PRESSYN; // killough 4/4/98: end
const char *s_DOSY        = DOSY;
const char *s_DETAILHI    = DETAILHI;
const char *s_DETAILLO    = DETAILLO;
const char *s_GAMMALVL0   = GAMMALVL0;
const char *s_GAMMALVL1   = GAMMALVL1;
const char *s_GAMMALVL2   = GAMMALVL2;
const char *s_GAMMALVL3   = GAMMALVL3;
const char *s_GAMMALVL4   = GAMMALVL4;
const char *s_EMPTYSTRING = EMPTYSTRING;
const char *s_GOTARMOR    = GOTARMOR;
const char *s_GOTMEGA     = GOTMEGA;
const char *s_GOTHTHBONUS = GOTHTHBONUS;
const char *s_GOTARMBONUS = GOTARMBONUS;
const char *s_GOTSTIM     = GOTSTIM;
const char *s_GOTMEDINEED = GOTMEDINEED;
const char *s_GOTMEDIKIT  = GOTMEDIKIT;
const char *s_GOTSUPER    = GOTSUPER;
const char *s_GOTBLUECARD = GOTBLUECARD;
const char *s_GOTYELWCARD = GOTYELWCARD;
const char *s_GOTREDCARD  = GOTREDCARD;
const char *s_GOTBLUESKUL = GOTBLUESKUL;
const char *s_GOTYELWSKUL = GOTYELWSKUL;
const char *s_GOTREDSKULL = GOTREDSKULL;
const char *s_GOTINVUL    = GOTINVUL;
const char *s_GOTBERSERK  = GOTBERSERK;
const char *s_GOTINVIS    = GOTINVIS;
const char *s_GOTSUIT     = GOTSUIT;
const char *s_GOTMAP      = GOTMAP;
const char *s_GOTVISOR    = GOTVISOR;
const char *s_GOTMSPHERE  = GOTMSPHERE;
const char *s_GOTCLIP     = GOTCLIP;
const char *s_GOTCLIPBOX  = GOTCLIPBOX;
const char *s_GOTROCKET   = GOTROCKET;
const char *s_GOTROCKBOX  = GOTROCKBOX;
const char *s_GOTCELL     = GOTCELL;
const char *s_GOTCELLBOX  = GOTCELLBOX;
const char *s_GOTSHELLS   = GOTSHELLS;
const char *s_GOTSHELLBOX = GOTSHELLBOX;
const char *s_GOTBACKPACK = GOTBACKPACK;
const char *s_GOTBFG9000  = GOTBFG9000;
const char *s_GOTCHAINGUN = GOTCHAINGUN;
const char *s_GOTCHAINSAW = GOTCHAINSAW;
const char *s_GOTLAUNCHER = GOTLAUNCHER;
const char *s_GOTPLASMA   = GOTPLASMA;
const char *s_GOTSHOTGUN  = GOTSHOTGUN;
const char *s_GOTSHOTGUN2 = GOTSHOTGUN2;
const char *s_PD_BLUEO    = PD_BLUEO;
const char *s_PD_REDO     = PD_REDO;
const char *s_PD_YELLOWO  = PD_YELLOWO;
const char *s_PD_BLUEK    = PD_BLUEK;
const char *s_PD_REDK     = PD_REDK;
const char *s_PD_YELLOWK  = PD_YELLOWK;
const char *s_PD_BLUEC    = PD_BLUEC;
const char *s_PD_REDC     = PD_REDC;
const char *s_PD_YELLOWC  = PD_YELLOWC;
const char *s_PD_BLUES    = PD_BLUES;
const char *s_PD_REDS     = PD_REDS;
const char *s_PD_YELLOWS  = PD_YELLOWS;
const char *s_PD_ANY      = PD_ANY;
const char *s_PD_ALL3     = PD_ALL3;
const char *s_PD_ALL6     = PD_ALL6;
const char *s_GGSAVED     = GGSAVED;
const char *s_HUSTR_MSGU  = HUSTR_MSGU;
const char *s_HUSTR_E1M1  = HUSTR_E1M1;
const char *s_HUSTR_E1M2  = HUSTR_E1M2;
const char *s_HUSTR_E1M3  = HUSTR_E1M3;
const char *s_HUSTR_E1M4  = HUSTR_E1M4;
const char *s_HUSTR_E1M5  = HUSTR_E1M5;
const char *s_HUSTR_E1M6  = HUSTR_E1M6;
const char *s_HUSTR_E1M7  = HUSTR_E1M7;
const char *s_HUSTR_E1M8  = HUSTR_E1M8;
const char *s_HUSTR_E1M9  = HUSTR_E1M9;
const char *s_HUSTR_E2M1  = HUSTR_E2M1;
const char *s_HUSTR_E2M2  = HUSTR_E2M2;
const char *s_HUSTR_E2M3  = HUSTR_E2M3;
const char *s_HUSTR_E2M4  = HUSTR_E2M4;
const char *s_HUSTR_E2M5  = HUSTR_E2M5;
const char *s_HUSTR_E2M6  = HUSTR_E2M6;
const char *s_HUSTR_E2M7  = HUSTR_E2M7;
const char *s_HUSTR_E2M8  = HUSTR_E2M8;
const char *s_HUSTR_E2M9  = HUSTR_E2M9;
const char *s_HUSTR_E3M1  = HUSTR_E3M1;
const char *s_HUSTR_E3M2  = HUSTR_E3M2;
const char *s_HUSTR_E3M3  = HUSTR_E3M3;
const char *s_HUSTR_E3M4  = HUSTR_E3M4;
const char *s_HUSTR_E3M5  = HUSTR_E3M5;
const char *s_HUSTR_E3M6  = HUSTR_E3M6;
const char *s_HUSTR_E3M7  = HUSTR_E3M7;
const char *s_HUSTR_E3M8  = HUSTR_E3M8;
const char *s_HUSTR_E3M9  = HUSTR_E3M9;
const char *s_HUSTR_E4M1  = HUSTR_E4M1;
const char *s_HUSTR_E4M2  = HUSTR_E4M2;
const char *s_HUSTR_E4M3  = HUSTR_E4M3;
const char *s_HUSTR_E4M4  = HUSTR_E4M4;
const char *s_HUSTR_E4M5  = HUSTR_E4M5;
const char *s_HUSTR_E4M6  = HUSTR_E4M6;
const char *s_HUSTR_E4M7  = HUSTR_E4M7;
const char *s_HUSTR_E4M8  = HUSTR_E4M8;
const char *s_HUSTR_E4M9  = HUSTR_E4M9;
const char *s_HUSTR_1     = HUSTR_1;
const char *s_HUSTR_2     = HUSTR_2;
const char *s_HUSTR_3     = HUSTR_3;
const char *s_HUSTR_4     = HUSTR_4;
const char *s_HUSTR_5     = HUSTR_5;
const char *s_HUSTR_6     = HUSTR_6;
const char *s_HUSTR_7     = HUSTR_7;
const char *s_HUSTR_8     = HUSTR_8;
const char *s_HUSTR_9     = HUSTR_9;
const char *s_HUSTR_10    = HUSTR_10;
const char *s_HUSTR_11    = HUSTR_11;
const char *s_HUSTR_12    = HUSTR_12;
const char *s_HUSTR_13    = HUSTR_13;
const char *s_HUSTR_14    = HUSTR_14;
const char *s_HUSTR_15    = HUSTR_15;
const char *s_HUSTR_16    = HUSTR_16;
const char *s_HUSTR_17    = HUSTR_17;
const char *s_HUSTR_18    = HUSTR_18;
const char *s_HUSTR_19    = HUSTR_19;
const char *s_HUSTR_20    = HUSTR_20;
const char *s_HUSTR_21    = HUSTR_21;
const char *s_HUSTR_22    = HUSTR_22;
const char *s_HUSTR_23    = HUSTR_23;
const char *s_HUSTR_24    = HUSTR_24;
const char *s_HUSTR_25    = HUSTR_25;
const char *s_HUSTR_26    = HUSTR_26;
const char *s_HUSTR_27    = HUSTR_27;
const char *s_HUSTR_28    = HUSTR_28;
const char *s_HUSTR_29    = HUSTR_29;
const char *s_HUSTR_30    = HUSTR_30;
const char *s_HUSTR_31    = HUSTR_31;
const char *s_HUSTR_32    = HUSTR_32;
const char *s_PHUSTR_1    = PHUSTR_1;
const char *s_PHUSTR_2    = PHUSTR_2;
const char *s_PHUSTR_3    = PHUSTR_3;
const char *s_PHUSTR_4    = PHUSTR_4;
const char *s_PHUSTR_5    = PHUSTR_5;
const char *s_PHUSTR_6    = PHUSTR_6;
const char *s_PHUSTR_7    = PHUSTR_7;
const char *s_PHUSTR_8    = PHUSTR_8;
const char *s_PHUSTR_9    = PHUSTR_9;
const char *s_PHUSTR_10   = PHUSTR_10;
const char *s_PHUSTR_11   = PHUSTR_11;
const char *s_PHUSTR_12   = PHUSTR_12;
const char *s_PHUSTR_13   = PHUSTR_13;
const char *s_PHUSTR_14   = PHUSTR_14;
const char *s_PHUSTR_15   = PHUSTR_15;
const char *s_PHUSTR_16   = PHUSTR_16;
const char *s_PHUSTR_17   = PHUSTR_17;
const char *s_PHUSTR_18   = PHUSTR_18;
const char *s_PHUSTR_19   = PHUSTR_19;
const char *s_PHUSTR_20   = PHUSTR_20;
const char *s_PHUSTR_21   = PHUSTR_21;
const char *s_PHUSTR_22   = PHUSTR_22;
const char *s_PHUSTR_23   = PHUSTR_23;
const char *s_PHUSTR_24   = PHUSTR_24;
const char *s_PHUSTR_25   = PHUSTR_25;
const char *s_PHUSTR_26   = PHUSTR_26;
const char *s_PHUSTR_27   = PHUSTR_27;
const char *s_PHUSTR_28   = PHUSTR_28;
const char *s_PHUSTR_29   = PHUSTR_29;
const char *s_PHUSTR_30   = PHUSTR_30;
const char *s_PHUSTR_31   = PHUSTR_31;
const char *s_PHUSTR_32   = PHUSTR_32;
const char *s_THUSTR_1    = THUSTR_1;
const char *s_THUSTR_2    = THUSTR_2;
const char *s_THUSTR_3    = THUSTR_3;
const char *s_THUSTR_4    = THUSTR_4;
const char *s_THUSTR_5    = THUSTR_5;
const char *s_THUSTR_6    = THUSTR_6;
const char *s_THUSTR_7    = THUSTR_7;
const char *s_THUSTR_8    = THUSTR_8;
const char *s_THUSTR_9    = THUSTR_9;
const char *s_THUSTR_10   = THUSTR_10;
const char *s_THUSTR_11   = THUSTR_11;
const char *s_THUSTR_12   = THUSTR_12;
const char *s_THUSTR_13   = THUSTR_13;
const char *s_THUSTR_14   = THUSTR_14;
const char *s_THUSTR_15   = THUSTR_15;
const char *s_THUSTR_16   = THUSTR_16;
const char *s_THUSTR_17   = THUSTR_17;
const char *s_THUSTR_18   = THUSTR_18;
const char *s_THUSTR_19   = THUSTR_19;
const char *s_THUSTR_20   = THUSTR_20;
const char *s_THUSTR_21   = THUSTR_21;
const char *s_THUSTR_22   = THUSTR_22;
const char *s_THUSTR_23   = THUSTR_23;
const char *s_THUSTR_24   = THUSTR_24;
const char *s_THUSTR_25   = THUSTR_25;
const char *s_THUSTR_26   = THUSTR_26;
const char *s_THUSTR_27   = THUSTR_27;
const char *s_THUSTR_28   = THUSTR_28;
const char *s_THUSTR_29   = THUSTR_29;
const char *s_THUSTR_30   = THUSTR_30;
const char *s_THUSTR_31   = THUSTR_31;
const char *s_THUSTR_32   = THUSTR_32;
const char *s_HUSTR_CHATMACRO1   = HUSTR_CHATMACRO1;
const char *s_HUSTR_CHATMACRO2   = HUSTR_CHATMACRO2;
const char *s_HUSTR_CHATMACRO3   = HUSTR_CHATMACRO3;
const char *s_HUSTR_CHATMACRO4   = HUSTR_CHATMACRO4;
const char *s_HUSTR_CHATMACRO5   = HUSTR_CHATMACRO5;
const char *s_HUSTR_CHATMACRO6   = HUSTR_CHATMACRO6;
const char *s_HUSTR_CHATMACRO7   = HUSTR_CHATMACRO7;
const char *s_HUSTR_CHATMACRO8   = HUSTR_CHATMACRO8;
const char *s_HUSTR_CHATMACRO9   = HUSTR_CHATMACRO9;
const char *s_HUSTR_CHATMACRO0   = HUSTR_CHATMACRO0;
const char *s_HUSTR_TALKTOSELF1  = HUSTR_TALKTOSELF1;
const char *s_HUSTR_TALKTOSELF2  = HUSTR_TALKTOSELF2;
const char *s_HUSTR_TALKTOSELF3  = HUSTR_TALKTOSELF3;
const char *s_HUSTR_TALKTOSELF4  = HUSTR_TALKTOSELF4;
const char *s_HUSTR_TALKTOSELF5  = HUSTR_TALKTOSELF5;
const char *s_HUSTR_MESSAGESENT  = HUSTR_MESSAGESENT;
const char *s_HUSTR_PLRGREEN     = HUSTR_PLRGREEN;
const char *s_HUSTR_PLRINDIGO    = HUSTR_PLRINDIGO;
const char *s_HUSTR_PLRBROWN     = HUSTR_PLRBROWN;
const char *s_HUSTR_PLRRED       = HUSTR_PLRRED;
//char sc_HUSTR_KEYGREEN   = HUSTR_KEYGREEN;
//char sc_HUSTR_KEYINDIGO  = HUSTR_KEYINDIGO;
//char sc_HUSTR_KEYBROWN   = HUSTR_KEYBROWN;
//char sc_HUSTR_KEYRED     = HUSTR_KEYRED;
const char *s_AMSTR_FOLLOWON     = AMSTR_FOLLOWON;
const char *s_AMSTR_FOLLOWOFF    = AMSTR_FOLLOWOFF;
const char *s_AMSTR_GRIDON       = AMSTR_GRIDON;
const char *s_AMSTR_GRIDOFF      = AMSTR_GRIDOFF;
const char *s_AMSTR_MARKEDSPOT   = AMSTR_MARKEDSPOT;
const char *s_AMSTR_MARKSCLEARED = AMSTR_MARKSCLEARED;
// CPhipps - automap rotate & overlay
const char* s_AMSTR_ROTATEON     = AMSTR_ROTATEON;
const char* s_AMSTR_ROTATEOFF    = AMSTR_ROTATEOFF;
const char* s_AMSTR_OVERLAYON    = AMSTR_OVERLAYON;
const char* s_AMSTR_OVERLAYOFF   = AMSTR_OVERLAYOFF;
const char *s_STSTR_MUS          = STSTR_MUS;
const char *s_STSTR_NOMUS        = STSTR_NOMUS;
const char *s_STSTR_DQDON        = STSTR_DQDON;
const char *s_STSTR_DQDOFF       = STSTR_DQDOFF;
const char *s_STSTR_KFAADDED     = STSTR_KFAADDED;
const char *s_STSTR_FAADDED      = STSTR_FAADDED;
const char *s_STSTR_NCON         = STSTR_NCON;
const char *s_STSTR_NCOFF        = STSTR_NCOFF;
const char *s_STSTR_BEHOLD       = STSTR_BEHOLD;
const char *s_STSTR_BEHOLDX      = STSTR_BEHOLDX;
const char *s_STSTR_CHOPPERS     = STSTR_CHOPPERS;
const char *s_STSTR_CLEV         = STSTR_CLEV;
const char *s_STSTR_COMPON       = STSTR_COMPON;
const char *s_STSTR_COMPOFF      = STSTR_COMPOFF;
const char *s_E1TEXT     = E1TEXT;
const char *s_E2TEXT     = E2TEXT;
const char *s_E3TEXT     = E3TEXT;
const char *s_E4TEXT     = E4TEXT;
const char *s_C1TEXT     = C1TEXT;
const char *s_C2TEXT     = C2TEXT;
const char *s_C3TEXT     = C3TEXT;
const char *s_C4TEXT     = C4TEXT;
const char *s_C5TEXT     = C5TEXT;
const char *s_C6TEXT     = C6TEXT;
const char *s_P1TEXT     = P1TEXT;
const char *s_P2TEXT     = P2TEXT;
const char *s_P3TEXT     = P3TEXT;
const char *s_P4TEXT     = P4TEXT;
const char *s_P5TEXT     = P5TEXT;
const char *s_P6TEXT     = P6TEXT;
const char *s_T1TEXT     = T1TEXT;
const char *s_T2TEXT     = T2TEXT;
const char *s_T3TEXT     = T3TEXT;
const char *s_T4TEXT     = T4TEXT;
const char *s_T5TEXT     = T5TEXT;
const char *s_T6TEXT     = T6TEXT;
const char *s_CC_ZOMBIE  = CC_ZOMBIE;
const char *s_CC_SHOTGUN = CC_SHOTGUN;
const char *s_CC_HEAVY   = CC_HEAVY;
const char *s_CC_IMP     = CC_IMP;
const char *s_CC_DEMON   = CC_DEMON;
const char *s_CC_LOST    = CC_LOST;
const char *s_CC_CACO    = CC_CACO;
const char *s_CC_HELL    = CC_HELL;
const char *s_CC_BARON   = CC_BARON;
const char *s_CC_ARACH   = CC_ARACH;
const char *s_CC_PAIN    = CC_PAIN;
const char *s_CC_REVEN   = CC_REVEN;
const char *s_CC_MANCU   = CC_MANCU;
const char *s_CC_ARCH    = CC_ARCH;
const char *s_CC_SPIDER  = CC_SPIDER;
const char *s_CC_CYBER   = CC_CYBER;
const char *s_CC_HERO    = CC_HERO;
// Ty 03/30/98 - new substitutions for background textures
//               during int screens
const char *bgflatE1     = "FLOOR4_8"; // end of DOOM Episode 1
const char *bgflatE2     = "SFLR6_1";  // end of DOOM Episode 2
const char *bgflatE3     = "MFLR8_4";  // end of DOOM Episode 3
const char *bgflatE4     = "MFLR8_3";  // end of DOOM Episode 4
const char *bgflat06     = "SLIME16";  // DOOM2 after MAP06
const char *bgflat11     = "RROCK14";  // DOOM2 after MAP11
const char *bgflat20     = "RROCK07";  // DOOM2 after MAP20
const char *bgflat30     = "RROCK17";  // DOOM2 after MAP30
const char *bgflat15     = "RROCK13";  // DOOM2 going MAP15 to MAP31
const char *bgflat31     = "RROCK19";  // DOOM2 going MAP31 to MAP32
const char *bgcastcall   = "BOSSBACK"; // Panel behind cast call

const char *startup1     = "";  // blank lines are default and are not printed
const char *startup2     = "";
const char *startup3     = "";
const char *startup4     = "";
const char *startup5     = "";

/* Ty 05/03/98 - externalized
 * cph - updated for prboom */
const char *savegamename = "prbmsav";

// end d_deh.h variable declarations
// ====================================================================

// Do this for a lookup--the pointer (loaded above) is cross-referenced
// to a string key that is the same as the define above.  We will use
// strdups to set these new values that we read from the file, orphaning
// the original value set above.

// CPhipps - make strings pointed to const
typedef struct {
   const char **ppstr;  // doubly indirect pointer to string
   const char *lookup;  // pointer to lookup string name
} deh_strs;

/* CPhipps - const, static
 *         - removed redundant "Can't XXX in a netgame" strings
 */
static const deh_strs deh_strlookup[] = {
                                           {&s_D_DEVSTR,"D_DEVSTR"},
                                           {&s_D_CDROM,"D_CDROM"},
                                           {&s_PRESSKEY,"PRESSKEY"},
                                           {&s_PRESSYN,"PRESSYN"},
                                           {&s_QUITMSG,"QUITMSG"},
                                           {&s_QSAVESPOT,"QSAVESPOT"},
                                           {&s_SAVEDEAD,"SAVEDEAD"},
                                           /* cph - disabled to prevent format string attacks in WAD files
                                           {&s_QSPROMPT,"QSPROMPT"},
                                           {&s_QLPROMPT,"QLPROMPT"},*/
                                           {&s_NEWGAME,"NEWGAME"},
                                           {&s_RESTARTLEVEL,"RESTARTLEVEL"},
                                           {&s_NIGHTMARE,"NIGHTMARE"},
                                           {&s_SWSTRING,"SWSTRING"},
                                           {&s_MSGOFF,"MSGOFF"},
                                           {&s_MSGON,"MSGON"},
                                           {&s_NETEND,"NETEND"},
                                           {&s_ENDGAME,"ENDGAME"},
                                           {&s_DOSY,"DOSY"},
                                           {&s_DETAILHI,"DETAILHI"},
                                           {&s_DETAILLO,"DETAILLO"},
                                           {&s_GAMMALVL0,"GAMMALVL0"},
                                           {&s_GAMMALVL1,"GAMMALVL1"},
                                           {&s_GAMMALVL2,"GAMMALVL2"},
                                           {&s_GAMMALVL3,"GAMMALVL3"},
                                           {&s_GAMMALVL4,"GAMMALVL4"},
                                           {&s_EMPTYSTRING,"EMPTYSTRING"},
                                           {&s_GOTARMOR,"GOTARMOR"},
                                           {&s_GOTMEGA,"GOTMEGA"},
                                           {&s_GOTHTHBONUS,"GOTHTHBONUS"},
                                           {&s_GOTARMBONUS,"GOTARMBONUS"},
                                           {&s_GOTSTIM,"GOTSTIM"},
                                           {&s_GOTMEDINEED,"GOTMEDINEED"},
                                           {&s_GOTMEDIKIT,"GOTMEDIKIT"},
                                           {&s_GOTSUPER,"GOTSUPER"},
                                           {&s_GOTBLUECARD,"GOTBLUECARD"},
                                           {&s_GOTYELWCARD,"GOTYELWCARD"},
                                           {&s_GOTREDCARD,"GOTREDCARD"},
                                           {&s_GOTBLUESKUL,"GOTBLUESKUL"},
                                           {&s_GOTYELWSKUL,"GOTYELWSKUL"},
                                           {&s_GOTREDSKULL,"GOTREDSKULL"},
                                           {&s_GOTINVUL,"GOTINVUL"},
                                           {&s_GOTBERSERK,"GOTBERSERK"},
                                           {&s_GOTINVIS,"GOTINVIS"},
                                           {&s_GOTSUIT,"GOTSUIT"},
                                           {&s_GOTMAP,"GOTMAP"},
                                           {&s_GOTVISOR,"GOTVISOR"},
                                           {&s_GOTMSPHERE,"GOTMSPHERE"},
                                           {&s_GOTCLIP,"GOTCLIP"},
                                           {&s_GOTCLIPBOX,"GOTCLIPBOX"},
                                           {&s_GOTROCKET,"GOTROCKET"},
                                           {&s_GOTROCKBOX,"GOTROCKBOX"},
                                           {&s_GOTCELL,"GOTCELL"},
                                           {&s_GOTCELLBOX,"GOTCELLBOX"},
                                           {&s_GOTSHELLS,"GOTSHELLS"},
                                           {&s_GOTSHELLBOX,"GOTSHELLBOX"},
                                           {&s_GOTBACKPACK,"GOTBACKPACK"},
                                           {&s_GOTBFG9000,"GOTBFG9000"},
                                           {&s_GOTCHAINGUN,"GOTCHAINGUN"},
                                           {&s_GOTCHAINSAW,"GOTCHAINSAW"},
                                           {&s_GOTLAUNCHER,"GOTLAUNCHER"},
                                           {&s_GOTPLASMA,"GOTPLASMA"},
                                           {&s_GOTSHOTGUN,"GOTSHOTGUN"},
                                           {&s_GOTSHOTGUN2,"GOTSHOTGUN2"},
                                           {&s_PD_BLUEO,"PD_BLUEO"},
                                           {&s_PD_REDO,"PD_REDO"},
                                           {&s_PD_YELLOWO,"PD_YELLOWO"},
                                           {&s_PD_BLUEK,"PD_BLUEK"},
                                           {&s_PD_REDK,"PD_REDK"},
                                           {&s_PD_YELLOWK,"PD_YELLOWK"},
                                           {&s_PD_BLUEC,"PD_BLUEC"},
                                           {&s_PD_REDC,"PD_REDC"},
                                           {&s_PD_YELLOWC,"PD_YELLOWC"},
                                           {&s_PD_BLUES,"PD_BLUES"},
                                           {&s_PD_REDS,"PD_REDS"},
                                           {&s_PD_YELLOWS,"PD_YELLOWS"},
                                           {&s_PD_ANY,"PD_ANY"},
                                           {&s_PD_ALL3,"PD_ALL3"},
                                           {&s_PD_ALL6,"PD_ALL6"},
                                           {&s_GGSAVED,"GGSAVED"},
                                           {&s_HUSTR_MSGU,"HUSTR_MSGU"},
                                           {&s_HUSTR_E1M1,"HUSTR_E1M1"},
                                           {&s_HUSTR_E1M2,"HUSTR_E1M2"},
                                           {&s_HUSTR_E1M3,"HUSTR_E1M3"},
                                           {&s_HUSTR_E1M4,"HUSTR_E1M4"},
                                           {&s_HUSTR_E1M5,"HUSTR_E1M5"},
                                           {&s_HUSTR_E1M6,"HUSTR_E1M6"},
                                           {&s_HUSTR_E1M7,"HUSTR_E1M7"},
                                           {&s_HUSTR_E1M8,"HUSTR_E1M8"},
                                           {&s_HUSTR_E1M9,"HUSTR_E1M9"},
                                           {&s_HUSTR_E2M1,"HUSTR_E2M1"},
                                           {&s_HUSTR_E2M2,"HUSTR_E2M2"},
                                           {&s_HUSTR_E2M3,"HUSTR_E2M3"},
                                           {&s_HUSTR_E2M4,"HUSTR_E2M4"},
                                           {&s_HUSTR_E2M5,"HUSTR_E2M5"},
                                           {&s_HUSTR_E2M6,"HUSTR_E2M6"},
                                           {&s_HUSTR_E2M7,"HUSTR_E2M7"},
                                           {&s_HUSTR_E2M8,"HUSTR_E2M8"},
                                           {&s_HUSTR_E2M9,"HUSTR_E2M9"},
                                           {&s_HUSTR_E3M1,"HUSTR_E3M1"},
                                           {&s_HUSTR_E3M2,"HUSTR_E3M2"},
                                           {&s_HUSTR_E3M3,"HUSTR_E3M3"},
                                           {&s_HUSTR_E3M4,"HUSTR_E3M4"},
                                           {&s_HUSTR_E3M5,"HUSTR_E3M5"},
                                           {&s_HUSTR_E3M6,"HUSTR_E3M6"},
                                           {&s_HUSTR_E3M7,"HUSTR_E3M7"},
                                           {&s_HUSTR_E3M8,"HUSTR_E3M8"},
                                           {&s_HUSTR_E3M9,"HUSTR_E3M9"},
                                           {&s_HUSTR_E4M1,"HUSTR_E4M1"},
                                           {&s_HUSTR_E4M2,"HUSTR_E4M2"},
                                           {&s_HUSTR_E4M3,"HUSTR_E4M3"},
                                           {&s_HUSTR_E4M4,"HUSTR_E4M4"},
                                           {&s_HUSTR_E4M5,"HUSTR_E4M5"},
                                           {&s_HUSTR_E4M6,"HUSTR_E4M6"},
                                           {&s_HUSTR_E4M7,"HUSTR_E4M7"},
                                           {&s_HUSTR_E4M8,"HUSTR_E4M8"},
                                           {&s_HUSTR_E4M9,"HUSTR_E4M9"},
                                           {&s_HUSTR_1,"HUSTR_1"},
                                           {&s_HUSTR_2,"HUSTR_2"},
                                           {&s_HUSTR_3,"HUSTR_3"},
                                           {&s_HUSTR_4,"HUSTR_4"},
                                           {&s_HUSTR_5,"HUSTR_5"},
                                           {&s_HUSTR_6,"HUSTR_6"},
                                           {&s_HUSTR_7,"HUSTR_7"},
                                           {&s_HUSTR_8,"HUSTR_8"},
                                           {&s_HUSTR_9,"HUSTR_9"},
                                           {&s_HUSTR_10,"HUSTR_10"},
                                           {&s_HUSTR_11,"HUSTR_11"},
                                           {&s_HUSTR_12,"HUSTR_12"},
                                           {&s_HUSTR_13,"HUSTR_13"},
                                           {&s_HUSTR_14,"HUSTR_14"},
                                           {&s_HUSTR_15,"HUSTR_15"},
                                           {&s_HUSTR_16,"HUSTR_16"},
                                           {&s_HUSTR_17,"HUSTR_17"},
                                           {&s_HUSTR_18,"HUSTR_18"},
                                           {&s_HUSTR_19,"HUSTR_19"},
                                           {&s_HUSTR_20,"HUSTR_20"},
                                           {&s_HUSTR_21,"HUSTR_21"},
                                           {&s_HUSTR_22,"HUSTR_22"},
                                           {&s_HUSTR_23,"HUSTR_23"},
                                           {&s_HUSTR_24,"HUSTR_24"},
                                           {&s_HUSTR_25,"HUSTR_25"},
                                           {&s_HUSTR_26,"HUSTR_26"},
                                           {&s_HUSTR_27,"HUSTR_27"},
                                           {&s_HUSTR_28,"HUSTR_28"},
                                           {&s_HUSTR_29,"HUSTR_29"},
                                           {&s_HUSTR_30,"HUSTR_30"},
                                           {&s_HUSTR_31,"HUSTR_31"},
                                           {&s_HUSTR_32,"HUSTR_32"},
                                           {&s_PHUSTR_1,"PHUSTR_1"},
                                           {&s_PHUSTR_2,"PHUSTR_2"},
                                           {&s_PHUSTR_3,"PHUSTR_3"},
                                           {&s_PHUSTR_4,"PHUSTR_4"},
                                           {&s_PHUSTR_5,"PHUSTR_5"},
                                           {&s_PHUSTR_6,"PHUSTR_6"},
                                           {&s_PHUSTR_7,"PHUSTR_7"},
                                           {&s_PHUSTR_8,"PHUSTR_8"},
                                           {&s_PHUSTR_9,"PHUSTR_9"},
                                           {&s_PHUSTR_10,"PHUSTR_10"},
                                           {&s_PHUSTR_11,"PHUSTR_11"},
                                           {&s_PHUSTR_12,"PHUSTR_12"},
                                           {&s_PHUSTR_13,"PHUSTR_13"},
                                           {&s_PHUSTR_14,"PHUSTR_14"},
                                           {&s_PHUSTR_15,"PHUSTR_15"},
                                           {&s_PHUSTR_16,"PHUSTR_16"},
                                           {&s_PHUSTR_17,"PHUSTR_17"},
                                           {&s_PHUSTR_18,"PHUSTR_18"},
                                           {&s_PHUSTR_19,"PHUSTR_19"},
                                           {&s_PHUSTR_20,"PHUSTR_20"},
                                           {&s_PHUSTR_21,"PHUSTR_21"},
                                           {&s_PHUSTR_22,"PHUSTR_22"},
                                           {&s_PHUSTR_23,"PHUSTR_23"},
                                           {&s_PHUSTR_24,"PHUSTR_24"},
                                           {&s_PHUSTR_25,"PHUSTR_25"},
                                           {&s_PHUSTR_26,"PHUSTR_26"},
                                           {&s_PHUSTR_27,"PHUSTR_27"},
                                           {&s_PHUSTR_28,"PHUSTR_28"},
                                           {&s_PHUSTR_29,"PHUSTR_29"},
                                           {&s_PHUSTR_30,"PHUSTR_30"},
                                           {&s_PHUSTR_31,"PHUSTR_31"},
                                           {&s_PHUSTR_32,"PHUSTR_32"},
                                           {&s_THUSTR_1,"THUSTR_1"},
                                           {&s_THUSTR_2,"THUSTR_2"},
                                           {&s_THUSTR_3,"THUSTR_3"},
                                           {&s_THUSTR_4,"THUSTR_4"},
                                           {&s_THUSTR_5,"THUSTR_5"},
                                           {&s_THUSTR_6,"THUSTR_6"},
                                           {&s_THUSTR_7,"THUSTR_7"},
                                           {&s_THUSTR_8,"THUSTR_8"},
                                           {&s_THUSTR_9,"THUSTR_9"},
                                           {&s_THUSTR_10,"THUSTR_10"},
                                           {&s_THUSTR_11,"THUSTR_11"},
                                           {&s_THUSTR_12,"THUSTR_12"},
                                           {&s_THUSTR_13,"THUSTR_13"},
                                           {&s_THUSTR_14,"THUSTR_14"},
                                           {&s_THUSTR_15,"THUSTR_15"},
                                           {&s_THUSTR_16,"THUSTR_16"},
                                           {&s_THUSTR_17,"THUSTR_17"},
                                           {&s_THUSTR_18,"THUSTR_18"},
                                           {&s_THUSTR_19,"THUSTR_19"},
                                           {&s_THUSTR_20,"THUSTR_20"},
                                           {&s_THUSTR_21,"THUSTR_21"},
                                           {&s_THUSTR_22,"THUSTR_22"},
                                           {&s_THUSTR_23,"THUSTR_23"},
                                           {&s_THUSTR_24,"THUSTR_24"},
                                           {&s_THUSTR_25,"THUSTR_25"},
                                           {&s_THUSTR_26,"THUSTR_26"},
                                           {&s_THUSTR_27,"THUSTR_27"},
                                           {&s_THUSTR_28,"THUSTR_28"},
                                           {&s_THUSTR_29,"THUSTR_29"},
                                           {&s_THUSTR_30,"THUSTR_30"},
                                           {&s_THUSTR_31,"THUSTR_31"},
                                           {&s_THUSTR_32,"THUSTR_32"},
                                           {&s_HUSTR_CHATMACRO1,"HUSTR_CHATMACRO1"},
                                           {&s_HUSTR_CHATMACRO2,"HUSTR_CHATMACRO2"},
                                           {&s_HUSTR_CHATMACRO3,"HUSTR_CHATMACRO3"},
                                           {&s_HUSTR_CHATMACRO4,"HUSTR_CHATMACRO4"},
                                           {&s_HUSTR_CHATMACRO5,"HUSTR_CHATMACRO5"},
                                           {&s_HUSTR_CHATMACRO6,"HUSTR_CHATMACRO6"},
                                           {&s_HUSTR_CHATMACRO7,"HUSTR_CHATMACRO7"},
                                           {&s_HUSTR_CHATMACRO8,"HUSTR_CHATMACRO8"},
                                           {&s_HUSTR_CHATMACRO9,"HUSTR_CHATMACRO9"},
                                           {&s_HUSTR_CHATMACRO0,"HUSTR_CHATMACRO0"},
                                           {&s_HUSTR_TALKTOSELF1,"HUSTR_TALKTOSELF1"},
                                           {&s_HUSTR_TALKTOSELF2,"HUSTR_TALKTOSELF2"},
                                           {&s_HUSTR_TALKTOSELF3,"HUSTR_TALKTOSELF3"},
                                           {&s_HUSTR_TALKTOSELF4,"HUSTR_TALKTOSELF4"},
                                           {&s_HUSTR_TALKTOSELF5,"HUSTR_TALKTOSELF5"},
                                           {&s_HUSTR_MESSAGESENT,"HUSTR_MESSAGESENT"},
                                           {&s_HUSTR_PLRGREEN,"HUSTR_PLRGREEN"},
                                           {&s_HUSTR_PLRINDIGO,"HUSTR_PLRINDIGO"},
                                           {&s_HUSTR_PLRBROWN,"HUSTR_PLRBROWN"},
                                           {&s_HUSTR_PLRRED,"HUSTR_PLRRED"},
                                           //{c_HUSTR_KEYGREEN,"HUSTR_KEYGREEN"},
                                           //{c_HUSTR_KEYINDIGO,"HUSTR_KEYINDIGO"},
                                           //{c_HUSTR_KEYBROWN,"HUSTR_KEYBROWN"},
                                           //{c_HUSTR_KEYRED,"HUSTR_KEYRED"},
                                           {&s_AMSTR_FOLLOWON,"AMSTR_FOLLOWON"},
                                           {&s_AMSTR_FOLLOWOFF,"AMSTR_FOLLOWOFF"},
                                           {&s_AMSTR_GRIDON,"AMSTR_GRIDON"},
                                           {&s_AMSTR_GRIDOFF,"AMSTR_GRIDOFF"},
                                           {&s_AMSTR_MARKEDSPOT,"AMSTR_MARKEDSPOT"},
                                           {&s_AMSTR_MARKSCLEARED,"AMSTR_MARKSCLEARED"},
                                           {&s_STSTR_MUS,"STSTR_MUS"},
                                           {&s_STSTR_NOMUS,"STSTR_NOMUS"},
                                           {&s_STSTR_DQDON,"STSTR_DQDON"},
                                           {&s_STSTR_DQDOFF,"STSTR_DQDOFF"},
                                           {&s_STSTR_KFAADDED,"STSTR_KFAADDED"},
                                           {&s_STSTR_FAADDED,"STSTR_FAADDED"},
                                           {&s_STSTR_NCON,"STSTR_NCON"},
                                           {&s_STSTR_NCOFF,"STSTR_NCOFF"},
                                           {&s_STSTR_BEHOLD,"STSTR_BEHOLD"},
                                           {&s_STSTR_BEHOLDX,"STSTR_BEHOLDX"},
                                           {&s_STSTR_CHOPPERS,"STSTR_CHOPPERS"},
                                           {&s_STSTR_CLEV,"STSTR_CLEV"},
                                           {&s_STSTR_COMPON,"STSTR_COMPON"},
                                           {&s_STSTR_COMPOFF,"STSTR_COMPOFF"},
                                           {&s_E1TEXT,"E1TEXT"},
                                           {&s_E2TEXT,"E2TEXT"},
                                           {&s_E3TEXT,"E3TEXT"},
                                           {&s_E4TEXT,"E4TEXT"},
                                           {&s_C1TEXT,"C1TEXT"},
                                           {&s_C2TEXT,"C2TEXT"},
                                           {&s_C3TEXT,"C3TEXT"},
                                           {&s_C4TEXT,"C4TEXT"},
                                           {&s_C5TEXT,"C5TEXT"},
                                           {&s_C6TEXT,"C6TEXT"},
                                           {&s_P1TEXT,"P1TEXT"},
                                           {&s_P2TEXT,"P2TEXT"},
                                           {&s_P3TEXT,"P3TEXT"},
                                           {&s_P4TEXT,"P4TEXT"},
                                           {&s_P5TEXT,"P5TEXT"},
                                           {&s_P6TEXT,"P6TEXT"},
                                           {&s_T1TEXT,"T1TEXT"},
                                           {&s_T2TEXT,"T2TEXT"},
                                           {&s_T3TEXT,"T3TEXT"},
                                           {&s_T4TEXT,"T4TEXT"},
                                           {&s_T5TEXT,"T5TEXT"},
                                           {&s_T6TEXT,"T6TEXT"},
                                           {&s_CC_ZOMBIE,"CC_ZOMBIE"},
                                           {&s_CC_SHOTGUN,"CC_SHOTGUN"},
                                           {&s_CC_HEAVY,"CC_HEAVY"},
                                           {&s_CC_IMP,"CC_IMP"},
                                           {&s_CC_DEMON,"CC_DEMON"},
                                           {&s_CC_LOST,"CC_LOST"},
                                           {&s_CC_CACO,"CC_CACO"},
                                           {&s_CC_HELL,"CC_HELL"},
                                           {&s_CC_BARON,"CC_BARON"},
                                           {&s_CC_ARACH,"CC_ARACH"},
                                           {&s_CC_PAIN,"CC_PAIN"},
                                           {&s_CC_REVEN,"CC_REVEN"},
                                           {&s_CC_MANCU,"CC_MANCU"},
                                           {&s_CC_ARCH,"CC_ARCH"},
                                           {&s_CC_SPIDER,"CC_SPIDER"},
                                           {&s_CC_CYBER,"CC_CYBER"},
                                           {&s_CC_HERO,"CC_HERO"},
                                           {&bgflatE1,"BGFLATE1"},
                                           {&bgflatE2,"BGFLATE2"},
                                           {&bgflatE3,"BGFLATE3"},
                                           {&bgflatE4,"BGFLATE4"},
                                           {&bgflat06,"BGFLAT06"},
                                           {&bgflat11,"BGFLAT11"},
                                           {&bgflat20,"BGFLAT20"},
                                           {&bgflat30,"BGFLAT30"},
                                           {&bgflat15,"BGFLAT15"},
                                           {&bgflat31,"BGFLAT31"},
                                           {&bgcastcall,"BGCASTCALL"},
                                           // Ty 04/08/98 - added 5 general purpose startup announcement
                                           // strings for hacker use.  See m_menu.c
                                           {&startup1,"STARTUP1"},
                                           {&startup2,"STARTUP2"},
                                           {&startup3,"STARTUP3"},
                                           {&startup4,"STARTUP4"},
                                           {&startup5,"STARTUP5"},
                                           {&savegamename,"SAVEGAMENAME"},  // Ty 05/03/98
                                        };

static int deh_numstrlookup =
   sizeof(deh_strlookup)/sizeof(deh_strlookup[0]);

const char *deh_newlevel = "NEWLEVEL"; // CPhipps - const

// DOOM shareware/registered/retail (Ultimate) names.
// CPhipps - const**const
const char **const mapnames[] =
   {
      &s_HUSTR_E1M1,
      &s_HUSTR_E1M2,
      &s_HUSTR_E1M3,
      &s_HUSTR_E1M4,
      &s_HUSTR_E1M5,
      &s_HUSTR_E1M6,
      &s_HUSTR_E1M7,
      &s_HUSTR_E1M8,
      &s_HUSTR_E1M9,

      &s_HUSTR_E2M1,
      &s_HUSTR_E2M2,
      &s_HUSTR_E2M3,
      &s_HUSTR_E2M4,
      &s_HUSTR_E2M5,
      &s_HUSTR_E2M6,
      &s_HUSTR_E2M7,
      &s_HUSTR_E2M8,
      &s_HUSTR_E2M9,

      &s_HUSTR_E3M1,
      &s_HUSTR_E3M2,
      &s_HUSTR_E3M3,
      &s_HUSTR_E3M4,
      &s_HUSTR_E3M5,
      &s_HUSTR_E3M6,
      &s_HUSTR_E3M7,
      &s_HUSTR_E3M8,
      &s_HUSTR_E3M9,

      &s_HUSTR_E4M1,
      &s_HUSTR_E4M2,
      &s_HUSTR_E4M3,
      &s_HUSTR_E4M4,
      &s_HUSTR_E4M5,
      &s_HUSTR_E4M6,
      &s_HUSTR_E4M7,
      &s_HUSTR_E4M8,
      &s_HUSTR_E4M9,

      &deh_newlevel,  // spares?  Unused.
      &deh_newlevel,
      &deh_newlevel,
      &deh_newlevel,
      &deh_newlevel,
      &deh_newlevel,
      &deh_newlevel,
      &deh_newlevel,
      &deh_newlevel
   };

// CPhipps - const**const
const char **const mapnames2[] = // DOOM 2 map names.
   {
      &s_HUSTR_1,
      &s_HUSTR_2,
      &s_HUSTR_3,
      &s_HUSTR_4,
      &s_HUSTR_5,
      &s_HUSTR_6,
      &s_HUSTR_7,
      &s_HUSTR_8,
      &s_HUSTR_9,
      &s_HUSTR_10,
      &s_HUSTR_11,

      &s_HUSTR_12,
      &s_HUSTR_13,
      &s_HUSTR_14,
      &s_HUSTR_15,
      &s_HUSTR_16,
      &s_HUSTR_17,
      &s_HUSTR_18,
      &s_HUSTR_19,
      &s_HUSTR_20,

      &s_HUSTR_21,
      &s_HUSTR_22,
      &s_HUSTR_23,
      &s_HUSTR_24,
      &s_HUSTR_25,
      &s_HUSTR_26,
      &s_HUSTR_27,
      &s_HUSTR_28,
      &s_HUSTR_29,
      &s_HUSTR_30,
      &s_HUSTR_31,
      &s_HUSTR_32,
   };

// CPhipps - const**const
const char **const mapnamesp[] = // Plutonia WAD map names.
   {
      &s_PHUSTR_1,
      &s_PHUSTR_2,
      &s_PHUSTR_3,
      &s_PHUSTR_4,
      &s_PHUSTR_5,
      &s_PHUSTR_6,
      &s_PHUSTR_7,
      &s_PHUSTR_8,
      &s_PHUSTR_9,
      &s_PHUSTR_10,
      &s_PHUSTR_11,

      &s_PHUSTR_12,
      &s_PHUSTR_13,
      &s_PHUSTR_14,
      &s_PHUSTR_15,
      &s_PHUSTR_16,
      &s_PHUSTR_17,
      &s_PHUSTR_18,
      &s_PHUSTR_19,
      &s_PHUSTR_20,

      &s_PHUSTR_21,
      &s_PHUSTR_22,
      &s_PHUSTR_23,
      &s_PHUSTR_24,
      &s_PHUSTR_25,
      &s_PHUSTR_26,
      &s_PHUSTR_27,
      &s_PHUSTR_28,
      &s_PHUSTR_29,
      &s_PHUSTR_30,
      &s_PHUSTR_31,
      &s_PHUSTR_32,
   };

// CPhipps - const**const
const char **const mapnamest[] = // TNT WAD map names.
   {
      &s_THUSTR_1,
      &s_THUSTR_2,
      &s_THUSTR_3,
      &s_THUSTR_4,
      &s_THUSTR_5,
      &s_THUSTR_6,
      &s_THUSTR_7,
      &s_THUSTR_8,
      &s_THUSTR_9,
      &s_THUSTR_10,
      &s_THUSTR_11,

      &s_THUSTR_12,
      &s_THUSTR_13,
      &s_THUSTR_14,
      &s_THUSTR_15,
      &s_THUSTR_16,
      &s_THUSTR_17,
      &s_THUSTR_18,
      &s_THUSTR_19,
      &s_THUSTR_20,

      &s_THUSTR_21,
      &s_THUSTR_22,
      &s_THUSTR_23,
      &s_THUSTR_24,
      &s_THUSTR_25,
      &s_THUSTR_26,
      &s_THUSTR_27,
      &s_THUSTR_28,
      &s_THUSTR_29,
      &s_THUSTR_30,
      &s_THUSTR_31,
      &s_THUSTR_32,
   };

// Function prototypes
void    rstrip(char *);      // strip trailing whitespace
char *  ptr_lstrip(char *);  // point past leading whitespace
boolean deh_GetData(char *, char *, uint_64_t *, char **, int );
boolean deh_procStringSub(char *, char *, char *, int );
char *  dehReformatStr(char *);

// Prototypes for block processing functions
// Pointers to these functions are used as the blocks are encountered.

void deh_procThing(DEHFILE *, int, char *);
void deh_procFrame(DEHFILE *, int, char *);
void deh_procPointer(DEHFILE *, int, char *);
void deh_procSounds(DEHFILE *, int, char *);
void deh_procAmmo(DEHFILE *, int, char *);
void deh_procWeapon(DEHFILE *, int, char *);
void deh_procSprite(DEHFILE *, int, char *);
void deh_procCheat(DEHFILE *, int, char *);
void deh_procMisc(DEHFILE *, int, char *);
void deh_procText(DEHFILE *, int, char *);
void deh_procPars(DEHFILE *, int, char *);
void deh_procStrings(DEHFILE *, int, char *);
void deh_procError(DEHFILE *, int, char *);
void deh_procBexCodePointers(DEHFILE *, int, char *);

// Structure deh_block is used to hold the block names that can
// be encountered, and the routines to use to decipher them

typedef struct
{
   const char *key;       // a mnemonic block code name // CPhipps - const*
   void (*const fptr)(DEHFILE *, int, char *); // handler
} deh_block;

#define DEH_BUFFERMAX 1024 // input buffer area size, hardcodedfor now
// killough 8/9/98: make DEH_BLOCKMAX self-adjusting
#define DEH_BLOCKMAX (sizeof deh_blocks/sizeof*deh_blocks)  // size of array
#define DEH_MAXKEYLEN 32 // as much of any key as we'll look at
#define DEH_MOBJINFOMAX 24 // number of ints in the mobjinfo_t structure (!)

// Put all the block header values, and the function to be called when that
// one is encountered, in this array:
static const deh_block deh_blocks[] = { // CPhipps - static const
                                         /* 0 */  {"Thing",deh_procThing},
                                         /* 1 */  {"Frame",deh_procFrame},
                                         /* 2 */  {"Pointer",deh_procPointer},
                                         /* 3 */  {"Sound",deh_procSounds},  // Ty 03/16/98 corrected from "Sounds"
                                         /* 4 */  {"Ammo",deh_procAmmo},
                                         /* 5 */  {"Weapon",deh_procWeapon},
                                         /* 6 */  {"Sprite",deh_procSprite},
                                         /* 7 */  {"Cheat",deh_procCheat},
                                         /* 8 */  {"Misc",deh_procMisc},
                                         /* 9 */  {"Text",deh_procText},  // --  end of standard "deh" entries,

                                         //     begin BOOM Extensions (BEX)

                                         /* 10 */ {"[STRINGS]",deh_procStrings}, // new string changes
                                         /* 11 */ {"[PARS]",deh_procPars}, // alternative block marker
                                         /* 12 */ {"[CODEPTR]",deh_procBexCodePointers}, // bex codepointers by mnemonic
                                         /* 13 */ {"",deh_procError} // dummy to handle anything else
                                      };

// flag to skip included deh-style text, used with INCLUDE NOTEXT directive
static boolean includenotext = false;

// MOBJINFO - Dehacked block name = "Thing"
// Usage: Thing nn (name)
// These are for mobjinfo_t types.  Each is an integer
// within the structure, so we can use index of the string in this
// array to offset by sizeof(int) into the mobjinfo_t array at [nn]
// * things are base zero but dehacked considers them to start at #1. ***
// CPhipps - static const

static const char *deh_mobjinfo[DEH_MOBJINFOMAX] =
   {
      "ID #",                // .doomednum
      "Initial frame",       // .spawnstate
      "Hit points",          // .spawnhealth
      "First moving frame",  // .seestate
      "Alert sound",         // .seesound
      "Reaction time",       // .reactiontime
      "Attack sound",        // .attacksound
      "Injury frame",        // .painstate
      "Pain chance",         // .painchance
      "Pain sound",          // .painsound
      "Close attack frame",  // .meleestate
      "Far attack frame",    // .missilestate
      "Death frame",         // .deathstate
      "Exploding frame",     // .xdeathstate
      "Death sound",         // .deathsound
      "Speed",               // .speed
      "Width",               // .radius
      "Height",              // .height
      "Mass",                // .mass
      "Missile damage",      // .damage
      "Action sound",        // .activesound
      "Bits",                // .flags
      "Bits2",               // .flags
      "Respawn frame"        // .raisestate
   };

// Strings that are used to indicate flags ("Bits" in mobjinfo)
// This is an array of bit masks that are related to p_mobj.h
// values, using the smae names without the MF_ in front.
// Ty 08/27/98 new code
//
// killough 10/98:
//
// Convert array to struct to allow multiple values, make array size variable

#define DEH_MOBJFLAGMAX (sizeof deh_mobjflags/sizeof*deh_mobjflags)

struct deh_mobjflags_s {
   const char *name; // CPhipps - const*
   uint_64_t value;
};

// CPhipps - static const
static const struct deh_mobjflags_s deh_mobjflags[] = {
         {"SPECIAL",      MF_SPECIAL}, // call  P_Specialthing when touched
         {"SOLID",        MF_SOLID}, // block movement
         {"SHOOTABLE",    MF_SHOOTABLE}, // can be hit
         {"NOSECTOR",     MF_NOSECTOR}, // invisible but touchable
         {"NOBLOCKMAP",   MF_NOBLOCKMAP}, // inert but displayable
         {"AMBUSH",       MF_AMBUSH}, // deaf monster
         {"JUSTHIT",      MF_JUSTHIT}, // will try to attack right back
         {"JUSTATTACKED", MF_JUSTATTACKED}, // take at least 1 step before attacking
         {"SPAWNCEILING", MF_SPAWNCEILING}, // initially hang from ceiling
         {"NOGRAVITY",    MF_NOGRAVITY}, // don't apply gravity during play
         {"DROPOFF",      MF_DROPOFF}, // can jump from high places
         {"PICKUP",       MF_PICKUP}, // will pick up items
         {"NOCLIP",       MF_NOCLIP}, // goes through walls
         {"SLIDE",        MF_SLIDE}, // keep info about sliding along walls
         {"FLOAT",        MF_FLOAT}, // allow movement to any height
         {"TELEPORT",     MF_TELEPORT}, // don't cross lines or look at heights
         {"MISSILE",      MF_MISSILE}, // don't hit same species, explode on block
         {"DROPPED",      MF_DROPPED}, // dropped, not spawned (like ammo clip)
         {"SHADOW",       MF_SHADOW}, // use fuzzy draw like spectres
         {"NOBLOOD",      MF_NOBLOOD}, // puffs instead of blood when shot
         {"CORPSE",       MF_CORPSE}, // so it will slide down steps when dead
         {"INFLOAT",      MF_INFLOAT}, // float but not to target height
         {"COUNTKILL",    MF_COUNTKILL}, // count toward the kills total
         {"COUNTITEM",    MF_COUNTITEM}, // count toward the items total
         {"SKULLFLY",     MF_SKULLFLY}, // special handling for flying skulls
         {"NOTDMATCH",    MF_NOTDMATCH}, // do not spawn in deathmatch

         // killough 10/98: TRANSLATION consists of 2 bits, not 1:

         {"TRANSLATION",  MF_TRANSLATION1}, // for Boom bug-compatibility
         {"TRANSLATION1", MF_TRANSLATION1}, // use translation table for color (players)
         {"TRANSLATION2", MF_TRANSLATION2}, // use translation table for color (players)
         {"UNUSED1",      MF_TRANSLATION2}, // unused bit # 1 -- For Boom bug-compatibility
         {"UNUSED2",      MF_STEALTH}, // unused bit # 2 -- For Boom compatibility
         {"STEALTH",      MF_STEALTH}, // for stealth monsters
         {"UNUSED3",      MF_TRANSLUC25}, // unused bit # 3 -- For Boom compatibility
         {"UNUSED4",      MF_TRANSLUC50}, // unused bit # 4 -- For Boom compatibility
         {"TRANSLUC25",   MF_TRANSLUC25}, // Translucency 25%
         {"TRANSLUC50",   MF_TRANSLUC50}, // Translucency 50%
         {"TRANSLUC75",   MF_TRANSLUC75}, // Translucency 25% + Translucency 50% = 75%
         {"TRANSLUCENT",  MF_TRANSLUCENT}, // apply translucency to sprite (BOOM)
         {"TOUCHY",       MF_TOUCHY},// dies on contact with solid objects (MBF)
         {"BOUNCES",      MF_BOUNCES},// bounces off floors, ceilings and maybe walls
         {"FRIEND",       MF_FRIEND},// a friend of the player(s) (MBF)
      };

// STATE - Dehacked block name = "Frame" and "Pointer"
// Usage: Frame nn
// Usage: Pointer nn (Frame nn)
// These are indexed separately, for lookup to the actual
// function pointers.  Here we'll take whatever Dehacked gives
// us and go from there.  The (Frame nn) after the pointer is the
// real place to put this value.  The "Pointer" value is an xref
// that Dehacked uses and is useless to us.
// * states are base zero and have a dummy #0 (TROO)

static const char *deh_state[] = // CPhipps - static const*
   {
      "Sprite number",    // .sprite (spritenum_t) // an enum
      "Sprite subnumber", // .frame (long)
      "Duration",         // .tics (long)
      "Next frame",       // .nextstate (statenum_t)
      // This is set in a separate "Pointer" block from Dehacked
      "Codep Frame",      // pointer to first use of action (actionf_t)
      "Unknown 1",        // .misc1 (long)
      "Unknown 2"         // .misc2 (long)
   };

// SFXINFO_STRUCT - Dehacked block name = "Sounds"
// Sound effects, typically not changed (redirected, and new sfx put
// into the pwad, but not changed here.  Can you tell that Gregdidn't
// know what they were for, mostly?  Can you tell that I don't either?
// Mostly I just put these into the same slots as they are in the struct.
// This may not be supported in our -deh option if it doesn't make sense by then.

// * sounds are base zero but have a dummy #0

static const char *deh_sfxinfo[] = // CPhipps - static const*
   {
      "Offset",     // pointer to a name string, changed in text
      "Zero/One",   // .singularity (int, one at a time flag)
      "Value",      // .priority
      "Zero 1",     // .link (sfxinfo_t*) referenced sound if linked
      "Zero 2",     // .pitch
      "Zero 3",     // .volume
      "Zero 4",     // .data (SAMPLE*) sound data
      "Neg. One 1", // .usefulness
      "Neg. One 2"  // .lumpnum
   };

// MUSICINFO is not supported in Dehacked.  Ignored here.
// * music entries are base zero but have a dummy #0

#if 0
// CPhipps - unused?
// SPRITE - Dehacked block name = "Sprite"
// Usage = Sprite nn
// Sprite redirection by offset into the text area - unsupported by BOOM
// * sprites are base zero and dehacked uses it that way.

static const char *deh_sprite[] = // CPhipps - static const*
   {
      "Offset"      // supposed to be the offset into the text section
   };
#endif

// AMMO - Dehacked block name = "Ammo"
// usage = Ammo n (name)
// Ammo information for the few types of ammo

static const char *deh_ammo[] = // CPhipps - static const*
   {
      "Max ammo",   // maxammo[]
      "Per ammo"    // clipammo[]
   };

// WEAPONS - Dehacked block name = "Weapon"
// Usage: Weapon nn (name)
// Basically a list of frames and what kind of ammo (see above)it uses.

static const char *deh_weapon[] = // CPhipps - static const*
   {
      "Ammo type",      // .ammo
      "Deselect frame", // .upstate
      "Select frame",   // .downstate
      "Bobbing frame",  // .readystate
      "Shooting frame", // .atkstate
      "Firing frame"    // .flashstate
   };

// CHEATS - Dehacked block name = "Cheat"
// Usage: Cheat 0
// Always uses a zero in the dehacked file, for consistency.  No meaning.
// These are just plain funky terms compared with id's
//
// killough 4/18/98: integrated into main cheat table now (see st_stuff.c)

// MISC - Dehacked block name = "Misc"
// Usage: Misc 0
// Always uses a zero in the dehacked file, for consistency.  No meaning.

static const char *deh_misc[] = // CPhipps - static const*
   {
      "Initial Health",    // initial_health
      "Initial Bullets",   // initial_bullets
      "Max Health",        // maxhealth
      "Max Armor",         // max_armor
      "Green Armor Class", // green_armor_class
      "Blue Armor Class",  // blue_armor_class
      "Max Soulsphere",    // max_soul
      "Soulsphere Health", // soul_health
      "Megasphere Health", // mega_health
      "God Mode Health",   // god_health
      "IDFA Armor",        // idfa_armor
      "IDFA Armor Class",  // idfa_armor_class
      "IDKFA Armor",       // idkfa_armor
      "IDKFA Armor Class", // idkfa_armor_class
      "BFG Cells/Shot",    // BFGCELLS
      "Monsters Infight"   // Unknown--not a specific number it seems, but
      // the logic has to be here somewhere or
      // it'd happen always
   };

// TEXT - Dehacked block name = "Text"
// Usage: Text fromlen tolen
// Dehacked allows a bit of adjustment to the length (why?)

// BEX extension [CODEPTR]
// Usage: Start block, then each line is:
// FRAME nnn = PointerMnemonic

// External references to action functions scattered about the code

extern void A_Light0();
extern void A_WeaponReady();
extern void A_Lower();
extern void A_Raise();
extern void A_Punch();
extern void A_ReFire();
extern void A_FirePistol();
extern void A_Light1();
extern void A_FireShotgun();
extern void A_Light2();
extern void A_FireShotgun2();
extern void A_CheckReload();
extern void A_OpenShotgun2();
extern void A_LoadShotgun2();
extern void A_CloseShotgun2();
extern void A_FireCGun();
extern void A_GunFlash();
extern void A_FireMissile();
extern void A_Saw();
extern void A_FirePlasma();
extern void A_BFGsound();
extern void A_FireBFG();
extern void A_BFGSpray();
extern void A_Explode();
extern void A_Pain();
extern void A_PlayerScream();
extern void A_Fall();
extern void A_XScream();
extern void A_Look();
extern void A_Chase();
extern void A_FaceTarget();
extern void A_PosAttack();
extern void A_Scream();
extern void A_SPosAttack();
extern void A_VileChase();
extern void A_VileStart();
extern void A_VileTarget();
extern void A_VileAttack();
extern void A_StartFire();
extern void A_Fire();
extern void A_FireCrackle();
extern void A_Tracer();
extern void A_SkelWhoosh();
extern void A_SkelFist();
extern void A_SkelMissile();
extern void A_FatRaise();
extern void A_FatAttack1();
extern void A_FatAttack2();
extern void A_FatAttack3();
extern void A_BossDeath();
extern void A_CPosAttack();
extern void A_CPosRefire();
extern void A_TroopAttack();
extern void A_SargAttack();
extern void A_HeadAttack();
extern void A_BruisAttack();
extern void A_SkullAttack();
extern void A_Metal();
extern void A_SpidRefire();
extern void A_BabyMetal();
extern void A_BspiAttack();
extern void A_Hoof();
extern void A_CyberAttack();
extern void A_PainAttack();
extern void A_PainDie();
extern void A_KeenDie();
extern void A_BrainPain();
extern void A_BrainScream();
extern void A_BrainDie();
extern void A_BrainAwake();
extern void A_BrainSpit();
extern void A_SpawnSound();
extern void A_SpawnFly();
extern void A_BrainExplode();
extern void A_Detonate();        // killough 8/9/98
extern void A_Mushroom();        // killough 10/98
extern void A_Die();             // killough 11/98
extern void A_Spawn();           // killough 11/98
extern void A_Turn();            // killough 11/98
extern void A_Face();            // killough 11/98
extern void A_Scratch();         // killough 11/98
extern void A_PlaySound();       // killough 11/98
extern void A_RandomJump();      // killough 11/98
extern void A_LineEffect();      // killough 11/98

typedef struct {
   actionf_t cptr;  // actual pointer to the subroutine
   const char *lookup;  // mnemonic lookup string to be specified in BEX
   // CPhipps - const*
} deh_bexptr;

static const deh_bexptr deh_bexptrs[] = // CPhipps - static const
   {
      {A_Light0,          "A_Light0"},
      {A_WeaponReady,     "A_WeaponReady"},
      {A_Lower,           "A_Lower"},
      {A_Raise,           "A_Raise"},
      {A_Punch,           "A_Punch"},
      {A_ReFire,          "A_ReFire"},
      {A_FirePistol,      "A_FirePistol"},
      {A_Light1,          "A_Light1"},
      {A_FireShotgun,     "A_FireShotgun"},
      {A_Light2,          "A_Light2"},
      {A_FireShotgun2,    "A_FireShotgun2"},
      {A_CheckReload,     "A_CheckReload"},
      {A_OpenShotgun2,    "A_OpenShotgun2"},
      {A_LoadShotgun2,    "A_LoadShotgun2"},
      {A_CloseShotgun2,   "A_CloseShotgun2"},
      {A_FireCGun,        "A_FireCGun"},
      {A_GunFlash,        "A_GunFlash"},
      {A_FireMissile,     "A_FireMissile"},
      {A_Saw,             "A_Saw"},
      {A_FirePlasma,      "A_FirePlasma"},
      {A_BFGsound,        "A_BFGsound"},
      {A_FireBFG,         "A_FireBFG"},
      {A_BFGSpray,        "A_BFGSpray"},
      {A_Explode,         "A_Explode"},
      {A_Pain,            "A_Pain"},
      {A_PlayerScream,    "A_PlayerScream"},
      {A_Fall,            "A_Fall"},
      {A_XScream,         "A_XScream"},
      {A_Look,            "A_Look"},
      {A_Chase,           "A_Chase"},
      {A_FaceTarget,      "A_FaceTarget"},
      {A_PosAttack,       "A_PosAttack"},
      {A_Scream,          "A_Scream"},
      {A_SPosAttack,      "A_SPosAttack"},
      {A_VileChase,       "A_VileChase"},
      {A_VileStart,       "A_VileStart"},
      {A_VileTarget,      "A_VileTarget"},
      {A_VileAttack,      "A_VileAttack"},
      {A_StartFire,       "A_StartFire"},
      {A_Fire,            "A_Fire"},
      {A_FireCrackle,     "A_FireCrackle"},
      {A_Tracer,          "A_Tracer"},
      {A_SkelWhoosh,      "A_SkelWhoosh"},
      {A_SkelFist,        "A_SkelFist"},
      {A_SkelMissile,     "A_SkelMissile"},
      {A_FatRaise,        "A_FatRaise"},
      {A_FatAttack1,      "A_FatAttack1"},
      {A_FatAttack2,      "A_FatAttack2"},
      {A_FatAttack3,      "A_FatAttack3"},
      {A_BossDeath,       "A_BossDeath"},
      {A_CPosAttack,      "A_CPosAttack"},
      {A_CPosRefire,      "A_CPosRefire"},
      {A_TroopAttack,     "A_TroopAttack"},
      {A_SargAttack,      "A_SargAttack"},
      {A_HeadAttack,      "A_HeadAttack"},
      {A_BruisAttack,     "A_BruisAttack"},
      {A_SkullAttack,     "A_SkullAttack"},
      {A_Metal,           "A_Metal"},
      {A_SpidRefire,      "A_SpidRefire"},
      {A_BabyMetal,       "A_BabyMetal"},
      {A_BspiAttack,      "A_BspiAttack"},
      {A_Hoof,            "A_Hoof"},
      {A_CyberAttack,     "A_CyberAttack"},
      {A_PainAttack,      "A_PainAttack"},
      {A_PainDie,         "A_PainDie"},
      {A_KeenDie,         "A_KeenDie"},
      {A_BrainPain,       "A_BrainPain"},
      {A_BrainScream,     "A_BrainScream"},
      {A_BrainDie,        "A_BrainDie"},
      {A_BrainAwake,      "A_BrainAwake"},
      {A_BrainSpit,       "A_BrainSpit"},
      {A_SpawnSound,      "A_SpawnSound"},
      {A_SpawnFly,        "A_SpawnFly"},
      {A_BrainExplode,    "A_BrainExplode"},
      {A_Detonate,        "A_Detonate"},       // killough 8/9/98
      {A_Mushroom,        "A_Mushroom"},       // killough 10/98
      {A_Die,             "A_Die"},            // killough 11/98
      {A_Spawn,           "A_Spawn"},          // killough 11/98
      {A_Turn,            "A_Turn"},           // killough 11/98
      {A_Face,            "A_Face"},           // killough 11/98
      {A_Scratch,         "A_Scratch"},        // killough 11/98
      {A_PlaySound,       "A_PlaySound"},      // killough 11/98
      {A_RandomJump,      "A_RandomJump"},     // killough 11/98
      {A_LineEffect,      "A_LineEffect"},     // killough 11/98

      // This NULL entry must be the last in the list
      {NULL,              "A_NULL"},  // Ty 05/16/98
   };

// to hold startup code pointers from INFO.C
// CPhipps - static
static actionf_t deh_codeptr[NUMSTATES];

// ====================================================================
// ProcessDehFile
// Purpose: Read and process a DEH or BEX file
// Args:    filename    -- name of the DEH/BEX file
//          outfilename -- output file (DEHOUT.TXT), appended to here
// Returns: void
//
// killough 10/98:
// substantially modified to allow input from wad lumps instead of .deh files.

void ProcessDehFile(const char *filename, const char *outfilename, int lumpnum)
{
   static int fileout = -1;       // In case -dehout was used
   DEHFILE infile, *filein = &infile;    // killough 10/98
   char inbuffer[DEH_BUFFERMAX];  // Place to put the primary infostring

   // Open output file if we're writing output
   if (outfilename && *outfilename && fileout < 0)
   {
      static boolean firstfile = true; // to allow append to output log
      if (strcmp(outfilename, "-"))
      {
         fileout = open(outfilename, firstfile ? O_WRONLY | O_CREAT :
                                                 O_WRONLY | O_APPEND);
         if (fileout < 0)
         {
            printf( "Could not open -dehout file %s\n... using stdout.\n",
                    outfilename);
         }
      }
      firstfile = false;
   }

   // killough 10/98: allow DEH files to come from wad lumps
   if (filename)
   {
      if ((infile.fd = open(filename,O_RDONLY)) < 0)
      {
         printf( "-deh file %s not found\n",filename);
         return;  // should be checked up front anyway
      }
      infile.inp = NULL;
   }
   else  // DEH file comes from lump indicated by third argument
   {
      int size = W_LumpLength(lumpnum);
      infile.size = (size < 0) ? 0 : (ssize_t)size;
      infile.inp  = W_CacheLumpNum(lumpnum);
      infile.fd=-1;
      filename = "(WAD)";
   }

   printf("Loading DEH file %s\n",filename);
   if (fileout >= 0)
      fdprintf(fileout,"\nLoading DEH file %s\n\n",filename);

   {
      static int i;   // killough 10/98: only run once, by keeping index static
      for (; i<NUMSTATES; i++)  // remember what they start as for deh xref
         deh_codeptr[i] = states[i].action;
   }

   // loop until end of file

   while (dehfgets(inbuffer,sizeof(inbuffer),filein))
   {
      unsigned int i;

      if (fileout >= 0)
         fdprintf(fileout,"Line='%s'\n",inbuffer);

      if (!*inbuffer || *inbuffer == '#' || *inbuffer == ' ')
         continue; /* Blank line or comment line */

      // -- If DEH_BLOCKMAX is set right, the processing is independently
      // -- handled based on data in the deh_blocks[] structure array

      // killough 10/98: INCLUDE code rewritten to allow arbitrary nesting,
      // and to greatly simplify code, fix memory leaks, other bugs

      if (!strncasecmp(inbuffer,"INCLUDE",7)) // include a file
      {
         // preserve state while including a file
         // killough 10/98: moved to here

         char *nextfile;
         boolean oldnotext = includenotext;       // killough 10/98

         // killough 10/98: exclude if inside wads (only to discourage
         // the practice, since the code could otherwise handle it)

         if (infile.inp)
         {
            if (fileout >= 0)
               fdprintf(fileout,
                        "No files may be included from wads: %s\n",
                        inbuffer);
            continue;
         }

         // check for no-text directive, used when including a DEH
         // file but using the BEX format to handle strings

         if (!strncasecmp(nextfile = ptr_lstrip(inbuffer+7),"NOTEXT",6))
            includenotext = true, nextfile = ptr_lstrip(nextfile+6);

         if (fileout >= 0)
            fdprintf(fileout,"Branching to include file %s...\n", nextfile);

         // killough 10/98:
         // Second argument must be NULL to prevent closing fileout too soon

         ProcessDehFile(nextfile,NULL,0); // do the included file

         includenotext = oldnotext;
         if (fileout >= 0)
            fdprintf(fileout,"...continuing with %s\n",filename);
         continue;
      }

      for (i=0; i<DEH_BLOCKMAX; i++)
         if (!strncasecmp(inbuffer,deh_blocks[i].key,strlen(deh_blocks[i].key)))
         { // matches one
            if (fileout >= 0)
               fdprintf(fileout,"Processing function [%d] for %s\n",
                       i, deh_blocks[i].key);
            deh_blocks[i].fptr(filein,fileout,inbuffer);  // call function
            break;  // we got one, that's enough for this block
         }
   }

   if (infile.inp)
   {
      W_UnlockLumpNum(lumpnum);      // Mark purgable
   }

   if (infile.fd >= 0)
   {
      close(infile.fd);              // Close real file
      infile.fd = -1;
   }

   if (fileout >= 0)
   {
      close(fileout);
      fileout = -1;
   }
}

// ====================================================================
// deh_procBexCodePointers
// Purpose: Handle [CODEPTR] block, BOOM Extension
// Args:    fpin  -- input file stream
//          fpout -- output file stream (DEHOUT.TXT)
//          line  -- current line in file to process
// Returns: void
//
void deh_procBexCodePointers(DEHFILE *fpin, int fpout, char *line)
{
   char key[DEH_MAXKEYLEN];
   char inbuffer[DEH_BUFFERMAX];
   int indexnum;
   char mnemonic[DEH_MAXKEYLEN];  // to hold the codepointer mnemonic
   int i; // looper
   boolean found; // know if we found this one during lookup or not

   // Ty 05/16/98 - initialize it to something, dummy!
   strncpy(inbuffer,line,DEH_BUFFERMAX);

   // for this one, we just read 'em until we hit a blank line
   while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' '))
   {
      if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break;
      if (!*inbuffer) break;   // killough 11/98: really exit on blank line

      // killough 8/98: allow hex numbers in input:
      if ( (3 != sscanf(inbuffer,"%s %d = %s", key, &indexnum, mnemonic))
            || (strcasecmp(key,"FRAME")) )  // NOTE: different format from normal
      {
         if (fpout) fdprintf(fpout,
                               "Invalid BEX codepointer line - must start with 'FRAME': '%s'\n",
                               inbuffer);
         return;  // early return
      }

      if (fpout) fdprintf(fpout,"Processing pointer at index %d: %s\n",
                            indexnum, mnemonic);
      if (indexnum < 0 || indexnum >= NUMSTATES)
      {
         if (fpout) fdprintf(fpout,"Bad pointer number %d of %d\n",
                               indexnum, NUMSTATES);
         return; // killough 10/98: fix SegViol
      }
      strcpy(key,"A_");  // reusing the key area to prefix the mnemonic
      strcat(key,ptr_lstrip(mnemonic));

      found = FALSE;
      i= -1; // incremented to start at zero at the top of the loop
      do  // Ty 05/16/98 - fix loop logic to look for null ending entry
      {
         ++i;
         if (!strcasecmp(key,deh_bexptrs[i].lookup))
         {  // Ty 06/01/98  - add  to states[].action for new djgcc version
            states[indexnum].action = deh_bexptrs[i].cptr; // assign
            if (fpout) fdprintf(fpout,
                                  " - applied %s from codeptr[%d] to states[%d]\n",
                                  deh_bexptrs[i].lookup,i,indexnum);
            found = TRUE;
         }
      } while (!found && (deh_bexptrs[i].cptr != NULL));

      if (!found)
         if (fpout) fdprintf(fpout,
                               "Invalid frame pointer mnemonic '%s' at %d\n",
                               mnemonic, indexnum);
   }
   return;
}

//---------------------------------------------------------------------------
// To be on the safe, compatible side, we manually convert DEH bitflags
// to prboom types - POPE
//---------------------------------------------------------------------------
uint_64_t getConvertedDEHBits(uint_64_t bits) {
   static const uint_64_t bitMap[32] = {
                                          /* cf linuxdoom-1.10 p_mobj.h */
                                          MF_SPECIAL, // 0 Can be picked up. When touched the thing can be picked up.
                                          MF_SOLID, // 1 Obstacle. The thing is solid and will not let you (or others) pass through it
                                          MF_SHOOTABLE, // 2 Shootable. Can be shot.
                                          MF_NOSECTOR, // 3 Total Invisibility. Invisible, but can be touched
                                          MF_NOBLOCKMAP, // 4 Don't use the blocklinks (inert but displayable)
                                          MF_AMBUSH, // 5 Semi deaf. The thing is a deaf monster
                                          MF_JUSTHIT, // 6 In pain. Will try to attack right back after being hit
                                          MF_JUSTATTACKED, // 7 Steps before attack. Will take at least one step before attacking
                                          MF_SPAWNCEILING, // 8 Hangs from ceiling. When the level starts, this thing will be at ceiling height.
                                          MF_NOGRAVITY, // 9 No gravity – Gravity does not affect this thing
                                          MF_DROPOFF, // 10 Travels over cliffs – Monsters normally do not walk off ledges/steps they could not walk up. With this set they can walk off any height of cliff. Usually only used for flying monsters.
                                          MF_PICKUP, // 11 Pick up items – The thing can pick up gettable items.
                                          MF_NOCLIP, // 12 No clipping - Thing can walk through walls.
                                          MF_SLIDE, // 13 Slides along walls – Keep info about sliding along walls (don’t really know much about this one).
                                          MF_FLOAT, // 14 Floating – Thing can move to any height
                                          MF_TELEPORT, // 15 Semi no clipping – Don’t cross lines or look at teleport heights. (don’t really know much about this one either).
                                          MF_MISSILE, // 16 Projectiles – Behaves like a projectile, explodes when hitting something that blocks movement
                                          MF_DROPPED, // 17 Disappearing weapon – Dropped, not spawned (like an ammo clip) I have not had much success in using this one.
                                          MF_SHADOW, // 18 Partial invisibility – Drawn like a spectre.
                                          MF_NOBLOOD, // 19 Puffs (vs. bleeds) – If hit will spawn bullet puffs instead of blood splats.
                                          MF_CORPSE, // 20 Sliding helpless – Will slide down steps when dead.
                                          MF_INFLOAT, // 21 No auto levelling - float but not to target height (?)
                                          MF_COUNTKILL, // 22 Affects kill % – counted as a killable enemy and affects percentage kills on level summary.
                                          MF_COUNTITEM, // 23 Affects item % –affects percentage items gathered on level summary.
                                          MF_SKULLFLY, // 24 Running - special handling for flying skulls.
                                          MF_NOTDMATCH, // 25 Not in deathmatch - do not spawn in deathmatch (like keys)
                                          MF_TRANSLATION1, // 26 Color 1 (grey / red)
                                          MF_TRANSLATION2, // 27 Color 2 (brown / red)
                                          MF_TRANSLATION, // 28 and 29 allow the green colours in a thing’s graphics to be remapped to a different colour like the players uniforms in multiplayer games. Leaving all the bits alone, the thing stays green. Setting 26 it becomes grey. Setting 27 it becomes brown. Setting both 26 and 27 it becomes red.
                                          MF_TRANSLATION,
                                          0,
                                          0
                                       };
   int i;
   uint_64_t shiftBits = bits;
   uint_64_t convertedBits = 0;
   for (i=0; i<32; i++) {
      if (shiftBits & 0x1) convertedBits |= bitMap[i];
      shiftBits >>= 1;
   }
   return convertedBits;
}

//---------------------------------------------------------------------------
// See usage below for an explanation of this function's existence - POPE
//---------------------------------------------------------------------------
void setMobjInfoValue(int mobjInfoIndex, int keyIndex, uint_64_t value) {
   mobjinfo_t *mi;
   if (mobjInfoIndex >= NUMMOBJTYPES || mobjInfoIndex < 0) return;
   mi = &mobjinfo[mobjInfoIndex];
   switch (keyIndex) {
   case 0: mi->doomednum = (int)value; return;
   case 1: mi->spawnstate = (int)value; return;
   case 2: mi->spawnhealth = (int)value; return;
   case 3: mi->seestate = (int)value; return;
   case 4: mi->seesound = (int)value; return;
   case 5: mi->reactiontime = (int)value; return;
   case 6: mi->attacksound = (int)value; return;
   case 7: mi->painstate = (int)value; return;
   case 8: mi->painchance = (int)value; return;
   case 9: mi->painsound = (int)value; return;
   case 10: mi->meleestate = (int)value; return;
   case 11: mi->missilestate = (int)value; return;
   case 12: mi->deathstate = (int)value; return;
   case 13: mi->xdeathstate = (int)value; return;
   case 14: mi->deathsound = (int)value; return;
   case 15: mi->speed = (int)value; return;
   case 16: mi->radius = (int)value; return;
   case 17: mi->height = (int)value; return;
   case 18: mi->mass = (int)value; return;
   case 19: mi->damage = (int)value; return;
   case 20: mi->activesound = (int)value; return;
   case 21: mi->flags = value; return;
   case 22: mi->raisestate = (int)value; return;
   default: return;
   }
}

// ====================================================================
// deh_procThing
// Purpose: Handle DEH Thing block
// Args:    fpin  -- input file stream
//          fpout -- output file stream (DEHOUT.TXT)
//          line  -- current line in file to process
// Returns: void
//
// Ty 8/27/98 - revised to also allow mnemonics for
// bit masks for monster attributes
//

void deh_procThing(DEHFILE *fpin, int fpout, char *line)
{
   char key[DEH_MAXKEYLEN];
   char inbuffer[DEH_BUFFERMAX];
   uint_64_t value;      // All deh values are ints or longs
   int indexnum;
   int ix;
   char *strval;

   strncpy(inbuffer,line,DEH_BUFFERMAX);
   if (fpout) fdprintf(fpout,"Thing line: '%s'\n",inbuffer);

   // killough 8/98: allow hex numbers in input:
   ix = sscanf(inbuffer,"%s %d",key, &indexnum);
   if (fpout) fdprintf(fpout,"count=%d, Thing %d\n",ix, indexnum);

   // Note that the mobjinfo[] array is base zero, but object numbers
   // in the dehacked file start with one.  Grumble.
   --indexnum;

   // now process the stuff
   // Note that for Things we can look up the key and use its offset
   // in the array of key strings as an int offset in the structure

   // get a line until a blank or end of file--it's not
   // blank now because it has our incoming key in it
   while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' '))
   {
      if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break;

      // killough 11/98: really bail out on blank lines (break != continue)
      if (!*inbuffer) break;  // bail out with blank line between sections
      if (!deh_GetData(inbuffer,key,&value,&strval,fpout)) // returns TRUE if ok
      {
         if (fpout) fdprintf(fpout,"Bad data pair in '%s'\n",inbuffer);
         continue;
      }
      for (ix=0; ix<DEH_MOBJINFOMAX; ix++) {
         if (strcasecmp(key,deh_mobjinfo[ix])) continue;

         if (strcasecmp(key,"bits")) {
            // standard value set

            // The old code here was the cause of a DEH-related bug in prboom.
            // When the mobjinfo_t.flags member was graduated to an int64, this
            // code was caught unawares and was indexing each property of the
            // mobjinfo as if it were still an int32. This caused sets of the
            // "raisestate" member to partially overwrite the "flags" member,
            // thus screwing everything up and making most DEH patches result in
            // unshootable enemy types. Moved to a separate function above
            // and stripped of all hairy struct address indexing. - POPE
            setMobjInfoValue(indexnum, ix, value);
         }
         else {
            // bit set
            if (value) { // proff
               value = getConvertedDEHBits(value);
               mobjinfo[indexnum].flags = value;
            }
            else {
               // figure out what the bits are
               value = 0;

               // killough 10/98: replace '+' kludge with strtok() loop
               // Fix error-handling case ('found' var wasn't being reset)
               //
               // Use OR logic instead of addition, to allow repetition
               for (;(strval = strtok(strval,",+| \t\f\r")); strval = NULL) {
                  unsigned int iy;
                  for (iy=0; iy < DEH_MOBJFLAGMAX; iy++) {
                     if (strcasecmp(strval,deh_mobjflags[iy].name)) continue;
                     if (fpout) {
                        fdprintf(fpout,
                                "ORed value 0x%08lX%08lX %s\n",
                                (unsigned long)(deh_mobjflags[iy].value>>32) & 0xffffffff,
                                (unsigned long)deh_mobjflags[iy].value & 0xffffffff, strval
                               );
                     }
                     value |= deh_mobjflags[iy].value;
                     break;
                  }
                  if (iy >= DEH_MOBJFLAGMAX && fpout) {
                     fdprintf(fpout, "Could not find bit mnemonic %s\n", strval);
                  }
               }

               // Don't worry about conversion -- simply print values
               if (fpout) {
                  fdprintf(fpout,
                          "Bits = 0x%08lX%08lX\n",
                          (unsigned long)(value>>32) & 0xffffffff,
                          (unsigned long)value & 0xffffffff
                         );
               }
            }
         }
         if (fpout) {
            fdprintf(fpout,
                    "Assigned 0x%08lx%08lx to %s(%d) at index %d\n",
                    (unsigned long)(value>>32) & 0xffffffff,
                    (unsigned long)value & 0xffffffff, key, indexnum, ix
                   );
         }
      }
   }
   return;
}

// ====================================================================
// deh_procFrame
// Purpose: Handle DEH Frame block
// Args:    fpin  -- input file stream
//          fpout -- output file stream (DEHOUT.TXT)
//          line  -- current line in file to process
// Returns: void
//
void deh_procFrame(DEHFILE *fpin, int fpout, char *line)
{
   char key[DEH_MAXKEYLEN];
   char inbuffer[DEH_BUFFERMAX];
   uint_64_t value;      // All deh values are ints or longs
   int indexnum;

   strncpy(inbuffer,line,DEH_BUFFERMAX);

   // killough 8/98: allow hex numbers in input:
   sscanf(inbuffer,"%s %d",key, &indexnum);
   if (fpout) fdprintf(fpout,"Processing Frame at index %d: %s\n",indexnum,key);
   if (indexnum < 0 || indexnum >= NUMSTATES)
      if (fpout) fdprintf(fpout,"Bad frame number %d of %d\n",indexnum, NUMSTATES);

   while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' '))
   {
      if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break;
      if (!*inbuffer) break;         // killough 11/98
      if (!deh_GetData(inbuffer,key,&value,NULL,fpout)) // returns TRUE if ok
      {
         if (fpout) fdprintf(fpout,"Bad data pair in '%s'\n",inbuffer);
         continue;
      }
      if (!strcasecmp(key,deh_state[0]))  // Sprite number
      {
         if (fpout) fdprintf(fpout," - sprite = %ld\n",(long)value);
         states[indexnum].sprite = (spritenum_t)value;
      }
      else
         if (!strcasecmp(key,deh_state[1]))  // Sprite subnumber
         {
            if (fpout) fdprintf(fpout," - frame = %ld\n",(long)value);
            states[indexnum].frame = (long)value; // long
         }
         else
            if (!strcasecmp(key,deh_state[2]))  // Duration
            {
               if (fpout) fdprintf(fpout," - tics = %ld\n",(long)value);
               states[indexnum].tics = (long)value; // long
            }
            else
               if (!strcasecmp(key,deh_state[3]))  // Next frame
               {
                  if (fpout) fdprintf(fpout," - nextstate = %ld\n",(long)value);
                  states[indexnum].nextstate = (statenum_t)value;
               }
               else
                  if (!strcasecmp(key,deh_state[4]))  // Codep frame (not set in Frame deh block)
                  {
                     if (fpout) fdprintf(fpout," - codep, should not be set in Frame section!\n");
                     /* nop */ ;
                  }
                  else
                     if (!strcasecmp(key,deh_state[5]))  // Unknown 1
                     {
                        if (fpout) fdprintf(fpout," - misc1 = %ld\n",(long)value);
                        states[indexnum].misc1 = (long)value; // long
                     }
                     else
                        if (!strcasecmp(key,deh_state[6]))  // Unknown 2
                        {
                           if (fpout) fdprintf(fpout," - misc2 = %ld\n",(long)value);
                           states[indexnum].misc2 = (long)value; // long
                        }
                        else
                           if (fpout) fdprintf(fpout,"Invalid frame string index for '%s'\n",key);
   }
   return;
}

// ====================================================================
// deh_procPointer
// Purpose: Handle DEH Code pointer block, can use BEX [CODEPTR] instead
// Args:    fpin  -- input file stream
//          fpout -- output file stream (DEHOUT.TXT)
//          line  -- current line in file to process
// Returns: void
//
void deh_procPointer(DEHFILE *fpin, int fpout, char *line) // done
{
   char key[DEH_MAXKEYLEN];
   char inbuffer[DEH_BUFFERMAX];
   uint_64_t value;      // All deh values are ints or longs
   int indexnum;
   int i; // looper

   strncpy(inbuffer,line,DEH_BUFFERMAX);
   // NOTE: different format from normal

   // killough 8/98: allow hex numbers in input, fix error case:
   if (sscanf(inbuffer,"%*s %*d (%s %d)",key, &indexnum) != 2)
   {
      if (fpout) fdprintf(fpout,"Bad data pair in '%s'\n",inbuffer);
      return;
   }

   if (fpout) fdprintf(fpout,"Processing Pointer at index %d: %s\n",indexnum, key);
   if (indexnum < 0 || indexnum >= NUMSTATES)
   {
      if (fpout)
         fdprintf(fpout,"Bad pointer number %d of %d\n",indexnum, NUMSTATES);
      return;
   }

   while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' '))
   {
      if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break;
      if (!*inbuffer) break;       // killough 11/98
      if (!deh_GetData(inbuffer,key,&value,NULL,fpout)) // returns TRUE if ok
      {
         if (fpout) fdprintf(fpout,"Bad data pair in '%s'\n",inbuffer);
         continue;
      }

      if ((signed)value < 0 || value >= NUMSTATES)
      {
         if (fpout)
            fdprintf(fpout,"Bad pointer number %lld of %d\n",value, NUMSTATES);
         return;
      }

      if (!strcasecmp(key,deh_state[4]))  // Codep frame (not set in Frame deh block)
      {
         states[indexnum].action = deh_codeptr[value];
         if (fpout) fdprintf(fpout," - applied from codeptr[%ld] to states[%d]\n",
                               (long)value,indexnum);
         // Write BEX-oriented line to match:
         for (i=0;i<NUMSTATES;i++)
         {
            if (!memcmp(&deh_bexptrs[i].cptr,&deh_codeptr[value],sizeof(actionf_t)))
            {
               if (fpout) fdprintf(fpout,"BEX [CODEPTR] -> FRAME %d = %s\n",
                                     indexnum, &deh_bexptrs[i].lookup[2]);
               break;
            }
         }
      }
      else
         if (fpout) fdprintf(fpout,"Invalid frame pointer index for '%s' at %lld\n",
                               key, value);
   }
   return;
}

// ====================================================================
// deh_procSounds
// Purpose: Handle DEH Sounds block
// Args:    fpin  -- input file stream
//          fpout -- output file stream (DEHOUT.TXT)
//          line  -- current line in file to process
// Returns: void
//
void deh_procSounds(DEHFILE *fpin, int fpout, char *line)
{
   char key[DEH_MAXKEYLEN];
   char inbuffer[DEH_BUFFERMAX];
   uint_64_t value;      // All deh values are ints or longs
   int indexnum;

   strncpy(inbuffer,line,DEH_BUFFERMAX);

   // killough 8/98: allow hex numbers in input:
   sscanf(inbuffer,"%s %d",key, &indexnum);
   if (fpout) fdprintf(fpout,"Processing Sounds at index %d: %s\n",
                         indexnum, key);
   if (indexnum < 0 || indexnum >= NUMSFX)
      if (fpout) fdprintf(fpout,"Bad sound number %d of %d\n",
                            indexnum, NUMSFX);

   while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' '))
   {
      if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break;
      if (!*inbuffer) break;         // killough 11/98
      if (!deh_GetData(inbuffer,key,&value,NULL,fpout)) // returns TRUE if ok
      {
         if (fpout) fdprintf(fpout,"Bad data pair in '%s'\n",inbuffer);
         continue;
      }
      if (!strcasecmp(key,deh_sfxinfo[0]))  // Offset
         /* nop */ ;  // we don't know what this is, I don't think
      else
         if (!strcasecmp(key,deh_sfxinfo[1]))  // Zero/One
            S_sfx[indexnum].singularity = (int)value;
         else
            if (!strcasecmp(key,deh_sfxinfo[2]))  // Value
               S_sfx[indexnum].priority = (int)value;
            else
               if (!strcasecmp(key,deh_sfxinfo[3]))  // Zero 1
                  S_sfx[indexnum].link = (sfxinfo_t *)((long) value);
               else
                  if (!strcasecmp(key,deh_sfxinfo[4]))  // Zero 2
                     S_sfx[indexnum].pitch = (int)value;
                  else
                     if (!strcasecmp(key,deh_sfxinfo[5]))  // Zero 3
                        S_sfx[indexnum].volume = (int)value;
                     else
                        if (!strcasecmp(key,deh_sfxinfo[6]))  // Zero 4
                           S_sfx[indexnum].data = (void *) ((long) value); // killough 5/3/98: changed cast
                        else
                           if (!strcasecmp(key,deh_sfxinfo[7]))  // Neg. One 1
                              S_sfx[indexnum].usefulness = (int)value;
                           else
                              if (!strcasecmp(key,deh_sfxinfo[8]))  // Neg. One 2
                                 S_sfx[indexnum].lumpnum = (int)value;
                              else
                                 if (fpout) fdprintf(fpout,
                                                       "Invalid sound string index for '%s'\n",key);
   }
   return;
}

// ====================================================================
// deh_procAmmo
// Purpose: Handle DEH Ammo block
// Args:    fpin  -- input file stream
//          fpout -- output file stream (DEHOUT.TXT)
//          line  -- current line in file to process
// Returns: void
//
void deh_procAmmo(DEHFILE *fpin, int fpout, char *line)
{
   char key[DEH_MAXKEYLEN];
   char inbuffer[DEH_BUFFERMAX];
   uint_64_t value;      // All deh values are ints or longs
   int indexnum;

   strncpy(inbuffer,line,DEH_BUFFERMAX);

   // killough 8/98: allow hex numbers in input:
   sscanf(inbuffer,"%s %d",key, &indexnum);
   if (fpout) fdprintf(fpout,"Processing Ammo at index %d: %s\n",
                         indexnum, key);
   if (indexnum < 0 || indexnum >= NUMAMMO)
      if (fpout) fdprintf(fpout,"Bad ammo number %d of %d\n",
                            indexnum,NUMAMMO);

   while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' '))
   {
      if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break;
      if (!*inbuffer) break;       // killough 11/98
      if (!deh_GetData(inbuffer,key,&value,NULL,fpout)) // returns TRUE if ok
      {
         if (fpout) fdprintf(fpout,"Bad data pair in '%s'\n",inbuffer);
         continue;
      }
      if (!strcasecmp(key,deh_ammo[0]))  // Max ammo
         maxammo[indexnum] = (int)value;
      else
         if (!strcasecmp(key,deh_ammo[1]))  // Per ammo
            clipammo[indexnum] = (int)value;
         else
            if (fpout) fdprintf(fpout,"Invalid ammo string index for '%s'\n",key);
   }
   return;
}

// ====================================================================
// deh_procWeapon
// Purpose: Handle DEH Weapon block
// Args:    fpin  -- input file stream
//          fpout -- output file stream (DEHOUT.TXT)
//          line  -- current line in file to process
// Returns: void
//
void deh_procWeapon(DEHFILE *fpin, int fpout, char *line)
{
   char key[DEH_MAXKEYLEN];
   char inbuffer[DEH_BUFFERMAX];
   uint_64_t value;      // All deh values are ints or longs
   int indexnum;

   strncpy(inbuffer,line,DEH_BUFFERMAX);

   // killough 8/98: allow hex numbers in input:
   sscanf(inbuffer,"%s %d",key, &indexnum);
   if (fpout) fdprintf(fpout,"Processing Weapon at index %d: %s\n",
                         indexnum, key);
   if (indexnum < 0 || indexnum >= NUMWEAPONS)
      if (fpout) fdprintf(fpout,"Bad weapon number %d of %d\n",
                            indexnum, NUMAMMO);

   while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' '))
   {
      if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break;
      if (!*inbuffer) break;       // killough 11/98
      if (!deh_GetData(inbuffer,key,&value,NULL,fpout)) // returns TRUE if ok
      {
         if (fpout) fdprintf(fpout,"Bad data pair in '%s'\n",inbuffer);
         continue;
      }
      if (!strcasecmp(key,deh_weapon[0]))  // Ammo type
         weaponinfo[indexnum].ammo = (ammotype_t)value;
      else
         if (!strcasecmp(key,deh_weapon[1]))  // Deselect frame
            weaponinfo[indexnum].upstate = (int)value;
         else
            if (!strcasecmp(key,deh_weapon[2]))  // Select frame
               weaponinfo[indexnum].downstate = (int)value;
            else
               if (!strcasecmp(key,deh_weapon[3]))  // Bobbing frame
                  weaponinfo[indexnum].readystate = (int)value;
               else
                  if (!strcasecmp(key,deh_weapon[4]))  // Shooting frame
                     weaponinfo[indexnum].atkstate = (int)value;
                  else
                     if (!strcasecmp(key,deh_weapon[5]))  // Firing frame
                        weaponinfo[indexnum].flashstate = (int)value;
                     else
                        if (fpout) fdprintf(fpout,"Invalid weapon string index for '%s'\n",key);
   }
   return;
}

// ====================================================================
// deh_procSprite
// Purpose: Dummy - we do not support the DEH Sprite block
// Args:    fpin  -- input file stream
//          fpout -- output file stream (DEHOUT.TXT)
//          line  -- current line in file to process
// Returns: void
//
void deh_procSprite(DEHFILE *fpin, int fpout, char *line) // Not supported
{
   char key[DEH_MAXKEYLEN];
   char inbuffer[DEH_BUFFERMAX];
   int indexnum;

   // Too little is known about what this is supposed to do, and
   // there are better ways of handling sprite renaming.  Not supported.

   strncpy(inbuffer,line,DEH_BUFFERMAX);

   // killough 8/98: allow hex numbers in input:
   sscanf(inbuffer,"%s %d",key, &indexnum);
   if (fpout) fdprintf(fpout,
                         "Ignoring Sprite offset change at index %d: %s\n",indexnum, key);
   while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' '))
   {
      if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break;
      if (!*inbuffer) break;      // killough 11/98
      // ignore line
      if (fpout) fdprintf(fpout,"- %s\n",inbuffer);
   }
   return;
}

// ====================================================================
// deh_procPars
// Purpose: Handle BEX extension for PAR times
// Args:    fpin  -- input file stream
//          fpout -- output file stream (DEHOUT.TXT)
//          line  -- current line in file to process
// Returns: void
//
void deh_procPars(DEHFILE *fpin, int fpout, char *line) // extension
{
   char key[DEH_MAXKEYLEN];
   char inbuffer[DEH_BUFFERMAX];
   int indexnum;
   int episode, level, partime, oldpar;

   // new item, par times
   // usage: After [PARS] Par 0 section identifier, use one or more of these
   // lines:
   //  par 3 5 120
   //  par 14 230
   // The first would make the par for E3M5 be 120 seconds, and the
   // second one makes the par for MAP14 be 230 seconds.  The number
   // of parameters on the line determines which group of par values
   // is being changed.  Error checking is done based on current fixed
   // array sizes of[4][10] and [32]

   strncpy(inbuffer,line,DEH_BUFFERMAX);

   // killough 8/98: allow hex numbers in input:
   sscanf(inbuffer,"%s %d",key, &indexnum);
   if (fpout) fdprintf(fpout,
                         "Processing Par value at index %d: %s\n",indexnum, key);
   // indexnum is a dummy entry
   while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' '))
   {
      if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break;
      strlwr(inbuffer); // lowercase it
      if (!*inbuffer) break;      // killough 11/98
      if (3 != sscanf(inbuffer,"par %d %d %d",&episode, &level, &partime))
      { // not 3
         if (2 != sscanf(inbuffer,"par %d %d",&level, &partime))
         { // not 2
            if (fpout) fdprintf(fpout,"Invalid par time setting string: %s\n",inbuffer);
         }
         else
         { // is 2
            // Ty 07/11/98 - wrong range check, not zero-based
            if (level < 1 || level > 32) // base 0 array (but 1-based parm)
            {
               if (fpout) fdprintf(fpout,"Invalid MAPnn value MAP%d\n",level);
            }
            else
            {
               oldpar = cpars[level-1];
               if (fpout) fdprintf(fpout,"Changed par time for MAP%d from %d to %d\n",level,oldpar,partime);
               cpars[level-1] = partime;
               deh_pars = TRUE;
            }
         }
      }
      else
      { // is 3
         // note that though it's a [4][10] array, the "left" and "top" aren't used,
         // effectively making it a base 1 array.
         // Ty 07/11/98 - level was being checked against max 3 - dumb error
         // Note that episode 4 does not have par times per original design
         // in Ultimate DOOM so that is not supported here.
         if (episode < 1 || episode > 3 || level < 1 || level > 9)
         {
            if (fpout) fdprintf(fpout,
                                  "Invalid ExMx values E%dM%d\n",episode, level);
         }
         else
         {
            oldpar = pars[episode][level];
            pars[episode][level] = partime;
            if (fpout) fdprintf(fpout,
                                  "Changed par time for E%dM%d from %d to %d\n",
                                  episode,level,oldpar,partime);
            deh_pars = TRUE;
         }
      }
   }
   return;
}

// ====================================================================
// deh_procCheat
// Purpose: Handle DEH Cheat block
// Args:    fpin  -- input file stream
//          fpout -- output file stream (DEHOUT.TXT)
//          line  -- current line in file to process
// Returns: void
//
void deh_procCheat(DEHFILE *fpin, int fpout, char *line) // done
{
   (void) fpin;
   (void) fpout;
   (void) line;
/*   char key[DEH_MAXKEYLEN];
   char inbuffer[DEH_BUFFERMAX];
   uint_64_t value;      // All deh values are ints or longs
   char ch = 0; // CPhipps - `writable' null string to initialise...
   char *strval = &ch;  // pointer to the value area
   int ix, iy;   // array indices
   char *p;  // utility pointer

   if (fpout) fdprintf(fpout,"Processing Cheat: %s\n",line);

   strncpy(inbuffer,line,DEH_BUFFERMAX);
   while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' '))
   {
      if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break;
      if (!*inbuffer) break;       // killough 11/98
      if (!deh_GetData(inbuffer,key,&value,&strval,fpout)) // returns TRUE if ok
      {
         if (fpout) fdprintf(fpout,"Bad data pair in '%s'\n",inbuffer);
         continue;
      }
      // Otherwise we got a (perhaps valid) cheat name,
      // so look up the key in the array

      // killough 4/18/98: use main cheat code table in st_stuff.c now
      for (ix=0; cheat[ix].cheat; ix++)
         if (cheat[ix].deh_cheat)   // killough 4/18/98: skip non-deh
         {
            if (!stricmp(key,cheat[ix].deh_cheat))  // found the cheat, ignored case
            {
               // replace it but don't overflow it.  Use current length as limit.
               // Ty 03/13/98 - add 0xff code
               // Deal with the fact that the cheats in deh files are extended
               // with character 0xFF to the original cheat length, which we don't do.
               for (iy=0; strval[iy]; iy++)
                  strval[iy] = (strval[iy]==(char)0xff) ? '\0' : strval[iy];

               iy = ix;     // killough 4/18/98

               // Ty 03/14/98 - skip leading spaces
               p = strval;
               while (*p == ' ') ++p;
               // Ty 03/16/98 - change to use a strdup and orphan the original
               // Also has the advantage of allowing length changes.
               // strncpy(cheat[iy].cheat,p,strlen(cheat[iy].cheat));
#if 0
               {    // killough 9/12/98: disable cheats which are prefixes of this one
                  int i;
                  for (i=0; cheat[i].cheat; i++)
                     if (cheat[i].when & not_deh &&
                           !strncasecmp(cheat[i].cheat,
                                        cheat[iy].cheat,
                                        strlen(cheat[i].cheat)) && i != iy)
                        cheat[i].deh_modified = true;
               }
#endif
               cheat[iy].cheat = strdup(p);
               if (fpout) fdprintf(fpout,
                                     "Assigned new cheat '%s' to cheat '%s'at index %d\n",
                                     p, cheat[ix].deh_cheat, iy); // killough 4/18/98
            }
         }
      if (fpout) fdprintf(fpout,"- %s\n",inbuffer);
   }
   return;
*/
}

// ====================================================================
// deh_procMisc
// Purpose: Handle DEH Misc block
// Args:    fpin  -- input file stream
//          fpout -- output file stream (DEHOUT.TXT)
//          line  -- current line in file to process
// Returns: void
//
void deh_procMisc(DEHFILE *fpin, int fpout, char *line) // done
{
   char key[DEH_MAXKEYLEN];
   char inbuffer[DEH_BUFFERMAX];
   uint_64_t value;      // All deh values are ints or longs

   strncpy(inbuffer,line,DEH_BUFFERMAX);
   while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' '))
   {
      if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break;
      if (!*inbuffer) break;    // killough 11/98
      if (!deh_GetData(inbuffer,key,&value,NULL,fpout)) // returns TRUE if ok
      {
         if (fpout) fdprintf(fpout,"Bad data pair in '%s'\n",inbuffer);
         continue;
      }
      // Otherwise it's ok
      if (fpout) fdprintf(fpout,"Processing Misc item '%s'\n", key);

      if (!strcasecmp(key,deh_misc[0]))  // Initial Health
         initial_health = (int)value;
      else
         if (!strcasecmp(key,deh_misc[1]))  // Initial Bullets
            initial_bullets = (int)value;
         else
            if (!strcasecmp(key,deh_misc[2]))  // Max Health
               maxhealth = (int)value;
            else
               if (!strcasecmp(key,deh_misc[3]))  // Max Armor
                  max_armor = (int)value;
               else
                  if (!strcasecmp(key,deh_misc[4]))  // Green Armor Class
                     green_armor_class = (int)value;
                  else
                     if (!strcasecmp(key,deh_misc[5]))  // Blue Armor Class
                        blue_armor_class = (int)value;
                     else
                        if (!strcasecmp(key,deh_misc[6]))  // Max Soulsphere
                           max_soul = (int)value;
                        else
                           if (!strcasecmp(key,deh_misc[7]))  // Soulsphere Health
                              soul_health = (int)value;
                           else
                              if (!strcasecmp(key,deh_misc[8]))  // Megasphere Health
                                 mega_health = (int)value;
                              else
                                 if (!strcasecmp(key,deh_misc[9]))  // God Mode Health
                                    god_health = (int)value;
                                 else
                                    if (!strcasecmp(key,deh_misc[10]))  // IDFA Armor
                                       idfa_armor = (int)value;
                                    else
                                       if (!strcasecmp(key,deh_misc[11]))  // IDFA Armor Class
                                          idfa_armor_class = (int)value;
                                       else
                                          if (!strcasecmp(key,deh_misc[12]))  // IDKFA Armor
                                             idkfa_armor = (int)value;
                                          else
                                             if (!strcasecmp(key,deh_misc[13]))  // IDKFA Armor Class
                                                idkfa_armor_class = (int)value;
                                             else
                                                if (!strcasecmp(key,deh_misc[14]))  // BFG Cells/Shot
                                                   bfgcells = (int)value;
                                                else
                                                   if (!strcasecmp(key,deh_misc[15]))  // Monsters Infight
                                                      /* No such switch in DOOM - nop */ ;
                                                   else
                                                      if (fpout) fdprintf(fpout,
                                                                            "Invalid misc item string index for '%s'\n",key);
   }
   return;
}

// ====================================================================
// deh_procText
// Purpose: Handle DEH Text block
// Notes:   We look things up in the current information and if found
//          we replace it.  At the same time we write the new and
//          improved BEX syntax to the log file for future use.
// Args:    fpin  -- input file stream
//          fpout -- output file stream (DEHOUT.TXT)
//          line  -- current line in file to process
// Returns: void
//
void deh_procText(DEHFILE *fpin, int fpout, char *line)
{
   char key[DEH_MAXKEYLEN];
   char inbuffer[DEH_BUFFERMAX*2];  // can't use line -- double size buffer too.
   int i; // loop variable
   int fromlen, tolen;  // as specified on the text block line
   int usedlen;  // shorter of fromlen and tolen if not matched
   boolean found = FALSE;  // to allow early exit once found
   char* line2 = NULL;   // duplicate line for rerouting

   // Ty 04/11/98 - Included file may have NOTEXT skip flag set
   if (includenotext) // flag to skip included deh-style text
   {
      if (fpout) fdprintf(fpout,
                            "Skipped text block because of notext directive\n");
      strcpy(inbuffer,line);
      while (!dehfeof(fpin) && *inbuffer && (*inbuffer != ' '))
         dehfgets(inbuffer, sizeof(inbuffer), fpin);  // skip block
      // Ty 05/17/98 - don't care if this fails
      return; // ************** Early return
   }

   // killough 8/98: allow hex numbers in input:
   sscanf(line,"%s %d %d",key,&fromlen,&tolen);
   if (fpout) fdprintf(fpout,
                         "Processing Text (key=%s, from=%d, to=%d)\n",
                         key, fromlen, tolen);

   // killough 10/98: fix incorrect usage of feof
   {
      int c, totlen = 0;
      while (totlen < fromlen + tolen && (c = dehfgetc(fpin)) != EOF)
         inbuffer[totlen++] = c;
      inbuffer[totlen]='\0';
   }

   // if the from and to are 4, this may be a sprite rename.  Check it
   // against the array and process it as such if it matches.  Remember
   // that the original names are (and should remain) uppercase.
   // Future: this will be from a separate [SPRITES] block.
   if (fromlen==4 && tolen==4)
   {
      i=0;
      while (sprnames[i])  // null terminated list in info.c //jff 3/19/98
      {                                                      //check pointer
         if (!strncasecmp(sprnames[i],inbuffer,fromlen))         //not first char
         {
            if (fpout) fdprintf(fpout,
                                  "Changing name of sprite at index %d from %s to %*s\n",
                                  i,sprnames[i],tolen,&inbuffer[fromlen]);
            // Ty 03/18/98 - not using strdup because length is fixed

            // killough 10/98: but it's an array of pointers, so we must
            // use strdup unless we redeclare sprnames and change all else
            {
               // CPhipps - fix constness problem
               char *s;
               sprnames[i] = s = strdup(sprnames[i]);

               strncpy(s,&inbuffer[fromlen],tolen);
            }
            found = TRUE;
            break;  // only one will match--quit early
         }
         ++i;  // next array element
      }
   }
   else
      if (fromlen < 7 && tolen < 7)  // lengths of music and sfx are 6 or shorter
      {
         usedlen = (fromlen < tolen) ? fromlen : tolen;
         if (fromlen != tolen)
            if (fpout) fdprintf(fpout,
                                  "Warning: Mismatched lengths from=%d, to=%d, used %d\n",
                                  fromlen, tolen, usedlen);
         // Try sound effects entries - see sounds.c
         for (i=1; i<NUMSFX; i++)
         {
            // avoid short prefix erroneous match
            if (strlen(S_sfx[i].name) != (size_t)fromlen) continue;
            if (!strncasecmp(S_sfx[i].name,inbuffer,fromlen))
            {
               if (fpout) fdprintf(fpout,
                                     "Changing name of sfx from %s to %*s\n",
                                     S_sfx[i].name,usedlen,&inbuffer[fromlen]);

               S_sfx[i].name = strdup(&inbuffer[fromlen]);
               found = TRUE;
               break;  // only one matches, quit early
            }
         }
         if (!found)  // not yet
         {
            // Try music name entries - see sounds.c
            for (i=1; i<NUMMUSIC; i++)
            {
               // avoid short prefix erroneous match
               if (strlen(S_music[i].name) != (size_t)fromlen) continue;
               if (!strncasecmp(S_music[i].name,inbuffer,fromlen))
               {
                  if (fpout) fdprintf(fpout,
                                        "Changing name of music from %s to %*s\n",
                                        S_music[i].name,usedlen,&inbuffer[fromlen]);

                  S_music[i].name = strdup(&inbuffer[fromlen]);
                  found = TRUE;
                  break;  // only one matches, quit early
               }
            }
         }  // end !found test
      }

   if (!found) // Nothing we want to handle here--see if strings can deal with it.
   {
      if (fpout) fdprintf(fpout,"Checking text area through strings for '%s%s' from=%d to=%d\n",inbuffer, (strlen(inbuffer) > 12) ? "..." : "",fromlen,tolen);
      if ((size_t)fromlen <= strlen(inbuffer))
      {
         line2 = strdup(&inbuffer[fromlen]);
         inbuffer[fromlen] = '\0';
      }

      deh_procStringSub(NULL, inbuffer, line2, fpout);
   }
   free(line2); // may be NULL, ignored by free()
   return;
}

void deh_procError(DEHFILE *fpin, int fpout, char *line)
{
   (void)fpin;
   char inbuffer[DEH_BUFFERMAX];

   strncpy(inbuffer,line,DEH_BUFFERMAX);
   if (fpout) fdprintf(fpout,"Unmatched Block: '%s'\n",inbuffer);
   return;
}

// ====================================================================
// deh_procStrings
// Purpose: Handle BEX [STRINGS] extension
// Args:    fpin  -- input file stream
//          fpout -- output file stream (DEHOUT.TXT)
//          line  -- current line in file to process
// Returns: void
//
void deh_procStrings(DEHFILE *fpin, int fpout, char *line)
{
   char key[DEH_MAXKEYLEN];
   char inbuffer[DEH_BUFFERMAX];
   uint_64_t value;    // All deh values are ints or longs
   char *strval;      // holds the string value of the line
   static int maxstrlen = 128; // maximum string length, bumped 128 at
   // a time as needed
   // holds the final result of the string after concatenation
   static char *holdstring = NULL;
   boolean found = false;  // looking for string continuation

   if (fpout) fdprintf(fpout,"Processing extended string substitution\n");

   if (!holdstring) holdstring = malloc(maxstrlen*sizeof(*holdstring));

   *holdstring = '\0';  // empty string to start with
   strncpy(inbuffer,line,DEH_BUFFERMAX);
   // Ty 04/24/98 - have to allow inbuffer to start with a blank for
   // the continuations of C1TEXT etc.
   while (!dehfeof(fpin) && *inbuffer)  /* && (*inbuffer != ' ') */
   {
      if (!dehfgets(inbuffer, sizeof(inbuffer), fpin)) break;
      if (*inbuffer == '#') continue;  // skip comment lines
      if (!*inbuffer) break;  // killough 11/98
      if (!*holdstring) // first one--get the key
      {
         if (!deh_GetData(inbuffer,key,&value,&strval,fpout)) // returns TRUE if ok
         {
            if (fpout) fdprintf(fpout,"Bad data pair in '%s'\n",inbuffer);
            continue;
         }
      }
      while (strlen(holdstring) + strlen(inbuffer) > (size_t)maxstrlen) // Ty03/29/98 - fix stupid error
      {
         // killough 11/98: allocate enough the first time
         maxstrlen += strlen(holdstring) + strlen(inbuffer) - maxstrlen;
         if (fpout) fdprintf(fpout,
                               "* increased buffer from to %d for buffer size %d\n",
                               maxstrlen,(int)strlen(inbuffer));
         holdstring = realloc(holdstring,maxstrlen*sizeof(*holdstring));
      }
      // concatenate the whole buffer if continuation or the value iffirst
      strcat(holdstring,ptr_lstrip(((*holdstring) ? inbuffer : strval)));
      rstrip(holdstring);
      // delete any trailing blanks past the backslash
      // note that blanks before the backslash will be concatenated
      // but ones at the beginning of the next line will not, allowing
      // indentation in the file to read well without affecting the
      // string itself.
      if (holdstring[strlen(holdstring)-1] == '\\')
      {
         holdstring[strlen(holdstring)-1] = '\0';
         continue; // ready to concatenate
      }
      if (*holdstring) // didn't have a backslash, trap above would catch that
      {
         // go process the current string
         found = deh_procStringSub(key, NULL, holdstring, fpout);  // supply keyand not search string

         if (!found)
            if (fpout) fdprintf(fpout,
                                  "Invalid string key '%s', substitution skipped.\n",key);

         *holdstring = '\0';  // empty string for the next one
      }
   }
   return;
}

// ====================================================================
// deh_procStringSub
// Purpose: Common string parsing and handling routine for DEH and BEX
// Args:    key       -- place to put the mnemonic for the string if found
//          lookfor   -- original value string to look for
//          newstring -- string to put in its place if found
//          fpout     -- file stream pointer for log file (DEHOUT.TXT)
// Returns: boolean: True if string found, false if not
//
boolean deh_procStringSub(char *key, char *lookfor, char *newstring, int fpout)
{
   boolean found; // loop exit flag
   int i;  // looper

   found = false;
   for (i=0;i<deh_numstrlookup;i++)
   {
      found = lookfor ?
              !strcasecmp(*deh_strlookup[i].ppstr,lookfor) :
              !strcasecmp(deh_strlookup[i].lookup,key);

      if (found)
      {
         char *t;
         *deh_strlookup[i].ppstr = t = strdup(newstring); // orphan originalstring
         found = true;
         // Handle embedded \n's in the incoming string, convert to 0x0a's
         {
            const char *s;
            for (s=*deh_strlookup[i].ppstr; *s; ++s, ++t)
            {
               if (*s == '\\' && (s[1] == 'n' || s[1] == 'N')) //found one
                  ++s, *t = '\n';  // skip one extra for second character
               else
                  *t = *s;
            }
            *t = '\0';  // cap off the target string
         }

         if (key)
            if (fpout) fdprintf(fpout,
                                  "Assigned key %s => '%s'\n",key,newstring);

         if (!key)
            if (fpout) fdprintf(fpout,
                                  "Assigned '%s%s' to'%s%s' at key %s\n",
                                  lookfor, (strlen(lookfor) > 12) ? "..." : "",
                                  newstring, (strlen(newstring) > 12) ? "..." :"",
                                  deh_strlookup[i].lookup);

         if (!key) // must have passed an old style string so showBEX
            if (fpout) fdprintf(fpout,
                                  "*BEX FORMAT:\n%s = %s\n*END BEX\n",
                                  deh_strlookup[i].lookup,
                                  dehReformatStr(newstring));

         break;
      }
   }
   if (!found)
      if (fpout) fdprintf(fpout,
                            "Could not find '%s'\n",key ? key: lookfor);

   return found;
}

// ====================================================================
// General utility function(s)
// ====================================================================

// ====================================================================
// dehReformatStr
// Purpose: Convert a string into a continuous string with embedded
//          linefeeds for "\n" sequences in the source string
// Args:    string -- the string to convert
// Returns: the converted string (converted in a static buffer)
//
char *dehReformatStr(char *string)
{
   static char buff[DEH_BUFFERMAX]; // only processing the changed string,
   //  don't need double buffer
   char *s, *t;

   s = string;  // source
   t = buff;    // target
   // let's play...

   while (*s)
   {
      if (*s == '\n')
         ++s, *t++ = '\\', *t++ = 'n', *t++ = '\\', *t++='\n';
      else
         *t++ = *s++;
   }
   *t = '\0';
   return buff;
}

// ====================================================================
// rstrip
// Purpose: Strips trailing blanks off a string
// Args:    s -- the string to work on
// Returns: void -- the string is modified in place
//
void rstrip(char *s)  // strip trailing whitespace
{
   char *p = s+strlen(s);         // killough 4/4/98: same here
   while (p > s && isspace(*--p)) // break on first non-whitespace
      *p='\0';
}

// ====================================================================
// ptr_lstrip
// Purpose: Points past leading whitespace in a string
// Args:    s -- the string to work on
// Returns: char * pointing to the first nonblank character in the
//          string.  The original string is not changed.
//
char *ptr_lstrip(char *p)  // point past leading whitespace
{
   while (isspace(*p))
      p++;
   return p;
}

// ====================================================================
// deh_GetData
// Purpose: Get a key and data pair from a passed string
// Args:    s -- the string to be examined
//          k -- a place to put the key
//          l -- pointer to a long integer to store the number
//          strval -- a pointer to the place in s where the number
//                    value comes from.  Pass NULL to not use this.
//          fpout  -- stream pointer to output log (DEHOUT.TXT)
// Notes:   Expects a key phrase, optional space, equal sign,
//          optional space and a value, mostly an int but treated
//          as a long just in case.  The passed pointer to hold
//          the key must be DEH_MAXKEYLEN in size.

boolean deh_GetData(char *s, char *k, uint_64_t *l, char **strval, int fpout)
{
   (void)fpout;
   char *t;  // current char
   long val; // to hold value of pair
   char buffer[DEH_MAXKEYLEN];  // to hold key in progress
   boolean okrc = TRUE;  // assume good unless we have problems
   int i;  // iterator

   *buffer = '\0';
   val = 0;  // defaults in case not otherwise set
   for (i=0, t=s; *t && i < DEH_MAXKEYLEN; t++, i++)
   {
      if (*t == '=') break;
      buffer[i] = *t;  // copy it
   }
   buffer[--i] = '\0';  // terminate the key before the '='
   if (!*t)  // end of string with no equal sign
   {
      okrc = FALSE;
   }
   else
   {
      if (!*++t)
      {
         val = 0;  // in case "thiskey =" with no value
         okrc = FALSE;
      }
      // we've incremented t
      val = atoi(t);//strtol(t,NULL,0);  // killough 8/9/98: allow hex or octal input
   }

   // go put the results in the passed pointers
   *l = val;  // may be a faked zero

   // if spaces between key and equal sign, strip them
   strcpy(k,ptr_lstrip(buffer));  // could be a zero-length string

   if (strval != NULL) // pass NULL if you don't want this back
      *strval = t;      // pointer, has to be somewhere in s,
   // even if pointing at the zero byte.

   return(okrc);
}
