chmbase.pas 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. { Copyright (C) <2005> <Andrew Haines> chmbase.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.FPC, included in this distribution,
  16. for details about the copyright.
  17. }
  18. unit chmbase;
  19. {$mode objfpc}{$H+}
  20. interface
  21. uses
  22. Classes, SysUtils;
  23. const
  24. CHMPackageVersion = '3.2.0'; // to be put in readme
  25. type
  26. {$PACKRECORDS C}
  27. TITSFHeader= record
  28. ITSFsig: array [0..3] of char;
  29. Version: LongWord;
  30. HeaderLength: LongWord;
  31. Unknown_1: LongWord;
  32. TimeStamp: LongWord; //bigendian
  33. LanguageID: LongWord;
  34. end;
  35. TITSFHeaderEntry = record
  36. PosFromZero: QWord;
  37. Length: QWord;
  38. end;
  39. //Version 3 has this qword. 2 does not
  40. TITSFHeaderSuffix = record
  41. Offset: QWord; // offset within file of content section 0
  42. end;
  43. TITSPHeaderPrefix = record
  44. Unknown1: LongWord;// = $01FE
  45. Unknown2: LongWord;// = 0
  46. FileSize: QWord;
  47. Unknown3: LongWord;// =0
  48. Unknown4: LongWord;// =0
  49. end;
  50. TITSPHeader = record
  51. ITSPsig: array [0..3] of char; // = 'ITSP'
  52. Version: LongWord; // =1
  53. DirHeaderLength: Longword; // Length of the directory header
  54. Unknown1: LongWord; // =$0a
  55. ChunkSize: LongWord; // $1000
  56. Density: LongWord; // usually = 2
  57. IndexTreeDepth: LongWord;// 1 if there is no index 2 if there is one level of PMGI chunks
  58. IndexOfRootChunk: LongInt;// -1 if no root chunk
  59. FirstPMGLChunkIndex,
  60. LastPMGLChunkIndex: LongWord;
  61. Unknown2: LongInt; // = -1
  62. DirectoryChunkCount: LongWord;
  63. LanguageID: LongWord;
  64. GUID: TGuid;
  65. LengthAgain: LongWord; //??? $54
  66. Unknown3: LongInt; // = -1
  67. Unknown4: LongInt; // = -1
  68. Unknown5: LongInt; // = -1
  69. end;
  70. TDirChunkType = (ctPMGL, ctPMGI, ctAOLL, ctAOLI, ctUnknown);
  71. TPMGListChunk = record
  72. PMGLsig: array [0..3] of char;
  73. UnusedSpace: Longword; ///!!! this value can also represent the size of quickref area in the end of the chunk
  74. Unknown1: Longword; //always 0
  75. PreviousChunkIndex: LongInt; // chunk number of the prev listing chunk when reading dir in sequence
  76. // (-1 if this is the first listing chunk)
  77. NextChunkIndex: LongInt; // chunk number of the next listing chunk (-1 if this is the last chunk)
  78. end;
  79. PPMGListChunkEntry = ^TPMGListChunkEntry;
  80. TPMGListChunkEntry = record
  81. //NameLength: LongInt; we don't need this permanantly so I've moved it to a temp var
  82. Name: String;
  83. ContentSection: LongWord;//QWord;
  84. ContentOffset: QWord;
  85. DecompressedLength: QWord;
  86. end;
  87. TPMGIIndexChunk = record
  88. PMGIsig: array [0..3] of char;
  89. UnusedSpace: LongWord; // has a quickref area
  90. end;
  91. TPMGIIndexChunkEntry = record
  92. Name: String;
  93. ListingChunk: DWord;
  94. end;
  95. const
  96. ITSFHeaderGUID : TGuid = '{7C01FD10-7BAA-11D0-9E0C-00A0C922E6EC}';
  97. ITSFFileSig: array [0..3] of char = 'ITSF';
  98. ITSPHeaderGUID : TGuid = '{5D02926A-212E-11D0-9DF9-00A0C922E6EC}';
  99. ITSPHeaderSig: array [0..3] of char = 'ITSP';
  100. // this function will advance the stream to the end of the compressed integer
  101. // and return the value
  102. function GetCompressedInteger(const Stream: TStream): DWord;
  103. // returns the number of bytes written to the stream
  104. function WriteCompressedInteger(const Stream: TStream; ANumber: DWord): DWord;
  105. function WriteCompressedInteger(Buffer: Pointer; ANumber: DWord): DWord;
  106. // stupid needed function
  107. function ChmCompareText(const S1, S2: String): Integer; inline;
  108. implementation
  109. function GetCompressedInteger(const Stream: TStream): DWord;
  110. var
  111. total: QWord = 0;
  112. temp: Byte;
  113. Sanity: Integer = 0;
  114. begin
  115. try
  116. temp := Stream.ReadByte;
  117. while temp >= $80 do
  118. begin
  119. total := total shl 7;
  120. total := total + temp and $7f;
  121. temp := Stream.ReadByte;
  122. Inc(Sanity);
  123. if Sanity > 8 then
  124. begin
  125. Result := 0;
  126. Exit;
  127. end;
  128. end;
  129. Result := (total shl 7) + temp;
  130. except
  131. Result := 0;
  132. end;
  133. end;
  134. // returns how many bytes were written
  135. function WriteCompressedInteger(const Stream: TStream; ANumber: DWord): DWord;
  136. var
  137. Buffer: QWord; // Easily large enough
  138. begin
  139. Result := WriteCompressedInteger(@Buffer, ANumber);
  140. Result := Stream.Write(Buffer, Result);
  141. end;
  142. // returns how many bytes were written
  143. function WriteCompressedInteger(Buffer: Pointer; ANumber: DWord): DWord;
  144. var
  145. bit: dword;
  146. mask: QWord;
  147. buf: PByte;
  148. Value: QWord = 0;
  149. TheEnd: DWord = 0;
  150. begin
  151. bit := 28; //((sizeof(dWord)*8)div 7)*7; // = 28
  152. buf := @Value;
  153. {$push}
  154. {$R-}
  155. while True do begin
  156. mask := $7f shl bit;
  157. if (bit = 0) or ((ANumber and mask)<>0) then break;
  158. Dec(bit, 7);
  159. end;
  160. while True do begin
  161. buf^ := Byte(((ANumber shr bit)and $7f));
  162. if(bit = 0) then break;
  163. buf^ := buf^ or $80;
  164. Inc(buf);
  165. Dec(bit, 7);
  166. Inc(TheEnd);
  167. end;
  168. {$pop}
  169. buf := @Value;
  170. Result := TheEnd+1;
  171. Move(Value, Buffer^, Result);
  172. {$ifdef chm_debug}
  173. if Result > 8 then WriteLn(' ', ANumber,' WRITE_COMPRESSED_INTEGER too big!: ', Result, ' ');
  174. {$endif}
  175. end;
  176. function ChmCompareText(const S1, S2: String): Integer;
  177. begin
  178. // for our purposes the CompareText function will not work.
  179. Result := CompareStr(LowerCase(S1), Lowercase(S2));
  180. end;
  181. end.