/*
helpdeco -- utility program to dissect Windows help files
Copyright (C) 1997 Manfred Winterhoff
Copyright (C) 2001 Ben Collver

This file is part of helpdeco; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place, Suite 330, Boston, MA, 02111-1307, USA or visit:
http://www.gnu.org
*/

#include "helpdeco.h"

/* neccessary compiler options for 16 bit version using Borland C/C++:
 *   bcc -ml -K -Os -p helpdeco.c helpdec1.c
 * Don't compile using Register Calling Convention in BC3.1: compiler bug.
 * To compile 32 bit version using Microsoft VC4.0 create a new workspace
 * for a WIN32 console application, insert HELPDECO.C and HELPDEC1.C into
 * it and compile. Use /KNOWEAS 16 bit version as stub to create dual-mode
 * application.
 * Byte align ! Portable to little endian machines only.
*/

FILEREF *external;

char HelpFileName[NAME_MAX];

char name[NAME_MAX];

char ext[_MAX_EXT];

FILE *AnnoFile;

HASHREC *hashrec;

int hashrecs = 0;

BROWSE *browse;

int browses;

int browsenums;

int32_t scaling;

int rounderr;

START *start;

int starts;

BOOL lzcompressed,
 Hall;

BOOL before31,
 after31;

BOOL win95;

BOOL mvp,
 multi;

BOOL warnings,
 missing;

int32_t *Topic;

int Topics;								/* 16 bit: max. 16348 Topics */

GROUP *group;

int groups;

CONTEXTREC *ContextRec;

int ContextRecs;						/* 16 bit: max. 8191 Context Records */

ALTERNATIVE *alternative;

int alternatives;

BOOL overwrite = FALSE;

BOOL exportLZ77 = FALSE;

BOOL extractmacros = TRUE;

BOOL guessing = TRUE;

int32_t guessed = 0L;

BOOL listtopic = FALSE;

BOOL nopagebreak = FALSE;

BOOL resolvebrowse = TRUE;

BOOL reportderived = FALSE;

BOOL checkexternal = FALSE;

BOOL exportplain = FALSE;

#define MAXKEYWORDS (64<<(sizeof(int)*2))	/* 16 bit: 1024, 32 bit: 16348 */
int NextKeywordRec,
 KeywordRecs;

KEYWORDREC *KeywordRec;

TOPICOFFSET NextKeywordOffset;

static const char *helpcomp;

char HelpFileTitle[NAME_MAX];

char TopicTitle[256];

char *Phrases;

unsigned int *PhraseOffsets;

unsigned int PhraseCount;

int32_t TopicFileLength;

int TopicBlockSize;						/* 2k or 4k */

int DecompressSize;						/* 4k or 16k */

char buffer[4096];

char keyword[512];

char index_separators[40] = ",;";

char *extension;

static unsigned int extensions = 0;

/* index into bmpext: bit 0=multiresolution bit 1=bitmap, bit 2=metafile, bit 3=hotspot data, bit 4=embedded, bit 5=transparent */
static const char *const bmpext[] =
	{ "???", "mrb", "bmp", "mrb", "wmf", "mrb", "mrb", "mrb", "shg", "mrb", "shg", "mrb", "shg", "mrb", "shg", "mrb" };
char **stopwordfilename;

int stopwordfiles;

char **fontname;

int fontnames;

unsigned char DefFont;

FONTDESCRIPTOR *font;

static unsigned int fonts;
unsigned char lookup[] = { 0, 3, 1, 2, 4, 5 };	/* to translate font styles */

struct
{
	unsigned char r, g, b;
} color[256];

int colors;

char **windowname;

int windownames;

BOOL NotInAnyTopic;

int TopicsPerRTF;

BOOL lists['z' - '0' + 1];

BOOL keyindex['z' - '0' + 1];

static signed char table[256] = {
	'\x00', '\xD1', '\xD2', '\xD3', '\xD4', '\xD5', '\xD6', '\xD7', '\xD8', '\xD9', '\xDA', '\xDB', '\xDC', '\xDD',
		'\xDE', '\xDF',
	'\xE0', '\xE1', '\xE2', '\xE3', '\xE4', '\xE5', '\xE6', '\xE7', '\xE8', '\xE9', '\xEA', '\xEB', '\xEC', '\xED',
		'\xEE', '\xEF',
	'\xF0', '\x0B', '\xF2', '\xF3', '\xF4', '\xF5', '\xF6', '\xF7', '\xF8', '\xF9', '\xFA', '\xFB', '\xFC', '\xFD',
		'\x0C', '\xFF',
	'\x0A', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D',
		'\x0E', '\x0F',
	'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
		'\x1E', '\x1F',
	'\x20', '\x21', '\x22', '\x23', '\x24', '\x25', '\x26', '\x27', '\x28', '\x29', '\x2A', '\x0B', '\x0C', '\x0D',
		'\x0E', '\x0D',
	'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
		'\x1E', '\x1F',
	'\x20', '\x21', '\x22', '\x23', '\x24', '\x25', '\x26', '\x27', '\x28', '\x29', '\x2A', '\x2B', '\x2C', '\x2D',
		'\x2E', '\x2F',
	'\x50', '\x51', '\x52', '\x53', '\x54', '\x55', '\x56', '\x57', '\x58', '\x59', '\x5A', '\x5B', '\x5C', '\x5D',
		'\x5E', '\x5F',
	'\x60', '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', '\x68', '\x69', '\x6A', '\x6B', '\x6C', '\x6D',
		'\x6E', '\x6F',
	'\x70', '\x71', '\x72', '\x73', '\x74', '\x75', '\x76', '\x77', '\x78', '\x79', '\x7A', '\x7B', '\x7C', '\x7D',
		'\x7E', '\x7F',
	'\x80', '\x81', '\x82', '\x83', '\x0B', '\x85', '\x86', '\x87', '\x88', '\x89', '\x8A', '\x8B', '\x8C', '\x8D',
		'\x8E', '\x8F',
	'\x90', '\x91', '\x92', '\x93', '\x94', '\x95', '\x96', '\x97', '\x98', '\x99', '\x9A', '\x9B', '\x9C', '\x9D',
		'\x9E', '\x9F',
	'\xA0', '\xA1', '\xA2', '\xA3', '\xA4', '\xA5', '\xA6', '\xA7', '\xA8', '\xA9', '\xAA', '\xAB', '\xAC', '\xAD',
		'\xAE', '\xAF',
	'\xB0', '\xB1', '\xB2', '\xB3', '\xB4', '\xB5', '\xB6', '\xB7', '\xB8', '\xB9', '\xBA', '\xBB', '\xBC', '\xBD',
		'\xBE', '\xBF',
	'\xC0', '\xC1', '\xC2', '\xC3', '\xC4', '\xC5', '\xC6', '\xC7', '\xC8', '\xC9', '\xCA', '\xCB', '\xCC', '\xCD',
		'\xCE', '\xCF'
};

char oldtable[256];
unsigned char untable[] =
	{ 0, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 0, '.', '_', 0, 0, 0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };
static const char *prefix[] = { "", "idh_", "helpid_", NULL, NULL, NULL, NULL, NULL };

int32_t prefixhash[sizeof(prefix) / sizeof(prefix[0])];

FONTDESCRIPTOR CurrentFont;

/* convert 3.1/'95 topic name to hash value */
static uint32_t hash(const char *name)
{
	uint32_t hash;
	const unsigned char *ptr;

	if (*name == '\0')
		return 1L;
	for (hash = 0L, ptr = (const unsigned char *) name; *ptr; ptr++)
	{
		hash = hash * 43UL + table[*ptr];
	}
	return hash;
}


static const char *unhash(uint32_t hash)		/* deliver 3.1 context id that fits hash value */
{
	static char buffer[15];
	int i, j, k;
	uint32_t hashlo, divlo, result, mask;
	unsigned char hashhi, divhi;
	char ch;

	i = 0;
	j = hashrecs;
	while (i < j)
	{
		k = (i + j) / 2;
		if (hashrec[k].hash < hash)
		{
			i = k + 1;
		} else if (hashrec[k].hash > hash)
		{
			j = k;
		} else
			return hashrec[k].name;
	}
	for (i = 0; i < 43; i++)
	{
		buffer[j = 14] = '\0';
		hashlo = hash;
		hashhi = i;
		while (1)
		{
			divhi = 21;
			divlo = 0x80000000UL;
			result = 0UL;
			for (mask = 0x80000000UL; mask; mask >>= 1)
			{
				if (hashhi > divhi || (hashhi == divhi && hashlo >= divlo))
				{
					result |= mask;
					hashhi -= divhi;
					if (divlo > hashlo)
						hashhi--;
					hashlo -= divlo;
				}
				divlo >>= 1;
				if (divhi & 1)
					divlo |= 0x80000000UL;
				divhi >>= 1;
			}
			ch = untable[(int) hashlo];
			if (!ch)
				break;
			buffer[--j] = ch;
			if (result == 0)
				return buffer + j;
			hashlo = result;
		}
	}
	/* should never happen */
	error("Can not find a matching string for hash value %08lx", (unsigned long)hash);
	sprintf(buffer, "HASH%08lx", (unsigned long)hash);
	return buffer;
}


static const char *ContextId(uint32_t hash)		/* unhash and verify for legal entry point */
{
	const char *ptr;
	int i;

	for (i = 0; i < ContextRecs; i++)
	{
		if (ContextRec[i].HashValue == hash)
		{
			return unhash(hash);
		}
	}
	ptr = unhash(hash);
	printf("Help Compiler will issue Warning 4113: Unresolved jump or popup '%s'\n", ptr);
	return ptr;
}


static void AddTopic(const char *TopicName, BOOL derived)	/* adds a known topic name to hash decode list */
{
	uint32_t x;
	int i, j, k;

	x = hash(TopicName);
	i = 0;
	j = hashrecs;
	while (i < j)
	{
		k = (i + j) / 2;
		if (hashrec[k].hash < x)
		{
			i = k + 1;
		} else if (hashrec[k].hash > x)
		{
			j = k;
		} else
		{
			if (stricmp(TopicName, hashrec[k].name) != 0)
			{
				if (!hashrec[k].derived)
				{
					if (!derived)
						fprintf(stderr, "ContextId %s already defined as %s\n", TopicName, hashrec[k].name);
					return;
				}
				if (derived)
					return;
				free(hashrec[k].name);
				hashrec[k].name = my_strdup(TopicName);
			}
			if (!derived && hashrec[k].derived)
			{
				guessed--;
				hashrec[k].derived = FALSE;
			}
			return;
		}
	}
	/* %100 to decrease memory fragmentation */
	if (hashrecs % 100 == 0)
		hashrec = (HASHREC *)my_realloc(hashrec, (hashrecs + 100) * sizeof(HASHREC));
	if (i < hashrecs)
		memmove(hashrec + i + 1, hashrec + i, sizeof(HASHREC) * (hashrecs - i));
	hashrec[i].name = my_strdup(TopicName);
	hashrec[i].derived = derived;
	hashrec[i].hash = x;
	if (derived)
		guessed++;
	hashrecs++;
}


/* ContextRec is sorted by TopicOffset. Binary search for an entry for
 * TopicOffset topic. If more than one entry with identical TopicOffset
 * is stored, return index of the first one, if not found return -1 */
static int FindContext(int32_t topic)
{
	int i, lwb, upb;

	lwb = 0;
	upb = ContextRecs;
	while (lwb < upb)
	{
		i = (lwb + upb) / 2;
		if (ContextRec[i].TopicOffset > topic)
		{
			upb = i;
		} else if (ContextRec[i].TopicOffset < topic)
		{
			lwb = i + 1;
		} else
		{
			while (i > 0 && ContextRec[i - 1].TopicOffset == topic)
				i--;
			return i;
		}
	}
	return -1;
}


/* Look if buf contains str (caseinsensitive) and update case of str so it is
 * matching case appearance in buf and return TRUE, FALSE if not found */
static int FindString(const char *buf, int buflen, unsigned char *str, int len)
{
	int i, j;
	unsigned char ch;

	buflen -= len;
	for (i = 0; i <= buflen; i++)
	{
		for (j = 0; j < len; j++)
		{
			if (table[ch = buf[j]] != table[str[j]])
				break;
			str[j] = ch;
		}
		if (j == len)
		{
			return TRUE;
		}
		buf++;
	}
	return FALSE;
}


/*
 * Many people and authoring systems create context ids from topic titles,
 * either by replacing every illegal char with _ or . or leaving it out,
 * sometimes using only part of the topic title or prefixing it with idh_
 * or helpid_. This function tries all variants and may find a context id
 * matching the hash value.
 * And if it doesn't derive a context id, don't bother, unhash delivers
 * one matching the hash code. This code is just to please the user.
 * Win95 allows for nearly every char to be used as a context ident.
 */
static BOOL Derive(const char *str, uint32_t desiredhash, char *buffer)
{
	int i, j, k, l, m, n, o, p, s;
	uint32_t hash, h, x, y;
	char *ptr;
	unsigned char ch;

	l = 2;
	s = strlen(str);
	for (i = !win95; i < l; i++)		/* three variants what to do with illegal characters */
	{									/* but only if an illegal character found (see below) */
		for (j = 0; prefix[j]; j++)
		{
			k = 0;
			while (str[k])
			{
				hash = prefixhash[j];
				strcpy(buffer, prefix[j]);
				ptr = strchr(buffer, '\0');
				for (m = k; str[m]; m++)
				{
					ch = str[m];
					if (i)
					{
						n = oldtable[ch];
						if (n == 0)
						{
							if (i == 2)
							{
								n = oldtable[ch = '_'];
							} else
							{
								continue;
							}
						}
					} else				/* Win95 */
					{
						n = table[ch];
						if (n == 0)
							continue;
					}
					*ptr++ = ch;
					hash = 43L * hash + n;
					for (x = 1L, o = 0; o < 6; o++, x *= 43L)
					{
						h = desiredhash - x * hash;
						if (h < x)
						{
							y = x;
							p = 0;
							while (h)
							{
								y /= 43L;
								ch = untable[(unsigned char) (h / y)];
								if (!ch)
									break;
								h %= y;
								ptr[p++] = ch;
							}
							if (h == 0 && (FindString(str, s, (unsigned char *)ptr, p) || p < 3))
							{
								ptr[p] = '\0';
								return TRUE;
							}
						}
					}
				}
				if (i)
				{
					while (oldtable[ch = str[k]])
						k++;
					if (ch)
						l = 3;
					while ((ch = str[k]) != '\0' && !oldtable[ch])
						k++;
				} else					/* win95 */
				{
					while (table[ch = str[k]])
						k++;
					if (ch)
						l = 3;
					while ((ch = str[k]) != '\0' && !table[ch])
						k++;
				}
			}
		}
	}
	return FALSE;
}


static void Guess(const char *str, TOPICOFFSET topic)
{
	int i, j, k, m;
	uint32_t hash;

	i = FindContext(topic);
	if (i != -1)						/* iff there is a # footnote assigned to that topic offset */
	{
		do
		{
			hash = ContextRec[i].HashValue;
			j = 0;
			k = hashrecs;
			while (j < k)
			{
				m = (j + k) / 2;
				if (hashrec[m].hash < hash)
				{
					j = m + 1;
				} else if (hashrec[m].hash > hash)
				{
					k = m;
				} else
					break;
			}
			if (j >= k)
				if (Derive(str, hash, buffer))
				{
					if (reportderived)
						printf("Derived %s\n", buffer);
					AddTopic(buffer, TRUE);
				}
		} while (++i < ContextRecs && ContextRec[i].TopicOffset == topic);
	}
}


static unsigned short fetchword(const void *ptr)
{
	const unsigned char *p = (const unsigned char *)ptr;
	return p[0] | (p[1] << 8);
}


static uint32_t fetchlong(const void *ptr)
{
	const unsigned char *p = (const unsigned char *)ptr;
	return (uint32_t)fetchword(p) | (((int32_t)(short)fetchword(p + 2)) << 16);
}


static void fetchwindow(SECWINDOW *win, const unsigned char *data)
{
	win->Flags = fetchword(data);
	my_strlcpy(win->Type, data + 2, sizeof(win->Type));
	my_strlcpy(win->Name, data + 12, sizeof(win->Name));
	my_strlcpy(win->Caption, data + 21, sizeof(win->Caption));
	win->X = fetchword(data + 72);
	win->Y = fetchword(data + 74);
	win->Width = fetchword(data + 76);
	win->Height = fetchword(data + 78);
	win->Maximize = fetchword(data + 80);
	win->Rgb[0] = data[82];
	win->Rgb[1] = data[83];
	win->Rgb[2] = data[84];
	win->Unknown1 = data[85];
	win->RgbNsr[0] = data[86];
	win->RgbNsr[1] = data[87];
	win->RgbNsr[2] = data[88];
	win->Unknown2 = data[89];
}


static void fetchmvbwindow(MVBWINDOW *win, const unsigned char *data)
{
	win->Flags = fetchword(data);
	my_strlcpy(win->Type, data + 2, sizeof(win->Type));
	my_strlcpy(win->Name, data + 12, sizeof(win->Name));
	my_strlcpy(win->Caption, data + 21, sizeof(win->Caption));
	win->MoreFlags = data[72];
	win->X = fetchword(data + 73);
	win->Y = fetchword(data + 75);
	win->Width = fetchword(data + 77);
	win->Height = fetchword(data + 79);
	win->Maximize = fetchword(data + 81);
	win->TopRgb[0] = data[83];
	win->TopRgb[1] = data[84];
	win->TopRgb[2] = data[85];
	win->Unknown0 = data[86];
	memcpy(win->Unknown, data + 87, 25);
	win->Rgb[0] = data[112];
	win->Rgb[1] = data[113];
	win->Rgb[2] = data[114];
	win->Unknown1 = data[115];
	win->RgbNsr[0] = data[116];
	win->RgbNsr[1] = data[117];
	win->RgbNsr[2] = data[118];
	win->Unknown2 = data[119];
	win->X2 = fetchword(data + 120);
	win->Y2 = fetchword(data + 122);
	win->Width2 = fetchword(data + 124);
	win->Height2 = fetchword(data + 126);
	win->X3 = fetchword(data + 128);
	win->Y3 = fetchword(data + 130);
}


static void fetchtopiclink(TOPICLINK *link, const unsigned char *data)
{
	link->BlockSize = fetchlong(data);
	link->DataLen2 = fetchlong(data + 4);
	link->PrevBlock = fetchlong(data + 8);
	link->NextBlock = fetchlong(data + 12);
	link->DataLen1 = fetchlong(data + 16);
	link->RecordType = data[20];
}


static void fetch_topicheader(TOPICHEADER *hdr, const void *ptr)
{
	const unsigned char *data = (const unsigned char *)ptr;
	
	hdr->BlockSize = fetchlong(data);
	hdr->BrowseBck = fetchlong(data + 4);
	hdr->BrowseFor = fetchlong(data + 8);
	hdr->TopicNum = fetchlong(data + 12);
	hdr->NonScroll = fetchlong(data + 16);
	hdr->Scroll = fetchlong(data + 20);
	hdr->NextTopic = fetchlong(data + 24);
}


static void fetch_topicheader30(TOPICHEADER30 *hdr, const void *ptr)
{
	const unsigned char *data = (const unsigned char *)ptr;
	
	hdr->BlockSize = fetchlong(data);
	hdr->PrevTopicNum = fetchword(data + 4);
	hdr->unused1 = fetchword(data + 6);
	hdr->NextTopicNum = fetchword(data + 8);
	hdr->unused2 = fetchword(data + 10);
}


/* gets global values from SYSTEM file */
static void SysLoad(FILE *HelpFile)
{
	SYSTEMRECORD *SysRec;
	SYSTEMHEADER SysHdr;
	int i;
	char kwdata[10];
	char kwbtree[10];
	
	if (!SearchFile(HelpFile, "|SYSTEM", NULL))
	{
		fputs("Internal |SYSTEM file not found. Can't continue.\n", stderr);
		exit(1);
	}
	read_systemheader(HelpFile, &SysHdr);
	before31 = SysHdr.Minor < 16;
	after31 = SysHdr.Minor > 21;
	multi = SysHdr.Minor == 27;
	lzcompressed = !before31 && (SysHdr.Flags == 4 || SysHdr.Flags == 8);
	win95 = SysHdr.Minor == 33 && !mvp;
	windownames = 0;
	windowname = NULL;
	if (before31)
	{
		DecompressSize = TopicBlockSize = 2048;
	} else
	{
		DecompressSize = 0x4000;
		if (SysHdr.Flags == 8)
		{
			TopicBlockSize = 2048;
		} else
		{
			TopicBlockSize = 4096;
		}
	}
	if (before31)
	{
		my_gets(HelpFileTitle, 33, HelpFile);
	} else
	{
		for (SysRec = GetFirstSystemRecord(HelpFile); SysRec; SysRec = GetNextSystemRecord(SysRec))
		{
			switch (SysRec->RecordType)
			{
			case 0x0001:
				my_strlcpy(HelpFileTitle, SysRec->Data, sizeof(HelpFileTitle));
				break;
			case 0x0006:
				{
					SECWINDOW win;
					
					fetchwindow(&win, SysRec->Data);
					windowname = (char **)my_realloc(windowname, (windownames + 1) * sizeof(char *));
					windowname[windownames] = NULL;
					if (win.Flags & WSYSFLAG_NAME)
					{
						windowname[windownames] = my_strdup(win.Name);
					}
				}
				windownames++;
				break;
			case 0x000E:
				keyindex[SysRec->Data[1] - '0'] = TRUE;
				break;
			}
		}
	}
	for (i = 'A'; i <= 'z'; i++)
	{
		sprintf(kwdata, "|%cWDATA", i);
		sprintf(kwbtree, "|%cWBTREE", i);
		if (SearchFile(HelpFile, kwdata, NULL) && SearchFile(HelpFile, kwbtree, NULL))
		{
			lists[i - '0'] = TRUE;
		}
	}
}

/*********************************************************************************

  GRAPHICS STUFF
  ==============
  ExtractBitmap(..) extracts the graphics stored as |bmXXX in the Windows
  HelpFile to separate files. In this version multiple resolution pictures
  (with and without hotspots) are saved as MRB's, pictures with hotspots are
  converted to the SHG-format, single resolution Bitmaps without hotspots
  to standard BMP-format and all Metafiles without hotspots to the Aldus
  Placeable Metafile WMF format.

  GENERAL NOTES ABOUT |bmXXX
  --------------------------

  |bmXXX contains one MRB-File. This can be directly recompiled. If hotspots
  in the graphics contained in the MRB-File shall be added or changed with
  SHED, the MRB-File has to be split into one SHG file for each graphic. This
  is very easy as the graphics-data will only be slightly rewrapped. If the
  graphics themselves shall be edited with a standard drawing program, the
  SHG files have to be converted into bitmaps or metafiles (as flagged in
  the SHG). The hotspot data is lost by this step.

  MRB-FILES
  ---------

  MRBC takes the input-file(s), converts them to "lp"-Files if needed, adds
  a resolution specification if none is given in the file and finally outputs
  a "lp"-File with the ending ".MRB". Depending on the given display type
  MRBC sets the following resolutions:

               |           |               | x-resolution | y-resolution
  display-type | extension | std-extension |    (ppi)     |    (ppi)
  -------------+-----------+---------------+--------------+-------------
       CGA     |   ".C*"   |     ".CGA"    |      96      |      48
       EGA     |   ".E*"   |     ".EGA"    |      96      |      72
       VGA     |   ".V*"   |     ".VGA"    |      96      |      96
       8514    |   ".8*"   |     ".854"    |     120      |     120

  SHG-Files
  ---------

  Structure of SHG-Data:

   Offset    | Size       | Ref | Description
   ----------+------------+-----+-----------------------------------------------
      0      | 1 Byte     |     | always 0x01, maybe a version-number
      1      | 1 Word     |  N  | the number of hotspots
      3      | 1 DWord    |  M  | size of macro-data (in bytes)
      7      | 15xN Bytes | HSD | Hotspot-Data (see below)
    7 + 15*N | M Bytes    |     | Macro-Data (ASCIIZ-Strings)
    7+15*N+M | 2*N*ASCIIZ |     | Hotspot-Id and Context- or Macro-Name as
             |            |     | ASCIIZ-String

  Structure of Hotspot-Data:

   Offset    | Size       | Ref | Description
   ----------+------------+-----+-------------------------------------------------
      0      | 3 Bytes    |     | Hotspot-Type: 0xE3 0x00 0x00 = jump, visible
             |            |     |               0xE7 0x04 0x00 = jump, invisible
             |            |     |               0xE2 0x00 0x00 = pop-up, visible
             |            |     |               0xE6 0x04 0x00 = pop-up, invisible
             |            |     |               0xC8 0x00 0x00 = macro, visible
             |            |     |               0xCC 0x04 0x00 = macro, invisible
      3      | 1 Word     |     | Left border of hotspot
      5      | 1 Word     |     | Top border of hotspot
      7      | 1 Word     |     | Right border - Left border
      9      | 1 Word     |     | Bottom border - Top border
     11      | 1 DWord    |     | 0x00000000 => nothing
             |            |     | 0x00000001 => this is a macro-hotspot
             |            |     |   others   => hash-value of the context-string
             |            |     |               according to the WinHelp standard

  03-06-1995 Holger Haase
**********************************************************************************/

/* compares filename a[.HLP] and filename b[.HLP], returning 0 if match */
static int filenamecmp(const char *a, const char *b)
{
	char aname[NAME_MAX], bname[NAME_MAX];
	char aext[_MAX_EXT], bext[_MAX_EXT];
	int i;

	my_splitpath(a, NULL, NULL, aname, aext);
	my_splitpath(b, NULL, NULL, bname, bext);
	if (aext[0] == '\0')
		strcpy(aext, ".hlp");
	if (bext[0] == '\0')
		strcpy(bext, ".hlp");
	i = strcmpi(aname, bname);
	if (i)
		return i;
	return strcmpi(aext, bext);
}


/* store external reference in list, checked later */
static void StoreReference(const char *filename, enum rectype type, char *id, uint32_t hash)
{
	CHECKREC *ptr;
	FILEREF *ref;
	PLACEREC *place;

	for (ref = external; ref; ref = ref->next)
	{
		if (filenamecmp(filename, ref->filename) == 0)
			break;
	}
	if (!ref)
	{
		ref = (FILEREF *)my_malloc(sizeof(FILEREF) + strlen(filename));
		strcpy(ref->filename, filename);
		ref->check = NULL;
		ref->next = external;
		external = ref;
	}
	for (ptr = ref->check; ptr; ptr = ptr->next)
	{
		if (ptr->type == type && ptr->hash == hash)
			break;
	}
	if (!ptr)
	{
		ptr = (CHECKREC *)my_malloc(sizeof(CHECKREC));
		ptr->type = type;
		ptr->hash = hash;
		ptr->id = my_strdup(id);
		ptr->here = NULL;
		ptr->next = ref->check;
		ref->check = ptr;
	}
	if (listtopic && TopicTitle[0])
	{
		place = (PLACEREC *)my_malloc(sizeof(PLACEREC) + strlen(TopicTitle));
		strcpy(place->topicname, TopicTitle);
		place->next = ptr->here;
		ptr->here = place;
	}
}


/* validate each entry in list of external references */
static void CheckReferences(void)
{
	FILEREF *ref;
	CHECKREC *ptr;
	FILE *f;
	BOOL found;
	int32_t offset;
	int i, n;
	CTXOMAPREC CTXORec;
	BTREEHEADER BTreeHdr;
	BTREENODEHEADER CurrNode;
	CONTEXTREC ContextRec;

	for (ref = external; ref; ref = ref->next)
	{
		f = fopen(ref->filename, "rb");
		if (!f)
		{
			fprintf(stderr, "%s not found\n", ref->filename);
		} else
		{
			if (SearchFile(f, NULL, NULL))
			{
				for (ptr = ref->check; ptr; ptr = ptr->next)
				{
					found = FALSE;
					if (ptr->type == CONTEXT)
					{
						if (SearchFile(f, "|CTXOMAP", NULL))
						{
							n = my_getw(f);
							for (i = 0; i < n; i++)
							{
								read_ctxomaprec(f, &CTXORec);
								if (CTXORec.MapID == ptr->hash)	/* hash is context id */
								{
									found = TRUE;
									break;
								}
							}
						}
					} else
					{
						if (SearchFile(f, "|CONTEXT", NULL))
						{
							read_btreeheader(f, &BTreeHdr);
							offset = ftell(f);
							CurrNode.PreviousPage = BTreeHdr.RootPage;
							for (n = 1; n < BTreeHdr.NLevels; n++)
							{
								fseek(f, offset + CurrNode.PreviousPage * (int32_t) BTreeHdr.PageSize, SEEK_SET);
								read_btreeindex(f, &CurrNode);
								for (i = 0; i < CurrNode.NEntries; i++)
								{
									ContextRec.HashValue = getdw(f);
									if (ContextRec.HashValue > ptr->hash)
										break;
									CurrNode.PreviousPage = my_getw(f);	/* Page */
								}
							}
							fseek(f, offset + CurrNode.PreviousPage * (int32_t) BTreeHdr.PageSize, SEEK_SET);
							read_btreenode(f, &CurrNode);
							for (i = 0; i < CurrNode.NEntries; i++)
							{
								read_contextrec(f, &ContextRec);
								if (ContextRec.HashValue == ptr->hash)
									found = TRUE;
								if (ContextRec.HashValue >= ptr->hash)
									break;
							}
						}
					}
					if (!found)
					{
						if (ptr->id)
						{
							printf("%s@%s not found\n", ptr->id, ref->filename);
						} else
						{
							printf("0x%08lx@%s not found\n", (unsigned long)ptr->hash, ref->filename);
						}
						while (ptr->here)
						{
							printf("  %s\n", ptr->here->topicname);
							ptr->here = ptr->here->next;
						}
					}
				}
			} else
			{
				printf("%s isn't a valid WinHelp file !\n", ref->filename);
			}
			fclose(f);
		}
	}
}


/* list each entry in list of external references */
static void ListReferences(void)
{
	FILEREF *ref;
	CHECKREC *ptr;

	hashrecs = 0;
	for (ref = external; ref; ref = ref->next)
	{
		for (ptr = ref->check; ptr; ptr = ptr->next)
		{
			printf("%s ", ref->filename);
			switch (ptr->type)
			{
			case TOPIC:
				printf("topic id ");
				if (ptr->id)
				{
					printf("'%s'", ptr->id);
				} else
				{
					printf("0x%08lx='%s'", (unsigned long)ptr->hash, unhash(ptr->hash));
				}
				break;
			case CONTEXT:
				printf("[MAP] id ");
				if (ptr->id)
				{
					printf("'%s'", ptr->id);
				} else
				{
					printf("0x%08lx=(%ld)", (unsigned long)ptr->hash, (long)ptr->hash);
				}
				break;
			}
			if (ptr->here)
			{
				printf(" referenced from:");
				while (ptr->here)
				{
					printf("\n    %s", ptr->here->topicname);
					ptr->here = ptr->here->next;
				}
			}
			putchar('\n');
		}
	}
}


static void unquote(char *ptr)
{
	char *dest;
	
	dest = ptr;
	do
	{
		if (*ptr == '\\' && ptr[1] != '\0')
		{
			ptr++;
		}
	} while ((*dest++ = *ptr++) != '\0');
}


/* searches for matching '`', '"', or '('. */
static char *findMatchingChar(char *p)
{
	char match = '\'';

	if (*p == '"')
		match = '"';
	else if (*p == '(')
		match = ')';
	else if (*p != '`')
		return p;						/* no match for this string */
	for (p++; *p != '\0' && *p != match; p++)
	{
		if (*p == '\\' && p[1] != '\0')
		{
			p++;
		} else if (*p == '`' || *p == '"' || *p == '(')
		{
			if (!*(p = findMatchingChar(p)))
				break;					/* error: end of string */
		}
	}
	return p;
}


/* scan macro string for topic names and external references */
static BOOL CheckMacroX(char *ptr)
{
	static const char *const macro[] = {
		"AddAccelerator(ssm", "AA(ssm",
		"AppendItem(sssm", "AI(sssm",
		"AL(ssts",
		"ChangeButtonBinding(sm", "CBB(sm",
		"CB(ssm",
		"CE(sm",
		"ChangeItemBinding(sm", "CIB(sm",
		"ExtInsertItem(sssmss",
		"IfThen(mm", "IT(mm",
		"IfThenElse(mmm", "IE(mmm",
		"ExecFile(sssm", "EF(sssm",
		"InsertItem(sssms", "II(sssms",
		"JumpContext(x", "JC(x", "JumpContext(fx", "JC(fx",
		"JumpHash(h", "JH(h", "JumpHash(fh", "JH(fh",
		"JumpId(c", "JI(c", "JumpId(fc", "JI(fc",
		"KL(ssts",
		"MPrintID(c",
		"Not(m",
		"PopupId(c", "PI(c", "PopupId(fc", "PI(fc",
		"PopupContext(x", "PC(x", "PopupContext(fx", "PC(fx",
		"PopupHash(h", "PopupHash(fh",
		"SetContents(fx",
		"SE(sssssc",
		"SH(sssst",
		"UpdateWindow(fc", "UW(fc"
	};
	const char *name;
	int namelen, parms, i, n, l;

	char *parm[20];

	while (1)
	{
		while (*ptr == ' ')
			ptr++;
		if (*ptr == '\0')
			return TRUE;
		if (!isalpha(*ptr))
			return FALSE;
		for (name = ptr, namelen = 0; isalnum(*ptr) || *ptr == '_'; namelen++)
			ptr++;
		while (*ptr == ' ')
			ptr++;
		if (*ptr != '(')
			return FALSE;
		ptr++;
		parms = 0;
		while (1)
		{
			while (*ptr == ' ')
				ptr++;
			parm[parms] = ptr;			/* save after skip spaces */
			if (*ptr == '`' || *ptr == '"')
			{
				parm[parms++] = ptr + 1;	/* advance past quote */
				ptr = findMatchingChar(ptr);
				if (*ptr == '\0')
					return FALSE;		/* unexpected end of string */
				*ptr++ = '\0';			/* zap terminating quote */
				while (*ptr == ' ' || *ptr == '\t')
					ptr++;
			} else if (*ptr != ')' && *ptr != '\0')
			{
				parm[parms++] = ptr;
				while (*ptr != ',' && *ptr != ')' && *ptr != '\0')
				{
					if (*ptr == '(' || *ptr == '`' || *ptr == '"')  /* `, " probably not needed */
					{
						ptr = findMatchingChar(ptr);
						if (*ptr == '\0')
							return FALSE;
					}
					ptr++;
				}
			}
			if (*ptr == ')')
				break;
			if (*ptr != ',')
				return FALSE;
			*ptr++ = '\0';
		}
		*ptr++ = '\0';
		for (i = 0; i < (int)(sizeof(macro) / sizeof(macro[0])); i++)
		{
			if ((int) strcspn(macro[i], "(") == namelen && my_memcmpi(macro[i], name, namelen) == 0
				&& (int)strlen(macro[i] + namelen + 1) >= parms)
				break;
		}
		if (i < (int)(sizeof(macro) / sizeof(macro[0])))	/* macro of interest */
		{
			char *at;

			for (n = 0; n < parms; n++)
				unquote(parm[n]);
			for (n = 0; n < parms; n++)
			{
				if (macro[i][namelen + 1 + n] == 'm')
				{
					CheckMacroX(parm[n]);	/* recursive */
				} else if (macro[i][namelen + 1 + n] == 'c')
				{
					if (extractmacros)
					{
						while (parm[n][0] == ' ')
							parm[n]++;
						for (l = strlen(parm[n]); l > 0 && parm[n][l - 1] == ' '; l--) ;
						parm[n][l] = '\0';
						AddTopic(parm[n], FALSE);
					}
				} else if (macro[i][namelen + 1 + n] == 'f')
				{
					at = strchr(parm[n], '>');
					if (at)
					{
						if (filenamecmp(parm[n], name) != 0)
						{
							if (macro[i][namelen + 2 + n] == 'c')
							{
								StoreReference(parm[n], TOPIC, parm[n + 1], hash(parm[n + 1]));
								n++;
							} else if (macro[i][namelen + 2 + n] == 'x')
							{
								StoreReference(parm[n], CONTEXT, parm[n + 1], strtoul(parm[n + 1], NULL, 0));
								n++;
							} else if (macro[i][namelen + 2 + n] == 'h')
							{
								StoreReference(parm[n], TOPIC, parm[n + 1], strtoul(parm[n + 1], NULL, 0));
								n++;
							}
						}
					}
				} else if (macro[i][namelen + 1 + n] == 't')
				{
					at = strchr(parm[n], '@');
					if (at)
					{
						if (filenamecmp(at + 1, name) != 0)
						{
							*at = '\0';
							StoreReference(at + 1, TOPIC, parm[n], hash(parm[n]));
						} else
						{
							AddTopic(parm[n], FALSE);
						}
					} else
					{
						AddTopic(parm[n], FALSE);
					}
				}
			}
		}
		while (*ptr == ' ')
			ptr++;
		if (*ptr != ':' && *ptr != ';')
			break;
		ptr++;
	}
	return TRUE;
}


static void CheckMacro(const char *ptr)
{
	char *temp;

	if (!multi)
	{
		temp = my_strdup(ptr);
		if (!CheckMacroX(temp))
			fprintf(stderr, "Bad macro: %s\n", ptr);
		free(temp);
	}
}


static void put_bitmapfileheader(FILE *f, const BITMAPFILEHEADER *bmfh)
{
	my_putw(bmfh->bfType, f);
	putdw(bmfh->bfSize, f);
	my_putw(bmfh->bfReserved1, f);
	my_putw(bmfh->bfReserved2, f);
	putdw(bmfh->bfOffBits, f);
}


static void put_bitmapinfoheader(FILE *f, const BITMAPINFOHEADER *bmih)
{
	putdw(bmih->biSize, f);
	putdw(bmih->biWidth, f);
	putdw(bmih->biHeight, f);
	my_putw(bmih->biPlanes, f);
	my_putw(bmih->biBitCount, f);
	putdw(bmih->biCompression, f);
	putdw(bmih->biSizeImage, f);
	putdw(bmih->biXPelsPerMeter, f);
	putdw(bmih->biYPelsPerMeter, f);
	putdw(bmih->biClrUsed, f);
	putdw(bmih->biClrImportant, f);
}


static void put_apmfileheader(FILE *f, const APMFILEHEADER *apm)
{
	putdw(apm->dwKey, f);
	my_putw(apm->hMF, f);
	my_putw(apm->rcBBox.left, f);
	my_putw(apm->rcBBox.top, f);
	my_putw(apm->rcBBox.right, f);
	my_putw(apm->rcBBox.bottom, f);
	my_putw(apm->wInch, f);
	putdw(apm->dwReserved, f);
	my_putw(apm->wChecksum, f);
}


static void put_hotspot(FILE *f, const HOTSPOT *hotspot)
{
	putc(hotspot->id0, f);
	putc(hotspot->id1, f);
	putc(hotspot->id2, f);
	my_putw(hotspot->x, f);
	my_putw(hotspot->y, f);
	my_putw(hotspot->w, f);
	my_putw(hotspot->h, f);
	putdw(hotspot->hash, f);
}


/*
 * check hotspot info of bitmap for topic names and external references and
 * extract (write to file) if checkexternal not set, write out first bitmap
 * or metafile only (no SHG/MRB) if exportplain (create lookalike rtf) set
 */
static int ExtractBitmap(char *szFilename, MFILE *f)
{
	FILE *fTarget;
	char *filename;
	int type;
	unsigned int n, j;
	unsigned char byType, byPacked;
	int32_t l, pos = 0, offset = 0, nextpict, FileStart;
	BITMAPFILEHEADER bmfh;
	BITMAPINFOHEADER bmih;
	APMFILEHEADER afh;
	unsigned short wMagic, mapmode = 0;
	uint32_t colors = 0;
	uint32_t dwRawSize = 0, dwDataSize, dwHotspotOffset, dwOffsBitmap, dwHotspotSize, dwPictureOffset, xPels = 0, yPels = 0;

	FileStart = f->tell(f);
	wMagic = GetWord(f);
	if (wMagic != 0x506C /* SHG */ && wMagic != 0x706C /* MRB */ )
	{
		error("Unknown magic 0x%04X (%c%c)", wMagic, wMagic & 0x00FF, wMagic >> 8);
		return 0;
	}
	fTarget = NULL;
	n = GetWord(f);
	type = !exportplain && n > 1;		/* contains multiple resolutions */
	/* do not depend on wMagic, because it is sometimes incorrect */
	nextpict = 4 + 4 * n;
	for (j = 0; j < n; j++)
	{
		f->seek(f, FileStart + 4 + 4 * j);
		dwOffsBitmap = GetDWord(f);
		f->seek(f, FileStart + dwOffsBitmap);
		byType = f->get(f);				/* type of picture: 5=DDB, 6=DIB, 8=METAFILE */
		byPacked = f->get(f);			/* packing method: 0=unpacked, 1=RunLen, 2=LZ77, 3=both */
		if ((byType == 6 && byPacked < 4) || (byType == 5 && byPacked < 2))
		{
			type |= 2;					/* contains bitmap */
			memset(&bmfh, 0, sizeof(bmfh));
			memset(&bmih, 0, sizeof(bmih));
			bmfh.bfType = 0x4D42;		/* bitmap magic ("BM") */
			bmih.biSize = SIZEOF_BITMAPINFOHEADER;
			xPels = GetCDWord(f);
			/* HC30 doesn't like certain PelsPerMeter */
			if (!before31)
				bmih.biXPelsPerMeter = (xPels * 79L + 1) / 2;
			yPels = GetCDWord(f);
			if (!before31)
				bmih.biYPelsPerMeter = (yPels * 79L + 1) / 2;
			bmih.biPlanes = GetCWord(f);
			bmih.biBitCount = GetCWord(f);
			bmih.biWidth = GetCDWord(f);
			bmih.biHeight = GetCDWord(f);
			colors = bmih.biClrUsed = GetCDWord(f);
			if (!colors)
				colors = 1 << bmih.biBitCount;
			bmih.biClrImportant = GetCDWord(f);
			if (after31 && bmih.biClrImportant == 1)
				type |= 0x20;			/* contains transparent bitmap */
			dwDataSize = GetCDWord(f);
			dwHotspotSize = GetCDWord(f);
			dwPictureOffset = GetDWord(f);
			dwHotspotOffset = GetDWord(f);
			if ((exportplain || n == 1) && (dwHotspotOffset == 0L || dwHotspotSize == 0L))
			{
				if (checkexternal)
					break;
				strcat(szFilename, ".bmp");
				fTarget = my_fopen(szFilename, "wb");
				if (fTarget)
				{
					put_bitmapfileheader(fTarget, &bmfh);
					put_bitmapinfoheader(fTarget, &bmih);
					if (byType == 6)
					{
						CopyBytes(f, colors * 4L, fTarget);
					} else
					{
						putdw(0x000000L, fTarget);
						putdw(0xFFFFFFL, fTarget);
					}
					bmfh.bfOffBits = SIZEOF_BITMAPFILEHEADER + SIZEOF_BITMAPINFOHEADER + colors * 4L;
					bmih.biSizeImage = (((bmih.biWidth * bmih.biBitCount + 31) / 32) * 4) * bmih.biHeight;
					if (byType == 5)	/* convert DDB to DIB */
					{
						int32_t width, length;
						unsigned char count, value;
						int pad;

						width = ((bmih.biWidth * bmih.biBitCount + 15) / 16) * 2;
						pad = (int) (((width + 3) / 4) * 4 - width);
						count = value = 0;
						for (l = 0; l < bmih.biHeight; l++)
						{
							if (byPacked == 1)
							{
								for (length = 0; length < width; length++)
								{
									if ((count & 0x7F) == 0)
									{
										count = f->get(f);
										value = f->get(f);
									} else if (count & 0x80)
									{
										value = f->get(f);
									}
									putc(value, fTarget);
									count--;
								}
							} else
							{
								CopyBytes(f, width, fTarget);
							}
							if (pad)
								fwrite(buffer, pad, 1, fTarget);
						}
					} else
					{
						DecompressIntoFile(byPacked, f, dwDataSize, fTarget);
					}
					/* update bitmap headers */
					bmfh.bfSize = ftell(fTarget);
					fseek(fTarget, 0L, SEEK_SET);
					put_bitmapfileheader(fTarget, &bmfh);
					put_bitmapinfoheader(fTarget, &bmih);
				}
				break;
			}
		} else if (byType == 8 && byPacked < 4)	/* Windows MetaFile */
		{
			type |= 4;					/* contains metafile */
			memset(&afh, 0, sizeof(afh));
			mapmode = GetCWord(f);		/* mapping mode */
			afh.rcBBox.right = GetWord(f);	/* width of metafile-picture */
			afh.rcBBox.bottom = GetWord(f);	/* height of metafile-picture */
			dwRawSize = GetCDWord(f);
			dwDataSize = GetCDWord(f);
			dwHotspotSize = GetCDWord(f);
			dwPictureOffset = GetDWord(f);
			dwHotspotOffset = GetDWord(f);
			if ((exportplain || n == 1) && (dwHotspotOffset == 0L || dwHotspotSize == 0L))
			{
				if (checkexternal)
					break;
				afh.dwKey = 0x9AC6CDD7L;
				afh.wInch = 2540;
			 	afh.wChecksum ^= afh.dwKey & 0xffff;
				afh.wChecksum ^= (afh.dwKey >> 16) & 0xffff;
				afh.wChecksum ^= afh.hMF;
				afh.wChecksum ^= afh.rcBBox.left;
				afh.wChecksum ^= afh.rcBBox.top;
				afh.wChecksum ^= afh.rcBBox.right;
				afh.wChecksum ^= afh.rcBBox.bottom;
				afh.wChecksum ^= afh.wInch;
				afh.wChecksum ^= afh.dwReserved & 0xffff;
				afh.wChecksum ^= (afh.dwReserved >> 16) & 0xffff;
				strcat(szFilename, ".wmf");
				fTarget = my_fopen(szFilename, "wb");
				if (fTarget != NULL)
				{
					put_apmfileheader(fTarget, &afh);
					DecompressIntoFile(byPacked, f, dwDataSize, fTarget);
				}
				break;
			}
		} else
		{
			error("Unknown format (%d) or packing method (%d)", byType, byPacked);
			break;
		}
		type |= 8;						/* contains hotspot info (set before accessing bmpext) */
		if (!checkexternal)
		{
			if (!fTarget)
			{
				strcat(szFilename, ".");
				strcat(szFilename, bmpext[type & 0x0F]);
				fTarget = my_fopen(szFilename, "wb");
				if (!fTarget)
					break;
				my_putw(wMagic, fTarget);
				my_putw(n, fTarget);
			}
			fseek(fTarget, 4 + 4 * j, SEEK_SET);
			putdw(nextpict, fTarget);
			fseek(fTarget, nextpict, SEEK_SET);
			putc(byType, fTarget);
			if (exportLZ77)
			{
				putc(byPacked, fTarget);
			} else
			{
				putc(byPacked & 1, fTarget);
			}
			if (byType == 8)
			{
				putcw(mapmode, fTarget);	/* mapping mode */
				my_putw(afh.rcBBox.right, fTarget);	/* width of metafile-picture */
				my_putw(afh.rcBBox.bottom, fTarget);	/* height of metafile-picture */
				putcdw(dwRawSize, fTarget);
			} else
			{
				putcdw(xPels, fTarget);
				putcdw(yPels, fTarget);
				putcw(bmih.biPlanes, fTarget);
				putcw(bmih.biBitCount, fTarget);
				putcdw(bmih.biWidth, fTarget);
				putcdw(bmih.biHeight, fTarget);
				putcdw(bmih.biClrUsed, fTarget);
				putcdw(bmih.biClrImportant, fTarget);
			}
			pos = ftell(fTarget);
			putdw(0, fTarget);			/* changed later ! */
			putdw(0, fTarget);			/* changed later ! */
			putdw(0, fTarget);			/* changed later ! */
			putdw(0, fTarget);			/* changed later ! */
			if (byType == 6)
				CopyBytes(f, colors * 4L, fTarget);
			offset = ftell(fTarget);
			f->seek(f, FileStart + dwOffsBitmap + dwPictureOffset);
			if (exportLZ77)
			{
				dwDataSize = CopyBytes(f, dwDataSize, fTarget);
			} else
			{
				dwDataSize = DecompressIntoFile(byPacked & 2, f, dwDataSize, fTarget);
			}
		}
		if (dwHotspotSize)
		{
			f->seek(f, FileStart + dwOffsBitmap + dwHotspotOffset);
			if (f->get(f) != 1)
			{
				fputs("No hotspots\n", stderr);
				dwHotspotSize = 0L;
			} else
			{
				unsigned int hotspots, n, j, l;
				uint32_t MacroDataSize;
				char *ptr;
				HOTSPOT *hotspot;

				hotspots = GetWord(f);
				MacroDataSize = GetDWord(f);
				hotspot = (HOTSPOT *)my_malloc(hotspots * sizeof(HOTSPOT));
				for (n = 0; n < hotspots; n++)
					read_hotspot(f, hotspot + n);
				if (checkexternal)
				{
					while (MacroDataSize-- > 0)
						f->get(f);
				} else
				{
					putc(1, fTarget);
					my_putw(hotspots, fTarget);
					putdw(MacroDataSize, fTarget);
					for (n = 0; n < hotspots; n++)
						put_hotspot(fTarget, hotspot + n);
					if (MacroDataSize)
						CopyBytes(f, MacroDataSize, fTarget);
				}
				for (n = 0; n < hotspots; n++)
				{
					j = StringRead(buffer, sizeof(buffer), f) + 1;
					l = j + StringRead(buffer + j, sizeof(buffer) - j, f) + 1;
					if (fTarget)
						fwrite(buffer, l, 1, fTarget);
					if (extractmacros)
						switch (hotspot[n].id0)
						{
						case 0xC8:		/* macro (never seen) */
						case 0xCC:		/* macro without font change */
							CheckMacro(buffer + j);
							break;
						case 0xE0:		/* popup jump HC30 */
						case 0xE1:		/* topic jump HC30 */
						case 0xE2:		/* popup jump HC31 */
						case 0xE3:		/* topic jump HC31 */
						case 0xE6:		/* popup jump without font change */
						case 0xE7:		/* topic jump without font change */
							if (hash(buffer + j) != hotspot[n].hash)
							{
								fprintf(stderr, "Wrong hash %08lx instead %08lx for '%s'\n", (unsigned long)hotspot[n].hash,
										(unsigned long)hash(buffer + j), buffer + j);
							}
							AddTopic(buffer + j, FALSE);
							break;
						case 0xEA:		/* popup jump into external file */
						case 0xEB:		/* topic jump into external file / secondary window */
						case 0xEE:		/* popup jump into external file without font change */
						case 0xEF:		/* topic jump into external file / secondary window without font change */
							if ((hotspot[n].id1 != 0 && hotspot[n].id1 != 1 && hotspot[n].id1 != 4 && hotspot[n].id1 != 6) ||
								(hotspot[n].id2 != 0))
							{
							} else
							{
								filename = strchr(buffer + j, '@');
								if (filename)
									*filename++ = '\0';
								ptr = strchr(buffer + j, '>');
								if (ptr)
									*ptr = '\0';
								if (filename)
								{
									StoreReference(filename, TOPIC, buffer + j, hash(buffer + j));
								} else
								{
									AddTopic(buffer + j, FALSE);
								}
								break;
							}
							/* fallthrough */
						default:
							error("Unknown hotspot %02x %02x %02x X=%u Y=%u W=%u H=%u %08lx,%s,%s", hotspot[n].id0,
								  hotspot[n].id1, hotspot[n].id2, hotspot[n].x, hotspot[n].y, hotspot[n].w,
								  hotspot[n].h, (unsigned long)hotspot[n].hash, buffer, buffer + j);
							break;
						}
				}
				free(hotspot);
			}
		}
		if (!checkexternal)
		{
			dwPictureOffset = offset - nextpict;
			nextpict = ftell(fTarget);
			/* fix up some locations */
			fseek(fTarget, pos, SEEK_SET);
			putdw((dwDataSize << 1) + 1, fTarget);
			putdw((dwHotspotSize << 1) + 1, fTarget);
			putdw(dwPictureOffset, fTarget);
			if (dwHotspotSize)
				putdw(dwPictureOffset + dwDataSize, fTarget);
		}
	}
	if (fTarget != NULL)
		my_fclose(fTarget);
	return type;
}

/****************************************************************************
END OF GRAPHICS STUFF
**************************************************************************/

static const char *getbitmapname(unsigned int n)		/* retrieve extension of exported bitmap n */
{
	static char name[12];

	if (n < extensions && extension[n])
	{
		sprintf(name, "bm%u.%s", n, bmpext[extension[n] & 0x0F]);
	} else if (n == 65535U)
	{
		missing = TRUE;
		fputs("There was a picture file rejected on creation of helpfile.\n", stderr);
		strcpy(name, "missing.bmp");
	} else								/* should never happen */
	{
		warnings = TRUE;
		fprintf(stderr, "Bitmap bm%u not exported\n", n);
		sprintf(name, "bm%u.bmp", n);
	}
	return name;
}


static void ListBitmaps(FILE *hpj)			/* writes out [BITMAPS] section */
{
	unsigned int i;

	if (hpj && extensions)
	{
		fputs("[BITMAPS]\n", hpj);
		for (i = 0; i < extensions; i++)
			if (extension[i])
			{
				fprintf(hpj, "bm%u.%s\n", i, bmpext[extension[i] & 0x0F]);
			}
		putc('\n', hpj);
	}
}


static void ExportBitmaps(FILE *HelpFile)		/* export all bitmaps */
{
	BUFFER buf;
	MFILE *mf;
	const char *leader;
	char FileName[20];
	int32_t FileLength;
	int i, n, type;
	unsigned int num;
	int32_t savepos;

	leader = &"|bm"[before31];
	SearchFile(HelpFile, NULL, NULL);
	for (n = GetFirstPage(HelpFile, &buf, NULL); n; n = GetNextPage(HelpFile, &buf))
	{
		for (i = 0; i < n; i++)
		{
			my_gets(FileName, sizeof(FileName), HelpFile);
			getdw(HelpFile);
			if (memcmp(FileName, leader, strlen(leader)) == 0)
			{
				savepos = ftell(HelpFile);
				if (SearchFile(HelpFile, FileName, &FileLength))
				{
					mf = CreateVirtual(HelpFile);
					type = ExtractBitmap(FileName + (FileName[0] == '|'), mf);
					CloseMap(mf);
					if (type)
					{
						num = atoi(FileName + (FileName[0] == '|') + 2);
						if (num >= extensions)
						{
							extension = (char *)my_realloc(extension, (num + 1) * sizeof(char));
							while (extensions <= num)
								extension[extensions++] = 0;
						}
						extension[num] = type;
					}
				}
				fseek(HelpFile, savepos, SEEK_SET);
			}
		}
	}
}


static const char *TopicName(int32_t topic)
{
	static char name[20];
	int i;

	if (before31)
	{
		if (topic == 0L)
			topic = Topic[0];
		for (i = 16; i < Topics; i++)
			if (Topic[i] == topic)
			{
				sprintf(name, "TOPIC%d", i);
				return name;
			}
	} else
	{
		if (topic == -1L)
		{
			NotInAnyTopic = TRUE;
			/* evaluates to -1 without generating help compiler warning */
			return "21KSYK4";
		}
		i = FindContext(topic);
		if (i != -1)
		{
			return unhash(ContextRec[i].HashValue);
		}
	}
	if (topic)
		fprintf(stderr, "Can not find topic offset %08lx\n", (long)topic);
	return NULL;
}


/* secondary window name from window number */
static const char *GetWindowName(int32_t n)
{
	if (windowname == NULL || n < 0 || n >= windownames || windowname[n] == NULL)
		return "main";
	return windowname[n];
}


/* create HPJ file from contents of |SYSTEM internal file */
static void SysList(FILE *HelpFile, FILE *hpj, char *IconFileName)
{
	int32_t FileLength;
	SYSTEMHEADER SysHdr;
	SYSTEMRECORD *SysRec;
	STOPHEADER StopHdr;
	char name[51];
	char *ptr;
	const char *topicname;
	long color;
	FILE *f;
	int fbreak, macro, windows, i, keywords, dllmaps, n;

	if (hpj && SearchFile(HelpFile, "|SYSTEM", NULL))
	{
		read_systemheader(HelpFile, &SysHdr);
		if (SysHdr.Minor == 15)
		{
			helpcomp = "HC30";
		} else if (SysHdr.Minor == 21)
		{
			helpcomp = "HC31/HCP";
		} else if (SysHdr.Minor == 27)
		{
			helpcomp = "WMVC/MVCC";
		} else if (SysHdr.Minor == 33)
		{
			if (mvp)
				helpcomp = "MVC";
			else
				helpcomp = "HCRTF";
		} else
		{
			helpcomp = NULL;
		}
		fputs("[OPTIONS]\n", hpj);
		if (before31)					/* If 3.0 get title */
		{
			my_gets(HelpFileTitle, 33, HelpFile);
			if (HelpFileTitle[0] != '\0' && HelpFileTitle[0] != '\n')
			{
				fprintf(hpj, "TITLE=%s\n", HelpFileTitle);
			}
			fprintf(hpj, "INDEX=%s\n", TopicName(0L));
			if (PhraseCount)
			{
				fputs("COMPRESS=TRUE\n", hpj);
			} else
			{
				fputs("COMPRESS=FALSE\n", hpj);
			}
			for (i = 'A'; i <= 'z'; i++)
				if (lists[i - '0'] && i != 'K')
				{
					fprintf(hpj, "MULTIKEY=%c\n", i);
				}
			putc('\n', hpj);
		} else							/* else get 3.1 System records */
		{
			macro = 0;
			fbreak = 0;
			windows = 0;
			keywords = 0;
			dllmaps = 0;
			for (SysRec = GetFirstSystemRecord(HelpFile); SysRec; SysRec = GetNextSystemRecord(SysRec))
			{
				switch (SysRec->RecordType)
				{
				case 0x0001:
					if (SysRec->Data[0])
						fprintf(hpj, "TITLE=%s\n", SysRec->Data);
					break;
				case 0x0002:
					ptr = (char *)memchr(SysRec->Data, '\r', SysRec->DataSize);
					if (ptr)
						strcpy(ptr, "%date");
					if (SysRec->Data[0])
						fprintf(hpj, "COPYRIGHT=%s\n", SysRec->Data);
					break;
				case 0x0003:
					topicname = TopicName(fetchlong(SysRec->Data));
					if (topicname)
						fprintf(hpj, "CONTENTS=%s\n", topicname);
					break;
				case 0x0004:
					macro = 1;
					break;
				case 0x0005:
					fprintf(hpj, "ICON=%s\n", IconFileName);
					f = my_fopen(IconFileName, "wb");
					if (f)
					{
						fwrite(SysRec->Data, SysRec->DataSize, 1, f);
						my_fclose(f);
					}
					break;
				case 0x0006:
					windows++;
					break;
				case 0x0008:
					if (SysRec->Data[0])
						fprintf(hpj, "CITATION=%s\n", SysRec->Data);
					break;
				case 0x0009:
					if (!mvp)
						fprintf(hpj, "LCID=0x%X 0x%X 0x%X\n", fetchword(SysRec->Data + 8), fetchword(SysRec->Data),
								fetchword(SysRec->Data + 2));
					break;
				case 0x000A:
					if (!mvp && SysRec->Data[0])
						fprintf(hpj, "CNT=%s\n", SysRec->Data);
					break;
				case 0x000B:
					fprintf(hpj, "CHARSET=%d\n", *(SysRec->Data+1));
					break;
				case 0x000C:
					if (mvp)
					{
						fbreak = 1;
					} else
					{
						fprintf(hpj, "DEFFONT=%s,%d,%d\n", SysRec->Data + 2, *SysRec->Data,
							*(SysRec->Data + 1));
					}
					break;
				case 0x000D:
					if (mvp)
						groups++;
					break;
				case 0x000E:
					if (mvp)
					{
						keywords = 1;
					} else
					{
						fprintf(hpj, "INDEX_SEPARATORS=\"%s\"\n", SysRec->Data);
						my_strlcpy(index_separators, SysRec->Data, sizeof(index_separators));
					}
					break;
				case 0x0012:
					if (SysRec->Data[0])
						fprintf(hpj, "LANGUAGE=%s\n", SysRec->Data);
					break;
				case 0x0013:
					dllmaps = 1;
					break;
				}
			}
			if (win95)
			{
				i = 0;
				if (lzcompressed)
					i |= 8;
				if (Hall)
					i |= 4;
				else if (PhraseCount)
					i |= 2;
				fprintf(hpj, "COMPRESS=%d\n", i);
			} else if (!lzcompressed)
			{
				fputs("COMPRESS=OFF\n", hpj);
			} else if (PhraseCount)
			{
				fputs("COMPRESS=HIGH\n", hpj);
			} else
			{
				fputs("COMPRESS=MEDIUM\n", hpj);
			}
			if (SysHdr.Flags == 8)
				fputs("CDROMOPT=TRUE\n", hpj);
			for (i = 'A'; i <= 'z'; i++)
				if (lists[i - '0'] && i != 'K' && (i != 'A' || !win95))
				{
					fprintf(hpj, "MULTIKEY=%c\n", i);
				}
			putc('\n', hpj);
			if (windows)
			{
				fputs("[WINDOWS]\n", hpj);
				for (SysRec = GetFirstSystemRecord(HelpFile); SysRec; SysRec = GetNextSystemRecord(SysRec))
				{
					if (SysRec->RecordType == 0x0006)
					{
						if (SysRec->DataSize == SIZEOF_SECWINDOW)
						{
							SECWINDOW win;
							
							fetchwindow(&win, SysRec->Data);
							PrintWindow(hpj, &win);
						} else
						{
							MVBWINDOW win;
							
							fetchmvbwindow(&win, SysRec->Data);
							PrintMVBWindow(hpj, &win);
						}
					}
				}
				putc('\n', hpj);
			}
			if (macro)
			{
				fputs("[CONFIG]\n", hpj);
				for (SysRec = GetFirstSystemRecord(HelpFile); SysRec; SysRec = GetNextSystemRecord(SysRec))
				{
					if (SysRec->RecordType == 0x0004)
					{
						if (sscanf((const char *)SysRec->Data, "SPC(%ld)%n", &color, &n) > 0)
						{
							fprintf(hpj, "SPC(%u,%u,%u)%s\n", (unsigned char) (color), (unsigned char) (color >> 8),
									(unsigned char) (color >> 16), SysRec->Data + n);
						} else
						{
							fprintf(hpj, "%s\n", SysRec->Data);
						}
					}
				}
				putc('\n', hpj);
			}
			if (fbreak)
			{
				fputs("[FTINDEX]\n", hpj);
				for (SysRec = GetFirstSystemRecord(HelpFile); SysRec; SysRec = GetNextSystemRecord(SysRec))
				{
					if (SysRec->RecordType == 0x000C)
					{
						ptr = strtok((char *) SysRec->Data, " ");
						if (ptr)
						{
							fprintf(hpj, "dtype%s", ptr);
							ptr = strtok(NULL, " ");
							if (ptr)
							{
								fprintf(hpj, "=%s", ptr);
								ptr = strtok(NULL, " ");
								if (ptr)
								{
									fprintf(hpj, "!%s", ptr);
									ptr = strtok(NULL, " ");
									if (ptr)
									{
										fprintf(hpj, ",%s", ptr + 1);
										if (SearchFile(HelpFile, ptr, NULL))
										{
											for (n = 0; n < stopwordfiles; n++)
											{
												if (strcmp(stopwordfilename[n], ptr) == 0)
													break;
											}
											if (n == stopwordfiles)
											{
												stopwordfilename = (char **)my_realloc(stopwordfilename, (stopwordfiles + 1) * sizeof(char *));
												stopwordfilename[stopwordfiles++] = my_strdup(ptr);
												f = my_fopen(ptr + 1, "wt");
												if (f)
												{
													read_stopheader(HelpFile, &StopHdr);
													for (n = 0; n < StopHdr.BytesUsed; n += 1 + strlen(buffer))
													{
														i = getc(HelpFile);
														my_fread(buffer, i, HelpFile);
														buffer[i] = '\0';
														fprintf(f, "%s\n", buffer);
													}
													my_fclose(f);
												}
											}
										}
										ptr = strtok(NULL, " ");
										if (ptr)
											fprintf(hpj, ",%s", ptr);
									}
								}
							}
							putc('\n', hpj);
						}
					}
				}
				putc('\n', hpj);
			}
			if ((groups || multi) && (browsenums > 1))
			{
				group = (GROUP *)my_malloc(groups * sizeof(GROUP));
				fputs("[GROUPS]\n", hpj);
				i = 0;
				for (SysRec = GetFirstSystemRecord(HelpFile); SysRec; SysRec = GetNextSystemRecord(SysRec))
				{
					if (SysRec->RecordType == 0x000D)
					{
						ptr = (char *)memchr(SysRec->Data, ' ', SysRec->DataSize);
						if (ptr)
							*ptr++ = '\0';
						groups = SearchFile(HelpFile, (const char *)SysRec->Data, NULL);
						n = strcspn((const char *)SysRec->Data, ".");
						SysRec->Data[n] = '\0';
						if (ptr && strcmp(ptr, "\"\" ") == 0)
						{
							fprintf(hpj, "group=%s\n", SysRec->Data);
						} else
						{
							fprintf(hpj, "group=%s,%s\n", SysRec->Data, ptr);
						}
						group[i].Name = my_strdup((const char *)SysRec->Data);
						if (groups)
						{
							read_groupheader(HelpFile, &group[i].GroupHeader);
							if (group[i].GroupHeader.GroupType == 2)
							{
								group[i].Bitmap = (unsigned char *)my_malloc(group[i].GroupHeader.BitmapSize);
								my_fread(group[i].Bitmap, group[i].GroupHeader.BitmapSize, HelpFile);
							}
						} else
						{
							group[i].GroupHeader.GroupType = 0;
						}
						i++;
					}
				}
				if (multi)
					for (i = 1; i < browsenums; i++)
						fprintf(hpj, "group=BROWSE%04x\n", i);
				if (SearchFile(HelpFile, "|GMACROS", &FileLength))
				{
					int32_t len;
					int32_t pos;
					int32_t off;

					getdw(HelpFile);	/* possible count or group number */
					for (pos = 4L; pos < FileLength; pos += len)
					{
						len = getdw(HelpFile);	/* length of record containing two longs and two strings */
						off = getdw(HelpFile);	/* offset of second string in record (first is at pos 8) */
						if (off <= 0L)
							off = len;
						if (len < 8)
							break;
						if (len < off)
							break;
						if (off > 8)
						{
							my_fread(buffer, off - 8, HelpFile);
							buffer[off - 8] = '\0';
							fprintf(hpj, "entry=%s\n", buffer);
						}
						if (len > off)
						{
							my_fread(buffer, len - off, HelpFile);
							buffer[len - off] = '\0';
							fprintf(hpj, "exit=%s\n", buffer);
						}
					}
				}
				putc('\n', hpj);
			}
			if (dllmaps)
			{
				fputs("[DLLMAPS]\n", hpj);
				for (SysRec = GetFirstSystemRecord(HelpFile); SysRec; SysRec = GetNextSystemRecord(SysRec))
				{
					if (SysRec->RecordType == 0x0013)
					{
						if (strcmp((const char *)SysRec->Data, "MVMCI") != 0 &&
							strcmp((const char *)SysRec->Data, "MVIMAGE") != 0 &&
							strcmp((const char *)SysRec->Data, "MVBRKR") != 0)
						{
							ptr = (char *)SysRec->Data;
							ptr += strlen(ptr) + 1;
							fprintf(hpj, "%s=%s,", SysRec->Data, ptr);
							ptr = ptr + strlen(ptr) + 1;
							fprintf(hpj, "%s,", ptr);
							ptr = ptr + strlen(ptr) + 1;
							fprintf(hpj, "%s,", ptr);
							ptr = ptr + strlen(ptr) + 1;
							fprintf(hpj, "%s\n", ptr);
						}
					}
				}
				putc('\n', hpj);
			}
			if (keywords)
			{
				fputs("[KEYINDEX]\n", hpj);
				for (SysRec = GetFirstSystemRecord(HelpFile); SysRec; SysRec = GetNextSystemRecord(SysRec))
				{
					if (SysRec->RecordType == 0x000E)
					{
						fprintf(hpj, "keyword=%c,\"%s\"\n", SysRec->Data[1], SysRec->Data + 30);
					}
				}
				putc('\n', hpj);
			}
			for (i = 0; i < windows; i++)
			{
				sprintf(name, "|CF%d", i);
				if (SearchFile(HelpFile, name, &FileLength))
				{
					fprintf(hpj, "[CONFIG:%d]\n", i);
					/* may use [CONFIG-GetWindowName] instead, but WindowName need not be defined */
					for (n = 0; n < FileLength; n += strlen(buffer) + 1)
					{
						my_gets(buffer, sizeof(buffer), HelpFile);
						fprintf(hpj, "%s\n", buffer);
					}
					putc('\n', hpj);
				}
			}
		}
	}
}


/* load phrases for decompression from old Phrases file or new PhrIndex,
 * PhrImage files of HCRTF */
static BOOL PhraseLoad(FILE *HelpFile)
{
	int32_t FileLength;
	char junk[30];
	BOOL newphrases;
	PHRINDEXHDR PhrIndexHdr;
	unsigned int n;
	int32_t l, offset;
	int32_t SavePos;

	if (SearchFile(HelpFile, "|PhrIndex", NULL))
	{
		read_phrindexhdr(HelpFile, &PhrIndexHdr);
		SavePos = ftell(HelpFile);
		if (SearchFile(HelpFile, "|PhrImage", &FileLength))
		{
			if (FileLength != PhrIndexHdr.phrimagecompressedsize)
			{
				fprintf(stderr, "PhrImage FileSize %ld, in PhrIndex.FileHdr %ld\n", (long)PhrIndexHdr.phrimagecompressedsize,
						(long)FileLength);
			}
			PhraseCount = (unsigned int) PhrIndexHdr.entries;
			PhraseOffsets = (unsigned int *)my_malloc(sizeof(*PhraseOffsets) * (PhraseCount + 1));
			Phrases = (char *)my_malloc(PhrIndexHdr.phrimagesize);
			if (PhrIndexHdr.phrimagesize == PhrIndexHdr.phrimagecompressedsize)
			{
				my_fread(Phrases, PhrIndexHdr.phrimagesize, HelpFile);
			} else
			{
				DecompressIntoBuffer(2, HelpFile, FileLength, Phrases, PhrIndexHdr.phrimagesize);
			}
			fseek(HelpFile, SavePos, SEEK_SET);
			GetBit(NULL);
			offset = 0;
			PhraseOffsets[0] = offset;
			for (l = 0; l < PhrIndexHdr.entries; l++)
			{
				for (n = 1; GetBit(HelpFile); n += 1 << PhrIndexHdr.bits)
					;
				if (GetBit(HelpFile))
					n += 1;
				if (PhrIndexHdr.bits > 1)
					if (GetBit(HelpFile))
						n += 2;
				if (PhrIndexHdr.bits > 2)
					if (GetBit(HelpFile))
						n += 4;
				if (PhrIndexHdr.bits > 3)
					if (GetBit(HelpFile))
						n += 8;
				if (PhrIndexHdr.bits > 4)
					if (GetBit(HelpFile))
						n += 16;
				offset += n;
				PhraseOffsets[(int) l + 1] = offset;
			}
		}
		Hall = TRUE;
		fprintf(stderr, "%u phrases loaded\n", PhraseCount);
	} else if (SearchFile(HelpFile, "|Phrases", &FileLength))
	{
		PhraseCount = my_getw(HelpFile);
		newphrases = PhraseCount == 0x0800;	/* VC4.0: MSDEV\HELP\MSDEV40.MVB */
		if (newphrases)
			PhraseCount = my_getw(HelpFile);
		if (my_getw(HelpFile) != 0x0100)
		{
			error("Unknown |Phrases file structure");
			return FALSE;
		}
		if (PhraseCount)
		{
			if (before31)
			{
				offset = (PhraseCount + 1) * SIZEOF_SHORT;
				FileLength -= (PhraseCount + 1) * SIZEOF_SHORT + 4;
				l = FileLength;
			} else
			{
				l = getdw(HelpFile);
				if (newphrases)
				{
					my_fread(&junk, sizeof(junk), HelpFile);
					offset = (PhraseCount + 1) * SIZEOF_SHORT;
					FileLength -= (PhraseCount + 1) * SIZEOF_SHORT + sizeof(junk) + 10;
				} else
				{
					offset = (PhraseCount + 1) * SIZEOF_SHORT;
					FileLength -= (PhraseCount + 1) * SIZEOF_SHORT + 8;
				}
			}
			PhraseOffsets = (unsigned int *)my_malloc(sizeof(*PhraseOffsets) * (PhraseCount + 1));
			for (n = 0; n <= PhraseCount; n++)
				PhraseOffsets[n] = my_getw(HelpFile) - offset;
			Phrases = (char *)my_malloc(l);
			DecompressIntoBuffer((before31 ? 0 : 2), HelpFile, FileLength, Phrases, l);
			fprintf(stderr, "%u phrases loaded\n", PhraseCount);
		}
		Hall = FALSE;
	}
	return TRUE;
}


/* write phrase PhraseNum to out and returns advanced out
 * or to f it out = NULL or uses PrintString if f = NULL, returns NULL then */
static char *PrintPhrase(unsigned int PhraseNum, char *out, FILE *f)
{
	char *ptr;
	size_t len;

	if (PhraseNum >= PhraseCount)
	{
		error("Phrase %u does not exist", PhraseNum);
		return out;
	}
	ptr = Phrases + PhraseOffsets[PhraseNum];
	len = PhraseOffsets[PhraseNum + 1] - PhraseOffsets[PhraseNum];
	if (out)
	{
		memcpy(out, ptr, len);
		return out + len;
	}
	if (f)
	{
		fwrite(ptr, len, 1, f);
	} else
	{
		PrintString(ptr, len);
	}
	return NULL;
}


/* writeout .PH file from already loaded phrases */
static void PhraseList(const char *FileName)
{
	FILE *f;
	unsigned int n;

	if (PhraseCount)
	{
		f = my_fopen(FileName, "wt");
		if (f)
		{
			for (n = 0; n < PhraseCount; n++)
			{
				PrintPhrase(n, NULL, f);
				putc('\n', f);
			}
			my_fclose(f);
		}
	}
}


static const char *FontFamily(unsigned int i)
{
	static const char *const familyname[] = { "nil", "roman", "swiss", "modern", "script", "decor", "tech" };

	if (i > 0 && i < 7)
		return familyname[i];
	return familyname[0];
}


/* collect another color into color table */
static unsigned char AddColor(unsigned char r, unsigned char g, unsigned char b)
{
	int n;

	for (n = 0; n < colors; n++)
	{
		if (r == color[n].r && g == color[n].g && b == color[n].b)
			break;
	}
	if (n == colors)
	{
		color[colors].r = r;
		color[colors].g = g;
		color[colors].b = b;
		colors++;
	}
	return n;
}


/* load fonts from help file, filling up internal font structure,
 * writing fonttbl, colortbl, and styletbl to rtf file */
static void FontLoad(FILE *HelpFile, FILE *rtf, FILE *hpj)
{
	static const char *const BestFonts[] =
		{ "Arial", "Times New Roman", "MS Sans Serif", "MS Serif", "Helv", "TmsRmn", "MS Sans Serif", "Helvetica", "Times Roman", "Times" };
	int default_font = 0;
	CHARMAPHEADER CharmapHeader;
	FONTHEADER FontHdr;
	FILE *f;
	char FontName[33];
	char CharMap[33];
	char *ptr;
	char *p;
	int32_t FontStart;
	int i, j, k, l, len;
	unsigned char *family;
	BOOL charmap;
	OLDFONT oldfont;
	NEWFONT newfont;
	MVBFONT mvbfont;
	MVBSTYLE *mvbstyle;
	NEWSTYLE *newstyle;
	FONTDESCRIPTOR *fd;

	if (SearchFile(HelpFile, "|FONT", NULL))
	{
		FontStart = ftell(HelpFile);
		read_fontheader(HelpFile, &FontHdr);
		fontnames = FontHdr.NumFacenames;
		len = (FontHdr.DescriptorsOffset - FontHdr.FacenamesOffset) / fontnames;
		fontname = (char **)my_malloc(fontnames * sizeof(char *));
		family = (unsigned char *)my_malloc(fontnames * sizeof(unsigned char));
		memset(family, 0, fontnames * sizeof(unsigned char));
		charmap = FALSE;
		mvbstyle = NULL;
		newstyle = NULL;
		for (i = 0; i < fontnames; i++)
		{
			fseek(HelpFile, FontStart + FontHdr.FacenamesOffset + len * i, SEEK_SET);
			my_fread(FontName, len, HelpFile);
			FontName[len] = '\0';
			if (FontName[0] == '\000')
			{
				strcpy(FontName, BestFonts[default_font]);
			}
			ptr = strchr(FontName, ',');
			if (ptr && FontHdr.FacenamesOffset >= 16)
			{
				*ptr++ = '\0';
				fseek(HelpFile, FontStart + FontHdr.CharmapsOffset, SEEK_SET);
				for (j = 0; hpj && j < FontHdr.NumCharmaps; j++)
				{
					my_fread(CharMap, 32, HelpFile);
					CharMap[32] = '\0';
					p = strchr(CharMap, ',');
					if (p && strcmp(p + 1, ptr) == 0 && strcmp(CharMap, "|MVCHARTAB,0") != 0)
					{
						if (!charmap)
						{
							fputs("[CHARMAP]\n", hpj);
							charmap = TRUE;
						}
						*p++ = '\0';
						if (strcmp(p, "0") == 0)
						{
							fprintf(hpj, "DEFAULT=%s\n", CharMap);
						} else
						{
							fprintf(hpj, "%s=%s\n", FontName, CharMap);
						}
						break;
					}
				}
			}
			fontname[i] = my_strdup(FontName);
		}
		if (charmap)
			putc('\n', hpj);
		if (hpj && FontHdr.FacenamesOffset >= 16)
			for (j = 0; j < FontHdr.NumCharmaps; j++)
			{
				fseek(HelpFile, FontStart + FontHdr.CharmapsOffset + j * 32, SEEK_SET);
				my_fread(CharMap, 32, HelpFile);
				CharMap[32] = '\0';
				p = strchr(CharMap, ',');
				if (p && strcmp(CharMap, "|MVCHARTAB,0") != 0)
				{
					*p++ = '\0';
					if (SearchFile(HelpFile, CharMap, NULL))
					{
						read_charmapheader(HelpFile, &CharmapHeader);
						f = my_fopen(CharMap, "wt");
						if (f)
						{
							fprintf(f, "%d,\n", CharmapHeader.Entries);
							for (k = 0; k < CharmapHeader.Entries; k++)
							{
								fprintf(f, "%5u,", my_getw(HelpFile));
								fprintf(f, "%5u,", my_getw(HelpFile));
								fprintf(f, "%3u,", getc(HelpFile));
								fprintf(f, "%3u,", getc(HelpFile));
								fprintf(f, "%3u,", getc(HelpFile));
								fprintf(f, "%3u,\n", getc(HelpFile));
								my_getw(HelpFile);
							}
							fprintf(f, "%d,\n", CharmapHeader.Ligatures);
							for (k = 0; k < CharmapHeader.Ligatures; k++)
							{
								for (l = 0; l < CharmapHeader.LigLen; l++)
								{
									fprintf(f, "%3u,", getc(HelpFile));
								}
								putc('\n', f);
							}
							my_fclose(f);
						}
					}
				}
			}
		fseek(HelpFile, FontStart + FontHdr.DescriptorsOffset, SEEK_SET);
		colors = 1;						/* auto */
		color[0].r = 1;
		color[0].g = 1;
		color[0].b = 0;
		fonts = FontHdr.NumDescriptors;
		if (font)
			free(font);
		font = (FONTDESCRIPTOR *)my_malloc(fonts * sizeof(FONTDESCRIPTOR));
		memset(font, 0, fonts * sizeof(FONTDESCRIPTOR));
		if (FontHdr.FacenamesOffset >= 16)
		{
			scaling = 1L;
			rounderr = 0;
			for (i = 0; i < FontHdr.NumDescriptors; i++)
			{
				read_mvbfont(HelpFile, &mvbfont);
				fd = font + i;
				fd->FontName = mvbfont.FontName;
				fd->HalfPoints = -2L * mvbfont.Height;
				fd->Bold = mvbfont.Weight > 500;
				fd->Italic = mvbfont.Italic != 0;
				fd->Underline = mvbfont.Underline != 0;
				fd->StrikeOut = mvbfont.StrikeOut != 0;
				fd->DoubleUnderline = mvbfont.DoubleUnderline != 0;
				fd->SmallCaps = mvbfont.SmallCaps != 0;
				fd->textcolor = AddColor(mvbfont.FGRGB[0], mvbfont.FGRGB[1], mvbfont.FGRGB[2]);
				fd->backcolor = AddColor(mvbfont.BGRGB[0], mvbfont.BGRGB[1], mvbfont.BGRGB[2]);
				fd->FontFamily = mvbfont.PitchAndFamily >> 4;
				fd->style = mvbfont.style;
				fd->up = mvbfont.up;
				fd->expndtw = mvbfont.expndtw;
			}
			fseek(HelpFile, FontStart + FontHdr.FormatsOffset, SEEK_SET);
			mvbstyle = (MVBSTYLE *)my_malloc(FontHdr.NumFormats * sizeof(MVBSTYLE));
			for (i = 0; i < FontHdr.NumFormats; i++)
				read_mvbstyle(HelpFile, mvbstyle + i);
			for (i = 0; i < FontHdr.NumFormats; i++)
			{
				MVBSTYLE *m;

				m = mvbstyle + i;
				m->font.FGRGB[0] = AddColor(m->font.FGRGB[0], m->font.FGRGB[1], m->font.FGRGB[2]);
				m->font.BGRGB[0] = AddColor(m->font.BGRGB[0], m->font.BGRGB[1], m->font.BGRGB[2]);
			}
		} else if (FontHdr.FacenamesOffset >= 12)
		{
			scaling = 1L;
			rounderr = 0;
			for (i = 0; i < FontHdr.NumDescriptors; i++)
			{
				read_newfont(HelpFile, &newfont);
				fd = font + i;
				fd->Bold = newfont.Weight > 500;
				fd->Italic = newfont.Italic != 0;
				fd->Underline = newfont.Underline != 0;
				fd->StrikeOut = newfont.StrikeOut != 0;
				fd->DoubleUnderline = newfont.DoubleUnderline != 0;
				fd->SmallCaps = newfont.SmallCaps != 0;
				fd->FontName = newfont.FontName;
				fd->HalfPoints = -2L * newfont.Height;
				fd->textcolor = AddColor(newfont.FGRGB[0], newfont.FGRGB[1], newfont.FGRGB[2]);
				fd->backcolor = AddColor(newfont.BGRGB[0], newfont.BGRGB[1], newfont.BGRGB[2]);
				fd->FontFamily = newfont.PitchAndFamily >> 4;
			}
			fseek(HelpFile, FontStart + FontHdr.FormatsOffset, SEEK_SET);
			newstyle = (NEWSTYLE *)my_malloc(FontHdr.NumFormats * sizeof(NEWSTYLE));
			for (i = 0; i < FontHdr.NumFormats; i++)
				read_newstyle(HelpFile, newstyle + i);
			for (i = 0; i < FontHdr.NumFormats; i++)
			{
				NEWSTYLE *m;

				m = newstyle + i;
				m->font.FGRGB[0] = AddColor(m->font.FGRGB[0], m->font.FGRGB[1], m->font.FGRGB[2]);
				m->font.BGRGB[0] = AddColor(m->font.BGRGB[0], m->font.BGRGB[1], m->font.BGRGB[2]);
			}
		} else
		{
			scaling = 10L;
			rounderr = 5;
			for (i = 0; i < FontHdr.NumDescriptors; i++)
			{
				read_oldfont(HelpFile, &oldfont);
				fd = font + i;
				fd->Bold = (oldfont.Attributes & FONT_BOLD) != 0;
				fd->Italic = (oldfont.Attributes & FONT_ITAL) != 0;
				fd->Underline = (oldfont.Attributes & FONT_UNDR) != 0;
				fd->StrikeOut = (oldfont.Attributes & FONT_STRK) != 0;
				fd->DoubleUnderline = (oldfont.Attributes & FONT_DBUN) != 0;
				fd->SmallCaps = (oldfont.Attributes & FONT_SMCP) != 0;
				fd->FontName = oldfont.FontName;
				fd->HalfPoints = oldfont.HalfPoints;
				fd->textcolor = AddColor(oldfont.FGRGB[0], oldfont.FGRGB[1], oldfont.FGRGB[2]);
				fd->backcolor = AddColor(oldfont.BGRGB[0], oldfont.BGRGB[1], oldfont.BGRGB[2]);
				if (oldfont.FontFamily < 6)
				{
					fd->FontFamily = lookup[oldfont.FontFamily];
				} else
				{
					fd->FontFamily = oldfont.FontFamily;
				}
			}
		}
		for (i = 0; i < FontHdr.NumDescriptors; i++)
		{
			if (font[i].FontName < fontnames)
			{
				family[font[i].FontName] = font[i].FontFamily;
			}
		}
		DefFont = 0;
		l = sizeof(BestFonts) / sizeof(BestFonts[0]);
		if (fontname)
		{
			for (i = 0; i < fontnames; i++)
				if (family[i])
				{
					for (j = 0; j < l; j++)
					{
						if (stricmp(fontname[i], BestFonts[j]) == 0)
						{
							DefFont = i;
							l = j;
							break;
						}
					}
				}
		}
		fprintf(rtf, "{\\rtf1\\ansi\\deff%d\n{\\fonttbl", DefFont);
		for (i = 0; i < fontnames; i++)
		{
			fprintf(rtf, "{\\f%d\\f%s %s;}", i, FontFamily(family[i]), fontname[i]);
			free(fontname[i]);
		}
		free(fontname);
		fputs("}\n", rtf);
		if (colors > 1)
		{
			fputs("{\\colortbl;", rtf);
			for (i = 1; i < colors; i++)
				fprintf(rtf, "\\red%d\\green%d\\blue%d;", color[i].r, color[i].g, color[i].b);
			fputs("}\n", rtf);
		}
		fprintf(rtf, "{\\stylesheet{\\fs%d \\snext0 Normal;}\n", font[0].HalfPoints);
		if (mvbstyle)
		{
			for (i = 0; i < FontHdr.NumFormats; i++)
			{
				MVBSTYLE *m, *n;

				m = mvbstyle + i;
				fprintf(rtf, "{\\*\\cs%u \\additive", m->StyleNum + 9);
				if (m->BasedOn)
				{
					n = mvbstyle + (m->BasedOn - 1);
					if (m->font.FontName != n->font.FontName)
						fprintf(rtf, "\\f%d", m->font.FontName);
					if (m->font.expndtw != n->font.expndtw)
						fprintf(rtf, "\\expndtw%d", m->font.expndtw);
					if (m->font.FGRGB[0] != n->font.FGRGB[0])
						fprintf(rtf, "\\cf%d", m->font.FGRGB[0]);
					if (m->font.BGRGB[0] != n->font.BGRGB[0])
						fprintf(rtf, "\\cb%d", m->font.BGRGB[0]);
					if (m->font.Height != n->font.Height)
						fprintf(rtf, "\\fs%ld", -2L * m->font.Height);
					if ((m->font.Weight > 500) != (n->font.Weight > 500))
						fprintf(rtf, "\\b%d", m->font.Weight > 500);
					if (m->font.Italic != n->font.Italic)
						fprintf(rtf, "\\i%d", m->font.Italic);
					if (m->font.Underline != n->font.Underline)
						fprintf(rtf, "\\ul%d", m->font.Underline);
					if (m->font.StrikeOut != n->font.StrikeOut)
						fprintf(rtf, "\\strike%d", m->font.StrikeOut);
					if (m->font.DoubleUnderline != n->font.DoubleUnderline)
						fprintf(rtf, "\\uldb%d", m->font.DoubleUnderline);
					if (m->font.SmallCaps != n->font.SmallCaps)
						fprintf(rtf, "\\scaps%d", m->font.SmallCaps);
					if (m->font.up != n->font.up)
						fprintf(rtf, "\\up%d", abs(m->font.up));
					fprintf(rtf, " \\sbasedon%u", m->BasedOn + 9);
				} else
				{
					fprintf(rtf, "\\f%d", m->font.FontName);
					if (m->font.Italic)
						fputs("\\i", rtf);
					if (m->font.Weight > 500)
						fputs("\\b", rtf);
					if (m->font.Underline)
						fputs("\\ul", rtf);
					if (m->font.StrikeOut)
						fputs("\\strike", rtf);
					if (m->font.DoubleUnderline)
						fputs("\\uldb", rtf);
					if (m->font.SmallCaps)
						fputs("\\scaps", rtf);
					if (m->font.expndtw)
						fprintf(rtf, "\\expndtw%d", m->font.expndtw);
					if (m->font.up > 0)
						fprintf(rtf, "\\up%d", m->font.up);
					else if (m->font.up < 0)
						fprintf(rtf, "\\dn%d", -m->font.up);
					fprintf(rtf, "\\fs%ld", -2 * (long)m->font.Height);
					if (m->font.FGRGB[0])
						fprintf(rtf, "\\cf%d", m->font.FGRGB[0]);
					if (m->font.BGRGB[0])
						fprintf(rtf, "\\cb%d", m->font.BGRGB[0]);
				}
				fprintf(rtf, " %s;}\n", m->StyleName);
			}
			free(mvbstyle);
		} else if (newstyle)
		{
			for (i = 0; i < FontHdr.NumFormats; i++)
			{
				NEWSTYLE *m, *n;

				m = newstyle + i;
				fprintf(rtf, "{\\*\\cs%u \\additive", m->StyleNum + 9);
				if (m->BasedOn)
				{
					n = newstyle + (m->BasedOn - 1);
					if (m->font.FontName != n->font.FontName)
						fprintf(rtf, "\\f%d", m->font.FontName);
					if (m->font.FGRGB[0] != n->font.FGRGB[0])
						fprintf(rtf, "\\cf%d", m->font.FGRGB[0]);
					if (m->font.BGRGB[0] != n->font.BGRGB[0])
						fprintf(rtf, "\\cb%d", m->font.BGRGB[0]);
					if (m->font.Height != n->font.Height)
						fprintf(rtf, "\\fs%ld", -2L * m->font.Height);
					if ((m->font.Weight > 500) != (n->font.Weight > 500))
						fprintf(rtf, "\\b%d", m->font.Weight > 500);
					if (m->font.Italic != n->font.Italic)
						fprintf(rtf, "\\i%d", m->font.Italic);
					if (m->font.Underline != n->font.Underline)
						fprintf(rtf, "\\ul%d", m->font.Underline);
					if (m->font.StrikeOut != n->font.StrikeOut)
						fprintf(rtf, "\\strike%d", m->font.StrikeOut);
					if (m->font.DoubleUnderline != n->font.DoubleUnderline)
						fprintf(rtf, "\\uldb%d", m->font.DoubleUnderline);
					if (m->font.SmallCaps != n->font.SmallCaps)
						fprintf(rtf, "\\scaps%d", m->font.SmallCaps);
					fprintf(rtf, " \\sbasedon%u", m->BasedOn + 9);
				} else
				{
					fprintf(rtf, "\\f%d", m->font.FontName);
					if (m->font.Italic)
						fputs("\\i", rtf);
					if (m->font.Weight > 500)
						fputs("\\b", rtf);
					if (m->font.Underline)
						fputs("\\ul", rtf);
					if (m->font.StrikeOut)
						fputs("\\strike", rtf);
					if (m->font.DoubleUnderline)
						fputs("\\uldb", rtf);
					if (m->font.SmallCaps)
						fputs("\\scaps", rtf);
					fprintf(rtf, "\\fs%ld", -2 * (long)m->font.Height);
					if (m->font.FGRGB[0])
						fprintf(rtf, "\\cf%d", m->font.FGRGB[0]);
					if (m->font.BGRGB[0])
						fprintf(rtf, "\\cb%d", m->font.BGRGB[0]);
				}
				fprintf(rtf, " %s;}\n", m->StyleName);
			}
			free(newstyle);
		}
		if (family)
			free(family);
		fputs("}\\pard\\plain\n", rtf);
		memset(&CurrentFont, 0, sizeof(CurrentFont));
		CurrentFont.FontName = DefFont;
		if (hpj)
		{
			fprintf(stderr, "%u font names, %u font descriptors", fontnames, FontHdr.NumDescriptors);
			if (FontHdr.FacenamesOffset >= 12)
				printf(", %u font styles", FontHdr.NumFormats);
			fputs(" loaded\n", stderr);
		}
	}
}


/*
 * read NumBytes from |TOPIC starting at TopicPos (or if TopicPos is 0
 * where last left off) into dest, returning number of bytes read.
 * TopicRead handles LZ77 decompression and the crossing of topic blocks
 */
static int32_t TopicRead(FILE *HelpFile, int32_t TopicPos, void *dest, int32_t NumBytes)
{
	static TOPICBLOCKHEADER TopicBlockHeader;
	static unsigned char TopicBuffer[0x4000];
	static int32_t TopicFileStart;
	static int32_t TopicBlockNum;
	unsigned int TopicBlockOffset;
	static unsigned int DecompSize;
	static int32_t LastTopicPos;
	int32_t n;

	if (!TopicFileStart)				/* first call: HelpFile is at start of |TOPIC */
	{
		TopicFileStart = ftell(HelpFile);
		TopicBlockNum = -1L;
	}
	if (!TopicPos)
		TopicPos = LastTopicPos;		/* continue where left off */
	if ((TopicPos - SIZEOF_TOPICBLOCKHEADER) / DecompressSize != TopicBlockNum)	/* other topic block */
	{
		TopicBlockNum = (TopicPos - SIZEOF_TOPICBLOCKHEADER) / DecompressSize;
		if (TopicBlockNum * TopicBlockSize >= TopicFileLength)
			return 0;
		fseek(HelpFile, TopicFileStart + TopicBlockNum * TopicBlockSize, SEEK_SET);
		n = TopicBlockSize;
		if (n + TopicBlockNum * TopicBlockSize > TopicFileLength)
		{
			n = TopicFileLength - TopicBlockNum * TopicBlockSize;
		}
		read_topicblockheader(HelpFile, &TopicBlockHeader);
		n -= SIZEOF_TOPICBLOCKHEADER;
		if (lzcompressed)
		{
			DecompSize = DecompressIntoBuffer(2, HelpFile, n, (char *)TopicBuffer, sizeof(TopicBuffer));
		} else
		{
			DecompSize = my_fread(TopicBuffer, n, HelpFile);
		}
	}
	TopicBlockOffset = (TopicPos - SIZEOF_TOPICBLOCKHEADER) % DecompressSize;
	if (TopicBlockOffset + NumBytes > DecompSize)	/* more than available in this block */
	{
		n = DecompSize - TopicBlockOffset;
		if (n)
			memcpy(dest, TopicBuffer + TopicBlockOffset, n);
		return n + TopicRead(HelpFile, (TopicBlockNum + 1) * DecompressSize + SIZEOF_TOPICBLOCKHEADER,
							 (char *) dest + n, NumBytes - n);
	}
	if (NumBytes)
		memcpy(dest, TopicBuffer + TopicBlockOffset, NumBytes);
	LastTopicPos = TopicPos + NumBytes;
	return NumBytes;
}


/* Hall or oldstyle Phrase replacement of str into out */
static char *PhraseReplace(unsigned char *str, int32_t len, char *out)
{
	int CurChar;

	if (Hall)
	{
		while (len)
		{
			CurChar = *str++;
			len--;
			if ((CurChar & 1) == 0)		/* phrases 0..127 */
			{
				out = PrintPhrase(CurChar / 2, out, NULL);
			} else if ((CurChar & 3) == 1)	/* phrases 128..16511 */
			{
				CurChar = 128 + (CurChar / 4) * 256 + *str++;
				len--;
				out = PrintPhrase(CurChar, out, NULL);
			} else if ((CurChar & 7) == 3)	/* copy next n characters */
			{
				while (CurChar > 0)
				{
					*out++ = *str++;
					len--;
					CurChar -= 8;
				}
			} else if ((CurChar & 0x0F) == 0x07)
			{
				while (CurChar > 0)
				{
					*out++ = ' ';
					CurChar -= 16;
				}
			} else						/* if((CurChar&0x0F)==0x0F) */
			{
				while (CurChar > 0)
				{
					*out++ = '\0';
					CurChar -= 16;
				}
			}
		}
	} else
	{
		while (len)
		{
			CurChar = *str++;
			len--;
			if (CurChar > 0 && CurChar < 16)	/* phrase 0..1919 */
			{
				CurChar = 256 * (CurChar - 1) + *str++;
				len--;
				out = PrintPhrase(CurChar / 2, out, NULL);
				if (CurChar & 1)
					*out++ = ' ';
			} else
			{
				*out++ = CurChar;
			}
		}
	}
	return out;
}


/*
 * reads next chunk from |TOPIC like TopicRead, but does phrase decompression
 * if Length > NumBytes, suitable to read LinkData2. If phrase decompression
 * doesn't expands to Length bytes, buffer is padded using 0. TopicPhraseRead
 * always NUL-terminates at dest[Length] just to be save
 */
static int32_t TopicPhraseRead(FILE *HelpFile, int32_t TopicPos, char *dest, int32_t NumBytes, int32_t Length)
{
	char *buffer;
	int32_t BytesRead;
	int32_t i;

	if (Length <= NumBytes)				/* no phrase compression in this case */
	{
		BytesRead = TopicRead(HelpFile, TopicPos, dest, Length);
		for (i = BytesRead; i <= Length; i++)
			dest[i] = '\0';
		if (BytesRead == Length && Length < NumBytes)	/* some trailing bytes are not used (bug in HCRTF ?) */
		{
			buffer = (char *)my_malloc(NumBytes - Length);
			BytesRead += TopicRead(HelpFile, 0L, buffer, NumBytes - Length);
			free(buffer);
		}
	} else
	{
		buffer = (char *)my_malloc(NumBytes);
		BytesRead = TopicRead(HelpFile, TopicPos, buffer, NumBytes);
		NumBytes = PhraseReplace((unsigned char *)buffer, NumBytes, dest) - dest;
		free(buffer);
		if (NumBytes > Length)
		{
			error("Phrase replacement delivers %ld bytes instead of %ld", (long)NumBytes, (long)Length);
			exit(1);
		}
	}
	while (NumBytes <= Length)
		dest[NumBytes++] = '\0';
	return BytesRead;
}


static void Annotate(int32_t pos, FILE *rtf)
{
	int32_t FileLength;
	char FileName[19];
	int i;
	int32_t l;

	sprintf(FileName, "%ld!0", (long)pos);
	if (SearchFile(AnnoFile, FileName, &FileLength))
	{
		fputs("{\\v {\\*\\atnid ANN}\\chatn {\\*\\annotation \\pard\\plain {\\chatn }", rtf);
		for (l = 0; l < FileLength && (i = getc(AnnoFile)) != -1; l++)
		{
			if (i == 0x0D)
			{
				fputs("\\par\n", rtf);
			} else if (i != '{' && i != '}' && i != '\\' && isprint(i))
			{
				putc(i, rtf);
			} else if (i == '{')
			{
				fputs("\\{\\-", rtf);
			} else if (i != '\0' && i != 0x0A)
			{
				fprintf(rtf, "\\'%02x", i);
			}
		}
		fputs("}}", rtf);
	}
}


/*
 * collect all keywords assigned to positions starting at NextKeywordOffset
 * from all keyword lists, saving the first MAXKEYWORDS in KeywordRec
 * (which is allocated to MAXKEYWORDS if NULL) for use in ListKeywords.
 * updates NextKeywordOffset, clears NextKeywordRec, sets KeywordRecs.
 */
static void CollectKeywords(FILE *HelpFile)
{
	unsigned short j, m;
	int i, n, k, l, map;
	int32_t FileLength, savepos, KWDataOffset, from;
	int32_t *keytopic;
	BUFFER buf;
	char kwdata[10];
	char kwbtree[10];

	fputs("Collecting keywords...", stderr);
	savepos = ftell(HelpFile);
	if (KeywordRec)						/* free old keywords */
	{
		for (i = 0; i < KeywordRecs; i++)
		{
			if (KeywordRec[i].Keyword)
				free(KeywordRec[i].Keyword);
		}
	} else
	{
		KeywordRec = (KEYWORDREC *)my_malloc(MAXKEYWORDS * sizeof(KEYWORDREC));
	}
	NextKeywordRec = KeywordRecs = 0;
	from = NextKeywordOffset;
	NextKeywordOffset = 0x7FFFFFFFL;
	for (k = 0; k < 2; k++)
		for (map = '0'; map <= 'z'; map++)
		{
			if (k)
			{
				if (!keyindex[map - '0'])
					continue;
				sprintf(kwdata, "|%cKWDATA", map);
				sprintf(kwbtree, "|%cKWBTREE", map);
			} else
			{
				if (!lists[map - '0'])
					continue;
				sprintf(kwdata, "|%cWDATA", map);
				sprintf(kwbtree, "|%cWBTREE", map);
			}
			if (SearchFile(HelpFile, kwdata, &FileLength))
			{
				int32_t keytopics = (FileLength / SIZEOF_LONG);
				
				keytopic = (int32_t *)my_malloc(keytopics * sizeof(*keytopic));
				for (i = 0; i < keytopics; i++)
					keytopic[i] = getdw(HelpFile);
				if (SearchFile(HelpFile, kwbtree, NULL))
				{
					for (n = GetFirstPage(HelpFile, &buf, NULL); n; n = GetNextPage(HelpFile, &buf))
					{
						for (i = 0; i < n; i++)
						{
							my_gets(keyword, sizeof(keyword), HelpFile);
							m = my_getw(HelpFile);
							KWDataOffset = getdw(HelpFile);
							for (j = 0; j < m; j++)
							{
								if (keytopic[KWDataOffset / 4 + j] >= from)
								{
									if (KeywordRecs >= MAXKEYWORDS)
									{
										NextKeywordOffset = KeywordRec[KeywordRecs - 1].TopicOffset;
										while (KeywordRec[KeywordRecs - 1].TopicOffset == NextKeywordOffset)
										{
											KeywordRecs--;
											if (KeywordRec[KeywordRecs].Keyword)
												free(KeywordRec[KeywordRecs].Keyword);
										}
									}
									l = KeywordRecs;
									while (l > 0 && KeywordRec[l - 1].TopicOffset > keytopic[KWDataOffset / 4 + j])
									{
										KeywordRec[l].KeyIndex = KeywordRec[l - 1].KeyIndex;
										KeywordRec[l].Footnote = KeywordRec[l - 1].Footnote;
										KeywordRec[l].Keyword = KeywordRec[l - 1].Keyword;
										KeywordRec[l].TopicOffset = KeywordRec[l - 1].TopicOffset;
										l--;
									}
									KeywordRec[l].KeyIndex = k > 0;
									KeywordRec[l].Footnote = map;
									KeywordRec[l].Keyword = my_strdup(keyword);
									KeywordRec[l].TopicOffset = keytopic[KWDataOffset / 4 + j];
									KeywordRecs++;
								}
							}
						}
					}
					free(keytopic);
				}
			}
		}
	fseek(HelpFile, savepos, SEEK_SET);
	for (i = 0; i < 22; i++)
		fputs("\b \b", stderr);
}


/*
 * writes out all keywords appearing up to position TopicOffset and eats
 * them up so they are not written out again. Merges keywords if possible
 */
static void ListKeywords(FILE *HelpFile, FILE *rtf, int32_t TopicOffset)
{
	int len, footnote, keyindex;

	if (NextKeywordRec >= KeywordRecs)
	{
		if (NextKeywordOffset == 0x7FFFFFFFL)
			return;
		CollectKeywords(HelpFile);
	}
	footnote = keyindex = len = 0;
	while (NextKeywordRec < KeywordRecs && KeywordRec[NextKeywordRec].TopicOffset <= TopicOffset)
	{
		if (len > 0
			&& (KeywordRec[NextKeywordRec].Footnote != footnote || (int)KeywordRec[NextKeywordRec].KeyIndex != keyindex
				|| len + strlen(KeywordRec[NextKeywordRec].Keyword) > (after31 ? 1023 : 254)))
		{
			fputs("}\n", rtf);
			len = 0;
		}
		if (len > 0)
		{
			putc(';', rtf);
		} else if (KeywordRec[NextKeywordRec].KeyIndex)
		{
			fprintf(rtf, "{\\up K}{\\footnote\\pard\\plain{\\up K} %c:", KeywordRec[NextKeywordRec].Footnote);
		} else
		{
			fprintf(rtf, "{\\up %c}{\\footnote\\pard\\plain{\\up %c} ", KeywordRec[NextKeywordRec].Footnote,
					KeywordRec[NextKeywordRec].Footnote);
		}
		len += strlen(KeywordRec[NextKeywordRec].Keyword) + 1;
		putrtf(rtf, KeywordRec[NextKeywordRec].Keyword);
		footnote = KeywordRec[NextKeywordRec].Footnote;
		keyindex = KeywordRec[NextKeywordRec].KeyIndex;
		NextKeywordRec++;
	}
	if (len)
		fputs("}\n", rtf);
}


/*
 * create > footnote if topic at TopicOffset has a window assigned to
 * using the |VIOLA internal file. Read VIOLA sequentially, reloading
 * next page only if neccessary, because it is properly ordered.
 */
static int ListWindows(FILE *HelpFile, int32_t TopicOffset)
{
	int32_t savepos;
	static int n, i;
	static BUFFER buf;
	static int VIOLAfound = -1;
	static VIOLAREC *Viola;
	int result;

	if (VIOLAfound == 0)
		return -1;
	savepos = ftell(HelpFile);
	if (VIOLAfound == -1)
	{
		VIOLAfound = 0;
		if (SearchFile(HelpFile, "|VIOLA", NULL))
		{
			n = GetFirstPage(HelpFile, &buf, NULL);
			if (n)
			{
				Viola = (VIOLAREC *)my_malloc(n * sizeof(VIOLAREC));
				for (i = 0; i < n; i++)
					read_violarec(HelpFile, &Viola[i]);
				i = 0;
				VIOLAfound = 1;
			}
		}
	}
	result = -1;
	if (VIOLAfound == 1)
	{
		while (i >= n || TopicOffset > Viola[i].TopicOffset)
		{
			if (i >= n)
			{
				free(Viola);
				n = GetNextPage(HelpFile, &buf);
				if (n == 0)
				{
					VIOLAfound = 0;
					break;
				}
				Viola = (VIOLAREC *)my_malloc(n * sizeof(VIOLAREC));
				for (i = 0; i < n; i++)
					read_violarec(HelpFile, &Viola[i]);
				i = 0;
				i = 0;
			} else
			{
				i++;
			}
		}
		if (i < n && Viola[i].TopicOffset == TopicOffset)
		{
			result = Viola[i].WindowNumber;
		}
	}
	fseek(HelpFile, savepos, SEEK_SET);
	return result;
}


/*
 * Browse sequence handling support functions. As an efficient means to
 * resolve browse sequences, FirstPass numbers and saves all browse
 * sequence start positions (that are topics where BrowseNext and
 * BrowsePrev both point to some topic behind) and links them with the
 * following topics. Whenever a topic is merged to the beginning of a
 * browse sequence, it's start topics browse subnumber is incremented.
 * The interesting part is a topic where BrowsePrev and BrowseNext both
 * point to earlier topics. Two different browse sequences need to be
 * merged at this point, that is will get the same browse start number
 * and one start topics subnumber needs to accommodate the other browse
 * sequence. Using the start records, TopicDump does know which browse
 * sequence starts at which topic and knows the browse sequence number
 * and subnumber assigned. */
static void AddStart(int32_t StartTopic, int BrowseNum, int Count)
{
	start = (START *)my_realloc(start, (starts + 1) * sizeof(START));
	start[starts].StartTopic = StartTopic;
	start[starts].BrowseNum = BrowseNum;
	start[starts].Start = Count;
	starts++;
}


static void FixStart(int BrowseNum, int NewBrowseNum, int AddCount)
{
	int i;

	for (i = 0; i < starts; i++)
		if (start[i].BrowseNum == BrowseNum)
		{
			start[i].BrowseNum = NewBrowseNum;
			start[i].Start += AddCount;
		}
}


static void AddBrowse(int32_t StartTopic, int32_t NextTopic, int32_t PrevTopic)
{
	int i;

	for (i = 0; i < browses; i++)
		if (browse[i].StartTopic == -1L)
			break;						/* empty space in array ? */
	if (i == browses)					/* no empty space, add to array */
	{
		browse = (BROWSE *)my_realloc(browse, ++browses * sizeof(BROWSE));
	}
	browse[i].StartTopic = StartTopic;
	browse[i].NextTopic = NextTopic;
	browse[i].PrevTopic = PrevTopic;
	browse[i].BrowseNum = browsenums++;
	browse[i].Start = 1;
	browse[i].Count = 1;
}


static void MergeBrowse(int32_t TopicOffset, int32_t OtherTopicOffset, int32_t NextTopic, int32_t PrevTopic)
{
	int i, j;

	for (i = 0; i < browses; i++)
		if (browse[i].StartTopic != -1L)
		{
			if (browse[i].NextTopic == TopicOffset || browse[i].NextTopic == OtherTopicOffset)
				break;
		}
	for (j = 0; j < browses; j++)
		if (browse[j].StartTopic != -1L)
		{
			if (browse[j].PrevTopic == TopicOffset || browse[j].PrevTopic == OtherTopicOffset)
				break;
		}
	if (i < browses && j < browses)
	{
		browse[i].Count++;
		browse[i].NextTopic = browse[j].NextTopic;
		FixStart(browse[j].BrowseNum, browse[i].BrowseNum, browse[i].Count);
		browse[j].Start += browse[i].Count;
		AddStart(browse[j].StartTopic, browse[i].BrowseNum, browse[j].Start);
		browse[i].Count += browse[j].Count;
		browse[j].StartTopic = -1L;
		if (browse[i].NextTopic == -1L && browse[i].PrevTopic == -1L)
		{
			AddStart(browse[i].StartTopic, browse[i].BrowseNum, browse[i].Start);
			browse[i].StartTopic = -1L;
		}
	} else
	{
		warnings = TRUE;
		fprintf(stderr, "Can not merge %08lx %08lx %08lx\n", (long)TopicOffset, (long)NextTopic, (long)PrevTopic);
	}
}


static void LinkBrowse(int32_t TopicOffset, int32_t OtherTopicOffset, int32_t NextTopic, int32_t PrevTopic)
{
	int i;

	for (i = 0; i < browses; i++)
		if (browse[i].StartTopic != -1L)
		{
			if (browse[i].NextTopic == TopicOffset || browse[i].NextTopic == OtherTopicOffset)
				break;
		}
	if (i < browses)
	{
		browse[i].NextTopic = NextTopic;
		browse[i].Count++;
		if (browse[i].NextTopic == -1L && browse[i].PrevTopic == -1L)
		{
			AddStart(browse[i].StartTopic, browse[i].BrowseNum, browse[i].Start);
			browse[i].StartTopic = -1L;
		}
	} else
	{
		warnings = TRUE;
		fprintf(stderr, "Can not link %08lx %08lx %08lx\n", (long)TopicOffset, (long)NextTopic, (long)PrevTopic);
		for (i = 0; i < browses; i++)
			if (browse[i].StartTopic != -1L)
			{
				fprintf(stderr, "Open browse %08lx %08lx\n", (long)browse[i].PrevTopic, (long)browse[i].NextTopic);
			}
	}
}


static void BackLinkBrowse(int32_t TopicOffset, int32_t OtherTopicOffset, int32_t NextTopic, int32_t PrevTopic)
{
	int i;

	for (i = 0; i < browses; i++)
		if (browse[i].StartTopic != -1L)
		{
			if (browse[i].PrevTopic == TopicOffset || browse[i].PrevTopic == OtherTopicOffset)
				break;
		}
	if (i < browses)
	{
		browse[i].PrevTopic = PrevTopic;
		browse[i].Count++;
		browse[i].Start++;
		FixStart(browse[i].BrowseNum, browse[i].BrowseNum, 1);
		if (browse[i].NextTopic == -1L && browse[i].PrevTopic == -1L)
		{
			AddStart(browse[i].StartTopic, browse[i].BrowseNum, browse[i].Start);
			browse[i].StartTopic = -1L;
		}
	} else
	{
		warnings = TRUE;
		fprintf(stderr, "Can not backlink %08lx %08lx %08lx\n", (long)TopicOffset, (long)NextTopic, (long)PrevTopic);
	}
}


static uint32_t AddLink(int32_t StartTopic, int32_t NextTopic, int32_t PrevTopic)
{
	int i, j;
	uint32_t result;

	result = 0L;
	for (i = 0; i < browses; i++)
		if (browse[i].StartTopic == -1L)
			break;
	if (i == browses)
		browse = (BROWSE *)my_realloc(browse, ++browses * sizeof(BROWSE));
	for (j = 0; j < starts; j++)
		if (start[j].StartTopic == StartTopic)
			break;
	if (j < starts)
	{
		browse[i].StartTopic = start[j].StartTopic;
		browse[i].BrowseNum = start[j].BrowseNum;
		browse[i].Start = start[j].Start;
		browse[i].Count = start[j].Start;
		browse[i].NextTopic = NextTopic;
		browse[i].PrevTopic = PrevTopic;
		result = browse[i].BrowseNum + ((int32_t) browse[i].Start << 16);
	} else
	{
		warnings = TRUE;
		fprintf(stderr, "Browse start %08lx not found\n", (long)StartTopic);
	}
	return result;
}


static uint32_t MergeLink(int32_t TopicOffset, int32_t OtherTopicOffset, int32_t NextTopic, int32_t PrevTopic)
{
	int i, j;

	uint32_t result;

	result = 0L;
	for (i = 0; i < browses; i++)
		if (browse[i].StartTopic != -1L)
		{
			if (browse[i].NextTopic == TopicOffset || browse[i].NextTopic == OtherTopicOffset)
				break;
		}
	for (j = 0; j < browses; j++)
		if (browse[j].StartTopic != -1L)
		{
			if (browse[j].PrevTopic == TopicOffset || browse[j].PrevTopic == OtherTopicOffset)
				break;
		}
	if (i < browses && j < browses)
	{
		browse[i].Count++;
		browse[j].Start--;
		if (browse[i].Count != browse[j].Start)
		{
			warnings = TRUE;
			fprintf(stderr, "Prev browse end %d doen't match next browse start %d\n", browse[i].Count, browse[j].Start);
		}
		result = browse[i].BrowseNum + ((int32_t) browse[i].Count << 16);
		browse[i].NextTopic = browse[j].NextTopic;
		browse[i].Count = browse[j].Count;
		browse[j].StartTopic = -1L;
	} else
	{
		warnings = TRUE;
		fprintf(stderr, "Can not merge %08lx %08lx %08lx\n", (long)TopicOffset, (long)NextTopic, (long)PrevTopic);
	}
	return result;
}


static uint32_t LinkLink(int32_t TopicOffset, int32_t OtherTopicOffset, int32_t NextTopic, int32_t PrevTopic)
{
	int i;

	uint32_t result;

	result = 0L;
	for (i = 0; i < browses; i++)
		if (browse[i].StartTopic != -1L)
		{
			if (browse[i].NextTopic == TopicOffset || browse[i].NextTopic == OtherTopicOffset)
				break;
		}
	if (i < browses)
	{
		browse[i].NextTopic = NextTopic;
		browse[i].Count++;
		result = browse[i].BrowseNum + ((int32_t) browse[i].Count << 16);
		if (browse[i].NextTopic == -1L && browse[i].PrevTopic == -1L)
		{
			browse[i].StartTopic = -1L;
		}
	} else
	{
		warnings = TRUE;
		fprintf(stderr, "Can not link %08lx %08lx %08lx\n", (long)TopicOffset, (long)NextTopic, (long)PrevTopic);
	}
	return result;
}


static uint32_t BackLinkLink(int32_t TopicOffset, int32_t OtherTopicOffset, int32_t NextTopic, int32_t PrevTopic)
{
	int i;

	uint32_t result;

	result = 0L;
	for (i = 0; i < browses; i++)
		if (browse[i].StartTopic != -1L)
		{
			if (browse[i].PrevTopic == TopicOffset || browse[i].PrevTopic == OtherTopicOffset)
				break;
		}
	if (i < browses)
	{
		browse[i].PrevTopic = PrevTopic;
		browse[i].Start--;
		result = browse[i].BrowseNum + ((int32_t) browse[i].Start << 16);
		if (browse[i].NextTopic == -1L && browse[i].PrevTopic == -1L)
		{
			browse[i].StartTopic = -1L;
		}
	} else
	{
		warnings = TRUE;
		fprintf(stderr, "Can not backlink %08lx %08lx %08lx\n", (long)TopicOffset, (long)NextTopic, (long)PrevTopic);
	}
	return result;
}


/* create numbered rtf file names, no numbering if i=0 */
static void BuildName(char *buffer, int i)
{
	char num[7];

	strcpy(buffer, name);
	if (i)
	{
		snprintf(num, 7, "%d", i);
		if (strlen(buffer) + strlen(num) > 8)
		{
			buffer[8 - strlen(num)] = '\0';
		}
		strcat(buffer, num);
	}
	strcat(buffer, ".rtf");
}


/* emit rtf commands to change to font i.
 * ul forces underline on, uldb forces doubleunderline on */
static void ChangeFont(FILE *rtf, unsigned int i, BOOL ul, BOOL uldb)
{
	FONTDESCRIPTOR *f;
	int32_t pos;

	if (i < fonts)
	{
		pos = ftell(rtf);
		f = font + i;
		if (f->style)
		{
			fprintf(rtf, "\\plain\\cs%d", f->style + 9);
			if (uldb)
				fputs("\\uldb", rtf);
			else if (ul)
				fputs("\\ul", rtf);
		} else
		{
			/* HC30 can't reset, so reset using \plain */
			if ((CurrentFont.Bold && !f->Bold)
				|| (CurrentFont.Italic && !f->Italic)
				|| (CurrentFont.Underline && !(!uldb && (ul || f->Underline)))
				|| (CurrentFont.StrikeOut && !f->StrikeOut)
				|| (CurrentFont.DoubleUnderline && !(uldb || f->DoubleUnderline))
				|| (CurrentFont.SmallCaps && !f->SmallCaps)
				|| (CurrentFont.FontName && !f->FontName)
				|| (CurrentFont.textcolor && !f->textcolor)
				|| (CurrentFont.backcolor && !f->backcolor)
				|| (CurrentFont.up && !f->up) || (CurrentFont.style && !f->style))
			{
				fputs("\\plain", rtf);
				memset(&CurrentFont, 0, sizeof(CurrentFont));
				CurrentFont.FontName = DefFont;
			}
			if (f->FontName != CurrentFont.FontName)
				fprintf(rtf, "\\f%d", f->FontName);
			if (f->Italic && !CurrentFont.Italic)
				fputs("\\i", rtf);
			if (f->Bold && !CurrentFont.Bold)
				fputs("\\b", rtf);
			if (!uldb && (ul || f->Underline) && !CurrentFont.Bold)
				fputs("\\ul", rtf);
			if (f->StrikeOut && !CurrentFont.StrikeOut)
				fputs("\\strike", rtf);
			if ((uldb || f->DoubleUnderline) && !CurrentFont.DoubleUnderline)
				fputs("\\uldb", rtf);
			if (f->SmallCaps && !CurrentFont.SmallCaps)
				fputs("\\scaps", rtf);
			if (f->expndtw != CurrentFont.expndtw)
				fprintf(rtf, "\\expndtw%d", f->expndtw);
			if (f->up != CurrentFont.up)
			{
				if (f->up > 0)
					fprintf(rtf, "\\up%d", f->up);
				else if (f->up < 0)
					fprintf(rtf, "\\dn%d", -f->up);
			}
			if (f->HalfPoints != CurrentFont.HalfPoints)
				fprintf(rtf, "\\fs%d", f->HalfPoints);
			if (f->textcolor != CurrentFont.textcolor)
				fprintf(rtf, "\\cf%d", f->textcolor);
			if (f->backcolor != CurrentFont.backcolor)
				fprintf(rtf, "\\cb%d", f->backcolor);
		}
		memcpy(&CurrentFont, f, sizeof(CurrentFont));
		if (ul)
			CurrentFont.Underline = 1;
		if (uldb)
		{
			CurrentFont.Underline = 0;
			CurrentFont.DoubleUnderline = 1;
		}
		if (ftell(rtf) != pos)
			putc(' ', rtf);
	}
}


/* list all groups the topic TopicNum is assigned to and/or emit footnote
 * for browse sequence of this topic as + footnote into rtf file */
static void ListGroups(FILE *rtf, uint32_t TopicNum, uint32_t BrowseNum)
{
	int i;
	BOOL grouplisted;

	grouplisted = FALSE;
	for (i = 0; i < groups; i++)
		if (group[i].GroupHeader.GroupType == 1 || group[i].GroupHeader.GroupType == 2)
		{
			if ((TopicNum >= group[i].GroupHeader.FirstTopic && TopicNum <= group[i].GroupHeader.LastTopic)
				&& ((group[i].GroupHeader.GroupType == 1 || group[i].GroupHeader.GroupType == 2)
					&& (group[i].Bitmap[TopicNum >> 3] & (1 << (TopicNum & 7)))))
			{
				if (!grouplisted)
				{
					fputs("{\\up +}{\\footnote\\pard\\plain{\\up +} ", rtf);
					if (BrowseNum)
						fprintf(rtf, "BROWSE%04x:%04x", (unsigned short) BrowseNum, (unsigned short) (BrowseNum >> 16));
					grouplisted = TRUE;
				}
				fprintf(rtf, ";%s", group[i].Name);
			}
		}
	if (grouplisted)
	{
		fputs("}\n", rtf);
	} else if (BrowseNum)
	{
		fprintf(rtf, "{\\up +}{\\footnote\\pard\\plain{\\up +} BROWSE%04x:%04x}\n", (unsigned short) BrowseNum,
				(unsigned short) (BrowseNum >> 16));
	}
}


/* advances TopicOffset to next block in |TOPIC if setting of TopicPos to
 * NextBlock crosses TOPICBLOCKHEADER */
static TOPICOFFSET NextTopicOffset(TOPICOFFSET TopicOffset, TOPICPOS NextBlock, TOPICPOS TopicPos)
{
	/*
	 * it should never be neccessary to subtract sizeof(TOPICBLOCKHEADER), as no
	 * TOPICLINK may start in the last (12..21) bytes, but just to make shure...
	 */
	if ((NextBlock - SIZEOF_TOPICBLOCKHEADER) / DecompressSize !=
		(TopicPos - SIZEOF_TOPICBLOCKHEADER) / DecompressSize)
	{
		return ((NextBlock - SIZEOF_TOPICBLOCKHEADER) / DecompressSize) * 0x8000L;
	}
	return TopicOffset;
}


/* TopicDump: converts the internal |TOPIC file to RTF format suitable for
 * recompilation inserting footnotes with information from other internal
 * files as required */
static FILE *TopicDump(FILE *HelpFile, FILE *rtf, FILE *hpj, BOOL makertf)
{
	TOPICLINK TopicLink;
	unsigned char topiclinkbuf[SIZEOF_TOPICLINK];
	char *LinkData1;					/* Data associated with this link */
	int32_t nonscroll = -1L;
	char *LinkData2;					/* Second set of data */
	int fontset, i;
	int NextContextRec;
	uint32_t BrowseNum;
	char *hotspot;
	const char *arg;
	BOOL firsttopic = TRUE;
	BOOL ul, uldb;
	unsigned int nextbitmap;
	int TopicInRTF, NumberOfRTF;
	int32_t TopicNum, TopicOffset, TopicPos;
	int col, cols;
	unsigned short lastcol;
	const short *iptr;
	unsigned short x1, x2, x3;
	short y1;
	int32_t l1 = 0;
	const char *ptr;
	const char *cmd;
	char *str;
	int32_t ActualTopicOffset = 0, MaxTopicOffset = 0;
	TOPICHEADER30 TopicHdr30;
	TOPICHEADER TopicHdr;
	int32_t BogusTopicOffset;

	if (SearchFile(HelpFile, "|TOPIC", &TopicFileLength))
	{
		fontset = -1;
		nextbitmap = 1;
		if (browse)
			free(browse);
		browse = NULL;
		browses = 0;
		NextContextRec = 0;
		ul = uldb = FALSE;
		hotspot = NULL;
		TopicOffset = 0L;
		TopicPos = 12L;
		TopicNum = 16;
		TopicInRTF = 0;
		NumberOfRTF = 1;
		while (TopicRead(HelpFile, TopicPos, topiclinkbuf, SIZEOF_TOPICLINK) == SIZEOF_TOPICLINK)
		{
			fetchtopiclink(&TopicLink, topiclinkbuf);
			if (before31)
			{
				if (TopicPos + TopicLink.NextBlock >= TopicFileLength)
					break;
			} else
			{
				if (TopicLink.NextBlock <= 0)
					break;
			}
			if (TopicLink.DataLen1 > SIZEOF_TOPICLINK)
			{
				LinkData1 = (char *)my_malloc(TopicLink.DataLen1 - SIZEOF_TOPICLINK + 1);
				if (TopicRead(HelpFile, 0L, LinkData1, TopicLink.DataLen1 - SIZEOF_TOPICLINK) !=
					TopicLink.DataLen1 - SIZEOF_TOPICLINK)
					break;
			} else
				LinkData1 = NULL;
			if (TopicLink.DataLen1 < TopicLink.BlockSize)	/* read LinkData2 using phrase replacement */
			{
				LinkData2 = (char *)my_malloc(TopicLink.DataLen2 + 1);
				if (TopicPhraseRead(HelpFile, 0L, LinkData2, TopicLink.BlockSize - TopicLink.DataLen1, TopicLink.DataLen2) != TopicLink.BlockSize - TopicLink.DataLen1)
					break;
			} else
				LinkData2 = NULL;
			if (LinkData1 && TopicLink.RecordType == TL_TOPICHDR)	/* display a Topic Header record */
			{
				if (TopicsPerRTF && ++TopicInRTF >= TopicsPerRTF)
				{
					putc('}', rtf);
					my_fclose(rtf);
					BuildName(buffer, ++NumberOfRTF);
					if (hpj)
						fprintf(hpj, "%s\n", buffer);
					rtf = my_fopen(buffer, "wt");
					FontLoad(HelpFile, rtf, NULL);
					TopicInRTF = 0;
				} else if (!firsttopic)
				{
					if (makertf && nopagebreak)
					{
						fputs("\\par\n", rtf);
					} else
					{
						fputs("\\page\n", rtf);
					}
				}
				firsttopic = FALSE;
				fprintf(stderr, "\rTopic %ld...", (long)TopicNum - 15);
				if (!makertf)
				{
					BrowseNum = 0L;
					if (before31)
					{
						fetch_topicheader30(&TopicHdr30, LinkData1);
						fprintf(rtf, "{\\up #}{\\footnote\\pard\\plain{\\up #} TOPIC%ld}\n", (long)TopicNum);
						if (resolvebrowse)
						{
							if ((TopicHdr30.NextTopicNum > TopicNum && TopicHdr30.PrevTopicNum > TopicNum)
								|| (TopicHdr30.NextTopicNum == -1 && TopicHdr30.PrevTopicNum > TopicNum)
								|| (TopicHdr30.NextTopicNum > TopicNum && TopicHdr30.PrevTopicNum == -1))
							{
								BrowseNum = AddLink(TopicNum, TopicHdr30.NextTopicNum, TopicHdr30.PrevTopicNum);
							} else if (TopicHdr30.NextTopicNum != -1 && TopicHdr30.NextTopicNum < TopicNum
									   && TopicHdr30.PrevTopicNum != -1 && TopicHdr30.PrevTopicNum < TopicNum)
							{
								BrowseNum =
									MergeLink(TopicNum, TopicNum, TopicHdr30.NextTopicNum, TopicHdr30.PrevTopicNum);
							} else if (TopicHdr30.NextTopicNum != -1 && TopicHdr30.NextTopicNum < TopicNum
									   && (TopicHdr30.PrevTopicNum == -1 || TopicHdr30.PrevTopicNum > TopicNum))
							{
								BrowseNum =
									BackLinkLink(TopicNum, TopicNum, TopicHdr30.NextTopicNum,
												 TopicHdr30.PrevTopicNum);
							} else if (TopicHdr30.PrevTopicNum != -1 && TopicHdr30.PrevTopicNum < TopicNum
									   && (TopicHdr30.NextTopicNum == -1 || TopicHdr30.NextTopicNum > TopicNum))
							{
								BrowseNum =
									LinkLink(TopicNum, TopicNum, TopicHdr30.NextTopicNum, TopicHdr30.PrevTopicNum);
							}
						}
						ListKeywords(HelpFile, rtf, TopicPos);
					} else
					{
						BogusTopicOffset = NextTopicOffset(TopicOffset, TopicLink.NextBlock, TopicPos);
						fetch_topicheader(&TopicHdr, LinkData1);
						if (TopicHdr.Scroll != -1L)
						{
							nonscroll = TopicHdr.Scroll;
						} else
						{
							nonscroll = TopicHdr.NextTopic;
						}
						if (resolvebrowse)
						{
							if ((TopicHdr.BrowseFor > TopicOffset && TopicHdr.BrowseBck > TopicOffset)
								|| (TopicHdr.BrowseFor == -1L && TopicHdr.BrowseBck > TopicOffset)
								|| (TopicHdr.BrowseFor > TopicOffset && TopicHdr.BrowseBck == -1L))
							{
								BrowseNum = AddLink(TopicOffset, TopicHdr.BrowseFor, TopicHdr.BrowseBck);
							} else if (TopicHdr.BrowseFor != -1L && TopicHdr.BrowseFor < TopicOffset
									   && TopicHdr.BrowseBck != -1L && TopicHdr.BrowseBck < TopicOffset)
							{
								BrowseNum =
									MergeLink(TopicOffset, BogusTopicOffset, TopicHdr.BrowseFor, TopicHdr.BrowseBck);
							} else if (TopicHdr.BrowseFor != -1L && TopicHdr.BrowseFor < TopicOffset
									   && (TopicHdr.BrowseBck == -1L || TopicHdr.BrowseBck > TopicOffset))
							{
								BrowseNum =
									BackLinkLink(TopicOffset, BogusTopicOffset, TopicHdr.BrowseFor,
												 TopicHdr.BrowseBck);
							} else if (TopicHdr.BrowseBck != -1L && TopicHdr.BrowseBck < TopicOffset
									   && (TopicHdr.BrowseFor == -1L || TopicHdr.BrowseFor > TopicOffset))
							{
								BrowseNum =
									LinkLink(TopicOffset, BogusTopicOffset, TopicHdr.BrowseFor, TopicHdr.BrowseBck);
							}
						}
					}
					ListGroups(rtf, TopicNum - 16, BrowseNum);
					if (LinkData2 && TopicLink.DataLen2 > 0)
					{
						if (*LinkData2)
						{
							fputs("{\\up $}{\\footnote\\pard\\plain{\\up $} ", rtf);
							putrtf(rtf, LinkData2);
							fputs("}\n", rtf);
						}
						for (i = strlen(LinkData2) + 1; i < TopicLink.DataLen2; i += strlen(LinkData2 + i) + 1)
						{
							fputs("{\\up !}{\\footnote\\pard\\plain{\\up !} ", rtf);
							if (!after31 && strlen(LinkData2 + i) > 254)
							{
								printf
									("Help compiler will issue Warning 3511: Macro '%s' exceeds limit of 254 characters\n",
									 LinkData2 + i);
							}
							putrtf(rtf, LinkData2 + i);
							fputs("}\n", rtf);
						}
					}
					while (NextContextRec < ContextRecs && ContextRec[NextContextRec].TopicOffset <= TopicOffset)
					{
						fputs("{\\up #}{\\footnote\\pard\\plain{\\up #} ", rtf);
						putrtf(rtf, unhash(ContextRec[NextContextRec].HashValue));
						fputs("}\n", rtf);
						if (!mvp)
							while (NextContextRec + 1 < ContextRecs
								   && ContextRec[NextContextRec].TopicOffset ==
								   ContextRec[NextContextRec + 1].TopicOffset)
							{
								NextContextRec++;
							}
						NextContextRec++;
					}
					i = ListWindows(HelpFile, TopicOffset);
					if (i != -1)
						fprintf(rtf, "{\\up >}{\\footnote\\pard\\plain{\\up >} %s}\n", GetWindowName(i));
				}
				TopicNum++;
			} else if (LinkData1 && LinkData2
					   && (TopicLink.RecordType == TL_DISPLAY30 || TopicLink.RecordType == TL_DISPLAY
						   || TopicLink.RecordType == TL_TABLE))
			{
				if (AnnoFile)
					Annotate(TopicPos, rtf);
				ptr = LinkData1;
				scanlong(&ptr);
				if (TopicLink.RecordType == TL_DISPLAY || TopicLink.RecordType == TL_TABLE)
				{
					x1 = scanword(&ptr);
					ActualTopicOffset = TopicOffset;
					MaxTopicOffset = ActualTopicOffset + x1;
					TopicOffset += x1;
				}
				if (TopicLink.RecordType == TL_TABLE)
				{
					fputs("\\trowd", rtf);
					cols = (unsigned char) *ptr++;
					x1 = (unsigned char) *ptr++;
					switch (x1)
					{
					case 0:
					case 2:
						l1 = fetchword(ptr);	/* min table width */
						ptr += SIZEOF_SHORT;
						fputs("\\trqc", rtf);
						break;
					case 1:
					case 3:
						l1 = 32767L;
						break;
					}
					iptr = (const short *) ptr;
					if (cols > 1)
					{
						x1 = fetchword(iptr) + fetchword(iptr + 1) + fetchword(iptr + 3) / 2;
						fprintf(rtf, "\\trgaph%ld\\trleft%ld \\cellx%ld\\cellx%ld",
								((fetchword(iptr + 3) * scaling - rounderr) * l1) / 32767L,
								(((fetchword(iptr + 1) - fetchword(iptr + 3)) * scaling - rounderr) * l1 - 32767) / 32767L,
								((x1 * scaling - rounderr) * l1) / 32767L,
								(((x1 + fetchword(iptr + 2) + fetchword(iptr + 3)) * scaling - rounderr) * l1) / 32767L);
						x1 += fetchword(iptr + 2) + fetchword(iptr + 3);
						for (col = 2; col < cols; col++)
						{
							x1 += fetchword(iptr + 2 * col) + fetchword(iptr + 2 * col + 1);
							fprintf(rtf, "\\cellx%ld", ((x1 * scaling - rounderr) * l1) / 32767L);
						}
					} else
					{
						fprintf(rtf, "\\trleft%ld \\cellx%ld ", ((fetchword(iptr + 1) * scaling - rounderr) * l1 - 32767) / 32767L,
								((fetchword(iptr + 0) * scaling - rounderr) * l1) / 32767L);
					}
					ptr = (const char *) (iptr + 2 * cols);
				}
				lastcol = 0xffff;
				str = LinkData2;
				for (col = 0;
					 (TopicLink.RecordType == TL_TABLE ? fetchword(ptr) != 0xffff : col == 0)
					 && ptr < LinkData1 + TopicLink.DataLen1 - SIZEOF_TOPICLINK; col++)
				{
					fputs("\\pard", rtf);
					if (TopicPos < nonscroll)
						fputs("\\keepn", rtf);
					if (TopicLink.RecordType == TL_TABLE)
					{
						fputs("\\intbl", rtf);
						lastcol = fetchword(ptr);
						ptr += 5;
					}
					ptr += 4;
					x2 = fetchword(ptr);
					ptr += SIZEOF_SHORT;
					if (x2 & 0x1000)
						fputs("\\keep", rtf);
					if (x2 & 0x0400)
						fputs("\\qr", rtf);
					if (x2 & 0x0800)
						fputs("\\qc", rtf);
					if (x2 & 0x0001)
						scanlong(&ptr);
					if (x2 & 0x0002)
						fprintf(rtf, "\\sb%ld", (long)scanint(&ptr) * scaling - rounderr);
					if (x2 & 0x0004)
						fprintf(rtf, "\\sa%ld", (long)scanint(&ptr) * scaling - rounderr);
					if (x2 & 0x0008)
						fprintf(rtf, "\\sl%ld", (long)scanint(&ptr) * scaling - rounderr);
					if (x2 & 0x0010)
						fprintf(rtf, "\\li%ld", (long)scanint(&ptr) * scaling - rounderr);
					if (x2 & 0x0020)
						fprintf(rtf, "\\ri%ld", (long)scanint(&ptr) * scaling - rounderr);
					if (x2 & 0x0040)
						fprintf(rtf, "\\fi%ld", (long)scanint(&ptr) * scaling - rounderr);
					if (x2 & 0x0100)
					{
						x1 = (unsigned char) *ptr++;
						if (x1 & 1)
							fputs("\\box", rtf);
						if (x1 & 2)
							fputs("\\brdrt", rtf);
						if (x1 & 4)
							fputs("\\brdrl", rtf);
						if (x1 & 8)
							fputs("\\brdrb", rtf);
						if (x1 & 0x10)
							fputs("\\brdrr", rtf);
						if (x1 & 0x20)
							fputs("\\brdrth", rtf);
						else
							fputs("\\brdrs", rtf);
						if (x1 & 0x40)
							fputs("\\brdrdb", rtf);
						ptr += 2;
					}
					if (x2 & 0x0200)
					{
						y1 = scanint(&ptr);
						while (y1-- > 0)
						{
							x1 = scanword(&ptr);
							if (x1 & 0x4000)
							{
								switch (scanword(&ptr))
								{
								case 1:
									fputs("\\tqr", rtf);
									break;
								case 2:
									fputs("\\tqc", rtf);
									break;
								}
							}
							fprintf(rtf, "\\tx%ld", (long)(x1 & 0x3FFF) * scaling - rounderr);
						}
					}
					putc(' ', rtf);
					while (1)			/* ptr<LinkData1+TopicLink.DataLen1 - SIZEOF_TOPICLINK &&str<end) */
					{
						if (*str && fontset >= 0 && (unsigned)fontset < fonts && font && font[fontset].SmallCaps)
							my_strlwr(str);
						do
						{
							if (!makertf)
							{
								while (NextContextRec < ContextRecs
									   && ContextRec[NextContextRec].TopicOffset <= ActualTopicOffset
									   && ContextRec[NextContextRec].TopicOffset < MaxTopicOffset)
								{
									fputs("{\\up #}{\\footnote\\pard\\plain{\\up #} ", rtf);
									putrtf(rtf, unhash(ContextRec[NextContextRec].HashValue));
									fputs("}\n", rtf);
									if (!mvp)
										while (NextContextRec + 1 < ContextRecs
											   && ContextRec[NextContextRec].TopicOffset ==
											   ContextRec[NextContextRec + 1].TopicOffset)
										{
											NextContextRec++;
										}
									NextContextRec++;
								}
								if (!before31)
									ListKeywords(HelpFile, rtf,
												 ActualTopicOffset <
												 MaxTopicOffset ? ActualTopicOffset : MaxTopicOffset - 1);
							}
							if (*str)
							{
								if (*str != '{' && *str != '}' && *str != '\\' && isprint((unsigned char) *str))
								{
									putc(*str, rtf);
								} else if (!makertf && *str == '{')
								{
									/* emit invisible dash after { brace */
									fputs("\\{\\-", rtf);
									/* because bmc or another legal command may follow, but this */
									/* command was not parsed the help file was build, so it was */
									/* used just as an example. The dash will be eaten up by the */
									/* help compiler on recompile. */
								} else
								{
									fprintf(rtf, "\\'%02x", (unsigned char) *str);
								}
							}
							if (ActualTopicOffset < MaxTopicOffset)
								ActualTopicOffset++;
						} while (*str++);
						if ((unsigned char) ptr[0] == 0xFF)
						{
							ptr++;
							break;
						} else
							switch ((unsigned char) ptr[0])
							{
							case 0x20:	/* vfld MVB */
								if (fetchlong(ptr + 1) != 0)
								{
									fprintf(rtf, "\\{vfld%ld\\}", (long)fetchlong(ptr + 1));
								} else
								{
									fputs("\\{vfld\\}", rtf);
								}
								ptr += 5;
								break;
							case 0x21:	/* dtype MVB */
								if (fetchword(ptr + 1) != 0)
								{
									fprintf(rtf, "\\{dtype%d\\}", fetchword(ptr + 1));
								} else
								{
									fputs("\\{dtype\\}", rtf);
								}
								ptr += 3;
								break;
							case 0x80:	/* font change */
								fontset = fetchword(ptr + 1);
								ChangeFont(rtf, fontset, ul, uldb);
								ptr += 3;
								break;
							case 0x81:
								fputs("\\line\n", rtf);
								ptr++;
								break;
							case 0x82:
								if (TopicLink.RecordType == TL_TABLE)
								{
									if ((unsigned char) ptr[1] != 0xFF)
									{
										fputs("\n\\par\\intbl ", rtf);
									} else if (fetchword(ptr + 2) == 0xffff)
									{
										fputs("\\cell\\intbl\\row\n", rtf);
									} else if (fetchword(ptr + 2) == lastcol)
									{
										fputs("\\par\\pard ", rtf);
									} else
									{
										fputs("\\cell\\pard ", rtf);
									}
								} else
								{
									fputs("\n\\par ", rtf);
								}
								ptr++;
								break;
							case 0x83:
								fputs("\\tab ", rtf);
								ptr++;
								break;
							case 0x86:
								x3 = (unsigned char) *ptr++;
								x1 = *ptr++;
								if (x1 == 0x05)
									cmd = "ewc";
								else
									cmd = "bmc";
								goto picture;
							case 0x87:
								x3 = (unsigned char) *ptr++;
								x1 = *ptr++;
								if (x1 == 0x05)
									cmd = "ewl";
								else
									cmd = "bml";
								goto picture;
							case 0x88:
								x3 = (unsigned char) *ptr++;
								x1 = *ptr++;
								if (x1 == 0x05)
									cmd = "ewr";
								else
									cmd = "bmr";
								goto picture;
							  picture:
								l1 = scanlong(&ptr);
								switch (x1)
								{
								case 0x22:	/* HC31 */
									ActualTopicOffset += scanword(&ptr);	/* number of hotspots in picture */
									if (ActualTopicOffset > MaxTopicOffset)
										ActualTopicOffset = MaxTopicOffset;
									/* fall thru */
								case 0x03:	/* HC30 */
									x1 = fetchword(ptr);
									switch (x1)
									{
									case 1:
										while (nextbitmap < extensions && extension[nextbitmap] < 0x10)
											nextbitmap++;
										if (nextbitmap >= extensions)
										{
											error("Bitmap never saved");
											break;
										}
										x2 = nextbitmap++;
										goto other;
									case 0:
										x2 = fetchword(ptr + 2);
									  other:
										if (makertf)
										{
											switch (x3)
											{
											case 0x86:
												fprintf(rtf, "{\\field {\\*\\fldinst import %s}}", getbitmapname(x2));
												break;
											case 0x87:
												fprintf(rtf, "{\\pvpara {\\field {\\*\\fldinst import %s}}\\par}\n",
														getbitmapname(x2));
												break;
											case 0x88:
												fprintf(rtf,
														"{\\pvpara\\posxr{\\field {\\*\\fldinst import %s}}\\par}\n",
														getbitmapname(x2));
												break;
											}
										} else
										{
											if (x2 < extensions && (extension[x2] & 0x20))
											{
												if (strcmp(cmd, "bmc") == 0)
													cmd = "bmct";
												else if (strcmp(cmd, "bml") == 0)
													cmd = "bmlt";
												else if (strcmp(cmd, "bmr") == 0)
													cmd = "bmrt";
											}
											fprintf(rtf, "\\{%s %s\\}", cmd, getbitmapname(x2));
										}
										break;
									}
									break;
								case 0x05:	/* ewc,ewl,ewr */
									if (ptr[6] == '!')
									{
										fprintf(rtf, "\\{button %s\\}", ptr + 7);
									} else if (ptr[6] == '*')
									{
										const char *plus;
										int n, c1, c2;

										sscanf(ptr + 7, "%d,%d,%n", &c1, &c2, &n);
										plus = strchr(ptr + 7 + n, '+');
										if ((c1 & 0xFFF5) != 0x8400)
											fprintf(stderr, "mci c1=%04x\n", c1);
										fputs("\\{mci", rtf);
										if (cmd[2] == 'r')
											fputs("_right", rtf);
										if (cmd[2] == 'l')
											fputs("_left", rtf);
										if (c2 == 1)
											fputs(" REPEAT", rtf);
										if (c2 == 2)
											fputs(" PLAY", rtf);
										if (!plus)
											fputs(" EXTERNAL", rtf);
										if (c1 & 8)
											fputs(" NOMENU", rtf);
										if (c1 & 2)
											fputs(" NOPLAYBAR", rtf);
										fprintf(rtf, ",%s\\}\n", plus ? plus + 1 : ptr + 7 + n);
									} else
									{
										fprintf(rtf, "\\{%s %s\\}", cmd, ptr + 6);
									}
									break;
								}
								ptr += l1;
								break;
							case 0x89:	/* end of hotspot */
								if (!makertf)
								{
									if (hotspot[0] == '%' && fontset >= 0 && (unsigned)fontset < fonts && font[fontset].Underline)
									{
										hotspot[0] = '*';
									}
								}
								ChangeFont(rtf, fontset, ul = FALSE, uldb = FALSE);
								if (!makertf)
								{
									if (!after31 && strlen(hotspot) > 255)
									{
										puts("Help compiler will issue Warning 4072: Context string exceeds limit of 255 characters");
									}
									fputs("{\\v ", rtf);
									putrtf(rtf, multi
										   && (hotspot[0] == '%' || hotspot[0] == '*') ? hotspot + 1 : hotspot);
									fputc('}', rtf);
								}
								ptr++;
								break;
							case 0xC8:	/* macro */
								ChangeFont(rtf, fontset, FALSE, uldb = TRUE);
								if (!makertf)
								{
									hotspot = (char *)my_realloc(hotspot, strlen(ptr + 3) + 2);
									sprintf(hotspot, "!%s", ptr + 3);
								}
								ptr += fetchword(ptr + 1) + 3;
								break;
							case 0xCC:	/* macro without font change */
								ChangeFont(rtf, fontset, FALSE, uldb = TRUE);
								if (!makertf)
								{
									hotspot = (char *)my_realloc(hotspot, strlen(ptr + 3) + 3);
									sprintf(hotspot, "%%!%s", ptr + 3);
								}
								ptr += fetchword(ptr + 1) + 3;
								break;
							case 0xE0:	/* popup jump HC30 */
								ChangeFont(rtf, fontset, ul = TRUE, FALSE);
								goto label0;
							case 0xE1:	/* topic jump HC30 */
								ChangeFont(rtf, fontset, FALSE, uldb = TRUE);
							  label0:
								if (!makertf)
								{
									hotspot = (char *)my_realloc(hotspot, 128);
									sprintf(hotspot, "TOPIC%ld", (long)fetchlong(ptr + 1));
								}
								ptr += 5;
								break;
							case 0xE2:	/* popup jump HC31 */
								ChangeFont(rtf, fontset, ul = TRUE, FALSE);
								goto label1;
							case 0xE3:	/* topic jump HC31 */
								ChangeFont(rtf, fontset, FALSE, uldb = TRUE);
							  label1:
								if (!makertf)
								{
									arg = ContextId(fetchlong(ptr + 1));
									hotspot = (char *)my_realloc(hotspot, strlen(arg) + 1);
									sprintf(hotspot, "%s", arg);
								}
								ptr += 5;
								break;
							case 0xE6:	/* popup jump without font change */
								ChangeFont(rtf, fontset, ul = TRUE, FALSE);
								goto label2;
							case 0xE7:	/* topic jump without font change */
								ChangeFont(rtf, fontset, FALSE, uldb = TRUE);
							  label2:
								if (!makertf)
								{
									arg = ContextId(fetchlong(ptr + 1));
									hotspot = (char *)my_realloc(hotspot, strlen(arg) + 2);
									sprintf(hotspot, "%%%s", arg);
								}
								ptr += 5;
								break;
							case 0xEA:	/* popup jump into external file */
							case 0xEE:
								ChangeFont(rtf, fontset, ul = TRUE, FALSE);
								goto label3;
							case 0xEB:	/* topic jump into external file / secondary window */
							case 0xEF:
								ChangeFont(rtf, fontset, FALSE, uldb = TRUE);
							  label3:
								if (!makertf)
								{
									if ((unsigned char) ptr[0] == 0xEE || (unsigned char) ptr[0] == 0xEF)
									{
										cmd = "%";
									} else
									{
										cmd = "";
									}
									arg = unhash(fetchlong(ptr + 4));	/* no ContextId, it may jump into external file */
									switch ((unsigned char) ptr[3])
									{
									case 0:
										hotspot = (char *)my_realloc(hotspot, strlen(cmd) + strlen(arg) + 1);
										sprintf(hotspot, "%s%s", cmd, arg);
										break;
									case 1:
										hotspot = (char *)my_realloc(hotspot, strlen(cmd) + strlen(arg) + 1 + strlen(GetWindowName(ptr[8])) + 1);
										sprintf(hotspot, "%s%s>%s", cmd, arg, GetWindowName(ptr[8]));
										break;
									case 4:
										hotspot = (char *)my_realloc(hotspot, strlen(cmd) + strlen(arg) + 1 + strlen(ptr + 8) + 1);
										sprintf(hotspot, "%s%s@%s", cmd, arg, ptr + 8);
										break;
									case 6:
										hotspot = (char *)my_realloc(hotspot, strlen(cmd) + strlen(arg) + 1 + strlen(ptr + 8) + 1 + strlen(strchr(ptr + 8, '\0') + 1) + 1);
										sprintf(hotspot, "%s%s>%s@%s", cmd, arg, ptr + 8, strchr(ptr + 8, '\0') + 1);
										break;
									}
								}
								ptr += fetchword(ptr + 1) + 3;
								break;
							case 0x8B:
								fputs("\\~", rtf);
								ptr++;
								break;
							case 0x8C:
								fputs("\\-", rtf);
								ptr++;
								break;
							default:
								ptr++;
								break;
							}
					}
				}
			}
			if (LinkData1)
				free(LinkData1);
			if (LinkData2)
				free(LinkData2);
			if (before31)
			{
				TopicPos += TopicLink.NextBlock;
			} else
			{
				TopicOffset = NextTopicOffset(TopicOffset, TopicLink.NextBlock, TopicPos);
				TopicPos = TopicLink.NextBlock;
			}
		}
	}
	return rtf;
}

static int ContextRecCmp(const void *a, const void *b)
{
	if (((const CONTEXTREC *) a)->TopicOffset < ((const CONTEXTREC *) b)->TopicOffset)
		return -1;
	if (((const CONTEXTREC *) a)->TopicOffset > ((const CONTEXTREC *) b)->TopicOffset)
		return 1;
	return 0;
}


static void ContextLoad(FILE *HelpFile)
{
	BUFFER buf;
	int i, n;
	int32_t entries;

	if (SearchFile(HelpFile, "|CONTEXT", NULL))
	{
		n = GetFirstPage(HelpFile, &buf, &entries);
		if (entries)
		{
			ContextRec = (CONTEXTREC *)my_malloc(entries * sizeof(CONTEXTREC));
			ContextRecs = 0;
			while (n)
			{
				for (i = 0; i < n; i++)
				{
					read_contextrec(HelpFile, ContextRec + ContextRecs + i);
				}
				ContextRecs += n;
				n = GetNextPage(HelpFile, &buf);
			}
			fprintf(stderr, "%d topic offsets and hash values loaded\n", ContextRecs);
			qsort(ContextRec, ContextRecs, sizeof(CONTEXTREC), ContextRecCmp);
		}
	} else if (SearchFile(HelpFile, "|TOMAP", &entries))
	{
		Topics = (int) (entries / SIZEOF_LONG);
		Topic = (int32_t *)my_malloc(Topics * sizeof(*Topic));
		for (i = 0; i < Topics; i++)
			Topic[i] = getdw(HelpFile);
	}
}


/* create a simple Win95 contents file */
static void GenerateContent(FILE *HelpFile, FILE *ContentFile)
{
	VIOLAREC *WindowRec;
	int32_t FileLength, offset;
	int n, i, j, WindowRecs;
	BUFFER buf;
	const char *ptr;

	fprintf(ContentFile, ":Base %s%s>main\n", name, ext);
	if (HelpFileTitle[0])
		fprintf(ContentFile, ":Title %s\n", HelpFileTitle);
	WindowRecs = 0;
	WindowRec = NULL;
	if (SearchFile(HelpFile, "|VIOLA", NULL))
	{
		n = GetFirstPage(HelpFile, &buf, &FileLength);
		if (FileLength)
		{
			WindowRec = (VIOLAREC *)my_malloc(FileLength * sizeof(VIOLAREC));
			while (n)
			{
				for (i = 0; i < n; i++)
					read_violarec(HelpFile, WindowRec + WindowRecs + i);
				WindowRecs += n;
				n = GetNextPage(HelpFile, &buf);
			}
		}
	}
	if (SearchFile(HelpFile, "|TTLBTREE", NULL))
	{
		for (n = GetFirstPage(HelpFile, &buf, NULL); n; n = GetNextPage(HelpFile, &buf))
		{
			for (i = 0; i < n; i++)
			{
				offset = getdw(HelpFile);
				if (my_gets(buffer, sizeof(buffer), HelpFile))
				{
					ptr = TopicName(offset);
					if (ptr)
					{
						fprintf(ContentFile, "1 %s=%s", buffer, ptr);
						for (j = 0; j < WindowRecs; j++)
						{
							if (WindowRec[j].TopicOffset == offset)
							{
								fprintf(ContentFile, ">%s", GetWindowName(WindowRec[j].WindowNumber));
								break;
							}
						}
						putc('\n', ContentFile);
					}
				}
			}
		}
	}
}


static void ListRose(FILE *HelpFile, FILE *hpj)
{
	int32_t FileLength, offset, hash, h, pos, savepos;
	unsigned char *ptr;
	int32_t *keytopic;
	int n, i, l, e;
	unsigned short j, count;
	BUFFER buf, buf2;

	if (SearchFile(HelpFile, "|Rose", NULL))
	{
		savepos = ftell(HelpFile);
		if (SearchFile(HelpFile, "|KWDATA", &FileLength))
		{
			int32_t keytopics = (FileLength / SIZEOF_LONG);

			keytopic = (int32_t *)my_malloc(keytopics * sizeof(*keytopic));
			for (i = 0; i < keytopics; i++)
				keytopic[i] = getdw(HelpFile);
			if (SearchFile(HelpFile, "|KWBTREE", NULL))
			{
				fputs("[MACROS]\n", hpj);
				for (n = GetFirstPage(HelpFile, &buf, NULL); n; n = GetNextPage(HelpFile, &buf))
				{
					for (i = 0; i < n; i++)
					{
						my_gets(keyword, sizeof(keyword), HelpFile);
						for (hash = 0L, ptr = (unsigned char *) keyword; *ptr; ptr++)
						{
							hash = hash * 43L + table[*ptr];
						}
						count = my_getw(HelpFile);
						offset = getdw(HelpFile);
						for (j = 0; j < count; j++)
						{
							if (keytopic[offset / 4 + j] == -1L)
							{
								pos = ftell(HelpFile);
								fseek(HelpFile, savepos, SEEK_SET);
								for (l = GetFirstPage(HelpFile, &buf2, NULL); l; l = GetNextPage(HelpFile, &buf2))
								{
									for (e = 0; e < l; e++)
									{
										h = getdw(HelpFile);
										my_gets(buffer, sizeof(buffer), HelpFile);
										if (h == hash)
										{
											fprintf(hpj, "%s\n%s\n", keyword, buffer);
											my_gets(buffer, sizeof(buffer), HelpFile);
											fprintf(hpj, "%s\n", buffer);
										} else
										{
											my_gets(buffer, sizeof(buffer), HelpFile);
										}
									}
								}
								fseek(HelpFile, pos, SEEK_SET);
								break;
							}
						}
					}
				}
				putc('\n', hpj);
			}
			free(keytopic);
		}
	}
}


/* dump section: all the dump-routines are used to display internal files
 * of the help file with known format of contents for debugging reasons */
static void PrintNewFont(int i, NEWFONT * newfont)
{
	printf("%3d: %-32.32s %6ld %-6s %02X%02X%02X %02X%02X%02X ", i, fontname[newfont->FontName], (long)newfont->Height,
		   FontFamily(newfont->PitchAndFamily >> 4), newfont->FGRGB[2], newfont->FGRGB[1], newfont->FGRGB[0],
		   newfont->BGRGB[2], newfont->BGRGB[1], newfont->BGRGB[0]);
	if (newfont->Weight > 500)
		putchar('b');
	if (newfont->Italic)
		putchar('i');
	if (newfont->Underline)
		putchar('u');
	if (newfont->StrikeOut)
		putchar('s');
	if (newfont->DoubleUnderline)
		putchar('d');
	if (newfont->SmallCaps)
		putchar('c');
	putchar('\n');
}


static void PrintMvbFont(int i, MVBFONT * mvbfont)
{
	printf("%3d: %-32.32s %6ld %-6s %02X%02X%02X %02X%02X%02X ", i, fontname[mvbfont->FontName], (long)mvbfont->Height,
		   FontFamily(mvbfont->PitchAndFamily >> 4), mvbfont->FGRGB[2], mvbfont->FGRGB[1], mvbfont->FGRGB[0],
		   mvbfont->BGRGB[2], mvbfont->BGRGB[1], mvbfont->BGRGB[0]);
	if (mvbfont->Weight > 500)
		putchar('b');
	if (mvbfont->Italic)
		putchar('i');
	if (mvbfont->Underline)
		putchar('u');
	if (mvbfont->StrikeOut)
		putchar('s');
	if (mvbfont->DoubleUnderline)
		putchar('d');
	if (mvbfont->SmallCaps)
		putchar('c');
	putchar('\n');
}


static void FontDump(FILE *HelpFile)
{
	FONTHEADER FontHdr;
	int32_t FileStart;
	OLDFONT oldfont;
	NEWFONT newfont;
	NEWSTYLE newstyle;
	MVBFONT mvbfont;
	MVBSTYLE mvbstyle;
	int i, n;

	/* Go to the FONT file and get the headers */
	FileStart = ftell(HelpFile);
	read_fontheader(HelpFile, &FontHdr);
	n = (FontHdr.DescriptorsOffset - FontHdr.FacenamesOffset) / FontHdr.NumFacenames;
	fontname = (char **)my_malloc(FontHdr.NumFacenames * sizeof(char *));
	fseek(HelpFile, FileStart + FontHdr.FacenamesOffset, SEEK_SET);
	for (i = 0; i < FontHdr.NumFacenames; i++)
	{
		my_fread(buffer, n, HelpFile);
		buffer[n] = '\0';
		printf("Font name %d: %s\n", i, buffer);
		fontname[i] = my_strdup(buffer);
	}
	puts("Font Facename                         Height Family Foregr Backgr Style");
	fseek(HelpFile, FileStart + FontHdr.DescriptorsOffset, SEEK_SET);
	if (FontHdr.FacenamesOffset >= 16)
	{
		for (i = 0; i < FontHdr.NumDescriptors; i++)
		{
			read_mvbfont(HelpFile, &mvbfont);
			PrintMvbFont(i, &mvbfont);
		}
		fseek(HelpFile, FileStart + FontHdr.FormatsOffset, SEEK_SET);
		for (i = 0; i < FontHdr.NumFormats; i++)
		{
			read_mvbstyle(HelpFile, &mvbstyle);
			printf("Style %d", mvbstyle.StyleNum);
			if (mvbstyle.BasedOn)
				printf(" based on %d", mvbstyle.BasedOn);
			printf(" named '%s':\n", mvbstyle.StyleName);
			PrintMvbFont(i, &mvbstyle.font);
		}
	} else if (FontHdr.FacenamesOffset >= 12)
	{
		for (i = 0; i < FontHdr.NumDescriptors; i++)
		{
			read_newfont(HelpFile, &newfont);
			PrintNewFont(i, &newfont);
		}
		fseek(HelpFile, FileStart + FontHdr.FormatsOffset, SEEK_SET);
		for (i = 0; i < FontHdr.NumFormats; i++)
		{
			read_newstyle(HelpFile, &newstyle);
			printf("Style %d", newstyle.StyleNum);
			if (newstyle.BasedOn)
				printf(" based on %d", newstyle.BasedOn);
			printf(" named '%s':\n", newstyle.StyleName);
			PrintNewFont(i, &newstyle.font);
		}
	} else
	{
		for (i = 0; i < FontHdr.NumDescriptors; i++)
		{
			read_oldfont(HelpFile, &oldfont);
			printf("%3d: %-32.32s %4d.%d %-6s %02X%02X%02X %02X%02X%02X ", i, fontname[oldfont.FontName],
				   oldfont.HalfPoints / 2, (oldfont.HalfPoints & 1) * 5,
				   FontFamily(oldfont.FontFamily < 6 ? lookup[oldfont.FontFamily] : oldfont.FontFamily),
				   oldfont.FGRGB[2], oldfont.FGRGB[1], oldfont.FGRGB[0], oldfont.BGRGB[2], oldfont.BGRGB[1],
				   oldfont.BGRGB[0]);
			if (oldfont.Attributes & FONT_BOLD)
				putchar('b');
			if (oldfont.Attributes & FONT_ITAL)
				putchar('i');
			if (oldfont.Attributes & FONT_UNDR)
				putchar('u');
			if (oldfont.Attributes & FONT_STRK)
				putchar('s');
			if (oldfont.Attributes & FONT_DBUN)
				putchar('d');
			if (oldfont.Attributes & FONT_SMCP)
				putchar('c');
			putchar('\n');
		}
	}
}


static void PhrImageDump(FILE *HelpFile)
{
	int32_t FileLength;
	unsigned int bytes;
	PHRINDEXHDR PhrIndexHdr;
	unsigned char *ptr;

	if (SearchFile(HelpFile, "|PhrIndex", NULL))
	{
		read_phrindexhdr(HelpFile, &PhrIndexHdr);
		if (SearchFile(HelpFile, "|PhrImage", &FileLength))
		{
			if (PhrIndexHdr.phrimagesize == PhrIndexHdr.phrimagecompressedsize)
			{
				HexDump(HelpFile, FileLength, 0L);
			} else
			{
				if (FileLength != PhrIndexHdr.phrimagecompressedsize)
				{
					fprintf(stderr, "PhrImage FileSize %ld, in PhrIndex.FileHdr %ld\n",
							(long)PhrIndexHdr.phrimagecompressedsize, (long)FileLength);
				}
				ptr = (unsigned char *)my_malloc(PhrIndexHdr.phrimagesize);
				bytes = DecompressIntoBuffer(2, HelpFile, FileLength, (char *)ptr, PhrIndexHdr.phrimagesize);
				HexDumpMemory(ptr, bytes);
				free(ptr);
			}
		}
	}
}


static void BTreeDump(FILE *HelpFile, const char *text)
{
	int n, i, j;
	int32_t count;
	BUFFER buf;
	char format[10];
	const char *ptr;

	n = GetFirstPage(HelpFile, &buf, NULL);
	while (n)
	{
		for (i = 0; i < n; i++)
		{
			for (ptr = text; *ptr; ptr++)
			{
				if (*ptr == '%')
				{
					j = strcspn(ptr, "hsdiouxX!");
					memcpy(format, ptr, j + 1);
					format[j + 1] = '\0';
					if (format[j] == '!')
					{
						count = getdw(HelpFile);
						while (count >= 8)
						{
							printf(" (%ld)", (long)getdw(HelpFile));
							printf("%08lx", (unsigned long)getdw(HelpFile));
							count -= 8;
						}
					} else if (format[j] == 'h')
					{
						format[j] = 's';
						printf(format, unhash(getdw(HelpFile)));
					} else if (format[j] == 's')
					{
						my_gets(buffer, sizeof(buffer), HelpFile);
						printf(format, buffer);
					} else if (strchr(format, 'l'))
					{
						printf(format, (long)getdw(HelpFile));
					} else
					{
						printf(format, my_getw(HelpFile));
					}
					ptr += j;
				} else
				{
					putchar(*ptr);
				}
			}
		}
		n = GetNextPage(HelpFile, &buf);
	}
}

static void PhraseDump(void)
{
	unsigned int n;

	for (n = 0; n < PhraseCount; n++)
	{
		printf("%-5d - ", n);
		PrintPhrase(n, NULL, NULL);
		putchar('\n');
	}
}


static void SysDump(FILE *HelpFile)
{
	SYSTEMHEADER SysHdr;
	SYSTEMRECORD *SysRec;
	struct tm *TimeRec;
	const char *ptr;

	read_systemheader(HelpFile, &SysHdr);
	if (SysHdr.Minor == 15)
	{
		helpcomp = "HC30";
	} else if (SysHdr.Minor == 21)
	{
		helpcomp = "HC31/HCP";
	} else if (SysHdr.Minor == 27)
	{
		helpcomp = "WMVC/MVCC";
	} else if (SysHdr.Minor == 33)
	{
		if (mvp)
		{
			helpcomp = "MVC";
		} else
		{
			helpcomp = "HCRTF";
		}
	} else
	{
		helpcomp = NULL;
	}
	printf("%s Help Compiler used.\n", helpcomp ? helpcomp : "Unknown");
	printf("System Flags & Compression Method=0x%04x\n", SysHdr.Flags);
	if (SysHdr.GenDate)
	{
		TimeRec = localtime(&SysHdr.GenDate);
		printf("Help File Generated: %s", asctime(TimeRec));
	}
	if (SysHdr.Minor < 16)
	{
		my_gets(HelpFileTitle, 33, HelpFile);
		printf("TITLE=%s\n", HelpFileTitle);
	} else
	{
		for (SysRec = GetFirstSystemRecord(HelpFile); SysRec; SysRec = GetNextSystemRecord(SysRec))
		{
			switch (SysRec->RecordType)
			{
			case 0x0001:
				printf("TITLE=%s\n", SysRec->Data);
				break;
			case 0x0002:
				printf("COPYRIGHT=%s\n", SysRec->Data);
				break;
			case 0x0003:
				printf("CONTENTS=0x%08lX\n", (long)fetchlong(SysRec->Data));
				break;
			case 0x0004:
				printf("[MACRO] %s\n", SysRec->Data);
				break;
			case 0x0005:
				puts("Icon in System record");
				break;
			case 0x0006:
				printf("[WINDOW] ");
				if (SysRec->DataSize == SIZEOF_SECWINDOW)
				{
					SECWINDOW win;
							
					fetchwindow(&win, SysRec->Data);
					PrintWindow(stdout, &win);
				} else if (SysRec->DataSize == SIZEOF_MVBWINDOW)
				{
					MVBWINDOW win;
							
					fetchmvbwindow(&win, SysRec->Data);
					PrintMVBWindow(stdout, &win);
				} else
				{
					HexDumpMemory(SysRec->Data, SysRec->DataSize);
					error("[WINDOW] data size does not match");
				}
				break;
			case 0x0008:
				printf("CITATION=%s\n", SysRec->Data);
				break;
			case 0x0009:
				if (!mvp)
				{
					printf("LCID=0x%X 0x%X 0x%X\n", fetchword(SysRec->Data + 8), fetchword(SysRec->Data), fetchword(SysRec->Data + 2));
				}
				break;
			case 0x000A:
				if (!mvp)
					printf("CNT=%s\n", SysRec->Data);
				break;
			case 0x000B:
				printf("CHARSET=%d\n", *(unsigned char *)(SysRec->Data+1));
				break;
			case 0x000C:
				if (mvp)
				{
					printf("[FTINDEX] dtype %s\n", SysRec->Data);
				} else
				{
					printf("DEFFONT=%s,%d,%d\n", SysRec->Data + 2, SysRec->Data[0], SysRec->Data[1]);
				}
				break;
			case 0x000D:
				if (mvp)
					printf("[GROUPS] %s\n", SysRec->Data);
				break;
			case 0x000E:
				if (mvp)
				{
					printf("[KEYINDEX] keyword=%c, \"%s\"\n", SysRec->Data[1], SysRec->Data + 30);
				} else
				{
					printf("INDEX_SEPARATORS=\"%s\"\n", SysRec->Data);
				}
				break;
			case 0x0012:
				if (SysRec->Data[0])
					printf("LANGUAGE=%s\n", SysRec->Data);
				break;
			case 0x0013:
				ptr = (const char *)SysRec->Data;
				ptr += strlen(ptr) + 1;
				printf("[DLLMAPS] %s=%s,", SysRec->Data, ptr);
				ptr += strlen(ptr) + 1;
				printf("%s,", ptr);
				ptr += strlen(ptr) + 1;
				printf("%s,", ptr);
				ptr += strlen(ptr) + 1;
				printf("%s\n", ptr);
				break;
			default:
				fprintf(stderr, "Unknown record type: 0x%04X\n", SysRec->RecordType);
				HexDumpMemory((const unsigned char *)SysRec->Data, SysRec->DataSize);
				break;
			}
		}
	}
}


/* dump the contents of |TOPIC for debugging */
static void DumpTopic(FILE *HelpFile, int32_t TopicPos)
{
	TOPICLINK TopicLink;
	unsigned char topiclinkbuf[SIZEOF_TOPICLINK];
	TOPICHEADER30 TopicHdr30;
	TOPICHEADER TopicHdr;
	const char *ptr;
	const char *str;
	const char *cmd;
	int32_t l;
	unsigned short x1, x2;
	unsigned char b;
	int cols, col;
	short i;
	char *LinkData1;
	char *LinkData2;
	int32_t TopicNum;
	int32_t TopicOffset;

	if (!SearchFile(HelpFile, "|TOPIC", &TopicFileLength))
		return;
	TopicOffset = 0L;
	if (TopicPos < 12)
		TopicPos = 12L;
	TopicNum = 16;
	while (TopicRead(HelpFile, TopicPos, topiclinkbuf, SIZEOF_TOPICLINK) == SIZEOF_TOPICLINK)
	{
		fetchtopiclink(&TopicLink, topiclinkbuf);
		puts("----------------------------------------------------------------------------");
		printf("TopicLink Type %02x: BlockSize=%08lx DataLen1=%08lx DataLen2=%08lx\n", TopicLink.RecordType,
			   (long)TopicLink.BlockSize, (long)TopicLink.DataLen1, (long)TopicLink.DataLen2);
		printf("TopicPos=%08lx TopicOffset=%08lx PrevBlock=%08lx NextBlock=%08lx\n", (long)TopicPos, (long)TopicOffset,
			   (long)TopicLink.PrevBlock, (long)TopicLink.NextBlock);
		if (TopicLink.DataLen1 > SIZEOF_TOPICLINK)
		{
			LinkData1 = (char *)my_malloc(TopicLink.DataLen1 - SIZEOF_TOPICLINK);
			if (TopicRead(HelpFile, 0L, LinkData1, TopicLink.DataLen1 - SIZEOF_TOPICLINK) != TopicLink.DataLen1 - SIZEOF_TOPICLINK)
				break;
		} else
			LinkData1 = NULL;
		if (TopicLink.DataLen1 < TopicLink.BlockSize)	/* read LinkData2 using phrase replacement */
		{
			LinkData2 = (char *)my_malloc(TopicLink.DataLen2 + 1);
			if (TopicPhraseRead(HelpFile, 0L, LinkData2, TopicLink.BlockSize - TopicLink.DataLen1, TopicLink.DataLen2) != TopicLink.BlockSize - TopicLink.DataLen1)
				break;
		} else
			LinkData2 = NULL;
		if (LinkData1)
			HexDumpMemory((const unsigned char *)LinkData1, TopicLink.DataLen1 - SIZEOF_TOPICLINK);
		if (TopicLink.RecordType == TL_TOPICHDR)
		{
			if (before31)
			{
				fetch_topicheader30(&TopicHdr30, LinkData1);
				puts("============================================================================");
				printf("TopicHeader TopicNum=%ld BlockSize=%ld PrevTopicNum=%d NextTopicNum=%d\n", (long)TopicNum,
					   (long)TopicHdr30.BlockSize, TopicHdr30.PrevTopicNum, TopicHdr30.NextTopicNum);
				TopicNum++;
			} else
			{
				fetch_topicheader(&TopicHdr, LinkData1);
				puts("============================================================================");
				printf("TopicHeader TopicNum=%ld BlockSize=%ld NextTopicOffset=%08lx\n", (long)TopicHdr.TopicNum,
					   (long)TopicHdr.BlockSize, (long)TopicHdr.NextTopic);
				printf("NonScroll=%08lx Scroll=%08lx BrowseBck=%08lx BrowseFor=%08lx\n", (long)TopicHdr.NonScroll,
					   (long)TopicHdr.Scroll, (long)TopicHdr.BrowseBck, (long)TopicHdr.BrowseFor);
			}
		} else if (TopicLink.RecordType == TL_DISPLAY30 || TopicLink.RecordType == TL_DISPLAY || TopicLink.RecordType == TL_TABLE)
		{
			switch (TopicLink.RecordType)
			{
			case TL_DISPLAY30:
				fputs("Text ", stdout);
				break;
			case TL_DISPLAY:
				fputs("Display ", stdout);
				break;
			case TL_TABLE:
				fputs("Table ", stdout);
				break;
			}
			ptr = LinkData1;
			printf("expandedsize=%ld ", (long)scanlong(&ptr));
			if (TopicLink.RecordType == TL_DISPLAY || TopicLink.RecordType == TL_TABLE)
			{
				x1 = scanword(&ptr);
				TopicOffset += x1;
				printf("topicoffsetincrement=%u ", x1);
			}
			if (TopicLink.RecordType == TL_TABLE)
			{
				cols = (const unsigned char) *ptr++;
				x1 = *ptr++;
				printf("columns=%d type=%d ", cols, x1);
				switch (x1)
				{
				case 0:
				case 2:
					printf("minwidth=%d ", fetchword(ptr));
					ptr += 2;
				case 1:
				case 3:
					break;
				default:
					error("Unknown TableType %d", x1);
					break;
				}
				for (col = 0; col < cols; col++)
				{
					printf("width=%d gap=%d ", fetchword(ptr), fetchword(ptr + 2));
					ptr += 4;
				}
			}
			putchar('\n');
			str = LinkData2;
			for (col = 0;
				 (TopicLink.RecordType == TL_TABLE ? fetchword(ptr) != 0xffff : col == 0)
				 && ptr < LinkData1 + TopicLink.DataLen1 - SIZEOF_TOPICLINK; col++)
			{
				/* FIXME: endian */
				if (TopicLink.RecordType == TL_TABLE)
				{
					printf("column=%d %04x %d ", fetchword(ptr), fetchword(ptr + 2),
						   *(const unsigned char *) (ptr + 4) - 0x80);
					ptr += 5;
				}
				printf("%02x %d id=%04x ", *(const unsigned char *) ptr, *(const unsigned char *) (ptr + 1) - 0x80,
					   fetchword(ptr + 2));
				ptr += 4;
				x2 = fetchword(ptr);
				ptr = ptr + 2;
				if (x2 & 0x0001)
					printf("unknownbit01=%ld ", (long)scanlong(&ptr)); /* found in MVBs, purpose unknown, may mean that x2 is compressed long */
				if (x2 & 0x0002)
					printf("topspacing=%d ", scanint(&ptr));
				if (x2 & 0x0004)
					printf("bottomspacing=%d ", scanint(&ptr));
				if (x2 & 0x0008)
					printf("linespacing=%d ", scanint(&ptr));
				if (x2 & 0x0010)
					printf("leftindent=%d ", scanint(&ptr));
				if (x2 & 0x0020)
					printf("rightindent=%d ", scanint(&ptr));
				if (x2 & 0x0040)
					printf("firstlineindent=%d ", scanint(&ptr));
				if (x2 & 0x0080)
					fputs("unknownbit80set", stdout);
				if (x2 & 0x0100)
				{
					x1 = (unsigned char) *ptr++;
					if (x1 & 1)
						fputs("box ", stdout);
					if (x1 & 2)
						fputs("topborder ", stdout);
					if (x1 & 4)
						fputs("leftborder ", stdout);
					if (x1 & 8)
						fputs("bottomborder ", stdout);
					if (x1 & 0x10)
						fputs("rightborder ", stdout);
					if (x1 & 0x20)
						fputs("thickborder ", stdout);
					if (x1 & 0x40)
						fputs("doubleborder ", stdout);
					if (x1 & 0x80)
						fputs("unknownborder", stdout);
					printf("%04x ", fetchword(ptr));
					ptr = ptr + SIZEOF_SHORT;
				}
				if (x2 & 0x0200)
				{
					i = scanint(&ptr);
					printf("tabs=%d ", i);
					while (i-- > 0)
					{
						x1 = scanword(&ptr);
						printf("stop=%d ", x1 & 0x3FFF);
						if (x1 & 0x4000)
						{
							x1 = scanword(&ptr);
							if (x1 == 1)
							{
								fputs("right ", stdout);
							} else if (x1 == 2)
							{
								fputs("center ", stdout);
							} else
							{
								error("unknowntabmodifier=%02x", x1);
							}
						}
					}
				}
				if (x2 & 0x0400)
					fputs("rightalign ", stdout);
				if (x2 & 0x0800)
					fputs("centeralign ", stdout);
				if (x2 & 0x1000)
					fputs("keeplinestogether ", stdout);
				if (x2 & 0x2000)
					fputs("unknownbit2000set ", stdout);	/* found in PRINTMAN.HLP */
				if (x2 & 0x4000)
					fputs("unknownbit4000set ", stdout);	/* found in PRINTMAN.HLP, RATTLER.HLP */
				if (x2 & 0x8000)
					fputs("unknownbit8000set", stdout);
				putchar('\n');
				for (;;)
				{
					str = PrintString(str, strlen(str)) + 1;
					if ((unsigned char) ptr[0] == 0xFF)
					{
						ptr++;
						break;
					} else
						switch ((unsigned char) ptr[0])
						{
						case 0x20:
							printf("{vfld%ld}", (long)fetchlong(ptr + 1));
							ptr += 5;
							break;
						case 0x21:
							printf("{dtype%d}", fetchword(ptr + 1));
							ptr += 3;
							break;
						case 0x80:		/* font change */
							printf("[font=%u]", fetchword(ptr + 1));
							ptr += 3;
							break;
						case 0x81:
							puts("[LF]");
							ptr++;
							break;
						case 0x82:
							puts("[CR]");
							ptr++;
							break;
						case 0x83:
							fputs("[TAB]", stdout);
							ptr++;
							break;
						case 0x86:
							ptr++;
							b = *ptr++;
							if (b == 0x05)
								cmd = "ewc";
							else
								cmd = "bmc";
							goto picture;
						case 0x87:
							ptr++;
							b = *ptr++;
							if (b == 0x05)
								cmd = "ewl";
							else
								cmd = "bml";
							goto picture;
						case 0x88:
							ptr++;
							b = *ptr++;
							if (b == 0x05)
								cmd = "ewr";
							else
								cmd = "bmr";
						  picture:
							printf("[%s %02x ", cmd, b);
							l = scanlong(&ptr);
							switch (b)
							{
							case 0x22:	/* HC31 */
								x1 = scanword(&ptr);
								printf("hotspots=%u ", x1);
								/* fall through */
							case 0x03:	/* HC30 */
								switch (fetchword(ptr))
								{
								case 0:
									fputs("baggage ", stdout);
									break;
								case 1:
									fputs("embedded ", stdout);
									break;
								default:
									error("Unknown %04x", fetchword(ptr));
									break;
								}
								printf("bm%u]", fetchword(ptr + 2));
								break;
							case 0x05:
								printf("%04x ", fetchword(ptr + 0));
								printf("%04x ", fetchword(ptr + 2));
								printf("%04x ", fetchword(ptr + 4));
								printf("%s]", ptr + 6);
								break;
							default:
								error("Unknown picture flag %02x", b);
								break;
							}
							ptr += l;
							break;
						case 0x89:		/* end of hot spot */
							fputs("[U]", stdout);
							ptr++;
							break;
						case 0x8B:		/* non-break-space */
							fputs("[~]", stdout);
							ptr++;
							break;
						case 0x8C:		/* non-break-hyphen */
							fputs("[-]", stdout);
							ptr++;
							break;
						case 0xC8:		/* macro */
							printf("[!%s]", ptr + 3);
							ptr += fetchword(ptr + 1) + 3;
							break;
						case 0xCC:		/* macro without font change */
							printf("[*!%s]", ptr + 3);
							ptr += fetchword(ptr + 1) + 3;
							break;
						case 0xE0:		/* Popup HC30 */
							printf("[^TOPIC%ld]", (long)fetchlong(ptr + 1));
							ptr += 5;
							break;
						case 0xE1:		/* Jump HC30 */
							printf("[TOPIC%ld]", (long)fetchlong(ptr + 1));
							ptr += 5;
							break;
						case 0xE2:		/* Popup HC31 */
							printf("[^%08lx]", (long)fetchlong(ptr + 1));
							ptr += 5;
							break;
						case 0xE3:		/* Jump HC31 */
							printf("[%08lx]", (long)fetchlong(ptr + 1));
							ptr += 5;
							break;
						case 0xE6:		/* Popup without font change */
							printf("[*^%08lx]", (long)fetchlong(ptr + 1));
							ptr += 5;
							break;
						case 0xE7:		/* Jump without font change */
							printf("[*%08lx]", (long)fetchlong(ptr + 1));
							ptr += 5;
							break;
						case 0xEA:		/* Popup into external file / secondary window */
							cmd = "^";
							goto jump;
						case 0xEB:		/* Jump into external file / secondary window */
							cmd = "";
							goto jump;
						case 0xEE:		/* Popup into external file / secondary window without font change */
							cmd = "^*";
							goto jump;
						case 0xEF:		/* Jump into external file / secondary window without font change */
							cmd = "*";
						  jump:
							switch (ptr[3])
							{
							case 0:
								printf("[%s%08lx] ", cmd, (long)fetchlong(ptr + 4));
								break;
							case 1:	/* Popup into secondary window (silly) */
								printf("[%s%08lx>%d]", cmd, (long)fetchlong(ptr + 4), (unsigned char) ptr[8]);
								break;
							case 4:
								printf("[%s%08lx@%s] ", cmd, (long)fetchlong(ptr + 4), ptr + 8);
								break;
							case 6:	/* Popup into external file / secondary window (silly) */
								printf("[%s%08lx>%s@%s] ", cmd, (long)fetchlong(ptr + 4), ptr + 8,
									   strchr(ptr + 8, '\0') + 1);
								break;
							default:
								putchar('[');
								for (i = 0; i < fetchword(ptr + 1); i++)
									printf("%02x", (unsigned char) ptr[i]);
								putchar(']');
								break;
							}
							ptr += fetchword(ptr + 1) + 3;
							break;
						default:
							printf("[%02x]", (unsigned char) *ptr++);
							break;
						}
				}
				putchar('\n');
			}
		}
		if (LinkData2
			&& (TopicLink.RecordType != TL_DISPLAY30 && TopicLink.RecordType != TL_DISPLAY
				&& TopicLink.RecordType != TL_TABLE))
		{
			PrintString(LinkData2, TopicLink.DataLen2);
			putchar('\n');
		}
		if (LinkData1)
			free(LinkData1);
		if (LinkData2)
			free(LinkData2);
		if (before31)
		{
			TopicPos += TopicLink.NextBlock;
			if (TopicPos >= TopicFileLength)
				break;
		} else
		{
			if (TopicLink.NextBlock <= 0)
				break;
			TopicOffset = NextTopicOffset(TopicOffset, TopicLink.NextBlock, TopicPos);
			TopicPos = TopicLink.NextBlock;
		}
	}
}


static void AliasList(FILE *hpj)				/* write [ALIAS] section to HPJ file */
{
	int i, n;
	BOOL headerwritten;

	headerwritten = FALSE;
	for (i = 0; i < ContextRecs; i = n)
	{
		for (n = i + 1; n < ContextRecs && ContextRec[i].TopicOffset == ContextRec[n].TopicOffset; n++)
		{
			if (!headerwritten)
			{
				fputs("Creating [ALIAS] section...\n", stderr);
				fputs("[ALIAS]\n", hpj);
				headerwritten = TRUE;
			}
			fprintf(hpj, "%s=", unhash(ContextRec[n].HashValue));
			fprintf(hpj, "%s\n", unhash(ContextRec[i].HashValue));
		}
	}
	if (headerwritten)
		putc('\n', hpj);
}


/* write [MAP] section to HPJ file */
static void CTXOMAPList(FILE *HelpFile, FILE *hpj)
{
	CTXOMAPREC CTXORec;
	unsigned short n, i;
	const char *ptr;

	if (SearchFile(HelpFile, "|CTXOMAP", NULL))
	{
		n = my_getw(HelpFile);
		if (n)
		{
			fputs("Creating [MAP] section...\n", stderr);
			fputs("[MAP]\n", hpj);
			for (i = 0; i < n; i++)
			{
				read_ctxomaprec(HelpFile, &CTXORec);
				ptr = TopicName(CTXORec.TopicOffset);
				if (ptr)
				{
					fprintf(hpj, "%s %ld\n", ptr, (long)CTXORec.MapID);
				} else
				{
					fprintf(hpj, "TOPIC%08lx %ld\n", (long)CTXORec.TopicOffset, (long)CTXORec.MapID);
				}
			}
			putc('\n', hpj);
		}
	}
}


static void GuessFromKeywords(FILE *HelpFile)
{
	int32_t *keytopic;
	char kwdata[10];
	char kwbtree[10];
	int m, i, n, k, l, j, map;
	int32_t FileLength, KWDataOffset, TopicOffset;
	BUFFER buf;

	fputs("Guessing...", stderr);
	for (k = 0; k < 2; k++)
		for (map = '0'; map <= 'z'; map++)
		{
			if (k)
			{
				if (!keyindex[map - '0'])
					continue;
				sprintf(kwdata, "|%cKWDATA", map);
				sprintf(kwbtree, "|%cKWBTREE", map);
			} else
			{
				if (!lists[map - '0'])
					continue;
				sprintf(kwdata, "|%cWDATA", map);
				sprintf(kwbtree, "|%cWBTREE", map);
			}
			if (SearchFile(HelpFile, kwdata, &FileLength))
			{
				int32_t keytopics = (FileLength / SIZEOF_LONG);
				
				keytopic = (int32_t *)my_malloc(keytopics * sizeof(*keytopic));
				for (i = 0; i < keytopics; i++)
					keytopic[i] = getdw(HelpFile);
				if (SearchFile(HelpFile, kwbtree, NULL))
				{
					for (n = GetFirstPage(HelpFile, &buf, NULL); n; n = GetNextPage(HelpFile, &buf))
					{
						for (i = 0; i < n; i++)
						{
							my_gets(keyword, sizeof(keyword), HelpFile);
							m = my_getw(HelpFile);
							KWDataOffset = getdw(HelpFile);
							for (j = 0; j < m; j++)
							{
								TopicOffset = keytopic[KWDataOffset / 4 + j];
								Guess(keyword, TopicOffset);
								for (l = 0; l < alternatives; l++)
								{
									if (alternative[l].OtherTopicOffset == TopicOffset)
									{
										Guess(keyword, alternative[l].TopicOffset);
									}
								}
							}
						}
						fputc('.', stderr);
					}
					free(keytopic);
				}
			}
		}
	if (guessed > 0)
	{
		fprintf(stderr, "%ld context ids found\n", (long)guessed);
	} else
	{
		fputs("no context ids found\n(you may use option /g to turn off guessing on this help file)\n", stderr);
	}
}


/* 1. extract topic names from topic macros, embedded pictures, and hotspot macros */
/* 2. build browse sequence start list */
/* 3. extract embedded pictures */
static void FirstPass(FILE *HelpFile)
{
	SYSTEMRECORD *SysRec;
	TOPICLINK TopicLink;
	unsigned char topiclinkbuf[SIZEOF_TOPICLINK];
	char *LinkData1;
	char *LinkData2;
	const char *ptr;
	int32_t l1, TopicNum, TopicPos, TopicOffset, BogusTopicOffset;
	int n, i, col, cols;
	BUFFER buf;
	TOPICHEADER30 TopicHdr30;
	TOPICHEADER TopicHdr;
	char filename[20];
	unsigned short x1, x2;
	short y1;
	MFILE *f;

	if (extractmacros)
	{
		for (SysRec = GetFirstSystemRecord(HelpFile); SysRec; SysRec = GetNextSystemRecord(SysRec))
		{
			if (SysRec->RecordType == 0x0004)
			{
				strcpy(TopicTitle, "[CONFIG] section");
				CheckMacro((const char *) SysRec->Data);
			}
		}
		if (SearchFile(HelpFile, "|TopicId", NULL))
		{
			for (n = GetFirstPage(HelpFile, &buf, NULL); n; n = GetNextPage(HelpFile, &buf))
			{
				for (i = 0; i < n; i++)
				{
					getdw(HelpFile);
					my_gets(buffer, sizeof(buffer), HelpFile);
					AddTopic(buffer, FALSE);
				}
			}
			guessing = FALSE;			/* it's not neccessary to guess context ids if you know them */
		}
	}
	browses = 0;
	browsenums = 1;
	if (!SearchFile(HelpFile, "|TOPIC", &TopicFileLength))
		return;
	TopicOffset = 0L;
	TopicPos = 12L;
	TopicNum = 16;
	while (TopicRead(HelpFile, TopicPos, topiclinkbuf, SIZEOF_TOPICLINK) == SIZEOF_TOPICLINK)
	{
		fetchtopiclink(&TopicLink, topiclinkbuf);
		if (before31)
		{
			if (TopicPos + TopicLink.NextBlock >= TopicFileLength)
				break;
		} else
		{
			if (TopicLink.NextBlock <= 0)
				break;
		}
		if (TopicLink.DataLen1 > SIZEOF_TOPICLINK)
		{
			LinkData1 = (char *)my_malloc(TopicLink.DataLen1 - SIZEOF_TOPICLINK + 1);
			if (TopicRead(HelpFile, 0L, LinkData1, TopicLink.DataLen1 - SIZEOF_TOPICLINK) != TopicLink.DataLen1 - SIZEOF_TOPICLINK)
				break;
		} else
			LinkData1 = NULL;
		if (TopicLink.DataLen1 < TopicLink.BlockSize)	/* read LinkData2 using phrase replacement */
		{
			LinkData2 = (char *)my_malloc(TopicLink.DataLen2 + 1);
			if (TopicPhraseRead(HelpFile, 0L, LinkData2, TopicLink.BlockSize - TopicLink.DataLen1, TopicLink.DataLen2)
				!= TopicLink.BlockSize - TopicLink.DataLen1)
				break;
		} else
			LinkData2 = NULL;
		if (TopicLink.RecordType == TL_TOPICHDR)	/* display a topic header record */
		{
			fprintf(stderr, "\rTopic %ld...", (long)TopicNum - 15);
			if (before31)
			{
				fetch_topicheader30(&TopicHdr30, LinkData1);
				if (resolvebrowse)
				{
					if ((TopicHdr30.NextTopicNum > TopicNum && TopicHdr30.PrevTopicNum > TopicNum)
						|| (TopicHdr30.NextTopicNum == -1 && TopicHdr30.PrevTopicNum > TopicNum)
						|| (TopicHdr30.NextTopicNum > TopicNum && TopicHdr30.PrevTopicNum == -1))
					{
						AddBrowse(TopicNum, TopicHdr30.NextTopicNum, TopicHdr30.PrevTopicNum);
					} else if (TopicHdr30.NextTopicNum != -1 && TopicHdr30.NextTopicNum < TopicNum
							   && TopicHdr30.PrevTopicNum != -1 && TopicHdr30.PrevTopicNum < TopicNum)
					{
						MergeBrowse(TopicNum, TopicNum, TopicHdr30.NextTopicNum, TopicHdr30.PrevTopicNum);
					} else if (TopicHdr30.NextTopicNum != -1 && TopicHdr30.NextTopicNum < TopicNum
							   && (TopicHdr30.PrevTopicNum == -1 || TopicHdr30.PrevTopicNum > TopicNum))
					{
						BackLinkBrowse(TopicNum, TopicNum, TopicHdr30.NextTopicNum, TopicHdr30.PrevTopicNum);
					} else if (TopicHdr30.PrevTopicNum != -1 && TopicHdr30.PrevTopicNum < TopicNum
							   && (TopicHdr30.NextTopicNum == -1 || TopicHdr30.NextTopicNum > TopicNum))
					{
						LinkBrowse(TopicNum, TopicNum, TopicHdr30.NextTopicNum, TopicHdr30.PrevTopicNum);
					}
				}
			} else
			{
				BogusTopicOffset = NextTopicOffset(TopicOffset, TopicLink.NextBlock, TopicPos);
				if (BogusTopicOffset != TopicOffset)
				{
					alternative = (ALTERNATIVE *)my_realloc(alternative, (alternatives + 1) * sizeof(ALTERNATIVE));
					alternative[alternatives].TopicOffset = TopicOffset;
					alternative[alternatives].OtherTopicOffset = BogusTopicOffset;
					alternatives++;
				}
				fetch_topicheader(&TopicHdr, LinkData1);
				if (resolvebrowse)
				{
					if ((TopicHdr.BrowseFor > TopicOffset && TopicHdr.BrowseBck > TopicOffset)
						|| (TopicHdr.BrowseFor == -1L && TopicHdr.BrowseBck > TopicOffset)
						|| (TopicHdr.BrowseFor > TopicOffset && TopicHdr.BrowseBck == -1L))
					{
						AddBrowse(TopicOffset, TopicHdr.BrowseFor, TopicHdr.BrowseBck);
					} else if (TopicHdr.BrowseFor != -1L && TopicHdr.BrowseFor < TopicOffset
							   && TopicHdr.BrowseBck != -1L && TopicHdr.BrowseBck < TopicOffset)
					{
						MergeBrowse(TopicOffset, BogusTopicOffset, TopicHdr.BrowseFor, TopicHdr.BrowseBck);
					} else if (TopicHdr.BrowseFor != -1L && TopicHdr.BrowseFor < TopicOffset
							   && (TopicHdr.BrowseBck == -1L || TopicHdr.BrowseBck > TopicOffset))
					{
						BackLinkBrowse(TopicOffset, BogusTopicOffset, TopicHdr.BrowseFor, TopicHdr.BrowseBck);
					} else if (TopicHdr.BrowseBck != -1L && TopicHdr.BrowseBck < TopicOffset
							   && (TopicHdr.BrowseFor == -1L || TopicHdr.BrowseFor > TopicOffset))
					{
						LinkBrowse(TopicOffset, BogusTopicOffset, TopicHdr.BrowseFor, TopicHdr.BrowseBck);
					}
				}
				if (extractmacros)
				{
					if (TopicLink.DataLen2 && *LinkData2)
					{
						my_strlcpy(TopicTitle, LinkData2, sizeof(TopicTitle));
						if (guessing)
						{
							Guess(LinkData2, TopicOffset);
							if (BogusTopicOffset != TopicOffset)
							{
								Guess(LinkData2, BogusTopicOffset);
							}
						}
					} else
					{
						strcpy(TopicTitle, "<< untitled topic >>");
					}
					if (TopicLink.DataLen2)
					{
						for (i = strlen(LinkData2) + 1; i < TopicLink.DataLen2; i += strlen(LinkData2 + i) + 1)
						{
							CheckMacro(LinkData2 + i);
						}
					}
				}
			}
			TopicNum++;
		} else if (TopicLink.RecordType == TL_DISPLAY30 || TopicLink.RecordType == TL_DISPLAY
				   || TopicLink.RecordType == TL_TABLE)
		{
			ptr = LinkData1;
			scanlong(&ptr);
			if (TopicLink.RecordType == TL_DISPLAY || TopicLink.RecordType == TL_TABLE)
			{
				TopicOffset += scanword(&ptr);
			}
			if (TopicLink.RecordType == TL_TABLE)
			{
				cols = (unsigned char) *ptr++;
				x1 = (unsigned char) *ptr++;
				switch (x1)
				{
				case 0:				/* found in CALC.HLP and TERMINAL.HLP */
				case 2:
					ptr += 2;
					break;
				case 1:
				case 3:
					break;
				default:
					error("Unknown TableType %d", x1);
					break;
				}
				ptr += 4 * cols;
			}
			for (col = 0;
				 (TopicLink.RecordType == TL_TABLE ? fetchword(ptr) != 0xffff : col == 0)
				 && ptr < LinkData1 + TopicLink.DataLen1 - SIZEOF_TOPICLINK; col++)
			{
				if (TopicLink.RecordType == TL_TABLE)
					ptr += 5;
				ptr += 4;
				x2 = fetchword(ptr);
				ptr += 2;
				if (x2 & 0x0001)
					scanlong(&ptr);		/* found in MVBs, purpose */
				/* unknown, may mean that x2 is really compressed long */
				if (x2 & 0x0002)
					scanint(&ptr);
				if (x2 & 0x0004)
					scanint(&ptr);
				if (x2 & 0x0008)
					scanint(&ptr);
				if (x2 & 0x0010)
					scanint(&ptr);
				if (x2 & 0x0020)
					scanint(&ptr);
				if (x2 & 0x0040)
					scanint(&ptr);
				if (x2 & 0x0100)
					ptr += 3;
				if (x2 & 0x0200)
				{
					y1 = scanint(&ptr);
					while (y1-- > 0)
						if (scanword(&ptr) & 0x4000)
							scanword(&ptr);
				}
				while (ptr < LinkData1 + TopicLink.DataLen1 - SIZEOF_TOPICLINK)
				{
					if ((unsigned char) ptr[0] == 0xFF)
					{
						ptr++;
						break;
					} else
					{
						switch ((unsigned char) ptr[0])
						{
						case 0x21:		/* dtype (MVB) */
						case 0x80:		/* font change */
							ptr += 3;
							break;
						case 0x81:
						case 0x82:
						case 0x83:
						case 0x89:		/* end of hotspot */
						case 0x8B:		/* non-break-space */
						case 0x8C:		/* non-break-hyphen */
							ptr++;
							break;
						case 0x86:
						case 0x87:
						case 0x88:
							ptr++;
							x1 = *ptr++;
							l1 = scanlong(&ptr);
							switch (x1)
							{
							case 0x22:	/* HC31 */
								x1 = scanword(&ptr);
								/* fall thru */
							case 0x03:	/* HC30 */
								switch (fetchword(ptr))
								{
								case 1:
									for (x2 = 1; x2 < extensions; x2++)
										if (!extension[x2])
											break;
									if (x2 >= extensions)
									{
										extension = (char *)my_realloc(extension, (x2 + 1) * sizeof(char));
										while (extensions <= x2)
											extension[extensions++] = 0;
									}
									sprintf(filename, "bm%u", x2);
									f = CreateMap(ptr + 2, l1 - 2);
									x1 = ExtractBitmap(filename, f);
									CloseMap(f);
									extension[x2] = x1 | 0x10;
									break;
								}
								break;
							case 0x05:
								if (ptr[6] == '!' && strchr(ptr + 7, ','))
								{
									CheckMacro(strchr(ptr + 7, ',') + 1);
								}
								break;
							}
							ptr += l1;
							break;
						case 0xC8:		/* macro */
						case 0xCC:		/* macro without font change */
							CheckMacro(ptr + 3);
							ptr += fetchword(ptr + 1) + 3;
							break;
						case 0x20:		/* vfld (MVC) */
						case 0xE0:		/* popup jump HC30 */
						case 0xE1:		/* topic jump HC30 */
						case 0xE2:		/* popup jump HC31 */
						case 0xE3:		/* topic jump HC31 */
						case 0xE6:		/* popup jump without font change */
						case 0xE7:		/* topic jump without font change */
							ptr += 5;
							break;
						case 0xEA:		/* popup jump into external file */
						case 0xEB:		/* topic jump into external file / secondary window */
						case 0xEE:		/* popup jump into external file without font change */
						case 0xEF:		/* topic jump into external file / secondary window without font change */
							switch ((unsigned char) ptr[3])
							{
							case 0:
							case 1:
								break;
							case 4:
								StoreReference(ptr + 8, TOPIC, NULL, fetchlong(ptr + 4));
								break;
							case 6:
								StoreReference(strchr(ptr + 8, '\0') + 1, TOPIC, NULL, fetchlong(ptr + 4));
								break;
							default:
								error("Unknown modifier %02x in tag %02x", (unsigned char) ptr[3],
									  (unsigned char) ptr[0]);
								break;
							}
							ptr += fetchword(ptr + 1) + 3;
							break;
						default:
							error("Unknown %02x", *(const unsigned char *) ptr);
							ptr++;
							break;
						}
					}
				}
			}
		}
		if (LinkData1)
			free(LinkData1);
		if (LinkData2)
			free(LinkData2);
		if (before31)
		{
			TopicPos += TopicLink.NextBlock;
		} else
		{
			TopicOffset = NextTopicOffset(TopicOffset, TopicLink.NextBlock, TopicPos);
			TopicPos = TopicLink.NextBlock;
		}
	}
}


static int CTXOMAPRecCmp(const void *a, const void *b)
{
	if (((const CTXOMAPREC *) a)->TopicOffset < ((const CTXOMAPREC *) b)->TopicOffset)
		return -1;
	if (((const CTXOMAPREC *) a)->TopicOffset > ((const CTXOMAPREC *) b)->TopicOffset)
		return 1;
	return 0;
}


static void ContextList(FILE *HelpFile)
{
	unsigned short maprecs, m;
	int j, window, len;
	BOOL morekeywords;
	CTXOMAPREC *map = NULL;
	char filename[13];
	TOPICLINK TopicLink;
	unsigned char topiclinkbuf[SIZEOF_TOPICLINK];
	char *LinkData1;
	char *LinkData2;
	const char *ptr;
	int32_t TopicPos, TopicNum, TopicOffset;

	if (SearchFile(HelpFile, "|CTXOMAP", NULL))
	{
		maprecs = my_getw(HelpFile);
		if (maprecs)
		{
			unsigned short i;
			
			map = (CTXOMAPREC *)my_malloc(maprecs * sizeof(CTXOMAPREC));
			for (i = 0; i < maprecs; i++)
				read_ctxomaprec(HelpFile, map + i);
			qsort(map, maprecs, sizeof(CTXOMAPREC), CTXOMAPRecCmp);
		}
	} else
	{
		map = NULL;
		maprecs = 0;
	}
	strcpy(filename, name);
	strcat(filename, ext);
	if (!SearchFile(HelpFile, "|TOPIC", &TopicFileLength))
		return;
	TopicOffset = 0L;
	TopicPos = 12L;
	TopicNum = 1;
	j = 0;
	len = 80;
	window = -1;
	morekeywords = TRUE;
	m = 0;
	while (TopicRead(HelpFile, TopicPos, topiclinkbuf, SIZEOF_TOPICLINK) == SIZEOF_TOPICLINK)
	{
		fetchtopiclink(&TopicLink, topiclinkbuf);
		if (TopicLink.DataLen1 > SIZEOF_TOPICLINK)
		{
			LinkData1 = (char *)my_malloc(TopicLink.DataLen1 - SIZEOF_TOPICLINK + 1);
			if (TopicRead(HelpFile, 0L, LinkData1, TopicLink.DataLen1 - SIZEOF_TOPICLINK) != TopicLink.DataLen1 - SIZEOF_TOPICLINK)
				break;
		} else
			LinkData1 = NULL;
		if (TopicLink.DataLen1 < TopicLink.BlockSize)	/* read LinkData2 using phrase replacement */
		{
			LinkData2 = (char *)my_malloc(TopicLink.DataLen2 + 1);
			if (TopicPhraseRead(HelpFile, 0L, LinkData2, TopicLink.BlockSize - TopicLink.DataLen1, TopicLink.DataLen2)
				!= TopicLink.BlockSize - TopicLink.DataLen1)
				break;
		} else
			LinkData2 = NULL;
		if (TopicLink.NextBlock <= 0 || TopicLink.RecordType == TL_TOPICHDR)	/* display a topic header record */
		{
			if (TopicNum > 1)
				putchar('\n');
			if (before31)
				TopicOffset = TopicPos;
			while (m < maprecs && map[m].TopicOffset < TopicOffset)
			{
				printf("  WinHelp(wnd,\"%s\",HELP_CONTEXT,%lu)\n", filename, (long)map[m].MapID);
				m++;
			}
			if (!before31)
			{
				while (j < ContextRecs && ContextRec[j].TopicOffset < TopicOffset)
				{
					if (len == 80)
					{
						fputs("  Jump", stdout);
					} else
					{
						fputs("  Popup", stdout);
					}
					printf("Id(`%s", filename);
					if (window != -1)
						printf(">%s", GetWindowName(window));
					printf("',`%s')\n", unhash(ContextRec[j].HashValue));
					j++;
				}
				if (morekeywords)
				{
					if (NextKeywordRec >= KeywordRecs)
					{
						if (NextKeywordOffset < 0x7FFFFFFFL)
						{
							CollectKeywords(HelpFile);
						} else
						{
							morekeywords = FALSE;
						}
					}
					while (NextKeywordRec < KeywordRecs && KeywordRec[NextKeywordRec].TopicOffset < TopicOffset)
					{
						if (KeywordRec[NextKeywordRec].Footnote == 'K')
						{
							printf("  JumpKeyword(`%s',`%s')\n", filename, KeywordRec[NextKeywordRec].Keyword);
						} else if (KeywordRec[NextKeywordRec].Footnote == 'A')
						{
							printf("  ALink(`%s@%s')\n", KeywordRec[NextKeywordRec].Keyword, filename);
						}
						NextKeywordRec++;
					}
				}
				window = ListWindows(HelpFile, TopicOffset);
			}
			if (TopicLink.NextBlock <= 0)
				break;
			if (LinkData2 && *LinkData2)
			{
				printf("Topic %ld: %s", (long)TopicNum, LinkData2);
				len = 80;
			} else
			{
				len = printf("Topic %ld: untitled: ", (long)TopicNum);
			}
			TopicNum++;
		} else if (LinkData2 && len < 79)
		{
			for (ptr = LinkData2; len < 79 && ptr < LinkData2 + TopicLink.DataLen2; ptr++)
				if (*ptr)
				{
					putchar(*ptr);
					len++;
				}
		}
		if (TopicLink.RecordType == TL_DISPLAY || TopicLink.RecordType == TL_TABLE)
		{
			ptr = LinkData1;
			scanlong(&ptr);
			TopicOffset += scanword(&ptr);
		}
		if (LinkData1)
			free(LinkData1);
		if (LinkData2)
			free(LinkData2);
		if (before31)
		{
			if (TopicPos + TopicLink.NextBlock >= TopicFileLength)
				break;
			TopicPos += TopicLink.NextBlock;
		} else
		{
			if (TopicLink.NextBlock <= 0)
				break;
			TopicOffset = NextTopicOffset(TopicOffset, TopicLink.NextBlock, TopicPos);
			TopicPos = TopicLink.NextBlock;
		}
	}
}


enum mode {
	MODE_DECOMPILE,
	MODE_HEXDUMP,
	MODE_LISTFILES,
	MODE_CREATE_RTF,
	MODE_GEN_CONTENT,
	MODE_LIST_ENTRIES,
	MODE_CHECK_REFERENCES,
	MODE_LIST_REFERENCES
};


static BOOL HelpDeCompile(FILE *HelpFile, const char *dumpfile, enum mode mode, const char *exportname, int32_t offset)
{
	char filename[PATH_MAX];
	char hpjfilename[PATH_MAX];
	int32_t FileLength;
	FILE *rtf;
	FILE *hpj;
	int d;
	long topic;

	if (!SearchFile(HelpFile, NULL, &FileLength))
		return FALSE;
	if (dumpfile == NULL)
	{
		switch (mode)
		{
		case MODE_DECOMPILE:
			SysLoad(HelpFile);
			fprintf(stderr, "Decompiling %s...\n", HelpFileTitle);
			ContextLoad(HelpFile);
			PhraseLoad(HelpFile);
			ExportBitmaps(HelpFile);
			fputs("Pass 1...\n", stderr);
			FirstPass(HelpFile);		/* valid only after ExportBitmaps */
			putc('\n', stderr);
			if (!before31 && guessing)
				GuessFromKeywords(HelpFile);	/* after FirstPass, before SysList */
			strcpy(hpjfilename, name);
			if (mvp)
			{
				strcat(hpjfilename, ".mvp");
			} else
			{
				strcat(hpjfilename, ".hpj");
			}
			hpj = my_fopen(hpjfilename, "wt");
			if (hpj)
			{
				strcpy(filename, name);
				strcat(filename, ".ico");
				SysList(HelpFile, hpj, filename);	/* after ContextLoad */
				ListBaggage(HelpFile, hpj, before31);
				if (!mvp)
					AliasList(hpj);		/* after ContextLoad, before TopicDump */
				strcpy(filename, name);
				strcat(filename, ".ph");
				PhraseList(filename);	/* after PhraseLoad */
				BuildName(filename, TopicsPerRTF > 0);
				rtf = my_fopen(filename, "wt");
				if (rtf)
				{
					FontLoad(HelpFile, rtf, hpj);
					fputs("Pass 2...\n", stderr);
					fprintf(hpj, "[FILES]\n%s\n\n", filename);
					rtf = TopicDump(HelpFile, rtf, hpj, FALSE);
					putc('}', rtf);
					putc('\n', stderr);
					my_fclose(rtf);
				}
				NotInAnyTopic = FALSE;
				CTXOMAPList(HelpFile, hpj);
				if (extensions && before31)
					ListBitmaps(hpj);
				if (win95)
					ListRose(HelpFile, hpj);
				my_fclose(hpj);
			}
			if (PhraseOffsets)
			{
				if (win95)
				{
					puts("Help Compiler will issue Note HC1002: Using existing phrase table");
				} else
				{
					puts("Help Compiler will issue Warning 5098: Using old key-phrase table");
				}
			}
			if (missing)
				puts("Help Compiler will issue Error 1230: File 'missing.bmp' not found");
			if (NotInAnyTopic)
				puts("Help Compiler will issue Warning 4098: Context string(s) in [MAP] section not defined in any topic");
			if (!extractmacros)
				puts("Help Compiler may issue Warning 4131: Hash conflict between 'x' and 'y'.");
			if (warnings)
			{
				printf("HELPDECO had problems with %s. Rebuilt helpfile may behave bad.\n", HelpFileName);
			}
			if (helpcomp != NULL)
			{
				const char *opt;
				
				if (win95 && SearchFile(HelpFile, "|Petra", NULL))
					opt = " /a";
				else
					opt = "";
				printf("Use %s%s %s to recompile ", helpcomp, opt, hpjfilename);
				if (AnnoFile)
					fputs("annotated ", stdout);
				puts("helpfile.");
			}
			break;
		case MODE_HEXDUMP:
			HexDump(HelpFile, FileLength, offset);
			break;
		case MODE_LISTFILES:
			ListFiles(HelpFile);
			break;
		case MODE_CREATE_RTF:						/* create lookalike RTF */
			SysLoad(HelpFile);
			fprintf(stderr, "Writing %s...\n", HelpFileTitle);
			exportplain = TRUE;
			ExportBitmaps(HelpFile);
			PhraseLoad(HelpFile);
			BuildName(filename, TopicsPerRTF > 0);
			rtf = my_fopen(filename, "wt");
			if (rtf)
			{
				FontLoad(HelpFile, rtf, NULL);
				rtf = TopicDump(HelpFile, rtf, NULL, TRUE);
				putc('}', rtf);
				putc('\n', stderr);
				my_fclose(rtf);
			}
			break;
		case MODE_GEN_CONTENT:						/* generate contents file */
			SysLoad(HelpFile);
			fprintf(stderr, "Scanning %s...\n", HelpFileTitle);
			ContextLoad(HelpFile);
			PhraseLoad(HelpFile);
			checkexternal = TRUE;
			ExportBitmaps(HelpFile);
			FirstPass(HelpFile);
			putc('\n', stderr);
			if (!before31 && guessing)
				GuessFromKeywords(HelpFile);	/* after FirstPass, before SysList */
			strcpy(filename, name);
			strcat(filename, ".cnt");
			rtf = my_fopen(filename, "wt");
			if (rtf)
			{
				GenerateContent(HelpFile, rtf);
				my_fclose(rtf);
			}
			break;
		case MODE_LIST_ENTRIES:						/* create entry point list  */
			resolvebrowse = FALSE;
			checkexternal = TRUE;
			SysLoad(HelpFile);
			fprintf(stderr, "Parsing %s...\n", HelpFileTitle);
			ContextLoad(HelpFile);
			PhraseLoad(HelpFile);
			ExportBitmaps(HelpFile);
			FirstPass(HelpFile);
			putc('\n', stderr);
			if (!before31 && guessing)
				GuessFromKeywords(HelpFile);	/* after FirstPass, before SysList */
			ContextList(HelpFile);
			break;
		case MODE_CHECK_REFERENCES:					/* check external references */
		case MODE_LIST_REFERENCES:
			resolvebrowse = FALSE;
			checkexternal = TRUE;
			SysLoad(HelpFile);
			fprintf(stderr, "Checking %s...\n", HelpFileTitle);
			PhraseLoad(HelpFile);
			FirstPass(HelpFile);
			putc('\n', stderr);
			if (!external)
			{
				printf("No references to external files found in %s.\n", HelpFileName);
			} else if (mode == MODE_CHECK_REFERENCES)
			{
				CheckReferences();
			} else
			{
				ListReferences();
			}
			break;
		}
	} else
	{
		if (!SearchFile(HelpFile, dumpfile, &FileLength))
		{
			filename[0] = '|';
			my_strlcpy(filename + 1, dumpfile, sizeof(filename) - 1);
			if (!SearchFile(HelpFile, filename, &FileLength))
			{
				fprintf(stderr, "Internal file %s not found.\n", dumpfile);
				return TRUE;
			}
			dumpfile = filename;
		}
		printf("FileName: %s FileSize: %ld\n", dumpfile, (long)FileLength);
		if (exportname)					/* export internal file */
		{
			FILE *f;

			f = my_fopen(exportname, "wb");
			if (f)
			{
				copy(HelpFile, FileLength, f);
				my_fclose(f);
			}
		} else if (mode == MODE_HEXDUMP)
		{
			HexDump(HelpFile, FileLength, offset);
		} else if (strcmp(dumpfile, "|TOPIC") == 0)
		{
			SysLoad(HelpFile);
			PhraseLoad(HelpFile);
			DumpTopic(HelpFile, offset);
		} else if (strlen(dumpfile) >= 4 && strcmpi(dumpfile + strlen(dumpfile) - 4, ".grp") == 0)
		{
			GroupDump(HelpFile);
		} else if (strcmp(dumpfile, "@LINK") == 0)
		{
			LinkDump(HelpFile);
		} else if (sscanf(dumpfile, "%ld!%d", &topic, &d) == 2 && topic != 0L && d == 0)
		{
			AnnotationDump(HelpFile, FileLength, dumpfile);
		} else if (strcmp(dumpfile, "|Phrases") == 0 || strcmp(dumpfile, "|PhrIndex") == 0)
		{
			SysLoad(HelpFile);
			PhraseLoad(HelpFile);
			PhraseDump();
			PhrImageDump(HelpFile);
		} else if (strcmp(dumpfile, "|SYSTEM") == 0)
		{
			SysDump(HelpFile);
		} else if (strcmp(dumpfile, "|TOMAP") == 0)
		{
			ToMapDump(HelpFile, FileLength);
		} else if (strcmp(dumpfile, "|CONTEXT") == 0)
		{
			BTreeDump(HelpFile, "ContextId: %h TopicOffset: 0x%08lx\n");
		} else if (dumpfile[0] == '|' && (strcmp(dumpfile + 2, "WBTREE") == 0 || strcmp(dumpfile + 2, "KWBTREE") == 0))
		{
			fseek(HelpFile, 7, SEEK_CUR);
			d = getc(HelpFile);
			fseek(HelpFile, -8, SEEK_CUR);
			if (d == '!')
			{
				BTreeDump(HelpFile, "Keyword: '%s' Occurrance: %!\n");
			} else
			{
				BTreeDump(HelpFile, "Keyword: '%s' Count: %u KWDataAddress: 0x%08lx\n");
			}
		} else if (dumpfile[0] == '|' && (strcmp(dumpfile + 2, "WMAP") == 0 || strcmp(dumpfile + 2, "KWMAP") == 0))
		{
			KWMapDump(HelpFile);
		} else if (dumpfile[0] == '|' && (strcmp(dumpfile + 2, "WDATA") == 0 || strcmp(dumpfile + 2, "KWDATA") == 0))
		{
			KWDataDump(HelpFile, FileLength);
		} else if (strcmp(dumpfile, "|VIOLA") == 0)
		{
			BTreeDump(HelpFile, "TopicOffset: 0x%08lx WindowNumber: %ld\n");
		} else if (strcmp(dumpfile, "|CTXOMAP") == 0)
		{
			CTXOMAPDump(HelpFile);
		} else if (strcmp(dumpfile, "|CATALOG") == 0)
		{
			CatalogDump(HelpFile);
		} else if (strcmp(dumpfile, "|Petra") == 0)
		{
			BTreeDump(HelpFile, "TopicOffset: 0x%08lx SourceFileName: %s\n");
		} else if (strcmp(dumpfile, "|TopicId") == 0)
		{
			BTreeDump(HelpFile, "TopicOffset: 0x%08lx ContextId: %s\n");
		} else if (strcmp(dumpfile, "|Rose") == 0)
		{
			BTreeDump(HelpFile, "KeywordHashValue: 0x%08lx\nMacro: %s\nTitle: %s\n");
		} else if (strcmp(dumpfile, "|TTLBTREE") == 0)
		{
			BTreeDump(HelpFile, "TopicOffset: 0x%08lx TopicTitle: %s\n");
		} else if (strcmp(dumpfile, "|FONT") == 0)
		{
			FontDump(HelpFile);
		} else							/* generic  */
		{
			topic = ftell(HelpFile);
			if (my_getw(HelpFile) == 0x293B)	/* if it's a B+ tree */
			{
				my_getw(HelpFile);
				my_getw(HelpFile);
				filename[0] = '\0';
				while ((d = getc(HelpFile)) > 0)	/* format according to Structure */
				{
					switch (d)
					{
					case 'L':
					case '4':
					case 'a':
						strcat(filename, "0x%08lx ");
						break;
					case '2':
						strcat(filename, "%5u ");
						break;
					case 'F':
					case 'i':
					case 'z':
						strcat(filename, "'%s' ");
						break;
					case '!':
						strcat(filename, "%!");
						break;
					default:
						error("Unknown Btree field type '%c'", d);
						break;
					}
				}
				strcat(filename, "\n");
				fseek(HelpFile, topic, SEEK_SET);
				BTreeDump(HelpFile, filename);
			} else
			{
				fseek(HelpFile, topic, SEEK_SET);
				HexDump(HelpFile, FileLength, offset);
			}
		}
	}
	return TRUE;
}


static void usage(FILE *f)
{
	fprintf(f, "HELPDECO - decompile *.HLP/*.MVB files of Windows 3.x / 95 - %d bit Version 2.1.3\n"
		"M.Winterhoff <mawin@gmx.net>, Geschw.-Scholl-Ring 17, 38444 Wolfsburg, Germany\n"
		"\n"
		"usage:   HELPDECO helpfile[.hlp]    [-y]  - decompile helpfile into all sources\n"
		"         HELPDECO helpfile[.hlp]    [-y] -a[annfile.ANN]  - and add annotations\n"
		"         HELPDECO helpfile[.hlp] -r [-y] [-n]    - decompile into lookalike RTF\n"
		"         HELPDECO helpfile[.hlp] -c [-y]  - generate Win95 .CNT content file\n"
		"         HELPDECO helpfile[.hlp] -l       - list entry points of this helpfile\n"
		"         HELPDECO helpfile[.hlp] -e [-f]  - list references to other helpfiles\n"
		"         HELPDECO helpfile[.hlp] -p [-f]  - check references to other helpfiles\n"
		"         HELPDECO helpfile[.hlp] -d [-x]  - display internal directory\n"
		"         HELPDECO helpfile[.hlp] \"internalfile\" [-x]    - display internal file\n"
		"         HELPDECO helpfile[.hlp] \"internalfile\" filename - export internal file\n"
		"options: -y overwrite without warning, -f list referencing topics, -x hex dump\n"
		"         -g no guessing, -hprefix add known contextid prefix, -n no page breaks\n"
		"To recreate all source files neccessary to rebuild a Windows helpfile, create\n"
		"a directory, change to this directory and call HELPDECO with the path and name\n"
		"of the helpfile to dissect. HELPDECO will extract all files contained in the\n"
		"helpfile in two passes and deposit them in the current directory. You may then\n"
		"rebuild the helpfile using the appropriate help compiler HC30, HC31, HCP, HCW,\n"
		"HCRTF, MVC, WMVC or MVCC. The file will not be identical, but should look and\n"
		"work like the original.\n",
		(int)sizeof(long) * 8);
}


int main(int argc, char *argv[])
{
	char AnnoFileName[NAME_MAX];
	char drive[_MAX_DRIVE];
	char dir[PATH_MAX];
	FILE *f;
	enum mode mode;
	BOOL annotate;
	const char *filename;
	const char *dumpfile;
	const char *exportname;
	int32_t offset;
	int i, j;

	/* initialize hash value coding oldtable */
	memset(oldtable, 0, sizeof(oldtable));
	for (i = 0; i < 9; i++)
		oldtable['1' + i] = i + 1;
	oldtable['0'] = 10;
	oldtable['.'] = 12;
	oldtable['_'] = 13;
	for (i = 0; i < 26; i++)
		oldtable['A' + i] = oldtable['a' + i] = 17 + i;
	exportname = dumpfile = filename = NULL;
	AnnoFileName[0] = '\0';
	mode = MODE_DECOMPILE;
	offset = 0;
	annotate = FALSE;
	/* scan arguments */
	for (i = 1; i < argc; i++)
	{
		if (argv[i][0] == '-')
		{
			switch (tolower((unsigned char) argv[i][1]))
			{
			case 'a':
				if (argv[i][2])
				{
					my_strlcpy(AnnoFileName, argv[i] + 2, sizeof(AnnoFileName));
				} else if (argv[i + 1] && argv[i + 1][0] != '/' && argv[i + 1][0] != '-')
				{
					my_strlcpy(AnnoFileName, argv[i + 1], sizeof(AnnoFileName));
					i++;
				}
				annotate = TRUE;
				break;
			case 'b':
				resolvebrowse = FALSE;
				break;
			case 'c':
				mode = MODE_GEN_CONTENT;
				break;
			case 'd':
				mode = MODE_LISTFILES;
				break;
			case 'e':
				mode = MODE_LIST_REFERENCES;
				break;
			case 'f':
				listtopic = TRUE;
				break;
			case 'g':
				guessing = FALSE;
				break;
			case 'h':					/* add entry to prefix table */
				for (j = 0; j < (int)(sizeof(prefix) / sizeof(prefix[0])) && prefix[j]; j++)
					;
				if (j < (int)(sizeof(prefix) / sizeof(prefix[0])))
				{
					if (argv[i][2])
					{
						prefix[j] = argv[i] + 2;
					} else if (argv[i + 1] && argv[i + 1][0] != '/' && argv[i + 1][0] != '-')
					{
						prefix[j] = argv[i + 1];
						i++;
					}
				} else
				{
					fprintf(stderr, "Prefix table full.\n");
				}
				break;
			case 'i':
				reportderived = TRUE;
				break;
			case 'l':
				mode = MODE_LIST_ENTRIES;
				break;
			case 'm':
				extractmacros = FALSE;
				break;
			case 'n':
				nopagebreak = TRUE;
				break;
			case 'p':
				mode = MODE_CHECK_REFERENCES;
				break;
			case 'r':
				mode = MODE_CREATE_RTF;
				break;
			case 's':
				if (argv[i][2])
				{
					TopicsPerRTF = atoi(argv[i] + 2);
				} else if (argv[i + 1] && argv[i + 1][0] != '/' && argv[i + 1][0] != '-')
				{
					TopicsPerRTF = atoi(argv[i + 1]);
					i++;
				}
				break;
			case 't':
				if (argv[i][2])
				{
					offset = strtoul(argv[i] + 2, NULL, 0);
				} else if (argv[i + 1] && argv[i + 1][0] != '/' && argv[i + 1][0] != '-')
				{
					offset = strtoul(argv[i + 1], NULL, 0);
					i++;
				}
				break;
			case 'x':
				mode = MODE_HEXDUMP;
				break;
			case 'y':
				overwrite = TRUE;
				break;
			case 'z':
				exportLZ77 = TRUE;
				break;
			default:
				fprintf(stderr, "unknown option '%s' ignored\n", argv[i]);
				break;
			}
		} else if (exportname)
		{
			fprintf(stderr, "additional parameter '%s' ignored\n", argv[i]);
		} else if (dumpfile)
		{
			exportname = argv[i];
		} else if (filename)
		{
			dumpfile = argv[i];
		} else
		{
			filename = argv[i];
		}
	}
	if (filename)
	{
		my_splitpath(filename, drive, dir, name, ext);
		if (ext[0] == '\0')
			strcpy(ext, ".hlp");
		mvp = ext[1] == 'M' || ext[1] == 'm';
		my_makepath(HelpFileName, drive, dir, name, ext);
		f = fopen(HelpFileName, "rb");
		if (f)
		{
			if (annotate)
			{
				if (AnnoFileName[0] == '\0')
					my_makepath(AnnoFileName, drive, dir, name, ".ann");
				AnnoFile = fopen(AnnoFileName, "rb");
				if (!AnnoFile)
				{
					fprintf(stderr, "Couldn't find annotation file '%s'\n", AnnoFileName);
				}
			}
			prefixhash[0] = 0L;
			for (i = 1; prefix[i]; i++)
			{
				prefixhash[i] = hash(prefix[i]);
			}
			if (!HelpDeCompile(f, dumpfile, mode, exportname, offset))
			{
				fprintf(stderr, "%s isn't a valid WinHelp file !\n", HelpFileName);
			}
			if (annotate && AnnoFile)
				my_fclose(AnnoFile);
			my_fclose(f);
		} else
		{
			fprintf(stderr, "Can not open '%s'\n", HelpFileName);
		}
	} else
	{
		usage(stderr);
		return 1;
	}
	return 0;
}
