/*
 * Help Viewer
 *
 * Copyright 1996 Ulrich Schmid
 */

#include <stdio.h>
#include <string.h>
#include "windows.h"
#include "windowsx.h"
#include "winhelp.h"

static void Report(LPCSTR str)
{
#if 0
  fprintf(stderr, "%s\n", str);
#endif
}

#define GET_USHORT(buffer, i)\
(((BYTE)((buffer)[(i)]) + 0x100 * (BYTE)((buffer)[(i)+1])))
#define GET_SHORT(buffer, i)\
(((BYTE)((buffer)[(i)]) + 0x100 * (signed char)((buffer)[(i)+1])))
#define GET_UINT(buffer, i)\
GET_USHORT(buffer, i) + 0x10000 * GET_USHORT(buffer, i+2)

static BOOL HLPFILE_DoReadHlpFile(HLPFILE*, LPCSTR);
static BOOL HLPFILE_ReadFileToBuffer(HFILE);
static BOOL HLPFILE_FindSubFile(LPCSTR name, BYTE**, BYTE**);
static VOID HLPFILE_SystemCommands(HLPFILE*);
static BOOL HLPFILE_Uncompress1_Phrases();
static BOOL HLPFILE_Uncompress1_Topic();
static BOOL HLPFILE_GetContext(HLPFILE*);
static BOOL HLPFILE_AddPage(HLPFILE*, BYTE*, BYTE*);
static BOOL HLPFILE_AddParagraph(HLPFILE*, BYTE *, BYTE*);
static UINT HLPFILE_Uncompressed2_Size(BYTE*, BYTE*);
static VOID HLPFILE_Uncompress2(BYTE**, BYTE*, BYTE*);

static HLPFILE *first_hlpfile = 0;
static HGLOBAL  hFileBuffer;
static BYTE    *file_buffer;

static struct
{
  UINT    num;
  BYTE   *buf;
  HGLOBAL hBuffer;
} phrases;

static struct
{
  BYTE    **map;
  BYTE    *end;
  UINT    wMapLen;
  HGLOBAL hMap;
  HGLOBAL hBuffer;
} topic;

static struct
{
  UINT  bDebug;
  UINT  wFont;
  UINT  wIndent;
  UINT  wHSpace;
  UINT  wVSpace;
  UINT  wVBackSpace;
  HLPFILE_LINK link;
} attributes;

/***********************************************************************
 *
 *           HLPFILE_Contents
 */

HLPFILE_PAGE *HLPFILE_Contents(LPCSTR lpszPath)
{
  HLPFILE *hlpfile = HLPFILE_ReadHlpFile(lpszPath);

  if (!hlpfile) return(0);

  return(hlpfile->first_page);
}

/***********************************************************************
 *
 *           HLPFILE_PageByNumber
 */

HLPFILE_PAGE *HLPFILE_PageByNumber(LPCSTR lpszPath, UINT wNum)
{
  HLPFILE_PAGE *page;
  HLPFILE *hlpfile = HLPFILE_ReadHlpFile(lpszPath);

  if (!hlpfile) return(0);

  for (page = hlpfile->first_page; page && wNum; page = page->next) wNum--;

  return page;
}

/***********************************************************************
 *
 *           HLPFILE_HlpFilePageByHash
 */

HLPFILE_PAGE *HLPFILE_PageByHash(LPCSTR lpszPath, LONG lHash)
{
  INT i;
  UINT wNum;
  HLPFILE_PAGE *page;
  HLPFILE *hlpfile = HLPFILE_ReadHlpFile(lpszPath);

  if (!hlpfile) return(0);

  for (i = 0; i < hlpfile->wContextLen; i++)
    if (hlpfile->Context[i].lHash == lHash) break;

  if (i >= hlpfile->wContextLen)
    {
      HLPFILE_FreeHlpFile(hlpfile);
      return(0);
    }

  wNum = hlpfile->Context[i].wPage;
  for (page = hlpfile->first_page; page && wNum; page = page->next) wNum--;

  return page;
}

/***********************************************************************
 *
 *           HLPFILE_Hash
 */

LONG HLPFILE_Hash(LPCSTR lpszContext)
{
  LONG lHash = 0;
  CHAR c;
  while((c = *lpszContext++))
    {
      CHAR x = 0;
      if (c >= 'A' && c <= 'Z') x = c - 'A' + 17;
      if (c >= 'a' && c <= 'z') x = c - 'a' + 17;
      if (c >= '1' && c <= '9') x = c - '0';
      if (c == '0') x = 10;
      if (c == '.') x = 12;
      if (c == '_') x = 13;
      if (x) lHash = lHash * 43 + x;
    }
  return lHash;
}

/***********************************************************************
 *
 *           HLPFILE_ReadHlpFile
 */

HLPFILE *HLPFILE_ReadHlpFile(LPCSTR lpszPath)
{
  HGLOBAL   hHlpFile;
  HLPFILE *hlpfile;

  for (hlpfile = first_hlpfile; hlpfile; hlpfile = hlpfile->next)
    if (!lstrcmp(hlpfile->lpszPath, lpszPath))
      {
	hlpfile->wRefCount++;
	return(hlpfile);
      }

  hHlpFile = GlobalAlloc(GMEM_FIXED, sizeof(HLPFILE) + lstrlen(lpszPath) + 1);
  if (!hHlpFile) return(0);

  hlpfile              = GlobalLock(hHlpFile);
  hlpfile->hSelf       = hHlpFile;
  hlpfile->wRefCount   = 1;
  hlpfile->hTitle      = 0;
  hlpfile->hContext    = 0;
  hlpfile->wContextLen = 0;
  hlpfile->first_page  = 0;
  hlpfile->first_macro = 0;
  hlpfile->prev        = 0;
  hlpfile->next        = first_hlpfile;
  first_hlpfile   = hlpfile;
  if (hlpfile->next) hlpfile->next->prev = hlpfile;

  hlpfile->lpszPath   = GlobalLock(hHlpFile);
  hlpfile->lpszPath  += sizeof(HLPFILE);
  strcpy(hlpfile->lpszPath, lpszPath);

  phrases.hBuffer = topic.hBuffer = hFileBuffer = 0;

  if (!HLPFILE_DoReadHlpFile(hlpfile, lpszPath))
    {
      HLPFILE_FreeHlpFile(hlpfile);
      hlpfile = 0;
    }

  if (phrases.hBuffer) GlobalFree(phrases.hBuffer);
  if (topic.hBuffer)   GlobalFree(topic.hBuffer);
  if (topic.hMap)      GlobalFree(topic.hMap);
  if (hFileBuffer)     GlobalFree(hFileBuffer);

  return(hlpfile);
}

/***********************************************************************
 *
 *           HLPFILE_DoReadHlpFile
 */

static BOOL HLPFILE_DoReadHlpFile(HLPFILE *hlpfile, LPCSTR lpszPath)
{
  BOOL     ret;
  HFILE    hFile;
  OFSTRUCT ofs;
  BYTE     *buf;

  hFile=OpenFile(lpszPath, &ofs, OF_READ | OF_SEARCH);
  if (hFile == HFILE_ERROR) return FALSE;

  ret = HLPFILE_ReadFileToBuffer(hFile);
  _lclose(hFile);
  if (!ret) return FALSE;

  HLPFILE_SystemCommands(hlpfile);
  if (!HLPFILE_Uncompress1_Phrases()) return FALSE;
  if (!HLPFILE_Uncompress1_Topic()) return FALSE;

  buf = topic.map[0] + 0xc;
  while(buf + 0xc < topic.end)
    {
      BYTE *end = min(buf + GET_UINT(buf, 0), topic.end);
      UINT next, index, offset;

      switch (buf[0x14])
	{
	case 0x02:
	  if (!HLPFILE_AddPage(hlpfile, buf, end)) return(FALSE);
	  break;

	case 0x20:
	  if (!HLPFILE_AddParagraph(hlpfile, buf, end)) return(FALSE);
	  break;

	case 0x23:
	  if (!HLPFILE_AddParagraph(hlpfile, buf, end)) return(FALSE);
	  break;

	default:
	  fprintf(stderr, "buf[0x14] = %x\n", buf[0x14]);
	}

      next   = GET_UINT(buf, 0xc);
      if (next == 0xffffffff) break;

      index  = next >> 14;
      offset = next & 0x3fff;
      if (index > topic.wMapLen) {Report("maplen"); break;}
      buf = topic.map[index] + offset;
    }

  return(HLPFILE_GetContext(hlpfile));
}

/***********************************************************************
 *
 *           HLPFILE_AddPage
 */

static BOOL HLPFILE_AddPage(HLPFILE *hlpfile, BYTE *buf, BYTE *end)
{
  HGLOBAL   hPage;
  HLPFILE_PAGE *page, **pageptr;
  BYTE      *title;
  UINT      titlesize;

  for (pageptr = &hlpfile->first_page; *pageptr; pageptr = &(*pageptr)->next)
    /* Nothing */;

  if (buf + 0x31 > end) {Report("page1"); return(FALSE);};
  title = buf + GET_UINT(buf, 0x10);
  if (title > end) {Report("page2"); return(FALSE);};

  titlesize = HLPFILE_Uncompressed2_Size(title, end);
  hPage = GlobalAlloc(GMEM_FIXED, sizeof(HLPFILE_PAGE) + titlesize);
  if (!hPage) return FALSE;
  page = *pageptr = GlobalLock(hPage);
  pageptr               = &page->next;
  page->hSelf           = hPage;
  page->file            = hlpfile;
  page->next            = 0;
  page->first_paragraph = 0;

  page->lpszTitle = GlobalLock(hPage);
  page->lpszTitle += sizeof(HLPFILE_PAGE);
  HLPFILE_Uncompress2(&title, end, page->lpszTitle);

  page->wNumber = GET_UINT(buf, 0x21);

  attributes.bDebug        = 0;
  attributes.wFont         = 0;
  attributes.wVSpace       = 0;
  attributes.wVBackSpace   = 0;
  attributes.wHSpace       = 0;
  attributes.wIndent       = 0;
  attributes.link.lpszPath = 0;

  return TRUE;
}

/***********************************************************************
 *
 *           HLPFILE_AddParagraph
 */

static BOOL HLPFILE_AddParagraph(HLPFILE *hlpfile, BYTE *buf, BYTE *end)
{
  HGLOBAL            hParagraph;
  HLPFILE_PAGE      *page;
  HLPFILE_PARAGRAPH *paragraph, **paragraphptr;
  UINT               textsize;
  BYTE              *format, *text;
  BOOL               format_header = TRUE;
  BOOL               format_end = FALSE;
  UINT               mask, i;

  if (!hlpfile->first_page) {Report("paragraph1"); return(FALSE);};

  for (page = hlpfile->first_page; page->next; page = page->next) /* Nothing */;
  for (paragraphptr = &page->first_paragraph; *paragraphptr;
       paragraphptr = &(*paragraphptr)->next) /* Nothing */;

  if (buf + 0x19 > end) {Report("paragraph2"); return(FALSE);};

  if (buf[0x14] == 0x02) return TRUE;

  text   = buf + GET_UINT(buf, 0x10);

  switch (buf[0x14])
    {
    case 0x20:
      format = buf + 0x18;
      while (*format) format++;
      format += 4;
      break;

    case 0x23:
      format = buf + 0x2b;
      if (buf[0x17] & 1) format++;
      break;

    default:
      Report("paragraph3");
      return FALSE;
    }

  while (text < end)
    {
      if (format_header)
	{
	  format_header = FALSE;

	  mask = GET_USHORT(format, 0);
	  mask &= 0x3ff;
	  format += 2;

	  for (i = 0; i < 10; i++, mask = mask >> 1)
	    {
	      if (mask & 1)
		{
		  BOOL twoargs = FALSE;
		  CHAR prefix0 = ' ';
		  CHAR prefix1 = '*';

		  if (i == 9 && !twoargs)
		    {
		      switch (*format++)
			{
			default:
			  prefix0 = prefix1 = '?';
			  break;

			case 0x82:
			  prefix0 = prefix1 = 'x';
			  break;

			case 0x84:
			  prefix0 = prefix1 = 'X';
			  twoargs = TRUE;
			}
		    }

		  if (*format & 1)
		    switch(*format)
		      {
		      default:
			format += 2;
			break;
		      }
		  else
		    switch(*format)
		      {

		      default:
			format++;
			break;

		      case 0x08:
			format += 3;
			break;
		      }

		  if (twoargs) format += (*format & 1) ? 2 : 1;
		}
	    }
	}

      for (; !format_header && text < end && format < end && !*text; text++)
	{
	  switch(*format)
	    {
	    case 0x80:
	      attributes.wFont = GET_USHORT(format, 1);
	      format += 3;
	      break;

	    case 0x81:
	      attributes.wVSpace++;
	      format += 1;
	      break;

	    case 0x82:
	      attributes.wVSpace += 2 - attributes.wVBackSpace;
	      attributes.wVBackSpace = 0;
	      attributes.wIndent = 0;
	      format += 1;
	      break;

	    case 0x83:
	      attributes.wIndent++;
	      format += 1;
	      break;

	    case 0x84:
	      format += 3;
	      break;

	    case 0x86:
	    case 0x87:
	    case 0x88:
	      format += 9;
	      break;

	    case 0x89:
	      attributes.wVBackSpace++;
	      format += 1;
	      break;

	    case 0xa9:
	      format += 2;
	      break;

	    case 0xe2:
	    case 0xe3:
	      attributes.link.lpszPath = hlpfile->lpszPath;
	      attributes.link.lHash    = GET_UINT(format, 1);
	      attributes.link.bPopup   = !(*format & 1);
	      format += 5;
	      break;

	    case 0xea:
	      attributes.link.lpszPath = format + 8;
	      attributes.link.lHash    = GET_UINT(format, 4);
	      attributes.link.bPopup   = !(*format & 1);
	      format += 3 + GET_USHORT(format, 1);
	      break;

	    case 0xff:
	      if (buf[0x14] != 0x23 || GET_USHORT(format, 1) == 0xffff)
		{
		  if (format_end) Report("format_end");
		  format_end = TRUE;
		  break;
		}
	      else
		{
		  format_header = TRUE;
		  format += 10;
		  break;
		}

	    default:
	      Report("format");
	      format++;
	    }
	}

      if (text > end || format > end) {Report("paragraph_end"); return(FALSE);};
      if (text == end && !format_end) Report("text_end");

      if (text == end) break;

      textsize = HLPFILE_Uncompressed2_Size(text, end);
      hParagraph = GlobalAlloc(GMEM_FIXED, sizeof(HLPFILE_PARAGRAPH) + textsize);
      if (!hParagraph) return FALSE;
      paragraph = *paragraphptr = GlobalLock(hParagraph);
      paragraphptr = &paragraph->next;
      paragraph->hSelf    = hParagraph;
      paragraph->next     = 0;
      paragraph->link     = 0;

      paragraph->lpszText = GlobalLock(hParagraph);
      paragraph->lpszText += sizeof(HLPFILE_PARAGRAPH);
      HLPFILE_Uncompress2(&text, end, paragraph->lpszText);

      paragraph->bDebug      = attributes.bDebug;
      paragraph->wFont       = attributes.wFont;
      paragraph->wVSpace     = attributes.wVSpace;
      paragraph->wHSpace     = attributes.wHSpace;
      paragraph->wIndent     = attributes.wIndent;
      if (attributes.link.lpszPath)
	{
	  LPSTR ptr;
	  HGLOBAL handle = GlobalAlloc(GMEM_FIXED, sizeof(HLPFILE_LINK) +
					   strlen(attributes.link.lpszPath) + 1);
	  if (!handle) return FALSE;
	  paragraph->link = GlobalLock(handle);
	  paragraph->link->hSelf = handle;

	  ptr = GlobalLock(handle);
	  ptr += sizeof(HLPFILE_LINK);
	  lstrcpy(ptr, (LPSTR) attributes.link.lpszPath);

	  paragraph->link->lpszPath = ptr;
	  paragraph->link->lHash    = attributes.link.lHash;
	  paragraph->link->bPopup   = attributes.link.bPopup;
	}

      attributes.bDebug        = 0;
      attributes.wVSpace       = 0;
      attributes.wHSpace       = 0;
      attributes.link.lpszPath = 0;
    }

  return TRUE;
}

/***********************************************************************
 *
 *           HLPFILE_ReadFileToBuffer
 */

static BOOL HLPFILE_ReadFileToBuffer(HFILE hFile)
{
  BYTE  header[16], dummy[1];
  UINT  size;

  if (_hread(hFile, header, 16) != 16) {Report("header"); return(FALSE);};

  size = GET_UINT(header, 12);
  hFileBuffer = GlobalAlloc(GMEM_FIXED, size + 1);
  if (!hFileBuffer) return FALSE;
  file_buffer = GlobalLock(hFileBuffer);

  memcpy(file_buffer, header, 16);
  if (_hread(hFile, file_buffer + 16, size - 16) != size - 16)
    {Report("filesize1"); return(FALSE);};

  if (_hread(hFile, dummy, 1) != 0) Report("filesize2");

  file_buffer[size] = '0';

  return TRUE;
}

/***********************************************************************
 *
 *           HLPFILE_FindSubFile
 */

static BOOL HLPFILE_FindSubFile(LPCSTR name, BYTE **subbuf, BYTE **subend)
{
  BYTE *root = file_buffer + GET_UINT(file_buffer,  4);
  BYTE *end  = file_buffer + GET_UINT(file_buffer, 12);
  BYTE *ptr  = root + 0x37;

  while (ptr < end && ptr[0] == 0x7c)
    {
      BYTE *fname = ptr + 1;
      ptr += strlen(ptr) + 1;
      if (!lstrcmpi(fname, name))
	{
	  *subbuf = file_buffer + GET_UINT(ptr, 0);
	  *subend = *subbuf + GET_UINT(*subbuf, 0);
	  if (file_buffer > *subbuf || *subbuf > *subend || *subend >= end)
	    {
	      Report("subfile");
	      return FALSE;
	    }
	  return TRUE;
	}
      else ptr += 4;
    }
  return FALSE;
}

/***********************************************************************
 *
 *           HLPFILE_SystemCommands
 */
static VOID HLPFILE_SystemCommands(HLPFILE* hlpfile)
{
  BYTE *buf, *ptr, *end;
  HGLOBAL handle;
  HLPFILE_MACRO *macro, **m;
  LPSTR p;

  hlpfile->lpszTitle = "";

  if (!HLPFILE_FindSubFile("SYSTEM", &buf, &end)) return;

  for (ptr = buf + 0x15; ptr + 4 <= end; ptr += GET_USHORT(ptr, 2) + 4)
    {
      switch (GET_USHORT(ptr, 0))
	{
	case 1:
	  if (hlpfile->hTitle) {Report("title"); break;}
	  hlpfile->hTitle = GlobalAlloc(GMEM_FIXED, strlen(ptr + 4) + 1);
	  if (!hlpfile->hTitle) return;
	  hlpfile->lpszTitle = GlobalLock(hlpfile->hTitle);
	  lstrcpy(hlpfile->lpszTitle, ptr + 4);
	  break;

	case 2:
	  if (GET_USHORT(ptr, 2) != 1 || ptr[4] != 0) Report("system2");
	  break;

	case 3:
	  if (GET_USHORT(ptr, 2) != 4 || GET_UINT(ptr, 4) != 0) Report("system3");
	  break;

	case 4:
	  handle = GlobalAlloc(GMEM_FIXED, sizeof(HLPFILE_MACRO) + lstrlen(ptr + 4) + 1);
	  if (!handle) break;
	  macro = GlobalLock(handle);
	  macro->hSelf = handle;
	  p  = GlobalLock(handle);
	  p += sizeof(HLPFILE_MACRO);
	  lstrcpy(p, (LPSTR) ptr + 4);
	  macro->lpszMacro = p;
	  macro->next = 0;
          m = &hlpfile->first_macro;
          while (*m) m = &(*m)->next;
	  *m = macro;
	  break;

	default:
	  Report("system");
	}
    }
}

/***********************************************************************
 *
 *           HLPFILE_Uncompressed1_Size
 */

static INT HLPFILE_Uncompressed1_Size(BYTE *ptr, BYTE *end)
{
  INT  i, newsize = 0;

  while (ptr < end)
    {
      INT mask=*ptr++;
      for (i = 0; i < 8 && ptr < end; i++, mask = mask >> 1)
	{
	  if (mask & 1)
	    {
	      INT code = GET_USHORT(ptr, 0);
	      INT len  = 3 + (code >> 12);
	      newsize += len;
	      ptr     += 2;
	    }
	  else newsize++, ptr++;
	}
    }

  return(newsize);
}

/***********************************************************************
 *
 *           HLPFILE_Uncompress1
 */

static BYTE *HLPFILE_Uncompress1(BYTE *ptr, BYTE *end, BYTE *newptr)
{
  INT i;

  while (ptr < end)
    {
      INT mask=*ptr++;
      for (i = 0; i < 8 && ptr < end; i++, mask = mask >> 1)
	{
	  if (mask & 1)
	    {
	      INT code   = GET_USHORT(ptr, 0);
	      INT len    = 3 + (code >> 12);
	      INT offset = code & 0xfff;
	      memcpy(newptr, newptr - offset - 1, len);
	      newptr += len;
	      ptr    += 2;
	    }
	  else *newptr++ = *ptr++;
	}
    }

  return(newptr);
}

/***********************************************************************
 *
 *           HLPFILE_Uncompress1_Phrases
 */

static BOOL HLPFILE_Uncompress1_Phrases()
{
  UINT i, num, newsize;
  BYTE *buf, *end, *newbuf;

  if (!HLPFILE_FindSubFile("Phrases", &buf, &end)) {Report("phrases0"); return FALSE;}

  num = phrases.num = GET_USHORT(buf, 9);
  if (buf + 2 * num + 0x13 >= end) {Report("uncompress1a"); return(FALSE);};

  newsize  = 2 * num + 2;
  newsize += HLPFILE_Uncompressed1_Size(buf + 0x13 + 2 * num, end);
  phrases.hBuffer = GlobalAlloc(GMEM_FIXED, newsize);
  if (!phrases.hBuffer) return FALSE;
  newbuf = phrases.buf = GlobalLock(phrases.hBuffer);

  memcpy(newbuf, buf + 0x11, 2 * num + 2);
  HLPFILE_Uncompress1(buf + 0x13 + 2 * num, end, newbuf + 2 * num + 2);

  for (i = 0; i < num; i++)
    {
      INT i0 = GET_USHORT(newbuf, 2 * i);
      INT i1 = GET_USHORT(newbuf, 2 * i + 2);
      if (i1 < i0 || i1 > newsize) {Report("uncompress1b"); return(FALSE);};
    }
  return TRUE;
}

/***********************************************************************
 *
 *           HLPFILE_Uncompress1_Topic
 */

static BOOL HLPFILE_Uncompress1_Topic()
{
  BYTE *buf, *ptr, *end, *newptr;
  INT  i, newsize = 0;

  if (!HLPFILE_FindSubFile("TOPIC", &buf, &end)) {Report("topic0"); return FALSE;}

  buf += 9;
  topic.wMapLen = (end - buf - 1) / 0x1000 + 1;

  for (i = 0; i < topic.wMapLen; i++)
    {
      ptr = buf + i * 0x1000;

      /* I don't know why, it's necessary for printman.hlp */
      if (ptr + 0x44 > end) ptr = end - 0x44;

      newsize += HLPFILE_Uncompressed1_Size(ptr + 0xc, min(end, ptr + 0x1000));
    }

  topic.hMap    = GlobalAlloc(GMEM_FIXED, topic.wMapLen * sizeof(topic.map[0]));
  topic.hBuffer = GlobalAlloc(GMEM_FIXED, newsize);
  if (!topic.hMap || !topic.hBuffer) return FALSE;
  topic.map = GlobalLock(topic.hMap);
  newptr = GlobalLock(topic.hBuffer);
  topic.end = newptr + newsize;

  for (i = 0; i < topic.wMapLen; i++)
    {
      ptr = buf + i * 0x1000;
      if (ptr + 0x44 > end) ptr = end - 0x44;

      topic.map[i] = newptr - 0xc;
      newptr = HLPFILE_Uncompress1(ptr + 0xc, min(end, ptr + 0x1000), newptr);
    }

  return TRUE;
}

/***********************************************************************
 *
 *           HLPFILE_Uncompressed2_Size
 */

static UINT HLPFILE_Uncompressed2_Size(BYTE *ptr, BYTE *end)
{
  UINT wSize   = 0;

  while (ptr < end && *ptr)
    {
      if (*ptr >= 0x20)
	wSize++, ptr++;
      else
	{
	  BYTE *phptr, *phend;
	  UINT code  = 0x100 * ptr[0] + ptr[1];
	  UINT index = (code - 0x100) / 2;
	  BOOL space = code & 1;

	  if (index < phrases.num)
	    {
	      phptr = phrases.buf + GET_USHORT(phrases.buf, 2 * index);
	      phend = phrases.buf + GET_USHORT(phrases.buf, 2 * index + 2);

	      if (phend < phptr) Report("uncompress2a");

	      wSize += phend - phptr;
	      if (space) wSize++;
	    }
	  else Report("uncompress2b");

	  ptr += 2;
	}
    }

  return(wSize + 1);
}

/***********************************************************************
 *
 *           HLPFILE_Uncompress2
 */

static VOID HLPFILE_Uncompress2(BYTE **pptr, BYTE *end, BYTE *newptr)
{
  BYTE *ptr    = *pptr;

  while (ptr < end && *ptr)
    {
      if (*ptr >= 0x20)
	*newptr++ = *ptr++;
      else
	{
	  BYTE *phptr, *phend;
	  UINT code  = 0x100 * ptr[0] + ptr[1];
	  UINT index = (code - 0x100) / 2;
	  BOOL space = code & 1;

	  phptr = phrases.buf + GET_USHORT(phrases.buf, 2 * index);
	  phend = phrases.buf + GET_USHORT(phrases.buf, 2 * index + 2);

	  memcpy(newptr, phptr, phend - phptr);
	  newptr += phend - phptr;
	  if (space) *newptr++ = ' ';

	  ptr += 2;
	}
    }
  *newptr  = '\0';
  *pptr    = ptr;
}

/***********************************************************************
 *
 *           HLPFILE_GetContext
 */

static BOOL HLPFILE_GetContext(HLPFILE *hlpfile)
{
  UINT i, j, clen, tlen;
  BYTE *cbuf, *cptr, *cend, *tbuf, *tptr, *tend;

  if (!HLPFILE_FindSubFile("CONTEXT", &cbuf, &cend)) {Report("context0"); return FALSE;}
  if (cbuf + 0x37 > cend) {Report("context1"); return(FALSE);};
  clen = GET_UINT(cbuf, 0x2b);
  if (cbuf + 0x37 + 8 * hlpfile->wContextLen > cend) {Report("context2"); return(FALSE);};

  if (!HLPFILE_FindSubFile("TTLBTREE", &tbuf, &tend)) {Report("ttlb0"); return FALSE;}
  if (tbuf + 0x37 > tend) {Report("ttlb1"); return(FALSE);};
  tlen = GET_UINT(tbuf, 0x2b);

  hlpfile->hContext = GlobalAlloc(GMEM_FIXED, clen * sizeof(HLPFILE_CONTEXT));
  if (!hlpfile->hContext) return FALSE;
  hlpfile->Context = GlobalLock(hlpfile->hContext);
  hlpfile->wContextLen = clen;

  cptr = cbuf + 0x37;
  for (i = 0; i < clen; i++, cptr += 8)
    {
      tptr = tbuf + 0x37;
      for (j = 0; j < tlen; j++, tptr += 5 + strlen(tptr + 4))
	{
	  if (tptr + 4 >= tend) {Report("ttlb2"); return(FALSE);};
	  if (GET_UINT(tptr, 0) == GET_UINT(cptr, 4)) break;
	}
      if (j >= tlen)
	{
	  Report("ttlb3");
	  j = 0;
	}
      hlpfile->Context[i].lHash = GET_UINT(cptr, 0);
      hlpfile->Context[i].wPage = j;
    }

  return TRUE;
}

/***********************************************************************
 *
 *           HLPFILE_DeleteParagraph
 */

static VOID HLPFILE_DeleteParagraph(HLPFILE_PARAGRAPH* paragraph)
{
  if (!paragraph) return;

  if (paragraph->link) GlobalFree(paragraph->link->hSelf);

  HLPFILE_DeleteParagraph(paragraph->next);
  GlobalFree(paragraph->hSelf);
}

/***********************************************************************
 *
 *           DeletePage
 */

static VOID HLPFILE_DeletePage(HLPFILE_PAGE* page)
{
  if (!page) return;

  HLPFILE_DeletePage(page->next);
  HLPFILE_DeleteParagraph(page->first_paragraph);
  GlobalFree(page->hSelf);
}

/***********************************************************************
 *
 *           DeleteMacro
 */

static VOID HLPFILE_DeleteMacro(HLPFILE_MACRO* macro)
{
  if (!macro) return;

  HLPFILE_DeleteMacro(macro->next);
  GlobalFree(macro->hSelf);
}

/***********************************************************************
 *
 *           HLPFILE_FreeHlpFile
 */

VOID HLPFILE_FreeHlpFile(HLPFILE* hlpfile)
{
  if (!hlpfile) return;
  if (--hlpfile->wRefCount) return;

  if (hlpfile->next) hlpfile->next->prev = hlpfile->prev;
  if (hlpfile->prev) hlpfile->prev->next = hlpfile->next;
  else first_hlpfile = 0;

  HLPFILE_DeletePage(hlpfile->first_page);
  HLPFILE_DeleteMacro(hlpfile->first_macro);
  if (hlpfile->hContext) GlobalFree(hlpfile->hContext);
  if (hlpfile->hTitle) GlobalFree(hlpfile->hTitle);
  GlobalFree(hlpfile->hSelf);
}

/***********************************************************************
 *
 *           FreeHlpFilePage
 */

VOID HLPFILE_FreeHlpFilePage(HLPFILE_PAGE* page)
{
  if (!page) return;
  HLPFILE_FreeHlpFile(page->file);
}

/* Local Variables:    */
/* c-file-style: "GNU" */
/* End:                */
