chmreader.pas 55 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712
  1. { Copyright (C) <2005> <Andrew Haines> chmreader.pas
  2. This library is free software; you can redistribute it and/or modify it
  3. under the terms of the GNU Library General Public License as published by
  4. the Free Software Foundation; either version 2 of the License, or (at your
  5. option) any later version.
  6. This program is distributed in the hope that it will be useful, but WITHOUT
  7. ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  8. FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
  9. for more details.
  10. You should have received a copy of the GNU Library General Public License
  11. along with this library; if not, write to the Free Software Foundation,
  12. Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  13. }
  14. {
  15. See the file COPYING.modifiedLGPL, included in this distribution,
  16. for details about the copyright.
  17. }
  18. unit chmreader;
  19. {$mode objfpc}{$H+}
  20. //{$DEFINE CHM_DEBUG}
  21. { $DEFINE CHM_DEBUG_CHUNKS}
  22. interface
  23. uses
  24. Classes, SysUtils, Contnrs, chmbase, paslzx, chmFIftiMain, chmsitemap;
  25. type
  26. TLZXResetTableArr = array of QWord;
  27. PContextItem = ^TContextItem;
  28. TContextItem = record
  29. Context: THelpContext;
  30. Url: String;
  31. end;
  32. TContextList = class(TList)
  33. public
  34. procedure AddContext(Context: THelpContext; Url: String);
  35. function GetURL(Context: THelpContext): String;
  36. procedure Clear; override;
  37. end;
  38. { TITSFReader }
  39. TFileEntryForEach = procedure(Name: String; Offset, UncompressedSize, Section: Integer) of object;
  40. TITSFReader = class(TObject)
  41. protected
  42. fStream: TStream;
  43. fFreeStreamOnDestroy: Boolean;
  44. fITSFHeader: TITSFHeader;
  45. fHeaderSuffix: TITSFHeaderSuffix;
  46. fDirectoryHeader: TITSPHeader;
  47. fDirectoryHeaderPos: QWord;
  48. fDirectoryHeaderLength: QWord;
  49. fDirectoryEntriesStartPos: QWord;
  50. fCachedEntry: TPMGListChunkEntry; //contains the last entry found by ObjectExists
  51. fDirectoryEntriesCount: LongWord;
  52. procedure ReadHeader; virtual;
  53. procedure ReadHeaderEntries; virtual;
  54. function GetChunkType(Stream: TMemoryStream; ChunkIndex: LongInt): TDirChunkType;
  55. procedure GetSections(out Sections: TStringList);
  56. private
  57. function GetDirectoryChunk(Index: Integer; OutStream: TStream): Integer;
  58. function ReadPMGLchunkEntryFromStream(Stream: TMemoryStream; var PMGLEntry: TPMGListChunkEntry): Boolean;
  59. function ReadPMGIchunkEntryFromStream(Stream: TMemoryStream; var PMGIEntry: TPMGIIndexChunkEntry): Boolean;
  60. procedure LookupPMGLchunk(Stream: TMemoryStream; out PMGLChunk: TPMGListChunk);
  61. procedure LookupPMGIchunk(Stream: TMemoryStream; out PMGIChunk: TPMGIIndexChunk);
  62. function GetBlockFromSection(SectionPrefix: String; StartPos: QWord; BlockLength: QWord): TMemoryStream;
  63. function FindBlocksFromUnCompressedAddr(var ResetTableEntry: TPMGListChunkEntry;
  64. out CompressedSize: QWord; out UnCompressedSize: QWord; out LZXResetTable: TLZXResetTableArr): QWord; // Returns the blocksize
  65. public
  66. constructor Create(AStream: TStream; FreeStreamOnDestroy: Boolean); virtual;
  67. destructor Destroy; override;
  68. public
  69. ChmLastError: LongInt;
  70. function IsValidFile: Boolean;
  71. procedure GetCompleteFileList(ForEach: TFileEntryForEach; AIncludeInternalFiles: Boolean = True); virtual;
  72. function ObjectExists(Name: String): QWord; virtual; // zero if no. otherwise it is the size of the object
  73. // NOTE directories will return zero size even if they exist
  74. function GetObject(Name: String): TMemoryStream; virtual; // YOU must Free the stream
  75. property CachedEntry: TPMGListChunkEntry read fCachedEntry;
  76. end;
  77. { TChmReader }
  78. TChmReader = class(TITSFReader)
  79. protected
  80. fDefaultPage: String;
  81. fIndexFile: String;
  82. fTOCFile: String;
  83. fTitle: String;
  84. fPreferedFont: String;
  85. fContextList: TContextList;
  86. fTOPICSStream,
  87. fURLSTRStream,
  88. fURLTBLStream,
  89. fStringsStream: TMemoryStream;
  90. fLocaleID: DWord;
  91. fWindowsList : TObjectList;
  92. fDefaultWindow: String;
  93. private
  94. FSearchReader: TChmSearchReader;
  95. procedure ReadCommonData;
  96. function ReadStringsEntry(APosition: DWord): String;
  97. function ReadStringsEntryFromStream ( strm:TStream ) : String;
  98. function ReadURLSTR(APosition: DWord): String;
  99. function CheckCommonStreams: Boolean;
  100. procedure ReadWindows(mem:TMemoryStream);
  101. public
  102. constructor Create(AStream: TStream; FreeStreamOnDestroy: Boolean); override;
  103. destructor Destroy; override;
  104. public
  105. function GetContextUrl(Context: THelpContext): String;
  106. function LookupTopicByID(ATopicID: Integer; out ATitle: String): String; // returns a url
  107. function GetTOCSitemap(ForceXML:boolean=false): TChmSiteMap;
  108. function GetIndexSitemap(ForceXML:boolean=false): TChmSiteMap;
  109. function HasContextList: Boolean;
  110. property DefaultPage: String read fDefaultPage;
  111. property IndexFile: String read fIndexFile;
  112. property TOCFile: String read fTOCFile;
  113. property Title: String read fTitle write fTitle;
  114. property PreferedFont: String read fPreferedFont;
  115. property LocaleID: dword read fLocaleID;
  116. property SearchReader: TChmSearchReader read FSearchReader write FSearchReader;
  117. property contextlist : tcontextlist read fcontextlist;
  118. property Windows : TObjectlist read fWindowsList;
  119. property DefaultWindow : string read fdefaultwindow;
  120. end;
  121. { TChmFileList }
  122. TChmFileList = class;
  123. TChmFileOpenEvent = procedure(ChmFileList: TChmFileList; Index: Integer) of object;
  124. TChmFileList = class(TStringList)
  125. protected
  126. fLastChm: TChmReader;
  127. fUnNotifiedFiles: TList;
  128. fOnOpenNewFile: TChmFileOpenEvent;
  129. procedure Delete(Index: Integer); override;
  130. function GetChm(AIndex: Integer): TChmReader;
  131. function GetFileName(AIndex: Integer): String;
  132. procedure OpenNewFile(AFileName: String);
  133. function CheckOpenFile(AFileName: String): Boolean;
  134. function MetaObjectExists(var Name: String): QWord;
  135. function MetaGetObject(Name: String): TMemoryStream;
  136. procedure SetOnOpenNewFile(AValue: TChmFileOpenEvent);
  137. public
  138. constructor Create(PrimaryFileName: String);
  139. destructor Destroy; override;
  140. function GetObject(Name: String): TMemoryStream;
  141. function IsAnOpenFile(AFileName: String): Boolean;
  142. function ObjectExists(Name: String; var fChm: TChmReader = nil): QWord;
  143. //properties
  144. property Chm[Index: Integer]: TChmReader read GetChm;
  145. property FileName[Index: Integer]: String read GetFileName;
  146. property OnOpenNewFile: TChmFileOpenEvent read fOnOpenNewFile write SetOnOpenNewFile;
  147. end;
  148. //ErrorCodes
  149. const
  150. ERR_NO_ERR = 0;
  151. ERR_STREAM_NOT_ASSIGNED = 1;
  152. ERR_NOT_SUPPORTED_VERSION = 2;
  153. ERR_NOT_VALID_FILE = 3;
  154. ERR_UNKNOWN_ERROR = 10;
  155. function ChmErrorToStr(Error: Integer): String;
  156. implementation
  157. uses ChmTypes;
  158. function ChmErrorToStr(Error: Integer): String;
  159. begin
  160. Result := '';
  161. case Error of
  162. ERR_STREAM_NOT_ASSIGNED : Result := 'ERR_STREAM_NOT_ASSIGNED';
  163. ERR_NOT_SUPPORTED_VERSION : Result := 'ERR_NOT_SUPPORTED_VERSION';
  164. ERR_NOT_VALID_FILE : Result := 'ERR_NOT_VALID_FILE';
  165. ERR_UNKNOWN_ERROR : Result := 'ERR_UNKNOWN_ERROR';
  166. end;
  167. end;
  168. function ChunkType(Stream: TMemoryStream): TDirChunkType;
  169. var
  170. ChunkID: array[0..3] of char;
  171. begin
  172. Result := ctUnknown;
  173. if Stream.Size< 4 then exit;
  174. Move(Stream.Memory^, ChunkId[0], 4);
  175. if ChunkID = 'PMGL' then Result := ctPMGL
  176. else if ChunkID = 'PMGI' then Result := ctPMGI
  177. else if ChunkID = 'AOLL' then Result := ctAOLL
  178. else if ChunkID = 'AOLI' then Result := ctAOLI;
  179. end;
  180. { TITSFReader }
  181. procedure TITSFReader.ReadHeader;
  182. begin
  183. fStream.Read(fITSFHeader,SizeOf(fITSFHeader));
  184. // Fix endian issues
  185. {$IFDEF ENDIAN_BIG}
  186. fITSFHeader.Version := LEtoN(fITSFHeader.Version);
  187. fITSFHeader.HeaderLength := LEtoN(fITSFHeader.HeaderLength);
  188. //Unknown_1
  189. fITSFHeader.TimeStamp := BEtoN(fITSFHeader.TimeStamp);//bigendian
  190. fITSFHeader.LanguageID := LEtoN(fITSFHeader.LanguageID);
  191. {$ENDIF}
  192. if fITSFHeader.Version < 4 then
  193. fStream.Seek(SizeOf(TGuid)*2, soCurrent);
  194. if not IsValidFile then Exit;
  195. ReadHeaderEntries;
  196. end;
  197. procedure TITSFReader.ReadHeaderEntries;
  198. var
  199. fHeaderEntries: array [0..1] of TITSFHeaderEntry;
  200. begin
  201. // Copy EntryData into memory
  202. fStream.Read(fHeaderEntries[0], SizeOf(fHeaderEntries));
  203. if fITSFHeader.Version = 3 then
  204. fStream.Read(fHeaderSuffix.Offset, SizeOf(QWord));
  205. fHeaderSuffix.Offset := LEtoN(fHeaderSuffix.Offset);
  206. // otherwise this is set in fill directory entries
  207. fStream.Position := LEtoN(fHeaderEntries[1].PosFromZero);
  208. fDirectoryHeaderPos := LEtoN(fHeaderEntries[1].PosFromZero);
  209. fStream.Read(fDirectoryHeader, SizeOf(fDirectoryHeader));
  210. {$IFDEF ENDIAN_BIG}
  211. with fDirectoryHeader do begin
  212. Version := LEtoN(Version);
  213. DirHeaderLength := LEtoN(DirHeaderLength);
  214. //Unknown1
  215. ChunkSize := LEtoN(ChunkSize);
  216. Density := LEtoN(Density);
  217. IndexTreeDepth := LEtoN(IndexTreeDepth);
  218. IndexOfRootChunk := LEtoN(IndexOfRootChunk);
  219. FirstPMGLChunkIndex := LEtoN(FirstPMGLChunkIndex);
  220. LastPMGLChunkIndex := LEtoN(LastPMGLChunkIndex);
  221. //Unknown2
  222. DirectoryChunkCount := LEtoN(DirectoryChunkCount);
  223. LanguageID := LEtoN(LanguageID);
  224. //GUID: TGuid;
  225. LengthAgain := LEtoN(LengthAgain);
  226. end;
  227. {$ENDIF}
  228. {$IFDEF CHM_DEBUG}
  229. WriteLn('PMGI depth = ', fDirectoryHeader.IndexTreeDepth);
  230. WriteLn('PMGI Root = ', fDirectoryHeader.IndexOfRootChunk);
  231. Writeln('DirCount = ', fDirectoryHeader.DirectoryChunkCount);
  232. {$ENDIF}
  233. fDirectoryEntriesStartPos := fStream.Position;
  234. fDirectoryHeaderLength := LEtoN(fHeaderEntries[1].Length);
  235. end;
  236. procedure TChmReader.ReadCommonData;
  237. // A little helper proc to make reading a null terminated string easier
  238. function ReadString(const Stream: TStream; StartPos: DWord; FixURL: Boolean): String;
  239. var
  240. buf: array[0..49] of char;
  241. begin
  242. Result := '';
  243. Stream.Position := StartPos;
  244. repeat
  245. Stream.Read(buf, 50);
  246. Result := Result + buf;
  247. until Pos(#0, buf) > -1;
  248. if FixURL then
  249. Result := StringReplace(Result, '\', '/', [rfReplaceAll]);
  250. end;
  251. procedure ReadFromSystem;
  252. var
  253. //Version: DWord;
  254. EntryType: Word;
  255. EntryLength: Word;
  256. Data: array[0..511] of char;
  257. fSystem: TMemoryStream;
  258. Tmp: String;
  259. begin
  260. fSystem := TMemoryStream(GetObject('/#SYSTEM'));
  261. if fSystem = nil then begin
  262. exit;
  263. end;
  264. fSystem.Position := 0;
  265. if fSystem.Size < SizeOf(DWord) then begin
  266. fSystem.Free;
  267. Exit;
  268. end;
  269. {Version := }LEtoN(fSystem.ReadDWord);
  270. while fSystem.Position < fSystem.Size do begin
  271. EntryType := LEtoN(fSystem.ReadWord);
  272. EntryLength := LEtoN(fSystem.ReadWord);
  273. case EntryType of
  274. 0: // Table of contents
  275. begin
  276. if EntryLength > 511 then EntryLength := 511;
  277. fSystem.Read(Data[0], EntryLength);
  278. Data[EntryLength] := #0;
  279. fTOCFile := '/'+Data;
  280. end;
  281. 1: // Index File
  282. begin
  283. if EntryLength > 511 then EntryLength := 511;
  284. fSystem.Read(Data[0], EntryLength);
  285. Data[EntryLength] := #0;
  286. fIndexFile := '/'+Data;
  287. end;
  288. 2: // DefaultPage
  289. begin
  290. if EntryLength > 511 then EntryLength := 511;
  291. fSystem.Read(Data[0], EntryLength);
  292. Data[EntryLength] := #0;
  293. fDefaultPage := '/'+Data;
  294. end;
  295. 3: // Title of chm
  296. begin
  297. if EntryLength > 511 then EntryLength := 511;
  298. fSystem.Read(Data[0], EntryLength);
  299. Data[EntryLength] := #0;
  300. fTitle := Data;
  301. end;
  302. 4: // Locale ID
  303. begin
  304. fLocaleID := LEtoN(fSystem.ReadDWord);
  305. fSystem.Position := (fSystem.Position + EntryLength) - SizeOf(DWord);
  306. end;
  307. 6: // chm file name. use this to get the index and toc name
  308. begin
  309. if EntryLength > 511 then EntryLength := 511;
  310. fSystem.Read(Data[0], EntryLength);
  311. Data[EntryLength] := #0;
  312. if (fIndexFile = '') then begin
  313. Tmp := '/'+Data+'.hhk';
  314. if (ObjectExists(Tmp) > 0) then begin
  315. fIndexFile := Tmp;
  316. end
  317. end;
  318. if (fTOCFile = '') then begin
  319. Tmp := '/'+Data+'.hhc';
  320. if (ObjectExists(Tmp) > 0) then begin
  321. fTOCFile := Tmp;
  322. end;
  323. end;
  324. end;
  325. 16: // Prefered font
  326. begin
  327. if EntryLength > 511 then EntryLength := 511;
  328. fSystem.Read(Data[0], EntryLength);
  329. Data[EntryLength] := #0;
  330. fPreferedFont := Data;
  331. end;
  332. else
  333. // Skip entries we are not interested in
  334. fSystem.Position := fSystem.Position + EntryLength;
  335. end;
  336. end;
  337. fSystem.Free;
  338. end;
  339. procedure ReadFromWindows;
  340. var
  341. fWindows,
  342. fStrings: TMemoryStream;
  343. EntryCount,
  344. EntrySize: DWord;
  345. EntryStart: QWord;
  346. X: Integer;
  347. OffSet: QWord;
  348. begin
  349. fWindows := TMemoryStream(GetObject('/#WINDOWS'));
  350. if fWindows = nil then begin
  351. exit;
  352. end;
  353. fStrings := TMemoryStream(GetObject('/#STRINGS'));
  354. if fStrings = nil then begin
  355. if fWindows <> nil then fWindows.Free;
  356. Exit;
  357. end;
  358. fWindows.Position := 0;
  359. if (fWindows.Size = 0) or (fStrings.Size = 0) then begin
  360. fWindows.Free;
  361. fStrings.Free;
  362. Exit;
  363. end;
  364. EntryCount := LEtoN(fWindows.ReadDWord);
  365. EntrySize := LEtoN(fWindows.ReadDWord);
  366. OffSet := fWindows.Position;
  367. for X := 0 to EntryCount -1 do begin
  368. EntryStart := OffSet + (X*EntrySize);
  369. if fTitle = '' then begin
  370. fWindows.Position := EntryStart + $14;
  371. fTitle := '/'+ReadString(fStrings, LEtoN(fWindows.ReadDWord), False);
  372. end;
  373. if fTOCFile = '' then begin
  374. fWindows.Position := EntryStart + $60;
  375. fTOCFile := '/'+ReadString(fStrings, LEtoN(fWindows.ReadDWord), True);
  376. end;
  377. if fIndexFile = '' then begin
  378. fWindows.Position := EntryStart + $64;
  379. fIndexFile := '/'+ReadString(fStrings, LEtoN(fWindows.ReadDWord), True);
  380. end;
  381. if fDefaultPage = '' then begin
  382. fWindows.Position := EntryStart + $68;
  383. fDefaultPage := '/'+ReadString(fStrings, LEtoN(fWindows.ReadDWord), True);
  384. end;
  385. end;
  386. ReadWindows(FWindows);
  387. end;
  388. procedure ReadContextIds;
  389. var
  390. fIVB,
  391. fStrings: TStream;
  392. Str: String;
  393. Value: DWord;
  394. OffSet: DWord;
  395. //TotalSize: DWord;
  396. begin
  397. fIVB := GetObject('/#IVB');
  398. if fIVB = nil then Exit;
  399. fStrings := GetObject('/#STRINGS');
  400. if fStrings = nil then begin
  401. fIVB.Free;
  402. Exit;
  403. end;
  404. fIVB.Position := 0;
  405. {TotalSize := }LEtoN(fIVB.ReadDWord);
  406. while fIVB.Position < fIVB.Size do begin
  407. Value := LEtoN(fIVB.ReadDWord);
  408. OffSet := LEtoN(fIVB.ReadDWord);
  409. Str := '/'+ ReadString(fStrings, Offset, True);
  410. fContextList.AddContext(Value, Str);
  411. end;
  412. end;
  413. begin
  414. ReadFromSystem;
  415. ReadFromWindows;
  416. ReadContextIds;
  417. {$IFDEF CHM_DEBUG}
  418. WriteLn('TOC=',fTocfile);
  419. WriteLn('DefaultPage=',fDefaultPage);
  420. {$ENDIF}
  421. end;
  422. function TChmReader.ReadStringsEntry ( APosition: DWord ) : String;
  423. begin
  424. Result := '';
  425. if fStringsStream = nil then
  426. fStringsStream := GetObject('/#STRINGS');
  427. if fStringsStream = nil then
  428. Exit;
  429. if APosition < fStringsStream.Size-1 then
  430. begin
  431. Result := PChar(fStringsStream.Memory+APosition);
  432. end;
  433. end;
  434. function TChmReader.ReadStringsEntryFromStream ( strm:TStream ) : String;
  435. var APosition : DWord;
  436. begin
  437. APosition:=LEtoN(strm.ReadDWord);
  438. result:=ReadStringsEntry(APosition);
  439. end;
  440. function TChmReader.ReadURLSTR ( APosition: DWord ) : String;
  441. var
  442. URLStrURLOffset: DWord;
  443. begin
  444. if not CheckCommonStreams then
  445. Exit;
  446. fURLTBLStream.Position := APosition;
  447. fURLTBLStream.ReadDWord; // unknown
  448. fURLTBLStream.ReadDWord; // TOPIC index #
  449. fURLSTRStream.Position := LEtoN(fURLTBLStream.ReadDWord);
  450. fURLSTRStream.ReadDWord;
  451. fURLSTRStream.ReadDWord;
  452. if fURLSTRStream.Position < fURLSTRStream.Size-1 then
  453. Result := PChar(fURLSTRStream.Memory+fURLSTRStream.Position);
  454. end;
  455. function TChmReader.CheckCommonStreams: Boolean;
  456. begin
  457. if fTOPICSStream = nil then
  458. fTOPICSStream := GetObject('/#TOPICS');
  459. if fURLSTRStream = nil then
  460. fURLSTRStream := GetObject('/#URLSTR');
  461. if fURLTBLStream = nil then
  462. fURLTBLStream := GetObject('/#URLTBL');
  463. Result := (fTOPICSStream <> nil)
  464. and (fURLSTRStream <> nil)
  465. and (fURLTBLStream <> nil);
  466. end;
  467. procedure TChmReader.ReadWindows(mem:TMemoryStream);
  468. var
  469. i,cnt,
  470. version : integer;
  471. x : TChmWindow;
  472. begin
  473. if not assigned(fwindowslist) then
  474. fWindowsList.Clear;
  475. mem.Position:=0;
  476. cnt := LEtoN(mem.ReadDWord);
  477. version := LEtoN(mem.ReadDWord);
  478. while (cnt>0) do
  479. begin
  480. x:=TChmWindow.Create;
  481. version := LEtoN(mem.ReadDWord); // 0 size of entry.
  482. mem.readDWord; // 4 unknown (bool Unicodestrings?)
  483. x.window_type :=ReadStringsEntryFromStream(mem); // 8 Arg 0, name of window
  484. x.flags := TValidWindowFields(LEtoN(mem.ReadDWord)); // C valid fields
  485. x.nav_style := LEtoN(mem.ReadDWord); // 10 arg 10 navigation pane style
  486. x.title_bar_text :=ReadStringsEntryFromStream(mem); // 14 Arg 1, title bar text
  487. x.styleflags := LEtoN(mem.ReadDWord); // 18 Arg 14, style flags
  488. x.xtdstyleflags := LEtoN(mem.ReadDWord); // 1C Arg 15, xtd style flags
  489. x.left := LEtoN(mem.ReadDWord); // 20 Arg 13, rect.left
  490. x.right := LEtoN(mem.ReadDWord); // 24 Arg 13, rect.top
  491. x.top := LEtoN(mem.ReadDWord); // 28 Arg 13, rect.right
  492. x.bottom := LEtoN(mem.ReadDWord); // 2C Arg 13, rect.bottom
  493. x.window_show_state:= LEtoN(mem.ReadDWord); // 30 Arg 16, window show state
  494. mem.readdword; // 34 - , HWND hwndhelp OUT: window handle"
  495. mem.readdword; // 38 - , HWND hwndcaller OUT: who called this window"
  496. mem.readdword; // 3C - , HH_INFO_TYPE paINFO_TYPES IN: Pointer to an array of Information Types"
  497. mem.readdword; // 40 - , HWND hwndtoolbar OUT: toolbar window in tri-pane window"
  498. mem.readdword; // 44 - , HWND hwndnavigation OUT: navigation window in tri-pane window"
  499. mem.readdword; // 48 - , HWND hwndhtml OUT: window displaying HTML in tri-pane window"
  500. x.navpanewidth := LEtoN(mem.ReadDWord); // 4C Arg 11, width of nav pane
  501. mem.readdword; // 50 - , rect.left, OUT:Specifies the coordinates of the Topic pane
  502. mem.readdword; // 54 - , rect.top , OUT:Specifies the coordinates of the Topic pane
  503. mem.readdword; // 58 - , rect.right, OUT:Specifies the coordinates of the Topic pane
  504. mem.readdword; // 5C - , rect.bottom, OUT:Specifies the coordinates of the Topic pane
  505. x.toc_file :=ReadStringsEntryFromStream(mem); // 60 Arg 2, toc file
  506. x.index_file :=ReadStringsEntryFromStream(mem); // 64 Arg 3, index file
  507. x.default_file :=ReadStringsEntryFromStream(mem); // 68 Arg 4, default file
  508. x.home_button_file :=ReadStringsEntryFromStream(mem); // 6c Arg 5, home button file.
  509. x.buttons := LEtoN(mem.ReadDWord); // 70 arg 12,
  510. x.navpane_initially_closed := LEtoN(mem.ReadDWord); // 74 arg 17
  511. x.navpane_default := LEtoN(mem.ReadDWord); // 78 arg 18,
  512. x.navpane_location := LEtoN(mem.ReadDWord); // 7C arg 19,
  513. x.wm_notify_id := LEtoN(mem.ReadDWord); // 80 arg 20,
  514. for i:=0 to 4 do
  515. mem.ReadDWord; // 84 - byte[20] unknown - "BYTE tabOrder[HH_MAX_TABS + 1]; // IN/OUT: tab order: Contents, Index, Search, History, Favorites, Reserved 1-5, Custom tabs"
  516. mem.ReadDWord; // 94 - int cHistory; // IN/OUT: number of history items to keep (default is 30)
  517. x.jumpbutton_1_text:=ReadStringsEntryFromStream(mem); // 9C Arg 7, The text of the Jump 1 button.
  518. x.jumpbutton_2_text:=ReadStringsEntryFromStream(mem); // A0 Arg 9, The text of the Jump 2 button.
  519. x.jumpbutton_1_file:=ReadStringsEntryFromStream(mem); // A4 Arg 6, The file shown for Jump 1 button.
  520. x.jumpbutton_2_file:=ReadStringsEntryFromStream(mem); // A8 Arg 8, The file shown for Jump 1 button.
  521. for i:=0 to 3 do
  522. mem.ReadDWord;
  523. dec(version,188); // 1.1 specific onesf
  524. while (version>=4) do
  525. begin
  526. mem.readdword;
  527. dec(version,4);
  528. end;
  529. fWindowslist.Add(x);
  530. dec(cnt);
  531. end;
  532. end;
  533. constructor TChmReader.Create(AStream: TStream; FreeStreamOnDestroy: Boolean);
  534. begin
  535. fContextList := TContextList.Create;
  536. fWindowslist := TObjectlist.Create(True);
  537. fDefaultWindow:='';
  538. inherited Create(AStream, FreeStreamOnDestroy);
  539. if not IsValidFile then exit;
  540. ReadCommonData;
  541. end;
  542. destructor TChmReader.Destroy;
  543. begin
  544. FreeAndNil(fContextList);
  545. FreeAndNil(FWindowslist);
  546. FreeAndNil(FSearchReader);
  547. FreeAndNil(fTOPICSStream);
  548. FreeAndNil(fURLSTRStream);
  549. FreeAndNil(fURLTBLStream);
  550. FreeAndNil(fStringsStream);
  551. inherited Destroy;
  552. end;
  553. function TITSFReader.GetChunkType(Stream: TMemoryStream; ChunkIndex: LongInt): TDirChunkType;
  554. var
  555. Sig: array[0..3] of char;
  556. begin
  557. Result := ctUnknown;
  558. Stream.Position := fDirectoryEntriesStartPos + (fDirectoryHeader.ChunkSize * ChunkIndex);
  559. Stream.Read(Sig, 4);
  560. if Sig = 'PMGL' then Result := ctPMGL
  561. else if Sig = 'PMGI' then Result := ctPMGI
  562. else if Sig = 'AOLL' then Result := ctAOLL
  563. else if Sig = 'AOLI' then Result := ctAOLI;
  564. end;
  565. function TITSFReader.GetDirectoryChunk(Index: Integer; OutStream: TStream): Integer;
  566. begin
  567. Result := Index;
  568. fStream.Position := fDirectoryEntriesStartPos + (fDirectoryHeader.ChunkSize * Index);
  569. OutStream.Position := 0;
  570. OutStream.Size := fDirectoryHeader.ChunkSize;
  571. OutStream.CopyFrom(fStream, fDirectoryHeader.ChunkSize);
  572. OutStream.Position := 0;
  573. end;
  574. procedure TITSFReader.LookupPMGLchunk(Stream: TMemoryStream; out PMGLChunk: TPMGListChunk);
  575. begin
  576. //Stream.Position := fDirectoryEntriesStartPos + (fDirectoryHeader.ChunkSize * ChunkIndex);
  577. Stream.Read(PMGLChunk, SizeOf(PMGLChunk));
  578. {$IFDEF ENDIAN_BIG}
  579. with PMGLChunk do begin
  580. UnusedSpace := LEtoN(UnusedSpace);
  581. //Unknown1
  582. PreviousChunkIndex := LEtoN(PreviousChunkIndex);
  583. NextChunkIndex := LEtoN(NextChunkIndex);
  584. end;
  585. {$ENDIF}
  586. end;
  587. function TITSFReader.ReadPMGLchunkEntryFromStream(Stream: TMemoryStream; var PMGLEntry: TPMGListChunkEntry): Boolean;
  588. var
  589. Buf: array [0..1023] of char;
  590. NameLength: LongInt;
  591. begin
  592. Result := False;
  593. //Stream.Position := fDirectoryEntriesStartPos + (fDirectoryHeader.ChunkSize * ChunkIndex);
  594. NameLength := LongInt(GetCompressedInteger(Stream));
  595. if NameLength > 1022 then NameLength := 1022;
  596. Stream.Read(buf[0], NameLength);
  597. buf[NameLength] := #0;
  598. PMGLEntry.Name := buf;
  599. PMGLEntry.ContentSection := LongWord(GetCompressedInteger(Stream));
  600. PMGLEntry.ContentOffset := GetCompressedInteger(Stream);
  601. PMGLEntry.DecompressedLength := GetCompressedInteger(Stream);
  602. if NameLength = 0 then Exit; // failed GetCompressedInteger sanity check
  603. Result := True;
  604. end;
  605. procedure TITSFReader.LookupPMGIchunk(Stream: TMemoryStream; out PMGIChunk: TPMGIIndexChunk);
  606. begin
  607. //Stream.Position := fDirectoryEntriesStartPos + (fDirectoryHeader.ChunkSize * ChunkIndex);
  608. Stream.Read(PMGIChunk, SizeOf(PMGIChunk));
  609. {$IFDEF ENDIAN_BIG}
  610. with PMGIChunk do begin
  611. UnusedSpace := LEtoN(UnusedSpace);
  612. end;
  613. {$ENDIF}
  614. end;
  615. function TITSFReader.ReadPMGIchunkEntryFromStream(Stream: TMemoryStream;
  616. var PMGIEntry: TPMGIIndexChunkEntry): Boolean;
  617. var
  618. Buf: array [0..1023] of char;
  619. NameLength: LongInt;
  620. begin
  621. Result := False;
  622. //Stream.Position := fDirectoryEntriesStartPos + (fDirectoryHeader.ChunkSize * ChunkIndex);
  623. NameLength := LongInt(GetCompressedInteger(Stream));
  624. if NameLength > 1023 then NameLength := 1023;
  625. Stream.Read(buf, NameLength);
  626. buf[NameLength] := #0;
  627. PMGIEntry.Name := buf;
  628. PMGIEntry.ListingChunk := GetCompressedInteger(Stream);
  629. if NameLength = 0 then Exit; // failed GetCompressedInteger sanity check
  630. Result := True;
  631. end;
  632. constructor TITSFReader.Create(AStream: TStream; FreeStreamOnDestroy: Boolean);
  633. begin
  634. fStream := AStream;
  635. fStream.Position := 0;
  636. fFreeStreamOnDestroy := FreeStreamOnDestroy;
  637. ReadHeader;
  638. if not IsValidFile then Exit;
  639. end;
  640. destructor TITSFReader.Destroy;
  641. begin
  642. if fFreeStreamOnDestroy then FreeAndNil(fStream);
  643. inherited Destroy;
  644. end;
  645. function TITSFReader.IsValidFile: Boolean;
  646. begin
  647. if (fStream = nil) then ChmLastError := ERR_STREAM_NOT_ASSIGNED
  648. else if (fITSFHeader.ITSFsig <> 'ITSF') then ChmLastError := ERR_NOT_VALID_FILE
  649. //else if (fITSFHeader.Version <> 2) and (fITSFHeader.Version <> 3)
  650. else if not (fITSFHeader.Version in [2..4])
  651. then
  652. ChmLastError := ERR_NOT_SUPPORTED_VERSION;
  653. Result := ChmLastError = ERR_NO_ERR;
  654. end;
  655. procedure TITSFReader.GetCompleteFileList(ForEach: TFileEntryForEach; AIncludeInternalFiles: Boolean = True);
  656. var
  657. ChunkStream: TMemoryStream;
  658. I : Integer;
  659. Entry: TPMGListChunkEntry;
  660. PMGLChunk: TPMGListChunk;
  661. CutOffPoint: Integer;
  662. NameLength: Integer;
  663. {$IFDEF CHM_DEBUG_CHUNKS}
  664. PMGIChunk: TPMGIIndexChunk;
  665. PMGIndex: Integer;
  666. {$ENDIF}
  667. begin
  668. if ForEach = nil then Exit;
  669. ChunkStream := TMemoryStream.Create;
  670. {$IFDEF CHM_DEBUG_CHUNKS}
  671. WriteLn('ChunkCount = ',fDirectoryHeader.DirectoryChunkCount);
  672. {$ENDIF}
  673. for I := 0 to fDirectoryHeader.DirectoryChunkCount-1 do begin
  674. GetDirectoryChunk(I, ChunkStream);
  675. case ChunkType(ChunkStream) of
  676. ctPMGL:
  677. begin
  678. LookupPMGLchunk(ChunkStream, PMGLChunk);
  679. {$IFDEF CHM_DEBUG_CHUNKS}
  680. WriteLn('PMGL: ', I, ' Prev PMGL: ', PMGLChunk.PreviousChunkIndex, ' Next PMGL: ', PMGLChunk.NextChunkIndex);
  681. {$ENDIF}
  682. CutOffPoint := ChunkStream.Size - PMGLChunk.UnusedSpace;
  683. while ChunkStream.Position < CutOffPoint do begin
  684. NameLength := GetCompressedInteger(ChunkStream);
  685. if (ChunkStream.Position > CutOffPoint) then Continue; // we have entered the quickref section
  686. SetLength(Entry.Name, NameLength);
  687. ChunkStream.ReadBuffer(Entry.Name[1], NameLength);
  688. if (Entry.Name = '') or (ChunkStream.Position > CutOffPoint) then Break; // we have entered the quickref section
  689. Entry.ContentSection := GetCompressedInteger(ChunkStream);
  690. if ChunkStream.Position > CutOffPoint then Break; // we have entered the quickref section
  691. Entry.ContentOffset := GetCompressedInteger(ChunkStream);
  692. if ChunkStream.Position > CutOffPoint then Break; // we have entered the quickref section
  693. Entry.DecompressedLength := GetCompressedInteger(ChunkStream);
  694. if ChunkStream.Position > CutOffPoint then Break; // we have entered the quickref section
  695. fCachedEntry := Entry; // if the caller trys to get this data we already know where it is :)
  696. if (Length(Entry.Name) = 1)
  697. or (AIncludeInternalFiles
  698. or
  699. ((Length(Entry.Name) > 1) and (not(Entry.Name[2] in ['#','$',':']))))
  700. then
  701. ForEach(Entry.Name, Entry.ContentOffset, Entry.DecompressedLength, Entry.ContentSection);
  702. end;
  703. end;
  704. {$IFDEF CHM_DEBUG_CHUNKS}
  705. ctPMGI:
  706. begin
  707. WriteLn('PMGI: ', I);
  708. LookupPMGIchunk(ChunkStream, PMGIChunk);
  709. CutOffPoint := ChunkStream.Size - PMGIChunk.UnusedSpace - 10;
  710. while ChunkStream.Position < CutOffPoint do begin
  711. NameLength := GetCompressedInteger(ChunkStream);
  712. SetLength(Entry.Name, NameLength);
  713. ChunkStream.ReadBuffer(Entry.Name[1], NameLength);
  714. PMGIndex := GetCompressedInteger(ChunkStream);
  715. WriteLn(Entry.Name, ' ', PMGIndex);
  716. end;
  717. end;
  718. ctUnknown: WriteLn('UNKNOWN CHUNKTYPE!' , I);
  719. {$ENDIF}
  720. end;
  721. end;
  722. end;
  723. function TITSFReader.ObjectExists(Name: String): QWord;
  724. var
  725. ChunkStream: TMemoryStream;
  726. QuickRefCount: Word;
  727. QuickRefIndex: array of Word;
  728. ItemCount: Integer;
  729. procedure ReadQuickRefSection;
  730. var
  731. OldPosn: QWord;
  732. Posn: Integer;
  733. I: Integer;
  734. begin
  735. OldPosn := ChunkStream.Position;
  736. Posn := ChunkStream.Size-SizeOf(Word);
  737. ChunkStream.Position := Posn;
  738. ItemCount := LEToN(ChunkStream.ReadWord);
  739. //WriteLn('Max ITems for next block = ', ItemCount-1);
  740. QuickRefCount := ItemCount div (1 + (1 shl fDirectoryHeader.Density));
  741. //WriteLn('QuickRefCount = ' , QuickRefCount);
  742. SetLength(QuickRefIndex, QuickRefCount+1);
  743. for I := 1 to QuickRefCount do begin
  744. Dec(Posn, SizeOf(Word));
  745. ChunkStream.Position := Posn;
  746. QuickRefIndex[I] := LEToN(ChunkStream.ReadWord);
  747. end;
  748. Inc(QuickRefCount);
  749. ChunkStream.Position := OldPosn;
  750. end;
  751. function ReadString(StreamPosition: Integer = -1): String;
  752. var
  753. NameLength: Integer;
  754. begin
  755. if StreamPosition > -1 then ChunkStream.Position := StreamPosition;
  756. NameLength := GetCompressedInteger(ChunkStream);
  757. SetLength(Result, NameLength);
  758. if NameLength>0 then
  759. ChunkStream.Read(Result[1], NameLength);
  760. end;
  761. var
  762. PMGLChunk: TPMGListChunk;
  763. PMGIChunk: TPMGIIndexChunk;
  764. //ChunkStream: TMemoryStream; declared above
  765. Entry: TPMGListChunkEntry;
  766. NextIndex: Integer;
  767. EntryName: String;
  768. CRes: Integer;
  769. I: Integer;
  770. begin
  771. Result := 0;
  772. //WriteLn('Looking for URL : ', Name);
  773. if Name = '' then Exit;
  774. if fDirectoryHeader.DirectoryChunkCount = 0 then exit;
  775. //WriteLn('Looking for ', Name);
  776. if Name = fCachedEntry.Name then
  777. Exit(fCachedEntry.DecompressedLength); // we've already looked it up
  778. ChunkStream := TMemoryStream.Create;
  779. try
  780. NextIndex := fDirectoryHeader.IndexOfRootChunk;
  781. if NextIndex < 0 then NextIndex := 0; // no PMGI chunks
  782. while NextIndex > -1 do begin
  783. GetDirectoryChunk(NextIndex, ChunkStream);
  784. NextIndex := -1;
  785. ReadQuickRefSection;
  786. {$IFDEF CHM_DEBUG}
  787. WriteLn('In Block ', NextIndex);
  788. {$endif}
  789. case ChunkType(ChunkStream) of
  790. ctUnknown: // something is wrong
  791. begin
  792. {$IFDEF CHM_DEBUG}WriteLn(NextIndex, ' << Unknown BlockType!');{$ENDIF}
  793. Break;
  794. end;
  795. ctPMGI: // we must follow the PMGI tree until we reach a PMGL block
  796. begin
  797. LookupPMGIchunk(ChunkStream, PMGIChunk);
  798. //QuickRefIndex[0] := ChunkStream.Position;
  799. I := 0;
  800. while ChunkStream.Position <= ChunkStream.Size - PMGIChunk.UnusedSpace do begin;
  801. EntryName := ReadString;
  802. if EntryName = '' then break;
  803. if ChunkStream.Position >= ChunkStream.Size - PMGIChunk.UnusedSpace then break;
  804. CRes := ChmCompareText(Name, EntryName);
  805. if CRes = 0 then begin
  806. // no more need of this block. onto the next!
  807. NextIndex := GetCompressedInteger(ChunkStream);
  808. Break;
  809. end;
  810. if CRes < 0 then begin
  811. if I = 0 then Break; // File doesn't exist
  812. // file is in previous entry
  813. Break;
  814. end;
  815. NextIndex := GetCompressedInteger(ChunkStream);
  816. Inc(I);
  817. end;
  818. end;
  819. ctPMGL:
  820. begin
  821. LookupPMGLchunk(ChunkStream, PMGLChunk);
  822. QuickRefIndex[0] := ChunkStream.Position;
  823. I := 0;
  824. while ChunkStream.Position <= ChunkStream.Size - PMGLChunk.UnusedSpace do begin
  825. // we consume the entry by reading it
  826. Entry.Name := ReadString;
  827. if Entry.Name = '' then break;
  828. if ChunkStream.Position >= ChunkStream.Size - PMGLChunk.UnusedSpace then break;
  829. Entry.ContentSection := GetCompressedInteger(ChunkStream);
  830. Entry.ContentOffset := GetCompressedInteger(ChunkStream);
  831. Entry.DecompressedLength := GetCompressedInteger(ChunkStream);
  832. CRes := ChmCompareText(Name, Entry.Name);
  833. if CRes = 0 then begin
  834. fCachedEntry := Entry;
  835. Result := Entry.DecompressedLength;
  836. Break;
  837. end;
  838. Inc(I);
  839. end;
  840. end; // case
  841. end;
  842. end;
  843. finally
  844. ChunkStream.Free;
  845. end;
  846. end;
  847. function TITSFReader.GetObject(Name: String): TMemoryStream;
  848. var
  849. SectionNames: TStringList;
  850. Entry: TPMGListChunkEntry;
  851. SectionName: String;
  852. begin
  853. Result := nil;
  854. if ObjectExists(Name) = 0 then begin
  855. //WriteLn('Object ', name,' Doesn''t exist or is zero sized.');
  856. Exit;
  857. end;
  858. Entry := fCachedEntry;
  859. if Entry.ContentSection = 0 then begin
  860. Result := TMemoryStream.Create;
  861. fStream.Position := fHeaderSuffix.Offset+ Entry.ContentOffset;
  862. Result.CopyFrom(fStream, fCachedEntry.DecompressedLength);
  863. end
  864. else begin // we have to get it from ::DataSpace/Storage/[MSCompressed,Uncompressed]/ControlData
  865. GetSections(SectionNames);
  866. FmtStr(SectionName, '::DataSpace/Storage/%s/',[SectionNames[Entry.ContentSection]]);
  867. Result := GetBlockFromSection(SectionName, Entry.ContentOffset, Entry.DecompressedLength);
  868. SectionNames.Free;
  869. end;
  870. if Result <> nil then Result.Position := 0;
  871. end;
  872. function TChmReader.GetContextUrl(Context: THelpContext): String;
  873. begin
  874. // will get '' if context not found
  875. Result := fContextList.GetURL(Context);
  876. end;
  877. function TChmReader.LookupTopicByID ( ATopicID: Integer; out ATitle: String) : String;
  878. var
  879. TopicURLTBLOffset: DWord;
  880. TopicTitleOffset: DWord;
  881. begin
  882. Result := '';
  883. ATitle := '';
  884. //WriteLn('Getting topic# ',ATopicID);
  885. if not CheckCommonStreams then
  886. Exit;
  887. fTOPICSStream.Position := ATopicID * 16;
  888. if fTOPICSStream.Position = ATopicID * 16 then
  889. begin
  890. fTOPICSStream.ReadDWord;
  891. TopicTitleOffset := LEtoN(fTOPICSStream.ReadDWord);
  892. TopicURLTBLOffset := LEtoN(fTOPICSStream.ReadDWord);
  893. if TopicTitleOffset <> $FFFFFFFF then
  894. ATitle := ReadStringsEntry(TopicTitleOffset);
  895. //WriteLn('Got a title: ', ATitle);
  896. Result := ReadURLSTR(TopicURLTBLOffset);
  897. end;
  898. end;
  899. const DefBlockSize = 2048;
  900. function LoadBtreeHeader(m:TMemoryStream;var btreehdr:TBtreeHeader):boolean;
  901. begin
  902. if m.size<sizeof(TBtreeHeader) Then
  903. Exit(False);
  904. result:=true;
  905. m.read(btreeHdr,sizeof(TBtreeHeader));
  906. {$IFDEF ENDIAN_BIG}
  907. btreehdr.flags :=LEToN(btreehdr.flags);
  908. btreehdr.blocksize :=LEToN(btreehdr.blocksize);
  909. btreehdr.lastlstblock :=LEToN(btreehdr.lastlstblock);
  910. btreehdr.indexrootblock:=LEToN(btreehdr.indexrootblock);
  911. btreehdr.nrblock :=LEToN(btreehdr.nrblock);
  912. btreehdr.treedepth :=LEToN(btreehdr.treedepth);
  913. btreehdr.nrkeywords :=LEToN(btreehdr.nrkeywords);
  914. btreehdr.codepage :=LEToN(btreehdr.codepage);
  915. btreehdr.lcid :=LEToN(btreehdr.lcid);
  916. btreehdr.ischm :=LEToN(btreehdr.ischm);
  917. {$endif}
  918. end;
  919. function readwcharstring(var head:pbyte;tail:pbyte;var readv : ansistring):boolean;
  920. var pw : PWord;
  921. oldhead : PByte;
  922. ws : WideString;
  923. n : Integer;
  924. begin
  925. oldhead:=head;
  926. pw:=pword(head);
  927. while (pw<pword(tail)) and (pw^<>word(0)) do
  928. inc(pw);
  929. inc(pw); // skip #0#0.
  930. head:=pbyte(pw);
  931. result:=head<tail;
  932. n:=head-oldhead;
  933. setlength(ws,n div sizeof(widechar));
  934. move(oldhead^,ws[1],n);
  935. for n:=1 to length(ws) do
  936. word(ws[n]):=LEToN(word(ws[n]));
  937. readv:=ws; // force conversion for now, and hope it doesn't require cwstring
  938. end;
  939. function TChmReader.GetIndexSitemap(ForceXML:boolean=false): TChmSiteMap;
  940. var Index : TMemoryStream;
  941. sitemap : TChmSiteMap;
  942. Item : TChmSiteMapItem;
  943. function AbortAndTryTextual:tchmsitemap;
  944. begin
  945. if Assigned(Index) Then Index.Free;
  946. // Second Try text Index
  947. Index := GetObject(IndexFile);
  948. if Index <> nil then
  949. begin
  950. Result := TChmSiteMap.Create(stIndex);
  951. Result.LoadFromStream(Index);
  952. Index.Free;
  953. end
  954. else
  955. result:=nil;
  956. end;
  957. procedure createentry(Name:ansistring;CharIndex:integer;Topic,Title:ansistring);
  958. var litem : TChmSiteMapItem;
  959. shortname : ansistring;
  960. longpart : ansistring;
  961. begin
  962. if charindex=0 then
  963. begin
  964. item:=sitemap.items.NewItem;
  965. item.keyword:=Name;
  966. item.local:=topic;
  967. item.text:=title;
  968. end
  969. else
  970. begin
  971. shortname:=copy(name,1,charindex-2);
  972. longpart:=copy(name,charindex,length(name)-charindex+1);
  973. if assigned(item) and (shortname=item.text) then
  974. begin
  975. litem:=item.children.newitem;
  976. litem.local:=topic;
  977. litem.keyword :=longpart; // recursively split this? No examples.
  978. litem.text:=title;
  979. end
  980. else
  981. begin
  982. item:=sitemap.items.NewItem;
  983. item.keyword:=shortname;
  984. item.local:=topic;
  985. item.text:=title;
  986. litem:=item.children.newitem;
  987. litem.keyword:=longpart;
  988. litem.local:=topic;
  989. litem.text :=Title; // recursively split this? No examples.
  990. end;
  991. end;
  992. end;
  993. procedure parselistingblock(p:pbyte);
  994. var hdr:PBTreeBlockHeader;
  995. head,tail : pbyte;
  996. isseealso,
  997. nrpairs : Integer;
  998. i : integer;
  999. PE : PBtreeBlockEntry;
  1000. title : string;
  1001. CharIndex,
  1002. ind:integer;
  1003. seealsostr,
  1004. topic,
  1005. Name : AnsiString;
  1006. item : TChmSiteMapItem;
  1007. begin
  1008. hdr:=PBTreeBlockHeader(p);
  1009. hdr^.Length :=LEToN(hdr^.Length);
  1010. hdr^.NumberOfEntries :=LEToN(hdr^.NumberOfEntries);
  1011. hdr^.IndexOfPrevBlock:=LEToN(hdr^.IndexOfPrevBlock);
  1012. hdr^.IndexOfNextBlock:=LEToN(hdr^.IndexOfNextBlock);
  1013. tail:=p+(2048-hdr^.length);
  1014. head:=p+sizeof(TBtreeBlockHeader);
  1015. {$ifdef binindex}
  1016. writeln('previndex : ',hdr^.IndexOfPrevBlock);
  1017. writeln('nextindex : ',hdr^.IndexOfNextBlock);
  1018. {$endif}
  1019. while head<tail do
  1020. begin
  1021. if not ReadWCharString(Head,Tail,Name) Then
  1022. Break;
  1023. {$ifdef binindex}
  1024. Writeln('name : ',name);
  1025. {$endif}
  1026. if (head+sizeof(TBtreeBlockEntry))>=tail then
  1027. break;
  1028. PE :=PBtreeBlockEntry(head);
  1029. NrPairs :=LEToN(PE^.nrpairs);
  1030. IsSeealso:=LEToN(PE^.isseealso);
  1031. CharIndex:=LEToN(PE^.CharIndex);
  1032. {$ifdef binindex}
  1033. Writeln('seealso: ',IsSeeAlso);
  1034. Writeln('entrydepth: ',LEToN(PE^.entrydepth));
  1035. Writeln('charindex : ',charindex );
  1036. Writeln('Nrpairs : ',NrPairs);
  1037. writeln('seealso data : ');
  1038. {$endif}
  1039. inc(head,sizeof(TBtreeBlockEntry));
  1040. if isseealso>0 then
  1041. begin
  1042. if not ReadWCharString(Head,Tail,SeeAlsoStr) Then
  1043. Break;
  1044. // have to figure out first what to do with it.
  1045. end
  1046. else
  1047. begin
  1048. if NrPairs>0 Then
  1049. for i:=0 to nrpairs-1 do
  1050. begin
  1051. if head<tail Then
  1052. begin
  1053. ind:=LEToN(plongint(head)^);
  1054. topic:=lookuptopicbyid(ind,title);
  1055. {$ifdef binindex}
  1056. writeln(i:3,' topic: ',topic);
  1057. writeln(' title: ',title);
  1058. {$endif}
  1059. inc(head,4);
  1060. end;
  1061. end;
  1062. end;
  1063. if nrpairs<>0 Then
  1064. createentry(Name,CharIndex,Topic,Title);
  1065. inc(head,4); // always 1
  1066. {$ifdef binindex}
  1067. if head<tail then
  1068. writeln('Zero based index (13 higher than last) :',plongint(head)^);
  1069. {$endif}
  1070. inc(head,4); // zero based index (13 higher than last
  1071. end;
  1072. end;
  1073. var TryTextual : boolean;
  1074. BHdr : TBTreeHeader;
  1075. block : Array[0..2047] of Byte;
  1076. i : Integer;
  1077. begin
  1078. Result := nil; SiteMap:=Nil;
  1079. // First Try Binary
  1080. Index := GetObject('/$WWKeywordLinks/BTree');
  1081. if (Index = nil) or ForceXML then
  1082. begin
  1083. Result:=AbortAndTryTextual;
  1084. Exit;
  1085. end;
  1086. if not CheckCommonStreams then
  1087. begin
  1088. Result:=AbortAndTryTextual;
  1089. Exit;
  1090. end;
  1091. SiteMap:=TChmSitemap.Create(StIndex);
  1092. Item :=Nil; // cached last created item, in case we need to make
  1093. // a child.
  1094. TryTextual:=True;
  1095. BHdr.LastLstBlock:=0;
  1096. if LoadBtreeHeader(index,BHdr) and (BHdr.LastLstBlock>0) Then
  1097. begin
  1098. if BHdr.BlockSize=defblocksize then
  1099. begin
  1100. for i:=0 to BHdr.lastlstblock do
  1101. begin
  1102. if (index.size-index.position)>=defblocksize then
  1103. begin
  1104. Index.read(block,defblocksize);
  1105. parselistingblock(@block)
  1106. end;
  1107. end;
  1108. trytextual:=false;
  1109. result:=sitemap;
  1110. end;
  1111. end;
  1112. if trytextual then
  1113. begin
  1114. sitemap.free;
  1115. Result:=AbortAndTryTextual;
  1116. end
  1117. else Index.Free;
  1118. end;
  1119. function TChmReader.GetTOCSitemap(ForceXML:boolean=false): TChmSiteMap;
  1120. function AddTOCItem(TOC: TStream; AItemOffset: DWord; SiteMapITems: TChmSiteMapItems): DWord;
  1121. var
  1122. Props: DWord;
  1123. Item: TChmSiteMapItem;
  1124. NextEntry: DWord;
  1125. TopicsIndex: DWord;
  1126. Title: String;
  1127. begin
  1128. Toc.Position:= AItemOffset + 4;
  1129. Item := SiteMapITems.NewItem;
  1130. Props := LEtoN(TOC.ReadDWord);
  1131. if (Props and TOC_ENTRY_HAS_LOCAL) = 0 then
  1132. Item.Text:= ReadStringsEntry(LEtoN(TOC.ReadDWord))
  1133. else
  1134. begin
  1135. TopicsIndex := LEtoN(TOC.ReadDWord);
  1136. Item.Local := LookupTopicByID(TopicsIndex, Title);
  1137. Item.Text := Title;
  1138. end;
  1139. TOC.ReadDWord;
  1140. Result := LEtoN(TOC.ReadDWord);
  1141. if Props and TOC_ENTRY_HAS_CHILDREN > 0 then
  1142. begin
  1143. NextEntry := LEtoN(TOC.ReadDWord);
  1144. repeat
  1145. NextEntry := AddTOCItem(TOC, NextEntry, Item.Children);
  1146. until NextEntry = 0;
  1147. end;
  1148. end;
  1149. var
  1150. TOC: TStream;
  1151. TOPICSOffset: DWord;
  1152. EntriesOffset: DWord;
  1153. EntryCount: DWord;
  1154. EntryInfoOffset: DWord;
  1155. NextItem: DWord;
  1156. begin
  1157. Result := nil;
  1158. // First Try Binary
  1159. TOC := GetObject('/#TOCIDX');
  1160. if (TOC = nil) or ForceXML then
  1161. begin
  1162. if Assigned(TOC) Then Toc.Free;
  1163. // Second Try text toc
  1164. TOC := GetObject(TOCFile);
  1165. if TOC <> nil then
  1166. begin
  1167. Result := TChmSiteMap.Create(stTOC);
  1168. Result.LoadFromStream(TOC);
  1169. Toc.Free;
  1170. end;
  1171. Exit;
  1172. end;
  1173. // TOPICS URLSTR URLTBL must all exist to read binary toc
  1174. // if they don't then try text file
  1175. if not CheckCommonStreams then
  1176. begin
  1177. TOC.Free;
  1178. TOC := GetObject(TOCFile);
  1179. if TOC <> nil then
  1180. begin
  1181. Result := TChmSiteMap.Create(stTOC);
  1182. Result.LoadFromStream(TOC);
  1183. Toc.Free;
  1184. end;
  1185. Exit;
  1186. end;
  1187. // Binary Toc Exists
  1188. Result := TChmSiteMap.Create(stTOC);
  1189. EntryInfoOffset := NtoLE(TOC.ReadDWord);
  1190. EntriesOffset := NtoLE(TOC.ReadDWord);
  1191. EntryCount := NtoLE(TOC.ReadDWord);
  1192. TOPICSOffset := NtoLE(TOC.ReadDWord);
  1193. if EntryCount = 0 then
  1194. begin
  1195. Toc.Free;
  1196. Exit;
  1197. end;
  1198. NextItem := EntryInfoOffset;
  1199. repeat
  1200. NextItem := AddTOCItem(Toc, NextItem, Result.Items);
  1201. until NextItem = 0;
  1202. TOC.Free;
  1203. end;
  1204. function TChmReader.HasContextList: Boolean;
  1205. begin
  1206. Result := fContextList.Count > 0;
  1207. end;
  1208. procedure TITSFReader.GetSections(out Sections: TStringList);
  1209. var
  1210. Stream: TStream;
  1211. EntryCount: Word;
  1212. X: Integer;
  1213. {$IFDEF ENDIAN_BIG}
  1214. I: Integer;
  1215. {$ENDIF}
  1216. WString: array [0..31] of WideChar;
  1217. StrLength: Word;
  1218. begin
  1219. Sections := TStringList.Create;
  1220. //WriteLn('::DataSpace/NameList Size = ', ObjectExists('::DataSpace/NameList'));
  1221. Stream := GetObject('::DataSpace/NameList');
  1222. if Stream = nil then begin
  1223. //WriteLn('Failed to get ::DataSpace/NameList!');
  1224. exit;
  1225. end;
  1226. Stream.Position := 2;
  1227. EntryCount := LEtoN(Stream.ReadWord);
  1228. for X := 0 to EntryCount -1 do begin
  1229. StrLength := LEtoN(Stream.ReadWord);
  1230. if StrLength > 31 then StrLength := 31;
  1231. Stream.Read(WString, SizeOf(WideChar)*(StrLength+1)); // the strings are stored null terminated
  1232. {$IFDEF ENDIAN_BIG}
  1233. for I := 0 to StrLength-1 do
  1234. WString[I] := WideChar(LEtoN(Ord(WString[I])));
  1235. {$ENDIF}
  1236. Sections.Add(WString);
  1237. end;
  1238. Stream.Free;
  1239. end;
  1240. function TITSFReader.GetBlockFromSection(SectionPrefix: String; StartPos: QWord;
  1241. BlockLength: QWord): TMemoryStream;
  1242. var
  1243. Compressed: Boolean;
  1244. Sig: Array [0..3] of char;
  1245. CompressionVersion: LongWord;
  1246. CompressedSize: QWord;
  1247. UnCompressedSize: QWord;
  1248. //LZXResetInterval: LongWord;
  1249. //LZXWindowSize: LongWord;
  1250. //LZXCacheSize: LongWord;
  1251. ResetTableEntry: TPMGListChunkEntry;
  1252. ResetTable: TLZXResetTableArr;
  1253. WriteCount: QWord;
  1254. BlockWriteLength: QWord;
  1255. WriteStart: LongWord;
  1256. ReadCount:LongInt;
  1257. LZXState: PLZXState;
  1258. InBuf: array of Byte;
  1259. OutBuf: PByte;
  1260. BlockSize: QWord;
  1261. X: Integer;
  1262. FirstBlock, LastBlock: LongInt;
  1263. ResultCode: LongInt;
  1264. procedure ReadBlock;
  1265. begin
  1266. if ReadCount > Length(InBuf) then
  1267. SetLength(InBuf, ReadCount);
  1268. fStream.Read(InBuf[0], ReadCount);
  1269. end;
  1270. begin
  1271. // okay now the fun stuff ;)
  1272. Result := nil;
  1273. Compressed := ObjectExists(SectionPrefix+'Transform/{7FC28940-9D31-11D0-9B27-00A0C91E9C7C}/InstanceData/ResetTable')>0;
  1274. // the easy method
  1275. if Not(Compressed) then begin
  1276. if ObjectExists(SectionPrefix+'Content') > 0 then begin
  1277. Result := TMemoryStream.Create;
  1278. fStream.Position := fHeaderSuffix.Offset + fCachedEntry.ContentOffset + StartPos;
  1279. Result.CopyFrom(fStream, BlockLength);
  1280. end;
  1281. Exit;
  1282. end
  1283. else
  1284. ResetTableEntry := fCachedEntry;
  1285. // First make sure that it is a compression we can read
  1286. if ObjectExists(SectionPrefix+'ControlData') > 0 then begin
  1287. fStream.Position := fHeaderSuffix.Offset + fCachedEntry.ContentOffset + 4;
  1288. fStream.Read(Sig, 4);
  1289. if Sig <> 'LZXC' then Exit;
  1290. CompressionVersion := LEtoN(fStream.ReadDWord);
  1291. if CompressionVersion > 2 then exit;
  1292. {LZXResetInterval := }LEtoN(fStream.ReadDWord);
  1293. {LZXWindowSize := }LEtoN(fStream.ReadDWord);
  1294. {LZXCacheSize := }LEtoN(fStream.ReadDWord);
  1295. BlockSize := FindBlocksFromUnCompressedAddr(ResetTableEntry, CompressedSize, UnCompressedSize, ResetTable);
  1296. if UncompressedSize > 0 then ; // to avoid a compiler note
  1297. if StartPos > 0 then
  1298. FirstBlock := StartPos div BlockSize
  1299. else
  1300. FirstBlock := 0;
  1301. LastBlock := (StartPos+BlockLength) div BlockSize;
  1302. if ObjectExists(SectionPrefix+'Content') = 0 then exit;
  1303. //WriteLn('Compressed Data start''s at: ', fHeaderSuffix.Offset + fCachedEntry.ContentOffset,' Size is: ', fCachedEntry.DecompressedLength);
  1304. Result := TMemoryStream.Create;
  1305. Result.Size := BlockLength;
  1306. SetLength(InBuf,BlockSize);
  1307. OutBuf := GetMem(BlockSize);
  1308. // First Init a PLZXState
  1309. LZXState := LZXinit(16);
  1310. if LZXState = nil then begin
  1311. Exit;
  1312. end;
  1313. // if FirstBlock is odd (1,3,5,7 etc) we have to read the even block before it first.
  1314. if FirstBlock and 1 = 1 then begin
  1315. fStream.Position := fHeaderSuffix.Offset + fCachedEntry.ContentOffset + (ResetTable[FirstBLock-1]);
  1316. ReadCount := ResetTable[FirstBlock] - ResetTable[FirstBlock-1];
  1317. BlockWriteLength:=BlockSize;
  1318. ReadBlock;
  1319. ResultCode := LZXdecompress(LZXState, @InBuf[0], OutBuf, ReadCount, LongInt(BlockWriteLength));
  1320. end;
  1321. // now start the actual decompression loop
  1322. for X := FirstBlock to LastBlock do begin
  1323. fStream.Position := fHeaderSuffix.Offset + fCachedEntry.ContentOffset + (ResetTable[X]);
  1324. if X = FirstBLock then
  1325. WriteStart := StartPos - (X*BlockSize)
  1326. else
  1327. WriteStart := 0;
  1328. if X = High(ResetTable) then
  1329. ReadCount := CompressedSize - ResetTable[X]
  1330. else
  1331. ReadCount := ResetTable[X+1] - ResetTable[X];
  1332. BlockWriteLength := BlockSize;
  1333. if FirstBlock = LastBlock then begin
  1334. WriteCount := BlockLength;
  1335. end
  1336. else if X = LastBlock then
  1337. WriteCount := (StartPos+BlockLength) - (X*BlockSize)
  1338. else WriteCount := BlockSize - WriteStart;
  1339. ReadBlock;
  1340. ResultCode := LZXdecompress(LZXState, @InBuf[0], OutBuf, ReadCount, LongInt(BlockWriteLength));
  1341. //now write the decompressed data to the stream
  1342. if ResultCode = DECR_OK then begin
  1343. Result.Write(OutBuf[WriteStart], QWord(WriteCount));
  1344. end
  1345. else begin
  1346. {$IFDEF CHM_DEBUG} // windows gui program will cause an exception with writeln's
  1347. WriteLn('Decompress FAILED with error code: ', ResultCode);
  1348. {$ENDIF}
  1349. Result.Free;
  1350. Result := Nil;
  1351. FreeMem(OutBuf);
  1352. SetLength(ResetTable,0);
  1353. LZXteardown(LZXState);
  1354. Exit;
  1355. end;
  1356. // if the next block is an even numbered block we have to reset the decompressor state
  1357. if (X < LastBlock) and (X and 1 = 1) then LZXreset(LZXState);
  1358. end;
  1359. FreeMem(OutBuf);
  1360. SetLength(ResetTable,0);
  1361. LZXteardown(LZXState);
  1362. end;
  1363. end;
  1364. function TITSFReader.FindBlocksFromUnCompressedAddr(var ResetTableEntry: TPMGListChunkEntry;
  1365. out CompressedSize: QWord; out UnCompressedSize: QWord; out LZXResetTable: TLZXResetTableArr): QWord;
  1366. var
  1367. BlockCount: LongWord;
  1368. {$IFDEF ENDIAN_BIG}
  1369. I: Integer;
  1370. {$ENDIF}
  1371. begin
  1372. Result := 0;
  1373. fStream.Position := fHeaderSuffix.Offset + ResetTableEntry.ContentOffset;
  1374. fStream.ReadDWord;
  1375. BlockCount := LEtoN(fStream.ReadDWord);
  1376. fStream.ReadDWord;
  1377. fStream.ReadDWord; // TableHeaderSize;
  1378. fStream.Read(UnCompressedSize, SizeOf(QWord));
  1379. UnCompressedSize := LEtoN(UnCompressedSize);
  1380. fStream.Read(CompressedSize, SizeOf(QWord));
  1381. CompressedSize := LEtoN(CompressedSize);
  1382. fStream.Read(Result, SizeOf(QWord)); // block size
  1383. Result := LEtoN(Result);
  1384. // now we are located at the first block index
  1385. SetLength(LZXResetTable, BlockCount);
  1386. fStream.Read(LZXResetTable[0], SizeOf(QWord)*BlockCount);
  1387. {$IFDEF ENDIAN_BIG}
  1388. for I := 0 to High(LZXResetTable) do
  1389. LZXResetTable[I] := LEtoN(LZXResetTable[I]);
  1390. {$ENDIF}
  1391. end;
  1392. { TContextList }
  1393. procedure TContextList.AddContext(Context: THelpContext; Url: String);
  1394. var
  1395. ContextItem: PContextItem;
  1396. begin
  1397. New(ContextItem);
  1398. Add(ContextItem);
  1399. ContextItem^.Context := Context;
  1400. ContextItem^.Url := Url;
  1401. end;
  1402. function TContextList.GetURL(Context: THelpContext): String;
  1403. var
  1404. X: Integer;
  1405. begin
  1406. Result := '';
  1407. for X := 0 to Count-1 do begin
  1408. if PContextItem(Get(X))^.Context = Context then begin
  1409. Result := PContextItem(Get(X))^.Url;
  1410. Exit;
  1411. end;
  1412. end;
  1413. end;
  1414. procedure TContextList.Clear;
  1415. var
  1416. X: Integer;
  1417. begin
  1418. for X := Count-1 downto 0 do begin
  1419. Dispose(PContextItem(Get(X)));
  1420. Delete(X);
  1421. end;
  1422. end;
  1423. { TChmFileList }
  1424. procedure TChmFileList.Delete(Index: Integer);
  1425. begin
  1426. Chm[Index].Free;
  1427. inherited Delete(Index);
  1428. end;
  1429. function TChmFileList.GetChm(AIndex: Integer): TChmReader;
  1430. begin
  1431. if AIndex = -1 then
  1432. Result := fLastChm
  1433. else
  1434. Result := TChmReader(Objects[AIndex]);
  1435. end;
  1436. function TChmFileList.GetFileName(AIndex: Integer): String;
  1437. begin
  1438. if AIndex = -1 then
  1439. AIndex := IndexOfObject(fLastChm);
  1440. Result := Strings[AIndex];
  1441. end;
  1442. procedure TChmFileList.OpenNewFile(AFileName: String);
  1443. var
  1444. AStream: TFileStream;
  1445. AChm: TChmReader;
  1446. AIndex: Integer;
  1447. begin
  1448. if not FileExists(AFileName) then exit;
  1449. AStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite);
  1450. AChm := TChmReader.Create(AStream, True);
  1451. AIndex := AddObject(AFileName, AChm);
  1452. fLastChm := AChm;
  1453. if Assigned(fOnOpenNewFile) then fOnOpenNewFile(Self, AIndex)
  1454. else fUnNotifiedFiles.Add(AChm);
  1455. end;
  1456. function TChmFileList.CheckOpenFile(AFileName: String): Boolean;
  1457. var
  1458. X: Integer;
  1459. begin
  1460. Result := False;
  1461. for X := 0 to Count-1 do begin
  1462. if ExtractFileName(FileName[X]) = AFileName then begin
  1463. fLastChm := Chm[X];
  1464. Result := True;
  1465. Exit;
  1466. end;
  1467. end;
  1468. if not Result then begin
  1469. AFileName := ExtractFilePath(FileName[0])+AFileName;
  1470. if FileExists(AFileName) and (ExtractFileExt(AFileName) = '.chm') then OpenNewFile(AFileName);
  1471. Result := True;
  1472. end;
  1473. end;
  1474. function TChmFileList.MetaObjectExists(var Name: String): QWord;
  1475. var
  1476. AFileName: String;
  1477. URL: String;
  1478. fStart, fEnd: Integer;
  1479. Found: Boolean;
  1480. begin
  1481. Found := False;
  1482. Result := 0;
  1483. //Known META file link types
  1484. // ms-its:name.chm::/topic.htm
  1485. //mk:@MSITStore:name.chm::/topic.htm
  1486. if Pos('ms-its:', Name) > 0 then begin
  1487. fStart := Pos('ms-its:', Name)+Length('ms-its:');
  1488. fEnd := Pos('::', Name)-fStart;
  1489. AFileName := Copy(Name, fStart, fEnd);
  1490. fStart := fEnd+fStart+2;
  1491. fEnd := Length(Name) - (fStart-1);
  1492. URL := Copy(Name, fStart, fEnd);
  1493. Found := True;
  1494. end
  1495. else if Pos('mk:@MSITStore:', Name) > 0 then begin
  1496. fStart := Pos('mk:@MSITStore:', Name)+Length('mk:@MSITStore:');
  1497. fEnd := Pos('::', Name)-fStart;
  1498. AFileName := Copy(Name, fStart, fEnd);
  1499. fStart := fEnd+fStart+2;
  1500. fEnd := Length(Name) - (fStart-1);
  1501. URL := Copy(Name, fStart, fEnd);
  1502. Found := True;
  1503. end;
  1504. if not Found then exit;
  1505. //WriteLn('Looking for URL ', URL, ' in ', AFileName);
  1506. if CheckOpenFile(AFileName) then
  1507. Result := fLastChm.ObjectExists(URL);
  1508. if Result > 0 then NAme := Url;
  1509. end;
  1510. function TChmFileList.MetaGetObject(Name: String): TMemoryStream;
  1511. begin
  1512. Result := nil;
  1513. if MetaObjectExists(Name) > 0 then Result := fLastChm.GetObject(Name);
  1514. end;
  1515. constructor TChmFileList.Create(PrimaryFileName: String);
  1516. begin
  1517. inherited Create;
  1518. fUnNotifiedFiles := TList.Create;
  1519. OpenNewFile(PrimaryFileName);
  1520. end;
  1521. destructor TChmFileList.Destroy;
  1522. begin
  1523. fUnNotifiedFiles.Free;
  1524. inherited Destroy;
  1525. end;
  1526. procedure TChmFileList.SetOnOpenNewFile(AValue: TChmFileOpenEvent);
  1527. var
  1528. X: Integer;
  1529. begin
  1530. fOnOpenNewFile := AValue;
  1531. if AValue = nil then exit;
  1532. for X := 0 to fUnNotifiedFiles.Count-1 do
  1533. AValue(Self, X);
  1534. fUnNotifiedFiles.Clear;
  1535. end;
  1536. function TChmFileList.ObjectExists(Name: String; var fChm: TChmReader = nil): QWord;
  1537. begin
  1538. Result := 0;
  1539. if Count = 0 then exit;
  1540. if fChm <> nil then fLastChm := fChm;
  1541. Result := fLastChm.ObjectExists(Name);
  1542. if Result = 0 then begin
  1543. Result := Chm[0].ObjectExists(Name);
  1544. if Result > 0 then fLastChm := Chm[0];
  1545. end;
  1546. if Result = 0 then begin
  1547. Result := MetaObjectExists(Name);
  1548. end;
  1549. if (Result <> 0) and (fChm = nil) then
  1550. fChm := fLastChm;
  1551. end;
  1552. function TChmFileList.GetObject(Name: String): TMemoryStream;
  1553. begin
  1554. Result := nil;
  1555. if Count = 0 then exit;
  1556. Result := fLastChm.GetObject(Name);
  1557. if Result = nil then Result := MetaGetObject(Name);
  1558. end;
  1559. function TChmFileList.IsAnOpenFile(AFileName: String): Boolean;
  1560. var
  1561. X: Integer;
  1562. begin
  1563. Result := False;
  1564. for X := 0 to Count-1 do begin
  1565. if AFileName = FileName[X] then Exit(True);
  1566. end;
  1567. end;
  1568. end.