/***************************************************************************
 *                                                                         *
 *   EXCPHAND.C                                                            *
 *                                                                         *
 *   Copyright (c) 1996-19997 Galacticomm, Inc.    All Rights Reserved.    *
 *                                                                         *
 *   Exception handler & exception report for NT version.  This module     *
 *   only uses WIN32 API calls.                                            *
 *                                                                         *
 *                                      - Ilya Minkin 01/26/96             *
 *                                                                         *
 ***************************************************************************/

#include <windows.h>
#include "gcomm.h"
#include "majorbbs.h"
#include "brkthu.h"
#include "phasedbg.h"
#include "excphand.h"
#include "elogapi.h"
#include "wgsmsgs.h"
#include "excprprt.h"
#include "welog.h"

#define FILREV "$Revision: 20 $"

// #define EXTENDED_MEMMAP

#define MAXSTP 20                  // max number of steps in stack walk

typedef struct {                   // region info
     VOID *rgnAddress;             //   address of mem region
     DWORD rgnProtection;          //   protection attribute
     DWORD rgnSize;                //   size
     DWORD rgnType;                //   type (Free,Image,Mapped,Private)
     DWORD rgnBlocks;              //   number of blocks
     DWORD rgnGuardBlks;           //   blocks with "guard" attribute
     GBOOL rgnIsStack;             //   is region a thread stack
} RGNINFO;

typedef struct {                   // block info
     VOID *blkAddress;             //   block address
     DWORD blkProtection;          //   protection attribute
     DWORD blkSize;                //   size
     DWORD blkType;                //   type (Free,Image,Mapped,Private)
} BLKINFO;

#define makePtr(cast,ptr,addValue) (cast)((DWORD)(ptr)+(DWORD)(addValue))

static VOID addToEventLog(CHAR *errorMessage);
static DWORD fixVirtualQuery(LPVOID lpvAddress,
                             PMEMORY_BASIC_INFORMATION pmbiBuffer,
                             DWORD cbLength);
static VOID printRptHeader(HANDLE hRptFile,CHAR *errorMessage,
                           CONTEXT *contRecord,VOID *excpAddress);
static VOID printGlobalInfo(HANDLE hRptFile,struct tagGVars *gVars);
static VOID printPhaseTrans(HANDLE hRptFile,struct tagGVars *gVars);
static VOID printMemoryMap(HANDLE hRptFile,
                           ULONG **stackStart,ULONG **stackEnd,ULONG *nStacks);
static VOID printStack(HANDLE hRptFile,ULONG *esp,ULONG *ebp,ULONG **stackStart,
                       ULONG **stackEnd,ULONG nStacks,VOID *excpAddress);
static VOID printRptFooter(HANDLE hRptFile);

static VOID makeRgnMapLine(RGNINFO *rgnInfo,CHAR *line,INT linesize);
static VOID addModuleAttr(CHAR *line,INT linesize,CHAR *modFileName);
static CHAR *memType2Text(DWORD memType);
static GBOOL getRgnInfo(VOID *memStart,RGNINFO *rgnInfo);
static VOID walkRgn(RGNINFO *rgnInfo);
static VOID displayExcpMsg(CHAR *errorMessage);
static VOID copyErrorMessage(CHAR *msgDest,const CHAR *msgSrc,INT msgDestSize);

#ifdef EXTENDED_MEMMAP
static GBOOL getBlkInfo(VOID *memStart,BLKINFO *blkInfo);
static VOID makeBlkMapLine (BLKINFO *blkInfo,CHAR *line);
static CHAR *memProt2Text(DWORD memProtect);
static CHAR *memFlags2Text(DWORD memFlags);
#endif

static VOID loadGVars(struct tagGVars *gVars);
static GBOOL isGoodPointer(VOID *ptr,INT byteCount);
static GBOOL isGoodString(CHAR *ptr);
static UINT isGoodBlock(VOID *ptr,UINT nbytes);
static VOID getFuncNames(ULONG pFunc,CHAR funcNames[][64],ULONG funcAddr[]);
static GBOOL loadExportLists(ULONG baseAddress,ULONG **functions,
                             USHORT **ordinals,CHAR ***names,
                             INT *nFunc,INT *nNames);

CHAR *exptReportFile="GALEXCEP.OUT";
static CHAR curDirectory[260];

static ULONG pMod[512];
static nMods=0;


// local copies of variables from wgserver and galgsbl.  GetProcAddress() is
// used to obtain values without explicit linking to wgserver.lib and galgsbl.lib
// if exception handler is used for offline utilites these values are not
// available and do not show up in exception report
struct tagGVars {
     GBOOL fGVarsLoaded;
     CHAR *_input;
     INT _status;
     struct module **_module;
     INT _nterms;
     INT _nmods;
     INT *_channel;
     INT _usrnum;
     CHAR *_bbsttl;
     CHAR *_bturno;
     struct usracc *_usaptr;
     struct user *_usrptr;
     CHAR *_version;
};

static POSTEXCPPROC pPostExcpProc=NULL;

VOID
setPostExcpProc(                   // set function to execute after excp handler
POSTEXCPPROC pProc)                //   function pointer
{
     pPostExcpProc=pProc;
}

LONG                               //   exit/continue/serch next handler
excpFilter(                        // handle exception and print report
EXCEPTION_POINTERS *excpPtrs)      //   ptr to exception info
{
     EXCEPTION_RECORD excpRecord;
     CONTEXT contextRecord;
     CHAR errMsg[256];
     LONG retVal=EXCEPTION_EXECUTE_HANDLER;
     static INT nExcp=0;

     excpRecord=*(excpPtrs->ExceptionRecord);
     contextRecord=*(excpPtrs->ContextRecord);
     errMsg[0]='\0';
     switch(excpRecord.ExceptionCode) {
     case EXCEPTION_ACCESS_VIOLATION:
          wsprintf(errMsg,"Access Violation. Attempt to %s %x",
                  excpRecord.ExceptionInformation[0] ? "write to:" :
                                                       "read from:",
                  excpRecord.ExceptionInformation[1]);
          break;
     case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
          lstrcpy(errMsg,"Array Bounds Exceeded");
          break;
//     case EXCEPTION_BREAKPOINT:
//          lstrcpy(errMsg,"Breakpoint");
//          break;
     case EXCEPTION_DATATYPE_MISALIGNMENT:
          lstrcpy(errMsg,"Data Type Misalignment");
          break;
//     case EXCEPTION_FLT_DENORMAL_OPERAND:
//          lstrcpy(errMsg,"Floating-point Denormal Operand");
//          break;
     case EXCEPTION_FLT_DIVIDE_BY_ZERO:
          lstrcpy(errMsg,"Floating-point Divide by Zero");
          retVal=EXCEPTION_EXECUTE_HANDLER;
          break;
//     case EXCEPTION_FLT_INEXACT_RESULT:
//          lstrcpy(errMsg,"Floating-point Inexact Result");
//          break;
//     case EXCEPTION_FLT_INVALID_OPERATION:
//          lstrcpy(errMsg,"Floating-point Invalid Operation");
//          break;
//     case EXCEPTION_FLT_OVERFLOW:
//          lstrcpy(errMsg,"Floating-point Overflow");
//          break;
     case EXCEPTION_FLT_STACK_CHECK:
          lstrcpy(errMsg,"Floating-point Stack Check");
          break;
//     case EXCEPTION_FLT_UNDERFLOW:
//          lstrcpy(errMsg,"Floating-point Underflow");
//          break;
     case EXCEPTION_ILLEGAL_INSTRUCTION:
          lstrcpy(errMsg,"Illegal Instruction");
          break;
//     case EXCEPTION_IN_PAGE_ERROR:
//          lstrcpy(errMsg,"Page Error");
//          break;
     case EXCEPTION_INT_DIVIDE_BY_ZERO:
          lstrcpy(errMsg,"Integer Divide by Zero");
          break;
//     case EXCEPTION_INT_OVERFLOW:
//          lstrcpy(errMsg,"Integer Overflow");
//          break;
//     case EXCEPTION_INVALID_DISPOSITION:
//          lstrcpy(errMsg,"Integer Invalid Disposition");
//          break;
     case EXCEPTION_NONCONTINUABLE_EXCEPTION:
          lstrcpy(errMsg,"Noncontinuable Exception");
          break;
//     case EXCEPTION_PRIV_INSTRUCTION:
//          lstrcpy(errMsg,"Priv Instruction");
//          break;
//     case EXCEPTION_SINGLE_STEP:
//          lstrcpy(errMsg,"Single Step");
//          break;
     case EXCEPTION_STACK_OVERFLOW:
          lstrcpy(errMsg,"Stack Overflow");
          break;
     default:
          return(EXCEPTION_CONTINUE_SEARCH);
     }
     if (nExcp++ > 0) {
     // if something happened while we are handling prev. exception just die
     // we allow multiple ASSERTs since this is how DOS used to work
          return(EXCEPTION_CONTINUE_SEARCH);
     }
     if (retVal == EXCEPTION_EXECUTE_HANDLER) {
          displayExcpMsg(errMsg);
     }
     printExcpReport(&contextRecord,errMsg,excpRecord.ExceptionAddress,exptReportFile);
     if (pPostExcpProc) {
          pPostExcpProc();
     }
     return(retVal);
}

static VOID
addToEventLog(
CHAR *errorMessage)
{
     CHAR exeName[1024];
     const CHAR *pInsertStg[2];

     GetModuleFileName((HMODULE)GetModuleHandle(NULL),exeName,sizeof(exeName));
     pInsertStg[0]=exeName;
     pInsertStg[1]=errorMessage;
     elogStatusEx(ELOG_EXCEPTION,pInsertStg,2,"WGServer");
}

GBOOL                              //   TRUE=success,FALSE=failed open file
printExcpReport(                   // print exception report
CONTEXT *contRecord,               //   platform dependant context record
CHAR *errorMessage,                //   error message
VOID *excpAddress,                 //   exception address
CHAR *fileName)                    //   report file name
{
     HANDLE hRptFile;
     ULONG *stackStart[32];
     ULONG *stackEnd[32];
     ULONG nStacks;
     CHAR errMsg[256];
     HINSTANCE hInst;
     FARPROC pFunc;
     struct tagGVars gVars;

     copyErrorMessage(errMsg,errorMessage,sizeof(errMsg));
     addToEventLog(errMsg);
     if ((hInst=LoadLibrary("wgswelog.dll")) != NULL) {
          if ((pFunc=GetProcAddress(hInst,"welogAdd")) != NULL){
               ((WELOGADD)pFunc)(errMsg);
          }
          FreeLibrary(hInst);
     }
     loadGVars(&gVars);
     hRptFile=CreateFile(fileName,
                         GENERIC_READ|GENERIC_WRITE,
                         0,
                         NULL,
                         OPEN_ALWAYS,
                         FILE_ATTRIBUTE_NORMAL,/*|FILE_FLAG_WRITE_THROUGH,*/
                         NULL);
     if (hRptFile == INVALID_HANDLE_VALUE) {
          return(FALSE);
     }
     SetFilePointer(hRptFile,0,NULL,FILE_END);
     printRptHeader(hRptFile,errMsg,contRecord,excpAddress);
     printGlobalInfo(hRptFile,&gVars);
     printPhaseTrans(hRptFile,&gVars);
     nStacks=sizeof(stackStart)/sizeof(ULONG *);
     printMemoryMap(hRptFile,stackStart,stackEnd,&nStacks);
     printStack(hRptFile,(ULONG*)contRecord->Esp,(ULONG*)contRecord->Ebp,
                stackStart,stackEnd,nStacks,excpAddress);
     printRptFooter(hRptFile);
     CloseHandle(hRptFile);
     return(TRUE);
}

static VOID
printRptHeader(                    // print report header
HANDLE hRptFile,                   //   handle to open report file
CHAR *errorMessage,                //   error message
CONTEXT *contRecord,               //   platform dependant context record
VOID *excpAddress)                 //   exception address
{
     CHAR line[512],*pIp;
     SYSTEMTIME sysTime;
     DWORD nBytes;
     INT i;

     GetLocalTime(&sysTime);
     wsprintf(line,
              "\r\n" SVR_NAMEC " EXCEPTION @ %08X (see \"Routines\" section below)"
              "\r\nRecorded %d/%d/%d %d:%02d:%02d\r\n",
              excpAddress,sysTime.wMonth,sysTime.wDay,sysTime.wYear,
              sysTime.wHour,sysTime.wMinute,sysTime.wSecond);
     WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
     WriteFile(hRptFile,errorMessage,lstrlen(errorMessage),&nBytes,NULL);
     wsprintf(line,"\r\nAX=%04X BX=%04X CX=%04X DX=%04X SI=%08X DI=%08X",
              contRecord->Eax,contRecord->Ebx,contRecord->Ecx,contRecord->Edx,
              contRecord->Esi,contRecord->Edi);
     WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
     wsprintf(line,"\r\nBP=%08X SP=%08X IP=%08X Flags=%08X\r\nCS=%08X SS=%08X",
              contRecord->Ebp,contRecord->Esp,contRecord->Eip,
              contRecord->EFlags,contRecord->SegCs,contRecord->SegSs);
     WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
     wsprintf(line,"\r\nCurrent IP==>");
     for (i=0,pIp=(CHAR*)excpAddress; i < 16 ; i++,pIp++) {
          if (IsBadCodePtr((FARPROC)pIp)) {
               break;
          }
          wsprintf(&line[lstrlen(line)],"%02X ",*pIp);
     }
     WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
}

static VOID
printGlobalInfo(
HANDLE hRptFile,                   // handle to open report file
struct tagGVars *gVars)            //   values of global variables
{
     CHAR line[512];
     DWORD nBytes;
     CHAR uid[30];                 // this is really UIDSIZ
     INT usrval=0;

     if (gVars->fGVarsLoaded) {
          wsprintf(line,"\r\n\r\n%s  (%s)\r\nServer Version: %s",
                   isGoodPointer(gVars->_bbsttl,2) ? gVars->_bbsttl
                                                  : "<server not available>",
                   isGoodPointer(gVars->_bturno,2) ? gVars->_bturno
                                                  : "<regnum not available>",
                   isGoodPointer(gVars->_version,sizeof(CHAR*)) ? gVars->_version
                                                  : "<unknown>");
          WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
          wsprintf(line,"\r\nUser %d of %d",gVars->_usrnum,gVars->_nterms);
          WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
          if (0 <= gVars->_usrnum && gVars->_usrnum < gVars->_nterms) {
               if (isGoodPointer(gVars->_channel,sizeof(INT))) {
                    wsprintf(line,"  channel %02X",gVars->_channel[gVars->_usrnum]);
                    WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
               }
          }
          if (isGoodBlock(gVars->_usaptr,sizeof(struct usracc)) == sizeof(struct usracc)
           && isGoodString(gVars->_usaptr->userid)
           && isprint(gVars->_usaptr->userid[0])) {
               MoveMemory(uid,gVars->_usaptr->userid,UIDSIZ-1);
               uid[UIDSIZ-1]='\0';
               wsprintf(line,"  User-ID \"%s\"",uid);
               WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
          }
          wsprintf(line,"  status %d\r\n",gVars->_status);
          WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
          if (isGoodBlock(gVars->_usrptr,sizeof(struct user)) == sizeof(struct user)) {
               usrval=1;
               wsprintf(line,"\r\nOnline level %d, state %d, substate %d",
                        gVars->_usrptr->usrcls,gVars->_usrptr->state,
                        gVars->_usrptr->substt);
               WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
          }
     }
     if (isGoodPointer(curmbk,sizeof(struct msgblk))) {
          if (isGoodBlock(curmbk,sizeof(struct msgblk))
                                                  == sizeof(struct msgblk)) {
               if (isGoodString(curmbk->filnam)) {
                    wsprintf(line,"\r\nMSG:%s/%d",curmbk->filnam,curmbk->lstmsg);
               }
               else {
                    wsprintf(line,"\r\nMSG:<INVALID STRING>/%d",curmbk->lstmsg);
               }
               WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
          }
     }
     if (gVars->fGVarsLoaded) {
          if (usrval
           && gVars->_usrptr->state >= 0
           && gVars->_usrptr->state < gVars->_nmods
           && isGoodPointer(gVars->_usrptr,sizeof(struct user))
           && isGoodPointer(gVars->_module[gVars->_usrptr->state],sizeof(struct module))
           && isGoodString(gVars->_module[gVars->_usrptr->state]->descrp)) {
               wsprintf(line,"\r\nModule \"%s\"",
                        gVars->_module[gVars->_usrptr->state]->descrp);
               WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
          }
          if (gVars->_input[0] == '\0' || isprint(gVars->_input[0])) {
               gVars->_input[256-1]='\0';       // uses INPSIZ for calculation
               wsprintf(line,"\r\nInput \"%s\"",gVars->_input);
          }
          else {
               wsprintf(line,"\r\nInput unprintable (starts with %02X)",gVars->_input[0]);
          }
          WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
     }
}

static VOID
printPhaseTrans(
HANDLE hRptFile,                   //   handle to open report file
struct tagGVars *gVars)            //   values of global variables
{
     struct phasedbg *pPhDebug[NUMPHS];
     INT i;
     INT totalPh;
     DWORD nBytes;
     CHAR line[512];

     for (i=0,totalPh=0 ; i < NUMPHS ; i++,totalPh++) {
          if ((pPhDebug[i]=pstphs(i)) == NULL) {
               break;
          }
     }
     if (totalPh == 0) {
          return;
     }
     wsprintf(line,"\r\n\r\nLast %d phase transitions (top=early, bottom=latest):",
              totalPh);
     WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
     for (i=totalPh-1 ; i >= 0 ; i--) {
          wsprintf(line,pPhDebug[i]->flags&PDBBEG ? "\r\nBEG %-24.24s0x%08lX"
                                                  : "\r\nEND %-24.24s0x%08lX",
                   pPhDebug[i]->name,pPhDebug[i]->num);
          WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
          if (gVars->fGVarsLoaded) {
               if (0 <= pPhDebug[i]->usrnum && pPhDebug[i]->usrnum < gVars->_nterms
                && isGoodPointer(gVars->_channel,sizeof(INT))
                && isGoodPointer(gVars->_module,sizeof(struct module*))
                && isGoodPointer(gVars->_module[pPhDebug[i]->usrsta],sizeof(struct module))
                && isGoodString(gVars->_module[pPhDebug[i]->usrsta]->descrp)) {
                    wsprintf(line," CH%02X%2d",
                             gVars->_channel[pPhDebug[i]->usrnum],
                             pPhDebug[i]->usrcls);
                    WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
                    if ((pPhDebug[i]->usrcls > SUPIPG || pPhDebug[i]->usrcls == BBSPRV)
                     && 0 <= pPhDebug[i]->usrsta && pPhDebug[i]->usrsta < gVars->_nmods) {
                         wsprintf(line," %-24.24s",gVars->_module[pPhDebug[i]->usrsta]->descrp);
                    }
                    else {
                         wsprintf(line," state=%6d            ",pPhDebug[i]->usrsta);
                    }
                    WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
                    wsprintf(line,"%6d",pPhDebug[i]->usrsbs);
                    WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
               }
               else {
                    wsprintf(line," usrnum=%d",pPhDebug[i]->usrnum);
                    WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
               }
          }
     }
}

static VOID
printMemoryMap(                    // print memory map
HANDLE hRptFile,                   //   handle to open report file
ULONG **stackStart,                //   beginning of stack region
ULONG **stackEnd,                  //   end of stack region
ULONG *nStacks)                    //   number of stacks
{
     CHAR line[512];
     VOID *memStart=0x0;
     RGNINFO rgnInfo;
     DWORD nBytes;
     ULONG st=0;

     if (GetCurrentDirectory(260,curDirectory) != 0) {
          lstrcat(curDirectory,"\\");
     }
     lstrcpy(line,"\r\n\r\nMemory Map:");
     WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
     while (getRgnInfo(memStart,&rgnInfo)) {
          if (stackStart[st] != NULL && stackEnd[st] == NULL) {
               stackEnd[st++]=rgnInfo.rgnAddress;
          }
          if (st < *nStacks && rgnInfo.rgnIsStack) {
               stackStart[st]=rgnInfo.rgnAddress;
               stackEnd[st]=NULL;
          }
          line[0]='\r';
          line[1]='\n';
          makeRgnMapLine(&rgnInfo,line+2,sizeof(line)-2);
          WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
#ifdef EXTENDED_MEMMAP
          {
               BLKINFO blkInfo;
               INT nBlock;

               for (nBlock=0 ;
                    nBlock < rgnInfo.rgnBlocks && getBlkInfo(memStart,&blkInfo);
                    nBlock++) {
                    line[0]='\r';
                    line[1]='\n';
                    makeBlkMapLine(&blkInfo,line+2);
                    WriteFile(hRptFile,line,lstrlen(line),&nBytes,NULL);
                    memStart=(CHAR*)memStart+blkInfo.blkSize;
               }
          }
#endif
          memStart=(CHAR*)rgnInfo.rgnAddress+rgnInfo.rgnSize;
     }
     *nStacks=st;
}

static VOID
printStack(                        // prints stack to exception report
HANDLE hRptFile,                   //   report file handle
ULONG *esp,                        //   current Esp register
ULONG *ebp,                        //   current Ebp register
ULONG **stackStart,                //   beginning of stack segent
ULONG **stackEnd,                  //   end of stack segent
ULONG nStacks,                     //   number of stacks
VOID *excpAddress)                 //   exception address
{
     CHAR line[256],litbuf[17],*pLine,*pEsp,*pLitbuf;
     CHAR funcNames[2][64];
     ULONG funcAddr[2];
     INT i,j,idxFunc;
     GBOOL fDone=FALSE,fGotBp;
     ULONG Func[MAXSTP];
     DWORD nbytes;
     ULONG st;
     GBOOL fOk;

     if (nStacks == 0) {
          return;
     }
     for (st=0,fOk=FALSE ; st < nStacks ; st++) {
          if (esp >= stackStart[st] && esp <= stackEnd[st]) {
               fOk=TRUE;
               break;
          }
     }
     if (!fOk) {
          return;
     }
     lstrcpy(line,"\r\nStack:");
     WriteFile(hRptFile,line,lstrlen(line),&nbytes,NULL);
     idxFunc=0;
     Func[idxFunc++]=(ULONG)excpAddress;// first entry is where it actually happened
     fGotBp=FALSE;
     while (!fDone) {
          wsprintf(line,"\r\n%08X    ",esp);
          for (i=0,pLine=line+12,pLitbuf=litbuf ; i < 4 ; i++,esp++) {
               if (esp >= stackEnd[st] || IsBadReadPtr(esp,4)) {
                    fDone=TRUE;
               }
               for (j=0,pEsp=(CHAR*)esp ; j < 4 ; j++,pEsp++,pLitbuf++) {
                    if (fDone) {
                         *pLitbuf='-';
                    }
                    else if (isprint(*pEsp)) {
                         *pLitbuf=*pEsp;
                    }
                    else {
                         *pLitbuf='.';
                    }
               }
               if (fDone) {
                    wsprintf(pLine,"--------  ");
                    pLine+=10;
               }
               else {
                    if (esp == ebp) {
                         *(pLine-1)='>';
                    }
                    wsprintf(pLine,"%08X",*esp);
                    pLine+=8;
                    if (fGotBp) {
                         Func[idxFunc++]=*esp;
                         fGotBp=FALSE;
                    }
                    if (esp == ebp) {
                         *pLine++='<';
                          ebp=(ULONG *)*esp;
                          fGotBp=TRUE;
                    }
                    else {
                         *pLine++=' ';
                    }
                    *pLine++=' ';
               }
          }
          *pLitbuf='\0';
          lstrcpy(pLine,litbuf);
          WriteFile(hRptFile,line,lstrlen(line),&nbytes,NULL);
     }
     lstrcpy(line,"\r\nRoutines:");
     WriteFile(hRptFile,line,lstrlen(line),&nbytes,NULL);
     for (i=0 ; i < idxFunc ; i++) {
          getFuncNames(Func[i],funcNames,funcAddr);
          wsprintf(line,"\r\n%08X %-20.20s  <  %08X  <  %08X %-20.20s",
                   funcAddr[0],funcNames[0],Func[i],funcAddr[1],funcNames[1]);
          WriteFile(hRptFile,line,lstrlen(line),&nbytes,NULL);
     }
     lstrcpy(line,"\r\n");
     WriteFile(hRptFile,line,lstrlen(line),&nbytes,NULL);
}

static VOID
printRptFooter(                    // print footer to exception report
HANDLE hRptFile)                   //   report file handle
{
     (VOID)hRptFile;
}

static VOID
makeRgnMapLine(                    // make region info line for mem map
RGNINFO *rgnInfo,                  //   region info
CHAR *line,                        //   buffer to fill in
INT linesize)                      //   buffer size
{
     INT len;
     CHAR modFileName[512];

     wsprintf(line,"%08X %s %10lu  ",rgnInfo->rgnAddress,
              memType2Text(rgnInfo->rgnType),rgnInfo->rgnSize);
#ifdef EXTENDED_MEMMAP
     if (rgnInfo->rgnType != MEM_FREE) {
          wsprintf(&line[lstrlen(line)],"%5u  %s     ",rgnInfo->rgnBlocks,
                  memProt2Text(rgnInfo->rgnProtection));
     }
#endif
     len=lstrlen(line);
     if (rgnInfo->rgnAddress == GetProcessHeap()) {
          lstrcat(line,"Default Process Heap");
     }
     else if (rgnInfo->rgnIsStack) {
          lstrcat(line,"Thread Stack");
     }
     if (rgnInfo->rgnAddress != NULL) {
          if (GetModuleFileName((HINSTANCE)rgnInfo->rgnAddress,modFileName,
                                 sizeof(modFileName)) > 0) {
               addModuleAttr(&line[len],linesize-len,modFileName);
               if (nMods < sizeof(pMod)/sizeof(pMod[0])) {
                    pMod[nMods++]=(DWORD)rgnInfo->rgnAddress;
               }
          }
     }
}

static VOID
addModuleAttr(                     // add module size,date,time to rgn info
CHAR *line,                        //   buffer to fill in
INT linesize,                      //   buffer size
CHAR *modFileName)
{
     INT len,tstlen;
     WIN32_FIND_DATA findData;
     FILETIME fileTime;
     SYSTEMTIME sysTime;
     CHAR *chkptr,*tstptr;
     HANDLE hFF;


     len=lstrlen(line);
     if ((linesize-len) < 260) {    // not enought room for file info
          return;
     }
     if ((hFF=FindFirstFile(modFileName,&findData)) != INVALID_HANDLE_VALUE) {
          FindClose(hFF);
          for (chkptr=curDirectory,tstptr=modFileName ;
                                                  *chkptr != '\0' ; ) {
               if (*chkptr != *tstptr) {
                    break;
               }
               chkptr++;
               tstptr++;
          }
          if (*chkptr == '\0') {   // the strings match
               tstlen=lstrlen(tstptr);
          }
          else {
               tstlen=lstrlen(modFileName);
               tstptr=modFileName;
          }
          if (tstlen > 22) {          // truncate the prefix and leave the end
               tstptr+=tstlen;
               tstptr-=22;
               *tstptr='.';
               *(tstptr+1)='.';
          }
          wsprintf(&line[len],"%-22s",tstptr);
          FileTimeToLocalFileTime(&findData.ftLastWriteTime,&fileTime);
          FileTimeToSystemTime(&fileTime,&sysTime);
          len=lstrlen(line);
          wsprintf(&line[len],"  %7lu  %2d/%02d/%02d %2d:%02d:%02d",
                   findData.nFileSizeLow,
                   sysTime.wMonth,sysTime.wDay,
                   sysTime.wYear-(sysTime.wYear > 1999 ? 2000 : 1900),
                   sysTime.wHour,sysTime.wMinute,sysTime.wSecond);
     }
     else {
          wsprintf(&line[len],"%s",modFileName);
          lstrcat(line,"  - Unable to find file");
     }
}

static DWORD
fixVirtualQuery (                  // compensate for bug in NT VirtualQuery
LPVOID lpvAddress,
PMEMORY_BASIC_INFORMATION pmbiBuffer,
DWORD cbLength)
{
     DWORD dwRetVal = VirtualQuery(lpvAddress,pmbiBuffer, cbLength);
     if (dwRetVal == cbLength) {
          if (((DWORD) pmbiBuffer->AllocationBase % 0x1000) == 0xFFF) {
               // If the AllocationBase ends with 0xFFF, it is 1 byte off.
               pmbiBuffer->AllocationBase=
                    (PVOID)((PBYTE) pmbiBuffer->AllocationBase + 1);
          }
          if ((pmbiBuffer->RegionSize % 0x1000) == 0xFFF) {
               // If the RegionSize ends with 0xFFF, the size is 1 byte off.
               pmbiBuffer->RegionSize++;
          }
          if (pmbiBuffer->State != MEM_FREE
           && pmbiBuffer->AllocationProtect == 0) {
               // If the the region is not free and the AllocationProtect is 0,
               // AllocationProtect should be PAGE_READONLY.
               pmbiBuffer->AllocationProtect = PAGE_READONLY;
          }
     }
     return(dwRetVal);
}

static GBOOL                       //   TRUE=success,FALSE=no more regions
getRgnInfo(                        // get info for next region
VOID *memStart,                    //   beginning of region
RGNINFO *rgnInfo)                  //   region info to fill in
{
     MEMORY_BASIC_INFORMATION memInfo;

     memset(rgnInfo,0,sizeof(*rgnInfo));
     if (fixVirtualQuery(memStart,&memInfo,sizeof(memInfo)) != sizeof(memInfo)) {
          return(FALSE);
     }
     if (memInfo.State == MEM_FREE) {
          rgnInfo->rgnAddress=memInfo.BaseAddress;
          rgnInfo->rgnProtection=memInfo.AllocationProtect;
          rgnInfo->rgnSize=memInfo.RegionSize;
          rgnInfo->rgnType=MEM_FREE;
          rgnInfo->rgnBlocks=0;
          rgnInfo->rgnGuardBlks=0;
          rgnInfo->rgnIsStack=FALSE;
          return(TRUE);
     }
     rgnInfo->rgnAddress=memInfo.AllocationBase;
     rgnInfo->rgnProtection=memInfo.AllocationProtect;
     walkRgn(rgnInfo);
     return(TRUE);
}

static VOID
walkRgn(                           // walk through region count blocks&size
RGNINFO *rgnInfo)                  //   region info to fill in
{
     MEMORY_BASIC_INFORMATION memInfo;
     VOID *memStart;
     DWORD blkProtAttr[4]={ 0 };

     memStart=rgnInfo->rgnAddress;
     rgnInfo->rgnType=MEM_PRIVATE;
     while (fixVirtualQuery(memStart,&memInfo,sizeof(memInfo)) == sizeof(memInfo)){
          if (memInfo.AllocationBase != rgnInfo->rgnAddress) {
               break;
          }
          blkProtAttr[rgnInfo->rgnBlocks%4]=
                    (memInfo.State == MEM_RESERVE) ? 0 : memInfo.Protect;
          rgnInfo->rgnBlocks++;
          rgnInfo->rgnSize+=memInfo.RegionSize;
          if (memInfo.Protect&PAGE_GUARD) {
               rgnInfo->rgnGuardBlks++;
          }
          if (rgnInfo->rgnType == MEM_PRIVATE) {
               rgnInfo->rgnType=memInfo.Type;
          }
          memStart=(CHAR *)memStart+memInfo.RegionSize;
     }
     if (rgnInfo->rgnGuardBlks > 0) {
          // NT stack has a guard page
          rgnInfo->rgnIsStack=TRUE;
     }
     else if (rgnInfo->rgnBlocks >= 4) {
          // Win95 has sequence reserved,PAGE_NOACCESS,PAGE_READWRITE,reserved
          // at the end of stack region
          if (blkProtAttr[(rgnInfo->rgnBlocks-1)%4] == 0
           && blkProtAttr[(rgnInfo->rgnBlocks-2)%4] == PAGE_READWRITE
           && blkProtAttr[(rgnInfo->rgnBlocks-3)%4] == PAGE_NOACCESS
           && blkProtAttr[(rgnInfo->rgnBlocks-4)%4] == 0) {
               rgnInfo->rgnIsStack=TRUE;
          }
     }
}

static CHAR *                      //   returns pointer to memory type stg
memType2Text(                      // converts memory type to a string
DWORD memType)                     //   memory type
{
     switch (memType) {
     case MEM_FREE:
          return("Free   ");
     case MEM_RESERVE:
          return("Reserve");
     case MEM_IMAGE:
          return("Image  ");
     case MEM_MAPPED:
          return("Mapped ");
     case MEM_PRIVATE:
          return("Private");
     default:
          return("Unknown");
     }
}

#ifdef EXTENDED_MEMMAP

static GBOOL                       //   TRUE-success,FALSE=no more blocks
getBlkInfo(                        // get block info
VOID *memStart,                    //   beginning address of block
BLKINFO *blkInfo)                  //   block info to fill in
{
     MEMORY_BASIC_INFORMATION memInfo;

     memset(blkInfo,0,sizeof(*blkInfo));
     if (fixVirtualQuery(memStart,&memInfo,sizeof(memInfo)) != sizeof(memInfo)) {
          return(FALSE);
     }
     blkInfo->blkAddress=memInfo.BaseAddress;
     blkInfo->blkSize=memInfo.RegionSize;
     if (memInfo.State == MEM_RESERVE) {
          blkInfo->blkProtection=memInfo.AllocationProtect;
          blkInfo->blkType=MEM_RESERVE;
     }
     else if (memInfo.State == MEM_COMMIT) {
          blkInfo->blkProtection=memInfo.Protect;
          blkInfo->blkType=memInfo.Type;
     }
     else {
          return(FALSE);
     }
     return(TRUE);
}

static VOID
makeBlkMapLine(                    // make block info line for mem map
BLKINFO *blkInfo,                  //   block info
CHAR *line)                        //   buffer to fill in
{
     wsprintf(line,"   %08X  %s  %10u         ",blkInfo->blkAddress,
              memType2Text(blkInfo->blkType),blkInfo->blkSize);
     if (blkInfo->blkType != MEM_FREE) {
          wsprintf(&line[lstrlen(line)],"%s  %s",
          memProt2Text(blkInfo->blkProtection),
          memFlags2Text(blkInfo->blkProtection));
     }
}

static CHAR *                      //   return ptr to mem protection stg
memProt2Text(                      // converts mem protection type to stg
DWORD memProtect)                  //   memory protection type
{
     if (memProtect&PAGE_READONLY) {
          return("-R--");
     }
     if (memProtect&PAGE_READWRITE) {
          return("-RW-");
     }
     if (memProtect&PAGE_WRITECOPY) {
          return("-RWC");
     }
     if (memProtect&PAGE_EXECUTE) {
          return("E---");
     }
     if (memProtect&PAGE_EXECUTE_READ) {
          return("ER--");
     }
     if (memProtect&PAGE_EXECUTE_READWRITE) {
          return("ERW-");
     }
     if (memProtect&PAGE_EXECUTE_WRITECOPY) {
          return("ERWC");
     }
     if (memProtect&PAGE_NOACCESS) {
          return("----");
     }
     return("Unknown");
}

static CHAR *                      //   return ptr to mem flags string
memFlags2Text(                     // converts mem flags to string
DWORD memFlags)                    //   memory protection flags
{
     if ((memFlags&(PAGE_GUARD|PAGE_NOCACHE)) == (PAGE_GUARD|PAGE_NOCACHE)) {
          return("GN");
     }
     if (memFlags&PAGE_GUARD) {
          return("G-");
     }
     if (memFlags&PAGE_NOCACHE) {
          return("-N");
     }
     return("--");
}
#endif // EXTENDED_MEMMAP

static VOID
displayExcpMsg(
CHAR *errorMessage)                 //   error message
{
     HANDLE hConOut;
     COORD coord;
     DWORD dummy;
     CHAR *text="Writing GALEXCEP.OUT";

     hConOut=CreateFile("CONOUT$",
                        GENERIC_READ|GENERIC_WRITE,
                        FILE_SHARE_READ|FILE_SHARE_WRITE,
                        NULL,
                        OPEN_EXISTING,
                        FILE_ATTRIBUTE_NORMAL,
                        NULL);
     if (hConOut !=  INVALID_HANDLE_VALUE) {
          coord.X=0;
          coord.Y=23;
          FillConsoleOutputAttribute(hConOut,
                                     BACKGROUND_BLUE|BACKGROUND_GREEN|BACKGROUND_RED,
                                     160,coord,&dummy);
          FillConsoleOutputCharacter(hConOut,' ',160,coord,&dummy);
          coord.X=0;
          coord.Y=23;
          WriteConsoleOutputCharacter(hConOut,errorMessage,
                                      lstrlen(errorMessage),coord,&dummy);
          coord.X=0;
          coord.Y=24;
          WriteConsoleOutputCharacter(hConOut,text,
                                      lstrlen(text),coord,&dummy);
          CloseHandle(hConOut);
     }
}

static VOID
loadGVars(                         // loads values of global vars from wgs/gsbl
struct tagGVars *gVars)            //   structure to fill in
{
     HMODULE hWGS;
     FARPROC pProc;

     gVars->fGVarsLoaded=FALSE;
     if ((hWGS=GetModuleHandle("wgserver.exe")) == NULL) {
          return;
     }
     if ((pProc=GetProcAddress(hWGS,"_input")) == NULL) {
          return;
     }
     gVars->_input=(CHAR *)pProc;
     if ((pProc=GetProcAddress(hWGS,"_status")) == NULL) {
          return;
     }
     gVars->_status=*((INT *)pProc);
     if ((pProc=GetProcAddress(hWGS,"_module")) == NULL) {
          return;
     }
     gVars->_module=*((struct module ***)pProc);
     if ((pProc=GetProcAddress(hWGS,"_nterms")) == NULL) {
          return;
     }
     gVars->_nterms=*((INT *)pProc);
     if ((pProc=GetProcAddress(hWGS,"_nmods")) == NULL) {
          return;
     }
     gVars->_nmods=*((INT *)pProc);
     if ((pProc=GetProcAddress(hWGS,"_channel")) == NULL) {
          return;
     }
     gVars->_channel=*((INT **)pProc);
     if ((pProc=GetProcAddress(hWGS,"_usrnum")) == NULL) {
          return;
     }
     gVars->_usrnum=*((INT *)pProc);
     if ((pProc=GetProcAddress(hWGS,"_bbsttl")) == NULL) {
          return;
     }
     gVars->_bbsttl=*((CHAR **)pProc);
     if ((pProc=GetProcAddress(hWGS,"_usaptr")) == NULL) {
          return;
     }
     gVars->_usaptr=*((struct usracc **)pProc);
     if ((pProc=GetProcAddress(hWGS,"_usrptr")) == NULL) {
          return;
     }
     gVars->_usrptr=*((struct user **)pProc);
     if ((pProc=GetProcAddress(hWGS,"_version")) == NULL) {
          return;
     }
     gVars->_version=(CHAR*)pProc;
     if ((hWGS=GetModuleHandle("galgsbl.dll")) == NULL) {
          return;
     }
     if ((pProc=GetProcAddress(hWGS,"_bturno")) == NULL) {
          return;
     }
     gVars->_bturno=(CHAR *)pProc;
     gVars->fGVarsLoaded=TRUE;
}

static GBOOL                       //   returns TRUE if pointer is valid
isGoodPointer(                     // is passed pointer valid?
VOID *ptr,                         //   pointer to test
INT byteCount)                     //   number of bytes to verify for
{
     if (ptr == NULL) {
          return(FALSE);
     }
     if (IsBadReadPtr(ptr,byteCount) || IsBadWritePtr(ptr,byteCount)) {
          return(FALSE);
     }
     return(TRUE);
}

static UINT                        //   return # of accessable bytes
isGoodBlock(                       // is blk of memory addressable w/o GP?
VOID *ptr,                         //   pointer to block of bytes
UINT nbytes)                       //   number of bytes to check
{
     if (ptr == NULL || IsBadReadPtr(ptr,nbytes) || IsBadWritePtr(ptr,nbytes)) {
          return(0);
     }
     return(nbytes);
}

static GBOOL                       //   return TRUE if stg pointer is valid
isGoodString(                      // validate string pointer
CHAR *ptr)                         //   pointer to validate
{
     if (IsBadStringPtr(ptr,0xFFFF)) {
          return(FALSE);
     }
     return(TRUE);
}

static VOID
copyErrorMessage(                  // copy error message converts \n to \r\n
CHAR *msgDest,                     //   destination buffer
const CHAR *msgSrc,                //   source buffer
INT msgDestSize)                   //   destination buffer size
{
     INT i;

     msgDestSize-=2;
     for (i=0 ; i < msgDestSize && *msgSrc != 0 ; i++,msgSrc++) {
          if (*msgSrc == '\n') {
               msgDest[i++]='\r';
          }
          msgDest[i]=*msgSrc;
     }
     msgDest[i]='\0';
}

static VOID
getFuncNames(                      // get closest exported functoin names
ULONG pFunc,                       //   function address
CHAR funcNames[][64],              //   exported function names to return
ULONG funcAddr[])                  //   exported function addresses to return
{
     INT mod;
     INT i;
     INT idxBefore,idxAfter;
     ULONG diff,diffBefore,diffAfter;
     CHAR *pNameBefore,*pNameAfter;
     ULONG *functions;
     USHORT *ordinals;
     CHAR **names;
     INT nFunc;
     INT nNames;

     lstrcpyn(funcNames[0],"Unknown",sizeof(funcNames[0]));
     funcAddr[0]=0;
     lstrcpyn(funcNames[1],"Unknown",sizeof(funcNames[1]));
     funcAddr[1]=0;
     if (nMods == 0) {
          return;
     }
     for (mod=0 ; mod < nMods ; mod++) {
          if (pMod[mod] > pFunc) {
               if (mod == 0) {     // should not happen
                    return;
               }
               break;
          }
     }
     mod--;
     if (!loadExportLists(pMod[mod],&functions,&ordinals,&names,&nFunc,&nNames)) {
          return;
     }
     for (i=0,diffBefore=diffAfter=0xFFFFFFFF ; i < nFunc ; i++) {
          if (functions[i] != 0) { // skip over gaps
               if (pFunc >= (functions[i]+pMod[mod])) {
                    diff=pFunc-(functions[i]+pMod[mod]);
                    if (diff < diffBefore) {
                         diffBefore=diff;
                         idxBefore=i;
                    }
               }
               else {
                    diff=(functions[i]+pMod[mod])-pFunc;
                    if (diff < diffAfter) {
                         diffAfter=diff;
                         idxAfter=i;
                    }
               }
          }
     }
     pNameBefore=pNameAfter=NULL;
     if (diffBefore != 0xFFFFFFFF) {
          for (i=0 ; i < nNames ; i++) {
               if (ordinals[i] == idxBefore) {
                    pNameBefore=names[i]+pMod[mod];
                    break;
               }
          }
     }
     if (diffAfter != 0xFFFFFFFF) {
          for (i=0 ; i < nNames ; i++) {
               if (ordinals[i] == idxAfter) {
                    pNameAfter=names[i]+pMod[mod];
                    break;
               }
          }
     }
     if (pNameBefore != NULL) {
          lstrcpyn(funcNames[0],pNameBefore,sizeof(funcNames[0]));
          funcAddr[0]=functions[idxBefore]+pMod[mod];
     }
     if (pNameAfter != NULL) {
          lstrcpyn(funcNames[1],pNameAfter,sizeof(funcNames[1]));
          funcAddr[1]=functions[idxAfter]+pMod[mod];
     }
}

static GBOOL
loadExportLists(                   // loads exported section of module
ULONG baseAddress,                 //   module base address
ULONG **functions,                 //   function's addresses list
USHORT **ordinals,                 //   ordinals list
CHAR ***names,                     //   names list
INT *nFunc,                        //   number of function's addresses
INT *nNames)                       //   number of function's names
{
     PIMAGE_NT_HEADERS pNTHeader;
     PIMAGE_DOS_HEADER pDOSHeader;
     PIMAGE_EXPORT_DIRECTORY pExportDir;
     DWORD exportsStartRVA;

     pDOSHeader=(PIMAGE_DOS_HEADER)baseAddress;
     pNTHeader=makePtr(PIMAGE_NT_HEADERS,pDOSHeader,pDOSHeader->e_lfanew);
     // First, verify that the e_lfanew field gave us a reasonable
     // pointer, then verify the PE signature.
     __try {
          if (pNTHeader->Signature != IMAGE_NT_SIGNATURE) {
               return(FALSE);
          }
     }
     __except( TRUE ) {   // Should only get here if pNTHeader is bogus
          return(FALSE);
     }
     exportsStartRVA=
      pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
     pExportDir=makePtr(PIMAGE_EXPORT_DIRECTORY,baseAddress,exportsStartRVA);
     *functions=(PDWORD)((DWORD)pExportDir->AddressOfFunctions+baseAddress);
     *ordinals=(PWORD)((DWORD)pExportDir->AddressOfNameOrdinals+baseAddress);
     *names=(CHAR **)((DWORD)pExportDir->AddressOfNames+baseAddress);
     *nFunc=pExportDir->NumberOfFunctions;
     *nNames=pExportDir->NumberOfNames;
     return(TRUE);
}
