/************************************************************************ * * * DECOMP.CPP * * * * Copyright (C) Microsoft Corporation 1993-1994 * * All Rights reserved. * * * ************************************************************************/ #include "stdafx.h" #pragma hdrstop #include "forage.h" #include "skip.h" #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif INLINE static PSTR STDCALL QchDecompressW(DWORD, PSTR, QPHR); static RC_TYPE STDCALL RcResolveQLA(QLA qla, QDE qde); static RC_TYPE STDCALL RcScanBlockOffset(QDE qde, GH gh, DWORD lcbRead, DWORD dwBlock, DWORD dwOffset, QVA qva, QOBJRG qobjrg); /*************************************************************************** * - Name: FixUpBlock - * Purpose: * Fixes up the Prev and Next pointers in the in-memory block image * from the MBHD. Once upon a time the compiler would generate the * wrong next and previous pointers in the block header. This routine * is called after calculating the correct values to place them into * the cached block image, so as not to again need recalculation should * the block again be requested while still in memory. * * Arguments: * qmbhd - pointer to MBHD containing the correct Next/Prev info * qbBuf - pointer to cached block, containing erroneous Next/Prev * wVersion - version number of the file we're dealing with * * Returns: * nothing * ***************************************************************************/ void STDCALL FixUpBlock(LPVOID qmbhd, LPVOID qbBuf, WORD wVersion) { if (wVersion != wVersion3_0) { ((QMBHD)qbBuf)->vaFCPPrev = ((QMBHD)qmbhd)->vaFCPPrev; ((QMBHD)qbBuf)->vaFCPNext = ((QMBHD)qmbhd)->vaFCPNext; } else { ((QMBHD)qbBuf)->vaFCPPrev.dword = VAToOffset30 (&((QMBHD)qmbhd)->vaFCPPrev); ((QMBHD)qbBuf)->vaFCPNext.dword = VAToOffset30 (&((QMBHD)qmbhd)->vaFCPNext); } } /*************************************************************************** * - Name CbDecompressQch - * Purpose * Decompresses the given string. * * Arguments * qchSrc -- String to be decompressed. * lcb -- size of string to be decompressed. * qchDest -- place to put decompressed string. * hphr -- handle to phrase table. * * Returns * Number of characters placed into the decompressed string. Returns * cbDecompressNil if it fails due to OOM. * * +++ * * Notes * Does not use huge pointers, so the source and destination buffers * cannot cross segment boundaries. Then why is the size of the * source passed as a long? I don't know. * ***************************************************************************/ int STDCALL CbDecompressQch(PSTR qchSrc, int lcb, PSTR qchDest, HPHR hphr, UINT wVersionNo) { DWORD wPhraseToken, wTokenMin, wTokenMax; PSTR qchStart, qchLast; QPHR qphr; /* * If hphr is NULL, then GetQFCINFO() should have thought we were * uncompressed and not called us. If GetQFCINFO() thinks we're * compressed, it means the uncompressed size is larger then the * compressed size, which almost certainly means we will break here * without hphr. */ ASSERT(hphr); if (hphr == NULL) { memmove(qchDest, qchSrc, lcb); return (WORD) lcb; } qphr = (QPHR) hphr; wTokenMin = qphr->wBaseToken; wTokenMax = qphr->wBaseToken + 2 * qphr->cPhrases; qchLast = qchSrc + lcb - 1; // Last possible position of a phrase token qchStart = qchDest; while (qchSrc < qchLast) { wPhraseToken = (((WORD) qchSrc[0]) << 8) + (BYTE) qchSrc[1]; if (wPhraseToken >= wTokenMin && wPhraseToken < wTokenMax) { qchDest = QchDecompressW(wPhraseToken, qchDest, qphr); if (qchDest == NULL) { return cbDecompressNil; } qchSrc += 2; ASSERT( qchSrc <= qchLast + 1 ); } else *qchDest++ = *qchSrc++; } // Check for last character if (qchSrc == qchLast) *qchDest++ = *qchSrc++; return qchDest - qchStart; } void STDCALL TranslateMFCP(LPVOID qvDst, LPVOID qvSrc, VA va, WORD wVersion) { QMFCP qmfcpSrc = (QMFCP) qvSrc; QMFCP qmfcpDst = (QMFCP) qvDst; // First copy whole structure to get the non-translated fields: *qmfcpDst = *qmfcpSrc; if (wVersion != wVersion3_0) return; OffsetToVA30(&(qmfcpDst->vaPrevFc), VAToOffset30(&va) - qmfcpSrc->vaPrevFc.dword); OffsetToVA30(&(qmfcpDst->vaNextFc), VAToOffset30(&va) + qmfcpSrc->vaNextFc.dword); } VA STDCALL VAFromQLA(QLA qla, QDE qde) { FVerifyQLA(qla); if (RcResolveQLA(qla, qde) == RC_Success) return qla->mla.va; else { VA vanil; vanil.dword = vaNil; return vanil; } } #ifdef _DEBUG void STDCALL FVerifyQLA(QLA qla) { ASSERT(qla != NULL); #ifdef MAGIC ASSERT(qla->wMagic == wLAMagic); #endif if (FResolvedQLA(qla)) { ASSERT(qla->mla.va.dword != vaNil); ASSERT(qla->mla.objrg != objrgNil); if (qla->wVersion != wVersion3_0) ASSERT(!FIsInvalidPA(qla->pa)); } } void STDCALL FVerifyQMOPG(QMOPG qmopg) { #ifdef MAGIC ASSERT(qmopg->bMagic == bMagicMOPG); #endif ASSERT(qmopg->libText >= 0); ASSERT(!qmopg->fStyle); ASSERT(!qmopg->fMoreFlags); ASSERT(qmopg->justify >= 0 && qmopg->justify <= JUSTIFYMOST); ASSERT(qmopg->ySpaceOver >= 0); ASSERT(qmopg->ySpaceUnder >= 0); ASSERT(qmopg->yLineSpacing >= -10000 && qmopg->yLineSpacing < 10000); ASSERT(qmopg->xRightIndent >= 0); ASSERT(qmopg->xFirstIndent >= -10000 && qmopg->xFirstIndent < 10000); ASSERT(qmopg->xTabSpacing >= 0 && qmopg->xTabSpacing < 10000); ASSERT(qmopg->cTabs >= 0 && qmopg->cTabs <= MAX_TABS); } #endif // _DEBUG /*************************************************************************** * - Name QchDecompressW - * Purpose * Given a phrase token and a pointer to a buffer, copies the * corresponding phrase to that buffer * * Arguments * wPhraseToken -- phrase token to be inserted. * qch -- buffer to place phrase. * qphr -- pointer to phrase table. * * Returns * A pointer to the character past the last character of the phrase * placed in the buffer. Returns NULL if unable to load the phrase * due to out of memory. * * +++ * * Notes * The phrase token includes an index into the phrase table, as * well as a flag indicating whether or not a space should be * appended to the phrase. * ***************************************************************************/ INLINE static PSTR STDCALL QchDecompressW(DWORD wPhraseToken, PSTR pszDst, QPHR qphr) { DWORD iPhrase; BOOL fSpace; WORD* pi; int cbPhrase; #ifdef _DEBUG PSTR pszPhrases; #endif ASSERT(qphr); pi = (WORD*) qphr->qcb; // Calculate iPhrase and fSpace: iPhrase = (DWORD) (wPhraseToken - qphr->wBaseToken); fSpace = iPhrase & 1; iPhrase >>= 1; ASSERT(iPhrase < (WORD) qphr->cPhrases); #ifdef _DEBUG pszPhrases = (PSTR) QFromQCb(pi, pi[iPhrase]); #endif cbPhrase = (int) pi[iPhrase + 1] - (int) pi[iPhrase]; MoveMemory(pszDst, QFromQCb(pi, pi[iPhrase]), cbPhrase); pszDst += cbPhrase; if (fSpace) *pszDst++ = ' '; return pszDst; } static RC_TYPE STDCALL RcResolveQLA(QLA qla, QDE qde) { RC_TYPE rc; int wErr; GH gh; int lcbRead; FVerifyQLA(qla); if (FResolvedQLA(qla)) return RC_Success; if (QDE_HFTOPIC(qde) == NULL) return RC_BadHandle; /* Read in the (possibly cached) block */ /* REVIEW: error return types? */ gh = GhFillBuf(qde, qla->pa.blknum, &lcbRead, &wErr); if (gh == NULL) return RC_Failure; rc = RcScanBlockOffset(qde, gh, lcbRead, qla->pa.blknum, qla->pa.objoff, &qla->mla.va, &qla->mla.objrg); if (rc != RC_Success) return rc; FVerifyQLA(qla); return RC_Success; } /* Given a block and an offset, return the FCID and OBJRG */ /* OBJRG is relative to the FC */ /*************************************************************************** * - Name: RcScanBlockOffset - * Purpose: ? * * Arguments: hf ? * qb This is originally a QchFillBuf() object, which * must be released by this procedure. * fcidMax ? * dwBlock ? * dwOffset ? * qfcid ? * qobjrg ? * * Returns: RC_Success or RC_Failure * * Globals Used: RC_Failure, RC_Success, etc? * * +++ * * Notes: * ***************************************************************************/ static RC_TYPE STDCALL RcScanBlockOffset(QDE qde, GH gh, DWORD lcbRead, DWORD dwBlock, DWORD dwOffset, QVA qva, QOBJRG qobjrg) { DWORD dwPrev; VA vaCur, vaT; MOBJ mobj; QMFCP qmfcp; MFCP mfcp; int wErr; PBYTE qb, qbBlock; MBHD mbhd; qbBlock = (PBYTE) gh; TranslateMBHD(&mbhd, qbBlock, QDE_HHDR(qde).wVersionNo); vaCur = mbhd.vaFCPNext; dwPrev = 0; for (;;) { // Before using qb, we ensure that we will still be looking inside the blk while (vaCur.bf.blknum == dwBlock && vaCur.bf.byteoff < lcbRead) { qb = qbBlock + vaCur.bf.byteoff; qmfcp = (QMFCP) qb; TranslateMFCP(&mfcp, qmfcp, vaCur, QDE_HHDR(qde).wVersionNo); CbUnpackMOBJ((QMOBJ)&mobj, (PBYTE)qmfcp + sizeof(MFCP)); /* * Does our given offset fall in this FC's range of object-region * numbers? */ if (dwOffset < dwPrev + mobj.wObjInfo) goto found_it; dwPrev += mobj.wObjInfo; vaT = vaCur; //ASSERT(qmfcp->ldichNextFc != (int) 0); vaCur = mfcp.vaNextFc; } /* NOTE: * In the case that a topic FC begins in the given block and ends * in the next, the object FC following it will also begin in the NEXT * block. But to make Larry's life easier we say that this object FC * belongs to our given block (as well as the block it lives in). So * if we are given an object offset bigger than the total object space * of the given block, we continue counting in the next block(s). * * So we increment the block num, read the next block, and prepare to * re-do the above WHILE loop until we find the FC we need. */ ++dwBlock; gh = GhFillBuf(qde, dwBlock, (int*) &lcbRead, &wErr); if (gh == NULL) { qva->dword = vaNil; *qobjrg = objrgNil; return RC_Failure; } qbBlock = (PBYTE) gh; } found_it: ASSERT(dwOffset >= dwPrev); *qva = vaCur; *qobjrg = (OBJRG)(dwOffset - dwPrev); return RC_Success; } // Perform 3.0 -> 3.5 addressing translation: void STDCALL TranslateMBHD(LPVOID qvDst, LPVOID qvSrc, WORD wVersion) { QMBHD qmbhdSrc = (QMBHD) qvSrc; QMBHD qmbhdDst = (QMBHD) qvDst; if (wVersion != wVersion3_0) *qmbhdDst = *qmbhdSrc; else { OffsetToVA30(&(qmbhdDst->vaFCPPrev), qmbhdSrc->vaFCPPrev.dword); OffsetToVA30(&(qmbhdDst->vaFCPNext), qmbhdSrc->vaFCPNext.dword); OffsetToVA30(&(qmbhdDst->vaFCPTopic), qmbhdSrc->vaFCPTopic.dword); } } /*------------------------------------------------------------------------- | CbUnpackMOBJ(qmobj, qv) | | | | Purpose: Unpack an MOBJ data structure. | -------------------------------------------------------------------------*/ int STDCALL CbUnpackMOBJ(QMOBJ qmobj, void* qv) { LPVOID qvFirst = qv; /* * Topic FCs are not packed, because the topic size needs to be * backpatched by the compiler. */ if (((QMOBJ) qv) ->bType == FCTYPE_TOPIC || ((QMOBJ) qv) ->bType == FCTYPE_TOPIC_COUNT) { qmobj->bType = *((LPBYTE) qv); qv = (((LPBYTE) qv) + 1); qmobj->lcbSize = *((int*) qv); qv = (((int*) qv) + 1); /* * If FC is uncounted, then it doesn't contain the last field in the * MOBJ, and we need to set wObjInfo to 0. Note that we cannot simply * copy the MOBJ structure because it is longer in Help 3.5: the MOBJ * for a Help 3.0 file (and any structure in general) may happen right * at the end of a segment. (See H3.5 739) */ if (qmobj->bType == FCTYPE_TOPIC_COUNT) { qmobj->wObjInfo = *((PWORD) qv); qv = (((PWORD) qv) + 1); } else qmobj->wObjInfo = 0; return ((INT) ((LPBYTE) qv - (LPBYTE) qvFirst)); } #ifdef MAGIC qmobj->bMagic = *((LPBYTE) qv); qv = (((LPBYTE) qv) + 1); ASSERT(qmobj->bMagic == bMagicMOBJ); #endif /* _DEBUG */ qmobj->bType = *((LPBYTE) qv); qv = (((LPBYTE) qv) + 1); qv = (LPVOID) QVSkipQGE((LPBYTE) qv, &qmobj->lcbSize); ASSERT(qmobj->lcbSize >= 0); if (qmobj->bType > MAX_UNCOUNTED_OBJ_TYPE) qv = QVSkipQGA(qv, (PWORD) &qmobj->wObjInfo); else qmobj->wObjInfo = 0; return((INT) ((LPBYTE) qv - (LPBYTE) qvFirst)); } /******************* * - 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. * ******************/ WORD STDCALL WCopyContext(QDE qde, VA vaPos, PSTR qchDest, int cb) { GH gh; PBYTE qb; int lcbRead, lcbT; int wErr; // Ignore cb of zero, will occur return wERRS_NONE; for beyond topic handles ASSERT(cb >= 0); if (cb <= 0L) return wERRS_NONE; // Initial fill of buffer -- should succeed if ((gh = GhFillBuf(qde, vaPos.bf.blknum, &lcbRead, &wErr)) == NULL) return wErr; qb = (PBYTE) gh; qb += vaPos.bf.byteoff; qb += sizeof(MFCP); lcbRead -= vaPos.bf.byteoff; lcbRead -= sizeof(MFCP); qchDest += sizeof(FCINFO); cb -= sizeof(FCINFO); ASSERT((int) 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. */ if (vaPos.bf.byteoff < sizeof(MBHD)) { /* * 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. */ qb += sizeof(MBHD) - vaPos.bf.byteoff; lcbRead -= sizeof(MBHD) - vaPos.bf.byteoff; } /* * ASSUMPTION!!! - the size of an FCP will never make it larger than * the file. */ lcbT = min(cb, lcbRead); memmove(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 = (PBYTE) gh; } return wERRS_NONE; }