/***************************************************************************
 *                                                                         *
 *   CNF.CPP                                                               *
 *                                                                         *
 *   Copyright (c) 1988-1996 Galacticomm, Inc.  All Rights Reserved.       *
 *                                                                         *
 *   This utility manages the operation of Worldgroup by editing           *
 *   operating parameters and text messages, installing them into the      *
 *   .MSG files.                                                           *
 *                                                                         *
 *                                                     RNStein  July 1990  *
 *                                                     TJS & SJB Mar 1992  *
 *                                                     RNStein   Jan 1993  *
 *                                      Visual C++ Port - Paul Roub, 9/96  *
 *                                                                         *
 ***************************************************************************/

#include "stdafx.h"
#include "WgsCnf.h"
#include "CnfDoc.h"
#include "MainFrm.h"

#include "gcommlib.h"
#include "majorbbs.h"
#include "mdlogic.h"
#include "tfscan.h"
#include "excphand.h"
#include "option.h"
#include  "levels.h"
#include "edtoff.h"
#include "CnfAsrt.h"
#include "SaveDlg.h"
#include "FileUtil.h"

#define FILREV "$Revision: 3 $"

#define LINLEN  128          // Maximum length of option line on main screen 
#define VALLEN  80   // Max length of option value (except types 'S' & 'T') 
#define ENLLEN  250     // Max length of enumerated list of type 'E' option 
#define EXCSTG  "UNUSED"           // string to keyoff excluding a option always


const char FBORDER = '-';                        // pseudo-option between borders 
const char FLEVEL = 'V';

const CString UpdateDir = "gcupdfil";


option *ophead,                                    // option list header pointer 
       *optail,                                      // option list tail pointer 
       *op;



msgfil mfhead;
msgfil *mftail = &mfhead;

INT nfiles=1;                    // Number of .MSG files -- set by fndfls() 

FILE *fp;                              // pointer to file of current option 
UINT totopt;                             // total number of options 
CHAR scnsav[4000];                         // save area for CNF main screen 
CHAR savscn[GVIDSCNSIZ];
CHAR *scnptr=scnsav;        // Pointer to main CNF screen (NULL if visible) 
CHAR value[LINLEN+1];                            // value of current option 
CHAR rdval[LINLEN+1];                           // value buffer for rdopt() 
CHAR lindsc[LINLEN+1];              // 1-line description of current option 
CHAR enlist[ENLLEN+1];               // enumerated list for type 'E' option 
CHAR hngnam[MOPTLEN+1];                             // name of hinge option 
CHAR hngval[VALLEN+1];                    // value(s) for that hinge option 
INT edit=0;                // Has this option been changed by the operator? 
CHAR trash[LINLEN+1]={""};                // stowed by <-   retrieved by -> 
SHORT nchang=0;                                // Number of options changed 
INT indx;                             // global return variable of elpick() 
INT done=0;                                 // totally done with everything 
INT expmode;                                            // expert mode flag 
INT outbsz;                        // max output buffer size when svr is up 

//#define altern(optr) ((struct altlng *)((optr)->value))
#define altern(optr) (optr)->values
                 // For 'T' options, op->value.ptr points to an array of    
                 // nlingo of these altlng structures.  Remember, the       
                 // indexing of this array is 0..lngcnt-1, NOT 0..nlingo-1. 
                 // To translate, use the mslidx[] array.                   

CHAR **langchoose;                             // choose list for languages 

CHAR funkx[]={1,8,15,22,33,40,47,54,65,72}; // Softkey coordinates, H model 
#define FUNKY 22                            // Y coord is now constant      

CHAR *softies[]={" HELP       ",    // F1             // Soft key legends 
                 "            ",    // F2 
                 "            ",    // F3 
                 "            ",    // F4 
                 "            ",    // F5 
                 "            ",    // F6 
                 "            ",    // F7 
                 "SEARCH      ",    // F8 
                 "FORGET  IT  ",    // F9 
                 "SAVE & EXIT "};   // F10 

CHAR *blanksf[]={"            ",    // F1             // Soft key legends 
                 "            ",    // F2 
                 "            ",    // F3 
                 "            ",    // F4 
                 "            ",    // F5 
                 "            ",    // F6 
                 "            ",    // F7 
                 "            ",    // F8 
                 "            ",    // F9 
                 "            "};   // F10 


//  DISPLAY ATTRIBUTES  (upper byte color, lower byte monochrome) 
//----------------------
#define ATRTTL  0x7070                           // Title at head of screen 
#define ATRIUP  0x1707                       // when files are being loaded 
#define ATRIDN  0x1D07                    // when files are being installed 
#define ATRIPC  0x1D07                      // percent complete in outmsg() 
#define ATRIBR  0x5D07                                // outmsg() bar graph 
#define ATRCMD  0x1F0F                        // confirm abandoning changes 
#define ATRFMT  0x1C07                            // format description box 
#define ATRFLG  0x2E0F                // alternate language in format field 
#define ATRSBG  0x1A07                                 // status background 
#define ATRLBG  0x1907                                 // loading bar-graph 
#define ATRSFG  0x1E0F                                 // status foreground 
#define ATRSNW  0x4E70                 // status foreground, changed option 
#define ATRLIN  0x1B07              // line description of unchanged option 
#define ATRLCH  0x4F0F              // line description of a changed option 
#define ATRLLU  0x1307  // line description of unchanged non-lingual option 
#define ATRLLC  0x4707    // line description of changed non-lingual option 
#define ATRCHB  0x2A07                     // choose list background border 
#define ATRCHL  0x2E0F                          // choose list of languages 
#define ATRCHC  0x7070                                // choose list cursor 
#define ATRCHT  0x2F70                             // choose list title bar 
#define ATREPB  0x1B07          // enumerated choose list background border 
#define ATREPL  0x1F07                      // choose list, multiple choice 
#define ATREPC  0x7070                                // choose list cursor 
#define ATREPT  0x1B70                             // choose list title bar 
#define ATRVAL  0x1F0F                     // value of an option, unchanged 
#define ATRENT  0x4F70                 // value of an option, while editing 
#define ATRNEW  0x4E70                 // value of an option, after changed 
#define ATRKEY  0x1F07                                    // softkey legend 
#define ATREKY  0x1F0F                                     // Edit soft-key 
#define ATRLKY  0x1F07                          // Choose-language soft-key 
#define ATRSPK  0x3070                // softkey legend, enumerated choices 
#define ATRELD  0x1E07                 // message while waiting for END key 
#define ATRHLF  0x1207                           // option help, top border 
#define ATRHLP  0x2F70                         // option help, help message 
#define ATRHLN  0x2170                      // option help, title underline 
#define ATRHRM  0x2070                           // option help, title line 
#define ATRGHP  0x1E07                                  // general help box 
#define ATRMEM  0x1C0F            // Low memory warning message in help box 
#define ATRSPW  0x1C0F              // spawn()/system() shelling out failed 
#define ATRSPC  0x1E0F                                // CNF Editor command 
#define ATRCLG  0x1A0F               // creating a new version of an option 
#define ATRWOS  0x1E0F                    // warning about change to OUTBSZ 
#define ATRWOB  0x1C0F        // warning that OUTBSZ exceeded after editing 
#define ATRWBB  0x1C0F         // warning that OUTBSZ exceeded when loading 
#define ATRWSP  0x1C0F          // warning that % symbols have been changed 
#define ATRWS2  0x1F8F              // warning about %'s, prompt characters 
#define ATRHKY  0x1F0F                                  // Un-help soft-key 
#define ATRSRC  0x1E0F                                    // Search command 
#define ATRBDR  0x3170                                       // file border 
#define ATRBFN  0x3170                               // file name in border 
#define ATRBKG  0x0707                   // background when shelling to DOS 
#define ATRXHB  0x1F70                 // exit-to-DOS help bar in shell-out 

#define ECYBOT 16                          // bottom Y of option entry area 
INT ecx=18,ecy=ECYBOT;                   // screen cursor for option screen 
                                     // command line parameter help message 
static CHAR parhlp[]="\nSyntax:  WGSCNF -L# [-M] [?]\n";

INT skipped;     // how many options has upop() or dnop() actually skipped? 

struct option *srcop;             // searching option now under examination 
CHAR *srcwhr;                       // return code of srcfwd() and srcbkw() 

static
INT ccratr=0x0707;                              // current screen attribute 
INT warnbsz=0;                              // flag that OUTBSZ has changed 
LONG allbytes=0L;                 // global between lodnxt() and shosts() 
LONG ldgbytes=0L;                 // global between lodnxt() and shosts() 
LONG omgbytes;                    // global between outmsg() and shobar() 

CHAR *ombuff;                                        // buffer for outmsg() 
#define OMBSIZ 16384                                     // bytes in ombuff 

#define MAXSTP 128                       // max number of % symbols checked 
CHAR oldstrip[MAXSTP];                 // stripped % symbols before editing 
CHAR newstrip[MAXSTP];                  // stripped % symbols after editing 
INT blindcur=0;                       // cursor sneaking around the screen? 
CHAR *altbuf;                                             // editing buffer 
CHAR boxscn[GVIDSCNSIZ];  // for drawing language choice & enumerated pick list 
CHAR chantyps[ENLLEN+1];     // extra channel types for BBSMAJOR.MSG GROUPn 
CHAR hdwrtyps[ENLLEN+1];    // extra hardware types for BBSMAJOR.MSG GROUPn 

CString bbsTitle;

VOID init(VOID);
VOID inichlang(VOID);
VOID readfc(VOID);
VOID inilod(VOID);
INT lodnxt(VOID);
static VOID scanlng(VOID);
static VOID undeflang(CHAR *lgname);
VOID oplinkup(struct option *opa,struct option *opb);
INT outmsg(VOID);
VOID zapmcv(VOID);
INT mlingo(struct msgfil *mfp,INT lngidx);
const CHAR *skblk(const CHAR *string);
const CHAR *skword(const CHAR *string);
const CHAR *eltokn(INT n);
CHAR *l2ah(LONG lnum);
//   INT hlprd(struct option *opp);
INT chkvis(struct option *opt);
VOID rsvhng(struct option *rop);
VOID parms(struct option *opp,CHAR *cs,INT np);
VOID parse(VOID);
VOID parsec(VOID);
VOID parstg(CHAR *to,INT len);
INT rdopt(struct option *opp);
VOID fndfls(VOID);
INT msgxst(CHAR *msgnam);
static CString ReadHelp(FILE *fp,LONG helpPos,LONG helpLen);
VOID reportStatus(const CString &msg);
static VOID reportError(const char *fmt,...);
static CString MakeUpdateName( const char *st );
int Pct( int part, int total );


VOID
init(VOID)                         // initialization                       
{
     const char *regname = mstscan( "WGSMAJOR.MSG", "COMPANY", 100 );

     if (regname != NULL)
     {
          theApp.SetRegName( regname );
     }

     inilingo();
     inichlang();
     inimsgrdr(1000);
     readfc();
     inimsgrdr(outbsz);
     altbuf=(CHAR *)alcmem(outbsz);
     fndfls();
     inilod();
     ombuff=(CHAR *)alcmem(OMBSIZ);
     eospawn();
}

VOID
inichlang(VOID)            // build *langchoose[] choose list for languages 
{
     INT ilingo;

     langchoose=(CHAR **)alczer((nlingo+1)*sizeof(CHAR *));
     for (ilingo=0 ; ilingo < nlingo ; ilingo++) {
          langchoose[ilingo]=alcdup(spr("%-15s %s",languages[ilingo]->name,
                         languages[ilingo]->desc));
     }
}

VOID
appgprept(VOID)                      // application-specific GP info (stub) 
{
}

VOID
appgprecd(VOID)                    // application-specific GP record (stub) 
{
}


VOID
readfc(VOID)                        // read in CRT and EXPMODE options     
{
     BOOL gotbsz=FALSE;
     BOOL gottitle=FALSE;

     CString   newName = MakeUpdateName(MAINFL);

     if ((rdfp=fp=fopen(curfn=newName,FOPRB)) != NULL) {
          mfhead.fullName = newName;
     }
     else if ((rdfp=fp=fopen(curfn=MAINFL,FOPRB)) != NULL) {
          mfhead.fullName = MAINFL;
     }
     else {
          catastro("CANNOT OPEN "MAINFL);
     }
     allbytes+=filelength(fileno(rdfp));
     while (!gotbsz || !gottitle) {
          if ((!rdmsg()) && (!gotbsz)) {
               catastro(MAINFL" is missing the OUTBSZ option." );
          }
          if (sameas(msgnam,"OUTBSZ")) {
               parse();
               outbsz=(INT)atol(rdval);
               gotbsz=TRUE;
          }
          else if (sameas(msgnam,"BBSTTL")) {
               bbsTitle=txtbuf;
               gottitle=TRUE;
          }
          scanalt();
     }
     fclose(rdfp);
}


VOID
inilod(VOID)                                // initialize to first position 
{
     while (totopt < 1) {
          if (lodnxt() == 0) {
               catastro("NO OPTIONS");
          }
     }
     op=ophead;
     ecy--;
     ecy++;
}


static void AddToDoc( LPCTSTR curfn, option *ldrop, FILE *rdfp, int level, LONG levelHelpPtr, int levelHelpLen )
{
     CString helpText;
     static CString oldfn;
     static LONG oldLevelHelp = -1;
     CString levelHelp;
     static CnfDoc *ourDoc = NULL;
     
     if (ldrop->flags & ISHPAR)
     {
          helpText = ReadHelp( rdfp, ldrop->parptr, ldrop->parlen );
     }

     if ((levelHelpPtr >= 0) && 
         ((levelHelpPtr != oldLevelHelp) || (oldfn != curfn))
        )
     {
          levelHelp = ReadHelp( rdfp, levelHelpPtr, levelHelpLen );
     }

     if (ourDoc == NULL)
     {
          POSITION tempIndex = theApp.GetFirstDocTemplatePosition();
          CDocTemplate *theTemplate = theApp.GetNextDocTemplate( tempIndex );

          CnfAssert( theTemplate != NULL );

          tempIndex = theTemplate->GetFirstDocPosition();
          CDocument *theDoc = theTemplate->GetNextDoc( tempIndex );

          CnfAssert( theDoc != NULL );

          ourDoc = static_cast< CnfDoc * >( theDoc );
     }

     ourDoc->AddOption( curfn, ldrop, helpText, ldgbytes+ftell(rdfp), 
                        allbytes, level, rdfp, &levelHelp
                      );

     oldfn = curfn;

     return;
}


static CString
ReadHelp(
FILE *fp,
LONG helpPos,
LONG helpLen)
{
     CString   helpText;
     long      oldPos = ftell( fp );

     if (fseek( fp, helpPos, SEEK_SET ) == 0)
     {    
          const int bufLen = 16 * 1024;
          static char ourText[ bufLen ];

          int         readLen = min( bufLen, helpLen );

          fread( ourText, 1, readLen, fp );
          ourText[ readLen ] = 0;

          helpText = ourText;
     }

     fseek( fp, oldPos, SEEK_SET );

     return( helpText );
}



enum LoaderStates {
     LDRINI,
     LDRFIL,
     LDRSCN,
     LDROPT,
     LDRFWU,
     LDRFNX,
     LDRFIN,
     LDRNUL
};
static LoaderStates ldrstt=LDRINI;
static struct msgfil *ldrmfp=NULL;

INT
lodnxt(VOID)                          // option loader finite state machine 
{                     // returns 0=done 1=working 2=refresh progress report 
     static struct option optemp;
     static struct option *ldrop;
     static LONG fpos;
     static int curlevel = -1;
     struct option *newopt;
     INT thislev=-1;
     INT nalc,oldgv;
     INT rc=1;
     static LONG levelHelp = -1;
     static INT levelHelpLen = -1;

     switch (ldrstt) {
     case LDRINI:
          totopt=0;
          ophead=optail=ldrop=NULL;
          ldrstt=LDRFIL;
          ldrmfp=&mfhead;
          break;
     case LDRFIL:
          if (ldrmfp == NULL) {
               ldrstt=LDRFIN;
               break;
          }
          ldrmfp->fstopt=NULL;
          if ((ldrmfp->fp=fopen(ldrmfp->fullName,FOPRB)) == NULL) {
               ldrstt=LDRFNX;                    // skip missing .MSG files 
               break;
          }
          curfn = ldrmfp->name;
          fpos=ldrmfp->bofopt=ldrmfp->eofopt=ftell(ldrmfp->fp);
          ldrmfp->numopt=0;
          ldrmfp->mslidx=(INT *)alczer(nlingo*sizeof(INT));
          ldrstt=LDRSCN;
          break;
     case LDRSCN:                         // scan thru lower levels in file 
          rdfp=ldrmfp->fp;
          curfn=ldrmfp->name;
          fseek(rdfp,fpos,SEEK_SET);
          if (!rdmsg()) {
               ldrstt=LDRFWU;
               break;
          }
          if (sameas("LANGUAGE",msgnam)) {
               if (!sameas(txtbuf,languages[0]->name)) {
                    catastro("%s is not based on the \"%s\" language\n"
                             "(Language 0 is \"%s\" instead.)",
                             curfn,languages[0]->name,txtbuf);
               }
               if (ldrmfp->lngcnt != 0) {
                    catastro("More than one LANGUAGE{} option in %s",curfn);
               }
               ldrmfp->boflng=altlnm[0].value.fsk;
               scanlng();
               ldrmfp->eoflng=ftell(rdfp)-1;
               ldrmfp->lngcnt=salingo;
          }
          else if (sameto("LEVEL",msgnam) && alldgs(msgnam+5)) {
               RegisterLevel( curfn, msgnam + 5, txtbuf );

               if (((curlevel=thislev=atoi(&msgnam[5])) > level) &&
                       ((level != AllLevels) || IsSecretLevel(thislev))
                  )
               {
                    ldrstt=LDRFWU;                  // skip higher levels 
               }
               else if ((thislev == level) || 
                        ((level == AllLevels) && ! IsSecretLevel(thislev))) 
               {
                    //newopt=(struct option *)alczer(sizeof( option ));
                    newopt=new option;
                    oplinkup(ldrop,newopt);
                    ldrop=newopt;
                    ldrop->file=ldrmfp;
                    ldrop->type=FLEVEL;
                    stzcpy( ldrop->name, msgnam, sizeof( ldrop->name ) );
                    ldrop->curValue=ldrop->origValue=txtbuf;

                    ldrstt=LDROPT;
                    gvbust=0;
                    if (ldrmfp->lngcnt == 0) {
                         ldrmfp->lngcnt=1;
                    }
                    if (ldrmfp->numopt == 0) {
                         ldrmfp->bofopt=cmtptr;            // Mark comment of 1st opt 
                         ldrmfp->fstopt=ldrop;
                         ldrmfp->numopt++;
                    }
                    if ((ldrop->parlen=cmtlen) > 0) {
                         ldrop->flags|=ISHPAR;
                         levelHelp = ldrop->parptr = cmtptr;
                         levelHelpLen = ldrop->parlen;
                    }
                    else {
                         ldrop->flags&=~ISHPAR;
                         levelHelp = -1;
                    }
                    ldrmfp->eofopt=ftell(ldrmfp->fp); // Remember end of last message 

                    AddToDoc( curfn, ldrop, rdfp, curlevel, levelHelp, levelHelpLen );
               }
          }
          else if (curlevel > 0 && !rdopt(&optemp)) {
               ldrstt=LDRFWU;                      // (skip lower levels) 
          }                           // done if file only has lower levels 
          fpos=ftell(rdfp);
          break;
     case LDROPT:                        // Init for first option in a file 
          rdfp=ldrmfp->fp;
          curfn=ldrmfp->name;
          fseek(rdfp,fpos,0);
          //setmem(&optemp,sizeof(struct option),0);
          optemp.clear();
          oldgv=gvbust;
          if (!rdmsg()) {
               ldrstt=LDRFWU;
               break;
          }
          else if (sameto("LEVEL",msgnam) && alldgs(msgnam+5)) {
               RegisterLevel( curfn, msgnam + 5, txtbuf );

               if ((level == AllLevels) && ! IsSecretLevel( msgnam + 5 )) {
                    curlevel = thislev = atoi( msgnam + 5 );

                    //newopt=(struct option *)alczer(sizeof( struct option ));
                    newopt = new option;
                    oplinkup(ldrop,newopt);
                    ldrop=newopt;
                    ldrop->file=ldrmfp;
                    ldrop->type=FLEVEL;
                    stzcpy( ldrop->name, msgnam, sizeof( ldrop->name ) );
                    ldrop->curValue=ldrop->origValue=txtbuf;
                    ldrstt=LDROPT;
                    gvbust=0;
                    if (ldrmfp->lngcnt == 0) {
                         ldrmfp->lngcnt=1;
                    }
                    if (ldrmfp->numopt == 0) {
                         ldrmfp->bofopt=cmtptr;            // Mark comment of 1st opt 
                         ldrmfp->fstopt=ldrop;
                         ldrmfp->numopt++;
                    }
                    if ((ldrop->parlen=cmtlen) > 0) {
                         ldrop->flags|=ISHPAR;
                         levelHelp = ldrop->parptr = cmtptr;
                         levelHelpLen = ldrop->parlen;
                    }
                    else {
                         ldrop->flags&=~ISHPAR;
                         levelHelp = -1;
                    }

                    fpos = ftell( rdfp );

                    AddToDoc( curfn, ldrop, rdfp, curlevel, levelHelp, levelHelpLen );
               }
               else {
                    ldrstt=LDRFWU;
               }
               break;
          }
          else if (!rdopt(&optemp))
          {
               ldrstt=LDRFWU;
               break;
          }
          if (gvbust && !oldgv) {
               reportError("Option %s in %s is too large.  It has been truncated"
                       " to %u\nbytes.\r\n\r\n(You may want to increase offline "
                       "Configuration option OUTBSZ.)",msgnam,curfn,bufsize-1);
          }
#if ! defined( WIN32 )
          if (strchr("NLHSEB",optemp.type) != NULL
           || (optemp.flags&(ISHING+ISHPAR))) {
               nalc=sizeof(struct option);           // full size structure 
          }
          else {
               nalc=OPCORE;                          // condensed structure 
          }
#else
               nalc = sizeof( option );
#endif

          //wopt=(struct option *)alcmem(nalc);
          newopt = new option;
          //memmove(newopt,&optemp,nalc);
          *newopt = optemp;
          oplinkup(ldrop,newopt);
          ldrop=newopt;
          ldrop->file=ldrmfp;
          ldrop->index=totopt;
          if (ldrmfp->numopt == 0) {
               ldrmfp->bofopt=cmtptr;            // Mark comment of 1st opt 
               ldrmfp->fstopt=ldrop;
          }
          ldrmfp->eofopt=ftell(ldrmfp->fp); // Remember end of last message 
          ldrmfp->numopt++;
          if (++totopt == 0) {
               catastro("MORE THAN 65535 OPTIONS!");
          }
          rc=2;
          switch (ldrop->type) {
          case 'S':
               ldrop->curValue=ldrop->origValue=rdval;
//               stlcpy(ldrop->value=(CHAR *)alcmem(LINLEN+1),rdval,LINLEN+1);
               break;
          case 'T':
               {
                    if (salingo > ldrmfp->lngcnt) {
                         catastro("Too many language-versions of option %s\n"
                                  "for the %s file",msgnam,curfn);
                    }
                    ldrop->values=(altlng *)alczer(nlingo*sizeof(struct altlng));
                    ldrop->origValues= new CString[ nlingo ];
                    if (salingo > 0) {
                         memmove(altern(ldrop),altlnm,salingo*sizeof(struct altlng));
                    }
               }
               break;
          default:
               ldrop->curValue=ldrop->origValue=rdval;
//               stlcpy(ldrop->curValue=(CHAR *)alcmem(VALLEN+1),rdval,VALLEN+1);
               break;
          }
#if ! defined( WIN32) // under Win32, favor easy deletion, not space
          if (ldrop->prev != NULL
           && ldrop->prev->lindsc != NULL
           && sameas(ldrop->prev->lindsc,lindsc)) {
               ldrop->lindsc=ldrop->prev->lindsc;
          }
          else {
#endif
//               ldrop->lindsc=alcdup(skpwht(lindsc));
               ldrop->lineDesc=lindsc;
#if ! defined( WIN32 )
          }
#endif
          rsvhng(ldrop);
          if (ldrop->type == 'E' || ldrop->type == 'B') {
               if (sameas(curfn,MAINFL)
             && sameto("GROUP",ldrop->name)
             && strstr(enlist,"<NONE>") != NULL) {
                    stlcat(enlist,chantyps,ENLLEN+1);
               }
               else if (sameas(curfn,MAINFL)
               && sameto("HTYPE",ldrop->name)
               && strstr(enlist,"SINGLE") != NULL) {
                    stlcat(enlist,hdwrtyps,ENLLEN+1);
               }
               ldrop->enList=enlist;
          }


            AddToDoc( curfn, ldrop, rdfp, curlevel, levelHelp, levelHelpLen );

          fpos=ftell(rdfp);
          break;
     case LDRFWU:                                           // file wind-up 
          if (ldrmfp->numopt > 0
           && fseek(ldrmfp->fp,ldrmfp->eofopt,0) == 0
           && getc(ldrmfp->fp) == '\r'
           && getc(ldrmfp->fp) == '\n') {
               ldrmfp->eofopt+=2;
          }           // sync up with remainder of file (for outmsg()) 
          ldrstt=LDRFNX;
          curlevel = -1;
          break;
     case LDRFNX:
          ldgbytes+=filelength(fileno(ldrmfp->fp));
          ldrmfp->flags|=FILOAD;
          ldrmfp=ldrmfp->next;
          ldrstt=LDRFIL;
          rc=2;
          break;
     case LDRFIN:
          ldrstt=LDRNUL;
     case LDRNUL:
          rc=0;
          break;
     }
     return(rc);
}

static VOID
scanlng(VOID)                                 // scan the LANGUAGE{} option 
{
     INT ilingo,needcomma;

     salingo=0;
     needcomma=1;
     while (1) {
          switch(getc(rdfp)) {
          case ',':
               salingo++;
               needcomma=0;
               break;
          case OPNMSG:
               if (!needcomma) {
                    getval(rdfp);
                    if ((ilingo=lngfnd(txtbuf)) == -1) {
                         undeflang(txtbuf);
                    }
                    ldrmfp->mslidx[ilingo]=salingo;
                    needcomma=1;
                    break;
               }
          default:
               catastro("Bad format for LANGUAGE{} option in %s",
                        curfn);
          case CHR_EOL:
          case EOF:
               salingo++;
               return;
          }
     }
}

static VOID
undeflang(                 // error message about undefined language 
CHAR *lgname)
{
     CHAR dmdnam[12+1];

     if (tfsopn("*.dmd") > 0) {
          dmdnam[0]='\0';
          while (tfsrdl() != TFSDUN) {
               if (tfstate == TFSLIN
             && tfspfx("Language:")
             && sameas(tfspst,lgname)) {
                    strcpy(dmdnam,tfsfb.ff_name);
                    break;
               }
          }
          tfsopn(dmdnam);
          while (tfsrdl() != TFSDUN) {
               if (tfstate == TFSLIN
             && tfspfx("Module Name:")) {
                    catastro("%s has an undefined language: \"%s\"\n"
                             "(Hint:  use the offline Basic Utility "
                             "WGSDMOD to re-enable\n"
                             "the \"%s\" module.  Then use WGSLANG\n"
                             "to remove the language from this .MSG file.)"
                             ,curfn,lgname,tfspst);
               }
          }
     }
     catastro("%s has an undefined language: \"%s\"\n"
              "(Hint:  you could use the offline Basic Utility WGSLANG\n"
              "to define this language with the <Alt-N> key.  Then you\n"
              "could also use WGSLANG to remove it from this .MSG file.)",
              curfn,txtbuf);
}

VOID
oplinkup(      // link a new option onto the end of the option list 
struct option *opa,        // old, parent option (or NULL if opb brand new) 
struct option *opb)
{
     if (opa == NULL) {
          ophead=opb;
     }
     else {
          opa->next=opb;
     }
     opb->prev=opa;
     opb->next=NULL;
     optail=opb;
}



INT                                          // 0=no disk space; 1=ok, done 
outmsg(VOID)                           // rewrite new messages to MSG files 
{
     INT nchg=0;
     FILE *tmp;
     msgfil *mfp;
     CString realnm;
     CHAR tempnm[12+1];
     Cffblk fb;
     INT lngidx,lngnum;
     LONG actbytes,nbof,ln;

     if (! DirExists( UpdateDir ))
     {
          if (! CreateDirectory( UpdateDir, NULL ))
          {
               ::MessageBox( theMainFrame->m_hWnd, "Unable to create directory " + UpdateDir, 
                             "" SVR_NAME " Configuration Editor", 
                             MB_OK | MB_ICONSTOP | MB_APPLMODAL 
                            );
               return( 0 );
          }
     }

     SaveDialog     dlg;
     dlg.Create( IDD_SAVEDIALOG, theMainFrame );
     dlg.m_saveProgress.SetPos( 0 );

     omgbytes=0L;
     for (mfp=&mfhead ; mfp != NULL ; mfp=mfp->next) {
          if (mfp->flags&ANYCHG) {
               omgbytes+=filelength(fileno(mfp->fp));
          }
     }

     actbytes=0L;

     for (mfp=&mfhead ; mfp != NULL ; mfp=mfp->next) {
          if (mfp->flags&ANYCHG) {
               dlg.m_saveName.SetWindowText( mfp->name );

               while (!(mfp->flags&FILOAD)) {
                    if (lodnxt() == 0) {
                         catastro("CANNOT FULLY LOAD FILE \"%s\"",mfp->name);
                    }
               }
               GBOOL     findRes = fnd1st(&fb,mfp->fullName,0);
               CnfAssert( findRes );
               if (dskfre(".") < (fb.ff_fsize>>10)) {
                    CString   outStr;
                    outStr.Format( "INSUFFICIENT DISK SPACE TO RE-WRITE \"%s\"\r\n\r\n"
                                   "Please free some disk space...",
                                   mfp->name
                                 );

                    ::MessageBox( NULL, outStr, "WGSCNF - Error saving file", MB_OK | MB_APPLMODAL );
                    return(0);
               }

               realnm = mfp->fullName;
               // strcpy(realnm,mfp->name);
               strcpy(tempnm,mfp->name);
               strcpy(tempnm+strlen(tempnm)-4,".$$$");
               if ((tmp=fopen(tempnm,FOPWB)) == NULL) {
                    catastro("CANNOT CREATE %s",tempnm);
               }
               fp=mfp->fp;
               if (setvbuf(tmp,ombuff,_IOFBF,OMBSIZ) != 0) {
                    catastro("Cannot set output buffer for %s",tempnm);
               }

               rewind(fp);
               if (mfp->flags&FILANG) {
                    if (mfp->boflng != 0L) {
                         xfrfil(fp,tmp,mfp->boflng);
                    }
                    else {
                fprintf(tmp,STR_EOL"LANGUAGE {");
                    }
                    fprintf(tmp,"%s}",languages[0]->name);
                    for (lngidx=1 ; lngidx < mfp->lngcnt ; lngidx++) {
                         fprintf(tmp,",{%s}",
                                 languages[mlingo(mfp,lngidx)]->name);
                    }
                    if (mfp->boflng != 0L) {
                         fseek(fp,mfp->eoflng,0);
                         nbof=mfp->bofopt-mfp->eoflng;
                    }
                    else {
                fprintf(tmp,STR_EOL STR_EOL);
                         nbof=mfp->bofopt;
                    }
               }
               else {
                    nbof=mfp->bofopt;
               }
               while (nbof > 0L) {
                    ln=min(nbof,1000L);
                    xfrfil(fp,tmp,ln);
                    nbof-=ln;
                    dlg.m_saveProgress.SetPos( Pct( actbytes + ftell(fp), omgbytes ) );
//                    shobar(actbytes+ftell(fp));
               }
               for (op=mfp->fstopt ; op != NULL
                                  && op->file == mfp ; op=op->next) {
                    if (op->type == FBORDER) {
                         continue;
                    }

                    if (op->flags & ANYCHG)
                    {
                         ++nchg;
                    }

                    LONG adjust;

                    if (op->flags&ISHPAR) {
                         adjust = ftell( tmp );
                         fseek(fp,op->parptr,0);
                         xfrfil(fp,tmp,(LONG)op->parlen);
                         op->parptr = adjust;
                    }
              fprintf(tmp,STR_EOL"%s {",op->name);
                    switch (op->type) {
                    case 'S':
                    case FLEVEL:
                         strcpy(txtbuf,op->curValue);
                         break;
                    case 'T':
                         //   no need to adjust -- ALWAYS already in memory
                         //
                         {
                              altlng *alt = altern(op);
                              loadtv(fp,alt);
                              if (! alt->altered)
                              {
                                   alt->value.fsk = ftell( tmp );
                              }
                         }
                         break;
                    default:
                         sprintf(txtbuf,"%s %s",op->lineDesc,op->curValue);
                         break;
                    }
                    putval(tmp);
                    fprintf(tmp,"}");
                    if (op->type == 'T') {
                         for (lngnum=mfp->lngcnt ; lngnum > 1 ; lngnum--) {
                              if (altern(op)[lngnum-1].value.fsk != 0L) {
                                   break;
                              }
                         }
                         for (lngidx=1 ; lngidx < lngnum ; lngidx++) {
                              fprintf(tmp,",");
                              altlng *alt = &altern(op)[ lngidx ];
                              CnfAssert( alt != NULL );

                              if (alt->value.fsk != 0L) {
                                   fprintf(tmp,"{");

                                   loadtv(fp,alt);
                                   if (! alt->altered)
                                   {
                                        alt->value.fsk = ftell( tmp );
                                   }

                                   putval(tmp);
                                   fprintf(tmp,"}");
                              }
                         }
                    }
                    if (op->flags&ISHING) {
                         if (op->hngop == HNGEXC) {
                              fprintf(tmp," (%s%c)",op->hngVal,op->hngop);

                         }
                         else {
                              fprintf(tmp," (%s%c%s)",
                                      op->hngptr->name,op->hngop,op->hngVal);
                         }
                    }
                    switch (op->type) {
                    case 'C':
                    case 'B':
                         fprintf(tmp," %c",op->type);
                         break;
                    case 'S':
                         fprintf(tmp," S %ld %s",op->ceiling,op->lineDesc);
                         break;
                    case 'T':
                         fprintf(tmp," T %s",op->lineDesc);
                         break;
                    case 'N':
                    case 'L':
                         fprintf(tmp," %c %ld %ld",
                                 op->type,op->floor,op->ceiling);
                         break;
                    case 'H':
                         fprintf(tmp," H %lX %lX",op->floor,op->ceiling);
                         break;
                    case 'E':
                         {
                              CString   elist = op->enList;
                              if (sameas(mfp->name,MAINFL)
                               && sameto("GROUP",op->name)
                               && samend(op->enList,chantyps)) {
                                   elist = op->enList.Left( op->enList.GetLength() - strlen(chantyps) );

                              }
                              else if (sameas(mfp->name,MAINFL)
                               && sameto("HTYPE",op->name)
                               && samend(op->enList,hdwrtyps)) {
                                   elist = op->enList.Left( op->enList.GetLength() - strlen(hdwrtyps) );

                              }
                              fprintf(tmp," E %s",(const char *)elist);
                         }
                         break;
                    }

                    fprintf(tmp,STR_EOL STR_EOL);
                    op->flags &= ~ANYCHG;   // no longer changed vs. disk
               }
               fseek(fp,mfp->eofopt,SEEK_SET);

               fpos_t newEofOpt = ftell( tmp );
               while (xfrfil(fp,tmp,1000) == 1000) {
                    dlg.m_saveProgress.SetPos( Pct( actbytes + ftell(fp), omgbytes ) );
               }
               actbytes+=filelength(fileno(mfp->fp));
               dlg.m_saveProgress.SetPos( Pct( actbytes, omgbytes ) );

               fclose(tmp);
               fclose(fp);
               CString   newName = MakeUpdateName( realnm );

               unlink( newName );

                  

               rename(tempnm,newName);
               mfp->fp=fopen( newName, FOPRB );       // don't leave it pointing to closed file
               CnfAssert( mfp->fp != NULL );
               mfp->ClearChanges();
               mfp->eofopt = newEofOpt;
               mfp->fullName = newName;
          }
     }

     dlg.DestroyWindow();

     CString   status;
     switch (nchg) {
     case 0:
          status = "No changes to configuration.";
          break;
     case 1:
          status = "1 option changed.";
          break;
     default:
          status.Format("%i options changed.",nchg);
          break;
     }

     reportStatus( status );

     return(1);
}

VOID
zapmcv(VOID)                              // delete any changed MCV file(s) 
{
     struct msgfil *mfp;
     CHAR *cp;

     for (mfp=&mfhead ; mfp != NULL ; mfp=mfp->next) {
          if (mfp->flags&ANYCHG) {
               strcpy(strchr(cp=spr("%s",mfp->name),'.'),".mcv");
               unlink(cp);
          }
     }
}


VOID
closeMsgFiles(VOID)                       // close any open .MSG's
{
     msgfil *mfp=NULL;
     msgfil *next=NULL;

     for (mfp=&mfhead ; mfp != NULL ; mfp=next) {
          next=mfp->next;

          if (mfp == &mfhead) {
               mfp->clear();
          }
          else {
               delete(mfp);
          }
     }

     if (langchoose != NULL) {
          for ( int ilingo=0; ilingo < nlingo ; ilingo++ ) {
               CnfAssert( langchoose[ ilingo ] != NULL );
               free( langchoose[ ilingo ] );
          }
          free( langchoose );
          langchoose = NULL;
     }

     if (languages != NULL) {
          for ( int ilingo = 0; ilingo < nlingo; ++ilingo ) {
               if (languages[ ilingo ] != NULL) {
                    free( languages[ ilingo ] );
               }
          }
          free( languages );
          languages = NULL;
     }

     if (altbuf != NULL) {
          free( altbuf );
          altbuf = NULL;
     }

     if (ombuff != NULL) {
          free( ombuff );
          ombuff = NULL;
     }

     if (edthandlers != NULL) {
          free( edthandlers );
          edthandlers = NULL;
     }

     clsmsgrdr();
}

INT
mlingo(     // xlate language index for file -> index for system 
struct msgfil *mfp,                                       // .msg file info 
INT lngidx)                                     // must be 0..mfp->lngcnt-1 
{                                           // returns a number 0..nlingo-1 
     INT ilingo;

     for (ilingo=0 ; ilingo < nlingo ; ilingo++) {
          if (mfp->mslidx[ilingo] == lngidx) {
               return(ilingo);
          }
     }
     catastro("MLINGO: language %d of %d disappeared from %s",
              lngidx,mfp->lngcnt,mfp->name);
     return(0);
}


const CHAR *
skblk(                                                // skip blanks 
const CHAR *string)
{
     while (*string == ' ') {
          string++;
     }
     return(string);
}

const CHAR *
skword(                                                 // skip word 
const CHAR *string)
{
     while (*string != '\0' && *string != ' ') {
          string++;
     }
     return(skblk(string));
}

const CHAR *
eltokn(                                 // get token from enumerated list 
INT n)
{
     const CHAR *cp;
     INT i;

     for (i=0,cp=skblk(op->enList) ; i < n && *cp ; i++,cp=skword(cp)) {
     }
     return(cp);
}

CHAR *
l2ah(                           // convert long int to a hexadecimal string 
LONG lnum)
{
     static CHAR buffer[8+1];

     sprintf(buffer,"%lX",lnum);
     return(buffer);
}


INT
chkvis(          // Is target option visible? (based on its "hinge" option) 
struct option *opt)
{                                                // return true iff visible 
     const CHAR *ap;         // pointer scanning value of hinge-referenced option 
     const CHAR *bp;                       // pointer scanning each value in list 
     const CHAR *cp;              // ptr to beginning of each hinge value in list 
     INT same;                           // found a match anywhere in list? 

     if (!(opt->flags&ISHING)) {
          return(1);                               // visible if not hinged 
     }
     if (opt->hngop == HNGEXC) {   // don't ever show an option with this
          return(0);
     }
     if (opt != opt->hngptr && !chkvis(opt->hngptr)) {
          return(0);            // not vis if hinged on an invisible option 
     }
     if (opt->hngptr->type == 'T') {
          return(0);                   // not vis if hinged on a 'T' option 
     }
     if (opt->hngVal.GetLength() == 0) {
          return((opt->hngop == HNGEQ) == (opt->hngptr->curValue.GetLength() == 0));
     }                  // =) tests option empty, #) tests option non-empty 
     for (cp=opt->hngVal ; *cp ; ) {
          same=1;
          for (ap=opt->hngptr->curValue,bp=cp
            ; (*bp != '\0' && *bp != HNGDLM) || *ap != '\0'
            ; bp++,ap++) {
               if (toupper(*bp) != toupper(*ap)) {
                    same=0;
                    break;
               }
          }
          if (same) {
               return(opt->hngop == HNGEQ);
          }
          while (*cp && *cp != HNGDLM) {
               cp++;
          }
          if (*cp == HNGDLM) {
               cp++;
          }
     }
     return(opt->hngop == HNGNE);
}

VOID
rsvhng(                                    // resolve a hinge reference 
struct option *rop)
// Implicit inputs:     
//   char hngnam[]      
//   char hngval[]      
//   rop->flags&ISHING  
// Implicit outputs:    
//   rop->hngptr        
//   rop->hngval        
{
     struct option *opp;
     CHAR buff[160];

     if (rop->flags&ISHING) {
          if (rop->hngop == HNGEXC && sameas(hngnam,EXCSTG)) {
               rop->hngVal=EXCSTG;
               rop->hngptr=rop;
               return;
          }
          for (opp=rop->file->fstopt ; opp != NULL ; opp=opp->next) {
               if (sameas(opp->name,hngnam)) {
                    rop->hngptr=opp;
                    rop->hngVal=hngval;
                    return;      // priority:  hinge to option in same file 
               }
               if (opp == rop) {
                    break;   // self is last chance in same file 
               }
          }
          for (opp=mfhead.fstopt ; opp != NULL
                                && opp->file == &mfhead ; opp=opp->next) {
               if (sameas(opp->name,hngnam)) {
                    rop->hngptr=opp;
                    rop->hngVal=hngval;
                    return;         // allow:  hinge to option in main file 
               }
          }
          sprintf(buff,"Option %s in %s should not hinge on\noption %s.  It "
                       "should only hinge on an option that\nappears earlier "
                       "in the same .MSG file, and at the same\nlevel.",
                       rop->name,rop->file->name,hngnam);
          catastro(buff);
     }
}


VOID
parms(                       // parse the numeric parameter(s) of an option 
struct option *opp,                                               // option 
CHAR *cs,                 // scanf control string for one or two long-int's 
INT np)                                    // number of parameters (1 or 2) 
{
     if (fscanf(rdfp,cs,&opp->floor,&opp->ceiling) != np) {
          catastro("BAD OR MISSING PARAMETERS AFTER OPTION %s IN %s",
                   opp->name,curfn);
     }
}

VOID
parse(VOID)  // Parse the description and value from text from between {}'s 
{              // value is last word of txtbuf, lindsc is whatever precedes 
     CHAR *cp;

     cp=lastwd(txtbuf);
     cp[VALLEN]='\0';
     strcpy(rdval,cp);
     *cp='\0';
     txtbuf[LINLEN]='\0';
     strcpy(lindsc,txtbuf);
}

VOID
parsec(VOID) // Parse descrip and val from text from between {}'s for C opt 
{              // value is last char of txtbuf, lindsc is whatever precedes 
     CHAR *cp;

     cp=&txtbuf[strlen(txtbuf)-1];
     cp[VALLEN]='\0';
     strcpy(rdval,cp);
     *cp='\0';
     txtbuf[LINLEN]='\0';
     strcpy(lindsc,txtbuf);
}

VOID
parstg(       // Parse the string parameter at the end of the option 
CHAR *to,       // make to==NULL and len==0 if there is no string parameter 
INT len)
{
     INT i,c;
     if (to != NULL) {
          *to='\0';
     }
     if ((c=getc(rdfp)) == ' ') {
          c=getc(rdfp);
     }
     for (i=0 ; c != EOF && c != CHR_EOL ; i++) {
          if (i < len) {
               *to++=c;
               *to='\0';
          }
          c=getc(rdfp);
     }
     if (c == CHR_EOL) {
          getc(rdfp);
     }
}

VOID
addlang(                             // add a language to a file 
struct msgfil *mfp,
INT ilingo)
{
     if (mfp->lngcnt >= nlingo) {
          catastro("Already %d languages in %s",mfp->lngcnt,mfp->name);
     }
     if (ilingo == 0 || mfp->mslidx[ilingo] != 0) {
          catastro("%s already has a %s language",mfp->name,
                                                  languages[ilingo]->name);
     }
     mfp->mslidx[ilingo]=mfp->lngcnt++;
     mfp->flags|=FILANG;
}

INT
rdopt(                                    // read an option from a MSG file 
struct option *opp)                                        // put data here 
        // uses data from rdmsg(), reads in hinge and option specifications 
                                                   // returns 1=done, 0=EOF 
                        // Implicit inputs:     
                        //   FILE *rdfp;        
                        //   CHAR *curfn;       
                        //   CHAR *msgnam;      
                        //   CHAR *cmtptr;      
                        // Implicit outputs:    
                        //   opp->name          
                        //   opp->parptr        
                        //   opp->parlen        
                        //   opp->flags&ISHPAR  
                        //   opp->hngop         
                        //   opp->flags&ISHING  
                        //   opp->type          
                        //   opp->ceiling       
                        //   opp->floor         
                        //   CHAR hngnam[]      
                        //   CHAR hngval[]      
                        //   CHAR rdval[]       
                        //   CHAR lindsc[]      
                        //   CHAR txtbuf[]      
                        //   struct altlng altlnm[] 
{
     INT i,c;

     opp->parptr=cmtptr;
     if ((opp->parlen=cmtlen) > 0) {
          opp->flags|=ISHPAR;
     }
     else {
          opp->flags&=~ISHPAR;
     }
     strcpy(opp->name,msgnam);
     if ((c=scanalt()) != EOF && isspace(c)) {
          c=nxtink();
     }
     if (c == EOF) {
          return(0);
     }
     if (c == OPNHNG) {
          for (i=0 ; isalnum(c=getc(rdfp)) && i < MOPTLEN ; i++) {
               hngnam[i]=c;
          }
          hngnam[i]='\0';
          if (c == HNGEXC) {       // for special UNUSED* hinge option
               if (!sameas(hngnam,EXCSTG)) {
                    catastro("BAD HINGE SPECIFICATION IN %s OPTION %s",curfn,
                                                                       opp->name);
               }
          }
          else if (c != HNGEQ && c != HNGNE) {
               catastro("BAD HINGE SPECIFICATION IN %s OPTION %s",curfn,
                                                                  opp->name);
          }
          opp->hngop=c;
          for (i=0 ; (c=getc(rdfp)) != EOF && c != CLSHNG && i < VALLEN ; i++) {
               hngval[i]=c;
          }
          hngval[i]='\0';
          if (c != CLSHNG) {
               catastro("BAD HINGE VALUE IN %s OPTION %s",curfn,opp->name);
          }
          if ((c=nxtink()) == EOF) {
               return(0);
          }
          opp->flags|=ISHING;
     }
     switch (opp->type=toupper(c)) {
     case 'H':
          parms(opp,"%lx%lx",2);
          parstg(NULL,0);
          parse();
          break;
     case 'N':
     case 'L':
          parms(opp,"%ld%ld",2);
          parstg(NULL,0);
          parse();
          break;
     case 'C':
          parstg(NULL,0);
          parsec();
          rdval[1]='\0';
          break;
     case 'B':
          parstg(NULL,0);
          parse();
          strcpy(enlist,"NO YES");
          break;
     case 'E':
          parstg(enlist,ENLLEN);
          parse();
          break;
     case 'S':
          parms(opp,"%ld",1);
          if ((opp->ceiling=opp->floor) == 0L) {
               opp->ceiling=LINLEN;
          }
          strcpy(rdval,txtbuf);
          parstg(lindsc,LINLEN);
          //   This was here so that the DOS version could display the full
          //   descriptive text at the expense of the option text.
          //   No longer needed.
          //
          //opp->ceiling=min(opp->ceiling,LINLEN-strlen(lindsc)-1);
          rdval[(INT)opp->ceiling]='\0';
          break;
     case 'T':
          parstg(lindsc,LINLEN);
          break;
     default:
          //   special case: certain older message files may have FILEnn tucked
          //   away in level 0.  When running WGSCNF -l0, we need to ignore them
          //
          if (sameto( "FILE", opp->name ) && isdigit( opp->name[ 4 ] ))
          {
               opp->flags |= ISHING;
               strcpy( hngnam, EXCSTG );
               opp->hngop = HNGEXC;
               hngval[ 0 ] = 0;
               parstg(lindsc,LINLEN);
               return( 1 );
          }

          catastro("BAD OR MISSING OPTION TYPE AFTER %s IN %s",opp->name,curfn);
          break;
     }
     depad(lindsc);
     return(1);
}

VOID
fndfls(VOID)               // find out what .MSG files we need to configure 
{
     CHAR *msgnam;
     struct msgfil *mfp;
     Cffblk fb;

     if (tfsopn("wgserv.cfg") != 1) {
          catastro("CANNOT FIND wgserv.cfg");
     }
     while (tfsrdl() != TFSDUN) {
          if (tfstate == TFSLIN
           && tfspfx("MSG=")
           && !msgxst(msgnam=spr("%s.msg",tfspst))) {      // weed dups! 
               CString   newName = MakeUpdateName( msgnam );
               CString   useName;

               if (fnd1st( &fb, newName, 0 )) {
                    useName = newName;
               }
               else if (fnd1st(&fb,msgnam,0)) {
                    useName = msgnam;
               }
               else {
                    catastro("Cannot find %s",msgnam);
               }
               allbytes+=fb.ff_fsize;
               //mfp=(struct msgfil *)alczer(sizeof(struct msgfil));
               mfp = new msgfil;
               strcpy(mfp->name,msgnam);
               mfp->fullName = useName;
               mftail->next=mfp;
               mftail=mfp;
               nfiles++;
          }
          else if (tfstate == TFSLIN
             && tfspfx("CHANTYPE=")) {
               stlcat(chantyps," ",ENLLEN+1);
               stlcat(chantyps,tfspst,ENLLEN+1);
          }
          else if (tfstate == TFSLIN
             && tfspfx("HDWRTYPE=")) {
               stlcat(hdwrtyps," ",ENLLEN+1);
               stlcat(hdwrtyps,tfspst,ENLLEN+1);
          }
     }
}

INT
msgxst(                // does this .MSG file exist in list already? 
CHAR *msgnam)
{
     struct msgfil *mfsp;

     for (mfsp=&mfhead ; mfsp != NULL ; mfsp=mfsp->next) {
          if (sameas(mfsp->name,msgnam)) {
               return(1);
          }
     }
     return(0);
}


VOID reportStatus(const CString &msg)
{
     if (theMainFrame != NULL)
     {
          theMainFrame->SetMessageText( msg );
     }
}

static VOID reportError(const char *fmt,...)
{
     char buf[ 2048 ];
     va_list args;

     va_start( args, fmt );

     _vsnprintf( buf, sizeof( buf ), fmt, args );

     va_end( args );

     ::MessageBox( NULL, fmt, "WGSCNF", MB_OK | MB_APPLMODAL );
}


static CString
MakeUpdateName(
const char *st)
{
     CString   result;

     if (strchr( st, SL ) != NULL) {
          result = st;
     }
     else
     {
          result = UpdateDir + SLS + st;
     }

     return( result );
}


int Pct( int part, int total )
{
     int       result = (total == 0) ? 100 :
                                       ((part * 100) / total);

     return( result );
}


/*

This program reads options from the .MSG files on the current directory,
allows the operator to edit these options, and then stores them back into the
.MSG files.

The file MJRBBS.CFG defines the user-languages and the .MSG files to be
searched for options.


Syntax of Options
-----------------

     descriptive text  <option name> { <option value> } <option specs>

the option name is 1 to 8 characters, the option specs always fit on one line


Example syntax of an option in an .MSG file (All types but types 'S' and 'T'):

     This is a paragraph-size
     description of the option:
     the "help" on the option.

     NAME {One line description of option:  VALUE} <format code> <format parms>


Example syntax of a type 'S' option in the .MSG file:

     This is a paragraph-size
     description of the option:
     the "help" on the option.

     NAME {String value of the option} S <length> One line description of option


Example syntax of a type 'T' option in the .MSG file:

     This is a paragraph-size
     description of the option:
     the "help" on the option.

     NAME {
     The value of this option can be
     an entire screen-ful of text
     } T One line description of option



Format Codes, and Parameters that follow, for various option types
------------------------------------------------------------------
C                     single printable character
S <L> <Descr>         one-line string (with max length and description)
N <F> <C>             decimal number, with floor and ceiling
H <F> <C>             hexadecimal number, with floor and ceiling
L <F> <C>             long decimal number, with floor and ceiling
B                     binary:  either YES or NO accepted
E <Val1> <Val2> ...   enumeration of the possible values of the option
T <Descr>             Text paragraph of information



Levels
------

Options are categorized into levels 1 and up.  Each .MSG file starts out at an
undefined level and starts a new level with a "null option", for example:

     LEVEL1 {}

All options which follow this one would be considered level 1 options.  Level
1, 2, and 3 options are edited in exclusive sets by running this program with
a "-L1", "-L2", or "-L3" command line directive.



Hinging
-------

A hinge is a device that can inhibit certain options based on the values of
other PRECEDING options.  Example:  If there is no "card #3", then you don't
want to ask for the base address, number of channels for that card, etc.  We
say that these options "hinge" on the selection of card #3.

The hinge specifier precedes the format codes and parameters (above) for the
option which may be inhibited.  The syntax is:

     (<hinge option><hinge operator><hinge value>)

          <hinge option>     The name of the option upon which the current
                             option hinges
          <hinge operator>   Relational operator "=" for equals, "#" for not
                             equals
          <hinge value>      The value to which is compared the value of the
                             hinge option (according to the hinge operator)

Example:

     THISOPT {Some string value} (THATOPT=YES) S 44 Description

Then the option THISOPT is only seen on the WGSCNF screen when the option
THATOPT has a value exactly equal to "YES".  THATOPT must precede THISOPT.

     IMPORTANT:  The hinge option must reference a PRE-DEFINED option,
     that is, one that precedes it in the current file.

Note:  You cannot hinge on the value of a text option


Note #2: If a hinge option with the verbage "UNUSED*" (no quotes) is used in
an option, the option is never displayed.  This is useful for having certain
messages not show up under one OS but not under another.  "UNUSED" gets put in
the option hngval field and a "*" gets put in the hngop field.  The code
actually checks the hngop field to determine if the option should be forced
invisible.


Command line parameters:
------------------------

WGSCNF -Ln [-M] [?]

     -Ln  Configure only LEVEL<n> options
     -M   Display memory usage statistics upon return to DOS
     ?    Gives you a parameter help message


Exit codes (for use by batch files):
------------------------------------

    errorlevel 0 -  operator quit(F9) or exit(F10)
    errorlevel 1 -  bad command line parameters
    errorlevel 49 - out of memory
    errorlevel 70 - 1 catastro, clean kill
    errorlevel 80 - 2 cata's, need reboot
    errorlevel 90 - cata w/no shutdown vec
*/