/**********************************************************************
 *
 * PROGRAM: HLPDUMP2.C
 *
 * PURPOSE: A dump program that lets you view internal WinHelp files
 *
 * Copyright 1997, Mike Wallace and Pete Davis
 *
 * Chapter 4, Windows Help File Format, from Undocumented Windows
 * File Formats, published by R&D Books, an imprint of Miller Freeman, Inc.
 *
 **********************************************************************/

#define MEM_DEBUG 1

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#include <ctype.h>
#include <limits.h>
#include <string.h>
#include "winhelp.h"
#include "hlpdump2.h"

#define HLP_DEBUG 1

#define CHECK_SIGNATURE(is, shouldbe) \
         {if (is != shouldbe) { \
            printf("Signature should be %x, but is %x\n"); \
            return;} }
             

/* Tells us if a particular bit is set or not */
#define BITSET(bitmap, bit) \
        ((bitmap & (1 << bit)) ? 1 : 0 )

/* Sum of set bits in a byte + 8 */
#define BYTESTOREAD(bitmap) \
        ( BITSET(bitmap, 0) + BITSET(bitmap, 1) + \
          BITSET(bitmap, 2) + BITSET(bitmap, 3) + \
          BITSET(bitmap, 4) + BITSET(bitmap, 5) + \
          BITSET(bitmap, 6) + BITSET(bitmap, 7) + 8 )


/***************************************************
  Loads the HFSFileHeader
****************************************************/

long LoadHeader(FILE *HelpFile) 
{
  HFSFILEHEADER fileHeader;
  
  fread(&fileHeader, sizeof(HFSFILEHEADER), 1, HelpFile);
  
#ifdef HLP_DEBUG
  printf("DEBUG -> LoadHeader()\n");
  printf("File plus Header: %ld\n", fileHeader.FilePlusHeader);
  printf("File size: %ld\n", fileHeader.FileSize);
  printf("File type: 0x%02x\n\n", fileHeader.FileType);
#endif
  
  return fileHeader.FileSize;
}


/*************************************************
  Decompresses the data using Microsoft's LZ77
derivative (called Zeck Compression)
**************************************************/ 

long Decompress(FILE *HelpFile, long CompSize, char *Buffer) {

long InBytes = 0;        /* How many bytes read in                    */
long OutBytes = 0;       /* How many bytes written out                */
BYTE BitMap, Set[16];    /* Bitmap and bytes associated with it       */
long NumToRead;          /* Number of bytes to read for next group    */
long counter, Index;     /* Going through next 8-16 codes or chars    */
long Length, Distance;   /* Code length and distance back in 'window' */
char *CurrPos;           /* Where we are at any given moment          */
char *CodePtr;           /* Pointer to back-up in LZ77 'window'       */


  CurrPos = Buffer;

  /* Go through until we're done */
  while (InBytes < CompSize) {

    /* Get BitMap and data following it */
    BitMap = (BYTE) fgetc(HelpFile);
    NumToRead = BYTESTOREAD(BitMap);

    /* If we're trying to read more than we've 
       got left, only read what we have left.  */
    NumToRead = (CompSize - InBytes) < NumToRead ? 
                                    CompSize-InBytes : NumToRead;

    fread(Set, 1, (int) NumToRead, HelpFile);    
    InBytes += NumToRead + 1;

    /* Go through and decode data */
    for (counter = 0, Index = 0; counter < 8; counter++) {

      /* It's a code, so decode it and copy the data */
      if (BITSET(BitMap, counter)) {
        Length = ((Set[Index+1] & 0xF0) >> 4) + 3;
        Distance = ((Set[Index+1] & 0x0F) << 8) + Set[Index] + 1;
        CodePtr = CurrPos - Distance;

        /* Copy data from 'window' */
        while (Length) {
          *CurrPos++ = *CodePtr++;
          OutBytes++;
          Length--;
        } 
        Index += 2;
      } 
      else {
        *CurrPos++ = Set[Index++];
        OutBytes++;
      }
    } /* for */
  } /* while  */
  return OutBytes;
} 


/***************************************************
  Performs a Hex/ASCII dump of an HFS file.
****************************************************/

void HexDumpData(FILE *HelpFile, long fileSize) 
{
  char           Buffer[16];
  long           counter;
  long           BytesToPrint, Index;

  printf("Offset                   Hex Values                           Ascii\n");
  printf("-------------------------------------------------------------------------\n");

  for (counter = 0; counter < fileSize; counter+=16) {

    printf("0x%08lX: ", counter);

    /* If this is the last line, how many bytes are in it? */
    BytesToPrint = ((fileSize - counter) > 16) ? 16 : (fileSize - counter);
    fread(Buffer, BytesToPrint, 1, HelpFile);

    /* Dump Hex */
    for (Index=0; Index < BytesToPrint; Index++)
      printf("%02X ", (BYTE) Buffer[Index]);

    /* If last line, fill in blanks */
    for (Index=0; Index < 16-BytesToPrint; Index++) 
      printf("   ");

    /* Dump Ascii */
    for (Index=0; Index < BytesToPrint; Index++) 
      putchar( isprint( Buffer[Index] ) ? Buffer[Index] : '.' );

    putchar('\n');
  }

  free(Buffer);
}


/***************************************************
  Performs a Hex/ASCII dump of an HFS file.
****************************************************/

void HexDumpFile(FILE *HelpFile, long FileStart) 
{
  long           fileSize;

  fseek(HelpFile, FileStart, SEEK_SET);

  fileSize = LoadHeader(HelpFile);  
  
  printf("File Size: 0x%08lX\n\n", fileSize);
  
  HexDumpData(HelpFile, fileSize);
}

/***************************************************
  Dumps the |SYSTEM info
****************************************************/

void SystemDump(FILE *HelpFile, long FileStart) 
{

  char            HelpFileTitle[33];
  SYSTEMREC       SystemRec;
  long            CurrentLocation;
  struct tm       *TimeRec;
  SECWINDOW       *SWin;     /* Secondary Window record */
  long            fileSize;
  SYSTEMHEADER    SysHeader;

  fseek(HelpFile, FileStart, SEEK_SET);

  fileSize = LoadHeader(HelpFile);

  fread(&SysHeader, sizeof(SysHeader), 1, HelpFile);
  printf("|SYSTEM Dump\n\n\n");

  /* Figure out Version and Revision */
  if (SysHeader.Revision == 0x15) printf("HC.EXE  3.10 Help Compiler used\n");
  else if (SysHeader.Revision == 0x21) printf("HCW.EXE. 4.00 or MVC.EXE\n");

  printf("\nVersion: %d\nRevision: %d\n", SysHeader.Version, SysHeader.Revision);
  printf("Flag: 0x%04X  - ",SysHeader.Flags);

  /* Determine compression, if any. */
  if (SysHeader.Flags == NO_COMPRESSION) printf("No compression\n");
  else if (SysHeader.Flags & COMPRESSION_HIGH) printf("High Compression\n");
  else printf("Unknown Compression: 0x%02x\n", SysHeader.Flags);

  TimeRec=localtime(&SysHeader.GenDate);
  printf("Help File Generated: %s", asctime(TimeRec));

  /* If 3.0 get title */
  CurrentLocation=12;
  if (SysHeader.Revision == 0x0F) {
     fgets(HelpFileTitle, 33, HelpFile);
     printf("Help File Title: %s\n", HelpFileTitle);
  }

  /* Else, get 3.1 System records */
  else {
    while (CurrentLocation < fileSize) {

      /* Read in system record and SystemRec data */
      fread(&SystemRec, 4, 1, HelpFile);
      SystemRec.RData = malloc(SystemRec.DataSize);
      if (SystemRec.RData == NULL)
      {
        printf("Allocation of SystemRec.RData failed.");
        return;
      }
      fread(SystemRec.RData, SystemRec.DataSize, 1, HelpFile);
      CurrentLocation=CurrentLocation+4+SystemRec.DataSize;

      switch(SystemRec.RecordType) 
      {
        case 0x0001:  printf("Help File Title: %s\n", SystemRec.RData);
          break;

        case 0x0002:  printf("Copyright Notice: %s\n", SystemRec.RData);
          break;

        case 0x0003:  printf("Contents ID: 0x%04X\n", (long) *SystemRec.RData);
          break;

        case 0x0004:  printf("Macro Data: %s\n",SystemRec.RData);
          break;

        case 0x0005:  printf("Icon in System record\n");
          break;

        case 0x0006:  printf("\nSecondary window:\n");
                      SWin = (SECWINDOW *)SystemRec.RData;
                      printf("Flag: %d\n", SWin->Flags);
                      if (SWin->Flags & WSYSFLAG_TYPE) 
                        printf("Type: %s\n", SWin->Type);
                      if (SWin->Flags & WSYSFLAG_NAME) 
                        printf("Name: %s\n", SWin->Name);
                      if (SWin->Flags & WSYSFLAG_CAPTION) 
                        printf("Caption: %s\n", SWin->Caption);
                      if (SWin->Flags & WSYSFLAG_X) 
                        printf("X: %d\n", SWin->X);
                      if (SWin->Flags & WSYSFLAG_Y) 
                        printf("Y: %d\n", SWin->Y);
                      if (SWin->Flags & WSYSFLAG_WIDTH)
                        printf("Width: %d\n", SWin->Width);
                      if (SWin->Flags & WSYSFLAG_HEIGHT)
                        printf("Height: %d\n", SWin->Height);
                      if (SWin->Flags & WSYSFLAG_MAXIMIZE) 
                        printf("Maximize Flag: %d\n", SWin->Maximize);
                      if (SWin->Flags & WSYSFLAG_RGB) 
                        printf("RGB Foreground Colors Set\n");
                      if (SWin->Flags & WSYSFLAG_RGBNSR)
                        printf("RGB For Non-Scrollable Region Set\n");
                      if (SWin->Flags & WSYSFLAG_TOP) 
                        printf("Secondary Window is always On Top\n");
          break;

        case 0x0008:  printf("Citation: %s\n", SystemRec.RData);
          break;

        case 0x000A:  printf("\nContents File: %s\n\n",SystemRec.RData);
          break;                                                 
          
        default:      printf("\nUnknown record type: 0x%04X\n",SystemRec.RecordType);
                      /* Back-up and hex-dump the data */
                      fseek(HelpFile, 
                            ftell(HelpFile) - SystemRec.DataSize, 
                            SEEK_SET);
                      HexDumpData(HelpFile, SystemRec.DataSize);
      } /* switch */
     
      free(SystemRec.RData);

    } /* while */
  } /* else */
} /* SysDump */


/***************************************************
  Dumps the |FONT file
****************************************************/

void FontDump(FILE *HelpFile, long FileStart) 
{
  FONTHEADER      FontHdr;
  FONTDESCRIPTOR  FontDesc;
  char            AFont[32];
  long            FontStart, CurrLoc;
  long            fileSize;
  long            counter;
  long            NameLen;

  /* Go to the FONT file and get the headers */
  fseek(HelpFile, FileStart, SEEK_SET);
  fileSize = LoadHeader(HelpFile);

  fread(&FontHdr, sizeof(FontHdr), 1, HelpFile);

  printf("|FONTS\n\n Number Fonts: %d\n",FontHdr.NumFonts);
  printf("Font #  -  Font Name\n");


  /* Font names are 20 chars prior to Winhelp 4.0 */
  /* In WinHelp 4.0, they are 32 characters.      */
  if (SysInfo.Revision == 0x15)
  {
    NameLen = 20;
  }
  else
  {
    NameLen = 32;
  }

  /* Keep track of start of fonts */
  FontStart = ftell(HelpFile);
  for (counter = 0; counter < (long) FontHdr.NumFonts; counter++) 
  {
    fread(AFont, NameLen, 1, HelpFile);
    printf(" %3d    -  %s\n", counter, AFont);
  }

  /* Go to Font Descriptors. Don't actually need this, because we're
     there, but wanted to show how to get there using the offset.    */
  fseek(HelpFile, FontStart + (long)(FontHdr.DescriptorsOffset) - sizeof(FontHdr), SEEK_SET); 
  printf("\nNum Font Descriptors: %d\n", FontHdr.NumDescriptors);
  printf("Default Descriptor: %d\n\n", FontHdr.DefDescriptor);
  printf("Attributes: n=none  b=bold  i=ital  u=undr  s=strkout  d=dblundr  C=smallcaps\n\n");
  printf("Font Name                        PointSize  Family   FG RGB      BG RGB   Attr\n");
  printf("------------------------------------------------------------------------------\n");

  for (counter = 0; counter < (long) FontHdr.NumDescriptors; counter++) {
    fread(&FontDesc, sizeof(FontDesc), 1, HelpFile);
    CurrLoc = ftell(HelpFile);
    fseek(HelpFile, FontStart + (NameLen * FontDesc.FontName), SEEK_SET);
    fread(AFont, NameLen, 1, HelpFile);
    fseek(HelpFile, CurrLoc, SEEK_SET);
      
    /* write out info on Font descriptor */
    printf("%-32s    %4.1f    ", AFont, (float)(FontDesc.HalfPoints / 2));
    switch (FontDesc.FontFamily) {
      case FAM_MODERN: printf("Modern");
                          break;

      case FAM_ROMAN:  printf("Roman ");
                       break;

      case FAM_SWISS:  printf("Swiss ");
                       break;

      case FAM_SCRIPT: printf("Script");
                       break;

      case FAM_DECOR:  printf("Decor ");
                       break;

      default:         printf("0X%02X ", FontDesc.FontFamily);
                       break;
    } /* Switch */
    printf(" 0X%08lX  ",RGB(FontDesc.SRRGB.rgbRed,
                            FontDesc.SRRGB.rgbGreen,
                            FontDesc.SRRGB.rgbBlue));
    printf("0X%08lX    ",RGB(FontDesc.NSRRGB.rgbRed,
                             FontDesc.NSRRGB.rgbGreen,
                             FontDesc.NSRRGB.rgbBlue));

    if (FontDesc.Attributes == 0) putchar('n');
    if (FontDesc.Attributes & FONT_BOLD) putchar('b');
    if (FontDesc.Attributes & FONT_ITAL) putchar('i');
    if (FontDesc.Attributes & FONT_UNDR) putchar('u');
    if (FontDesc.Attributes & FONT_STRK) putchar('s');
    if (FontDesc.Attributes & FONT_DBUN) putchar('d');
    if (FontDesc.Attributes & FONT_SMCP) putchar('C');
    printf("\n");

#ifdef HLP_DEBUG
    printf("Unknown = %d\n", FontDesc.Unknown);
#endif
  }
}


/***************************************************
  Dumps the |CONTEXT file
****************************************************/

void ContextDump(FILE *HelpFile, long FileStart) 
{
  long            count;
  long            CurrPage, FirstPageLoc;
  long            TopicOffset, HashValue;
  BTREEHEADER     BTreeHdr;
  BTREELEAFHEADER CurrNode;

  /* Go to the TTLBTREE file and get the headers */
  fseek(HelpFile, FileStart, SEEK_SET);
  LoadHeader(HelpFile);
  fread(&BTreeHdr, sizeof(BTreeHdr), 1, HelpFile);

  /* Save the current location */
  FirstPageLoc = ftell(HelpFile);
   
  fseek(HelpFile, 
        FirstPageLoc+(BTreeHdr.RootPage * BTreeHdr.PageSize), 
        SEEK_SET);

  printf("# Context Hash Values in |CONTEXT %lu\n\n", BTreeHdr.TotalBtreeEntries);
  CurrPage = BTreeHdr.FirstLeaf;

  do 
  {
    fseek(HelpFile, 
          FirstPageLoc+(CurrPage * BTreeHdr.PageSize), 
          SEEK_SET);
    fread(&CurrNode, 8, 1, HelpFile);
    for(count = 1; count <= CurrNode.NEntries; count++) 
    {
      fread(&HashValue, sizeof(HashValue), 1, HelpFile);
      fread(&TopicOffset, sizeof(TopicOffset), 1, HelpFile);
      printf("Topic Offset:0x%08lX  Hash Value: 0x%08lX\n", TopicOffset, HashValue);
    }
    CurrPage = CurrNode.NextPage;
  } while(CurrPage != -1);
}


/***************************************************
  Prints a Phrase from the Phrase table.
****************************************************/
void PrintPhrase(long PhraseNum) 
{
  short *Offsets;
  char  *p;

  p = Phrases+Offsets[PhraseNum];
  while (p < Phrases + Offsets[PhraseNum + 1])
    putchar(*p++);
}


/***************************************************
  Dumps the Keyword B-Tree
****************************************************/

void KWBTreeDump(FILE *HelpFile, long FileStart) 
{
  char            Keyword[80], c;
  long            count, Index;
  long            CurrPage, FirstPageLoc;
  long            KeywordOffset;
  long            KeywordCount;
  BTREEHEADER     BTreeHdr;
  BTREELEAFHEADER CurrNode;

  /* Go to the KWBTREE file and get the headers */
  fseek(HelpFile, FileStart, SEEK_SET);
  LoadHeader(HelpFile);
  fread(&BTreeHdr, sizeof(BTreeHdr), 1, HelpFile);

  /* Save the current location */
  FirstPageLoc = ftell(HelpFile);
   
  fseek(HelpFile, 
        FirstPageLoc+(BTreeHdr.RootPage * BTreeHdr.PageSize), 
        SEEK_SET);

  printf("# Keywords - %lu\n\n", BTreeHdr.TotalBtreeEntries);
  CurrPage = BTreeHdr.FirstLeaf;

  do 
  {
    fseek(HelpFile, 
          FirstPageLoc+(CurrPage * BTreeHdr.PageSize), 
          SEEK_SET);
    fread(&CurrNode, 8, 1, HelpFile);
    for(count = 1; count <= CurrNode.NEntries; count++) 
    {
      Index = 0;
      while(c = (char) fgetc(HelpFile))
        Keyword[Index++] = c;
        
      Keyword[Index] = 0;
      fread(&KeywordCount, sizeof(KeywordCount), 1, HelpFile);
      fread(&KeywordOffset, sizeof(KeywordOffset), 1, HelpFile);

      printf("Offset: 0x%08lX  Count: %d  Keyword: %s\n", 
                          KeywordOffset, KeywordCount, Keyword);
    }
    CurrPage = CurrNode.NextPage;
  } while(CurrPage != -1);
}


/***************************************************
  Dumps the Keyword Data file
****************************************************/

void KWDataDump(FILE *HelpFile, long FileStart) 
{
  long fileSize;
  long nIndex;
  long Offset;
  
  /* Go to the KWDATA file and get the headers */
  fseek(HelpFile, FileStart, SEEK_SET);
  fileSize = LoadHeader(HelpFile);
          
  printf("Dumping Keyword Data File\n\n");
  /* Go through all keyword offsets (fileSize / 4) */
  for (nIndex = 0; nIndex < (fileSize / sizeof(Offset)); nIndex++)
  {
    fread(&Offset, sizeof(Offset), 1, HelpFile);
    printf("Index: %d   Offset: 0x%08lX\n", nIndex, Offset);
  }
}


/***************************************************
  Dumps the Keyword Map file
****************************************************/

void KWMapDump(FILE *HelpFile, long FileStart) 
{
  long      fileSize;
  long      nIndex;
  WORD      nKWMaps;
  KWMAPREC  kwMap;
  
  /* Go to the KWMAP file and get the headers */
  fseek(HelpFile, FileStart, SEEK_SET);
  fileSize = LoadHeader(HelpFile);
          
  printf("Dumping Keyword Map\n\n");
  
  fread(&nKWMaps, sizeof(nKWMaps), 1, HelpFile);
  
  /* Go through all keyword offsets (fileSize / 4) */
  for (nIndex = 0; nIndex < (int) nKWMaps; nIndex++)
  {
    fread(&kwMap, sizeof(KWMAPREC), 1, HelpFile);
    printf("Index: %d   First Keyword 0x%08lX    Leaf Page#: %05u\n", 
                          nIndex, kwMap.FirstRec, kwMap.PageNum);
  }
}


/***************************************************
  Dumps the Topic Titles B-Tree
****************************************************/

void TTLDump(FILE *HelpFile, long FileStart) 
{
  char            Title[80], c;
  long            count, Index;
  long            CurrPage, FirstPageLoc, TopicOffset;
  BTREEHEADER     BTreeHdr;
  BTREELEAFHEADER CurrNode;

  /* Go to the TTLBTREE file and get the headers */
  fseek(HelpFile, FileStart, SEEK_SET);
  LoadHeader(HelpFile);
  fread(&BTreeHdr, sizeof(BTreeHdr), 1, HelpFile);

  /* Save the current location */
  FirstPageLoc = ftell(HelpFile);
   
  fseek(HelpFile, 
        FirstPageLoc+(BTreeHdr.RootPage * BTreeHdr.PageSize), 
        SEEK_SET);

  printf("# Titles in |TTLBTREE %lu\n\n", BTreeHdr.TotalBtreeEntries);
  CurrPage = BTreeHdr.FirstLeaf;

  do 
  {
    fseek(HelpFile, 
          FirstPageLoc+(CurrPage * BTreeHdr.PageSize), 
          SEEK_SET);
    fread(&CurrNode, 8, 1, HelpFile);
    for(count = 1; count <= CurrNode.NEntries; count++) 
    {
      fread(&TopicOffset, sizeof(TopicOffset), 1, HelpFile);
      Index = 0;
      while(c = (char) fgetc(HelpFile))
        Title[Index++] = c;
        
      Title[Index] = 0;

      printf("Topic Offset:0x%08lX  Title: %s\n", TopicOffset, Title);
    }
    CurrPage = CurrNode.NextPage;

  } while(CurrPage != -1);
}


/***************************************************
  Dumps the |Phrases file
****************************************************/

void PhrasesDump(FILE *HelpFile, long FileStart) 
{
  long  nOuterIndex, nInnerIndex;
  WORD  start, len;
  
  PhrasesLoad(HelpFile, FileStart);

  printf("Phrase#     Phrase\n");
  for (nOuterIndex = 0; nOuterIndex < NumPhrases; nOuterIndex++)
  {
    start = (WORD) PhrOffsets[nOuterIndex] ;
    len = PhrOffsets[nOuterIndex + 1] - start;
    printf(" %5d     ", nOuterIndex + 1);
    for (nInnerIndex = 0; nInnerIndex < (int) len; nInnerIndex++)
    {
      printf("%c", (Phrases[start + nInnerIndex]));
    }
    printf("\n");
  }
}


/***************************************************
  Loads the |SYSTEM info
****************************************************/

int SysLoad(FILE *HelpFile, long FileStart) 
{
  fseek(HelpFile, FileStart, SEEK_SET);

  LoadHeader(HelpFile);
  
  fread(&SysInfo, sizeof(SYSTEMHEADER), 1, HelpFile);
  
#ifdef HLP_DEBUG
  printf("DEBUG -> SysLoad()\n");
  printf("Magic: 0x%02x\n", SysInfo.Magic);
  printf("Version: 0x%02x\n", SysInfo.Version);
  printf("Revision: 0x%02x\n", SysInfo.Revision);
  printf("Flags: 0x%04x\n\n", SysInfo.Flags);
#endif

  if (SysInfo.Magic != SYS_MAGIC)
  {
    return 0;
  }
  
  return 1;
}


/***************************************************
  Loads the compression phrases
****************************************************/

void PhrasesLoad(FILE *HelpFile, long FileStart) 
{
  PHRASEHEADER  phraseHeader;
  long          FileSize;
  long          DeCompSize;
  
  fseek(HelpFile, FileStart, SEEK_SET);
  FileSize = LoadHeader(HelpFile);

  fread(&phraseHeader, sizeof(phraseHeader), 1, HelpFile);
  
  if (SysInfo.Flags != NO_COMPRESSION)
  {
    if ((PhrOffsets = malloc(phraseHeader.PhrasesSize + 
                            (phraseHeader.NumPhrases + 1) * 2 + 10)) == NULL)
    {
      printf("Unable to allocate space for Phrases.\n");
      return;
    }

    /* Assign Phrases to  where the comrpessed phrases are */   
    Phrases = (char*) PhrOffsets + fread(PhrOffsets, 
                                         2, 
                                         phraseHeader.NumPhrases + 1, 
                                         HelpFile);

    DeCompSize = Decompress(HelpFile,
                            FileSize - (sizeof(phraseHeader) +
                            2 * (phraseHeader.NumPhrases + 1)),
                            Phrases);
    if (DeCompSize != (long) phraseHeader.PhrasesSize)
    {
      printf("Warning, Phrases did not decompress to the proper size.\n");
    }
  }
  else
  {
    if ((PhrOffsets = malloc(phraseHeader.PhrasesSize + 
                            (phraseHeader.NumPhrases + 1) * 2 + 10)) == NULL)
    {
      printf("Unable to allocate space for Phrases.\n");
      return;
    }
    /* Back up four bytes if phrases aren't compressed */
    /* because PhrasesSize field doesn't exist.        */
    fseek(HelpFile, -4, SEEK_CUR);
    fread(PhrOffsets, FileSize - 4, 1, HelpFile);
  }
  /* Reset Phrases to be equal to PhrOffsets */
  Phrases = (char *) PhrOffsets;  
  NumPhrases = phraseHeader.NumPhrases;
}


/***************************************************
  Finds an HFS File by traversing the HFS b-tree
  Note: This is the only place I actually traverse
  the b-tree instead of cycling through the leaf
  pages. I put this in specifically to show how to
  traverse the b-tree, since speed isn't a real
  concern for HelpDump.
****************************************************/

char FindFile(FILE *HelpFile, char* filename, long* offset)
{
  BTREEHEADER       HFSHeader;
  BTREEINDEXHEADER* HFSIndexHeader;
  BTREELEAFHEADER*  HFSLeafHeader;
  long              HFSStart;
  short*            pNextPage;
  char*             buffer;
  char*             currPtr;
  long              nKeys, nFiles;
  char              found = 0;
  long              currLevel = 1;

  /* Go to the HFS and read the header. */
  fseek(HelpFile, HelpHeader.HFSLoc, SEEK_SET);
  LoadHeader(HelpFile);
  fread(&HFSHeader, sizeof(HFSHeader), 1, HelpFile);

  /* Allocate space for read buffer */
  buffer = malloc(HFSHeader.PageSize);
  if (buffer == NULL)
  {
    printf("Unable to allocate space for buffer.\n");
    return found;
  }
  HFSIndexHeader = (BTREEINDEXHEADER*) buffer;

  HFSStart = ftell(HelpFile);
  
  /* Advance to root page */
  fseek(HelpFile, 
        (HFSHeader.RootPage * HFSHeader.PageSize) + HFSStart, 
        SEEK_SET);
      
  /* If there's only one page, then it must be a leaf */
  if (HFSHeader.TotalPages > 1)
  {
    /* Traverse b-tree looking for the key for the leaf page */  
    while (!found)
    {
      /* Read in the page */
      fread(buffer, HFSHeader.PageSize, 1, HelpFile);
      currPtr = buffer + sizeof(BTREEINDEXHEADER);
      pNextPage = (int *) currPtr;

      currPtr += sizeof(int);
      
      /* Go through all keys in the page */
      for (nKeys = 0; nKeys < HFSIndexHeader->NEntries; nKeys++)
      {
        /* If filename is less than key, this is our page. */
        if (strcmp(filename, currPtr) < 0)
        {
          break;
        }
        else
        {
          /* Advance to the next page# */
          while (*currPtr)
            currPtr++;
          currPtr++;
          pNextPage = (int *) currPtr;
          currPtr += sizeof(int);
        }
      }
      
      /* Advance to next page */
      fseek(HelpFile, 
            (*pNextPage * HFSHeader.PageSize) + HFSStart, 
            SEEK_SET);
      
      /* If this is the last index page */
      /* then pNextPage points to a     */
      /* leaf page                      */
      if (currLevel == HFSHeader.nLevels - 1)
      {
        found = 1;
      }
      currLevel++;
    }
  }
  
  fread(buffer, HFSHeader.PageSize, 1, HelpFile);
  HFSLeafHeader = (BTREELEAFHEADER*) buffer;
  currPtr = buffer + sizeof(BTREELEAFHEADER);


  found = 0;
  
  /* Loop through all files in this page */
  for (nFiles = 0; nFiles < HFSLeafHeader->NEntries; nFiles++)
  {
    if (strcmp(filename, currPtr))
    {  
      /* Advance to the file offset */
      while (*currPtr)
        currPtr++;
      
      /* Move past the null and file offset */
      /* to next file                       */
      currPtr += 5;    
    }
    else
    {
      /* Save the offset of the file */
      while (*currPtr)
        currPtr++;
      currPtr++;
      
      *offset = (long) *((long*) currPtr);
      found = 1;
      break;
    }
  }

  /* TRUE if file was found, FALSE if it wasn't */
  return found;
}


/***************************************************
  DumpFile
****************************************************/

void DumpFile(FILE *HelpFile)
{
  long fileOffset;
  char fileName[255];
  
  /* For many files we need to know information about    */
  /* The system, so we'll load the system data info here */
  /* if it's available                                   */
  SysLoaded = 0;
  strcpy(fileName, "|SYSTEM");
  if (FindFile(HelpFile, fileName, &fileOffset))
  {
     SysLoaded = (char) SysLoad(HelpFile, fileOffset);
  }

  /* If it's not a version 3 help file */
  /* then we can't handle it.          */
  if (SysInfo.Version != 0x03)
  {
    printf("Warning: Not a version 3 help file. Version is %d.\n",
           SysInfo.Version);
  }
  
  /* If it's version 3, but not revision   */
  /* 0x15 or 0x21, we also can't handle it */
  if (SysInfo.Revision != 0x15 && SysInfo.Revision != 0x21)
  {
    printf("Revision not 0x15 or 0x21. Revision is 0x%02x.\n",
           SysInfo.Revision);
  }
  
  /* If we're reading the |TOPIC file, then we need to */
  /* pre-load the phrases from the |Phrases file       */  
  if (!strcmp(HFSFileToRead, "|TOPIC") ||
      !strcmp(HFSFileToRead, "TOPIC"))
  {
    strcpy(fileName, "|Phrases");
    if (FindFile(HelpFile, fileName, &fileOffset))
    {
      PhrasesLoad(HelpFile, fileOffset);
    }
  }

  strcpy(fileName, HFSFileToRead);
  if (!FindFile(HelpFile, fileName, &fileOffset))
  {
    /* Append a "|" character to the beginning  */
    /* of the filename and try to find the file */
    strcpy(fileName, "|");
    strcat(fileName, HFSFileToRead);
    if (!FindFile(HelpFile, fileName, &fileOffset))
    {
      /* File not found */
      printf("Error: Unable to find HFS file %s or %s\n",
             HFSFileToRead, fileName);
      return;
    }
  }

  if (!ForceHex)
  {  
    if (!strcmp(fileName, "|SYSTEM"))
      SystemDump(HelpFile, fileOffset);
    else if (!strcmp(fileName, "|TTLBTREE"))
      TTLDump(HelpFile, fileOffset);
    else if (!strcmp(fileName, "|CONTEXT"))
      ContextDump(HelpFile, fileOffset);
    else if (!strcmp(fileName, "|FONT"))
      FontDump(HelpFile, fileOffset);
    else if (!strcmp(fileName, "|KWBTREE"))
      KWBTreeDump(HelpFile, fileOffset);
    else if (!strcmp(fileName, "|AWBTREE"))
      KWBTreeDump(HelpFile, fileOffset);
    else if (!strcmp(fileName, "|KWDATA"))
      KWDataDump(HelpFile, fileOffset);
    else if (!strcmp(fileName, "|AWDATA"))
      KWDataDump(HelpFile, fileOffset);
    else if (!strcmp(fileName, "|KWMAP"))
      KWMapDump(HelpFile, fileOffset);
    else if (!strcmp(fileName, "|Phrases"))
      PhrasesDump(HelpFile, fileOffset);
    else 
      HexDumpFile(HelpFile, fileOffset);
  }
  else
  {
    HexDumpFile(HelpFile, fileOffset);
  }  
}

/***************************************************
  List out all the HFS files in a .HLP file
****************************************************/

void ListFiles(FILE *HelpFile)
{
  BTREEHEADER       HFSHeader;
  BTREELEAFHEADER*  HFSLeafHeader;
  long              HFSStart;
  char*             buffer;
  char*             currPtr;
  long              nIndex, nFiles;
  long*             offset;

  /* Go to the HFS and read the header. */
  fseek(HelpFile, HelpHeader.HFSLoc, SEEK_SET);
  LoadHeader(HelpFile);
  fread(&HFSHeader, sizeof(HFSHeader), 1, HelpFile);

  CHECK_SIGNATURE(HFSHeader.Signature, 0x293B);

#ifdef HLP_DEBUG  
  printf("DEBUG -> ListFiles()\n");
  printf("B-Tree Page Size %d\n", HFSHeader.PageSize);
  printf("B-Tree First Leaf %d\n", HFSHeader.FirstLeaf);
  printf("B-Tree Num. Levels %d\n", HFSHeader.nLevels);
  printf("B-Tree Total Pages %d\n", HFSHeader.TotalPages);
  printf("B-Tree Total # Entries %ld\n", HFSHeader.TotalBtreeEntries);
#endif 
  /* Start of the HFS b-tree pages */
  HFSStart = ftell(HelpFile);

  /* Go to the first leaf page of the HFS */  
  fseek(HelpFile, HFSHeader.FirstLeaf * HFSHeader.PageSize, SEEK_CUR);

  /* Allocate space for read buffer and read first page */
  buffer = malloc(HFSHeader.PageSize);
  if (buffer == NULL)
  {
    printf("Unable to allocate space for buffer.\n");
    return;
  }
  HFSLeafHeader = (BTREELEAFHEADER*) buffer;
  
  printf("\nHFS Filename                 Offset\n");
  printf("-----------------------------------\n");
  
  /* Loop through all HFS leaf pages */
  for (nIndex = 0; nIndex < HFSHeader.TotalPages; nIndex++)
  {
    fread(buffer, HFSHeader.PageSize, 1, HelpFile);

    currPtr = buffer + sizeof(BTREELEAFHEADER);

    /* Loop through all files in this page */
    for (nFiles = 0; nFiles < HFSLeafHeader->NEntries; nFiles++)
    {

      /* Print filename */
      printf("%-30s", currPtr);
    
      /* Advance to next filename */
      while (*currPtr)
        currPtr++;
      currPtr++;
      
      offset = (long*) currPtr;
      /* print offset to file */
      printf("0x%08lX\n", *offset);

      /* Move past the file offset to next file */
      currPtr += 4;    
    }
  }
}

/***************************************************
  Check to make sure it's a help file. Then
  either dump the HFS directory or dump an HFS
  file.
****************************************************/

void HelpDump(FILE *HelpFile) 
{
  fread(&HelpHeader, sizeof(HelpHeader), 1, HelpFile);
  if (HelpHeader.MagicNumber != HF_MAGIC) 
  {
    printf("Fatal Error:\n");
    printf("  Not a valid WinHelp file!\n");
    return;
  }

  if (ReadHFSFile) 
     DumpFile(HelpFile);
  else
     ListFiles(HelpFile);

}


/***************************************************
  Show usage
****************************************************/

void Usage()
{
  printf("HLPDUMP2  (version 2.0 of Help Dump)\n");
  printf("By Pete Davis and Mike Wallace               Copyright 1997\n\n");
  printf("Usage: HLPDUMP2 helpfile[.hlp] [hfsfilename] [/H]\n\n");
  printf("where:\n");
  printf("  helpfile    - name of .HLP/.GID/.ANN/.BMK file to open\n");
  printf("  hfsfilename - name of HFS file to read\n");
  printf("  /H          - force a hex dump\n\n");
  printf("note: Do not include the pipe '|' character in the hfsfilename.\n");
}


/***************************************************
 Entry point to HLPDUMP2
****************************************************/

int main(int argc, char *argv[])
{
  char filename[_MAX_PATH];
  FILE *HelpFile;

  if (argc < 2) 
  {
    Usage();
    return EXIT_FAILURE;
  }
  ReadHFSFile = 0;
  if (argc >= 3) 
  {
    strcpy(HFSFileToRead, argv[2]);
    ReadHFSFile = 1;
  }

  /* Are we forcing a hex dump? */
  ForceHex = 0;
  if (argc == 4)
  {

  if (stricmp(argv[3], "/H"))
    {
      printf("Error: Argument 3 unrecognized\n");
      return EXIT_FAILURE;
    }
    ForceHex = 1;

  }
  strcpy(filename, argv[1]);
  strupr(filename);
  if (!strchr(filename, '.')) 
    strcat(filename, ".HLP");

  if ((HelpFile = fopen(filename, "rb")) == NULL) 
  {
    printf("%s does not exist!", filename);
    return EXIT_FAILURE;
  }

  printf("Dumping %s\n\n", filename);
  HelpDump(HelpFile);
  fclose(HelpFile);

  return EXIT_SUCCESS;
  
}
