itolitlsreader.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. { Copyright (C) <2010> <Andrew Haines> itloitlsreader.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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  13. }
  14. {
  15. See the file COPYING.modifiedLGPL, included in this distribution,
  16. for details about the copyright.
  17. }
  18. {$IFNDEF FPC_DOTTEDUNITS}
  19. unit ITOLITLSReader;
  20. {$ENDIF FPC_DOTTEDUNITS}
  21. {$mode objfpc}{$H+}
  22. { $DEFINE DEBUG_HELP2}
  23. interface
  24. {$IFDEF FPC_DOTTEDUNITS}
  25. uses
  26. System.Classes, Chm.Reader, Chm.ItolItls.Types, System.SysUtils, Chm.Base, Chm.ItsFTransform;
  27. {$ELSE FPC_DOTTEDUNITS}
  28. uses
  29. Classes, chmreader, itolitlstypes, Sysutils, chmbase, itsftransform;
  30. {$ENDIF FPC_DOTTEDUNITS}
  31. type
  32. { TITOLITLSReader }
  33. TITOLITLSReader = class(TITSFReader)
  34. private
  35. FStartStreamPos: QWord; // used when the data we are reading is part of a larger file
  36. SectionNames: TStringList;
  37. function GetStreamPos: Qword;
  38. procedure SetStreamPos(const AValue: Qword);
  39. private
  40. Header: TITOLITLSHeader;
  41. HeaderSectionTable: array of TITOLITLSHeaderSectionEntry;
  42. PostHeader: TITOLITLSPostHeader;
  43. CAOLHeader: TCAOLRec;
  44. function FileSize: QWord;
  45. function GetChunkType(AStream: TStream): TDirChunkType;
  46. function GetTransform(const AGuid: TGuid): TITSFTransform;
  47. procedure ReadHeader; override;
  48. procedure ReadHeaderEntries; override;
  49. function GetTransforms(ASectionPrefix: String): TITSFTransformList;
  50. property StreamPos: Qword read GetStreamPos write SetStreamPos;
  51. public
  52. constructor Create(AStream: TStream; FreeStreamOnDestroy: Boolean); override;
  53. destructor Destroy; override;
  54. procedure GetCompleteFileList(ForEach: TFileEntryForEach; AIncludeInternalFiles: Boolean = True); override;
  55. function ObjectExists(Name: String): QWord; override;
  56. function GetObject(Name: String): TMemoryStream; override;
  57. end;
  58. implementation
  59. type
  60. { TStreamChunk }
  61. TStreamChunk = class(TStream)
  62. private
  63. FStream: TStream;
  64. FSize: QWord;
  65. FBasePos: QWord;
  66. FPos: QWord;
  67. public
  68. Function GetSize : Int64; Override;
  69. function Read(var Buffer; Count: Longint): Longint; override;
  70. function Seek(Offset: Longint; Origin: Word): Longint; override;
  71. constructor Create(AHostStream: TStream; ABasePos, ASize: QWord);
  72. end;
  73. { TStreamChunk }
  74. function TStreamChunk.GetSize: Int64;
  75. begin
  76. Result:=FSize;
  77. end;
  78. function TStreamChunk.Read(var Buffer; Count: Longint): Longint;
  79. begin
  80. FStream.Seek(FBasePos+FPos, soFromBeginning);
  81. {$IFDEF DEBUG_HELP2}
  82. //WriteLn('Want Read Count: ', Count,' Pos = ', FPos);
  83. //if FSize - FPos < Count then
  84. // Count := FSize - FPos;
  85. {$ENDIF}
  86. Result := FStream.Read(Buffer, Count);
  87. Inc(FPos, Result);
  88. end;
  89. function TStreamChunk.Seek(Offset: Longint; Origin: Word): Longint;
  90. var
  91. NewPosition: LongInt;
  92. begin
  93. Case Origin of
  94. soFromBeginning : NewPosition:=Offset;
  95. soFromEnd : NewPosition:=FSize+Offset;
  96. soFromCurrent : NewPosition:=NewPosition+Offset;
  97. end;
  98. {$IFDEF DEBUG_HELP2}
  99. //WriteLn('WantSeek = ', Offset,' Size = ', FSize);
  100. {$ENDIF}
  101. FPos:=NewPosition;
  102. Exit(NewPosition);
  103. if NewPosition < 0 then NewPosition := 0;
  104. if NewPosition >= FSize then NewPosition := FSize-1;
  105. FStream.Position := FBasePos+NewPosition;
  106. Result := FStream.Position - FBasePos;
  107. FPos := Result;
  108. {$IFDEF DEBUG_HELP2}
  109. //WriteLn('Pos = ', fpos);
  110. {$ENDIF}
  111. end;
  112. constructor TStreamChunk.Create(AHostStream: TStream; ABasePos, ASize: QWord);
  113. begin
  114. FStream := AHostStream;
  115. FBasePos := ABasePos;
  116. FSize := ASize;
  117. {$IFDEF DEBUG_HELP2}
  118. //WriteLn('Created Size = ', FSize, ' Offset = ', ABasePos);
  119. {$ENDIF}
  120. end;
  121. { TITOLITLSReader }
  122. function TITOLITLSReader.GetStreamPos: Qword;
  123. begin
  124. Result := fStream.Position-FStartStreamPos;
  125. end;
  126. procedure TITOLITLSReader.SetStreamPos(const AValue: Qword);
  127. begin
  128. fStream.Position:=FStartStreamPos+AValue;
  129. end;
  130. function TITOLITLSReader.FileSize: QWord;
  131. begin
  132. fStream.Position:=LEtoN(HeaderSectionTable[0].OffSet)+8;
  133. fStream.Read(Result, 8);
  134. Result := LEtoN(Result);
  135. end;
  136. function TITOLITLSReader.GetChunkType(AStream: TStream): TDirChunkType;
  137. var
  138. Sig: TSig;
  139. begin
  140. AStream.Read(Sig, 4);
  141. if Sig = 'PMGL' then Result := ctPMGL
  142. else if Sig = 'PMGI' then Result := ctPMGI
  143. else if Sig = 'AOLL' then Result := ctAOLL
  144. else if Sig = 'AOLI' then Result := ctAOLI;
  145. AStream.Seek(-4,soFromCurrent);
  146. end;
  147. function TITOLITLSReader.GetTransform(const AGuid: TGuid): TITSFTransform;
  148. begin
  149. Result := nil;
  150. end;
  151. procedure TITOLITLSReader.ReadHeader;
  152. var
  153. CachedPos: QWord;
  154. SectionName: string;
  155. i: Integer;
  156. begin
  157. {$IFDEF DEBUG_HELP2}
  158. WriteLn(ClassName);
  159. {$ENDIF}
  160. fStream.Read(Header, SizeOf(TITOLITLSHeader));
  161. if not((Header.Sig[0] = 'ITOL') and (Header.Sig[1] = 'ITLS')) then
  162. Exit;
  163. ReadHeaderEntries;
  164. CachedPos := StreamPos;
  165. fStream.Read(PostHeader, Sizeof(TITOLITLSPostHeader));
  166. StreamPos := CachedPos + PostHeader.CAOLOffset;
  167. fStream.Read(CAOLHeader, SizeOf(TCAOLRec));
  168. {$IFDEF DEBUG_HELP2}
  169. WriteLn(CAOLHeader.ITSFHeader.ITSFsig);
  170. {$ENDIF}
  171. GetSections(SectionNames);
  172. for i := 1 to SectionNames.Count-1 do
  173. begin
  174. FmtStr(SectionName, '::DataSpace/Storage/%s/',[SectionNames[i]]);
  175. SectionNames.Objects[i] := GetTransforms(SectionName);
  176. end;
  177. end;
  178. procedure TITOLITLSReader.ReadHeaderEntries;
  179. var
  180. i: Integer;
  181. begin
  182. StreamPos := Header.HeaderSectionTableOffset;
  183. SetLength(HeaderSectionTable, Header.HeaderSectionEntryCount);
  184. for i := 0 to Header.HeaderSectionEntryCount -1 do
  185. begin
  186. fStream.Read(HeaderSectionTable[i], SizeOf(TITOLITLSHeaderSectionEntry));
  187. HeaderSectionTable[i].OffSet:= LEtoN(HeaderSectionTable[i].OffSet);
  188. HeaderSectionTable[i].Length:= LEtoN(HeaderSectionTable[i].Length);
  189. {$IFDEF DEBUG_HELP2}
  190. //WriteLn('Entry #',i,' Offset = ',Entry.OffSet,' Length = ',Entry.Length);
  191. {$ENDIF}
  192. end;
  193. end;
  194. function TITOLITLSReader.GetTransforms(ASectionPrefix: String): TITSFTransformList;
  195. var
  196. Stream: TMemoryStream;
  197. Guid: TGuid;
  198. GCount: Integer;
  199. Transform: TITSFTransform;
  200. TransformClass: TITSFTransformClass = nil;
  201. Idx: Integer;
  202. begin
  203. Result := nil;
  204. Stream := GetObject(ASectionPrefix+'Transform/List');
  205. if Stream = nil then
  206. begin
  207. {$IFDEF DEBUG_HELP2}
  208. //WriteLn(ASectionPrefix+'Transform/List doesnt exist!');
  209. {$ENDIF}
  210. Exit;
  211. end;
  212. Result := TITSFTransformList.Create;
  213. FillChar(Guid, SizeOf(Guid), 0);
  214. TransformClass := RegisteredTransforms.Transform[Guid];
  215. Idx := Result.AddTransform(TransformClass);
  216. Transform := TransformClass.Create(@Self.GetObject, nil);
  217. Result.TransformInstance[Idx] := Transform;
  218. {$IFDEF DEBUG_HELP2}
  219. WriteLn('Sec: ', ASectionPrefix, ' Transform Add ', Transform.ClassName);
  220. {$ENDIF}
  221. GCount := Stream.Size div SizeOf(TGuid);
  222. while GCount > 0 do
  223. begin
  224. Dec(GCount);
  225. Stream.Read(Guid, 16);
  226. TransformClass := RegisteredTransforms.Transform[Guid];
  227. Idx := Result.AddTransform(TransformClass);
  228. Transform := TransformClass.Create(@Self.GetObject, Transform);
  229. Result.TransformInstance[Idx] := Transform;
  230. {$IFDEF DEBUG_HELP2}
  231. WriteLn('Sec: ', ASectionPrefix, ' Transform Add ', Transform.ClassName);
  232. {$ENDIF}
  233. end;
  234. Stream.Free;
  235. end;
  236. constructor TITOLITLSReader.Create(AStream: TStream;
  237. FreeStreamOnDestroy: Boolean);
  238. begin
  239. inherited Create(AStream, FreeStreamOnDestroy);
  240. end;
  241. destructor TITOLITLSReader.Destroy;
  242. begin
  243. if SectionNames<> nil then
  244. begin
  245. while SectionNames.Count > 0 do
  246. begin
  247. if SectionNames.Objects[SectionNames.Count-1] <> nil then
  248. SectionNames.Objects[SectionNames.Count-1].Free;
  249. SectionNames.Delete(SectionNames.Count-1);
  250. end;
  251. SectionNames.Free;
  252. end;
  253. inherited Destroy;
  254. end;
  255. function ReadEntry(AStream: TStream): TPMGListChunkEntry;
  256. var
  257. NameLength: LongInt;
  258. begin
  259. NameLength:=GetCompressedInteger(AStream);
  260. SetLength(Result.Name, NameLength);
  261. AStream.Read(Result.Name[1], NameLength);
  262. Result.ContentSection:= GetCompressedInteger(AStream);
  263. Result.ContentOffset:= GetCompressedInteger(AStream);
  264. Result.DecompressedLength:= GetCompressedInteger(AStream);
  265. end;
  266. procedure TITOLITLSReader.GetCompleteFileList(ForEach: TFileEntryForEach; AIncludeInternalFiles: Boolean = True);
  267. var
  268. SecOffset: QWord;
  269. IFCM: TIFCMRec;
  270. ChunkType: TDirChunkType;
  271. Chunk: TMemoryStream;
  272. i, j: Integer;
  273. AOLL: TAOLLChunkHeader;
  274. AOLI: TAOLIChunkHeader;
  275. Entry: TPMGListChunkEntry;// not really a PMGL entry but the members are the same
  276. NameLength: LongInt;
  277. EntryCount: Word;
  278. begin
  279. StreamPos:=HeaderSectionTable[1].OffSet;
  280. fStream.Read(IFCM, SizeOf(IFCM));
  281. for i := 0 to IFCM.ChunkCount-1 do
  282. begin
  283. Chunk:= TMemoryStream.Create;
  284. Chunk.CopyFrom(fStream, IFCM.ChunkSize);
  285. Chunk.Position:=0;
  286. ChunkType:= GetChunkType(Chunk);
  287. case ChunkType of
  288. ctAOLL :
  289. begin
  290. Chunk.Read(AOLL, SizeOf(AOLL));
  291. Chunk.Seek(-2, soFromEnd);
  292. EntryCount:= LEtoN(Chunk.ReadWord);
  293. {$IFDEF DEBUG_HELP2}
  294. WriteLn(EntryCount);
  295. {$ENDIF}
  296. Chunk.Seek(SizeOf(AOLL), soFromBeginning);
  297. for j := 0 to EntryCount-1 do
  298. begin
  299. Entry := ReadEntry(Chunk);
  300. ForEach(Entry.Name, Entry.ContentOffset, Entry.DecompressedLength, Entry.ContentSection);
  301. end;
  302. end;
  303. ctAOLI :
  304. begin
  305. //Chunk.Read(AOLI, SizeOf(AOLI));
  306. end;
  307. end;
  308. Chunk.Free;
  309. end;
  310. end;
  311. function TITOLITLSReader.ObjectExists(Name: String): QWord;
  312. var
  313. IFCM: TIFCMRec;
  314. ChunkIndex: QWord;
  315. Chunk: TMemoryStream;
  316. StartOfChunks: QWord;
  317. EntryCount: Word;
  318. AOLL: TAOLLChunkHeader;
  319. AOLI: TAOLIChunkHeader;
  320. Entry: TPMGListChunkEntry;
  321. CRes: LongInt;
  322. i: integer;
  323. begin
  324. Result := 0;
  325. if Name = fCachedEntry.Name then
  326. Exit(fCachedEntry.DecompressedLength); // we've already looked it up
  327. fCachedEntry.Name:='';
  328. fCachedEntry.ContentSection:=LongWord(-1);
  329. fCachedEntry.DecompressedLength:=0;
  330. fCachedEntry.ContentOffset:=QWord(-1);
  331. StreamPos:=HeaderSectionTable[1].OffSet;
  332. fStream.Read(IFCM, SizeOf(IFCM));
  333. StartOfChunks := fstream.Position;
  334. {$push}
  335. {$R-}
  336. ChunkIndex:=PostHeader.ListChunkInfo.TopAOLIChunkIndex;
  337. if ChunkIndex = -1 then
  338. ChunkIndex := PostHeader.ListChunkInfo.FirstAOLLChunkIndex;
  339. Chunk := TMemoryStream.Create;
  340. while ChunkIndex <> -1 do
  341. begin
  342. Chunk.Position:=0;
  343. fStream.Position:= StartOfChunks + (IFCM.ChunkSize*ChunkIndex);
  344. Chunk.CopyFrom(fStream, IFCM.ChunkSize);
  345. Chunk.Position:=0;
  346. case GetChunkType(Chunk) of
  347. ctAOLL :
  348. begin
  349. Chunk.Read(AOLL, SizeOf(AOLL));
  350. Chunk.Seek(-2, soFromEnd);
  351. EntryCount:= LEtoN(Chunk.ReadWord);
  352. {$IFDEF DEBUG_HELP2}
  353. WriteLn(EntryCount);
  354. {$ENDIF}
  355. Chunk.Seek(SizeOf(AOLL), soFromBeginning);
  356. for i := 0 to EntryCount-1 do
  357. begin
  358. Entry := ReadEntry(Chunk);
  359. CRes := ChmCompareText(Name, Entry.Name);
  360. if CRes = 0 then
  361. begin
  362. ChunkIndex:=-1;
  363. fCachedEntry := Entry;
  364. Break;
  365. end
  366. else if CRes > 0 then
  367. Continue
  368. else
  369. begin
  370. ChunkIndex := -1;
  371. Break;
  372. end;
  373. end;
  374. end;
  375. ctAOLI :
  376. begin
  377. //Chunk.Read(AOLI, SizeOf(AOLI));
  378. end;
  379. end;
  380. end;
  381. {$pop}
  382. Chunk.Free;
  383. Result := fCachedEntry.DecompressedLength;
  384. end;
  385. function TITOLITLSReader.GetObject(Name: String): TMemoryStream;
  386. var
  387. Entry,
  388. ContentEntry: TPMGListChunkEntry;
  389. SectionName: String;
  390. Transforms: TITSFTransformList;
  391. Transform: TITSFTransform;
  392. ContentStream: TStream;
  393. ChunkPos: QWord;
  394. i: Integer;
  395. begin
  396. Result := nil;
  397. {$IFDEF DEBUG_HELP2}
  398. WriteLn('Want: ', Name);
  399. {$ENDIF}
  400. if ObjectExists(Name) = 0 then begin
  401. //WriteLn('Object ', name,' Doesn''t exist or is zero sized.');
  402. Exit;
  403. end;
  404. if Name = '/' then
  405. Exit; // wierd bug where written size and offset contain random data
  406. Entry := fCachedEntry;
  407. if Entry.ContentSection = 0 then begin
  408. Result := TMemoryStream.Create;
  409. {$IFDEF DEBUG_HELP2}
  410. WriteLn('Offset = ', Entry.ContentOffset);
  411. {$ENDIF}
  412. //StreamPos := CAOLHeader.ITSFHeader.Section0Offset + Entry.ContentOffset;
  413. ChunkPos := CAOLHeader.ITSFHeader.Section0Offset;// + fCachedEntry.ContentOffset;
  414. ContentStream := TStreamChunk.Create(fStream, ChunkPos, FileSize-ChunkPos);
  415. ContentStream.Seek(Entry.ContentOffset, soFromBeginning);
  416. Result.CopyFrom(ContentStream, Entry.DecompressedLength);
  417. ContentStream.Free;
  418. end
  419. else
  420. begin
  421. FmtStr(SectionName, '::DataSpace/Storage/%s/',[SectionNames[Entry.ContentSection]]);
  422. {$IFDEF DEBUG_HELP2}
  423. WriteLn('Want: ', SectionName);
  424. {$ENDIF}
  425. if ObjectExists(SectionName+'Content') = 0 then
  426. Exit;
  427. ContentEntry := fCachedEntry;
  428. Transforms := TITSFTransformList(SectionNames.Objects[Entry.ContentSection]);
  429. if Transforms = nil then
  430. Exit;
  431. ChunkPos := CAOLHeader.ITSFHeader.Section0Offset + ContentEntry.ContentOffset;
  432. ContentStream := TStreamChunk.Create(fStream, ChunkPos, ContentEntry.DecompressedLength);
  433. //ContentStream := GetObject(SectionName+'Content');
  434. Result := TMemoryStream.Create;
  435. {$IFDEF DEBUG_HELP2}
  436. {for i := Transforms.Count-1 downto 0 do
  437. begin
  438. //WriteLn('Found Transform: ', GUIDToString(Transforms.TransformIndex[i].GUID));
  439. //WriteLn(Transform.ClassName);
  440. Transform := Transforms.TransformInstance[i];
  441. if Transform = nil then
  442. WriteLn('Trqansform = nil!');
  443. end;}
  444. WriteLn('Transform Count = ', Transforms.Count);
  445. WriteLn('Asking ', Transforms.TransformInstance[Transforms.Count-1].ClassName,' for data');
  446. {$ENDIF}
  447. Transforms.TransformInstance[Transforms.Count-1].WantData(SectionName, ContentStream, Entry.ContentOffset, Entry.DecompressedLength, Result);
  448. ContentStream.Free;
  449. end;
  450. Result.Position := 0;
  451. end;
  452. end.