/*****************************************************************************
*																			 *
*  FCSUPPOR.C																 *
*																			 *
*  Copyright (C) Microsoft Corporation 1990.								 *
*  All Rights reserved. 													 *
*																			 *
******************************************************************************
*																			 *
*  Module Intent:  This module provides all the file format specific		 *
*				   routines for the full-context manager.  In particular,	 *
*				   this module is responsivle for reading in text,			 *
*				   parsing the text into full-context chunks, and providing  *
*				   those chunks to higher level routines.					 *
*																			 *
*****************************************************************************/

/* Here is a brief overview of the Topic file structure:
 *
 *
 *			  A Block					   The next block
 * |--------------------------------|------------------------------|-- . . .
 *	MBHD|<stuff>MFCP:MOBJ<stuff>...  MBHD|<stuff><stuff>MFCP:MOBJ<s MBHD|
 *
 * Every block is headed by an MBHD (memory block header) which contains:
 *	vaFCPPrev  -- previous FCP, in previous block.
 *	vaFCPNext  -- next FCP, 1st one in this block.
 *	vaFCPTopic -- next Topic FCP, may or may not be in this block.
 *
 * An MFCP is the header for a full-context portion (MFCP for Memory Full
 * Context Something?).  The MFCP contains:
 *	LcbSizeCompressed -- Size of WHOLE FC Post-Phrase-Compressed block.
 *	LcbSizeText -- Size of "text" after compression (text is whole block
 *	  not including the MFCP itself).
 *	vaPrevFC -- The MFCP before this one (maybe in prior block).
 *	vaNextFC -- the MFCP after this one  (maybe in next block).
 *	ichText  -- Offset into the <stuff> of the beginning of the actual text
 *	  (skipping commands?).
 *
 * An MOBJ (Memory Object) is the smallest unit of "stuff" -- command, text,
 * bitmap...  The MOBJ contains:
 *	bType -- the type of object, an enumeration.
 *	lcbSize -- the size of the object in bytes.
 *	wObjInfo -- ??
 *
 * The first object following a topic mfcp is an MTOP structure (memory
 * topic).	An MTOP congains:
 *	Prev -- Previous topic in browse sequence.
 *	Next -- Next topic in the browse sequence.
 *	lTopicNo -- topic number (??).
 *	vaNSR -- address of beginning of Non Scrollable Region, vaNil if none.
 *	vaSR  -- address of beginning of Scrollable Region, vaNil if none.
 *	vaNextSeqTopic -- next topic in the file.  Use for scrollbar position
 *	 approximation when faced with variable sized decompressed blocks.
 */

#include "help.h"
#pragma hdrstop

#include "inc\fcpriv.h"
#include "inc\compress.h"

_subsystem(FCMANAGE);

/*****************************************************************************
*																			 *
*								Prototypes									 *
*																			 *
*****************************************************************************/

static int STDCALL WCopyContext (QDE, VA, LPSTR, LONG);
static VOID STDCALL GetTopicFCTextData(QFCINFO, QTOP);

/*******************
 *
 - Name:	   GhFillBuf
 -
 * Purpose:    Reads, decompresses & returns one "BLOCK" from |Topic file.
 *
 * Arguments:  qde	   - To determine vernum & flags of help file.
 *			   blknum  - Block number to read.	We read starting at
 *						 X bytes into the | topic file where X is:
 *							X = blocknum * Block_Size
 *			   plcbRead- Where to return how many uncompressed bytes were
 *						 obtained.
 *			   qwErr   - Where to return error codes.
 *
 * Returns:    success: A global handle to the read block.
 *			   failure: NULL, and *qwErr gets error code.
 *
 * Block sizes vary -- in 3.0 files they were 2K, in 3.5 files they are
 *	  4K.  The block may or may not be "Zeck" block compressed.  We
 *	  decompress if "Zeck" compressed, but do not perform phrase
 *	  decompression (callers are responsible for that).
 *
 * This routine gets called MANY times repeatedly on the same blocks, so
 * we cache 3 decompressed blocks to speed up response time.  These
 * caches are not discardable, but could be if we recoded our callers
 * to deal with discarded blocks (ie call some new routines here).
 *
 ******************/

#define blknumNil ((DWORD)-1)

// This is the cache:

static struct s_read_buffs {
	GH	  gh;
	HF	  hf;
	DWORD ulBlknum;
	DWORD lcb;
} BuffCache[] = {					  // size of cache is the number of
	{ NULL, NULL, blknumNil, 0 },		// initializers present.
	{ NULL, NULL, blknumNil, 0 },
	{ NULL, NULL, blknumNil, 0 }
};

#define BLK_CACHE_SIZE (sizeof(BuffCache) / sizeof (BuffCache[0]))

static int iNextCache;	// psuedo LRU index.

GH STDCALL GhFillBuf(QDE qde, DWORD blknum, QUL plcbRead, int* qwErr)
{
	int i;
	LONG cbBlock_Size;	// depends on version number...
	HF	 hfTopic, hfTopicCache;
	LONG lcbRet, lcbRead;
	GH ghReadBuff;
	QB qbReadBuff;	// Buffer compressed data read into.
	QB qbRetBuff;				   // 16k buffer uncompressed data returned.
	GH ghRetBuff = NULL;
	BOOL fBlockCompressed = QDE_HHDR(qde).wFlags & fBLOCK_COMPRESSION;
#ifdef _DEBUG
	QRWFO qrwfo;
#endif

	// confirm argument validity:

	ASSERT(qde); ASSERT(plcbRead != NULL); ASSERT(qwErr != NULL);

	if (QDE_HHDR(qde).wVersionNo == wVersion3_0) {
		cbBlock_Size = cbBLOCK_SIZE_30;
	}
	else {
		ASSERT(QDE_HHDR(qde).wVersionNo >= wVersion3_1);
		cbBlock_Size = cbBLOCK_SIZE;
	}

	hfTopic = hfTopicCache = QDE_HFTOPIC(qde);
#ifdef _DEBUG
	qrwfo = (QRWFO) hfTopic;
#endif

	// Check for a cache hit:

	for (i = 0; i < BLK_CACHE_SIZE; ++i) {
		if (BuffCache[i].hf == hfTopicCache
				&& BuffCache[i].ulBlknum == blknum
				&& BuffCache[i].gh != NULL) {
			qbReadBuff = (LPBYTE) PtrFromGh(BuffCache[i].gh);
			lcbRet = BuffCache[i].lcb;
			ghRetBuff = BuffCache[i].gh;
			*plcbRead = lcbRet; 	  // return count of bytes read.

			// very simple sort-of LRU:

			iNextCache = (i + 1) % BLK_CACHE_SIZE;
			return(ghRetBuff);
		}
	}

	if (LSeekHf(hfTopic, blknum * cbBlock_Size, wFSSeekSet) == -1) {
		*qwErr = WGetIOError();
		if (*qwErr == wERRS_NO)
			*qwErr = wERRS_FSReadWrite;
		return NULL;
	}

	ghReadBuff = GhAlloc(GPTR, cbBlock_Size);
	if (!ghReadBuff) {
		*qwErr = wERRS_OOM;
		return(NULL);
	}
	qbReadBuff = (LPBYTE) PtrFromGh(ghReadBuff);
	ASSERT(qbReadBuff);

	// Read full BLOCK_SIZE block:

	lcbRead = LcbReadHf(hfTopic, qbReadBuff, cbBlock_Size);

	if (lcbRead == -1 || !lcbRead) {
		FreeGh(ghReadBuff);
		*qwErr = WGetIOError();
		if (*qwErr == wERRS_NO)
			*qwErr = wERRS_FSReadWrite;
		return NULL;
	}

	if (fBlockCompressed) {// TEST FOR ZECK COMPRESSION:

		// Allocate buffer to decompress into:
#ifdef _X86_
		ghRetBuff = GhAlloc(GPTR, cbMAX_BLOCK_SIZE + sizeof(MBHD));
#else
		LONG lcbMBHD;
		lcbMBHD = LcbStructSizeSDFF(QDE_ISDFFTOPIC(qde), SE_MBHD);

		ghRetBuff = GhAlloc( GPTR, cbMAX_BLOCK_SIZE+lcbMBHD );
#endif
		if (!ghRetBuff) {
			*qwErr = wERRS_OOM;
			return(NULL);
		}
		qbRetBuff = (LPBYTE) PtrFromGh(ghRetBuff);

		// NOTICE: the first MBHD struct in every block is not compressed:

		*(QMBHD) qbRetBuff = *(QMBHD) qbReadBuff;
#ifdef _X86_
		lcbRet = LcbUncompressZeck(qbReadBuff + sizeof(MBHD),
			qbRetBuff + sizeof(MBHD), lcbRead - sizeof(MBHD));
		ASSERT(lcbRet);
		lcbRet += sizeof(MBHD);
#else
		lcbRet = LcbUncompressZeck( qbReadBuff + lcbMBHD,
		 qbRetBuff + lcbMBHD, lcbRead - lcbMBHD);
		ASSERT(lcbRet);
		lcbRet += lcbMBHD;
#endif

		// resize the buff based on the decompressed size:

		ghRetBuff = (GH) GhResize(ghRetBuff, GMEM_FIXED, lcbRet);

		/* H3.1 1147 (kevynct) 91/05/27
		 *
		 * DANGER: We do not check success of the resize for a few lines.
		 */

		// Free the read buff since we're done with it:

		FreeGh(ghReadBuff);

		// DANGER: We now check success of above GhResize

		if (ghRetBuff == NULL) {
			*qwErr = wERRS_OOM;
			return(NULL);
		}
	}
	else {
		// When no compression happens, the ret buff is the same as the
		// read buff:
		ghRetBuff = ghReadBuff;
		qbRetBuff = qbReadBuff;
		lcbRet = lcbRead;
	}

	// Punt the LRU cache entry:

	if (BuffCache[iNextCache].gh != NULL)
		FreeGh(BuffCache[iNextCache].gh);

	// Store the buffer in our cache:

	BuffCache[iNextCache].hf = hfTopicCache;
	BuffCache[iNextCache].ulBlknum = blknum;
	BuffCache[iNextCache].lcb = lcbRet;
	BuffCache[iNextCache].gh = ghRetBuff;

	iNextCache = (iNextCache + 1) % BLK_CACHE_SIZE;

	*plcbRead = lcbRet; 		// return count of bytes read.
	return ghRetBuff;
}

/*******************
 *
 - Name:	   GetQFCINFO
 -
 * Purpose:    Creates HFC of correct size based on ich
 *
 * Arguments:  qfcinfo - far pointer to header of FCP
 *			   qde	   - ptr to DE -- our package of globals.
 *			   ich	   - file offset to copy
 *			   wVersion- help file version number
 *			   qwErr   - pointer to error code word
 *
 * Returns:    success: the requested HFC;
 *			   failure: FCNULL, and *qwErr gets error code
 *
 ******************/

HFC STDCALL GetQFCINFO(QDE qde, VA va, int* qwErr)
{
	QMFCP qmfcp;
	MFCP mfcp;
	GH	gh;
	QB	qb;
	DWORD dwOffset;
	HFC hfcNew; 					 // hfc from disk (possibly compress)*/
	HFC hfcNew2;					 // hfc after decompression
	DWORD cbFCPCompressed;			 // Size of in memory hfc from disk
	DWORD cbFCPUncompressed;		 // Size of in memory hfc after decom*/
	DWORD cbNonText;				 // Size of non-text portion of hfc
	DWORD cbTextCompressed; 		 // Size of compressed text
	DWORD cbTextUncompressed;		 // Size of uncompressed text
	BOOL fCompressed;				 // TRUE iff compressed
	QFCINFO qfcinfo;				 // Pointer for compressed HFC
	QFCINFO qfcinfo2;				 // Pointer for uncompressed HFC
	MBHD mbhd;
	DWORD lcbRead;
									 /* The QMFCP should be at ich. 	 */
									 /*   since it cannot be split across*/
									 /*   a block, we can read it from	 */
									 /*   buffer.						 */
#ifndef _X86_
	LONG lcbMFCP;
#endif

	if ((gh = GhFillBuf(qde, va.bf.blknum, &lcbRead, qwErr)) == NULL)
		return FCNULL;

#ifndef _X86_
	lcbMFCP = LcbStructSizeSDFF(QDE_ISDFFTOPIC(qde), SE_MFCP);
#endif

   	qb = PtrFromGh(gh);
	/* (kevynct)
	 * The following fixes a bug encountered with Help 3.0 files that
	 * shipped with the Win 3.0 SDK. We look at where the block header says
	 * the next FC is. If it points into the previous block (BOGUS) we need
	 * to seek back to find the correct address.
	 */

#ifdef _X86_
	TranslateMBHD(&mbhd, qb, QDE_HHDR(qde) .wVersionNo);
#else
	TranslateMBHD(&mbhd, qb, QDE_HHDR(qde) .wVersionNo, QDE_ISDFFTOPIC(qde));
#endif
	if (mbhd.vaFCPNext.bf.blknum < va.bf.blknum) {
		VA	vaT;
		VA	vaV;

		vaT = mbhd.vaFCPNext;
		if ((gh = GhFillBuf(qde, vaT.bf.blknum, &lcbRead, qwErr)) == NULL) {
			return FCNULL;
		}
		qmfcp = (QMFCP)((PBYTE)PtrFromGh( gh ) + vaT.bf.byteoff );
		if (QDE_HHDR(qde).wVersionNo != wVersion3_0)
#ifdef _X86_
			CopyMemory(&mfcp, qmfcp, sizeof(MFCP));
#else
			CopyMemory(&mfcp, qmfcp, lcbMFCP);
#endif
		else
#ifdef _X86_
			TranslateMFCP(&mfcp, qmfcp, vaT, QDE_HHDR(qde) .wVersionNo);
#else
			TranslateMFCP( &mfcp, qmfcp, vaT, QDE_HHDR(qde).wVersionNo, QDE_ISDFFTOPIC(qde) );
#endif
		vaV = mfcp.vaNextFc;

		// Now read the block we originally wanted. And fix up the pointers.

		if ((gh = GhFillBuf(qde, va.bf.blknum, &lcbRead, qwErr)) == NULL){
			return FCNULL;
		}
		qb = PtrFromGh( gh );
#ifdef _X86_
		TranslateMBHD(&mbhd, qb, QDE_HHDR(qde) .wVersionNo);
#else
		TranslateMBHD(&mbhd, qb, QDE_HHDR(qde) .wVersionNo,QDE_ISDFFTOPIC(qde));
#endif

		mbhd.vaFCPPrev = vaT;
		mbhd.vaFCPNext = vaV;

		// Patch the block in-memory image, so we won't have to do this
		// again while that block remains in memory.

#ifdef _X86_
		FixUpBlock(&mbhd, qb, QDE_HHDR(qde).wVersionNo);
#else
		FixUpBlock(&mbhd, qb, QDE_HHDR(qde).wVersionNo, QDE_ISDFFTOPIC(qde));
#endif
	}

	/* (kevynct)
	 * We now scan the block to calculate how many object regions come
	 * before this FC in this block's region space.  We use this number
	 * so that we are able to decide if a physical address points into
	 * an FC without needing to resolve the physical address.  We can
	 * also resolve the physical address with this number without going
	 * back to disk.  Note that FCID = fcid_given, OBJRG = 0 corresponds
	 * to the number we want.
	 *
	 * (We must have a valid fcidMax at this point.)
	 */

	if (RcScanBlockVA(gh, lcbRead, &mbhd, va, (OBJRG) 0, &dwOffset,
			QDE_HHDR(qde).wVersionNo) != rcSuccess) {
		*qwErr = wERRS_OOM; 	  // Hackish guess...
		return FCNULL;
	}

	if ((gh = GhFillBuf(qde, va.bf.blknum, &lcbRead, qwErr)) == NULL) {
		return FCNULL;
	}
	qmfcp = (QMFCP) ((PBYTE) PtrFromGh(gh) + va.bf.byteoff);
	if (QDE_HHDR(qde).wVersionNo != wVersion3_0)
#ifdef _X86_
		CopyMemory(&mfcp, qmfcp, sizeof(MFCP));
#else
		CopyMemory(&mfcp, qmfcp, lcbMFCP);
#endif
	else
#ifdef _X86_
		TranslateMFCP(&mfcp, qmfcp, va, QDE_HHDR(qde) .wVersionNo);
#else
		TranslateMFCP(&mfcp, qmfcp, va, QDE_HHDR(qde) .wVersionNo, QDE_ISDFFTOPIC(qde));
#endif

	/*
	 * Since we do not store the MFCP, the size on disk is the total
	 * size of the FCP - size of the memory FCP plus our special block of
	 * info used for FCManagement calls
	 */

#ifdef _X86_
	cbFCPCompressed   = mfcp.lcbSizeCompressed - sizeof(MFCP) + sizeof(FCINFO);
	cbNonText = mfcp.ichText- sizeof(MFCP) + sizeof(FCINFO);
#else
	cbFCPCompressed   = mfcp.lcbSizeCompressed - lcbMFCP + sizeof(FCINFO);
	cbNonText = mfcp.ichText- lcbMFCP + sizeof(FCINFO);
#endif
	cbTextCompressed   = mfcp.lcbSizeCompressed - mfcp.ichText;
	cbTextUncompressed = mfcp.lcbSizeText;
	cbFCPUncompressed  = cbNonText + cbTextUncompressed;

	/*
	 * If the compressed size is equal to the uncompressed, we assume no
	 * compression occurred.
	 */

	fCompressed = (cbFCPCompressed < cbFCPUncompressed) &&
				  (mfcp.lcbSizeText > 0);

	ASSERT(cbFCPCompressed	 >= sizeof(FCINFO));
	ASSERT(cbFCPUncompressed >= sizeof(FCINFO));

	if (cbFCPUncompressed < sizeof(FCINFO)) {
		*qwErr = wERRS_FSReadWrite;
		return FCNULL;
	}

	hfcNew = GhForceAlloc(0, cbFCPCompressed);
	ValidateF(hfcNew);

	qfcinfo = (QFCINFO) PtrFromGh(hfcNew);

	// Fill the FC structure

	qfcinfo->vaPrev  = mfcp.vaPrevFc;
	qfcinfo->vaCurr  = va;
	qfcinfo->vaNext  = mfcp.vaNextFc;
	qfcinfo->ichText = cbNonText;
	qfcinfo->lcbText = cbTextUncompressed;
	qfcinfo->lcbDisk = mfcp.lcbSizeCompressed;
	qfcinfo->hhf	 = QDE_HFTOPIC(qde);
	qfcinfo->hphr	 = qde->pdb->hphr;
	qfcinfo->cobjrgP = (COBJRG) dwOffset;

	// Copy the data from disk

	*qwErr = WCopyContext(qde, va, (LPSTR) qfcinfo, cbFCPCompressed);

	if (*qwErr != wERRS_NO) {
		FreeGh(hfcNew);
		return FCNULL;
	}

	// Create new handle and expand if the text is compressed

	if (fCompressed) {
		if ((hfcNew2 = GhForceAlloc(0, cbFCPUncompressed + 16)) == FCNULL) {
			FreeGh(hfcNew);
			*qwErr = wERRS_OOM;
			return FCNULL;
		}

		qfcinfo2 = (QFCINFO) PtrFromGh(hfcNew2);
		CopyMemory(qfcinfo2, qfcinfo, cbNonText);

		if (qde->pdb->hphr || !qde->pdb->lpJPhrase) {
			if (CbDecompressQch(((PCSTR) qfcinfo) + cbNonText,
					(int) cbTextCompressed,
					((LPSTR) qfcinfo2) + cbNonText, qde->pdb->hphr,
					PDB_HHDR(qde->pdb).wVersionNo) == DECOMPRESS_NIL)
				BOOM(wERRS_OOM_DECOMP_FAIL);
		}
		else {
			if (DecompressJPhrase(((PCSTR) qfcinfo) + cbNonText,
					(int) cbTextCompressed,
					((PSTR) qfcinfo2) + cbNonText,
					// REVIEW: can't we at least use void*?
					(UINT) qde->pdb->lpJPhrase) == DECOMPRESS_NIL)
				BOOM(wERRS_OOM_JDECOMP_FAIL);
		}

		FreeGh(hfcNew);
		return hfcNew2;
	}

	return hfcNew;
}

/*******************
 *
 - Name:	   HfcFindPrevFc
 -
 * Purpose:    Return the full-context less than or equal to the passed
 *			   offset.	Note that this routine hides the existence of
 *			   Topic FCs, so that if the VA given falls on a Topic FC,
 *			   this routine will return a handle to the first Object FC
 *			   following that Topic FC (if it exists).
 *
 * Arguments:  hhf		- Help file handle
 *			   ichPos	- Position within the topic
 *			   qtop 	- topic structure to fill in for the offset requested
 *			   wVersion - version of the system being used.
 *			   qwErr	- variable to fill with error from this function
 *
 * Returns:    nilHFC if error, else the requested HFC.  qwErr is filled with
 *			   error code if nilHFC is returned.
 *
 * Note:	   HfcNear is implemented as a macro using this function.
 *
 ******************/

// prototype this hackish 3.0 bug fixing routine:

static BOOL STDCALL fFix30MobjCrossing(QMFCP qmfcp, MOBJ *pmobj, LONG lcbBytesLeft,
	QDE qde, LONG blknum, int* qwErr);

HFC STDCALL HfcFindPrevFc(QDE qde, VA vaPos, QTOP qtop, int* qwErr)
{
	VA		  vaNow;	// VA of spot we are searching.
	VA		  vaTopic;	// VA of Topic we found
	VA		  vaPostTopicFC;	  // VA of first FC after Topic FC
	DWORD	  cbTopicFC = 0L, lcbRead;
	DWORD	  lcbTopic;
	QMBHD	  qmbhd;
	QMFCP	  qmfcp;
	QB		  qb;
	MOBJ	  mobj;
	MOBJ	  mobj2;			  // for gross HACK!
	HFC 	  hfcTopic;
	HFC 	  hfc;
	GH		  gh;
#ifndef _X86_
	LONG	  lcbMFCP;
#endif

	// WARNING: For temporary fix

	MFCP mfcp;
	MBHD mbhd;

	*qwErr = wERRS_NO;

	// Read the block which contains the position to start searching at:

	if ((gh = GhFillBuf(qde, vaPos.bf.blknum, &lcbRead, qwErr)) == NULL) {
		return FCNULL;
	}
#ifndef _X86_
	lcbMFCP = LcbStructSizeSDFF(QDE_ISDFFTOPIC(qde), SE_MFCP);
#endif
	qmbhd = PtrFromGh(gh);
#ifdef _X86_
	TranslateMBHD(&mbhd, qmbhd, QDE_HHDR(qde).wVersionNo);
#else
	TranslateMBHD(&mbhd, qmbhd, QDE_HHDR(qde).wVersionNo,QDE_ISDFFTOPIC(qde));
#endif

	// first topic in block:

	vaTopic = mbhd.vaFCPTopic;
	vaPostTopicFC.dword = vaNil;

	if ((vaPos.dword < mbhd.vaFCPNext.dword)
			&& (mbhd.vaFCPPrev.dword != vaNil )) //check for no-prev endcase
		vaNow = mbhd.vaFCPPrev;
	else
		vaNow = mbhd.vaFCPNext;
	for (;;) {
		if ((gh = GhFillBuf(qde, vaNow.bf.blknum, &lcbRead, qwErr)) == NULL) {
			return FCNULL;
		}
		qmfcp = (QMFCP)(((PBYTE)PtrFromGh( gh )) + vaNow.bf.byteoff);
		if (QDE_HHDR(qde).wVersionNo != wVersion3_0)
			CopyMemory(&mfcp, qmfcp, sizeof(MFCP));
		else
#ifdef _X86_
			TranslateMFCP(&mfcp, qmfcp, vaNow, QDE_HHDR(qde).wVersionNo);
#else
			TranslateMFCP(&mfcp, qmfcp, vaNow, QDE_HHDR(qde).wVersionNo,
						QDE_ISDFFTOPIC(qde));
#endif
#ifdef MAGIC
		ASSERT((qmfcp)->bMagic == bMagicMFCP);
#endif

		// If part of the MOBJ is in a different block from MFCP, read next block

#ifdef _X86_
		if (vaNow.bf.byteoff + sizeof(MFCP) + sizeof(MOBJ) > lcbRead) {
#else
		if (vaNow.bf.byteoff + lcbMFCP + lcbMaxMOBJ > lcbRead) {
#endif
			if (fFix30MobjCrossing(qmfcp, &mobj, lcbRead - vaNow.bf.byteoff, qde,
					vaNow.bf.blknum, qwErr)) {
				return NULL;
			}
		}
		else {

			// The normal code. Leave this here.

#ifdef _X86_
			CbUnpackMOBJ((QMOBJ)&mobj, (PBYTE)qmfcp + sizeof(MFCP));
#else
			CbUnpackMOBJ((QMOBJ)&mobj, (PBYTE)qmfcp + lcbMFCP, QDE_ISDFFTOPIC(qde));
#endif
		}

		ASSERT(mobj.bType  > 0);
		ASSERT(mobj.bType  <= MAX_OBJ_TYPE);

		if (mobj.bType == bTypeTopic) {
			vaTopic = vaNow;
			cbTopicFC = mfcp.lcbSizeCompressed;
			vaPostTopicFC = mfcp.vaNextFc;
			lcbTopic = mobj.lcbSize;
		}

		// KLUDGE:	WILL NOT WORK FOR MAGNETIC UPDATE!!!! (why? -Tom)

		if ((vaPos.dword < mfcp.vaNextFc.dword) &&
				(vaNow.dword != vaTopic.dword)) {
			break;
		}

		vaNow = mfcp.vaNextFc;

		/*
		 * The following test traps the case where we ask for the
		 * mysterious bogus Topic FC which always terminates the topic file.
		 */

		if (vaNow.dword == vaNil)
			return FCNULL;
	}	// for

	if ((hfcTopic = HfcCreate(qde, vaTopic, qwErr)) == FCNULL) {
		return FCNULL;
	}


	/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! *
	 * HACK ALERT	 HACK ALERT    HACK ALERT	HACK ALERT	 HACK ALERT   *
	 *																	  *
	 *																	  *
	 * PROBLEM:  We want to save the info about the first FC which		  *
	 * follows the topic FC and put it in the TOP struct.				  *
	 *																	  *
	 * If we are given an FC to a topic > 2K in length which is in a	  *
	 * different block than the topic FC, we will not find the topic	  *
	 * FC while scanning in the above FOR loop.  We use the fact that	  *
	 * cbTopicFC will become non-zero if we have found the topic FC.	  *
	 * Otherwise, we do not change the values in qtop (used in frame	  *
	 * code as FclFirstQde, etc., since we assume that they are valid and *
	 * have been set already.  This code will fail if we do not call this *
	 * function with an FC in the same block as the topic FC before any   *
	 * other FC is used.												  *
	 *																	  *
	 * TEMPORARY FIX: SEEK back to TOPIC FC to grab info		 -- kct   *
	 *																	  *
	 *																	  *
	 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

	/* (kevynct)
	 * vaPostTopicFC will also be uninitialized if cbTopicFC is 0,
	 * so we need to set that as well in this case.
	 */
	if (cbTopicFC == 0L) {
		if ((gh = GhFillBuf(qde, vaTopic.bf.blknum, &lcbRead, qwErr)) == NULL) {
			return FCNULL;
		}
		qmfcp = (QMFCP)(((PBYTE)PtrFromGh( gh )) + vaTopic.bf.byteoff);
		if (QDE_HHDR(qde) .wVersionNo != wVersion3_0)
			CopyMemory(&mfcp, qmfcp, sizeof(MFCP));
		else
#ifdef _X86_
			TranslateMFCP(&mfcp, qmfcp, vaTopic, QDE_HHDR(qde) .wVersionNo);
		if (vaTopic.bf.byteoff + sizeof(MFCP) + sizeof(MOBJ) > lcbRead) {
			if (fFix30MobjCrossing(qmfcp, &mobj2,
					lcbRead - vaTopic.bf.byteoff, qde,
					vaTopic.bf.blknum, qwErr)) {
				return NULL;
			}
#else
			TranslateMFCP(&mfcp, qmfcp, vaTopic, QDE_HHDR(qde) .wVersionNo,
							QDE_ISDFFTOPIC(qde));
		if (vaTopic.bf.byteoff + lcbMFCP + lcbMaxMOBJ > lcbRead) {
			if (fFix30MobjCrossing(qmfcp+ lcbMFCP, &mobj2,
					lcbRead - vaTopic.bf.byteoff - lcbMFCP, qde,
					vaTopic.bf.blknum, qwErr)) {
				return NULL;
			}
#endif
		}
		else {

		  // The normal code. Leave this here.

#ifdef _X86_
		  CbUnpackMOBJ((QMOBJ)&mobj2, (PBYTE)qmfcp + sizeof(MFCP));
#else
		  CbUnpackMOBJ((QMOBJ)&mobj2, (PBYTE)qmfcp + lcbMFCP,
						QDE_ISDFFTOPIC(qde));
#endif
		}
		ASSERT(mobj2.bType ==bTypeTopic);
		cbTopicFC = mfcp.lcbSizeCompressed;
		vaPostTopicFC = mfcp.vaNextFc;
		lcbTopic = mobj2.lcbSize;
	}
	ASSERT( cbTopicFC != 0L );

	qb = (PBYTE)QobjLockHfc(hfcTopic);
#ifdef _X86_
	qb += CbUnpackMOBJ((QMOBJ)&mobj, qb);
#else
	qb += CbUnpackMOBJ((QMOBJ)&mobj, qb, QDE_ISDFFTOPIC(qde));
#endif

	// NOTE: Version dependency here. See <version.h>

#ifdef _X86_
	qb += CbUnpackMTOP((QMTOP)&qtop->mtop, qb, QDE_HHDR(qde).wVersionNo,
		vaTopic, lcbTopic, vaPostTopicFC, cbTopicFC);
#else
	qb += CbUnpackMTOP((QMTOP)&qtop->mtop, qb, QDE_HHDR(qde).wVersionNo,
		vaTopic, lcbTopic, vaPostTopicFC, cbTopicFC, QDE_ISDFFTOPIC(qde));
#endif
	qtop->fITO = (QDE_HHDR(qde).wVersionNo == wVersion3_0);

	// If we are using pa's, then assert that they have been patched properly

	ASSERT( qtop->fITO ||
	  (qtop->mtop.next.addr != addrNotNil && qtop->mtop.prev.addr != addrNotNil));

	hfc = HfcCreate(qde, vaNow, qwErr);
	if (hfc == NULL || *qwErr != wERRS_NO) {
		FreeGh(hfcTopic);
		return NULL;
	}

	GetTopicFCTextData((QFCINFO) PtrFromGh(hfcTopic), qtop);

   /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! *
	*																	  *
	* The following reference to mobj is assumed to refer to a TOPIC FC   *
	* in which case lcbSize refers to the compressed length of the entire *
	* Topic (Topic FC+object FCs) (Was "backpatched" by HC).			  *
	*																	  *
	* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

	qtop->cbTopic = mobj.lcbSize - cbTopicFC;

	qtop->vaCurr = vaNow;

	FreeGh(hfcTopic);

	return hfc;
}

/*******************
 *
 - Name:	   fFix30MobjCrossing
 -
 * Purpose:    The Help 3.0 compiler had a bug where it allowed the MOBJ
 *			   directly following a Topic MFCP to cross from one 2K block
 *			   into the next.  This routine is called when that case is
 *			   detected (statistically pretty rare) and glues the two
 *			   pieces of the split MOBJ together.
 *
 * Arguments:  qmfcp	- pointer to MFCP we are looking at.
 *			   pmobj	- pointer to mobj in which to put the glued mobj.
 *			   lcbBytesLeft - number of bytes left in the qmfcp buffer.
 *			   qde		- DE of help file, so we can read more of it.
 *			   blknum	- block number of the block we are poking in.
 *
 * Returns:    FALSE if successful, TRUE otherwise.
 *
 ******************/

static BOOL STDCALL fFix30MobjCrossing(QMFCP qmfcp, MOBJ *pmobj, LONG lcbBytesLeft,
	QDE qde, LONG blknum, int* qwErr)
{
	MOBJ mobjtmp;
	QB bpsrc;
	PSTR bpdst;
	int i, c;
	LONG lcbRead;
	GH gh;

	// copy in the portion of the mobj that we have:

	bpsrc = (PBYTE)qmfcp + sizeof(MFCP);
	bpdst = (PSTR)&mobjtmp;

	i = lcbBytesLeft - sizeof(MFCP);
	ASSERT( i );
	c = 0;
	for( ; i > 0; i-- ) {
		*bpdst++ = *bpsrc++;
		c++;
	}

	// Read in the next block to get the rest of the MOBJ:

	if ((gh = GhFillBuf(qde, blknum + 1, &lcbRead, qwErr)) == NULL)
		return TRUE;

	bpsrc = (PBYTE)PtrFromGh(gh);
#ifdef _X86_
	bpsrc += sizeof(MBHD);
#else
	bpsrc += LcbStructSizeSDFF(QDE_ISDFFTOPIC(qde), SE_MBHD);
#endif

	// copy in the rest of the partial mobj:
	i = sizeof(MOBJ) - (lcbBytesLeft - sizeof(MFCP));
	ASSERT( i );
	for( ; i > 0; i-- ) {
		*bpdst++ = *bpsrc++;
		c++;
	}
	ASSERT( c == sizeof( MOBJ ) );
#ifdef _X86_
	CbUnpackMOBJ((QMOBJ)pmobj, (PBYTE)&mobjtmp);
#else
  CbUnpackMOBJ((QMOBJ)pmobj, (QB)&mobjtmp, QDE_ISDFFTOPIC(qde));
#endif

	return(FALSE);		 // success
}


/*******************
 *
 - Name:	   WCopyContext
 -
 * Purpose:    Copy the text of a full context into a global block of
 *			   memory;
 *
 * Arguments:  hhf	   - help file handle
 *			   ichPos  - position within that topic to start copying
 *			   qchDest - Where to copy the topic to
 *			   cb	   - number of bytes to copy
 *
 * Returns:    wERRS_NO on success, other error code if it was unable
 *			   to copy the text.
 *
 * Method:	   Copies partial or complete buffers to qchDest until
 *			   cb bytes have been copied.
 *
 ******************/

static int STDCALL WCopyContext(QDE qde, VA vaPos, PSTR qchDest, LONG cb)
{
  GH		gh;
  QB		qb;
  DWORD 	lcbRead, lcbT;
  int	   wErr;
#ifndef _X86_
  LONG	lcbMBHD;
  LONG	lcbMFCP;
#endif

  ASSERT(cb >= 0);
  if (cb <= 0L) 		  /* Ignore cb of zero, will occur	  */
	return wERRS_NO;	  /*   for beyond topic handles 	  */
						  /* Initial fill of buffer -- should */
						  /*   succeed						  */
  if ((gh = GhFillBuf(qde, vaPos.bf.blknum, &lcbRead, &wErr)) == NULL)
	return wErr;
#ifndef _X86_
  lcbMFCP = LcbStructSizeSDFF(QDE_ISDFFTOPIC(qde), SE_MFCP);
  lcbMBHD = LcbStructSizeSDFF(QDE_ISDFFTOPIC(qde), SE_MBHD);
#endif
  qb = (LPBYTE) PtrFromGh(gh);
  qb += vaPos.bf.byteoff;
#ifdef _X86_
  qb += sizeof(MFCP);
#else
  qb += lcbMFCP;
#endif
  lcbRead -= vaPos.bf.byteoff;
#ifdef _X86_
  lcbRead -= sizeof(MFCP);
#else
  lcbRead -= lcbMFCP;
#endif
  qchDest += sizeof(FCINFO);
  cb -= sizeof(FCINFO);
  ASSERT((LONG) lcbRead >= 0);		 // check for MFCP crossing 2K boundary.

  // Loop reading successive blocks until we've read cb bytes:

  for (;;) {
	/*
	 * The first sizeof(MBHD) bytes of a block are the block header, so
	 * skip them.
	 */

#ifdef _X86_
	if (vaPos.bf.byteoff < sizeof(MBHD)) {
#else
	if ((LONG)vaPos.bf.byteoff < lcbMBHD) {
#endif

	  /*
	   * Fix for bug 1636 (kevynct)
	   * ichPos was not being updated by the size of the block header
	   * when the block was first read in.
	   *
	   * Note that we update ichPos using IBlock(qch), so that it
	   * must be done before qch is increased.
	   */
#ifdef _X86_
	  qb += sizeof(MBHD) - vaPos.bf.byteoff;
	  lcbRead -= sizeof(MBHD) - vaPos.bf.byteoff;
#else
	  qb += lcbMBHD - vaPos.bf.byteoff;
	  lcbRead -= lcbMBHD - vaPos.bf.byteoff;
#endif
	}

	/*
	 * ASSUMPTION!!! - the size of an FCP will never make it larger than
	 * the file.
	 */

	lcbT = min((DWORD) cb, lcbRead);
	MoveMemory(qchDest, qb, lcbT);
	cb -= lcbT;
	vaPos.bf.blknum += 1;
	vaPos.bf.byteoff = 0;
	ASSERT(cb >= 0);					// cb should never go negative
	qchDest += lcbT;

	if (cb == 0)
		break;				// FCP is now copied
	ASSERT(cb >= 0);

	if ((gh = GhFillBuf(qde, vaPos.bf.blknum, &lcbRead, &wErr)) == NULL)
		return wErr;
	qb = PtrFromGh(gh);
  }
  return wERRS_NO;
}

/*******************
 *
 - Name:	   HfcCreate
 -
 * Purpose:    Creates a new full context
 *
 *
 * Arguments:  hhf	   - help file handle
 *			   ifcCurr - position of FCP to create handle for.
 *			   qwErr   - pointer to error code word
 *
 * Returns:    handle to a full context.  FCNULL is returned if an
 *			   error occurs, in which case *qwErr gets the error code
 *
 * Notes:	   ifcCurr MUST POINT TO THE START OF AN FCP!!!
 *
 ******************/

HFC STDCALL HfcCreate(QDE qde, VA vaCurr, int* qwErr)
{
	VA vaPrev, vaNext;
	HFC hfcNew;
								 /* If the current position of the	 */
	if (vaCurr.dword == vaNil)	 /*   FCP is beyond the end of the	 */
	{							 /*   topic, then create undef topic */
		vaPrev.dword = vaBEYOND_TOPIC;
		vaNext.dword = vaBEYOND_TOPIC;
		hfcNew	= NULL;
	}
	else
		hfcNew = GetQFCINFO(qde, vaCurr, qwErr);

	return (hfcNew);
}

/*******************
 *
 - Name:	  WGetIOError()
 -
 * Purpose:   Returns an error code that is purportedly related to
 *			  the most recent file i/o operation.
 *
 * Returns:   the error code (a wERRS_* type deal)
 *
 * Note:	  We here abandon pretense of not using FS.
 *
 ******************/

WORD STDCALL WGetIOError(void)
{
  switch (RcGetFSError()) {
	case rcSuccess:
	  return wERRS_NO;
	  break;

	case rcOutOfMemory:
	  return wERRS_OOM;
	  break;

	case rcDiskFull:
	  return wERRS_DiskFull;
	  break;

	default:
	  return wERRS_FSReadWrite;
	  break;
	}
  }

/*******************
 *
 - Name:	  GetTopicFCTextData
 -
 * Purpose:   Places the title, title size and the entry macro in the
 *			  TOP structure.
 *
 * Returns:   Nothing.
 *
 * Note:	  If there is not enough memory for the title or the entry
 *			  macro, the handle is set to NULL and no error is given.
 *
 ******************/

static VOID STDCALL GetTopicFCTextData(QFCINFO qfcinfo, QTOP qtop)
{
	QB	 qbT;
	DWORD lcb;

	if (qtop->hTitle != NULL)
		FreeGh(qtop->hTitle);

	if (qtop->hEntryMacro != NULL)
		FreeGh(qtop->hEntryMacro);

	qtop->hTitle	  = NULL;
	qtop->hEntryMacro = NULL;
	qtop->cbTitle	  = 0;

	if (qfcinfo->lcbText == 0)
		return;

	qbT = ((PBYTE) qfcinfo) + qfcinfo->ichText;

	/*
	 * If only a title is specified, it isn't null-terminated, so we can't
	 * do a strlen. It only gets null-terminated if there is also an
	 * auto-entry macro.
	 */

	lcb = 0;
	while ((lcb < qfcinfo->lcbText) && (*qbT != '\0')) {
		qbT++;
		lcb++;
	}

	ASSERT(lcb <= qfcinfo->lcbText);

	if ((lcb > 0) && ((qtop->hTitle = GhAlloc(GPTR, (LONG) lcb + 1)) != NULL)) {
		qbT = PtrFromGh(qtop->hTitle);
		MoveMemory(qbT, (PBYTE) qfcinfo + qfcinfo->ichText, (LONG) lcb);
		*((LPSTR) qbT + lcb) = '\0';
		qtop->cbTitle = lcb;
	}

	if (lcb + 1 < qfcinfo->lcbText) {
		qfcinfo->ichText += lcb + 1;
		lcb = qfcinfo->lcbText - (lcb + 1);
		if (lcb == 0)
			return;

		if ((qtop->hEntryMacro = GhAlloc(GPTR, (LONG) lcb + 1)) != NULL) {
			qbT = PtrFromGh(qtop->hEntryMacro);
			MoveMemory(qbT, (PBYTE) qfcinfo + qfcinfo->ichText, (LONG) lcb);
			*((LPSTR) qbT + lcb) = '\0';
		}
	}
}

/***************************************************************************
 *
 -	Name:	  FlushCache()
 -
 *	Purpose:  Discard contents of 4K-block cache.
 *
 *		  The cache tracks blocks within a file, but does not correctly
 *	track between files because the file-handle is recorded, but not the
 *	full file name.  This is PTR  1036 for help 3.1.  The fix is to flush
 *	the entire cache when a new file is loaded so that we don't get
 *	false cache-hits if the file handle of a 2nd file happens to be the
 *	same as a previous file.
 *
 *	Arguments:	None
 *
 *	Returns:	Nothing
 *
 *	Globals Used: Cacheing array BuffCache[].
 *
 ***************************************************************************/

VOID STDCALL FlushCache(void)
{
	int i;

	for (i = 0; i < BLK_CACHE_SIZE; ++i) {
		if (BuffCache[i].gh != NULL) {
			FreeGh(BuffCache[i].gh);
		}
		BuffCache[i].hf = NULL;
		BuffCache[i].gh = NULL;
		BuffCache[i].ulBlknum = blknumNil;
		BuffCache[i].lcb = 0;
	}
}