mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1149 lines
31 KiB
1149 lines
31 KiB
/*****************************************************************************
|
|
* *
|
|
* 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;
|
|
}
|
|
}
|