/************************************************************************ * * * FORAGE.CPP * * * * Copyright (C) Microsoft Corporation 1993-1994 * * All Rights reserved. * * * ************************************************************************* * * * Module Intent * * * * This utility extracts useful info from the help file it is given. * * * ************************************************************************/ #include "stdafx.h" #include "fcpriv.h" #include "skip.h" #include "forage.h" #include "fspriv.h" #include "..\hwdll\zeck.h" #ifdef _DEBUG #include #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif static void STDCALL CallbackLphs(HS*, HANDLE); static void STDCALL CbReadMemQLA(QLA qla, LPBYTE qb, WORD wHelpVersion); static int STDCALL CbUnpackMOPG(QDE qde, QMOPG qmopg, LPVOID qv); static int STDCALL CbUnpackMTOP(QMTOP qmtop, LPVOID qv, int wHelpVer, VA vaTopic, int lcbTopic, VA vaPostTopicFC, int lcbTopicFC); static void STDCALL DestroyHphr(HPHR hphr); static BOOL STDCALL fFix30MobjCrossing(QMFCP qmfcp, MOBJ *pmobj, int lcbBytesLeft, QDE qde, int blknum, int* qwErr); static BOOL STDCALL FGetNextMLTFile(CInput*, PSTR); static BOOL STDCALL FGetSystemHeaderHfs(HFS hfs, QHHDR qhhdr); static BOOL STDCALL FVerifyVersionInfo(QHHDR qhhdr); static HFC STDCALL GetQFCINFO(QDE qde, VA va, HPHR hphr, int* qwErr); static void STDCALL GetTopicFCTextData(QFCINFO qfcinfo, QTOP qtop); static HBGH STDCALL HbghReadBitmapHfs(HFS hfs, int cBitmap, int *plcb); static HFC STDCALL HfcCreate(QDE qde, VA vaCurr, HPHR hphr, int* qwErr); static HFC STDCALL HfcFindPrevFc(QDE qde, VA vaPos, QTOP qtop, HPHR hphr, int* qwErr); static HFC STDCALL HfcNextPrevHfc(HFC hfc, BOOL fNext, QDE qde, int* qwErr, VA vaMarkTop, VA vaMarkBottom); static int STDCALL IDoForage(PSTR); static void STDCALL OutBitmapCountedInfo(QDE, LPBYTE, LPSTR, QOLS); static void STDCALL OutCommandInfo(DWORD dwRegion, BYTE bCmd); static void STDCALL OutError(void); static void STDCALL OutFCHeaderInfo(HFC hfc, QTOP qtop); static void STDCALL OutGidKeywords(QDE qde); static void STDCALL OutHashInfo(QDE); static void STDCALL OutHotspotInfo(LPBYTE); static void STDCALL OutLinkInfo(QDE, CHAR); static void STDCALL OutMOPGInfo(QMOPG); static void STDCALL OutObjectInfo(QDE, LPBYTE, LPSTR, QOLS); static void STDCALL OutParaGroupInfo(QDE, LPBYTE, LPSTR, QOLS); static void STDCALL OutSideBySideInfo(QDE, LPBYTE, LPSTR, QOLS); static void STDCALL OutTextInfo(LPSTR qchStart, DWORD dwRegionFirst, DWORD dwRegionLast); static void STDCALL OutTopicHeaderInfo(QTOP qtop, QPA qpa, VA va); static void STDCALL OutTopicTerminator(); static void STDCALL OutWarning(void); static LPBYTE STDCALL QobjLockHfc(HFC hfc); static RC_TYPE STDCALL RcFirstHbt(QBTHR qbthr, KEY key, LPVOID qvRec, QBTPOS qbtpos); static RC_TYPE STDCALL RcScanBlockVA(GH gh, DWORD lcbRead, LPVOID qmbhd, VA va, OBJRG objrg, DWORD FAR* qdwOffset, WORD wVersion); static VA STDCALL VaFromHfc(HFC hfc); static WORD STDCALL WGetIOError(void); const char txtMAPNAME[] = "|KWMAP"; FORAGE_CMD OutputType; DWORD dwMaxRegion; static PA paGlobal; void STDCALL forage(PSTR psz) { char szInFileName[_MAX_PATH]; int iRet = 0; OutputType = (FORAGE_CMD) *psz; psz = IsThereMore(psz); if (!psz) { MsgBox(IDF_NO_FILENAME); return; } psz = GetArg(szInFileName, psz); ChangeDirectory(szInFileName); if (psz && *psz) { // output file was specified pcout = new COutput(psz); if (!pcout->fInitialized) { OutSz(IDS_CANT_OPEN, psz); return; } char szFullPath[MAX_PATH]; PSTR pszFileName; if (GetFullPathName(psz, sizeof(szFullPath), szFullPath, &pszFileName) == 0) { OutSz(IDS_CANT_OPEN, psz); return; } else { OutSz(IDF_FORAGE_WRITING, szFullPath); } } CInput* pinput = NULL; { char szFile[256]; SzPartsFm(szInFileName, szFile, PARTBASE); wsprintf(szParentString, GetStringResource(IDS_FORAGE_GRIND), szFile); InitGrind(szParentString); } iRet = IDoForage(szInFileName); RemoveGrind(); if (pinput) delete pinput; if (pcout) { delete pcout; pcout = NULL; } SendStringToParent(GetStringResource(IDF_FORAGE_DONE)); return; } /*************************************************************************** FUNCTION: IDoForage PURPOSE: zero for success, else an error number PARAMETERS: pszFilename RETURNS: COMMENTS: This processes one document (.hlp, .wdc, whatever) file. Depending on the setting of OutputType, we get either the appropriate structure info or an indexer file. WARNING: Uses the global OutputType to see what to dump. MODIFICATION DATES: 12-Jan-1994 [ralphw] ***************************************************************************/ const int GRIND_INCREMENT = 20; static int STDCALL IDoForage(PSTR pszFilename) { DE de; FM fm = 0; HFS hfs = 0; HF hfTopic = 0; HPHR hphr = 0; int iForageRet = 0; VA vaBogus; VA vaMoreThanUCanChew; static DB db; QDE qde = (QDE) &de; UINT uiErr; BOOL fGidFile = FALSE; vaBogus.dword = vaNil; dwMaxRegion = (DWORD) 0; vaMoreThanUCanChew.bf.blknum = 0; ByteOff(vaMoreThanUCanChew) = sizeof(MBHD); // REVIEW: Is this safe? FM fmFile = FmNewSzDir(pszFilename, DIR_CURRENT); if (fmFile) { CharLower(fmFile); if (strstr(fmFile, ".gid")) fGidFile = TRUE; } else { Groan(rcFSError); OutSz(IDS_CANT_FIND, pszFilename); iForageRet = 1; goto error_return; } if (!(hfs = HfsOpenFm(fmFile, FS_OPEN_READ_ONLY))) { Groan(rcFSError); OutSz(IDS_CANT_OPEN, pszFilename); iForageRet = 1; goto error_return; } if (OutputType == ForageSystem) { iForageRet = OutSystemFile(hfs, pszFilename); goto error_return; } ZeroMemory(&de, sizeof(de)); ZeroMemory(&db, sizeof(db)); QDE_PDB(((QDE)&de)) = &db; if (!fGidFile) { if (!FReadSystemFile(hfs, QDE_PDB(((QDE) &de)), &uiErr, FALSE)) { OutSz(IDF_BAD_HEADER, pszFilename); iForageRet = 1; goto error_return; } PDB_HPHR(QDE_PDB(((QDE)&de))) = hphr = HphrLoadTableHfs(hfs, de.pdb->hhdr.wVersionNo); if (hphr == hphrOOM) OOM(); // doesn't return // BUGBUG Need to load hall compression tables if they exist. if ((hfTopic = HfOpenHfs((QFSHR) hfs, txtTopic, FS_OPEN_READ_ONLY)) == 0) { OutSz(IDF_NO_TOPICS, pszFilename); iForageRet = 1; goto error_return; } PDB_HFTOPIC(QDE_PDB(((QDE)&de))) = hfTopic; PDB_HALL(QDE_PDB(((QDE)&de))) = (hphr ? NULL : LoadJohnTables(hfs)); } PDB_HFS(QDE_PDB(((QDE)&de))) = hfs; QDE_FM(&de) = pszFilename; // // BUGBUG debug testing for now. Who creates this normaly? // if (!pcout) { char szFile[_MAX_PATH]; strcpy(szFile, pszFilename); ChangeExtension(szFile, "dmp"); pcout = new COutput(szFile); pcout->SupressNewline(); if (!pcout->fInitialized) { OutSz(IDS_CANT_OPEN, szFile); iForageRet = 1; goto error_return; } OutSz(IDF_FORAGE_WRITING, szFile); SendStringToParent(txtEol); } // At this point, we MUST have an output file ConfirmOrDie(pcout); TOP top; top.pszTitle = 0; top.pszEntryMacro = 0; top.cbTitle = 0L; /* * Initialization of DE fields. All DE fields which are used in the FC * manager go here. WARNING! HfTopic and Hhdr are both pointed to by the * PDB. */ /* * We now call CbUnpackMOPG, which need the DE aspect ratios. The * standard VGA ones will do. */ de.wXAspectMul = 96; de.wXAspectDiv = 144; de.wYAspectMul = 96; de.wYAspectDiv = 144; switch(OutputType) { case ForageHash: OutHashInfo((QDE)&de); break; case ForageKLinkInfo: if (fGidFile) OutGidKeywords(&de); else OutLinkInfo(&de, 'K'); break; case ForageALinkInfo: OutLinkInfo(&de, 'A'); break; default: HFC hfc; HFC hfcNew; int wErr; DWORD dwBlockCurr; DWORD dwBlockPrev; DWORD dwRegionCount; PA pa; VA vaCurr; hfc = HfcNear((QDE) &de, vaMoreThanUCanChew, &top, hphr, &wErr); vaCurr = VaFromHfc(hfc); if (wErr == wERRS_NONE) { char szBuf[256]; // Output header info for first topic, if there is one. dwBlockPrev = 0; dwRegionCount = 0; pa.blknum = VaFromHfc(hfc).bf.blknum; pa.objoff = dwRegionCount; if (OutputType == ForageTopics) { wsprintf(szBuf, GetStringResource(IDF_TOPIC_TITLES), QDE_FM(qde)); pcout->outstring_eol(szBuf); } OutTopicHeaderInfo(&top, &pa, vaCurr); } while (hfc && wErr == wERRS_NONE) { OLS ols; MOBJ mobj; PSTR qchText; PBYTE qbObj; VA vaNext; qbObj = QobjLockHfc(hfc); qchText = (PSTR) (qbObj + CbUnpackMOBJ(&mobj, qbObj)); qchText += mobj.lcbSize; vaCurr = VaFromHfc(hfc); dwBlockCurr = vaCurr.bf.blknum; if (dwBlockCurr != dwBlockPrev) { dwBlockPrev = dwBlockCurr; dwRegionCount = 0; } OutFCHeaderInfo(hfc, &top); ols.lichText = 0; ols.dwBlockCurr = dwBlockCurr; ols.dwcRegion = dwRegionCount; OutObjectInfo((QDE) &de, qbObj, qchText, (QOLS) &ols); dwRegionCount += (DWORD) mobj.wObjInfo; vaNext = ((QFCINFO) hfc) ->vaNext; hfcNew = HfcNextHfc(hfc, &wErr, (QDE)&de, vaBogus, vaBogus); if (wErr == wERRS_FCEndOfTopic) { OutTopicTerminator(); hfcNew = HfcNear((QDE) &de, vaNext, &top, hphr, &wErr); if (hfcNew != 0 && wErr == wERRS_NONE) { /* (kevynct) * Special case: If the Topic FC is in one block * and its first object FC is in a following block, * reset the object region count in the physical * address that we pass to the topic header dumper. * The real reset is done at the top of this loop. */ pa.blknum = VaFromHfc(hfcNew) .bf.blknum; if (pa.blknum != dwBlockCurr) pa.objoff = 0; else pa.objoff = dwRegionCount; OutTopicHeaderInfo(&top, &pa, vaCurr); } } lcFree(hfc); hfc = hfcNew; } if (OutputType == ForageRegions) { wsprintf(szParentString, GetStringResource(IDF_LARGESTOJBECT), dwMaxRegion + 1); SendStringToParent(szParentString); } break; } error_return: if (fm != fmNil) DisposeFm(fm); if (hphr != 0) DestroyHphr(hphr); if (hfTopic != 0) RcCloseHf(hfTopic); if (hfs != 0) RcCloseHfs(hfs); if (fmFile != fmNil) DisposeFm(fmFile); return iForageRet; } static void STDCALL OutObjectInfo(QDE qde, LPBYTE qbObj, PSTR qchText, QOLS qols) { MOBJ mobj; qbObj += CbUnpackMOBJ(&mobj, qbObj); switch (mobj.bType) { case FCTYPE_PARAGROUP: case FCTYPE_PARAGROUP_COUNT: OutParaGroupInfo(qde, qbObj, qchText, qols); break; case FCTYPE_SBYS_COUNT: OutSideBySideInfo(qde, qbObj, qchText, qols); break; case FCTYPE_BITMAP_COUNT: OutBitmapCountedInfo(qde, qbObj, qchText, qols); break; case FCTYPE_BITMAP: case FCTYPE_SBYS: case FCTYPE_WINDOW: case FCTYPE_WINDOW_COUNT: OutWarning(); pcout->outint(IDF_SKIPPING_FC, mobj.bType); break; default: OutWarning(); pcout->outint(IDF_ILLEGAL_FC, mobj.bType); break; } } /*------------------------------------------------------------------------+ | void OutBitmapCountedInfo(qde, qbObj, qchText, qols) | | | | For Rawhide indexing, grab the bitmap's indexable hotspots, if any, and | | insert their addresses and text into the indexfile stream. | +------------------------------------------------------------------------*/ static void STDCALL OutBitmapCountedInfo(QDE qde, LPBYTE qbObj, PSTR qchText, QOLS qols) { QOBM qobm; MOBJ mobj; BGH* qbgh; HBGH hbgh = 0; HBMH hbmh = 0; PBMH qbmh; int cBest; /* * Get the hotspot information from the bitmap. (The following code * somewhat similar to HbmaAlloc) */ qobm = (QOBM) (qbObj + CbUnpackMOBJ(&mobj, qbObj)); // Check for error in compile: if (!qobm->fInline && qobm->cBitmap < 0) { OutError(); SendStringToParent(IDF_SKIPPING_PICT); goto bitmap_return; } // Get pointer to group header if (qobm->fInline) qbgh = (BGH*) &qobm->cBitmap; else { hbgh = HbghReadBitmapHfs(QDE_HFS(qde), qobm->cBitmap, NULL); if (hbgh == 0) { OutError(); OutInt(IDF_CANT_READ_PICT, qobm->cBitmap); goto bitmap_return; } qbgh = (BGH*) hbgh; } // Always use the first bitmap in a MR group cBest = 0; if ((hbmh = HbmhExpandQv((PBYTE) qbgh + qbgh->acBmh[cBest])) == hbmhOOM || hbmh == hbmhInvalid) { OutError(); OutInt(IDF_CANT_DECOM_BMP, qobm->cBitmap); goto bitmap_return; } qbmh = (PBMH) hbmh; if (qbmh->cbSizeExtra != 0L) { /* * Enumerate the bitmap hotspots and index them if necessary. The * function CallbackLphs is called for each hotspot in the bitmap. I * apologize for the use of paGlobal. */ paGlobal.blknum = qols->dwBlockCurr; paGlobal.objoff = qols->dwcRegion; qols->dwBlockCurr = paGlobal.blknum; qols->dwcRegion = paGlobal.objoff; } bitmap_return: if (hbmh != 0) FreeHbmh((PBMH) hbmh); if (!qobm->fInline && hbgh != 0) lcFree(hbgh); } static void STDCALL CallbackLphs(HS* lphs, HANDLE hData) { paGlobal.objoff++; } static HBGH STDCALL HbghReadBitmapHfs(HFS hfs, int cBitmap, int* plcb) { char szBuffer[15]; HBGH hbgh; HF hf; int lcb; // Open file in file system CreateBitmapName(szBuffer, cBitmap); hf = HfOpenHfs((QFSHR) hfs, szBuffer, FS_OPEN_READ_ONLY); // Check for 3.0 file naming conventions: if (!hf && rcFSError == RC_NoExists) hf = HfOpenHfs((QFSHR) hfs, szBuffer + 1, FS_OPEN_READ_ONLY); // If file does not exist, just make OOM bitmap: if (!hf) return 0; // Allocate global handle lcb = LcbSizeHf(hf); if (!lcb) return 0; hbgh = (HBGH) lcMalloc(lcb); // Read in data if (LcbReadHf(hf, hbgh, lcb) != lcb) { ASSERT(FALSE); Panic(rcIOError); } if (plcb != NULL) *plcb = lcb; RcCloseHf(hf); return hbgh; } /*------------------------------------------------------------------------+ | void OutSideBySideInfo(qde, qbObj, qchText, ql, qols) | | | +------------------------------------------------------------------------*/ static void STDCALL OutSideBySideInfo(QDE qde, LPBYTE qbObj, PSTR qchText, QOLS qols) { QMSBS qmsbs; QMCOL qmcol; PBYTE qbObjChild; INT16 *qwChild; MOBJ mobj; qmsbs = (QMSBS) (qbObj); if (qmsbs->fAbsolute) qmcol = (QMCOL) (qmsbs + 1); else { PWORD qw = (PWORD) (qmsbs + 1); qmcol = (QMCOL) (qw + 1); } qmcol += (INT16) qmsbs->bcCol; for (qwChild = (INT16 *) qmcol; *qwChild != iColumnNil;) { qbObjChild = (PBYTE) qwChild + sizeof(INT16); OutObjectInfo(qde, qbObjChild, qchText, qols); qwChild = (INT16 *) (qbObjChild + CbUnpackMOBJ(&mobj, qbObjChild)); qwChild = (INT16 *) ((PBYTE) qwChild + mobj.lcbSize); } } /*------------------------------------------------------------------------+ | void OutParaGroupInfo(qde, qbObj, qchText, qols) | | | | Parses the command table, dumping stuff it finds on its way thru. | +------------------------------------------------------------------------*/ static void STDCALL OutParaGroupInfo(QDE qde, LPBYTE qbObj, PSTR qchText, QOLS qols) { PBYTE qbCom; MOBJ mobj; DWORD dwRegion; DWORD dwRegionStart; PSTR qchTextStart; PA pa; PSTR qchStart = NULL; INT cb; MOPG mopg; dwRegion = dwRegionStart = qols->dwcRegion; pa.blknum = qols->dwBlockCurr; qchText += qols->lichText; qchTextStart = qchText; qbCom = qbObj + (cb = CbUnpackMOPG(qde, &mopg, qbObj)); OutMOPGInfo(&mopg); for (;;) { if (*qchText == chCommand) { /* * Put out any text preceeding this command byte and * following the last command byte. */ if (qchStart != NULL) { pa.objoff = dwRegionStart; OutTextInfo(qchStart, dwRegionStart, dwRegion - 1); OutForageText(qchStart, (int) (qchText - qchStart), OutputType); qchStart = NULL; } OutCommandInfo(dwRegion, *qbCom); /* * Note that regions for wrapped and inline objects come * AFTER the region reserved for their command byte. So we * increment here. */ ++dwRegion; switch (*qbCom) { // One-byte commands case CMD_NEWLINE: case CMD_NEWPARA: case CMD_TAB: case CMD_END_HOTSPOT: qbCom++; break; // Three-byte commands case CMD_BLANK_LINE: case CMD_WORD_FORMAT: qbCom += 3; break; // Special formats case CMD_WRAP_LEFT: case CMD_WRAP_RIGHT: case CMD_INLINE_OBJ: /* * Note that regions for wrapped and inline objects * come BEFORE the region reserved for their command byte. */ qbCom++; cb = CbUnpackMOBJ(&mobj, qbCom); switch (mobj.bType) { case FCTYPE_BITMAP_COUNT: OLS ols; ols.lichText = qols->lichText; ols.dwBlockCurr = qols->dwBlockCurr; ols.dwcRegion = dwRegion; OutBitmapCountedInfo(qde, qbCom, qchText, (QOLS)&ols); /* * This is the number of extra hotspots (i.e. not * including the entire bitmap). */ dwRegion = ols.dwcRegion; break; default: break; } qbCom += cb; // Skip MOBJ qbCom += mobj.lcbSize; // NOTE: We currently do not handle embedded paragroup objs here break; case bEnd: ++qchText; goto end_of_text; default: ASSERT(FHotspot(*qbCom)); OutHotspotInfo(qbCom); if (FShortHotspot(*qbCom)) { qbCom += 5; } else if (FLongHotspot(*qbCom)) { qbCom++; qbCom += 2 + *((INT16 *)qbCom); } else { OutWarning(); pcout->outint(IDF_UNKNOWN_CMD, (INT16) *qbCom); } break; } } else { /* * If this is the first byte of text following a command byte, * remember its position. */ if (qchStart == NULL) { qchStart = qchText; dwRegionStart = dwRegion; } ++dwRegion; } ++qchText; } end_of_text: qols->lichText += qchText - qchTextStart; qols->dwcRegion = dwRegion; return; } /*------------------------------------------------------------------------+ | void OutHotspotInfo(qbCom) | | | | Puts out the hotspot type and its binding data, given its cmd table ptr | +------------------------------------------------------------------------*/ static void STDCALL OutHotspotInfo(LPBYTE qbCom) { if (OutputType == ForageBindings) { pcout->outint((int) *qbCom); pcout->outstring("..."); if (FShortHotspot(*qbCom)) { ++qbCom; pcout->outchar(' '); pcout->outint(*((DWORD FAR*) qbCom)); pcout->outeol(); } else { WORD w; WORD b; ++qbCom; w = *((PWORD)qbCom); b = 0; qbCom += 2; pcout->outchar('\t'); while (w-- > 0) { pcout->outint((int) *qbCom); if (++b % 10 == 0) { pcout->outeol(); pcout->outchar('\t'); } else pcout->outchar(' '); } pcout->outeol(); } } } /*************************************************************************** FUNCTION: OutTopicHeaderInfo PURPOSE: Puts out topic title and other info depending on global OutputType flag PARAMETERS: qtop qpa RETURNS: COMMENTS: MODIFICATION DATES: 21-Mar-1994 [ralphw] ***************************************************************************/ static void STDCALL OutTopicHeaderInfo(QTOP qtop, QPA qpa, VA va) { PSTR pszTitle; char szBuf[512]; char szUntitled[256]; if (qtop->pszTitle == 0) { strcpy(szUntitled, GetStringResource(IDF_NO_TITLE)); pszTitle = szUntitled; } else { pszTitle = qtop->pszTitle; } // REVIEW: The title is NULL-terminated, and lcb does not include the NULL switch (OutputType) { case ForageBindings: pcout->outstring("*** Topic"); pcout->outint(qtop->mtop.lTopicNo); pcout->outeol(); break; case ForageTopics: wsprintf(szBuf, GetStringResource(IDF_TOPIC_HEADER), qtop->mtop.lTopicNo + 1, pszTitle); pcout->outstring_eol(szBuf); break; case ForageStructs: { char chBuffer[24]; int ii; wsprintf(chBuffer, "0x%8x", va.dword); for (ii = 0; ii < 10; ii++) if (chBuffer[ii] == ' ') chBuffer[ii] = '0'; wsprintf(szBuf, "Topic %ld\t%10s\t%s", qtop->mtop.lTopicNo + 1, chBuffer, pszTitle); pcout->outstring_eol(szBuf); } break; } if (OutputType == ForageStructs) { qpa = (QPA) &qtop->mtop.prev; pcout->outint(IDF_PREV_NEXT, qpa->blknum, qpa->objoff); qpa = (QPA) &qtop->mtop.next; wsprintf(szBuf, " / %u.%u)\n", qpa->blknum, qpa->objoff); pcout->outstring(szBuf); pcout->outint(IDF_TOPIC_NO, qtop->mtop.lTopicNo); } } /*------------------------------------------------------------------------+ | void OutTopicTerminator() | +------------------------------------------------------------------------*/ static void STDCALL OutTopicTerminator() { if (OutputType == ForageStructs) pcout->outstring_eol(IDF_END_OF_TOPIC); } /*------------------------------------------------------------------------+ | void OutCommandInfo(DWORD dwRegion, BYTE bCmd) | | | | Translates a command byte to its English text description. | +------------------------------------------------------------------------*/ static void STDCALL OutCommandInfo(DWORD dwRegion, BYTE bCmd) { if (OutputType == ForageRegions) { pcout->outchar(CH_OPEN_PAREN); pcout->outint(dwRegion); pcout->outstring(") CMD: * "); dwMaxRegion = MAX(dwMaxRegion, dwRegion); switch (bCmd) { case bWordFormat: pcout->outstring(GetStringResource(IDF_FONT_CHANGE)); break; case bNewLine: pcout->outstring(GetStringResource(IDF_NEWLINE)); break; case bNewPara: pcout->outstring(GetStringResource(IDF_NEW_PARAGRAPH)); break; case bTab: pcout->outstring(GetStringResource(IDF_TAB)); break; case bBlankLine: pcout->outstring(GetStringResource(IDF_BLANK_LINE)); break; case bInlineObject: pcout->outstring(GetStringResource(IDF_INLINE_OBJECT)); break; case bWrapObjLeft: pcout->outstring(GetStringResource(IDF_LEFT_WRAP)); break; case bWrapObjRight: pcout->outstring(GetStringResource(IDF_RIGHT_WRAP)); break; case bEndHotspot: pcout->outstring(GetStringResource(IDF_END_HOTSPOT)); break; case bEnd: pcout->outstring(GetStringResource(IDF_END_TEXT)); break; default: if (FHotspot(bCmd)) pcout->outint(IDF_BEGIN_HOTSPOT, bCmd); else pcout->outstring(GetStringResource(IDF_BOGUS)); break; } } } /*------------------------------------------------------------------------+ | void OutTextInfo(PSTR qchStart, DWORD dwRegionFirst, DWORD dwRegionLast) | | | | Puts out the text of an FC to stdout, along with its region space info. | +------------------------------------------------------------------------*/ static void STDCALL OutTextInfo(PSTR qchStart, DWORD dwRegionFirst, DWORD dwRegionLast) { if (OutputType == ForageRegions) { PSTR qchT; INT i; DWORD dwch; DWORD dwoffs; ASSERT(dwRegionLast >= dwRegionFirst); dwch = dwRegionLast - dwRegionFirst + 1; dwoffs = dwRegionFirst; for (qchT = qchStart; dwch > 0;) { if (dwch == 1) wsprintf(szParentString, "(%ld)'", dwoffs); else wsprintf(szParentString, "(%ld to %ld) '", dwoffs, dwoffs + MIN(dwch, 30) - 1); int pos = strlen(szParentString); for (i = 0; dwch > 0 && i < 30; i++, dwch--, dwoffs++) szParentString[pos++] = *qchT++; strcpy(szParentString + pos, "\'\n"); pcout->outstring(szParentString); } dwMaxRegion = MAX(dwMaxRegion, dwRegionLast); } } /*------------------------------------------------------------------------+ | void OutFCHeaderInfo(hfc, qtop); | +------------------------------------------------------------------------*/ static void STDCALL OutFCHeaderInfo(HFC hfc, QTOP qtop) { QFCINFO qfcinfo; qfcinfo = (QFCINFO) hfc; if (OutputType == ForageStructs || OutputType == ForageRegions) { PSTR pszDst = szParentString; pcout->outint(IDF_VA, (int) (qfcinfo->vaCurr.dword), (int) qfcinfo->lcbDisk); pcout->outint(IDF_PREV_VA, (int) (qfcinfo->vaPrev.dword), (int) (qfcinfo->vaNext.dword)); if (qfcinfo->lcbText == (int) 0) pcout->outstring(GetStringResource(IDF_NO_TEXT)); else pcout->outint(IDF_BYTES_OF_TEXT, qfcinfo->lcbText, qfcinfo->ichText); pcout->outint(IDF_PREV_REGION, (int) qfcinfo->cobjrgP); } } /*------------------------------------------------------------------------+ | BOOL FGetNextMLTFile(fp, qch, icb) | | | | Given a handle to an open build (MLT) file, gets the next document | | filename to use from the build file. | | | | We keep existing filename extensions or add the Rawhide extension if | | no extension is present. | | | | WARNING! This code may be duplicated in other indexing tools that | | read the title file. It skips blank lines. | | | +------------------------------------------------------------------------*/ static BOOL STDCALL FGetNextMLTFile(CInput* pinput, PSTR pszFile) { // Scan over blank lines to the title do { if (!pinput->getline(pszFile)) return FALSE; } while (!*pszFile); // Scan over blank lines to the filename do { if (!pinput->getline(pszFile)) return FALSE; } while (!*pszFile); ChangeExtension(pszFile, ".hlp"); return TRUE; } /*------------------------------------------------------------------------+ | This code duplicates code in system.c. It reads the header from the | System file. | | +------------------------------------------------------------------------*/ static BOOL STDCALL FGetSystemHeaderHfs(HFS hfs, QHHDR qhhdr) { HF hf; int lcbSystemFile; BYTE *pBuffer; int lcbRead; hf = 0; // Open the |SYSTEM subsystem. if ((hf = HfOpenHfs((QFSHR) hfs, "|SYSTEM", FS_OPEN_READ_ONLY)) == 0) { Groan(rcFSError); return FALSE; } // Get the size of the |SYSTEM file, and read it into a buffer. lcbSystemFile = LcbSizeHf(hf); ASSERT(lcbSystemFile < 65535U); // REVEW: remove once we're 32 bits CMem mem(lcbSystemFile); if (!LcbReadHf(hf, mem.pb, lcbSystemFile)) { Groan(rcFSError); goto error_quit; } lcbRead = 0L; Ensure(RcCloseHf(hf), RC_Success); if (lcbSystemFile < sizeof(HHDR)) goto error_quit; // wError = wERRS_BADFILE; // Read in the first field of the HHDR, the Magic number. memmove((PSTR) &qhhdr->wMagic, mem.pb, sizeof(WORD)); if (qhhdr->wMagic != MagicWord) goto error_quit; // wError = wERRS_OLDFILE; pBuffer = mem.pb + sizeof(WORD); // Read in the rest of the fields, except for those that are new. memcpy(&qhhdr->wVersionNo, pBuffer, sizeof(WORD)); pBuffer += sizeof(WORD); memcpy(&qhhdr->wVersionFmt, pBuffer, sizeof(WORD)); pBuffer += sizeof(WORD); memcpy(&qhhdr->lDateCreated, pBuffer, sizeof(int)); pBuffer += sizeof(int); memcpy(&qhhdr->wFlags, pBuffer, sizeof(WORD)); /* * WARNING: Version dependency: Fix for Help 3.5 bug 488. The Help 3.0 * and 3.1 compilers do not initialize the wFlags bits. Only the fDebug * bit is used. */ if (qhhdr->wVersionNo == wVersion3_0) qhhdr->wFlags &= fDEBUG; if ((qhhdr->wMagic != MagicWord) #ifdef MAGIC || ((qhhdr->wVersionNo < VersionNo) && (qhhdr->wVersionNo != wVersion3_0) && (! (fDebugState & fDEBUGVERSION)))) #else || ((qhhdr->wVersionNo < VersionNo) && (qhhdr->wVersionNo != wVersion3_0))) #endif { // wError = wERRS_OLDFILE; goto error_quit; } #ifdef MAGIC if (!FVerifyVersionInfo(qhhdr) && !(fDebugState & fDEBUGVERSION)) #else if (!FVerifyVersionInfo(qhhdr)) #endif { Groan(RC_BadVersion); goto error_quit; } if ((qhhdr->wFlags & fDEBUG) != fVerDebug) { // wError = wERRS_DEBUGMISMATCH; goto error_quit; } return TRUE; error_quit: if (hf != 0) RcCloseHf(hf); // We ignore error distinctions for now return FALSE; } /*************************************************************************** FUNCTION: FVerifyVersionInfo PURPOSE: Verify that we are dealing with a 3.1 or 4.0 help file. PARAMETERS: qhhdr RETURNS: COMMENTS: MODIFICATION DATES: 12-Feb-1994 [ralphw] ***************************************************************************/ static BOOL STDCALL FVerifyVersionInfo(QHHDR qhhdr) { return (qhhdr->wVersionNo == wVersion3_0 || qhhdr->wVersionNo == wVersion3_5 || // really 3.1 qhhdr->wVersionNo == wVersion40); } /*------------------------------------------------------------------------+ | static void STDCALL OutHashInfo(qde) | +------------------------------------------------------------------------*/ static void STDCALL OutHashInfo(QDE qde) { RC_TYPE rc; BTPOS btpos; int lHash; int addr; char szBuf[1024]; wsprintf(szBuf, GetStringResource(IDF_HASH_NUMBERS), QDE_FM(qde)); pcout->outstring_eol(szBuf); QBTHR qbthr = HbtOpenBtreeSz("|CONTEXT", QDE_HFS(qde), FS_OPEN_READ_ONLY); if (!qbthr) { fTellParent = TRUE; OutError(); SendStringToParent(IDF_CANT_OPEN_HASH); fTellParent = FALSE; return; } rc = RcFirstHbt(qbthr, (KEY) (LPVOID) &lHash, &addr, &btpos); CStr cszFormat(IDF_TOPIC_ADDRESS); while (rc == RC_Success) { int wErr; HFC hfc; TOP top; VA va; LA la; CbReadMemQLA(&la, (PBYTE) &addr, QDE_HHDR(qde).wVersionNo); va = VAFromQLA(&la, qde); top.pszEntryMacro = NULL; top.pszTitle = 0; hfc = HfcNear(qde, va, &top, QDE_HPHR(qde), &wErr); if (hfc == 0) { if (wErr != wERRS_NONE) { OutError(); OutInt(IDF_MISSING_TOPIC, addr); } break; } wsprintf(szBuf, cszFormat, lHash, lHash, top.mtop.lTopicNo, top.pszTitle ? top.pszTitle : "untitled"); pcout->outstring(szBuf); if (top.pszEntryMacro) lcFree(top.pszEntryMacro); if (top.pszTitle != 0) lcFree(top.pszTitle); if (hfc != 0) lcFree(hfc); rc = RcNextPos(qbthr, &btpos, &btpos); if (rc != RC_Success) { if (rc != RC_NoExists) { OutError(); OutInt(IDF_RET_CODE, rc); } break; } rc = RcLookupByPos(qbthr, &btpos, (KEY) (LPVOID) &lHash, &addr); } if (qbthr) RcCloseBtreeHbt(qbthr); } static char szKWBtree[] = "|KWBTREE"; static char szKWData[] = "|KWDATA"; typedef struct { short iCount; LONG lOffset; } RECKW; const int KLINK_GRIND = 100; static void STDCALL OutLinkInfo(QDE qde, char ch) { RC_TYPE rc; BTPOS btpos; char szBuf[1024]; char szLink[256]; RECKW kwrec; LONG addr; int ii; char szTitle[512]; wsprintf(szBuf, GetStringResource(IDF_KEYWORD_LIST), ch, QDE_FM(qde)); pcout->outstring_eol(szBuf); szKWBtree[1] = ch; szKWData[1] = ch; QBTHR qbthrLink = HbtOpenBtreeSz(szKWBtree, QDE_HFS(qde), FS_OPEN_READ_ONLY); if (!qbthrLink) { wsprintf(szParentString, GetStringResource(IDF_NO_KEYWORDS), QDE_FM(qde), ch); fTellParent = TRUE; SendStringToParent(); fTellParent = FALSE; return; } QBTHR qbthrTitle = HbtOpenBtreeSz(txtTTLBTREENAME, QDE_HFS(qde), FS_OPEN_READ_ONLY); HF hfLnkData = HfOpenHfs((QFSHR) QDE_HFS(qde), szKWData, FS_OPEN_READ_ONLY); if (!hfLnkData) { fTellParent = TRUE; OutError(); wsprintf(szParentString, GetStringResource(IDF_CORRUPT_HELP), QDE_FM(qde)); SendStringToParent(); fTellParent = FALSE; goto LinkInfoExit; } rc = RcFirstHbt(qbthrLink, (KEY) (LPVOID) szLink, &kwrec, &btpos); while (rc == RC_Success) { LSeekHf(hfLnkData, kwrec.lOffset, 0); if (kwrec.iCount) { for (ii = 0; ii < kwrec.iCount; ii++) { if (LcbReadHf(hfLnkData, &addr, sizeof(addr)) != sizeof(addr)) goto LinkInfoExit; BTPOS btpos; if (!qbthrTitle) { wsprintf(szBuf, "%s\t%s", szLink, GetStringResource(IDF_NO_TITLE)); pcout->outstring_eol(szBuf); } else if (RcLookupByKey(qbthrTitle, (KEY) &addr, &btpos, szTitle) == RC_Success) { wsprintf(szBuf, "%s\t%s", szLink, szTitle); pcout->outstring_eol(szBuf); } else if (FValidPos(&btpos)) { RcLookupByPos(qbthrTitle, &btpos, (KEY) szTitle, NULL); } else { wsprintf(szBuf, "%s\t%s", szLink, GetStringResource(IDF_ERR_TITLE)); pcout->outstring_eol(szBuf); } } } else { wsprintf(szBuf, "%s\t%s", szLink, GetStringResource(IDF_ERR_TITLE)); pcout->outstring_eol(szBuf); } rc = RcNextPos(qbthrLink, &btpos, &btpos); if (rc != RC_Success) { if (rc != RC_NoExists) { OutError(); OutInt(IDF_RET_CODE, rc); } break; } rc = RcLookupByPos(qbthrLink, &btpos, (KEY) (LPVOID) szLink, &kwrec); } LinkInfoExit: if (qbthrLink) RcCloseBtreeHbt(qbthrLink); if (qbthrTitle) RcCloseBtreeHbt(qbthrTitle); if (hfLnkData) RcCloseHf(hfLnkData); } static void STDCALL OutMOPGInfo(QMOPG qmopg) { if (OutputType == ForageRegions) { int iTab; pcout->outint(IDF_LIB_TEXT, qmopg->libText); pcout->outint(IDF_LIB_STYLE, qmopg->fStyle); pcout->outint(IDF_LIB_MOREFLAGS, qmopg->fMoreFlags); pcout->outint(IDF_LIB_BOXED, qmopg->fBoxed); if (qmopg->fBoxed) { pcout->outstring(GetStringResource(IDF_LINE_TYPE)); switch (qmopg->mbox.wLineType) { case BOXLINENORMAL: pcout->outstring(GetStringResource(IDF_LINE_NORMAL)); break; case BOXLINETHICK: pcout->outstring(GetStringResource(IDF_LINE_THICK)); break; case BOXLINEDOUBLE: pcout->outstring(GetStringResource(IDF_LINE_DOUBLE)); break; case BOXLINESHADOW: pcout->outstring(GetStringResource(IDF_LINE_SHADOW)); break; case BOXLINEDOTTED: pcout->outstring(GetStringResource(IDF_LINE_DOTTED)); break; } pcout->outeol(); pcout->outstring(IDF_BOX_LINES); if (qmopg->mbox.fFullBox) pcout->outstring(IDF_BOX_FULL); if (qmopg->mbox.fTopLine) pcout->outstring(IDF_BOX_TOP); if (qmopg->mbox.fLeftLine) pcout->outstring(IDF_BOX_LEFT); if (qmopg->mbox.fBottomLine) pcout->outstring(IDF_BOX_BOTTOM); pcout->outeol(); } pcout->outstring(IDF_JUSTIFY); switch (qmopg->justify) { case JUSTIFYLEFT: pcout->outstring(IDF_BOX_LEFT); break; case JUSTIFYRIGHT: pcout->outstring(IDF_JUSTIFY_RIGHT); break; case JUSTIFYCENTER: pcout->outstring(IDF_JUSTIFY_CENTER); break; } pcout->outeol(); pcout->outint(IDF_SINGLE_LINE, qmopg->fSingleLine); pcout->outint(IDF_SPACE_OVER, qmopg->ySpaceOver); pcout->outint(IDF_SPACE_UNDER, qmopg->ySpaceUnder); pcout->outint(IDF_LINE_SPACING, qmopg->yLineSpacing); pcout->outint(IDF_LEFT_INDENT, qmopg->xLeftIndent); pcout->outint(IDF_RIGHT_INDENT, qmopg->xRightIndent); pcout->outint(IDF_FIRST_INDENT, qmopg->xFirstIndent); pcout->outint(IDF_TAB_SPACING, qmopg->xTabSpacing); pcout->outint(IDF_NUM_TAB_STOPS, qmopg->cTabs); for (iTab = 0; iTab < qmopg->cTabs; iTab++) { pcout->outint(IDF_TAB_PLACEMENT, (int) iTab, (int) qmopg->rgtab[iTab].x); switch (qmopg->rgtab[iTab].wType) { case TABTYPELEFT: pcout->outstring(GetStringResource(IDF_LEFT)); break; case TABTYPERIGHT: pcout->outstring(GetStringResource(IDF_RIGHT)); break; case TABTYPECENTER: pcout->outstring(GetStringResource(IDF_CENTER)); break; case TABTYPEDECIMAL: pcout->outstring(GetStringResource(IDF_DECIMAL)); break; } pcout->outeol(); } } } static void STDCALL OutError(void) { SendStringToParent(IDF_ERROR); } static void STDCALL OutWarning(void) { SendStringToParent(IDS_FWARNING); } static LPBYTE STDCALL QobjLockHfc(HFC hfc) { if (!hfc) // Bad handle return NULL; // Index past structure to data return (PBYTE) hfc + sizeof(FCINFO); } /******************* * - 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 * hphr - handle to phrase table to use in decompression * 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. * ******************/ static HFC STDCALL HfcFindPrevFc(QDE qde, VA vaPos, QTOP qtop, HPHR hphr, 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 int cbTopicFC = 0L, lcbRead; DWORD lcbTopic; QMBHD qmbhd; QMFCP qmfcp; QFCINFO qfcinfo; PBYTE qb; MOBJ mobj; MOBJ mobj2; // for gross HACK! HFC hfcTopic; HFC hfc; GH gh; // WARNING: For temporary fix MFCP mfcp; MBHD mbhd; *qwErr = wERRS_NONE; // Read the block which contains the position to start searching at: if ((gh = GhFillBuf(qde, vaPos.bf.blknum, &lcbRead, qwErr)) == NULL) { return FCNULL; } qmbhd = (QMBHD) gh; TranslateMBHD(&mbhd, qmbhd, QDE_HHDR(qde) .wVersionNo); // 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) gh) + vaNow.bf.byteoff); TranslateMFCP( &mfcp, qmfcp, vaNow, QDE_HHDR(qde).wVersionNo ); /* WARNING!! !! Temporary bug fix !! Remove this! */ /* If part of the MOBJ is in a different block from MFCP, */ /* read next block */ if (vaNow.bf.byteoff + sizeof(MFCP) + sizeof(MOBJ) > (DWORD) lcbRead) { if (fFix30MobjCrossing(qmfcp, &mobj, lcbRead - vaNow.bf.byteoff, qde, vaNow.bf.blknum, qwErr)) { return NULL; } } else { // The normal code. Leave this here. CbUnpackMOBJ(&mobj, (LPBYTE) qmfcp + sizeof(MFCP)); } ASSERT(mobj.bType > 0); ASSERT(mobj.bType <= MAX_OBJ_TYPE); if (mobj.bType == FCTYPE_TOPIC) { 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, hphr, 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)gh) + vaTopic.bf.byteoff); TranslateMFCP( &mfcp, qmfcp, vaTopic, QDE_HHDR(qde).wVersionNo ); if (vaTopic.bf.byteoff + sizeof(MFCP) + sizeof(MOBJ) > (DWORD) lcbRead) { if (fFix30MobjCrossing(qmfcp, &mobj2, lcbRead - vaTopic.bf.byteoff, qde, vaTopic.bf.blknum, qwErr)) { return NULL; } } else { // The normal code. Leave this here. CbUnpackMOBJ(&mobj2, (LPBYTE) qmfcp + sizeof(MFCP)); } ASSERT(mobj2.bType == FCTYPE_TOPIC); cbTopicFC = mfcp.lcbSizeCompressed; vaPostTopicFC = mfcp.vaNextFc; lcbTopic = mobj2.lcbSize; } ASSERT(cbTopicFC != 0L); qb = (LPBYTE)QobjLockHfc(hfcTopic); qb += CbUnpackMOBJ(&mobj, qb); // NOTE: Version dependency here. See qb += CbUnpackMTOP((QMTOP)&qtop->mtop, qb, QDE_HHDR(qde).wVersionNo, vaTopic, lcbTopic, vaPostTopicFC, cbTopicFC); 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 != addrNotNil && qtop->mtop.prev != addrNotNil)); hfc = HfcCreate(qde, vaNow, hphr, qwErr); if (hfc == NULL || *qwErr != wERRS_NONE) { lcFree(hfcTopic); return NULL; } qfcinfo = (QFCINFO) hfcTopic; GetTopicFCTextData(qfcinfo, 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; lcFree(hfcTopic); return hfc; } static void STDCALL DestroyHphr(HPHR hphr) { if (hphr == NULL) return; // No hphr to destroy! lcFree(((QPHR) hphr)->qcb); lcFree(hphr); } /******************* * - Name: HfcNextPrevHfc - * Purpose: Return the next or previous full context in the help file. * * Arguments: hfc - Handle to some full context in the file * fDir - direction - next FC if TRUE, previous if FALSE. * vaMarkTop - the first FC in this layout. * vaMarkBottom - the first FC in the next layout. * * Returns: FCNULL if at the end/beginning of the topic * * Notes: HfcNextHfc() and HfcPrevHfc() are macros calling this function. * ******************/ static HFC STDCALL HfcNextPrevHfc(HFC hfc, BOOL fNext, QDE qde, int* qwErr, VA vaMarkTop, VA vaMarkBottom) { VA va; QFCINFO qfcinfo; PBYTE qb; MOBJ mobj; int bType; HPHR hphr; *qwErr = wERRS_NONE; qfcinfo = (QFCINFO) hfc; ASSERT(qfcinfo->vaCurr.dword != vaNil); if (qfcinfo->vaCurr.dword == vaMarkTop.dword && !fNext) { *qwErr = wERRS_FCEndOfTopic; return FCNULL; } va = (fNext) ? qfcinfo->vaNext : qfcinfo->vaPrev; if (va.dword == vaMarkBottom.dword && fNext) { *qwErr = wERRS_FCEndOfTopic; return FCNULL; } hphr = qfcinfo->hphr; // (kevynct) * Note! The caller is responsible for freeing the old FC. if ((hfc = HfcCreate(qde, va, hphr, qwErr)) == FCNULL) return FCNULL; qb = (PBYTE)QobjLockHfc(hfc); CbUnpackMOBJ(&mobj, qb); #ifdef MAGIC ASSERT(mobj.bMagic == bMagicMOBJ); #endif bType = mobj.bType; if (bType == FCTYPE_TOPIC) { lcFree(hfc); *qwErr = wERRS_FCEndOfTopic; return FCNULL; } return hfc; } /*------------------------------------------------------------------------- | CbUnpackMOPG(qde, qmopg, qv) | | | | Purpose: Unpacks an MOPG data structure. | -------------------------------------------------------------------------*/ int STDCALL CbUnpackMOPG(QDE qde, QMOPG qmopg, LPVOID qv) { LPVOID qvFirst = qv; MPFG mpfg; INT iTab; #ifdef MAGIC qmopg->bMagic = *((PBYTE)qv); qv = (((PBYTE)qv) + 1); ASSERT(qmopg->bMagic == bMagicMOPG); #endif /* _DEBUG */ qv = QVSkipQGE((LPBYTE) qv, &qmopg->libText); mpfg = *((QMPFG) qv); qv = (((QMPFG) qv) + 1); // REVIEW qmopg->fStyle = mpfg.fStyle; ASSERT(!qmopg->fStyle); qmopg->fMoreFlags = mpfg.rgf.fMoreFlags; ASSERT(!qmopg->fMoreFlags); qmopg->fBoxed = mpfg.rgf.fBoxed; qmopg->justify = mpfg.rgf.justify; qmopg->fSingleLine = mpfg.rgf.fSingleLine; if (mpfg.rgf.fMoreFlags) qv = QVSkipQGE((LPBYTE) qv, &qmopg->lMoreFlags); else qmopg->lMoreFlags = 0; if (mpfg.rgf.fSpaceOver) { qv = QVSkipQGD((LPBYTE) qv, &qmopg->ySpaceOver); qmopg->ySpaceOver = YPixelsFromPoints(qde, qmopg->ySpaceOver); } else qmopg->ySpaceOver = 0; if (mpfg.rgf.fSpaceUnder) { qv = QVSkipQGD((LPBYTE) qv, &qmopg->ySpaceUnder); qmopg->ySpaceUnder = YPixelsFromPoints(qde, qmopg->ySpaceUnder); } else qmopg->ySpaceUnder = 0; if (mpfg.rgf.fLineSpacing) { qv = QVSkipQGD((LPBYTE) qv, &qmopg->yLineSpacing); qmopg->yLineSpacing = YPixelsFromPoints(qde, qmopg->yLineSpacing); } else qmopg->yLineSpacing = 0; if (mpfg.rgf.fLeftIndent) { qv = QVSkipQGD((LPBYTE) qv, &qmopg->xLeftIndent); qmopg->xLeftIndent = XPixelsFromPoints(qde, qmopg->xLeftIndent); } else qmopg->xLeftIndent = 0; if (mpfg.rgf.fRightIndent) { qv = QVSkipQGD((LPBYTE) qv, &qmopg->xRightIndent); qmopg->xRightIndent = XPixelsFromPoints(qde, qmopg->xRightIndent); } else qmopg->xRightIndent = 0; if (mpfg.rgf.fFirstIndent) { qv = QVSkipQGD((LPBYTE) qv, &qmopg->xFirstIndent); qmopg->xFirstIndent = XPixelsFromPoints(qde, qmopg->xFirstIndent); } else qmopg->xFirstIndent = 0; if (mpfg.rgf.fTabSpacing) qv = QVSkipQGD((LPBYTE) qv, &qmopg->xTabSpacing); else qmopg->xTabSpacing = 72; qmopg->xTabSpacing = XPixelsFromPoints(qde, qmopg->xTabSpacing); if (mpfg.rgf.fBoxed) { qmopg->mbox = *((QMBOX)qv); qv = (((QMBOX)qv) + 1); } if (mpfg.rgf.fTabs) qv = QVSkipQGD((LPBYTE) qv, &qmopg->cTabs); else qmopg->cTabs = 0; for (iTab = 0; iTab < qmopg->cTabs; iTab++) { qv = QVSkipQGA(qv, &qmopg->rgtab[iTab].x); if (qmopg->rgtab[iTab].x & 0x4000) qv = QVSkipQGA(qv, &qmopg->rgtab[iTab].wType); else qmopg->rgtab[iTab].wType = TABTYPELEFT; qmopg->rgtab[iTab].x = qmopg->rgtab[iTab].x & 0xBFFF; qmopg->rgtab[iTab].x = XPixelsFromPoints(qde, qmopg->rgtab[iTab].x); } FVerifyQMOPG(qmopg); return((INT) ((PBYTE)qv - (PBYTE)qvFirst)); } /***************************************************************************\ * - Function: RcFirstHbt( hbt, key, qvRec, qbtpos ) - * Purpose: Get first key and record from btree. * * ASSUMES * args IN: hbt * key - points to buffer big enough to hold a key * (256 bytes is more than enough) * qvRec - pointer to buffer for record or qNil if not wanted * qbtpos- pointer to buffer for btpos or qNil if not wanted * * PROMISES * returns: RC_Success if anything found, else error code. * args OUT: key - key copied here * qvRec - record copied here * qbtpos- btpos of first entry copied here * \***************************************************************************/ static RC_TYPE STDCALL RcFirstHbt(QBTHR qbthr, KEY key, LPVOID qvRec, QBTPOS qbtpos) { BK bk; PCACHE pcache; int cbKey, cbRec; PBYTE qb; if (qbthr->bth.lcEntries == 0) { if (qbtpos) { qbtpos->bk = bkNil; qbtpos->iKey = 0; qbtpos->cKey = 0; } return rcBtreeError = RC_NoExists; } bk = qbthr->bth.bkFirst; ASSERT( bk != bkNil ); if (!qbthr->pCache) RcMakeCache(qbthr); if (!(pcache = QFromBk(bk, qbthr->bth.cLevels - 1, qbthr))) return rcBtreeError; // REVIEW: 18-Jun-1994 [ralphw] This is probably broken since pcache // uses DWORD not BK. qb = pcache->db.rgbBlock + 2 * sizeof( BK ); cbKey = CbSizeKey((KEY) qb, qbthr, TRUE); if (key) memmove((LPVOID) key, qb, (int) cbKey); qb += cbKey; cbRec = CbSizeRec(qb, qbthr); if (qvRec) memmove(qvRec, qb, (int) cbRec); if (qbtpos) { qbtpos->bk = bk; qbtpos->iKey = 2 * sizeof(BK); qbtpos->cKey = 0; } return rcBtreeError = RC_Success; } /******************* * - 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 { void* gh; HF hf; DWORD ulBlknum; DWORD lcb; } BuffCache[] = { // size of cache is the number of initializers present. { NULL, NULL, blknumNil, 0 }, { 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, int* plcbRead, int* qwErr) { int i; int cbBlock_Size; // depends on version number... HF hfTopic, hfTopicCache; int lcbRet, lcbRead; PBYTE qbReadBuff; // Buffer compressed data read into. PBYTE qbRetBuff; // 16k buffer uncompressed data returned. BOOL fBlockCompressed = QDE_HHDR(qde).wFlags & fBLOCK_COMPRESSION; // 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_5); cbBlock_Size = cbBLOCK_SIZE; } hfTopic = hfTopicCache = QDE_HFTOPIC(qde); // 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 = (PBYTE) BuffCache[i].gh; lcbRet = BuffCache[i].lcb; *plcbRead = lcbRet; // return count of bytes read. // very simple sort-of LRU: iNextCache = (i + 1) % BLK_CACHE_SIZE; return(qbReadBuff); } } if (LSeekHf(hfTopic, blknum * cbBlock_Size, SEEK_SET) == -1) { *qwErr = WGetIOError(); if (*qwErr == wERRS_NONE) *qwErr = wERRS_FSReadWrite; return NULL; } // REVIEW: necessary to zero-allocate? qbReadBuff = (PBYTE) lcCalloc(cbBlock_Size); // Read full BLOCK_SIZE block: lcbRead = LcbReadHf(hfTopic, qbReadBuff, cbBlock_Size); if (lcbRead == -1 || !lcbRead) { lcFree(qbReadBuff); *qwErr = WGetIOError(); if (*qwErr == wERRS_NONE) *qwErr = wERRS_FSReadWrite; return NULL; } if (fBlockCompressed) { // TEST FOR ZECK COMPRESSION: // Allocate buffer to decompress into: qbRetBuff = (PBYTE) lcCalloc(cbMAX_BLOCK_SIZE + sizeof(MBHD)); // NOTICE: the first MBHD struct in every block is not compressed: *(QMBHD) qbRetBuff = *(QMBHD) qbReadBuff; lcbRet = LcbUncompressZeck(qbReadBuff + sizeof(MBHD), qbRetBuff + sizeof(MBHD), lcbRead - sizeof(MBHD)); ASSERT(lcbRet); lcbRet += sizeof(MBHD); // resize the buff based on the decompressed size: qbRetBuff = (PBYTE) lcReAlloc(qbRetBuff, lcbRet); lcFree(qbReadBuff); } else { // When no compression happens, the ret buff is the same as the read buff: qbRetBuff = qbReadBuff; lcbRet = lcbRead; } // Punt the LRU cache entry: if (BuffCache[iNextCache].gh != NULL) lcFree(BuffCache[iNextCache].gh); // Store the buffer in our cache: BuffCache[iNextCache].hf = hfTopicCache; BuffCache[iNextCache].ulBlknum = blknum; BuffCache[iNextCache].lcb = lcbRet; BuffCache[iNextCache].gh = qbRetBuff; iNextCache = (iNextCache + 1) % BLK_CACHE_SIZE; *plcbRead = lcbRet; // return count of bytes read. return qbRetBuff; } /******************* * - 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, int lcbBytesLeft, QDE qde, int blknum, int* qwErr ) { MOBJ mobjtmp; PBYTE bpsrc; PSTR bpdst; int i, c; int lcbRead; GH gh; // copy in the portion of the mobj that we have: bpsrc = (LPBYTE)qmfcp + sizeof(MFCP); bpdst = (PSTR)&mobjtmp; i = (INT) 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 = (LPBYTE) gh; bpsrc += sizeof(MBHD); // copy in the rest of the partial mobj: i = sizeof(MOBJ) - ((INT)lcbBytesLeft - sizeof(MFCP)); ASSERT( i ); for (; i > 0; i--) { *bpdst++ = *bpsrc++; c++; } ASSERT(c == sizeof(MOBJ)); CbUnpackMOBJ(pmobj, (LPBYTE)&mobjtmp); return(FALSE); // success } /*------------------------------------------------------------------------- | CbUnpackMTOP(qmtop, qv, wHelpVer) | | | | Purpose: Unpacks an MTOP data structure. | -------------------------------------------------------------------------*/ static int STDCALL CbUnpackMTOP(QMTOP qmtop, void* qv, int wHelpVer, VA vaTopic, int lcbTopic, VA vaPostTopicFC, int lcbTopicFC) { LPVOID qvFirst = qv; if (wHelpVer == wVersion3_0) { /* In Help 3.0, FCLs were int's cast to signed longs. Scary! * This is important because itoNil was (WORD) -1, not (int) -1. */ qmtop->prev = * ((int*) qv); qv = ((int*) qv) + 1; qmtop->next = * ((int*) qv); qv = ((int*) qv) + 1; ASSERT( itoNil == -1L); /* If this changes, we need to add some code * * here to translate */ qmtop->lTopicNo = -1; /* REVIEW: We really need a topic number type */ // Must manufacture the new 3.5 VA fields: // If the topic FC is the last FC in a block, and there is padding // between it and the end of the block, vaTopic + lcbTopic // will be #paddingbytes too small, but the scrollbar code should // handle this. // // In the case that there is no next sequential topic, we manufacture // an address by adding the length of the topic FC to the VA of the // topic FC. // OffsetToVA30( &(qmtop->vaNextSeqTopic), VAToOffset30(&vaTopic) + lcbTopic); qmtop->vaNSR.dword = vaNil; if (vaPostTopicFC.dword != vaNil) qmtop->vaSR = vaPostTopicFC; else OffsetToVA30( &(qmtop->vaSR), VAToOffset30(&vaTopic) + lcbTopicFC); return((INT) ((LPBYTE)qv - (LPBYTE)qvFirst)); } // No Packing with 3.5 -- just copy it whole-hog: *qmtop = *(QMTOP)qv; return( sizeof( MTOP ) ); #if 0 #ifdef MAGIC qmtop->bMagic = * (LPBYTE) qv; qv = ((LPBYTE) qv) + 1; ASSERT( qmtop->bMagic == bMagicMTOP ); #endif /* MAGIC */ mftp = *((QMFTP)qv); qv = (((QMFTP)qv) + 1); if (mftp.fMoreFlags) qv = QVSkipQGE(qv, &lMoreFlags); else lMoreFlags = 0L; if (mftp.fNextPrev) { qmtop->prev.addr = * ((int*) qv); qv = ((int*) qv) + 1; qmtop->next.addr = * ((int*) qv); qv = ((int*) qv) + 1; } else qmtop->prev.addr = qmtoJp->next.addr = addrNil; if( mftp.fHasNSR ) { qmtop->vaNSR = *((QVA)qv); qv = ((QVA)qv) + 1; } else { qmtop->vaNSR.dword = vaNil; } if( mftp.fHasSR ) { qmtop->vaSR = *((QVA)qv); qv = ((QVA)qv) + 1; } else { qmtop->vaSR.dword = vaNil; } if( mftp.fHasNextSeqTopic ) { qmtop->vaNextSeqTopic = *((QVA)qv); qv = ((QVA)qv) + 1; } else { qmtop->vaNextSeqTopic.dword = vaNil; ASSERT( 0 );// should not reach. } if (mftp.fTopicNo) qv = QVSkipQGB(qv, (int*)&qmtop->lTopicNo); else qmtop->lTopicNo = -1; return((INT) ((LPBYTE)qv - (LPBYTE)qvFirst)); #endif } /******************* * - 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) { PBYTE pb; DWORD lcb; if (qtop->pszTitle) lcClearFree(&qtop->pszTitle); if (qtop->pszEntryMacro) lcClearFree(&qtop->pszEntryMacro); qtop->cbTitle = 0; if (qfcinfo->lcbText == 0) return; pb = (PBYTE) qfcinfo + qfcinfo->ichText; lcb = 0; while ((lcb < qfcinfo->lcbText) && (*pb != '\0')) { pb++; lcb++; } ASSERT(lcb <= qfcinfo->lcbText); if (lcb > 0) { qtop->pszTitle = (PSTR) lcCalloc(lcb + 1); memmove(qtop->pszTitle, (PBYTE) qfcinfo + qfcinfo->ichText, lcb); qtop->pszTitle[lcb] = '\0'; qtop->cbTitle = lcb; } if (lcb + 1 < qfcinfo->lcbText) { qfcinfo->ichText += lcb + 1; lcb = qfcinfo->lcbText - (lcb + 1); if (!lcb) return; qtop->pszEntryMacro = (PSTR) lcCalloc(lcb + 1); memmove(pb, (PBYTE) qfcinfo + qfcinfo->ichText, lcb); qtop->pszEntryMacro[lcb] = '\0'; } } /******************* * - Name: VaFromHfc - * Purpose: Returns the address of a particular full context. * * Arguments: hfc - Handle to a full context * * Returns: -1L if an error is encounterd, vaBEYOND_TOPIC if the current * full context is not withing the topic, or the actual offset. * * Method: Gets value from structure stored at base of handle data * ******************/ static VA STDCALL VaFromHfc(HFC hfc) { return ((QFCINFO) hfc)->vaCurr; } /******************* * - 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!!! * ******************/ static HFC STDCALL HfcCreate(QDE qde, VA vaCurr, HPHR hphr, int* qwErr) { VA vaPrev, vaNext; HFC hfcNew; /* * If the current position of the FCP is beyond the end of the topic, * then create undef topic */ if (vaCurr.dword == vaNil) { vaPrev.dword = vaBEYOND_TOPIC; vaNext.dword = vaBEYOND_TOPIC; hfcNew = NULL; } else hfcNew = GetQFCINFO(qde, vaCurr, hphr, 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. * ******************/ static WORD STDCALL WGetIOError(void) { switch (rcFSError) { case RC_Success: return wERRS_NONE; break; case RC_OutOfMemory: return wERRS_OOM; break; case RC_DiskFull: return wERRS_DiskFull; break; default: return wERRS_FSReadWrite; break; } } static void STDCALL CbReadMemQLA(QLA qla, LPBYTE qb, WORD wHelpVersion) { #ifdef MAGIC qla->wMagic = wLAMagic; #endif switch (wHelpVersion) { case wVersion3_0: qla->wVersion = wVersion3_0; qla->fSearchMatch = TRUE; SetInvalidPA(qla->pa); /* * Help 3.0 used a int called an FCL. This maps directly * to a logical address with FCID = FCL, OBJRG = 0. * * Which then maps to a VA: */ OffsetToVA30( &qla->mla.va, *(int*)qb ); qla->mla.objrg = 0; break; case wVersion3_5: default: // Help 3.5 uses a int called a PA (Physical Address). qla->wVersion = wVersion3_5; qla->fSearchMatch = TRUE; qla->pa = *(QPA) qb; qla->mla.va.dword = 0; // Note: zero is special real address qla->mla.objrg = objrgNil; break; } FVerifyQLA(qla); } /******************* * - 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 * hphr - handle to phrase table * wVersion- help file version number * qwErr - pointer to error code word * * Returns: success: the requested HFC; * failure: FCNULL, and *qwErr gets error code * ******************/ static HFC STDCALL GetQFCINFO(QDE qde, VA va, HPHR hphr, int* qwErr) { QMFCP qmfcp; MFCP mfcp; GH gh; PBYTE 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; int lcbRead; /* The QMFCP should be at ich. */ /* since it cannot be split across*/ /* a block, we can read it from */ /* buffer. */ if ((gh = GhFillBuf(qde, va.bf.blknum, &lcbRead, qwErr)) == NULL) return FCNULL; qb = (LPBYTE) 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. */ TranslateMBHD( &mbhd, qb, QDE_HHDR(qde).wVersionNo ); 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) gh + vaT.bf.byteoff); TranslateMFCP( &mfcp, qmfcp, vaT, QDE_HHDR(qde).wVersionNo ); 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 = (LPBYTE) gh; TranslateMBHD( &mbhd, qb, QDE_HHDR(qde).wVersionNo ); 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. // FixUpBlock (&mbhd, qb, QDE_HHDR(qde) .wVersionNo); } /* (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) != RC_Success) { *qwErr = wERRS_OOM; /* Hackish guess... */ return FCNULL; } if ((gh = GhFillBuf(qde, va.bf.blknum, &lcbRead, qwErr)) == NULL) { return FCNULL; } qmfcp = (QMFCP)((PBYTE) gh + va.bf.byteoff ); TranslateMFCP( &mfcp, qmfcp, va, QDE_HHDR(qde).wVersionNo ); #ifdef MAGIC ASSERT((qmfcp)->bMagic == bMagicMFCP); #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 */ cbFCPCompressed = mfcp.lcbSizeCompressed - sizeof(MFCP) + sizeof(FCINFO); cbNonText = mfcp.ichText- sizeof(MFCP) + sizeof(FCINFO); 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 > 0L); ASSERT(cbFCPCompressed >= sizeof(FCINFO)); ASSERT(cbFCPUncompressed >= sizeof(FCINFO)); hfcNew = (HFC) lcMalloc(cbFCPCompressed); qfcinfo = (QFCINFO) 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 = hphr; qfcinfo->cobjrgP = (COBJRG) dwOffset; // Copy the data from disk *qwErr = WCopyContext(qde, va, (PSTR) qfcinfo, cbFCPCompressed); if (*qwErr != wERRS_NONE) { lcFree(hfcNew); return FCNULL; } // Create new handle and expand if the text is phrase compressed if (fCompressed) { hfcNew2 = (HFC) lcMalloc(cbFCPUncompressed); qfcinfo2 = (QFCINFO) hfcNew2; memmove(qfcinfo2, qfcinfo, cbNonText); if (hphr || !qde->pdb->lpJPhrase) { if (CbDecompressQch(((PSTR) qfcinfo) + cbNonText, cbTextCompressed, ((PSTR) qfcinfo2) + cbNonText, hphr, PDB_HHDR(qde->pdb).wVersionNo) == cbDecompressNil) OOM(); } else { if (DecompressJPhrase(((PSTR) qfcinfo) + cbNonText, (INT) cbTextCompressed, ((PSTR) qfcinfo2) + cbNonText, qde->pdb->lpJPhrase) == cbDecompressNil) OOM(); } lcFree(hfcNew); return hfcNew2; } return hfcNew; } /* REVIEW: Non-API public functions. Perhaps these function go somewhere else */ /* REVIEW: Do the Scan functions belong in the fcmanager? */ /* Takes a block, and given an FC with an FC object space co-ordinate * within the block, returns the block object space co-ordinate in qwOffset. */ static RC_TYPE STDCALL RcScanBlockVA(GH gh, DWORD lcbRead, LPVOID qmbhd, VA va, OBJRG objrg, DWORD FAR* qdwOffset, WORD wVersion) { DWORD dwCount = (DWORD)0; VA vaCur; MOBJ mobj; QMFCP qmfcp; MFCP mfcp; DWORD dwBlock; PBYTE qb; MBHD mbhd; dwBlock = va.bf.blknum; qb = (LPBYTE) gh; if (!qmbhd) { TranslateMBHD( &mbhd, qb, wVersion ); vaCur = mbhd.vaFCPNext; } else { vaCur = ((QMBHD)qmbhd)->vaFCPNext; } qb += vaCur.bf.byteoff; while (vaCur.bf.blknum == va.bf.blknum && vaCur.bf.byteoff < lcbRead ) { if (vaCur.dword == va.dword) break; /* * Move on to the next FC in the block, adding the current FC's * object space size to the running total. */ qmfcp = (QMFCP)qb; TranslateMFCP( &mfcp, qmfcp, vaCur, wVersion ); CbUnpackMOBJ(&mobj, (PBYTE)qmfcp + sizeof(MFCP)); // REVIEW: Add this: ASSERT(mobj.wObjInfo != dwCount += mobj.wObjInfo; //ASSERT(qmfcp->ldichNextFc != (int) 0); qb += mfcp.vaNextFc.bf.byteoff - vaCur.bf.byteoff; vaCur = mfcp.vaNextFc; } if (vaCur.dword != va.dword) { *qdwOffset = (DWORD) 0; return RC_BadArg; } *qdwOffset = dwCount + objrg; return RC_Success; } /*************************************************************************** FUNCTION: OutForageText PURPOSE: Either dumps text to the given file in .ANS (indexer-desired) format which is rather strange-looking, or puts to stdout. PARAMETERS: fid -- file handle dw qch lcb RETURNS: COMMENTS: Called by Forage MODIFICATION DATES: 13-Jan-1994 [ralphw] ***************************************************************************/ INLINE void STDCALL OutForageText(PSTR qch, int lcb, UINT OutputType) { if (lcb && OutputType == ForageText) pcout->outstring_eol(qch); } static char szConfigName[] = "|CF "; // 3 trailing spaces required int STDCALL OutSystemFile(HFS hfs, PCSTR pszFilename) { CMem memHdr(sizeof(HHDR)); CMem memDb(sizeof(DB)); CTable* ptblConfig = NULL; // We use a pointer to make it easier for this code to duplicate the code // in winhlp32\system.c QHHDR qhhdr = (QHHDR) memHdr.pb; PDB pdb = (PDB) memDb.pb; PDB_ADDRCONTENTS(pdb) = addrNil; PDB_RGCHCOPYRIGHT(pdb)[0] = '\0'; PDB_RGCHTITLE(pdb)[0] = '\0'; // Open the |SYSTEM subsystem. HF hf; if ((hf = HfOpenHfs((QFSHR) hfs, txtSystem, FS_OPEN_READ_ONLY)) == NULL) { Groan(rcFSError); return FALSE; } // Get the size of the |SYSTEM file, and read it into a buffer. cbSystemFile = LcbSizeHf(hf); CMem memSystem(cbSystemFile); PBYTE pSysBuf = (PBYTE) memSystem.pb; if (!LcbReadHf(hf, pSysBuf, cbSystemFile)) { Groan(rcFSError); RcCloseHf(hf); return FALSE; } pSysBufRead = pSysBuf; int cbRead = 0; RcCloseHf(hf); // Following code is FCheckSystem() in ..\winhlp32\system.c // Read in the first field of the HHDR, the Magic number. if ((!FReadBufferQch((PBYTE) &qhhdr->wMagic, sizeof(WORD))) || (qhhdr->wMagic != MagicWord)) { OutSz(IDF_OLD_FILE, pszFilename); return FALSE; } // Read in the rest of the fields, except for those that are new. if ((!FReadBufferQch((PBYTE) &qhhdr->wVersionNo, sizeof(WORD))) || (! FReadBufferQch((LPBYTE) &qhhdr->wVersionFmt, sizeof(WORD))) || (! FReadBufferQch((LPBYTE) &qhhdr->lDateCreated, sizeof(LONG))) || (! FReadBufferQch((LPBYTE) &qhhdr->wFlags, sizeof(WORD)))) { OutSz(IDF_OLD_FILE, pszFilename); return FALSE; } /* * WARNING: Version dependency: Fix for Help 3.5 bug 488. The Help 3.0 * and 3.1 compilers do not initialize the wFlags bits. Only the fDebug * bit is used. */ if (qhhdr->wVersionNo == wVersion3_0) qhhdr->wFlags &= fDEBUG; else if (qhhdr->wVersionNo < wVersion3_1 && qhhdr->wVersionNo != wVersion3_0) { OutSz(IDF_OLD_FILE, pszFilename); return FALSE; } else if (qhhdr->wVersionNo > VersionNo) { OutSz(IDF_OLD_FILE, pszFilename); return FALSE; } pcout->outstring_eol("[OPTIONS]"); int curHelpFileVersion = (int) PDB_HHDR(pdb).wVersionNo; if (curHelpFileVersion == wVersion3_0) { if (!FReadBufferQch((PBYTE) PDB_RGCHTITLE(pdb), cbSystemFile - cbRead)) { OutSz(IDF_OLD_FILE, pszFilename); return FALSE; } pcout->outstring("TITLE="); pcout->outstring_eol(pdb->rgchTitle); return TRUE; } int iWsmag = 0; WORD tagRead = tagFirst; for (;;) { if (cbRead == cbSystemFile || !FReadBufferQch((PBYTE) &tagRead, sizeof(WORD))) break; // Out of tags. ASSERT ((tagRead > tagFirst) && (tagRead < tagLast)); WORD cbwData; if (!FReadBufferQch((PBYTE) &cbwData, sizeof(WORD))) { OutSz(IDF_CORRUPTED, pszFilename); return FALSE; } int cbData = (int) cbwData; // The value of tagRead decides where we will read the data into. CMem mem(cbData + 16); if (tagRead != tagWindow) { if (!FReadBufferQch(mem.pb, cbData)) { OutSz(IDF_CORRUPTED, pszFilename); return FALSE; } } char szBuf[256]; switch (tagRead) { case tagTitle: pcout->outstring("TITLE="); pcout->outstring_eol(mem.psz); break; case tagCopyright: pcout->outstring("COPYRIGHT="); pcout->outstring_eol(mem.psz); break; case tagCitation: pcout->outstring("CITATION="); pcout->outstring_eol(mem.psz); break; case tagLCID: #ifdef _X86_ ASSERT(cbData == sizeof(KEYWORD_LOCALE)); #else ASSERT(cbData == sizeof(KEYWORD_LOCALE)-2); #endif { KEYWORD_LOCALE* pkwlcid = (KEYWORD_LOCALE*) mem.pb; wsprintf(szBuf, "LCID=0x%x 0x%x 0x%x\r\n", pkwlcid->langid, pkwlcid->fsCompareI, pkwlcid->fsCompare); pcout->outstring(szBuf); } break; case tagDefFont: wsprintf(szBuf, "DEFFONT=%s,%u,%u\r\n", mem.psz + 2, (int) mem.pb[0], (int) mem.pb[1]); pcout->outstring(szBuf); break; case tagCNT: pcout->outstring("CNT="); pcout->outstring_eol(mem.psz); break; case tagIndexSep: pcout->outstring("INDEX_SEPARATORS="); pcout->outstring_eol(mem.psz); break; case tagConfig: if (!ptblConfig) ptblConfig = new CTable; ptblConfig->AddString(mem); break; case tagWindow: // window tag. We collect all the wsmag structures into a single // block of memory, and hang that sucker off the de. if (!pdb->hrgwsmag) { /* * Block has not yet been allocated. We always allocate * the maximum size block, just because managing it as * variable size is more of a pain than it's worth. When we * go to multiple secondary windows and the number increases, * this will no longer be true. */ ASSERT (iWsmag == 0); pdb->hrgwsmag = (HWSMAG) lcMalloc(sizeof(RGWSMAG)); } else { // Increase the size to allow for the new window array pdb->hrgwsmag = (HWSMAG) lcReAlloc(pdb->hrgwsmag, sizeof(RGWSMAG) + sizeof(WSMAG) * (iWsmag + 1)); } qrgwsmag = (QRGWSMAG) pdb->hrgwsmag; // Increment the count of structures in the block, point at the // appropriate new slot, and copy in the new structure. CopyMemory(&qrgwsmag->rgwsmag[iWsmag++], pSysBufRead, sizeof(WSMAG)); qrgwsmag->cWsmag = iWsmag; FSkipReadBufferQch(cbData); break; default: break; } // switch (tagRead) } // for(;;) if (pdb->hrgwsmag) { pcout->outstring_eol("\r\n[WINDOWS]"); for (int i = 0; i < iWsmag; i++) { QRGWSMAG qrgwsmag = (QRGWSMAG) pdb->hrgwsmag; PWSMAG pwsmag = &qrgwsmag->rgwsmag[i]; pcout->outstring(pwsmag->rgchMember); pcout->outstring("=\""); if (pwsmag->grf & FWSMAG_CAPTION) pcout->outstring(pwsmag->rgchCaption); pcout->outstring("\",("); if (pwsmag->grf & FWSMAG_X) pcout->outint(pwsmag->x); pcout->outchar(','); if (pwsmag->grf & FWSMAG_Y) pcout->outint(pwsmag->y); pcout->outchar(','); if (pwsmag->grf & FWSMAG_DX) pcout->outint(pwsmag->dx); pcout->outchar(','); if (pwsmag->grf & FWSMAG_DY) pcout->outint(pwsmag->dy); pcout->outstring("),"); pcout->outint(pwsmag->wMax); if (pwsmag->grf & FWSMAG_RGBMAIN) { char szBuf[256]; wsprintf(szBuf, ",(r%u)", pwsmag->rgbMain); pcout->outstring(szBuf); } else if (pwsmag->grf & FWSMAG_RGBNSR || pwsmag->grf & (FWSMAG_AUTO_SIZE | FWSMAG_ABSOLUTE) || pwsmag->grf & FWSMAG_ON_TOP) pcout->outchar(','); if (pwsmag->grf & FWSMAG_RGBNSR) { char szBuf[256]; wsprintf(szBuf, ",(r%u)", pwsmag->rgbNSR); pcout->outstring(szBuf); } else if (pwsmag->grf & (FWSMAG_AUTO_SIZE | FWSMAG_ABSOLUTE) || pwsmag->grf & FWSMAG_ON_TOP) pcout->outchar(','); if (pwsmag->grf & (FWSMAG_AUTO_SIZE | FWSMAG_ABSOLUTE)) { UINT fsWin = (pwsmag->grf & FWSMAG_AUTO_SIZE) ? AUTHOR_AUTO_SIZE : 0; if (pwsmag->grf & FWSMAG_ON_TOP) fsWin |= AUTHOR_WINDOW_ON_TOP; if (pwsmag->grf & FWSMAG_ABSOLUTE) fsWin |= AUTHOR_ABSOLUTE; pcout->outstring(",f"); pcout->outint(fsWin); } else if (pwsmag->grf & FWSMAG_ON_TOP) { pcout->outchar(','); pcout->outchar('1'); } pcout->outstring_eol(txtZeroLength); } // Now add the [CONFIG-window] sections for (i = 0; i < iWsmag; i++) { QRGWSMAG qrgwsmag = (QRGWSMAG) pdb->hrgwsmag; PWSMAG pwsmag = &qrgwsmag->rgwsmag[i]; wsprintf(szConfigName + 3, "%u", i); if ((hf = HfOpenHfs(hfs, szConfigName, FS_OPEN_READ_ONLY)) == NULL) continue; // there was no config section for this window int cb = LcbSizeHf(hf); { CMem mem(cb); if (LcbReadHf(hf, mem.psz, cb) > 0) { CStr csz("\r\n[CONFIG-"); csz += pwsmag->rgchMember; csz += "]"; pcout->outstring_eol(csz); for (int i = 0; macropair[i].pszShort; i++) { if (nstrisubcmp(mem.psz, macropair[i].pszShort)) { CMem memTmp(strlen(mem.psz) + 100); strcpy(memTmp, mem.psz); ReplaceStrings(memTmp, macropair[i].pszShort, macropair[i].pszExpanded); TweakAddAccelerator(memTmp); pcout->outstring_eol(memTmp); break; } } if (!macropair[i].pszShort) { if (nstrisubcmp(mem.psz, "AddAccelerator")) { CMem memTmp(strlen(mem.psz) + 100); strcpy(memTmp, mem.psz); TweakAddAccelerator(memTmp); pcout->outstring_eol(memTmp); } else pcout->outstring_eol(mem.psz); } } } RcCloseHf(hf); } } if (ptblConfig) { pcout->outstring_eol("\r\n[CONFIG]"); for (int j = 1; j <= ptblConfig->CountStrings(); j++) { for (int i = 0; macropair[i].pszShort; i++) { if (nstrisubcmp(ptblConfig->GetPointer(j), macropair[i].pszShort)) { CMem mem(strlen(ptblConfig->GetPointer(j)) + 100); strcpy(mem, ptblConfig->GetPointer(j)); ReplaceStrings(mem, macropair[i].pszShort, macropair[i].pszExpanded); TweakAddAccelerator(mem); pcout->outstring_eol(mem); break; } } if (!macropair[i].pszShort) { if (nstrisubcmp(ptblConfig->GetPointer(j), "AddAccelerator")) { CMem mem(strlen(ptblConfig->GetPointer(j)) + 100); strcpy(mem, ptblConfig->GetPointer(j)); TweakAddAccelerator(mem); pcout->outstring_eol(mem); } else pcout->outstring_eol(ptblConfig->GetPointer(j)); } } } return TRUE; } typedef LONG MADR; // Master address; used as key to title btree typedef struct { DWORD cb; MADR amadr[CBBTREEBLOCKDEFAULT]; } MASTER_RECKW; static void STDCALL OutGidKeywords(QDE qde) { char szBuf[1024]; int cItems; wsprintf(szBuf, GetStringResource(IDF_KEYWORD_LIST), 'K', QDE_FM(qde)); pcout->outstring_eol(szBuf); szKWBtree[1] = 'K'; szKWData[1] = 'K'; HBT hbtKeywords = HbtOpenBtreeSz(szKWBtree, QDE_HFS(qde), FS_OPEN_READ_ONLY); if (!hbtKeywords) { wsprintf(szParentString, GetStringResource(IDF_NO_KEYWORDS), QDE_FM(qde), 'K'); fTellParent = TRUE; SendStringToParent(); fTellParent = FALSE; return; } HMAPBT hmapbt = HmapbtOpenHfs(QDE_HFS(qde), txtMAPNAME); if (!hmapbt) { fTellParent = TRUE; OutError(); wsprintf(szParentString, GetStringResource(IDF_CORRUPT_HELP), QDE_FM(qde)); SendStringToParent(); fTellParent = FALSE; goto LinkInfoExit; } Ensure(RcGetBtreeInfo(hbtKeywords, NULL, &cItems, NULL), RC_Success); int i; for (i = 0; i < cItems; i++) { Ensure(RcKeyFromIndexHbt(hbtKeywords, hmapbt, (KEY) szBuf, i), RC_Success); pcout->outstring_eol(szBuf); } LinkInfoExit: if (hbtKeywords) RcCloseBtreeHbt(hbtKeywords); if (hmapbt) RcCloseHmapbt(hmapbt); }