{ This file is part of the Free Pascal Integrated Development Environment Copyright (c) 2000 by Berczi Gabor Help support for Windows Help (.HLP) files See the file COPYING.FPC, included in this distribution, for details about the copyright. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. **********************************************************************} {$R-} unit WWinHelp; {$ifdef cpullvm} {$modeswitch nestedprocvars} {$endif} interface uses Objects, WUtils,WHelp; const WinHelpMagicNo = $00035F3F; WinHelpBTreeHeaderMagicNo = $293B; WinHelpSystemHeaderMagicNo = $036c; { WinHelp Btree dataformat descriptors } wh_bdf_Long = 'L'; wh_bdf_ASCIIZ = 'F'; wh_bdf_ASCIIZ2 = 'i'; wh_bdf_Short = '2'; wh_bdf_Long2 = '4'; wh_bdf_ASCIIZ3 = 'z'; { WinHelp system record type } wh_srt_Title = 1; wh_srt_Copyright = 2; wh_srt_Contents = 3; wh_srt_Config = 4; wh_srt_Icon = 5; wh_srt_Window = 6; wh_srt_Citation = 8; wh_srt_LangID = 9; wh_srt_Content = 10; wh_srt_DefFont = 11; wh_srt_FtIndex = 12; wh_srt_Groups = 13; wh_srt_KeyIndex = 14; wh_srt_Language = 18; wh_srt_DLLMaps = 19; type PInteger = ^integer; TWinHelpHeader = packed record MagicNo : longint; DirectoryStart : longint; NonDirectoryStart: longint; EntireFileSize : longint; end; TWinHelpFileEntryHeader = packed record ReservedSpace : longint; UsedSpace : longint; FileFlags : byte; end; TWinHelpRecordHeader = packed record RecordType : word; DataSize : word; end; TWinHelpBTreeHeader = packed record Magic : word; Flags : word; { 0x0002 always set, 0x0400 set if directory } PageSize : word; DataFormat : array[1..16] of char; MustBeZero : word; { $0000 } PageSplits : word; RootPage : word; MustBeNegOne : integer; { $ffff } TotalPages : word; NumLevels : word; TotalEntries : word; Pages : record end; end; TWinHelpBTreeIndexHeader = packed record Unknown : longint; NumEntries : word; { no of index-entries } PrevPage : integer; end; { TWinHelpBTreeIndexEntry = packed record FileName : STRINGZ; PageNumber : word; end; } TWinHelpBTreeNodeHeader = packed record Unknown : longint; NumEntries : word; { no of index-entries } PrevPage : integer; NextPage : integer; end; TWinHelpSystemHeader = packed record Magic : word; Version : word; Always1 : word; GenDate : longint; Flags : word; end; TWinHelpPhrIndexHeader = packed record Magic : longint; NumEntries : longint; CompressedSize : longint; PhrImageSize : longint; PhrImageCompressedSize: longint; Always0 : longint; BitCount_Unk : word; { BitCount = lower 4 bits } Dunno : word; end; TWinHelpTopicBlockHeader = packed record LastTopicLink : longint; FirstTopicLink : longint; LastTopicHeader : longint; end; TWinHelpTopicBlock = packed record Header : TWinHelpTopicBlockHeader; Data : record end; end; TWinHelpTopicHeader = packed record BlockSize : longint; PrevOffset : longint; { prev topic } NextOffset : longint; { next topic } TopicNumber : longint; NonScrollRgnOfs : longint; { start of non-scrolling region (topic ofs) } ScrollRgnOfs : longint; { topic ofs } NextTopic : longint; { next type 2 record } end; TWinHelpTopicLink = packed record BlockSize : longint; DataLen2 : longint; PrevBlock : longint; NextBlock : longint; DataLen1 : longint; RecordType : byte; end; TTopicBlock = object Header : TWinHelpTopicBlockHeader; DataSize : longint; DataPtr : PByteArray; private CurOfs: longint; procedure Seek(Pos: longint); function GetPos: longint; function GetSize: longint; procedure Read(var Buf; Count: longint); end; PTopicEnumData = ^TTopicEnumData; TTopicEnumData = record TB : TTopicBlock; BlockNo : longint; TopicPos: longint; TopicOfs: longint; TL : TWinHelpTopicLink; LinkData1Size: longint; LinkData1: PByteArray; LinkData2Size: longint; LinkData2: PByteArray; end; PWinHelpFile = ^TWinHelpFile; TWinHelpFile = object(THelpFile) constructor Init(AFileName: string; AID: word); destructor Done; virtual; public function LoadIndex: boolean; virtual; function ReadTopic(T: PTopic): boolean; virtual; private F: PStream; Header: TWinHelpHeader; SysHeader: TWinHelpSystemHeader; Title: string; CNTFileName: string; PhrasesStart: longint; TTLBTreeStart: longint; TopicFileStart: longint; PhrIndexStart: longint; PhrImageStart: longint; Phrases: PUnsortedStringCollection; TreeDone: boolean; IndexLoaded: boolean; function ReadHeader: boolean; function ReadInternalDirectory: boolean; function ProcessLeafPage(PagesBase: longint; PageSize,PageNo,TotalPages: word; ReadLeafEntryMethod: pointer; ScannedPages: PIntCollection): boolean; function ProcessIndexPage(CurLevel,MaxLevel: integer; PagesBase: longint; PageSize: word; PageNo, TotalPages: word; ReadIndexEntryMethod, ReadLeafEntryMethod: pointer; ScannedPages: PIntCollection): boolean; function ProcessTree(ReadIndexEntryMethod, ReadLeafEntryMethod: pointer; NoDuplicates: boolean): boolean; function IDIRReadIndexEntry(SubPageNo: PInteger): boolean; function IDIRReadLeafEntry(P: pointer): boolean; function IDIRProcessFile(const FileName: string; FileOfs: longint): boolean; function TTLBReadIndexEntry(SubPageNo: PInteger): boolean; function TTLBReadLeafEntry(P: pointer): boolean; function TTLBProcessTopicEntry(const TopicTitle: string; FileOfs: longint): boolean; function ReadSystemFile: boolean; function ReadPhraseFile: boolean; function ReadTTLBTree: boolean; function ReadPhrIndexFile(PhraseOfs: PIntCollection; var IH: TWinHelpPhrIndexHeader): boolean; function ReadPhrImageFile(PhraseOfs: PIntCollection; const IH: TWinHelpPhrIndexHeader): boolean; function TopicBlockSize: word; function LZ77Compressed: boolean; function UsesHallCompression: boolean; procedure ExtractTopicOffset(TopicOffset: longint; var TopicBlockNo, TopicBlockOffset: word); function ReadTopicBlock(BlockNo: word; var T: TTopicBlock; ReadData: boolean): boolean; function ProcessTopicBlock(BlockNo: longint; EnumProc: TCallbackFunBoolParam): boolean; procedure PhraseDecompress(SrcBufP: pointer; SrcBufSize: longint; DestBufP: pointer; DestBufSize: longint); procedure HallDecompress(SrcBufP: pointer; SrcBufSize: longint; DestBufP: pointer; DestBufSize: longint); end; procedure RegisterHelpType; implementation uses Strings; function ReadString(F: PStream): string; var S: string; C: char; begin S:=''; if Assigned(F) then repeat F^.Read(C,sizeof(C)); if (F^.Status=stOK) and (C<>#0) then S:=S+C; until (C=#0) or (F^.Status<>stOK); ReadString:=S; end; function TTopicBlock.GetPos: longint; begin GetPos:=CurOfs; end; function TTopicBlock.GetSize: longint; begin GetSize:=DataSize; end; procedure TTopicBlock.Seek(Pos: longint); begin CurOfs:=Pos; end; procedure TTopicBlock.Read(var Buf; Count: longint); begin FillChar(Buf,Count,0); if Count>(DataSize-CurOfs) then begin Count:=Max(0,DataSize-CurOfs); end; Move(DataPtr^[CurOfs],Buf,Count); Inc(CurOfs,Count); end; constructor TWinHelpFile.Init(AFileName: string; AID: word); var OK: boolean; begin if inherited Init(AID)=false then Fail; New(Phrases, Init(1000,1000)); F:=New(PFastBufStream, Init(AFileName, stOpenRead, HelpStreamBufSize)); OK:=F<>nil; if OK then OK:=(F^.Status=stOK); if OK then begin OK:=ReadHeader; end; if OK=false then begin Done; Fail; end; end; function TWinHelpFile.ReadHeader: boolean; var OK: boolean; begin F^.Read(Header,sizeof(Header)); OK:=(F^.Status=stOK); OK:=OK and (Header.MagicNo=WinHelpMagicNo); if OK then begin F^.Seek(Header.DirectoryStart); OK:=(F^.Status=stOK); end; if OK then OK:=ReadInternalDirectory; ReadHeader:=OK; end; function TWinHelpFile.TopicBlockSize: word; var Size: word; begin if (SysHeader.Version<=16) then Size:=2048 else if (SysHeader.Flags in[0,4]) then Size:=4096 else Size:=2048; TopicBlockSize:=Size; end; function TWinHelpFile.LZ77Compressed: boolean; begin LZ77Compressed:=(SysHeader.Version>16) and (SysHeader.Flags<>0); end; function TWinHelpFile.UsesHallCompression: boolean; begin UsesHallCompression:=(PhrIndexStart<>0) and (PhrImageStart<>0); end; function TWinHelpFile.ReadSystemFile: boolean; var OK: boolean; FH: TWinHelpFileEntryHeader; RH: TWinHelpRecordHeader; StartOfs,RecStartOfs: longint; begin F^.Read(FH,sizeof(FH)); OK:=(F^.Status=stOK); StartOfs:=F^.GetPos; F^.Read(SysHeader,sizeof(SysHeader)); OK:=OK and (F^.Status=stOK); OK:=OK and (SysHeader.Magic=WinHelpSystemHeaderMagicNo); if OK then if SysHeader.Version>16 then begin repeat F^.Read(RH,sizeof(RH)); OK:=(F^.Status=stOK); RecStartOfs:=F^.GetPos; if OK then begin case RH.RecordType of wh_srt_Title : Title:=ReadString(F); wh_srt_Content : CNTFileName:=ReadString(F); end; if F^.GetPos<>RecStartOfs+RH.DataSize then F^.Seek(RecStartOfs+RH.DataSize); OK:=(F^.Status=stOK); end; until (OK=false) or (F^.GetPos>=StartOfs+FH.UsedSpace); end else Title:=ReadString(F); OK:=OK and (F^.Status=stOK); ReadSystemFile:=OK; end; function LZ77Decompress(SrcBufP: pointer; SrcSize: longint; DestBufP: pointer; DestSize: longint): longint; var SrcBuf: PByteArray absolute SrcBufP; DestBuf: PByteArray absolute DestBufP; var SrcOfs: longint; function GetByte: byte; begin GetByte:=PByteArray(SrcBuf)^[SrcOfs]; Inc(SrcOfs); end; var DestOfs: longint; procedure PutByte(B: byte); begin PByteArray(DestBuf)^[DestOfs]:=B; Inc(DestOfs); end; var B,Mask: byte; Len,J: word; I: integer; const N = 4096; F=16; begin SrcOfs:=0; DestOfs:=0; I:=N-F; while (SrcOfs0 then begin J:=GetByte; Len:=GetByte; J:=J+(Len and $0f) shl 8; Len:=(Len and $f0) shr 4+3; while (Len>0) do begin PutByte(PByteArray(DestBuf)^[DestOfs-J-1]); {Inc(J); }Inc(I); Dec(Len); end; end else begin PutByte(GetByte); I:=(I+1) and (N-1); end; end; end; LZ77Decompress:=DestOfs; end; function TWinHelpFile.ReadPhraseFile: boolean; var OK: boolean; FH: TWinHelpFileEntryHeader; NumPhrases: word; DecompSize: longint; W: word; PhraseOfss: PWordArray; PhraseOfssSize: word; PhraseBuf: PByteArray; TempBuf: pointer; TempSize: longint; I,PhraseBufSize,PhraseOfs,PhraseSize: longint; S: string; begin F^.Read(FH,sizeof(FH)); OK:=(F^.Status=stOK); F^.Read(NumPhrases,sizeof(NumPhrases)); F^.Read(W,sizeof(W)); OK:=(F^.Status=stOK) and (W=$0100); if OK then begin PhraseOfssSize:=(NumPhrases+1)*sizeof(word); GetMem(PhraseOfss,PhraseOfssSize); F^.Read(W,sizeof(W)); if W=2*(NumPhrases+1) then begin { uncompressed data } PhraseOfss^[0]:=W; F^.Read(PhraseOfss^[1],PhraseOfssSize-sizeof(word)*1); DecompSize:=FH.UsedSpace-(PhraseOfssSize+2+2+2); PhraseBufSize:=DecompSize; GetMem(PhraseBuf,PhraseBufSize); F^.Read(PhraseBuf^,DecompSize); end else begin DecompSize:=W; F^.Read(W,sizeof(W)); DecompSize:=DecompSize+longint(W) shl 16; Inc(DecompSize); PhraseOfss^[0]:=DecompSize; F^.Read(PhraseOfss^[1],PhraseOfssSize); PhraseBufSize:=DecompSize+10; GetMem(PhraseBuf,PhraseBufSize); FillChar(PhraseBuf^,DecompSize,0); TempSize:=FH.UsedSpace-(PhraseOfssSize+2+2+2); GetMem(TempBuf,TempSize); F^.Read(TempBuf^,TempSize); LZ77Decompress(TempBuf,TempSize,PhraseBuf,DecompSize); FreeMem(TempBuf,TempSize); end; for I:=1 to NumPhrases do begin PhraseOfs:=PhraseOfss^[I]-PhraseOfss^[1]; if I=NumPhrases then PhraseSize:=DecompSize-PhraseOfs else PhraseSize:=PhraseOfss^[I+1]-PhraseOfss^[I]; S:=MemToStr(PhraseBuf^[PhraseOfs],PhraseSize); Phrases^.InsertStr(S); end; FreeMem(PhraseOfss,PhraseOfssSize); FreeMem(PhraseBuf,PhraseBufSize); end; ReadPhraseFile:=OK; end; function TWinHelpFile.ProcessLeafPage(PagesBase: longint; PageSize,PageNo,TotalPages: word; ReadLeafEntryMethod: pointer; ScannedPages: PIntCollection): boolean; var OK: boolean; BNH: TWinHelpBTreeNodeHeader; Count: longint; PageOfs: longint; CurPage: longint; begin CurPage:=PageNo; repeat PageOfs:=PagesBase+longint(PageSize)*CurPage; F^.Seek(PageOfs); OK:=(F^.Status=stOK); if OK then begin F^.Read(BNH,sizeof(BNH)); OK:=(F^.Status=stOK); end; if OK then if (ScannedPages<>nil) and ScannedPages^.Contains(CurPage) then Break else begin if Assigned(ScannedPages) then ScannedPages^.Add(CurPage); Count:=0; repeat TreeDone:=not (longint(CallPointerMethod(ReadLeafEntryMethod,@Self,nil))<>0); Inc(Count); until (OK=false) or (F^.Status<>stOK) or TreeDone or (Count>=BNH.NumEntries){ or (F^.GetPos>=PageOfs+PageSize-BNH.NumEntries)}; end; if (BNH.PrevPage<>-1) and (TreeDone=false) then CurPage:=BNH.PrevPage; until (OK=false) or TreeDone or (BNH.PrevPage=-1); ProcessLeafPage:=OK; end; function TWinHelpFile.ProcessIndexPage(CurLevel,MaxLevel: integer; PagesBase: longint; PageSize: word; PageNo, TotalPages: word; ReadIndexEntryMethod, ReadLeafEntryMethod: pointer; ScannedPages: PIntCollection): boolean; var BIH: TWinHelpBTreeIndexHeader; I: integer; SubPageNo: integer; OK: boolean; OldPos: longint; CurPage: longint; begin CurPage:=PageNo; repeat F^.Seek(PagesBase+longint(PageSize)*CurPage); OK:=(F^.Status=stOK); if OK then begin F^.Read(BIH,sizeof(BIH)); OK:=(F^.Status=stOK); end; if OK then if (ScannedPages<>nil) and ScannedPages^.Contains(CurPage) then Break else begin if Assigned(ScannedPages) then ScannedPages^.Add(CurPage); for I:=1 to BIH.NumEntries do begin SubPageNo:=-1; OK:=(longint(CallPointerMethod(ReadIndexEntryMethod,@Self,@SubPageNo))<>0); OK:=OK and (F^.Status=stOK); if OK then if CurLevelOldPos then F^.Seek(OldPos); end end else { process leaf page } if (0<=SubPageNo) then if (ScannedPages=nil) or (ScannedPages^.Contains(SubPageNo)=false) then begin OldPos:=F^.GetPos; OK:=ProcessLeafPage(PagesBase,PageSize,SubPageNo,TotalPages,ReadLeafEntryMethod,ScannedPages); if F^.GetPos<>OldPos then F^.Seek(OldPos); end; if TreeDone then Break; end; end; if (BIH.PrevPage>0) and (TreeDone=false) then CurPage:=BIH.PrevPage else Break; until (OK=false) or TreeDone; ProcessIndexPage:=OK; end; function TWinHelpFile.ProcessTree(ReadIndexEntryMethod, ReadLeafEntryMethod: pointer; NoDuplicates: boolean): boolean; var BTH: TWinHelpBTreeHeader; OK: boolean; PagesBase: longint; ScannedPages: PIntCollection; begin ScannedPages:=nil; TreeDone:=false; F^.Read(BTH,sizeof(BTH)); OK:=(F^.Status=stOK); PagesBase:=F^.GetPos; if OK then begin OK:=(BTH.Magic=WinHelpBTreeHeaderMagicNo) and (BTH.MustBeZero=0) and (BTH.MustBeNegOne=-1); end; if OK then begin if NoDuplicates then New(ScannedPages, Init(500,100)); if BTH.NumLevels>1 then begin OK:=ProcessIndexPage(1,BTH.NumLevels,PagesBase,BTH.PageSize,BTH.RootPage,BTH.TotalPages, ReadIndexEntryMethod,ReadLeafEntryMethod,ScannedPages); end else OK:=ProcessLeafPage(PagesBase,BTH.PageSize,BTH.RootPage,BTH.TotalPages,ReadLeafEntryMethod,ScannedPAges); if Assigned(ScannedPages) then Dispose(ScannedPages, Done); end; ProcessTree:=OK; end; (*function TWinHelpFile.IDIRProcessFile(const FileName: string; FileOfs: longint): boolean; var OK: boolean; begin OK:=true; if FileName='|SYSTEM' then begin F^.Seek(FileOfs); OK:=(F^.Status=stOK); if OK then OK:=ReadSystemFile; end else if (FileName='|Phrases') then begin PhrasesStart:=FileOfs; end else ; IDIRProcessFile:=OK; end; function TWinHelpFile.IDIRProcessLeafPage(PagesBase: longint; PageSize,PageNo,TotalPages: word): boolean; var OK: boolean; BNH: TWinHelpBTreeNodeHeader; FileOfs: longint; Count: integer; S: string; CurOfs,PageOfs,OldPos: longint; begin PageOfs:=PagesBase+PageSize*PageNo; F^.Seek(PageOfs); OK:=(F^.Status=stOK); repeat if OK then begin F^.Read(BNH,sizeof(BNH)); OK:=(F^.Status=stOK); end; if OK then begin Count:=0; repeat S:=ReadString(F); F^.Read(FileOfs,sizeof(FileOfs)); { longint } OK:=OK and (F^.Status=stOK); if OK then begin OldPos:=F^.GetPos; OK:=IDIRProcessFile(S,FileOfs); if F^.GetPos<>OldPos then F^.Seek(OldPos); end; Inc(Count); until (OK=false) or (Count=BNH.NumEntries){ or (F^.GetPos>=PageOfs+PageSize-BNH.NumEntries)}; end; if BNH.PrevPage<>-1 then begin F^.Seek(PagesBase+PageSize*BNH.PrevPage); OK:=(F^.Status=stOK); end; until (OK=false) or (BNH.PrevPage=-1); IDIRProcessLeafPage:=OK; end; function TWinHelpFile.IDIRProcessIndexPage(CurLevel,MaxLevel: integer; PagesBase: longint; PageSize: word; PageNo, TotalPages: word): boolean; var BIH: TWinHelpBTreeIndexHeader; I: integer; SubPageNo: integer; OK: boolean; S: string; OldPos: longint; begin F^.Seek(PagesBase+PageSize*PageNo); OK:=(F^.Status=stOK); repeat if OK then begin F^.Read(BIH,sizeof(BIH)); OK:=(F^.Status=stOK); end; if OK then for I:=1 to BIH.NumEntries do begin S:=ReadString(F); F^.Read(SubPageNo,sizeof(SubPageNo)); { word } OK:=OK and (F^.Status=stOK); if OK then if CurLevelOldPos then F^.Seek(OldPos); end end else { process leaf page } if (0<=SubPageNo) then OK:=IDIRProcessLeafPage(PagesBase,PageSize,SubPageNo,TotalPages); end; if (BIH.PrevPage>0) then begin F^.Seek(PagesBase+PageSize*BIH.PrevPage); OK:=(F^.Status=stOK); end else Break; until (OK=false); IDIRProcessIndexPage:=OK; end; function TWinHelpFile.ReadInternalDirectory: boolean; var BTH: TWinHelpBTreeHeader; OK: boolean; PagesBase: longint; begin F^.Read(BTH,sizeof(BTH)); OK:=(F^.Status=stOK); PagesBase:=F^.GetPos; if OK then begin OK:=(BTH.Magic=WinHelpBTreeHeaderMagicNo) and (BTH.MustBeZero=0) and (BTH.MustBeNegOne=-1); end; if BTH.NumLevels>1 then OK:=IDIRProcessIndexPage(1,BTH.NumLevels,PagesBase,BTH.PageSize,BTH.RootPage,BTH.TotalPages) else OK:=IDIRProcessLeafPage(PagesBase,BTH.PageSize,BTH.RootPage,BTH.TotalPages); ReadInternalDirectory:=OK; end; *) function TWinHelpFile.IDIRProcessFile(const FileName: string; FileOfs: longint): boolean; var OK: boolean; begin OK:=true; if FileName='|SYSTEM' then begin F^.Seek(FileOfs); OK:=(F^.Status=stOK); if OK then OK:=ReadSystemFile; end else if (FileName='|Phrases') then begin PhrasesStart:=FileOfs; end else if (FileName='|TOPIC') then begin TopicFileStart:=FileOfs; end else if (FileName='|TTLBTREE') then begin TTLBTreeStart:=FileOfs; end else if (FileName='|PhrIndex') then begin PhrIndexStart:=FileOfs; end else if (FileName='|PhrImage') then begin PhrImageStart:=FileOfs; end else ; IDIRProcessFile:=OK; end; function TWinHelpFile.IDIRReadIndexEntry(SubPageNo: PInteger): boolean; var {S: string;} OK: boolean; begin {S:=}ReadString(F); F^.Read(SubPageNo^,sizeof(SubPageNo^)); OK:=(F^.Status=stOK); IDIRReadIndexEntry:=OK; end; function TWinHelpFile.IDIRReadLeafEntry(P: pointer): boolean; var OK: boolean; S: string; FileOfs,OldPos: longint; begin S:=ReadString(F); F^.Read(FileOfs,sizeof(FileOfs)); { longint } OK:=(F^.Status=stOK); if OK then begin OldPos:=F^.GetPos; OK:=IDIRProcessFile(S,FileOfs); if F^.GetPos<>OldPos then F^.Seek(OldPos); OK:=OK and (F^.Status=stOK); end; IDIRReadLeafEntry:=OK; end; function TWinHelpFile.ReadInternalDirectory: boolean; var OK: boolean; FH: TWinHelpFileEntryHeader; begin F^.Read(FH,sizeof(FH)); OK:=(F^.Status=stOK); if OK then OK:=ProcessTree(@TWinHelpFile.IDIRReadIndexEntry,@TWinHelpFile.IDIRReadLeafEntry,true); ReadInternalDirectory:=OK; end; function TWinHelpFile.TTLBReadIndexEntry(SubPageNo: PInteger): boolean; var TopicOffset: longint; OK: boolean; begin F^.Read(TopicOffset,sizeof(TopicOffset)); F^.Read(SubPageNo^,sizeof(SubPageNo^)); OK:=(F^.Status=stOK); TTLBReadIndexEntry:=OK; end; function TWinHelpFile.TTLBReadLeafEntry(P: pointer): boolean; var OK: boolean; S: string; TopicOfs,OldPos: longint; begin F^.Read(TopicOfs,sizeof(TopicOfs)); { longint } S:=ReadString(F); OK:=(F^.Status=stOK); if OK then begin OldPos:=F^.GetPos; OK:=TTLBProcessTopicEntry(S,TopicOfs); if F^.GetPos<>OldPos then F^.Seek(OldPos); OK:=OK and (F^.Status=stOK); end; TTLBReadLeafEntry:=OK; end; function TWinHelpFile.TTLBProcessTopicEntry(const TopicTitle: string; FileOfs: longint): boolean; var OK: boolean; {const Count: longint = 0;} begin { Inc(Count); if (Count mod 100)=1 then begin gotoxy(1,1); write(Count,' - ',IndexEntries^.Count,' - ',Topics^.Count); end;} OK:=(IndexEntries^.Count'') and (FileOfs>=0) then begin AddIndexEntry(TopicTitle,FileOfs); AddTopic(FileOfs,FileOfs,'',nil,0); end; end; TTLBProcessTopicEntry:=OK; end; function TWinHelpFile.ReadTTLBTree: boolean; var OK: boolean; FH: TWinHelpFileEntryHeader; begin F^.Read(FH,sizeof(FH)); OK:=(F^.Status=stOK); if OK then OK:=ProcessTree(@TWinHelpFile.TTLBReadIndexEntry,@TWinHelpFile.TTLBReadLeafEntry,true); ReadTTLBTree:=OK; end; function TWinHelpFile.ReadPhrIndexFile(PhraseOfs: PIntCollection; var IH: TWinHelpPhrIndexHeader): boolean; var OK: boolean; FH: TWinHelpFileEntryHeader; TotalBitPos,BufBitPos: longint; BitBuf: array[0..1023] of byte; CurFrag: word; function GetBit: integer; begin BufBitPos:=(TotalBitPos mod ((High(BitBuf)-Low(BitBuf)+1)*8)); if (BufBitPos=0) then begin CurFrag:=Min(sizeof(BitBuf),FH.UsedSpace-(TotalBitPos div 8)); F^.Read(BitBuf,CurFrag); OK:=OK and (F^.Status=stOK); end; if (BitBuf[Low(BitBuf)+BufBitPos div 8] and (1 shl (BufBitPos mod 8)))<>0 then GetBit:=1 else GetBit:=0; Inc(TotalBitPos); end; var Delta: longint; I,J,LastOfs: longint; BitCount: integer; begin F^.Read(FH,sizeof(FH)); OK:=(F^.Status=stOK); if OK then begin F^.Read(IH,sizeof(IH)); OK:=(F^.Status=stOK) and (IH.Magic=1); end; if OK then begin PhraseOfs^.Add(0); TotalBitPos:=0; LastOfs:=0; BitCount:=(IH.BitCount_Unk and $0f); for I:=1 to IH.NumEntries do begin Delta:=1; while GetBit=1 do Delta:=Delta+(1 shl BitCount); for J:=0 to BitCount-1 do Delta:=Delta+(1 shl J)*GetBit; Inc(LastOfs,Delta); PhraseOfs^.Add(LastOfs); end; end; ReadPhrIndexFile:=OK; end; function TWinHelpFile.ReadPhrImageFile(PhraseOfs: PIntCollection; const IH: TWinHelpPhrIndexHeader): boolean; var OK: boolean; FH: TWinHelpFileEntryHeader; PhraseBufSize: longint; PhraseBuf: PByteArray; TempBufSize: longint; TempBuf: pointer; CurOfs,NextOfs: longint; I: longint; begin F^.Read(FH,sizeof(FH)); OK:=(F^.Status=stOK); OK:=OK and (IH.PhrImageCompressedSize=FH.UsedSpace); if OK then begin PhraseBufSize:=IH.PhrImageSize; GetMem(PhraseBuf,PhraseBufSize); if IH.PhrImageSize=IH.PhrImageCompressedSize then begin F^.Read(PhraseBuf^,PhraseBufSize); end else begin TempBufSize:=IH.PhrImageCompressedSize; GetMem(TempBuf,TempBufSize); F^.Read(TempBuf^,TempBufSize); OK:=(F^.Status=stOK); if OK then LZ77Decompress(TempBuf,TempBufSize,PhraseBuf,PhraseBufSize); FreeMem(TempBuf,TempBufSize); end; if OK then begin for I:=1 to IH.NumEntries do begin CurOfs:=PhraseOfs^.AtInt(I-1); NextOfs:=PhraseOfs^.AtInt(I); Phrases^.InsertStr(MemToStr(PhraseBuf^[CurOfs],NextOfs-CurOfs)); end; end; FreeMem(PhraseBuf,PhraseBufSize); end; ReadPhrImageFile:=OK; end; function TWinHelpFile.LoadIndex: boolean; var OK: boolean; PO: PIntCollection; IH: TWinHelpPhrIndexHeader; begin if IndexLoaded then OK:=true else begin if PhrasesStart<>0 then begin F^.Seek(PhrasesStart); OK:=(F^.Status=stOK); if OK then OK:=ReadPhraseFile; end else if (PhrIndexStart<>0) and (PhrImageStart<>0) then begin New(PO, Init(1000,1000)); F^.Seek(PhrIndexStart); OK:=(F^.Status=stOK); if OK then OK:=ReadPhrIndexFile(PO,IH); if OK then begin F^.Seek(PhrImageStart); OK:=(F^.Status=stOK); end; if OK then OK:=ReadPhrImageFile(PO,IH); Dispose(PO, Done); end; if TTLBTreeStart<>0 then begin F^.Seek(TTLBTreeStart); OK:=(F^.Status=stOK); if OK then OK:=ReadTTLBTree; end; IndexLoaded:=OK; end; LoadIndex:=OK; end; procedure TWinHelpFile.ExtractTopicOffset(TopicOffset: longint; var TopicBlockNo, TopicBlockOffset: word); var {OfsBitCount: longint; OfsMask: longint;} BS: longint; begin { if LZ77Compressed then BS:=32768 else BS:=TopicBlockSize;} BS:=32768; { for OfsBitCount:=0 to 31 do if (1 shl OfsBitCount)=BS then Break; OfsMask:=(1 shl OfsBitCount)-1; TopicBlockNo:=(TopicOffset and not OfsMask) shr OfsBitCount; TopicBlockOffset:=(TopicOffset and OfsMask);} TopicBlockNo:=TopicOffset div BS; TopicBlockOffset:=TopicOffset mod BS; end; function TWinHelpFile.ReadTopicBlock(BlockNo: word; var T: TTopicBlock; ReadData: boolean): boolean; var TempBuf: pointer; BlockBuf: ^TWinHelpTopicBlock; OK: boolean; RS,DecompSize: longint; const TempBufSize = 16384; begin F^.Reset; FillChar(T,sizeof(T),0); F^.Seek(TopicFileStart+sizeof(TWinHelpFileEntryHeader)+longint(BlockNo)*TopicBlockSize); OK:=(F^.Status=stOK); if OK then if ReadData=false then begin F^.Read(T.Header,sizeof(T.Header)); OK:=(F^.Status=stOK); end else begin GetMem(BlockBuf, TopicBlockSize); F^.Read(BlockBuf^,TopicBlockSize); OK:=(F^.Status=stOK); if OK then begin Move(BlockBuf^.Header,T.Header,sizeof(T.Header)); if LZ77Compressed then begin GetMem(TempBuf,TempBufSize); DecompSize:=LZ77Decompress(@BlockBuf^.Data,TopicBlockSize-sizeof(BlockBuf^.Header),TempBuf,TempBufSize); T.DataSize:=DecompSize; GetMem(T.DataPtr,T.DataSize); Move(TempBuf^,T.DataPtr^,T.DataSize); FreeMem(TempBuf,TempBufSize); end else begin T.DataSize:=TopicBlockSize-sizeof(BlockBuf^.Header); GetMem(T.DataPtr,T.DataSize); Move(BlockBuf^.Data,T.DataPtr^,T.DataSize); end; end; FreeMem(BlockBuf,TopicBlockSize); end; ReadTopicBlock:=OK; end; procedure FreeTopicBlock(T: TTopicBlock); begin if (T.DataSize>0) and (T.DataPtr<>nil) then begin FreeMem(T.DataPtr,T.DataSize); T.DataPtr:=nil; end; end; procedure TWinHelpFile.PhraseDecompress(SrcBufP: pointer; SrcBufSize: longint; DestBufP: pointer; DestBufSize: longint); var SrcBuf: PByteArray absolute SrcBufP; DestBuf: PByteArray absolute DestBufP; var SrcOfs: longint; function GetByte: byte; begin GetByte:=SrcBuf^[SrcOfs]; Inc(SrcOfs); end; var DestOfs: longint; procedure PutByte(B: byte); begin if DestOfs15) then PutByte(B) else begin Index:=longint(B)*256-256+GetByte; S:=GetStr(Phrases^.At(Index div 2)); if (Index mod 2)=1 then S:=S+' '; for I:=1 to length(S) do PutByte(ord(S[I])); end; end; end; procedure TWinHelpFile.HallDecompress(SrcBufP: pointer; SrcBufSize: longint; DestBufP: pointer; DestBufSize: longint); var SrcBuf: PByteArray absolute SrcBufP; DestBuf: PByteArray absolute DestBufP; var SrcOfs: longint; function GetByte: byte; begin GetByte:=SrcBuf^[SrcOfs]; Inc(SrcOfs); end; var DestOfs: longint; procedure PutByte(B: byte); begin if DestOfs0 then TB.Seek((TB.Header.FirstTopicLink and $3fff)-sizeof(TB.Header)); if TB.Header.LastTopicLink=-1 then LastLinkOfs:=TB.DataSize-1-sizeof(TL) else LastLinkOfs:={(TB.Header.LastTopicLink-BlockFileOfs-sizeof(TB.Header))}TB.GetSize-1; while (DoCont) and OK and (TB.GetPos<=LastLinkOfs) do begin CurBlockOfs:=TB.GetPos; TopicPos:=TB.GetPos+sizeof(TB.Header); TB.Read(TL,sizeof(TL)); if (TL.BlockSize=0) or (TL.DataLen1=0) or (TB.GetPos>LastLinkOfs) or (TB.GetSize-TB.GetPosTL.BlockSize-TL.DataLen1 then begin TempBufSize:=TL.BlockSize-TL.DataLen1; GetMem(TempBuf,TempBufSize); TB.Read(TempBuf^,TempBufSize); if UsesHallCompression then HallDecompress(TempBuf,TempBufSize,LinkData2,LinkData2Size) else PhraseDecompress(TempBuf,TempBufSize,LinkData2,LinkData2Size); FreeMem(TempBuf,TempBufSize); end else TB.Read(LinkData2^,TL.DataLen2); FillChar(TEN,sizeof(TEN),0); TEN.TB:=TB; TEN.BlockNo:=BlockNo; TEN.TopicPos:=TopicPos; TEN.TopicOfs:=TopicOfs; TEN.TL:=TL; TEN.LinkData1Size:=LinkData1Size; TEN.LinkData1:=LinkData1; TEN.LinkData2Size:=LinkData2Size; TEN.LinkData2:=LinkData2; DoCont:=(longint(CallPointerLocal(EnumProc, get_caller_frame(get_frame,get_pc_addr),@TEN)) and $ff)<>0; case TL.RecordType of $02: ; $20,$23: begin Inc(TopicOfs,TL.DataLen2); end; end; FreeMem(LinkData1,LinkData1Size); FreeMem(LinkData2,LinkData2Size); end; FreeTopicBlock(TB); end; ProcessTopicBlock:=OK; end; function TWinHelpFile.ReadTopic(T: PTopic): boolean; var OK: boolean; BlockNo,BlockOfs: word; TopicStartPos: longint; GotIt: boolean; TH: TWinHelpTopicHeader; CurLine: string; Lines: PUnsortedStringCollection; EmitSize: longint; LastEmittedChar: integer; procedure FlushLine; begin Lines^.InsertStr(CurLine); CurLine:=''; end; procedure EmitText(const S: string); begin Inc(EmitSize,length(S)); if length(CurLine)+length(S)>High(S) then FlushLine; CurLine:=CurLine+S; if length(S)>0 then LastEmittedChar:=ord(S[length(S)]); end; procedure EmitTextC(C: PChar); var RemSize,CurOfs,CurFrag: longint; S: string; begin if C=nil then Exit; RemSize:=StrLen(C); CurOfs:=0; while (RemSize>0) do begin CurFrag:=Min(RemSize,255); S[0]:=chr(CurFrag); Move(PByteArray(C)^[CurOfs],S[1],CurFrag); EmitText(S); Dec(RemSize,CurFrag); Inc(CurOfs,CurFrag); end; end; function SearchTopicStart(P: PTopicEnumData): boolean; begin case P^.TL.RecordType of $02 : TopicStartPos:=P^.TopicPos; end; GotIt:=(P^.TL.RecordType in [$20,$23]) and (P^.TopicOfs<=BlockOfs) and (BlockOfs0 then S:=S+chr(B); until B=0; ReadString:=S; end; procedure EmitDebugText(const S: string); begin {$ifdef DEBUGMSG} EmitText(S); {$endif} end; var Finished: boolean; S: string; { ---- } Cmd: integer; I,TopicSize: longint; NumberOfCols,TableType: byte; MinTableWidth: integer; Flags: longint; NumberOfTabStops: integer; TabStop: longint; PType: integer; Len: word; SLen,LinkOfs: longint; SPtr: pointer; SBuf: PChar; PictureSize,PictureStartOfs: longint; FontNumber: integer; begin Finished:=((P^.TopicPos>TopicStartPos) or (P^.BlockNo>BlockNo)) and (P^.TL.RecordType=$02); { next topic header found } if (Finished=false) and (P^.TopicPos>=TopicStartPos) then case P^.TL.RecordType of $02 : begin S[0]:=chr(Min(StrLen(pointer(P^.LinkData2)),P^.LinkData2Size)); Move(P^.LinkData2^,S[1],ord(S[0])); if S<>'' then begin EmitText(' '+S+' Ü'+hscLineBreak); EmitText(' '+CharStr('ß',length(S)+3)+hscLineBreak); end; end; $20,$23 : begin EmitDebugText(hscLineBreak+'<------ new record ------>'+hscLineBreak); LinkData1Ofs:=0; LinkData2Ofs:=0; EmitSize:=0; { ---- } MinTableWidth:=0; TopicSize:=ReadComprULONG; if P^.TL.RecordType in[$20,$23] then {TopicLen:=}ReadComprUSHORT; if P^.TL.RecordType=$23 then begin NumberOfCols:=ReadUCHAR; TableType:=ReadUCHAR; if TableType in[0,2] then MinTableWidth:=ReadSHORT; for I:=1 to NumberOfCols do begin {GapWidth:=}ReadSHORT; {ColWidth:=}ReadSHORT; end; end; if P^.TL.RecordType=$23 then begin {Column:=}ReadSHORT; {-1 = end of topic} {Unknown:=}ReadSHORT; {Always0:=}ReadCHAR; end; {Unknown:=}ReadUCHAR; {Uknown:=}ReadCHAR; ID:=ReadUSHORT; Flags:=ReadUSHORT; if (Flags and 1)<>0 then {Unknown:=}ReadComprLONG; if (Flags and 2)<>0 then {SpacingAbove:=}ReadComprSHORT; if (Flags and 4)<>0 then {SpacingBelow:=}ReadComprSHORT; if (Flags and 8)<>0 then {SpacingLines:=}ReadComprSHORT; if (Flags and 16)<>0 then {LeftIndent:=}ReadComprSHORT; if (Flags and 32)<>0 then {RightIndent:=}ReadComprSHORT; if (Flags and 64)<>0 then {FirstLineIndent:=}ReadComprSHORT; if (Flags and 256)<>0 then {BorderInfo} begin {BorderFlags:=}ReadUCHAR; {BorderWidth:=}ReadSHORT; end; if (Flags and 512)<>0 then {TabInfo} begin NumberOfTabStops:=ReadComprSHORT; for I:=1 to NumberOfTabStops do begin TabStop:=ReadComprUSHORT; if (TabStop and $4000)<>0 then {TabType:=}ReadComprUSHORT; end; end; for I:=10 to 14 do if (Flags and (1 shl I))<>0 then ReadUCHAR; { if (TH.NonScrollRgnOfs<>-1) then if (P^.TopicPos=(TH.ScrollRgnOfs and $3fff)) then begin EmitText(hscLineBreak); EmitText(CharStr('Ä',80)); EmitText(hscLineBreak); end; } while (LinkData2Ofs0 then SBuf:=SPtr else SBuf:=nil; Inc(LinkData2Ofs,SLen+1); Cmd:=-1; if (LinkData1Ofsord(hscLineBreak) then EmitText(hscLineBreak); EmitDebugText('[tag0x'+hexstr(Cmd,2)+']'); end; $80 : begin FontNumber:=ReadSHORT; EmitDebugText('[font'+inttostr(FontNumber)+']'); end; $81 : {LineBreak} begin EmitDebugText('[br]'); EmitText(hscLineBreak); end; $82 : {End Of Paragraph} begin EmitDebugText('[eop]'); EmitText(hscLineBreak); end; $83 : {TAB} begin EmitDebugText('[tab]'); EmitText(' '); end; $86, $87, $88 : { ewc or bmc or bmcwd or bmct or button or mci } begin PType:=ReadUCHAR; PictureSize:=ReadComprLONG; if PType=$22 then {NumberOfHotSpots:=}ReadComprSHORT; PictureStartOfs:=LinkData1Ofs; PictureSize:=Min(PictureSize,P^.LinkData1Size-LinkData1Ofs); if PType in[$03,$22] then begin {PictureIsEmbedded:=}ReadSHORT; {PictureNumber:=}ReadSHORT; for I:=1 to PictureSize-4 do {PictureData[I-1]:=}ReadCHAR; end; if PType=$05 then begin {Unknown1:=}ReadSHORT; {Unknown2:=}ReadSHORT; {Unknown3:=}ReadSHORT; { +??? } end; while (LinkData1Ofs0 then begin EmitText(hscLink); AddLinkToTopic(T,ID,LinkOfs); end; end; $e2, { popup jump } $e3, { topic jump } $e6, { popup jump without font change } $e7 : { topic jump without font change } begin EmitDebugText('[link]'); LinkOfs:=ReadLONG; if LinkOfs>0 then begin EmitText(hscLink); AddLinkToTopic(T,ID,LinkOfs); end; end; $ea, { popup jump into external file } $eb, { popup jump into external file without font change } $ee, { popup jump into external file / secondary window } $ef : { popup jump into external file / secondary window } begin EmitDebugText('[linkext]'); Len:=ReadSHORT; PType:=ReadUCHAR; LinkOfs:=ReadLONG; {WindowNo:=}ReadUCHAR; {NameOfExternalFile:=}ReadString; {WindowName:=}ReadString; if LinkOfs>0 then begin EmitText(hscLink); AddLinkToTopic(T,ID,LinkOfs); end; end; else EmitDebugText('[tag0x'+hexstr(Cmd,2)+']'); end; end; if SLen>0 then EmitTextC(SPtr); { case Cmd of $81 : EmitText(hscLineBreak); $82 : EmitText(hscLineBreak); end;} if LinkOfs>0 then begin EmitText(hscLink); EmitDebugText('[eol]'); end; end; end; end; RenderTopicProc:=not Finished; end; begin F^.Reset; OK:=(TopicFileStart<>0) and (T<>nil); if OK then begin ExtractTopicOffset(T^.FileOfs,BlockNo,BlockOfs); TopicStartPos:=-1; GotIt:=false; OK:=ProcessTopicBlock(BlockNo,TCallbackFunBoolParam(@SearchTopicStart)); OK:=OK and GotIt and (TopicStartPos<>-1); if OK then begin CurLine:=''; New(Lines, Init(1000,1000)); LastEmittedChar:=-1; OK:=ProcessTopicBlock(BlockNo,TCallbackFunBoolParam(@RenderTopicProc)); FlushLine; BuildTopic(Lines,T); Dispose(Lines, Done); end; end; ReadTopic:=OK; end; destructor TWinHelpFile.Done; begin if Assigned(F) then Dispose(F, Done); F:=nil; if Assigned(Phrases) then Dispose(Phrases, Done); Phrases:=nil; inherited Done; end; function CreateProc(const FileName,Param: string;Index : longint): PHelpFile; begin CreateProc:=New(PWinHelpFile, Init(FileName,Index)); end; procedure RegisterHelpType; begin RegisterHelpFileType({$ifdef FPC}@{$endif}CreateProc); end; END.