chmtypes.pas 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. { Copyright (C) <2005> <Andrew Haines> chmtypes.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.FPC, included in this distribution,
  16. for details about the copyright.
  17. }
  18. unit chmtypes;
  19. {$mode objfpc}{$H+}
  20. interface
  21. uses
  22. Classes, SysUtils;
  23. type
  24. TSectionName = (snMSCompressed, snUnCompressed);
  25. TSectionNames = set of TSectionName;
  26. { TDirectoryChunk }
  27. TDirectoryChunk = class(TObject)
  28. private
  29. FHeaderSize: Integer;
  30. FQuickRefEntries: Word;
  31. Buffer: array[0..$1000-1] of byte;
  32. CurrentPos: Integer;
  33. FItemCount: Word;
  34. FClearCount: Integer;
  35. public
  36. function CanHold(ASize: Integer): Boolean;
  37. function FreeSpace: Integer;
  38. procedure WriteHeader(AHeader: Pointer);
  39. procedure WriteEntry(Size: Integer; Data: Pointer);
  40. procedure WriteChunkToStream(Stream: TStream); overload;
  41. procedure Clear;
  42. property ItemCount: Word read FItemCount;
  43. constructor Create(AHeaderSize: Integer);
  44. end;
  45. { TPMGIDirectoryChunk }
  46. TPMGIDirectoryChunk = class(TDirectoryChunk)
  47. private
  48. FChunkLevelCount: Integer;
  49. FParentChunk: TPMGIDirectoryChunk;
  50. public
  51. procedure WriteChunkToStream(Stream: TStream; var AIndex: Integer; Final: Boolean = False); overload;
  52. property ParentChunk: TPMGIDirectoryChunk read FParentChunk write FParentChunk;
  53. property ChunkLevelCount: Integer read FChunkLevelCount write FChunkLevelCount;
  54. end;
  55. PFileEntryRec = ^TFileEntryRec;
  56. TFileEntryRec = record
  57. Path: String;
  58. Name: String;
  59. DecompressedOffset: QWord;
  60. DecompressedSize: QWord;
  61. Compressed: Boolean; // True means it goes in section1 False means section0
  62. end;
  63. { TFileEntryList }
  64. TFileEntryList = class(TList)
  65. private
  66. FPaths: TStringList;
  67. function GetFileEntry(Index: Integer): TFileEntryRec;
  68. procedure SetFileEntry(Index: Integer; const AValue: TFileEntryRec);
  69. public
  70. function AddEntry(AFileEntry: TFileEntryRec; CheckPathIsAdded: Boolean = True): Integer;
  71. procedure Delete(Index: Integer);
  72. property FileEntry[Index: Integer]: TFileEntryRec read GetFileEntry write SetFileEntry;
  73. procedure Sort;
  74. constructor Create;
  75. destructor Destroy; override;
  76. end;
  77. implementation
  78. uses chmbase;
  79. { TDirectoryChunk }
  80. function TDirectoryChunk.CanHold(ASize: Integer): Boolean;
  81. begin
  82. Result := CurrentPos < $1000-1 - ASize - (SizeOf(Word) * (FQuickRefEntries+2));
  83. end;
  84. function TDirectoryChunk.FreeSpace: Integer;
  85. begin
  86. Result := $1000 - CurrentPos;
  87. end;
  88. procedure TDirectoryChunk.WriteHeader(AHeader: Pointer);
  89. begin
  90. Move(AHeader^, Buffer[0], FHeaderSize);
  91. end;
  92. procedure TDirectoryChunk.WriteEntry(Size: Integer; Data: Pointer);
  93. var
  94. ReversePos: Integer;
  95. Value: Word;
  96. begin
  97. if not CanHold(Size) then Raise Exception.Create('Trying to write past the end of the buffer');
  98. Move(Data^, Buffer[CurrentPos], Size);
  99. Inc(CurrentPos, Size);
  100. Inc(FItemCount);
  101. // now put a quickref entry if needed
  102. if ItemCount mod 5 = 0 then begin
  103. Inc(FQuickRefEntries);
  104. ReversePos := ($1000-1) - SizeOf(Word) - (SizeOf(Word)*FQuickRefEntries);
  105. Value := NtoLE(Word(CurrentPos - Size));
  106. Move(Value, Buffer[ReversePos], SizeOf(Word));
  107. end;
  108. end;
  109. procedure TDirectoryChunk.WriteChunkToStream(Stream: TStream);
  110. var
  111. ReversePos: Integer;
  112. begin
  113. ReversePos := $1000-1 - SizeOf(Word);
  114. FItemCount := NtoLE(ItemCount);
  115. Move(ItemCount, Buffer[ReversePos], SizeOf(Word));
  116. Stream.Write(Buffer[0], $1000);
  117. {$IFDEF DEBUG_CHM_CHUNKS}
  118. WriteLn('Writing ', Copy(PChar(@Buffer[0]),0,4),' ChunkToStream');
  119. {$ENDIF}
  120. end;
  121. procedure TDirectoryChunk.Clear;
  122. begin
  123. FillChar(Buffer, $1000, 0);
  124. FItemCount := 0;
  125. CurrentPos := FHeaderSize;
  126. FQuickRefEntries := 0;
  127. Inc(FClearCount);
  128. end;
  129. constructor TDirectoryChunk.Create(AHeaderSize: Integer);
  130. begin
  131. FHeaderSize := AHeaderSize;
  132. CurrentPos := FHeaderSize;
  133. end;
  134. { TFileEntryList }
  135. function TFileEntryList.GetFileEntry(Index: Integer): TFileEntryRec;
  136. begin
  137. Result := PFileEntryRec(Items[Index])^;
  138. end;
  139. procedure TFileEntryList.SetFileEntry(Index: Integer; const AValue: TFileEntryRec);
  140. begin
  141. PFileEntryRec(Items[Index])^ := AValue;
  142. end;
  143. function TFileEntryList.AddEntry(AFileEntry: TFileEntryRec; CheckPathIsAdded: Boolean = True): Integer;
  144. var
  145. TmpEntry: PFileEntryRec;
  146. begin
  147. New(TmpEntry);
  148. //WriteLn('Adding: ', AFileEntry.Path+AFileEntry.Name,' Size = ', AFileEntry.DecompressedSize,' Offset = ', AFileEntry.DecompressedOffset);
  149. if CheckPathIsAdded and (FPaths.IndexOf(AFileEntry.Path) < 0) then begin
  150. // all paths are included in the list of files in section 0 with a size and offset of 0
  151. FPaths.Add(AFileEntry.Path);
  152. TmpEntry^.Path := AFileEntry.Path;
  153. TmpEntry^.Name := '';
  154. TmpEntry^.DecompressedOffset := 0;
  155. TmpEntry^.DecompressedSize := 0;
  156. TmpEntry^.Compressed := False;
  157. (Self as TList).Add(TmpEntry);
  158. New(TmpEntry);
  159. end;
  160. TmpEntry^ := AFileEntry;
  161. Result := (Self as TList).Add(TmpEntry);
  162. end;
  163. procedure TFileEntryList.Delete(Index: Integer);
  164. begin
  165. Dispose(PFileEntryRec(Items[Index]));
  166. Inherited Delete(Index);
  167. end;
  168. function FileEntrySortFunc(Item1, Item2: PFileEntryRec): Integer;
  169. var
  170. Str1, Str2: String;
  171. begin
  172. Str1 := Item1^.Path + Item1^.Name;
  173. Str2 := Item2^.Path + Item2^.Name;
  174. Result := ChmCompareText(Str1, Str2);
  175. end;
  176. procedure TFileEntryList.Sort;
  177. begin
  178. Inherited Sort(TListSortCompare(@FileEntrySortFunc));
  179. end;
  180. constructor TFileEntryList.Create;
  181. begin
  182. Inherited Create;
  183. FPaths := TStringList.Create;
  184. end;
  185. destructor TFileEntryList.Destroy;
  186. var
  187. I: Integer;
  188. begin
  189. for I := Count-1 downto 0 do
  190. Delete(I);
  191. FPaths.Free;
  192. inherited Destroy;
  193. end;
  194. { TPMGIDirectoryChunk }
  195. procedure TPMGIDirectoryChunk.WriteChunkToStream(Stream: TStream; var AIndex: Integer
  196. ; Final: Boolean = False);
  197. var
  198. NewBuffer: array[0..512] of byte;
  199. EntryLength,
  200. WriteSize: Integer;
  201. OldPos, NewPos, NewStart: Int64;
  202. procedure FinishBlock;
  203. var
  204. Header: TPMGIIndexChunk;
  205. begin
  206. Inc(AIndex);
  207. Header.PMGIsig := 'PMGI';
  208. Header.UnusedSpace := FParentChunk.FreeSpace;
  209. FParentChunk.WriteHeader(@Header);
  210. FParentChunk.WriteChunkToStream(Stream, AIndex, Final);
  211. FParentChunk.Clear;
  212. end;
  213. begin
  214. if FItemCount < 1 then begin
  215. WriteLn('WHAT ARE YOU DOING!!');
  216. Dec(AIndex);
  217. Exit;
  218. end;
  219. OldPos := Stream.Position;
  220. WriteChunkToStream(Stream);
  221. NewPos := Stream.Position;
  222. Inc(FChunkLevelCount);
  223. if Final and (ChunkLevelCount < 2) then begin
  224. FParentChunk.Free;
  225. FParentChunk := nil;
  226. Exit;
  227. end;
  228. if FParentChunk = nil then FParentChunk := TPMGIDirectoryChunk.Create(FHeaderSize);
  229. NewStart := OldPos+FHeaderSize;
  230. Stream.Position := NewStart;
  231. EntryLength := GetCompressedInteger(Stream);
  232. WriteSize := (Stream.Position - NewStart) + EntryLength;
  233. Move(Buffer[FHeaderSize], NewBuffer[0], WriteSize);
  234. Inc(WriteSize, WriteCompressedInteger(@NewBuffer[WriteSize], AIndex));
  235. Stream.Position := NewPos;
  236. if not FParentChunk.CanHold(WriteSize) then begin
  237. FinishBlock;
  238. end;
  239. FParentChunk.WriteEntry(WriteSize, @NewBuffer[0]);
  240. if Final then FinishBlock;
  241. //WriteLn(ChunkLevelCount);
  242. end;
  243. end.