chmwriter.pas 89 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578
  1. { Copyright (C) <2005> <Andrew Haines> chmwriter.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, Inc.,
  12. 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
  13. }
  14. {
  15. See the file COPYING.FPC, included in this distribution,
  16. for details about the copyright.
  17. }
  18. {$IFNDEF FPC_DOTTEDUNITS}
  19. unit chmwriter;
  20. {$ENDIF FPC_DOTTEDUNITS}
  21. {$MODE OBJFPC}{$H+}
  22. interface
  23. {$IFDEF FPC_DOTTEDUNITS}
  24. uses System.Generics.Collections,System.Classes, Chm.Base, Chm.Types, Chm.SpecialFiles, Chm.HtmlIndexer, Chm.Sitemap, System.Contnrs, Fcl.Streams.Extra, Fcl.AVLTree, Chm.Lzx.Compressthread;
  25. {$ELSE FPC_DOTTEDUNITS}
  26. uses Generics.Collections,Classes, ChmBase, chmtypes, chmspecialfiles, HtmlIndexer, chmsitemap, contnrs, StreamEx, Avl_Tree, lzxcompressthread;
  27. {$ENDIF FPC_DOTTEDUNITS}
  28. Const
  29. DefaultHHC = 'Default.hhc';
  30. DefaultHHK = 'Default.hhk';
  31. Type
  32. TGetDataFunc = function (const DataName: String; out PathInChm: String; out FileName: String; var Stream: TStream): Boolean of object;
  33. // DataName : A FileName or whatever so that the getter can find and open the file to add
  34. // PathInChm: This is the absolute path in the archive. i.e. /home/user/helpstuff/
  35. // becomes '/' and /home/user/helpstuff/subfolder/ > /subfolder/
  36. // FileName : /home/user/helpstuff/index.html > index.html
  37. // Stream : the file opened with DataName should be written to this stream
  38. Type
  39. TStringIndex = Class // AVLTree needs wrapping in non automated reference type also used in filewriter.
  40. TheString : String;
  41. StrId : Integer;
  42. end;
  43. TUrlStrIndex = Class
  44. UrlStr : String;
  45. UrlStrId : Integer;
  46. end;
  47. { TITSFWriter }
  48. TITSFWriter = class(TObject)
  49. FOnLastFile: TNotifyEvent;
  50. private
  51. ForceExit: Boolean;
  52. FInternalFiles: TFileEntryList; // Contains a complete list of files in the chm including
  53. FFrameSize: LongWord; // uncompressed files and special internal files of the chm
  54. FCurrentStream: TStream; // used to buffer the files that are to be compressed
  55. FCurrentIndex: Integer;
  56. FOnGetFileData: TGetDataFunc;
  57. FSection0: TMemoryStream;
  58. FSection1: TStream; // Compressed Stream
  59. FSection1Size: QWord;
  60. FSection1ResetTable: TMemoryStream; // has a list of frame positions NOT window positions
  61. FDirectoryListings: TStream;
  62. FOutStream: TStream;
  63. FFileNames: TStrings;
  64. FDestroyStream: Boolean;
  65. FTempStream: TStream;
  66. FPostStream: TStream;
  67. FWindowSize: LongWord;
  68. FReadCompressedSize: QWord; // Current Size of Uncompressed data that went in Section1 (compressed)
  69. FPostStreamActive: Boolean;
  70. // Linear order of file
  71. ITSFHeader: TITSFHeader;
  72. HeaderSection0Table: TITSFHeaderEntry; // points to HeaderSection0
  73. HeaderSection1Table: TITSFHeaderEntry; // points to HeaderSection1
  74. HeaderSuffix: TITSFHeaderSuffix; //contains the offset of CONTENTSection0 from zero
  75. HeaderSection0: TITSPHeaderPrefix;
  76. HeaderSection1: TITSPHeader; // DirectoryListings header
  77. FReadmeMessage : String;
  78. FCores : integer;
  79. // DirectoryListings
  80. // CONTENT Section 0 (section 1 is contained in section 0)
  81. // EOF
  82. // end linear header parts
  83. procedure InitITSFHeader;
  84. procedure InitHeaderSectionTable;
  85. procedure SetTempRawStream(const AValue: TStream);
  86. procedure WriteHeader(Stream: TStream);
  87. procedure CreateDirectoryListings;
  88. procedure WriteDirectoryListings(Stream: TStream);
  89. procedure WriteInternalFilesBefore; virtual;
  90. procedure WriteInternalFilesAfter; virtual;
  91. procedure StartCompressingStream;
  92. procedure WriteREADMEFile;
  93. procedure WriteFinalCompressedFiles; virtual;
  94. procedure WriteSection0;
  95. procedure WriteSection1;
  96. procedure WriteDataSpaceFiles(const AStream: TStream);
  97. procedure FileAdded(AStream: TStream; const AEntry: TFileEntryRec); virtual;
  98. // callbacks for lzxcomp
  99. function AtEndOfData: Longbool;
  100. function GetData(Count: LongInt; Buffer: PByte): LongInt;
  101. function WriteCompressedData(Count: Longint; Buffer: Pointer): LongInt;
  102. procedure MarkFrame(UnCompressedTotal, CompressedTotal: LongWord);
  103. // end callbacks
  104. // callbacks for lzx compress threads
  105. function LTGetData(Sender: TLZXCompressor; WantedByteCount: Integer; Buffer: Pointer): Integer;
  106. function LTIsEndOfFile(Sender: TLZXCompressor): Boolean;
  107. procedure LTChunkDone(Sender: TLZXCompressor; CompressedSize: Integer; UncompressedSize: Integer; Buffer: Pointer);
  108. procedure LTMarkFrame(Sender: TLZXCompressor; CompressedTotal: Integer; UncompressedTotal: Integer);
  109. // end callbacks
  110. public
  111. constructor Create(AOutStream: TStream; FreeStreamOnDestroy: Boolean); virtual;
  112. destructor Destroy; override;
  113. procedure Execute;
  114. procedure AddStreamToArchive(AFileName, APath: String; AStream: TStream; Compress: Boolean = True);
  115. procedure PostAddStreamToArchive(AFileName, APath: String; AStream: TStream; Compress: Boolean = True);
  116. procedure LocaleToLanguageID(Locale: LongWord);
  117. function LocaleFromLanguageID: LongWord;
  118. property WindowSize: LongWord read FWindowSize write FWindowSize default 2; // in $8000 blocks
  119. property FrameSize: LongWord read FFrameSize write FFrameSize default 1; // in $8000 blocks
  120. property FilesToCompress: TStrings read FFileNames;
  121. property OnGetFileData: TGetDataFunc read FOnGetFileData write FOnGetFileData;
  122. property OnLastFile: TNotifyEvent read FOnLastFile write FOnLastFile;
  123. property OutStream: TStream read FOutStream;
  124. property TempRawStream: TStream read FTempStream write SetTempRawStream;
  125. property ReadmeMessage : String read fReadmeMessage write fReadmeMessage;
  126. property Cores : integer read fcores write fcores;
  127. { MS Locale ID code }
  128. property LocaleID: LongWord read LocaleFromLanguageID write LocaleToLanguageID;
  129. end;
  130. { TChmWriter }
  131. TChmWriter = class(TITSFWriter)
  132. private
  133. FHasBinaryTOC: Boolean;
  134. FHasBinaryIndex: Boolean;
  135. FDefaultFont: String;
  136. FDefaultPage: String;
  137. FFullTextSearch: Boolean;
  138. FFullTextSearchAvailable: Boolean;
  139. FSearchTitlesOnly: Boolean;
  140. FStringsStream: TMemoryStream; // the #STRINGS file
  141. FTopicsStream: TMemoryStream; // the #TOPICS file
  142. FURLTBLStream: TMemoryStream; // the #URLTBL file. has offsets of strings in URLSTR
  143. FURLSTRStream: TMemoryStream; // the #URLSTR file
  144. FFiftiMainStream: TMemoryStream;
  145. FContextStream: TMemoryStream; // the #IVB file
  146. FIDXHdrStream : TMemoryStream; // the #IDXHDR and chunk 13 in #SYSTEM
  147. FTitle: String;
  148. FHasTOC: Boolean;
  149. FHasIndex: Boolean;
  150. FIndexedFiles: TIndexedWordList;
  151. FAvlStrings : TAVLTree; // dedupe strings
  152. FAVLTopicdedupe : TAVlTree; // Topic deduping, if we load it both from hhp and TOC
  153. FAvlURLStr : TAVLTree; // dedupe urltbl + binindex must resolve URL to topicid
  154. FDictTopicsUrlInd : specialize TDictionary<string,integer>; // if url exists reuse topic.
  155. SpareString : TStringIndex;
  156. SpareUrlStr : TUrlStrIndex;
  157. FWindows : TObjectList;
  158. FDefaultWindow: String;
  159. FTocName : String;
  160. FIndexName : String;
  161. FMergeFiles : TStringList;
  162. FTocSM : TCHMSitemap;
  163. FHasKLinks : Boolean;
  164. FNrTopics : Integer;
  165. protected
  166. procedure FileAdded(AStream: TStream; const AEntry: TFileEntryRec); override;
  167. private
  168. procedure WriteInternalFilesBefore; override;
  169. procedure WriteInternalFilesAfter; override;
  170. procedure WriteFinalCompressedFiles; override;
  171. procedure WriteSYSTEM;
  172. procedure WriteITBITS;
  173. procedure WriteSTRINGS;
  174. procedure WriteTOPICS;
  175. procedure WriteIVB; // context ids
  176. procedure CreateIDXHDRStream;
  177. procedure WriteIDXHDR;
  178. procedure WriteURL_STR_TBL;
  179. procedure WriteOBJINST;
  180. procedure WriteFiftiMain;
  181. procedure WriteWindows;
  182. function AddString(AString: String): LongWord;
  183. function AddURL(AURL: String; TopicsIndex: DWord): LongWord;
  184. procedure CheckFileMakeSearchable(AStream: TStream; AFileEntry: TFileEntryRec);
  185. function AddTopic(ATitle,AnUrl:AnsiString;code:integer=-1):integer;
  186. function AddTopicindex(ATitle,AnUrl:AnsiString;code:integer=-1):integer;
  187. procedure ScanSitemap(asitemap:TCHMSiteMap);
  188. function NextTopicIndex: Integer;
  189. procedure Setwindows (AWindowList:TObjectList);
  190. procedure SetMergefiles(src:TStringList);
  191. public
  192. constructor Create(AOutStream: TStream; FreeStreamOnDestroy: Boolean); override;
  193. destructor Destroy; override;
  194. procedure AppendTOC(AStream: TStream);
  195. procedure AppendBinaryTOCFromSiteMap(ASiteMap: TChmSiteMap);
  196. procedure AppendBinaryIndexFromSiteMap(ASiteMap: TChmSiteMap;chw:boolean);
  197. procedure AppendBinaryTOCStream(AStream: TStream);
  198. procedure AppendBinaryIndexStream(IndexStream,DataStream,MapStream,Propertystream: TStream;chw:boolean);
  199. procedure AppendIndex(AStream: TStream);
  200. procedure AppendSearchDB(AName: String; AStream: TStream);
  201. procedure AddContext(AContext: DWord; ATopic: String);
  202. procedure AddDummyALink;
  203. property Title: String read FTitle write FTitle;
  204. property FullTextSearch: Boolean read FFullTextSearch write FFullTextSearch;
  205. property SearchTitlesOnly: Boolean read FSearchTitlesOnly write FSearchTitlesOnly;
  206. property HasBinaryTOC: Boolean read FHasBinaryTOC write FHasBinaryTOC;
  207. property HasBinaryIndex: Boolean read FHasBinaryIndex write FHasBinaryIndex;
  208. property DefaultFont: String read FDefaultFont write FDefaultFont;
  209. property DefaultPage: String read FDefaultPage write FDefaultPage;
  210. property Windows : TObjectlist read fwindows write setwindows;
  211. property TOCName : String read FTocName write FTocName;
  212. property IndexName : String read FIndexName write FIndexName;
  213. property DefaultWindow : string read fdefaultwindow write fdefaultwindow;
  214. property MergeFiles :TStringList read FMergeFiles write setmergefiles;
  215. property Tocsitemap :TChmSitemap read ftocsm write ftocsm;
  216. end;
  217. Function CompareStrings(Node1, Node2: Pointer): integer; // also used in filewriter
  218. implementation
  219. {$IFDEF FPC_DOTTEDUNITS}
  220. uses System.DateUtils, System.SysUtils, Chm.Lzx.Compress, Chm.FiftiMain;
  221. {$ELSE FPC_DOTTEDUNITS}
  222. uses dateutils, sysutils, paslzxcomp, chmFiftiMain;
  223. {$ENDIF FPC_DOTTEDUNITS}
  224. const
  225. LZX_WINDOW_SIZE = 16; // 16 = 2 frames = 1 shl 16
  226. LZX_FRAME_SIZE = $8000;
  227. {$ifdef binindex}
  228. procedure logentry(s:string);
  229. begin
  230. Writeln(s);
  231. flush(stdout);
  232. end;
  233. {$endif}
  234. {$I chmobjinstconst.inc}
  235. Function CompareStrings(Node1, Node2: Pointer): integer;
  236. var n1,n2 : TStringIndex;
  237. begin
  238. n1:=TStringIndex(Node1); n2:=TStringIndex(Node2);
  239. Result := CompareText(n1.TheString, n2.TheString);
  240. if Result < 0 then Result := -1
  241. else if Result > 0 then Result := 1;
  242. end;
  243. Function CompareUrlStrs(Node1, Node2: Pointer): integer;
  244. var n1,n2 : TUrlStrIndex;
  245. begin
  246. n1:=TUrlStrIndex(Node1); n2:=TUrlStrIndex(Node2);
  247. Result := CompareText(n1.UrlStr, n2.UrlStr);
  248. if Result < 0 then Result := -1
  249. else if Result > 0 then Result := 1;
  250. end;
  251. { TChmWriter }
  252. procedure TITSFWriter.InitITSFHeader;
  253. begin
  254. with ITSFHeader do begin
  255. ITSFsig := ITSFFileSig;
  256. Version := NToLE(DWord(3));
  257. // we fix endian order when this is written to the stream
  258. HeaderLength := NToLE(DWord(SizeOf(TITSFHeader) + (SizeOf(TGuid)*2)+ (SizeOf(TITSFHeaderEntry)*2) + SizeOf(TITSFHeaderSuffix)));
  259. Unknown_1 := NToLE(DWord(1));
  260. TimeStamp:= NToBE(MilliSecondOfTheDay(Now)); //bigendian
  261. LanguageID := NToLE(DWord($0409)); // English / English_US
  262. end;
  263. end;
  264. procedure TITSFWriter.LocaleToLanguageID(Locale: LongWord);
  265. begin
  266. ITSFHeader.LanguageID := NToLE(Locale);
  267. end;
  268. function TITSFWriter.LocaleFromLanguageID: LongWord;
  269. begin
  270. Result := LEToN(ITSFHeader.LanguageID);
  271. end;
  272. procedure TITSFWriter.InitHeaderSectionTable;
  273. begin
  274. // header section 0
  275. HeaderSection0Table.PosFromZero := LEToN(ITSFHeader.HeaderLength);
  276. HeaderSection0Table.Length := SizeOf(TITSPHeaderPrefix);
  277. // header section 1
  278. HeaderSection1Table.PosFromZero := HeaderSection0Table.PosFromZero + HeaderSection0Table.Length;
  279. HeaderSection1Table.Length := SizeOf(TITSPHeader)+FDirectoryListings.Size;
  280. //contains the offset of CONTENT Section0 from zero
  281. HeaderSuffix.Offset := HeaderSection1Table.PosFromZero + HeaderSection1Table.Length;
  282. // now fix endian stuff
  283. HeaderSection0Table.PosFromZero := NToLE(HeaderSection0Table.PosFromZero);
  284. HeaderSection0Table.Length := NToLE(HeaderSection0Table.Length);
  285. HeaderSection1Table.PosFromZero := NToLE(HeaderSection1Table.PosFromZero);
  286. HeaderSection1Table.Length := NToLE(HeaderSection1Table.Length);
  287. with HeaderSection0 do begin // TITSPHeaderPrefix;
  288. Unknown1 := NToLE(DWord($01FE));
  289. Unknown2 := 0;
  290. // at this point we are putting together the headers. content sections 0 and 1 are complete
  291. FileSize := NToLE(HeaderSuffix.Offset + FSection0.Size + FSection1Size);
  292. Unknown3 := 0;
  293. Unknown4 := 0;
  294. end;
  295. with HeaderSection1 do begin // TITSPHeader; // DirectoryListings header
  296. ITSPsig := ITSPHeaderSig;
  297. Version := NToLE(DWord(1));
  298. DirHeaderLength := NToLE(DWord(SizeOf(TITSPHeader))); // Length of the directory header
  299. Unknown1 := NToLE(DWord($0A));
  300. ChunkSize := NToLE(DWord($1000));
  301. Density := NToLE(DWord(2));
  302. // updated when directory listings were created
  303. //IndexTreeDepth := 1 ; // 1 if there is no index 2 if there is one level of PMGI chunks. will update as
  304. //IndexOfRootChunk := -1;// if no root chunk
  305. //FirstPMGLChunkIndex,
  306. //LastPMGLChunkIndex: LongWord;
  307. Unknown2 := NToLE(Longint(-1));
  308. //DirectoryChunkCount: LongWord;
  309. LanguageID := ITSFHeader.LanguageID;
  310. GUID := ITSPHeaderGUID;
  311. LengthAgain := NToLE(DWord($54));
  312. Unknown3 := NToLE(Longint(-1));
  313. Unknown4 := NToLE(Longint(-1));
  314. Unknown5 := NToLE(Longint(-1));
  315. end;
  316. // more endian stuff
  317. HeaderSuffix.Offset := NToLE(HeaderSuffix.Offset);
  318. end;
  319. procedure TITSFWriter.SetTempRawStream(const AValue: TStream);
  320. begin
  321. if (FCurrentStream.Size > 0) or (FSection1.Size > 0) then
  322. raise Exception.Create('Cannot set the TempRawStream once data has been written to it!');
  323. if AValue = nil then
  324. raise Exception.Create('TempRawStream cannot be nil!');
  325. if FCurrentStream = AValue then
  326. exit;
  327. FCurrentStream.Free;
  328. FCurrentStream := AValue;
  329. end;
  330. procedure TITSFWriter.WriteHeader(Stream: TStream);
  331. begin
  332. Stream.Write(ITSFHeader, SizeOf(TITSFHeader));
  333. if ITSFHeader.Version < 4 then
  334. begin
  335. Stream.Write(ITSFHeaderGUID, SizeOf(TGuid));
  336. Stream.Write(ITSFHeaderGUID, SizeOf(TGuid));
  337. end;
  338. Stream.Write(HeaderSection0Table, SizeOf(TITSFHeaderEntry));
  339. Stream.Write(HeaderSection1Table, SizeOf(TITSFHeaderEntry));
  340. Stream.Write(HeaderSuffix, SizeOf(TITSFHeaderSuffix));
  341. Stream.Write(HeaderSection0, SizeOf(TITSPHeaderPrefix));
  342. end;
  343. procedure TITSFWriter.CreateDirectoryListings;
  344. type
  345. TFirstListEntry = record
  346. Entry: array[0..511] of byte;
  347. Size: Integer;
  348. end;
  349. var
  350. Buffer: array [0..511] of Byte;
  351. IndexBlock: TPMGIDirectoryChunk;
  352. ListingBlock: TDirectoryChunk;
  353. I: Integer;
  354. Size: Integer;
  355. FESize: Integer;
  356. FileName: String;
  357. FileNameSize: Integer;
  358. LastListIndex: Integer;
  359. FirstListEntry: TFirstListEntry;
  360. ChunkIndex: Integer;
  361. ListHeader: TPMGListChunk;
  362. const
  363. PMGL = 'PMGL';
  364. PMGI = 'PMGI';
  365. procedure UpdateLastListChunk;
  366. var
  367. Tmp: QWord;
  368. begin
  369. if ChunkIndex < 1 then begin
  370. Exit;
  371. end;
  372. Tmp := FDirectoryListings.Position;
  373. FDirectoryListings.Position := (LastListIndex) * $1000;
  374. FDirectoryListings.Read(ListHeader, SizeOf(TPMGListChunk));
  375. FDirectoryListings.Position := (LastListIndex) * $1000;
  376. ListHeader.NextChunkIndex := NToLE(ChunkIndex);
  377. FDirectoryListings.Write(ListHeader, SizeOf(TPMGListChunk));
  378. FDirectoryListings.Position := Tmp;
  379. end;
  380. procedure WriteIndexChunk(ShouldFinish: Boolean = False);
  381. var
  382. IndexHeader: TPMGIIndexChunk;
  383. ParentIndex,
  384. TmpIndex: TPMGIDirectoryChunk;
  385. begin
  386. with IndexHeader do
  387. begin
  388. PMGIsig := PMGI;
  389. UnusedSpace := NToLE(IndexBlock.FreeSpace);
  390. end;
  391. IndexBlock.WriteHeader(@IndexHeader);
  392. IndexBlock.WriteChunkToStream(FDirectoryListings, ChunkIndex, ShouldFinish);
  393. IndexBlock.Clear;
  394. if HeaderSection1.IndexOfRootChunk < 0 then HeaderSection1.IndexOfRootChunk := ChunkIndex;
  395. if ShouldFinish then
  396. begin
  397. HeaderSection1.IndexTreeDepth := 2;
  398. ParentIndex := IndexBlock.ParentChunk;
  399. if ParentIndex <> nil then
  400. repeat // the parent index is notified by our child index when to write
  401. HeaderSection1.IndexOfRootChunk := ChunkIndex;
  402. TmpIndex := ParentIndex;
  403. ParentIndex := ParentIndex.ParentChunk;
  404. TmpIndex.Free;
  405. Inc(HeaderSection1.IndexTreeDepth);
  406. Inc(ChunkIndex);
  407. until ParentIndex = nil;
  408. end;
  409. Inc(ChunkIndex);
  410. end;
  411. procedure WriteListChunk;
  412. begin
  413. with ListHeader do begin
  414. PMGLsig := PMGL;
  415. UnusedSpace := NToLE(ListingBlock.FreeSpace);
  416. Unknown1 := 0;
  417. PreviousChunkIndex := NToLE(LastListIndex);
  418. NextChunkIndex := NToLE(Longint(-1)); // we update this when we write the next chunk
  419. end;
  420. if HeaderSection1.FirstPMGLChunkIndex <= 0 then
  421. HeaderSection1.FirstPMGLChunkIndex := NToLE(ChunkIndex);
  422. HeaderSection1.LastPMGLChunkIndex := NToLE(ChunkIndex);
  423. ListingBlock.WriteHeader(@ListHeader);
  424. ListingBlock.WriteChunkToStream(FDirectoryListings);
  425. ListingBlock.Clear;
  426. UpdateLastListChunk;
  427. LastListIndex := ChunkIndex;
  428. Inc(ChunkIndex);
  429. // now add to index
  430. if not IndexBlock.CanHold(FirstListEntry.Size) then
  431. WriteIndexChunk;
  432. IndexBlock.WriteEntry(FirstListEntry.Size, @FirstListEntry.Entry[0])
  433. end;
  434. begin
  435. // first sort the listings
  436. FInternalFiles.Sort;
  437. HeaderSection1.IndexTreeDepth := 1;
  438. HeaderSection1.IndexOfRootChunk := -1;
  439. ChunkIndex := 0;
  440. IndexBlock := TPMGIDirectoryChunk.Create(SizeOf(TPMGIIndexChunk));
  441. ListingBlock := TDirectoryChunk.Create(SizeOf(TPMGListChunk));
  442. LastListIndex := -1;
  443. // add files to a pmgl block until it is full.
  444. // after the block is full make a pmgi block and add the first entry of the pmgl block
  445. // repeat until the index block is full and start another.
  446. // the pmgi chunks take care of needed parent chunks in the tree
  447. for I := 0 to FInternalFiles.Count-1 do begin
  448. Size := 0;
  449. FileName := FInternalFiles.FileEntry[I].Path + FInternalFiles.FileEntry[I].Name;
  450. FileNameSize := Length(FileName);
  451. // filename length
  452. Inc(Size, WriteCompressedInteger(@Buffer[Size], FileNameSize));
  453. // filename
  454. Move(FileName[1], Buffer[Size], FileNameSize);
  455. Inc(Size, FileNameSize);
  456. FESize := Size;
  457. // File is compressed...
  458. Inc(Size, WriteCompressedInteger(@Buffer[Size], Ord(FInternalFiles.FileEntry[I].Compressed)));
  459. // Offset from section start
  460. Inc(Size, WriteCompressedInteger(@Buffer[Size], FInternalFiles.FileEntry[I].DecompressedOffset));
  461. // Size when uncompressed
  462. Inc(Size, WriteCompressedInteger(@Buffer[Size], FInternalFiles.FileEntry[I].DecompressedSize));
  463. if not ListingBlock.CanHold(Size) then
  464. WriteListChunk;
  465. ListingBlock.WriteEntry(Size, @Buffer[0]);
  466. if ListingBlock.ItemCount = 1 then begin // add the first list item to the index
  467. Move(Buffer[0], FirstListEntry.Entry[0], FESize);
  468. FirstListEntry.Size := FESize + WriteCompressedInteger(@FirstListEntry.Entry[FESize], ChunkIndex);
  469. end;
  470. end;
  471. if ListingBlock.ItemCount > 0 then WriteListChunk;
  472. if ChunkIndex > 1 then begin
  473. if (IndexBlock.ItemCount > 1)
  474. or ( (IndexBlock.ItemCount > 0) and (HeaderSection1.IndexOfRootChunk > -1) )
  475. then WriteIndexChunk(True);
  476. end;
  477. HeaderSection1.DirectoryChunkCount := NToLE(DWord(FDirectoryListings.Size div $1000));
  478. IndexBlock.Free;
  479. ListingBlock.Free;
  480. //now fix some endian stuff
  481. HeaderSection1.IndexOfRootChunk := NToLE(HeaderSection1.IndexOfRootChunk);
  482. HeaderSection1.IndexTreeDepth := NtoLE(HeaderSection1.IndexTreeDepth);
  483. end;
  484. procedure TITSFWriter.WriteDirectoryListings(Stream: TStream);
  485. begin
  486. Stream.Write(HeaderSection1, SizeOf(HeaderSection1));
  487. FDirectoryListings.Position := 0;
  488. Stream.CopyFrom(FDirectoryListings, FDirectoryListings.Size);
  489. FDirectoryListings.Position := 0;
  490. //TMemoryStream(FDirectoryListings).SaveToFile('dirlistings.pmg');
  491. end;
  492. procedure TITSFWriter.WriteInternalFilesBefore;
  493. begin
  494. // written to Section0 (uncompressed)
  495. WriteREADMEFile;
  496. end;
  497. procedure TITSFWriter.WriteInternalFilesAfter;
  498. begin
  499. end;
  500. procedure IterateWord(aword:TIndexedWord;State:pointer);
  501. var i,cnt : integer;
  502. begin
  503. cnt:=pinteger(state)^;
  504. for i := 0 to AWord.DocumentCount-1 do
  505. Inc(cnt, AWord.GetLogicalDocument(i).NumberOfIndexEntries);
  506. // was commented in original procedure, seems to list index entries per doc.
  507. //WriteLn(AWord.TheWord,' documents = ', AWord.DocumentCount, ' h
  508. pinteger(state)^:=cnt;
  509. end;
  510. procedure TITSFWriter.WriteREADMEFile;
  511. const DISCLAIMER_STR = 'This archive was not made by the MS HTML Help Workshop(r)(tm) program, but by Free Pascal''s chm package '+chmpackageversion+'.'#13#10;
  512. var
  513. Entry: TFileEntryRec;
  514. begin
  515. // This procedure puts a file in the archive that says it wasn't compiled with the MS compiler
  516. Entry.Compressed := False;
  517. Entry.DecompressedOffset := FSection0.Position;
  518. FSection0.Write(DISCLAIMER_STR, SizeOf(DISCLAIMER_STR));
  519. if length(FReadmeMessage)>0 then
  520. FSection0.Write(FReadmeMessage[1], length(FReadmeMessage));
  521. Entry.DecompressedSize := FSection0.Position - Entry.DecompressedOffset;
  522. Entry.Path := '/';
  523. Entry.Name := '_#_README_#_'; //try to use a name that won't conflict with normal names
  524. FInternalFiles.AddEntry(Entry);
  525. end;
  526. procedure TITSFWriter.WriteFinalCompressedFiles;
  527. begin
  528. end;
  529. procedure TITSFWriter.WriteSection0;
  530. begin
  531. FSection0.Position := 0;
  532. FOutStream.CopyFrom(FSection0, FSection0.Size);
  533. end;
  534. procedure TITSFWriter.WriteSection1;
  535. begin
  536. WriteContentToStream(FOutStream, FSection1);
  537. end;
  538. procedure TITSFWriter.WriteDataSpaceFiles(const AStream: TStream);
  539. var
  540. Entry: TFileEntryRec;
  541. begin
  542. // This procedure will write all files starting with ::
  543. Entry.Compressed := False; // None of these files are compressed
  544. // ::DataSpace/NameList
  545. Entry.DecompressedOffset := FSection0.Position;
  546. Entry.DecompressedSize := WriteNameListToStream(FSection0, [snUnCompressed,snMSCompressed]);
  547. Entry.Path := '::DataSpace/';
  548. Entry.Name := 'NameList';
  549. FInternalFiles.AddEntry(Entry, False);
  550. // ::DataSpace/Storage/MSCompressed/ControlData
  551. Entry.DecompressedOffset := FSection0.Position;
  552. Entry.DecompressedSize := WriteControlDataToStream(FSection0, 2, 2, 1);
  553. Entry.Path := '::DataSpace/Storage/MSCompressed/';
  554. Entry.Name := 'ControlData';
  555. FInternalFiles.AddEntry(Entry, False);
  556. // ::DataSpace/Storage/MSCompressed/SpanInfo
  557. Entry.DecompressedOffset := FSection0.Position;
  558. Entry.DecompressedSize := WriteSpanInfoToStream(FSection0, FReadCompressedSize);
  559. Entry.Path := '::DataSpace/Storage/MSCompressed/';
  560. Entry.Name := 'SpanInfo';
  561. FInternalFiles.AddEntry(Entry, False);
  562. // ::DataSpace/Storage/MSCompressed/Transform/List
  563. Entry.DecompressedOffset := FSection0.Position;
  564. Entry.DecompressedSize := WriteTransformListToStream(FSection0);
  565. Entry.Path := '::DataSpace/Storage/MSCompressed/Transform/';
  566. Entry.Name := 'List';
  567. FInternalFiles.AddEntry(Entry, False);
  568. // ::DataSpace/Storage/MSCompressed/Transform/{7FC28940-9D31-11D0-9B27-00A0C91E9C7C}/
  569. // ::DataSpace/Storage/MSCompressed/Transform/{7FC28940-9D31-11D0-9B27-00A0C91E9C7C}/InstanceData/ResetTable
  570. Entry.DecompressedOffset := FSection0.Position;
  571. Entry.DecompressedSize := WriteResetTableToStream(FSection0, FSection1ResetTable);
  572. Entry.Path := '::DataSpace/Storage/MSCompressed/Transform/{7FC28940-9D31-11D0-9B27-00A0C91E9C7C}/InstanceData/';
  573. Entry.Name := 'ResetTable';
  574. FInternalFiles.AddEntry(Entry, True);
  575. // ::DataSpace/Storage/MSCompressed/Content do this last
  576. Entry.DecompressedOffset := FSection0.Position;
  577. Entry.DecompressedSize := FSection1Size; // we will write it directly to FOutStream later
  578. Entry.Path := '::DataSpace/Storage/MSCompressed/';
  579. Entry.Name := 'Content';
  580. FInternalFiles.AddEntry(Entry, False);
  581. end;
  582. procedure TITSFWriter.FileAdded(AStream: TStream; const AEntry: TFileEntryRec);
  583. begin
  584. // do nothing here
  585. end;
  586. function _AtEndOfData(arg: pointer): LongBool; cdecl;
  587. begin
  588. Result := TITSFWriter(arg).AtEndOfData;
  589. end;
  590. function TITSFWriter.AtEndOfData: LongBool;
  591. begin
  592. Result := ForceExit or (FCurrentIndex >= FFileNames.Count-1);
  593. if Result then
  594. Result := Integer(FCurrentStream.Position) >= Integer(FCurrentStream.Size)-1;
  595. end;
  596. function _GetData(arg: pointer; Count: LongInt; Buffer: Pointer): LongInt; cdecl;
  597. begin
  598. Result := TITSFWriter(arg).GetData(Count, PByte(Buffer));
  599. end;
  600. function TITSFWriter.GetData(Count: LongInt; Buffer: PByte): LongInt;
  601. var
  602. FileEntry: TFileEntryRec;
  603. begin
  604. Result := 0;
  605. while (Result < Count) and (not AtEndOfData) do begin
  606. Inc(Result, FCurrentStream.Read(Buffer[Result], Count-Result));
  607. if (Result < Count) and (not AtEndOfData)
  608. then begin
  609. // the current file has been read. move to the next file in the list
  610. FCurrentStream.Position := 0;
  611. FCurrentStream.Size:=0;
  612. Inc(FCurrentIndex);
  613. ForceExit := OnGetFileData(FFileNames[FCurrentIndex], FileEntry.Path, FileEntry.Name, FCurrentStream);
  614. FileEntry.DecompressedSize := FCurrentStream.Size;
  615. FileEntry.DecompressedOffset := FReadCompressedSize; //269047723;//to test writing really large numbers
  616. FileEntry.Compressed := True;
  617. FileAdded(FCurrentStream, FileEntry);
  618. FInternalFiles.AddEntry(FileEntry);
  619. // So the next file knows it's offset
  620. Inc(FReadCompressedSize, FileEntry.DecompressedSize);
  621. FCurrentStream.Position := 0;
  622. end;
  623. // this is intended for programs to add perhaps a file
  624. // after all the other files have been added.
  625. if (AtEndOfData)
  626. and (FCurrentStream <> FPostStream) then
  627. begin
  628. FPostStreamActive := True;
  629. if Assigned(FOnLastFile) then
  630. FOnLastFile(Self);
  631. FCurrentStream.Free;
  632. WriteFinalCompressedFiles;
  633. FCurrentStream := FPostStream;
  634. FCurrentStream.Position := 0;
  635. Inc(FReadCompressedSize, FCurrentStream.Size);
  636. end;
  637. end;
  638. end;
  639. function _WriteCompressedData(arg: pointer; Count: LongInt; Buffer: Pointer): LongInt; cdecl;
  640. begin
  641. Result := TITSFWriter(arg).WriteCompressedData(Count, Buffer);
  642. end;
  643. function TITSFWriter.WriteCompressedData(Count: Longint; Buffer: Pointer): LongInt;
  644. begin
  645. // we allocate a MB at a time to limit memory reallocation since this
  646. // writes usually 2 bytes at a time
  647. if (FSection1 is TMemoryStream) and (FSection1.Position >= FSection1.Size-1) then begin
  648. FSection1.Size := FSection1.Size+$100000;
  649. end;
  650. Result := FSection1.Write(Buffer^, Count);
  651. Inc(FSection1Size, Result);
  652. end;
  653. procedure _MarkFrame(arg: pointer; UncompressedTotal, CompressedTotal: LongWord); cdecl;
  654. begin
  655. TITSFWriter(arg).MarkFrame(UncompressedTotal, CompressedTotal);
  656. end;
  657. procedure TITSFWriter.MarkFrame(UnCompressedTotal, CompressedTotal: LongWord);
  658. procedure WriteQWord(Value: QWord);
  659. begin
  660. FSection1ResetTable.Write(NToLE(Value), 8);
  661. end;
  662. procedure IncEntryCount;
  663. var
  664. OldPos: QWord;
  665. Value: DWord;
  666. begin
  667. OldPos := FSection1ResetTable.Position;
  668. FSection1ResetTable.Position := $4;
  669. Value := LeToN(FSection1ResetTable.ReadDWord)+1;
  670. FSection1ResetTable.Position := $4;
  671. FSection1ResetTable.WriteDWord(NToLE(Value));
  672. FSection1ResetTable.Position := OldPos;
  673. end;
  674. procedure UpdateTotalSizes;
  675. var
  676. OldPos: QWord;
  677. begin
  678. OldPos := FSection1ResetTable.Position;
  679. FSection1ResetTable.Position := $10;
  680. WriteQWord(FReadCompressedSize); // size of read data that has been compressed
  681. WriteQWord(CompressedTotal);
  682. FSection1ResetTable.Position := OldPos;
  683. end;
  684. begin
  685. if FSection1ResetTable.Size = 0 then begin
  686. // Write the header
  687. FSection1ResetTable.WriteDWord(NtoLE(DWord(2)));
  688. FSection1ResetTable.WriteDWord(0); // number of entries. we will correct this with IncEntryCount
  689. FSection1ResetTable.WriteDWord(NtoLE(DWord(8))); // Size of Entries (qword)
  690. FSection1ResetTable.WriteDWord(NtoLE(DWord($28))); // Size of this header
  691. WriteQWord(0); // Total Uncompressed Size
  692. WriteQWord(0); // Total Compressed Size
  693. WriteQWord(NtoLE($8000)); // Block Size
  694. WriteQWord(0); // First Block start
  695. end;
  696. IncEntryCount;
  697. UpdateTotalSizes;
  698. WriteQWord(CompressedTotal); // Next Block Start
  699. // We have to trim the last entry off when we are done because there is no next block in that case
  700. end;
  701. function TITSFWriter.LTGetData(Sender: TLZXCompressor; WantedByteCount: Integer;
  702. Buffer: Pointer): Integer;
  703. begin
  704. Result := GetData(WantedByteCount, Buffer);
  705. //WriteLn('Wanted ', WantedByteCount, ' got ', Result);
  706. end;
  707. function TITSFWriter.LTIsEndOfFile(Sender: TLZXCompressor): Boolean;
  708. begin
  709. Result := AtEndOfData;
  710. end;
  711. procedure TITSFWriter.LTChunkDone(Sender: TLZXCompressor;
  712. CompressedSize: Integer; UncompressedSize: Integer; Buffer: Pointer);
  713. begin
  714. WriteCompressedData(CompressedSize, Buffer);
  715. end;
  716. procedure TITSFWriter.LTMarkFrame(Sender: TLZXCompressor;
  717. CompressedTotal: Integer; UncompressedTotal: Integer);
  718. begin
  719. MarkFrame(UncompressedTotal, CompressedTotal);
  720. //WriteLn('Mark Frame C = ', CompressedTotal, ' U = ', UncompressedTotal);
  721. end;
  722. constructor TITSFWriter.Create(AOutStream: TStream; FreeStreamOnDestroy: Boolean);
  723. begin
  724. if AOutStream = nil then Raise Exception.Create('TITSFWriter.OutStream Cannot be nil!');
  725. FOutStream := AOutStream;
  726. FCurrentIndex := -1;
  727. FCurrentStream := TMemoryStream.Create;
  728. FInternalFiles := TFileEntryList.Create;
  729. FSection0 := TMemoryStream.Create;
  730. FSection1 := TMemoryStream.Create;
  731. FSection1ResetTable := TMemoryStream.Create;
  732. FDirectoryListings := TMemoryStream.Create;
  733. FPostStream := TMemoryStream.Create;;
  734. FDestroyStream := FreeStreamOnDestroy;
  735. FFileNames := TStringList.Create;
  736. InitITSFHeader;
  737. end;
  738. destructor TITSFWriter.Destroy;
  739. begin
  740. if FDestroyStream then FOutStream.Free;
  741. FInternalFiles.Free;
  742. FCurrentStream.Free;
  743. FSection0.Free;
  744. FSection1.Free;
  745. FSection1ResetTable.Free;
  746. FDirectoryListings.Free;
  747. FFileNames.Free;
  748. inherited Destroy;
  749. end;
  750. procedure TITSFWriter.Execute;
  751. begin
  752. FOutStream.Position := 0;
  753. FSection1Size := 0;
  754. // write any internal files to FCurrentStream that we want in the compressed section
  755. WriteInternalFilesBefore;
  756. // move back to zero so that we can start reading from zero :)
  757. FReadCompressedSize := FCurrentStream.Size;
  758. FCurrentStream.Position := 0; // when compressing happens, first the FCurrentStream is read
  759. // before loading user files. So we can fill FCurrentStream with
  760. // internal files first.
  761. // this gathers ALL files that should be in section1 (the compressed section)
  762. StartCompressingStream;
  763. FSection1.Size := FSection1Size;
  764. WriteInternalFilesAfter;
  765. //this creates all special files in the archive that start with ::DataSpace
  766. WriteDataSpaceFiles(FSection0);
  767. // creates all directory listings including header
  768. CreateDirectoryListings;
  769. // do this after we have compressed everything so that we know the values that must be written
  770. InitHeaderSectionTable;
  771. // Now we can write everything to FOutStream
  772. WriteHeader(FOutStream);
  773. WriteDirectoryListings(FOutStream);
  774. WriteSection0; //does NOT include section 1 even though section0.content IS section1
  775. WriteSection1; // writes section 1 to FOutStream
  776. end;
  777. // this procedure is used to manually add files to compress to an internal stream that is
  778. // processed before FileToCompress is called. Files added this way should not be
  779. // duplicated in the FilesToCompress property.
  780. procedure TITSFWriter.AddStreamToArchive(AFileName, APath: String; AStream: TStream; Compress: Boolean = True);
  781. var
  782. TargetStream: TStream;
  783. Entry: TFileEntryRec;
  784. begin
  785. // in case AddStreamToArchive is used after we should be writing to the post stream
  786. if FPostStreamActive then
  787. begin
  788. PostAddStreamToArchive(AFileName, APath, AStream, Compress);
  789. Exit;
  790. end;
  791. if AStream = nil then Exit;
  792. if Compress then
  793. TargetStream := FCurrentStream
  794. else
  795. TargetStream := FSection0;
  796. Entry.Name := AFileName;
  797. Entry.Path := APath;
  798. Entry.Compressed := Compress;
  799. Entry.DecompressedOffset := TargetStream.Position;
  800. Entry.DecompressedSize := AStream.Size;
  801. FileAdded(AStream,Entry);
  802. FInternalFiles.AddEntry(Entry);
  803. AStream.Position := 0;
  804. TargetStream.CopyFrom(AStream, AStream.Size);
  805. end;
  806. procedure TITSFWriter.PostAddStreamToArchive(AFileName, APath: String;
  807. AStream: TStream; Compress: Boolean);
  808. var
  809. TargetStream: TStream;
  810. Entry: TFileEntryRec;
  811. begin
  812. if AStream = nil then Exit;
  813. if Compress then
  814. TargetStream := FPostStream
  815. else
  816. TargetStream := FSection0;
  817. Entry.Name := AFileName;
  818. Entry.Path := APath;
  819. Entry.Compressed := Compress;
  820. if not Compress then
  821. Entry.DecompressedOffset := TargetStream.Position
  822. else
  823. Entry.DecompressedOffset := FReadCompressedSize + TargetStream.Position;
  824. Entry.DecompressedSize := AStream.Size;
  825. FInternalFiles.AddEntry(Entry);
  826. AStream.Position := 0;
  827. TargetStream.CopyFrom(AStream, AStream.Size);
  828. FileAdded(AStream, Entry);
  829. end;
  830. procedure TITSFWriter.StartCompressingStream;
  831. var
  832. LZXdata: Plzx_data;
  833. WSize: LongInt;
  834. Compressor: TLZXCompressor;
  835. begin
  836. if fcores=0 then
  837. begin
  838. lzx_init(@LZXdata, LZX_WINDOW_SIZE, @_GetData, Self, @_AtEndOfData,
  839. @_WriteCompressedData, Self, @_MarkFrame, Self);
  840. WSize := 1 shl LZX_WINDOW_SIZE;
  841. while not AtEndOfData do begin
  842. lzx_reset(LZXdata);
  843. lzx_compress_block(LZXdata, WSize, True);
  844. end;
  845. //we have to mark the last frame manually
  846. MarkFrame(LZXdata^.len_uncompressed_input, LZXdata^.len_compressed_output);
  847. lzx_finish(LZXdata, nil);
  848. end
  849. else
  850. begin
  851. if fcores=0 then fcores:=4;
  852. Compressor := TLZXCompressor.Create(fcores);
  853. Compressor.OnChunkDone :=@LTChunkDone;
  854. Compressor.OnGetData :=@LTGetData;
  855. Compressor.OnIsEndOfFile:=@LTIsEndOfFile;
  856. Compressor.OnMarkFrame :=@LTMarkFrame;
  857. Compressor.Execute(True);
  858. //Sleep(20000);
  859. Compressor.Free;
  860. end;
  861. end;
  862. procedure TChmWriter.WriteSYSTEM;
  863. var
  864. Entry: TFileEntryRec;
  865. TmpStr: String;
  866. TmpTitle: String;
  867. const
  868. VersionStr = 'HHA Version 4.74.8702'; // does this matter?
  869. begin
  870. // this creates the /#SYSTEM file
  871. Entry.Name := '#SYSTEM';
  872. Entry.Path := '/';
  873. Entry.Compressed := False;
  874. Entry.DecompressedOffset := FSection0.Position;
  875. { if FileExists('#SYSTEM') then
  876. begin
  877. TmpStream := TMemoryStream.Create;
  878. TmpStream.LoadFromFile('#SYSTEM');
  879. TmpStream.Position := 0;
  880. FSection0.CopyFrom(TmpStream, TmpStream.Size);
  881. end; }
  882. // EntryCodeOrder: 10 9 4 2 3 16 6 0 1 5
  883. FSection0.WriteDWord(NToLE(Word(3))); // Version
  884. if Title <> '' then
  885. TmpTitle := Title
  886. else
  887. TmpTitle := 'default';
  888. // Code -> Length -> Data
  889. // 10
  890. FSection0.WriteWord(NToLE(Word(10)));
  891. FSection0.WriteWord(NToLE(Word(SizeOf(DWord))));
  892. FSection0.WriteDWord(NToLE(MilliSecondOfTheDay(Now)));
  893. // 9
  894. FSection0.WriteWord(NToLE(Word(9)));
  895. FSection0.WriteWord(NToLE(Word(SizeOf(VersionStr)+1)));
  896. FSection0.Write(VersionStr, SizeOf(VersionStr));
  897. FSection0.WriteByte(0);
  898. // 4 A struct that is only needed to set if full text search is on.
  899. FSection0.WriteWord(NToLE(Word(4)));
  900. FSection0.WriteWord(NToLE(Word(36))); // size
  901. FSection0.WriteDWord(ITSFHeader.LanguageID);
  902. FSection0.WriteDWord(0);
  903. FSection0.WriteDWord(NToLE(DWord(Ord(FFullTextSearch and FFullTextSearchAvailable))));
  904. FSection0.WriteDWord(NToLE(Dword(Ord(FHasKLinks))) ); // klinks
  905. FSection0.WriteDWord(0); // alinks
  906. // two for a QWord
  907. FSection0.WriteDWord(0);
  908. FSection0.WriteDWord(0);
  909. FSection0.WriteDWord(0);
  910. FSection0.WriteDWord(0);
  911. ////////////////////////<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  912. // 2 default page to load
  913. if FDefaultPage <> '' then begin
  914. FSection0.WriteWord(NToLE(Word(2)));
  915. FSection0.WriteWord(NToLE(Word(Length(FDefaultPage)+1)));
  916. FSection0.Write(FDefaultPage[1], Length(FDefaultPage));
  917. FSection0.WriteByte(0);
  918. end;
  919. // 3 Title
  920. if FTitle <> '' then begin
  921. FSection0.WriteWord(NToLE(Word(3)));
  922. FSection0.WriteWord(NToLE(Word(Length(FTitle)+1)));
  923. FSection0.Write(FTitle[1], Length(FTitle));
  924. FSection0.WriteByte(0);
  925. end;
  926. // 16 Default Font
  927. if FDefaultFont <> '' then begin
  928. FSection0.WriteWord(NToLE(Word(16)));
  929. FSection0.WriteWord(NToLE(Word(Length(FDefaultFont)+1)));
  930. FSection0.Write(FDefaultFont[1], Length(FDefaultFont));
  931. FSection0.WriteByte(0);
  932. end;
  933. // 6
  934. // unneeded. if output file is : /somepath/OutFile.chm the value here is outfile(lowercase)
  935. {FSection0.WriteWord(6);
  936. FSection0.WriteWord(Length('test1')+1);
  937. Fsection0.Write('test1', 5);
  938. FSection0.WriteByte(0);}
  939. // 0 Table of contents filename
  940. if FHasTOC then begin
  941. if fTocName ='' then
  942. TmpStr := DefaultHHC
  943. else
  944. TmpStr := fTocName;
  945. FSection0.WriteWord(0);
  946. FSection0.WriteWord(NToLE(Word(Length(TmpStr)+1)));
  947. FSection0.Write(TmpStr[1], Length(TmpStr));
  948. FSection0.WriteByte(0);
  949. end;
  950. // 1
  951. // hhk Index
  952. if FHasIndex then begin
  953. if fIndexName='' then
  954. TmpStr := DefaultHHK
  955. else
  956. TmpStr := fIndexName;
  957. FSection0.WriteWord(NToLE(Word(1)));
  958. FSection0.WriteWord(NToLE(Word(Length(TmpStr)+1)));
  959. FSection0.Write(TmpStr[1], Length(TmpStr));
  960. FSection0.WriteByte(0);
  961. end;
  962. // 5 Default Window
  963. if FDefaultWindow<>'' then
  964. begin
  965. FSection0.WriteWord(NTOLE(Word(5)));
  966. tmpstr:=FDefaultWindow;
  967. FSection0.WriteWord(NToLE(Word(Length(TmpStr)+1)));
  968. FSection0.Write(TmpStr[1], Length(TmpStr));
  969. FSection0.WriteByte(0);
  970. end;
  971. // 7 Binary Index
  972. if FHasBinaryIndex then
  973. begin
  974. {$ifdef binindex}
  975. logentry('binary index!');
  976. {$endif}
  977. FSection0.WriteWord(NToLE(Word(7)));
  978. FSection0.WriteWord(NToLE(Word(4)));
  979. FSection0.WriteDWord(DWord(0)); // what is this number to be?
  980. end;
  981. // 11 Binary TOC
  982. if FHasBinaryTOC then
  983. begin
  984. FSection0.WriteWord(NToLE(Word(11)));
  985. FSection0.WriteWord(NToLE(Word(4)));
  986. FSection0.WriteDWord(DWord(0)); // what is this number to be?
  987. end;
  988. // 13
  989. if FIDXHdrStream.size>0 then
  990. begin
  991. FSection0.WriteWord(NToLE(Word(13)));
  992. FSection0.WriteWord(NToLE(Word(FIDXHdrStream.size)));
  993. FSection0.copyfrom(FIDXHdrStream,0);
  994. end;
  995. Entry.DecompressedSize := FSection0.Position - Entry.DecompressedOffset;
  996. FInternalFiles.AddEntry(Entry);
  997. end;
  998. procedure TChmWriter.WriteITBITS;
  999. var
  1000. Entry: TFileEntryRec;
  1001. begin
  1002. // This is an empty and useless file
  1003. Entry.Name := '#ITBITS';
  1004. Entry.Path := '/';
  1005. Entry.Compressed := False;
  1006. Entry.DecompressedOffset :=0;// FSection0.Position;
  1007. Entry.DecompressedSize := 0;
  1008. FInternalFiles.AddEntry(Entry);
  1009. end;
  1010. procedure TChmWriter.WriteSTRINGS;
  1011. begin
  1012. if FStringsStream.Size = 0 then;
  1013. FStringsStream.WriteByte(0);
  1014. FStringsStream.Position := 0;
  1015. PostAddStreamToArchive('#STRINGS', '/', FStringsStream);
  1016. end;
  1017. procedure TChmWriter.WriteTOPICS;
  1018. begin
  1019. if FTopicsStream.Size = 0 then
  1020. Exit;
  1021. if tocname<>'' then
  1022. AddTopic('',self.TOCName,2);
  1023. if indexname<>'' then
  1024. AddTopic('',self.IndexName,2);
  1025. FTopicsStream.Position := 0;
  1026. PostAddStreamToArchive('#TOPICS', '/', FTopicsStream);
  1027. // I commented the code below since the result seemed unused
  1028. // FHits:=0;
  1029. // FIndexedFiles.ForEach(@IterateWord,FHits);
  1030. end;
  1031. procedure TChmWriter.WriteIDXHDR;
  1032. begin
  1033. if FIDXHdrStream.Size = 0 then
  1034. Exit;
  1035. FIDXHdrStream.Position := 0;
  1036. PostAddStreamToArchive('#IDXHDR', '/', FIDXHdrStream);
  1037. end;
  1038. procedure TChmWriter.WriteIVB;
  1039. begin
  1040. if FContextStream = nil then exit;
  1041. FContextStream.Position := 0;
  1042. // the size of all the entries
  1043. FContextStream.WriteDWord(NToLE(DWord(FContextStream.Size-SizeOf(dword))));
  1044. FContextStream.Position := 0;
  1045. AddStreamToArchive('#IVB', '/', FContextStream);
  1046. end;
  1047. const idxhdrmagic ='T#SM';
  1048. procedure TChmWriter.CreateIDXHDRStream;
  1049. var i : Integer;
  1050. begin
  1051. if (fmergefiles.count=0) and not HasBinaryIndex then // I assume text/site properties could also trigger idxhdr
  1052. exit;
  1053. FIDXHdrStream.setsize(4096);
  1054. FIDXHdrStream.position:=0;
  1055. FIDXHdrStream.write(idxhdrmagic[1],4); // 0 Magic
  1056. FIDXHdrStream.writedword(ntole(0)); // 4 Unknown timestamp/checksum
  1057. FIDXHdrStream.writedword(ntole(1)); // 8 1 (unknown)
  1058. FIDXHdrStream.writedword(ntole(FNrTopics)); // C Number of topic nodes including the contents & index files
  1059. FIDXHdrStream.writedword(ntole(0)); // 10 0 (unknown)
  1060. // 14 Offset in the #STRINGS file of the ImageList param of the "text/site properties" object of the sitemap contents (0/-1 = none)
  1061. if assigned(ftocsm) and (ftocsm.ImageList<>'') then
  1062. FIDXHdrStream.writedwordLE(addstring(ftocsm.ImageList))
  1063. else
  1064. FIDXHdrStream.writedwordLE($FFFFFFFF);
  1065. // 18 0 (unknown)
  1066. FIDXHdrStream.writedwordLE(0);
  1067. // 1C 1 if the value of the ImageType param of the "text/site properties" object of the sitemap contents is Folder. 0 otherwise.
  1068. if assigned(ftocsm) and (ftocsm.UseFolderImages) then
  1069. FIDXHdrStream.writedwordLE(1)
  1070. else
  1071. FIDXHdrStream.writedwordLE(0);
  1072. // 20 The value of the Background param of the "text/site properties" object of the sitemap contents
  1073. if assigned(ftocsm) then
  1074. FIDXHdrStream.writedwordLE(ftocsm.Backgroundcolor)
  1075. else
  1076. FIDXHdrStream.writedwordLE($FFFFFFFF);
  1077. // 24 The value of the Foreground param of the "text/site properties" object of the sitemap contents
  1078. if assigned(ftocsm) then
  1079. FIDXHdrStream.writedwordLE(ftocsm.Foregroundcolor)
  1080. else
  1081. FIDXHdrStream.writedwordLE($FFFFFFFF);
  1082. // 28 Offset in the #STRINGS file of the Font param of the "text/site properties" object of the sitemap contents (0/-1 = none)
  1083. if assigned(ftocsm) and (ftocsm.Font<>'') then
  1084. FIDXHdrStream.writedwordLE(addstring(ftocsm.font))
  1085. else
  1086. FIDXHdrStream.writedwordLE($FFFFFFFF);
  1087. // 2C The value of the Window Styles param of the "text/site properties" object of the sitemap contents
  1088. if assigned(ftocsm) then
  1089. FIDXHdrStream.writedwordLE(FTocsm.WindowStyles)
  1090. else
  1091. FIDXHdrStream.writedwordLE($FFFFFFFF);
  1092. // 30 The value of the EXWindow Styles param of the "text/site properties" object of the sitemap contents
  1093. if assigned(ftocsm) then
  1094. FIDXHdrStream.writedwordLE(FTocSm.ExWindowStyles)
  1095. else
  1096. FIDXHdrStream.writedwordLE(0);
  1097. // 34 Unknown. Often -1. Sometimes 0.
  1098. FIDXHdrStream.writedwordLE($FFFFFFFF);
  1099. // 38 Offset in the #STRINGS file of the FrameName param of the "text/site properties" object of the sitemap contents (0/-1 = none)
  1100. if assigned(ftocsm) and (ftocsm.framename<>'') then
  1101. FIDXHdrStream.writedwordLE(addstring(FTocsm.Framename))
  1102. else
  1103. FIDXHdrStream.writedwordLE(0);
  1104. // 3C Offset in the #STRINGS file of the WindowName param of the "text/site properties" object of the sitemap contents (0/-1 = none)
  1105. if assigned(ftocsm) and (ftocsm.windowname<>'') then
  1106. FIDXHdrStream.writedwordLE(addstring(FTocsm.windowname))
  1107. else
  1108. FIDXHdrStream.writedwordLE($FFFFFFFF);
  1109. FIDXHdrStream.writedword(ntole(0)); // 40 Number of information types.
  1110. FIDXHdrStream.writedword(ntole(1)); // 44 Unknown. Often 1. Also 0, 3.
  1111. FIDXHdrStream.writedword(ntole(fmergefiles.count)); // 48 Number of files in the [MERGE FILES] list.
  1112. // 4C Unknown. Often 0. Non-zero mostly in files with some files in the merge files list.
  1113. if fmergefiles.count>0 then
  1114. FIDXHdrStream.writedwordLE(1)
  1115. else
  1116. FIDXHdrStream.writedwordLE(0);
  1117. for i:=0 to FMergefiles.count-1 do
  1118. FIDXHdrStream.WriteDword(addstring(fmergefiles[i]));
  1119. for i:=0 to 1004-fmergefiles.count-1 do
  1120. FIDXHdrStream.WriteDword(0);
  1121. end;
  1122. procedure TChmWriter.WriteURL_STR_TBL;
  1123. begin
  1124. if FURLSTRStream.Size <> 0 then begin
  1125. FURLSTRStream.Position := 0;
  1126. PostAddStreamToArchive('#URLSTR', '/', FURLSTRStream);
  1127. end;
  1128. if FURLTBLStream.Size <> 0 then begin
  1129. FURLTBLStream.Position := 0;
  1130. PostAddStreamToArchive('#URLTBL', '/', FURLTBLStream);
  1131. end;
  1132. end;
  1133. procedure TChmWriter.WriteOBJINST;
  1134. var
  1135. i: Integer;
  1136. ObjStream: TMemoryStream;
  1137. //Flags: Word;
  1138. begin
  1139. ObjStream := TMemorystream.Create;
  1140. // this file is needed to enable searches for the ms reader
  1141. ObjStream.WriteDWord(NtoLE($04000000));
  1142. ObjStream.WriteDWord(NtoLE(Dword(2))); // two entries
  1143. ObjStream.WriteDWord(NtoLE(DWord(24))); // offset into file of entry
  1144. ObjStream.WriteDWord(NtoLE(DWord(2691))); // size
  1145. ObjStream.WriteDWord(NtoLE(DWord(2715))); // offset into file of entry
  1146. ObjStream.WriteDWord(NtoLE(DWord(36))); // size
  1147. // first entry
  1148. // write guid 4662DAAF-D393-11D0-9A56-00C04FB68BF7
  1149. ObjStream.WriteDWord(NtoLE($4662DAAF));
  1150. ObjStream.WriteWord(NtoLE($D393));
  1151. ObjStream.WriteWord(NtoLE(word($11D0)));
  1152. ObjStream.WriteWord(NtoLE(word($569A)));
  1153. ObjStream.WriteByte($00);
  1154. ObjStream.WriteByte($C0);
  1155. ObjStream.WriteByte($4F);
  1156. ObjStream.WriteByte($B6);
  1157. ObjStream.WriteByte($8B);
  1158. ObjStream.WriteByte($F7);
  1159. ObjStream.WriteDWord(NtoLE($04000000));
  1160. ObjStream.WriteDWord(NtoLE(11)); // bit flags
  1161. ObjStream.WriteDWord(NtoLE(DWord(1252)));
  1162. ObjStream.WriteDWord(NtoLE(DWord(1033)));
  1163. ObjStream.WriteDWord(NtoLE($00000000));
  1164. ObjStream.WriteDWord(NtoLE($00000000));
  1165. ObjStream.WriteDWord(NtoLE($00145555));
  1166. ObjStream.WriteDWord(NtoLE($00000A0F));
  1167. ObjStream.WriteWord(NtoLE($0100));
  1168. ObjStream.WriteDWord(NtoLE($00030005));
  1169. for i := 0 to 5 do
  1170. ObjStream.WriteDWord($00000000);
  1171. ObjStream.WriteWord($0000);
  1172. // okay now the fun stuff
  1173. for i := 0 to $FF do
  1174. ObjStream.Write(ObjInstEntries[i], SizeOF(TObjInstEntry));
  1175. {begin
  1176. if i = 1 then
  1177. Flags := 7
  1178. else
  1179. Flags := 0;
  1180. if (i >= $41) and (i <= $5A) then
  1181. Flags := Flags or 2;
  1182. if (i >= $61) and (i <= $7A) then
  1183. Flags := Flags or 1;
  1184. if i = $27 then
  1185. Flags := Flags or 6;
  1186. ObjStream.WriteWord(NtoLE(Flags));
  1187. ObjStream.WriteWord(NtoLE(Word(i)));
  1188. if (i >= $41) and (i <= $5A) then
  1189. ObjStream.WriteByte(NtoLE(i+$20))
  1190. else
  1191. ObjStream.WriteByte(NtoLE(i));
  1192. ObjStream.WriteByte(NtoLE(i));
  1193. ObjStream.WriteByte(NtoLE(i));
  1194. ObjStream.WriteByte(NtoLE(i));
  1195. ObjStream.WriteWord(NtoLE($0000));
  1196. end;}
  1197. ObjStream.WriteDWord(NtoLE($E66561C6));
  1198. ObjStream.WriteDWord(NtoLE($73DF6561));
  1199. ObjStream.WriteDWord(NtoLE($656F8C73));
  1200. ObjStream.WriteWord(NtoLE(word($6F9C)));
  1201. ObjStream.WriteByte($65);
  1202. // third bit of second entry
  1203. // write guid 8FA0D5A8-DEDF-11D0-9A61-00C04FB68BF7
  1204. ObjStream.WriteDWord(NtoLE($8FA0D5A8));
  1205. ObjStream.WriteWord(NtoLE($DEDF));
  1206. ObjStream.WriteWord(NtoLE(word($11D0)));
  1207. ObjStream.WriteWord(NtoLE(word($619A)));
  1208. ObjStream.WriteByte($00);
  1209. ObjStream.WriteByte($C0);
  1210. ObjStream.WriteByte($4F);
  1211. ObjStream.WriteByte($B6);
  1212. ObjStream.WriteByte($8B);
  1213. ObjStream.WriteByte($F7);
  1214. ObjStream.WriteDWord(NtoLE($04000000));
  1215. ObjStream.WriteDWord(NtoLE(DWord(1)));
  1216. ObjStream.WriteDWord(NtoLE(DWord(1252)));
  1217. ObjStream.WriteDWord(NtoLE(DWord(1033)));
  1218. ObjStream.WriteDWord(NtoLE(DWord(0)));
  1219. // second entry
  1220. // write guid 4662DAB0-D393-11D0-9A56-00C04FB68B66
  1221. ObjStream.WriteDWord(NtoLE($4662DAB0));
  1222. ObjStream.WriteWord(NtoLE($D393));
  1223. ObjStream.WriteWord(NtoLE(word($11D0)));
  1224. ObjStream.WriteWord(NtoLE(word($569A)));
  1225. ObjStream.WriteByte($00);
  1226. ObjStream.WriteByte($C0);
  1227. ObjStream.WriteByte($4F);
  1228. ObjStream.WriteByte($B6);
  1229. ObjStream.WriteByte($8B);
  1230. ObjStream.WriteByte($66);
  1231. ObjStream.WriteDWord(NtoLE(DWord(666))); // not kidding
  1232. ObjStream.WriteDWord(NtoLE(DWord(1252)));
  1233. ObjStream.WriteDWord(NtoLE(DWord(1033)));
  1234. ObjStream.WriteDWord(NtoLE(DWord(10031)));
  1235. ObjStream.WriteDWord(NtoLE(DWord(0)));
  1236. ObjStream.Position := 0;
  1237. AddStreamToArchive('$OBJINST', '/', ObjStream, True);
  1238. ObjStream.Free;
  1239. end;
  1240. procedure TChmWriter.WriteFiftiMain;
  1241. var
  1242. SearchWriter: TChmSearchWriter;
  1243. begin
  1244. if FTopicsStream.Size = 0 then
  1245. Exit;
  1246. SearchWriter := TChmSearchWriter.Create(FFiftiMainStream, FIndexedFiles);
  1247. // do not add an empty $FIftiMain
  1248. if not SearchWriter.HasData then
  1249. begin
  1250. FFullTextSearchAvailable := False;
  1251. SearchWriter.Free;
  1252. Exit;
  1253. end;
  1254. FFullTextSearchAvailable := True;
  1255. SearchWriter.WriteToStream;
  1256. SearchWriter.Free;
  1257. if FFiftiMainStream.Size = 0 then
  1258. Exit;
  1259. FFiftiMainStream.Position := 0;
  1260. PostAddStreamToArchive('$FIftiMain', '/', FFiftiMainStream);
  1261. end;
  1262. procedure TChmWriter.WriteWindows;
  1263. Var WindowStream : TMemoryStream;
  1264. i,j : Integer;
  1265. win : TChmWindow;
  1266. begin
  1267. if FWindows.Count>0 then
  1268. begin
  1269. WindowStream:=TMemoryStream.Create;
  1270. WindowStream.WriteDword(NToLE(dword(FWindows.Count)));
  1271. WindowStream.WriteDword(NToLE(dword(196))); // 1.1 or later. 188 is old style.
  1272. for i:=0 to FWindows.Count-1 Do
  1273. begin
  1274. Win:=TChmWindow(FWindows[i]);
  1275. WindowStream.WriteDwordLE (196); // 0 size of entry.
  1276. WindowStream.WriteDwordLE (0); // 4 unknown (bool Unicodestrings?)
  1277. WindowStream.WriteDword(NToLE(addstring(win.window_type ))); // 8 Arg 0, name of window
  1278. WindowStream.WriteDword(NToLE(dword(win.flags ))); // C valid fields
  1279. WindowStream.WriteDword(NToLE(dword(win.nav_style))); // 10 arg 10 navigation pane style
  1280. WindowStream.WriteDword(NToLE(addstring(win.title_bar_text))); // 14 Arg 1, title bar text
  1281. WindowStream.WriteDword(NToLE(dword(win.styleflags))); // 18 Arg 14, style flags
  1282. WindowStream.WriteDword(NToLE(dword(win.xtdstyleflags))); // 1C Arg 15, xtd style flags
  1283. WindowStream.WriteDword(NToLE(dword(win.left))); // 20 Arg 13, rect.left
  1284. WindowStream.WriteDword(NToLE(dword(win.top))); // 24 Arg 13, rect.top
  1285. WindowStream.WriteDword(NToLE(dword(win.right))); // 28 Arg 13, rect.right
  1286. WindowStream.WriteDword(NToLE(dword(win.bottom))); // 2C Arg 13, rect.bottom
  1287. WindowStream.WriteDword(NToLE(dword(win.window_show_state))); // 30 Arg 16, window show state
  1288. WindowStream.WriteDword(NToLE(dword(0))); // 34 - , HWND hwndhelp OUT: window handle"
  1289. WindowStream.WriteDword(NToLE(dword(0))); // 38 - , HWND hwndcaller OUT: who called this window"
  1290. WindowStream.WriteDword(NToLE(dword(0))); // 3C - , HH_INFO_TYPE paINFO_TYPES IN: Pointer to an array of Information Types"
  1291. WindowStream.WriteDword(NToLE(dword(0))); // 40 - , HWND hwndtoolbar OUT: toolbar window in tri-pane window"
  1292. WindowStream.WriteDword(NToLE(dword(0))); // 44 - , HWND hwndnavigation OUT: navigation window in tri-pane window"
  1293. WindowStream.WriteDword(NToLE(dword(0))); // 48 - , HWND hwndhtml OUT: window displaying HTML in tri-pane window"
  1294. WindowStream.WriteDword(NToLE(dword(win.navpanewidth))); // 4C Arg 11, width of nav pane
  1295. WindowStream.WriteDword(NToLE(dword(0))); // 50 - , rect.left, OUT:Specifies the coordinates of the Topic pane
  1296. WindowStream.WriteDword(NToLE(dword(0))); // 54 - , rect.top , OUT:Specifies the coordinates of the Topic pane
  1297. WindowStream.WriteDword(NToLE(dword(0))); // 58 - , rect.right, OUT:Specifies the coordinates of the Topic pane
  1298. WindowStream.WriteDword(NToLE(dword(0))); // 5C - , rect.bottom, OUT:Specifies the coordinates of the Topic pane
  1299. WindowStream.WriteDword(NToLE(addstring(win.toc_file))); // 60 Arg 2, toc file
  1300. WindowStream.WriteDword(NToLE(addstring(win.index_file))); // 64 Arg 3, index file
  1301. WindowStream.WriteDword(NToLE(addstring(win.default_file))); // 68 Arg 4, default file
  1302. WindowStream.WriteDword(NToLE(addstring(win.home_button_file))); // 6c Arg 5, home button file.
  1303. WindowStream.WriteDword(NToLE(dword(win.buttons))); // 70 arg 12,
  1304. WindowStream.WriteDword(NToLE(dword(win.navpane_initially_closed))); // 74 arg 17
  1305. WindowStream.WriteDword(NToLE(dword(win.navpane_default))); // 78 arg 18,
  1306. WindowStream.WriteDword(NToLE(dword(win.navpane_location))); // 7C arg 19,
  1307. WindowStream.WriteDword(NToLE(dword(win.wm_notify_id))); // 80 arg 20,
  1308. for j:=0 to 4 do
  1309. WindowStream.WriteDword(NToLE(dword(0))); // 84 - byte[20] unknown - "BYTE tabOrder[HH_MAX_TABS + 1]; // IN/OUT: tab order: Contents, Index, Search, History, Favorites, Reserved 1-5, Custom tabs"
  1310. WindowStream.WriteDword(NToLE(dword(0))); // 94 - int cHistory; // IN/OUT: number of history items to keep (default is 30)
  1311. WindowStream.WriteDword(NToLE(addstring(win.Jumpbutton_1_Text))); // 9C Arg 7, The text of the Jump 1 button.
  1312. WindowStream.WriteDword(NToLE(addstring(win.Jumpbutton_2_Text))); // A0 Arg 9, The text of the Jump 2 button.
  1313. WindowStream.WriteDword(NToLE(addstring(win.Jumpbutton_1_File))); // A4 Arg 6, The file shown for Jump 1 button.
  1314. WindowStream.WriteDword(NToLE(addstring(win.Jumpbutton_2_File))); // A8 Arg 8, The file shown for Jump 1 button.
  1315. for j:=0 to 3 do
  1316. WindowStream.WriteDword(NToLE(dword(0))); // AA - byte[16] (TRECT) "RECT rcMinSize; // Minimum size for window (ignored in version 1)"
  1317. // 1.1+ fields
  1318. WindowStream.WriteDword(NToLE(dword(0))); // BC - int cbInfoTypes; // size of paInfoTypes;
  1319. WindowStream.WriteDword(NToLE(dword(0))); // C0 - LPCTSTR pszCustomTabs; // multiple zero-terminated strings
  1320. end;
  1321. WindowStream.Position := 0;
  1322. AddStreamToArchive('#WINDOWS', '/', WindowStream, True);
  1323. WindowStream.Free;
  1324. end;
  1325. end;
  1326. procedure TChmWriter.WriteInternalFilesAfter;
  1327. begin
  1328. // This creates and writes the #ITBITS (empty) file to section0
  1329. WriteITBITS;
  1330. // This creates and writes the #SYSTEM file to section0
  1331. WriteSystem;
  1332. if Assigned(FTocSM) then
  1333. Scansitemap(FTocSM);
  1334. end;
  1335. procedure TChmWriter.WriteFinalCompressedFiles;
  1336. begin
  1337. inherited WriteFinalCompressedFiles;
  1338. WriteTOPICS;
  1339. WriteURL_STR_TBL;
  1340. WriteWINDOWS;
  1341. CreateIDXHDRStream;
  1342. WriteIDXHDR;
  1343. WriteSTRINGS;
  1344. WriteFiftiMain;
  1345. end;
  1346. procedure TChmWriter.FileAdded(AStream: TStream; const AEntry: TFileEntryRec);
  1347. begin
  1348. inherited FileAdded(AStream, AEntry);
  1349. if FullTextSearch then
  1350. CheckFileMakeSearchable(AStream, AEntry);
  1351. end;
  1352. procedure TChmWriter.WriteInternalFilesBefore;
  1353. begin
  1354. inherited WriteInternalFilesBefore;
  1355. WriteIVB;
  1356. WriteOBJINST;
  1357. end;
  1358. constructor TChmWriter.Create(AOutStream: TStream; FreeStreamOnDestroy: Boolean);
  1359. begin
  1360. inherited Create(AOutStream, FreeStreamOnDestroy);
  1361. FStringsStream := TmemoryStream.Create;
  1362. FTopicsStream := TMemoryStream.Create;
  1363. FURLSTRStream := TMemoryStream.Create;
  1364. FURLTBLStream := TMemoryStream.Create;
  1365. FFiftiMainStream := TMemoryStream.Create;
  1366. FIndexedFiles := TIndexedWordList.Create;
  1367. FAVLTopicdedupe :=TAVLTree.Create(@CompareStrings); // dedupe filenames in topics.
  1368. FAvlStrings := TAVLTree.Create(@CompareStrings); // dedupe strings
  1369. FAvlURLStr := TAVLTree.Create(@CompareUrlStrs); // dedupe urltbl + binindex must resolve URL to topicid
  1370. SpareString := TStringIndex.Create; // We need an object to search in avltree
  1371. SpareUrlStr := TUrlStrIndex.Create; // to avoid create/free circles we keep one in spare
  1372. FIDXHdrStream := TMemoryStream.Create; // the #IDXHDR and chunk 13 in #SYSTEM
  1373. // for searching purposes
  1374. FWindows := TObjectlist.Create(True);
  1375. FDefaultWindow:= '';
  1376. FMergeFiles :=TStringList.Create;
  1377. FNrTopics :=0;
  1378. FDictTopicsUrlInd :=specialize TDictionary<string,integer>.Create;
  1379. end;
  1380. destructor TChmWriter.Destroy;
  1381. begin
  1382. if Assigned(FContextStream) then FContextStream.Free;
  1383. FMergeFiles.Free;
  1384. FIndexedFiles.Free;
  1385. FStringsStream.Free;
  1386. FTopicsStream.Free;
  1387. FURLSTRStream.Free;
  1388. FURLTBLStream.Free;
  1389. FFiftiMainStream.Free;
  1390. FIDXHdrStream.Free;
  1391. SpareString.free;
  1392. SpareUrlStr.free;
  1393. FAvlUrlStr.FreeAndClear;
  1394. FAvlUrlStr.Free;
  1395. FAvlStrings.FreeAndClear;
  1396. FAvlStrings.Free;
  1397. FAVLTopicdedupe.FreeAndClear;
  1398. FAVLTopicdedupe.free;
  1399. FWindows.Free;
  1400. FDictTopicsUrlInd.Free;
  1401. inherited Destroy;
  1402. end;
  1403. function TChmWriter.AddString(AString: String): LongWord;
  1404. var
  1405. NextBlock: DWord;
  1406. Pos: DWord;
  1407. n : TAVLTreeNode;
  1408. StrRec : TStringIndex;
  1409. begin
  1410. // #STRINGS starts with a null AnsiChar
  1411. if FStringsStream.Size = 0 then FStringsStream.WriteByte(0);
  1412. SpareString.TheString:=AString;
  1413. n:=fAvlStrings.FindKey(SpareString,@CompareStrings);
  1414. if assigned(n) then
  1415. exit(TStringIndex(n.data).strid);
  1416. // each entry is a null terminated string
  1417. Pos := DWord(FStringsStream.Position);
  1418. // Strings are contained in $1000 byte blocks and cannot cross blocks
  1419. NextBlock := ($0000F000 and Pos) + $00001000;
  1420. if Length(AString) + 1 > NextBlock then
  1421. begin
  1422. FStringsStream.Size:= NextBlock;
  1423. FStringsStream.Position := NextBlock;
  1424. end;
  1425. Result := FStringsStream.Position;
  1426. if length(AString)>0 Then
  1427. FStringsStream.WriteBuffer(AString[1], Length(AString));
  1428. FStringsStream.WriteByte(0);
  1429. StrRec:=TStringIndex.Create;
  1430. StrRec.TheString:=AString;
  1431. StrRec.Strid :=Result;
  1432. fAvlStrings.Add(StrRec);
  1433. end;
  1434. function TChmWriter.AddURL ( AURL: String; TopicsIndex: DWord ) : LongWord;
  1435. procedure CheckURLStrBlockCanHold(Const AString: String);
  1436. var
  1437. Rem: LongWord;
  1438. Len: LongWord;
  1439. begin
  1440. Rem := $4000 - (FURLSTRStream.Size mod $4000);
  1441. Len := 9 + Length(AString); // 2 dwords the string and NT
  1442. if Rem < Len then
  1443. while Rem > 0 do
  1444. begin
  1445. FURLSTRStream.WriteByte(0);
  1446. Dec(Rem);
  1447. end;
  1448. end;
  1449. function AddURLString(Const AString: String): DWord;
  1450. var urlstrrec : TUrlStrIndex;
  1451. begin
  1452. CheckURLStrBlockCanHold(AString);
  1453. if FURLSTRStream.Size mod $4000 = 0 then
  1454. FURLSTRStream.WriteByte(0);
  1455. Result := FURLSTRStream.Position;
  1456. UrlStrRec:=TUrlStrIndex.Create;
  1457. UrlStrRec.UrlStr:=AString;
  1458. UrlStrRec.UrlStrid:=result;
  1459. FAvlUrlStr.Add(UrlStrRec);
  1460. FURLSTRStream.WriteDWord(NToLE(DWord(0))); // URL Offset for topic after the the "Local" value
  1461. FURLSTRStream.WriteDWord(NToLE(DWord(0))); // Offset of FrameName??
  1462. if Length(AString) > 0 then
  1463. FURLSTRStream.Write(AString[1], Length(AString));
  1464. FURLSTRStream.WriteByte(0); //NT
  1465. end;
  1466. function LookupUrlString(const AUrl : String):DWord;
  1467. var n :TAvlTreeNode;
  1468. begin
  1469. SpareUrlStr.UrlStr:=AUrl;
  1470. n:=FAvlUrlStr.FindKey(SpareUrlStr,@CompareUrlStrs);
  1471. if assigned(n) Then
  1472. result:=TUrlStrIndex(n.data).UrlStrId
  1473. else
  1474. result:=AddUrlString(AUrl);
  1475. end;
  1476. var UrlIndex : Integer;
  1477. begin
  1478. if (Length(AURL) > 0) and (AURL[1] = '/') then Delete(AURL,1,1);
  1479. UrlIndex:=LookupUrlString(AUrl);
  1480. //if $1000 - (FURLTBLStream.Size mod $1000) = 4 then // we are at 4092
  1481. if FURLTBLStream.Size and $FFC = $FFC then // faster :)
  1482. FURLTBLStream.WriteDWord(0);
  1483. Result := FURLTBLStream.Position;
  1484. FURLTBLStream.WriteDWord(0);//($231e9f5c); //unknown
  1485. FURLTBLStream.WriteDWord(NtoLE(TopicsIndex)); // Index of topic in #TOPICS
  1486. FURLTBLStream.WriteDWord(NtoLE(UrlIndex));
  1487. end;
  1488. procedure TChmWriter.CheckFileMakeSearchable(AStream: TStream; AFileEntry: TFileEntryRec);
  1489. var
  1490. TopicEntry: TTopicEntry;
  1491. ATitle: String;
  1492. begin
  1493. if Pos('.ht', AFileEntry.Name) > 0 then
  1494. begin
  1495. ATitle := FIndexedFiles.IndexFile(AStream, NextTopicIndex, FSearchTitlesOnly);
  1496. AddTopic(ATitle,AFileEntry.Path+AFileEntry.Name,-1);
  1497. end;
  1498. end;
  1499. function TChmWriter.AddTopic(ATitle,AnUrl:AnsiString;code:integer=-1):integer;
  1500. var
  1501. TopicEntry: TTopicEntry;
  1502. begin
  1503. ATitle :=StringReplace(Atitle, '&x27;', '', [rfReplaceAll]);
  1504. anurl:=StringReplace(anurl, '\', '/', [rfReplaceAll]);
  1505. if ATitle <> '' then
  1506. TopicEntry.StringsOffset := AddString(ATitle)
  1507. else
  1508. TopicEntry.StringsOffset := $FFFFFFFF;
  1509. result:=NextTopicIndex;
  1510. TopicEntry.URLTableOffset := AddURL(AnUrl, Result);
  1511. if code=-1 then
  1512. begin
  1513. if ATitle<>'' then
  1514. TopicEntry.InContents := 6
  1515. else
  1516. TopicEntry.InContents := 2;
  1517. if pos('#',AnUrl)>0 then
  1518. TopicEntry.InContents := 0;
  1519. end
  1520. else
  1521. TopicEntry.InContents := code;
  1522. inc(FNrTopics);
  1523. TopicEntry.Unknown := 0;
  1524. TopicEntry.TocOffset := 0;
  1525. FTopicsStream.WriteDWord(LEtoN(TopicEntry.TocOffset));
  1526. FTopicsStream.WriteDWord(LEtoN(TopicEntry.StringsOffset));
  1527. FTopicsStream.WriteDWord(LEtoN(TopicEntry.URLTableOffset));
  1528. FTopicsStream.WriteWord(LEtoN(TopicEntry.InContents));
  1529. FTopicsStream.WriteWord(LEtoN(TopicEntry.Unknown));
  1530. {$ifdef binindex}
  1531. writeln('topout:',result, ' ' , TopicEntry.StringsOffset,' ' ,TopicEntry.URLTableOffset, ' ',atitle,' - ', anurl);
  1532. {$endif}
  1533. end;
  1534. function TChmWriter.AddTopicindex(ATitle, AnUrl: AnsiString; code: integer
  1535. ): integer;
  1536. begin
  1537. ATitle :=StringReplace(Atitle, '&x27;', '', [rfReplaceAll]);
  1538. // adhoc subsitutions. Replace with real code if exact behaviour is known.
  1539. { Atitle:=StringReplace(atitle, '&x27;', '''', [rfReplaceAll]);
  1540. if length(atitle)>0 then
  1541. atitle[1]:=uppercase(atitle[1])[1];}
  1542. {$ifdef binindex}
  1543. writeln('Enter ',ATitle,' ',AnUrl);
  1544. {$endif}
  1545. if FDictTopicsUrlInd.trygetvalue(anurl,result) then
  1546. begin
  1547. {$ifdef binindex}
  1548. writeln('found:',result);
  1549. {$endif}
  1550. end
  1551. else
  1552. begin
  1553. result:=addtopic(atitle,anurl);
  1554. FDictTopicsUrlInd.add(anurl,result);
  1555. end;
  1556. end;
  1557. procedure TChmWriter.ScanSitemap(asitemap:TCHMSiteMap);
  1558. procedure scanitems(it:TChmSiteMapItems);
  1559. var i : integer;
  1560. x : TChmSiteMapItem;
  1561. s : string;
  1562. strrec : TStringIndex;
  1563. begin
  1564. for i:=0 to it.count -1 do
  1565. begin
  1566. x:=it.item[i];
  1567. // if sanitizeurl(fbasepath,x.local,S) then // sanitize, remove stuff etc.
  1568. // begin
  1569. // writeln(x.text,' : ',x.local,' ',x.url,' ' ,x.merge);
  1570. if assigned(x.children) and (x.children.count>0) then
  1571. scanitems(x.children);
  1572. end;
  1573. end;
  1574. begin
  1575. scanitems(asitemap.items);
  1576. end;
  1577. function TChmWriter.NextTopicIndex: Integer;
  1578. begin
  1579. Result := FTopicsStream.Size div 16;
  1580. end;
  1581. procedure TChmWriter.AppendTOC(AStream: TStream);
  1582. var tmpstr : string;
  1583. begin
  1584. fHasTOC := True;
  1585. if fTocName = '' then
  1586. tmpstr := defaulthhc
  1587. else
  1588. tmpstr := fTocName;
  1589. PostAddStreamToArchive(tmpstr, '/', AStream, True);
  1590. end;
  1591. procedure TChmWriter.AppendBinaryTOCFromSiteMap(ASiteMap: TChmSiteMap);
  1592. var
  1593. Header: TTOCIdxHeader;
  1594. Entry: TTocEntry;
  1595. EntryInfo: TTOCEntryPageBookInfo;
  1596. EntryInfoStream,
  1597. EntryTopicOffsetStream,
  1598. EntryStream: TMemoryStream;
  1599. TOCIDXStream: TMemoryStream;
  1600. NextLevelItems,
  1601. CurrentLevelItems: TFPList;
  1602. i,j: Integer;
  1603. MenuItem: TChmSiteMapItem;
  1604. MenuItems: TChmSiteMapItems;
  1605. TopicEntry: TTopicEntry;
  1606. EntryCount: DWord = $29A;
  1607. procedure FixParentBookFirstChildOffset(AChildOffset: DWord);
  1608. var
  1609. ParentEntry: TTOCEntryPageBookInfo;
  1610. begin
  1611. // read parent entry
  1612. EntryInfoStream.Position := MenuItems.InternalData;
  1613. EntryInfoStream.Read(ParentEntry, SizeOf(ParentEntry));
  1614. // update child offset
  1615. ParentEntry.FirstChildOffset:= NtoLE(DWord(4096 + AChildOffset));
  1616. // write back to stream
  1617. EntryInfoStream.Position := MenuItems.InternalData;
  1618. EntryInfoStream.Write(ParentEntry, SizeOf(ParentEntry));
  1619. // move to end of stream
  1620. EntryInfoStream.Position := AChildOffset;
  1621. end;
  1622. begin
  1623. FillChar(Header, 4096, 0);
  1624. // create streams
  1625. TOCIDXStream := TMemoryStream.Create;
  1626. EntryInfoStream := TMemoryStream.Create;
  1627. EntryTopicOffsetStream := TMemoryStream.Create;
  1628. EntryStream := TMemoryStream.Create;
  1629. NextLevelItems := TFPList.Create;
  1630. NextLevelItems.Add(ASiteMap.Items);
  1631. if NextLevelItems.Count = 0 then
  1632. FreeAndNil(NextLevelItems);
  1633. while NextLevelItems <> nil do
  1634. begin
  1635. CurrentLevelItems := NextLevelItems;
  1636. NextLevelItems := TFPList.Create;
  1637. for i := 0 to CurrentLevelItems.Count-1 do
  1638. begin
  1639. MenuItems := TChmSiteMapItems(CurrentLevelItems.Items[i]);
  1640. for j := 0 to MenuItems.Count-1 do
  1641. begin
  1642. MenuItem := MenuItems.Item[j];
  1643. // first figure out the props
  1644. EntryInfo.Props := 0;
  1645. if MenuItem.Children.Count > 0 then
  1646. EntryInfo.Props := EntryInfo.Props or TOC_ENTRY_HAS_CHILDREN;
  1647. if Length(MenuItem.Local) > 0 then
  1648. EntryInfo.Props := EntryInfo.Props or TOC_ENTRY_HAS_LOCAL;
  1649. if EntryInfo.Props and TOC_ENTRY_HAS_LOCAL > 0 then
  1650. begin
  1651. // Write #TOPICS entry
  1652. TopicEntry.TocOffset := NtoLE(DWord(4096 + EntryInfoStream.Position));
  1653. TopicEntry.StringsOffset := NtoLE(AddString(MenuItem.Text));
  1654. TopicEntry.URLTableOffset := NtoLE(AddURL(MenuItem.Local, NextTopicIndex));
  1655. TopicEntry.InContents := NtoLE(Word( 2 ));
  1656. TopicEntry.Unknown := 0;
  1657. EntryInfo.TopicsIndexOrStringsOffset := NtoLE(Dword(NextTopicIndex));;
  1658. FTopicsStream.Write(TopicEntry, SizeOf(TopicEntry));
  1659. EntryTopicOffsetStream.WriteDWord(EntryInfo.TopicsIndexOrStringsOffset);
  1660. // write TOCEntry
  1661. Entry.PageBookInfoOffset:= NtoLE(4096 + EntryInfoStream.Position);
  1662. Entry.IncrementedInt := NtoLE(EntryCount);
  1663. EntryStream.Write(Entry, SizeOf(Entry));
  1664. Inc(EntryCount);
  1665. end
  1666. else
  1667. begin
  1668. EntryInfo.TopicsIndexOrStringsOffset := NtoLE(AddString(MenuItem.Text));
  1669. end;
  1670. // write TOCEntryInfo
  1671. EntryInfo.Unknown1 := 0;
  1672. EntryInfo.EntryIndex := NtoLE(Word(EntryCount - $29A)); //who knows how useful any of this is
  1673. if MenuItems.InternalData <> maxLongint then
  1674. EntryInfo.ParentPageBookInfoOffset := MenuItems.InternalData
  1675. else
  1676. EntryInfo.ParentPageBookInfoOffset := 0;
  1677. if j = MenuItems.Count-1 then
  1678. EntryInfo.NextPageBookOffset := 0
  1679. else if (EntryInfo.Props and TOC_ENTRY_HAS_CHILDREN) > 0 then
  1680. EntryInfo.NextPageBookOffset := 4096 + EntryInfoStream.Position + 28
  1681. else
  1682. EntryInfo.NextPageBookOffset := 4096 + EntryInfoStream.Position + 20;
  1683. // Only if TOC_ENTRY_HAS_CHILDREN is set are these written
  1684. EntryInfo.FirstChildOffset := 0; // we will update this when the child is written
  1685. // in fact lets update the *parent* of this item now if needed
  1686. if (j = 0) and (MenuItems.InternalData <> maxLongint) then
  1687. FixParentBookFirstChildOffset(EntryInfoStream.Position);
  1688. EntryInfo.Unknown3 := 0;
  1689. // fix endian order
  1690. EntryInfo.Props := NtoLE(EntryInfo.Props);
  1691. EntryInfo.ParentPageBookInfoOffset := NtoLE(EntryInfo.ParentPageBookInfoOffset);
  1692. EntryInfo.NextPageBookOffset := NtoLE(EntryInfo.NextPageBookOffset);
  1693. if MenuItem.Children.Count > 0 then
  1694. begin
  1695. NextLevelItems.Add(MenuItem.Children);
  1696. MenuItem.Children.InternalData := EntryInfoStream.Position;
  1697. end;
  1698. // write to stream
  1699. EntryInfoStream.Write(EntryInfo, PageBookInfoRecordSize(@EntryInfo));
  1700. end;
  1701. end;
  1702. FreeAndNil(CurrentLevelItems);
  1703. if NextLevelItems.Count = 0 then
  1704. FreeAndNil(NextLevelItems);
  1705. end;
  1706. // write all streams to TOCIdxStream and free everything
  1707. EntryInfoStream.Position:=0;
  1708. EntryTopicOffsetStream.Position:=0;
  1709. EntryStream.Position:=0;
  1710. Header.BlockSize := NtoLE(DWord(4096));
  1711. Header.EntriesCount := NtoLE(DWord(EntryCount - $29A));
  1712. Header.EntriesOffset := NtoLE(DWord(4096 + EntryInfoStream.Size + EntryTopicOffsetStream.Size));
  1713. Header.TopicsOffset := NtoLE(DWord(4096 + EntryInfoStream.Size));
  1714. TOCIDXStream.Write(Header, SizeOf(Header));
  1715. TOCIDXStream.CopyFrom(EntryInfoStream, EntryInfoStream.Size);
  1716. EntryInfoStream.Free;
  1717. TOCIDXStream.CopyFrom(EntryTopicOffsetStream, EntryTopicOffsetStream.Size);
  1718. EntryTopicOffsetStream.Free;
  1719. TOCIDXStream.CopyFrom(EntryStream, EntryStream.Size);
  1720. EntryStream.Free;
  1721. TOCIDXStream.Position := 0;
  1722. AppendBinaryTOCStream(TOCIDXStream);
  1723. TOCIDXStream.Free;
  1724. end;
  1725. Const
  1726. BinIndexIdent : array[0..1] of AnsiChar = (CHR($3B),CHR($29));
  1727. AlwaysX44 : Array[0..15] of AnsiChar = ('X','4','4',#0,#0,#0,#0,#0,
  1728. #0,#0,#0,#0,#0,#0,#0,#0);
  1729. DataEntry : Array[0..12] of Byte = ($00,$00,$00,$00,$05,$00,$00,$00,$80,$00,$00,$00,$00);
  1730. {
  1731. IndexStream:=TMemoryStream.Create;
  1732. IndexStream.Write(BinIndexIdent,2);
  1733. IndexStream.Write(NToLE(word(2)),2);
  1734. IndexStream.Write(NToLE(word(2048)),2);
  1735. IndexStream.Write(AlwaysX44,sizeof(AlwaysX44));
  1736. IndexStrem.Write (dword(0),2);
  1737. }
  1738. Const DefBlockSize = 2048;
  1739. Type TIndexBlock = Array[0..DefBlockSize-1] of Byte;
  1740. procedure writeword(var p:pbyte;w:word); inline;
  1741. begin
  1742. pword(p)^:=NToLE(w);
  1743. inc(pword(p));
  1744. end;
  1745. procedure writedword(var p:pbyte;d:dword); inline;
  1746. begin
  1747. pdword(p)^:=NToLE(d);
  1748. inc(pdword(p));
  1749. end;
  1750. procedure TChmWriter.AppendBinaryIndexFromSiteMap(ASiteMap: TChmSiteMap;chw:boolean);
  1751. Var
  1752. IndexStream : TMemoryStream;
  1753. //n : Integer;
  1754. curblock : TIndexBlock; // current listing block being built
  1755. TestBlock : TIndexBlock; // each entry is first built here. then moved to curblock
  1756. curind : integer; // next byte to write in testblock.
  1757. blocknr : Integer; // blocknr of block in testblock;
  1758. lastblock : Integer; // blocknr of last block.
  1759. Entries : Integer; // Number of entries in this block so far
  1760. TotalEntries: Integer; // Total number of entries
  1761. MapEntries : Integer;
  1762. MapIndex : Integer;
  1763. indexblocknr: Integer;
  1764. blockind : Integer; // next byte to write in blockn[blocknr]
  1765. blockentries: Integer; // entries so far ins blockn[blocknr]
  1766. blockn : Array Of TIndexBlock;
  1767. BlockNPlus1 : Array of TIndexBlock;
  1768. Mod13value : integer; // A value that is increased by 13 for each entry. (?!?!)
  1769. EntryToIndex: boolean; // helper var to make sure the first block is always indexed.
  1770. blocknplusindex : Integer; // blocks in level n+1 (second part)
  1771. blocknplusentries : Integer; // The other blocks indexed on creation.
  1772. datastream,mapstream,propertystream : TMemoryStream;
  1773. procedure preparecurrentblock(force:boolean);
  1774. var p: PBTreeBlockHeader;
  1775. begin
  1776. {$ifdef binindex}
  1777. writeln('prepcurblock ' ,Entries,' ',lastblock,' ' ,blocknr,' ',indexstream.position);
  1778. {$endif}
  1779. p:=@curblock[0];
  1780. fillchar(p^,sizeof(TBtreeBlockHeader),#0);
  1781. p^.Length:=NToLE(Defblocksize-curind);
  1782. p^.NumberOfEntries:=Entries;
  1783. p^.IndexOfPrevBlock:=cardinal(lastblock); // lastblock can be -1, avoid rangecheck
  1784. p^.IndexOfNextBlock:=Blocknr;
  1785. if force and (blocknr=0) then // only one listblock -> no indexblocks.
  1786. p^.IndexOfNextBlock:=dword(-1);
  1787. IndexStream.Write(curblock[0],Defblocksize);
  1788. fillchar(curblock[0],DefBlockSize,#0);
  1789. MapStream.Write(NToLE(MapEntries),sizeof(dword));
  1790. MapStream.Write(NToLE(BlockNr),Sizeof(DWord));
  1791. MapEntries:=TotalEntries;
  1792. curind:=sizeof(TBtreeBlockHeader); // index into current block;
  1793. lastblock:=blocknr;
  1794. inc(blocknr);
  1795. Entries:=0;
  1796. {$ifdef binindex}
  1797. writeln('prepcurblock post' , indexstream.position);
  1798. {$endif}
  1799. end;
  1800. procedure prepareindexblockn(listingblocknr:integer);
  1801. var p:PBTreeIndexBlockHeader;
  1802. begin
  1803. {$ifdef binindex}
  1804. writeln('prepindexblockn');
  1805. {$endif}
  1806. p:=@Blockn[IndexBlockNr];
  1807. p^.Length:=defblocksize-BlockInd;
  1808. p^.NumberOfEntries:=BlockEntries;
  1809. // p^.IndexOfChildBlock // already entered on block creation, since of first entry, not last.
  1810. inc(Indexblocknr);
  1811. BlockEntries:=0;
  1812. BlockInd:=0;
  1813. if Indexblocknr>=length(blockn) then
  1814. begin
  1815. setlength(blockn,length(blockn)+1); // larger increments also possible. #blocks is kept independantly.
  1816. fillchar(blockn[0][0],sizeof(blockn[0]),#0);
  1817. end;
  1818. p:=@Blockn[IndexBlockNr];
  1819. p^.IndexOfChildBlock:=ListingBlockNr;
  1820. blockind:=sizeof(TBTreeIndexBlockHeader);
  1821. end;
  1822. procedure finalizeindexblockn(p:pbyte;var ind:integer;xEntries:integer);
  1823. var ph:PBTreeIndexBlockHeader;
  1824. begin
  1825. ph:=PBTreeIndexBlockHeader(p);
  1826. ph^.Length:=defblocksize-Ind;
  1827. ph^.NumberOfEntries:=xEntries;
  1828. // p^.IndexOfChildBlock // already entered on block creation, since of first entry, not last.
  1829. // inc(Ind);
  1830. end;
  1831. procedure CurEntryToIndex(entrysize:integer);
  1832. var p,pentry : pbyte;
  1833. indexentrysize : integer;
  1834. begin
  1835. {$ifdef binindex}
  1836. writeln('curentrytoindex ', entrysize);
  1837. {$endif}
  1838. indexentrysize:=entrysize-sizeof(dword); // index entry is 4 bytes shorter, and only the last dword differs
  1839. if (blockind+indexentrysize)>=Defblocksize then
  1840. prepareindexblockn(blocknr);
  1841. p:=@blockn[Indexblocknr][blockind];
  1842. move(testblock[0],p^,indexentrysize);
  1843. pentry:=@p[indexentrysize-sizeof(dword)]; // ptr to last dword
  1844. writedword(pentry,blocknr); // patch up the "index of child field"
  1845. inc(blockind,indexentrysize);
  1846. end;
  1847. procedure WritestrNT(var p:pbyte;const str:Unicodestring);
  1848. var i : integer;
  1849. p2 : pbyte;
  1850. begin
  1851. p2:=p;
  1852. for i:=1 to Length(str) do
  1853. WriteWord(p2,Word(str[i])); // write the wstr in little endian
  1854. WriteWord(p2,0); // NT
  1855. p:=p2;
  1856. end;
  1857. procedure CreateEntry(Item:TChmSiteMapItem;const Str:UnicodeString;commaatposition,level:integer);
  1858. var p : pbyte;
  1859. topicid: integer;
  1860. seealso: Integer;
  1861. entrysize:Integer;
  1862. i : Integer;
  1863. sb :TChmSiteMapSubItem;
  1864. begin
  1865. inc(TotalEntries);
  1866. fillchar(testblock[0],DefBlockSize,#0);
  1867. p:=@TestBlock[0];
  1868. WritestrNT(p,Str);
  1869. if item.seealso='' then // no seealso for now
  1870. seealso:=0
  1871. else
  1872. seealso:=2;
  1873. WriteWord(p,seealso); // =0 not a see also 2 =seealso
  1874. WriteWord(p,level); // Entrydepth. We can't know it, so write 2.
  1875. WriteDword(p,commaatposition); // position of the comma
  1876. WriteDword(p,0); // unused 0
  1877. if seealso=2 then
  1878. begin
  1879. {$ifdef binindex}
  1880. write('!seealso');
  1881. {$endif}
  1882. WriteDword(p,1);
  1883. WritestrNT(p,item.seealso)
  1884. end
  1885. else
  1886. begin
  1887. WriteDword(p,item.SubItemcount);
  1888. for i:=0 to item.SubItemcount-1 do
  1889. begin
  1890. sb:=item.SubItem[i];
  1891. if sb.name='' then
  1892. sb.name:=item.name;
  1893. {$ifdef binindex}
  1894. writeln('---',sb.name,' ',sb.local);
  1895. {$endif}
  1896. TopicId:=AddTopicIndex(sb.Name,sb.Local);
  1897. WriteDword(p,TopicId);
  1898. end;
  1899. end;
  1900. WriteDword(p,1); // always 1 (unknown);
  1901. WriteDword(p,mod13value); //a value that increments with 13.
  1902. mod13value:=mod13value+13;
  1903. entrysize:=p-pbyte(@testblock[0]);
  1904. {$ifdef binindex}
  1905. writeln(curind, ' ',entrysize, ' ',defblocksize);
  1906. writeln('curstr ',str,' ',commaatposition);
  1907. {$endif}
  1908. if (curind+entrysize)>=Defblocksize then
  1909. begin
  1910. {$ifdef binindex}
  1911. writeln('larger!');
  1912. {$endif}
  1913. preparecurrentblock(False);
  1914. EntrytoIndex:=true;
  1915. end;
  1916. if EntryToIndex Then
  1917. begin
  1918. {$ifdef binindex}
  1919. writeln('entrytoindex');
  1920. {$endif}
  1921. CurEntryToIndex(entrysize);
  1922. EntryToIndex:=False;
  1923. end;
  1924. move(testblock[0],curblock[curind],entrysize);
  1925. inc(curind,entrysize);
  1926. datastream.write(DataEntry,Sizeof(DataEntry));
  1927. inc(Entries);
  1928. end;
  1929. procedure MoveIndexEntry(nr:integer;bytes:integer;childblock:integer);
  1930. var
  1931. pscr,pdest : pbyte;
  1932. begin
  1933. {$ifdef binindex}
  1934. writeln(' moveindexentry ',nr,' bytes:',bytes,' childblock:',childblock);
  1935. flush(stdout);
  1936. {$endif}
  1937. if ((blockind+bytes)>=defblocksize) then
  1938. begin
  1939. {$ifdef binindex}
  1940. writeln(' in scalecheck ',blockind);
  1941. flush(stdout);
  1942. {$endif}
  1943. FinalizeIndexBlockn(@blocknplus1[blocknplusindex][0],blockind,blocknplusentries);
  1944. inc(blocknplusindex);
  1945. if blocknplusindex>=length(blocknplus1) then
  1946. begin
  1947. setlength(blocknplus1,length(blocknplus1)+1);
  1948. fillchar(blocknplus1[length(blocknplus1)-1][0],sizeof(blocknplus1[0]),#0);
  1949. end;
  1950. blockInd:=Sizeof(TBTreeIndexBlockHeader);
  1951. pdword(@blocknplus1[blocknplusindex][0])[4]:=NToLE(ChildBlock); /// init 2nd level index to first 1st level index block
  1952. end;
  1953. {$ifdef binindex}
  1954. writeln(' len:',length(blocknplus1),' blockind:',blockind,' index:',blocknplusindex);
  1955. flush(stdout);
  1956. {$endif}
  1957. // copy entry from one indexblock to another
  1958. pscr:=@blockn[nr][sizeof(TBtreeIndexBlockHeader)];
  1959. pdest:=@blocknplus1[blocknplusindex][blockind];
  1960. move(pscr^,pdest^,bytes);
  1961. pdword(@pdest[bytes-sizeof(dword)])^:=NToLE(childblock); // correcting the childindex
  1962. inc (blockind,bytes);
  1963. inc(blocknplusentries); // not needed for writing, but used to check if something has been written. End condition
  1964. end;
  1965. function ScanIndexBlock(blk:Pbyte):Integer;
  1966. var start : pbyte;
  1967. n : Integer;
  1968. i : Integer;
  1969. begin
  1970. start:=@blk[sizeof(TBtreeIndexBlockHeader)];
  1971. blk:=start;
  1972. while pword(blk)^<>0 do // skip wchar
  1973. inc(pword(blk));
  1974. inc(pword(blk)); // skip NT
  1975. inc(pword(blk)); // skip see also
  1976. inc(pword(blk)); // skip depth
  1977. inc(pdword(blk)); // skip Character Index.
  1978. inc(pdword(blk)); // skip always 0
  1979. n:=LEToN(pdword(blk)^);
  1980. inc(pdword(blk)); // skip nr of pairs.
  1981. for i:= 1 to n do
  1982. inc(pdword(blk)); // skip <n> topicids
  1983. inc(pdword(blk)); // skip childindex
  1984. Result:=blk-start;
  1985. end;
  1986. procedure CombineWithChildren(ParentItem:TChmSiteMapItem;Str:UnicodeString;commaatposition:integer;level:integer);
  1987. var i : Integer;
  1988. llItem : TChmSiteMapItem;
  1989. begin
  1990. str:=StringReplace(str, '&x27;', '', [rfReplaceAll]);
  1991. {$ifdef binindex}
  1992. writeln('i:',level,' ',str);
  1993. {$endif}
  1994. // if ParentItem.Children.Count = 0 Then
  1995. // Begin
  1996. // comment/fix next
  1997. // if commatposition=length(str) then commaatposition:=0;
  1998. if level=0 then
  1999. CreateEntry(ParentItem,Str,0,level)
  2000. else
  2001. CreateEntry(ParentItem,Str,commaatposition,level);
  2002. // End
  2003. // Else
  2004. for i:=0 to ParentItem.Children.Count-1 do
  2005. begin
  2006. llitem := TChmSiteMapItem(ParentItem.Children.Item[i]);
  2007. { if level=0 Then
  2008. CombineWithChildren(Item,str+', '+item.text,0,level+1)
  2009. else}
  2010. CombineWithChildren(llItem,Str+', '+llitem.text,length(str)+2,level+1);
  2011. end;
  2012. end;
  2013. Var i : Integer;
  2014. Key : UnicodeString;
  2015. Item : TChmSiteMapItem;
  2016. ListingBlocks : Integer;
  2017. EntryBytes : Integer;
  2018. Hdr : TBTreeHeader;
  2019. TreeDepth : Integer;
  2020. {$ifdef binindex}
  2021. procedure printloopvars(i:integer);
  2022. begin
  2023. Writeln('location :' ,i, ' blocknr :', blocknr,' level:',TreeDepth);
  2024. Writeln('blockn length: ',length(blockn),' indexblocknr: ',indexblocknr,' blockind ',blockind);
  2025. Writeln('blocknplus1 length: ',length(blocknplus1),' blocknplusindex:',blocknplusindex,' entries:',blocknplusentries);
  2026. flush(stdout);
  2027. end;
  2028. {$endif}
  2029. begin
  2030. {$ifdef binindex}
  2031. writeln('starting index');
  2032. {$endif}
  2033. ASiteMap.sort(@indexitemcompare);
  2034. IndexStream:=TMemoryStream.Create;
  2035. indexstream.size:=sizeof(TBTreeHeader);
  2036. IndexStream.position:=Sizeof(TBTreeHeader);
  2037. datastream:=TMemoryStream.Create;
  2038. mapstream :=TMemoryStream.Create;
  2039. mapstream.size:=2;
  2040. mapstream.position:=2;
  2041. propertystream :=TMemoryStream.Create;
  2042. propertystream.write(NToLE(0),sizeof(longint));
  2043. // we iterate over all entries and write listingblocks directly to the stream.
  2044. // and the first (and maybe last) level is written to blockn.
  2045. // we can't do higher levels yet because we don't know how many listblocks we get
  2046. BlockNr :=0; // current block number
  2047. Lastblock :=-1; // previous block nr or -1 if none.
  2048. Entries :=0; // entries in this block
  2049. TotalEntries:=0; // entries so far.
  2050. Mod13value :=0; // value that increments by 13 entirely.
  2051. indexblocknr:=0; // nr of first index block.
  2052. BlockEntries:=0; // entries into current block;
  2053. MapEntries :=0; // entries before the current listing block, for MAP file
  2054. TreeDepth :=0;
  2055. fillchar(testblock[0],DefBlockSize,#0);
  2056. fillchar(curblock[0],DefBlockSize,#0);
  2057. curind :=sizeof(TBTreeBlockHeader); // index into current listing block;
  2058. blockind :=sizeof(TBtreeIndexBlockHeader); // index into current index block
  2059. Setlength(blockn,1);
  2060. fillchar(blockn[0][0],sizeof(blockn[0]),#0);
  2061. pdword(@blockn[0][4])^:=NToLE(0); /// init first listingblock nr to 0 in the first index block
  2062. EntryToIndex := True;
  2063. {$ifdef binindex}
  2064. writeln('items:',asitemap.items.count);
  2065. {$endif}
  2066. for i:=0 to ASiteMap.Items.Count-1 do
  2067. begin
  2068. item := TChmSiteMapItem(ASiteMap.Items.Item[i]);
  2069. key :=Item.Text;
  2070. {$ifdef binindex}
  2071. writeln('item: ',i,' ',key);
  2072. {$endif}
  2073. {$ifdef chm_windowsbinindex}
  2074. // append 2 to all index level 0 entries. This
  2075. // so we can see if Windows loads the binary or textual index.
  2076. CombineWithChildren(Item,Key+'2',length(key)+1,true);
  2077. {$else}
  2078. CombineWithChildren(Item,Key,length(key),0);
  2079. {$endif}
  2080. end;
  2081. PrepareCurrentBlock(True); // flush last listing block.
  2082. Listingblocks:=blocknr; // blocknr is from now on the number of the first block in blockn.
  2083. // we still need the # of listingblocks for the header though
  2084. {$ifdef binindex}
  2085. writeln('binindex: listingblocks : '+inttostr(listingblocks),' indexblocks: ',indexblocknr,' entries:',blockentries);
  2086. {$endif}
  2087. // we have now created and written the listing blocks, and created the first level of index in <blockn>
  2088. // the following loop uses <blockn> to calculate the next level (in blocknplus1), then write out blockn,
  2089. // and repeat until we have no entries left.
  2090. // First we finalize the current set of blocks
  2091. if blocknr>1 then
  2092. begin
  2093. if Blockind<>sizeof(TBtreeIndexBlockHeader) Then
  2094. begin
  2095. {$ifdef binindex}
  2096. writeln('finalizing level 1 index');
  2097. {$endif}
  2098. FinalizeIndexBlockN(@blockn[indexblocknr][0],blockind,blockentries); // also increasing indexblocknr
  2099. inc(IndexBlockNr);
  2100. end;
  2101. {$ifdef binindex}
  2102. writeln('binindex: listingblocks : '+inttostr(listingblocks),' indexblocks: ',indexblocknr,' entries:',blockentries);
  2103. {$endif}
  2104. while (Indexblocknr>1) do
  2105. begin
  2106. {$ifdef binindex}
  2107. printloopvars(1);
  2108. {$endif}
  2109. blockind :=sizeof(TBtreeIndexBlockHeader);
  2110. pdword(@blockn[0][4])^:=NToLE(Listingblocks); /// init 2nd level index to first 1st level index block
  2111. blocknplusindex :=0;
  2112. blocknplusentries :=0;
  2113. if length(blocknplus1)<1 then
  2114. begin
  2115. Setlength(blocknplus1,1);
  2116. fillchar(blocknplus1[0][0],sizeof(blocknplus1[0]),#0);
  2117. end;
  2118. EntryToIndex :=True;
  2119. {$ifdef binindex}
  2120. printloopvars(2);
  2121. {$endif}
  2122. for i:=0 to Indexblocknr-1 do
  2123. begin
  2124. Entrybytes:=ScanIndexBlock(@blockn[i][0]);
  2125. // writeln('after scan ,',i, ' bytes: ',entrybytes,' blocknr:',blocknr,' indexblocknr:',indexblocknr,' to:',blocknr+i);
  2126. MoveIndexEntry(i,Entrybytes,blocknr+i);
  2127. indexStream.Write(blockn[i][0],defblocksize);
  2128. end;
  2129. {$ifdef binindex}
  2130. printloopvars(3);
  2131. {$endif}
  2132. If Blockind<>sizeof(TBtreeIndexBlockHeader) Then
  2133. begin
  2134. {$ifdef binindex}
  2135. logentry('finalizing');
  2136. {$endif}
  2137. FinalizeIndexBlockn(@blocknplus1[blocknplusindex][0],blockind,blocknplusentries);
  2138. inc(blocknplusindex);
  2139. end;
  2140. inc(blocknr,indexblocknr);
  2141. indexblocknr:=blocknplusindex;
  2142. blockn:=copy(blocknplus1); setlength(blocknplus1,1);
  2143. {$ifdef binindex}
  2144. printloopvars(5);
  2145. {$endif}
  2146. inc(TreeDepth);
  2147. end;
  2148. indexStream.Write(blockn[0][0],defblocksize);
  2149. inc(blocknr);
  2150. end;
  2151. // Fixup header.
  2152. hdr.ident[0]:=chr($3B); hdr.ident[1]:=chr($29);
  2153. hdr.flags :=NToLE(word($2)); // bit $2 is always 1, bit $0400 1 if dir? (always on)
  2154. hdr.blocksize :=NToLE(word(defblocksize)); // size of blocks (2048)
  2155. hdr.dataformat :=AlwaysX44; // "X44" always the same, see specs.
  2156. hdr.unknown0 :=NToLE(0); // always 0
  2157. hdr.lastlstblock :=NToLE(dword(ListingBlocks-1)); // index of last listing block in the file;
  2158. hdr.indexrootblock :=NToLE(dword(blocknr-1)); // Index of the root block in the file.
  2159. hdr.unknown1 :=NToLE(dword(-1)); // always -1
  2160. hdr.nrblock :=NToLE(blocknr); // Number of blocks
  2161. hdr.treedepth :=NToLE(word(TreeDepth)); // The depth of the tree of blocks (1 if no index blocks, 2 one level of index blocks, ...)
  2162. hdr.nrkeywords :=NToLE(Totalentries); // number of keywords in the file.
  2163. hdr.codepage :=NToLE(dword(1252)); // Windows code page identifier (usually 1252 - Windows 3.1 US (ANSI))
  2164. hdr.lcid :=NToLE(0); // ???? LCID from the HHP file.
  2165. if not chw then
  2166. hdr.ischm :=NToLE(dword(1)) // 0 if this a BTREE and is part of a CHW file, 1 if it is a BTree and is part of a CHI or CHM file
  2167. else
  2168. hdr.ischm :=NToLE(0);
  2169. hdr.unknown2 :=NToLE(dword(10031)); // Unknown. Almost always 10031. Also 66631 (accessib.chm, ieeula.chm, iesupp.chm, iexplore.chm, msoe.chm, mstask.chm, ratings.chm, wab.chm).
  2170. hdr.unknown3 :=NToLE(0); // unknown 0
  2171. hdr.unknown4 :=NToLE(0); // unknown 0
  2172. hdr.unknown5 :=NToLE(0); // unknown 0
  2173. if totalentries<>0 then
  2174. begin
  2175. // If there are no links of this type in the CHM then this will be a zero DWORD. Othewise it contains the following DWORDs: 0, 0, 0, 0xC, 1, 1, 0, 0. AFAICS this file is pretty much useless.
  2176. // we already have written the first 0 dword
  2177. propertystream.write(NToLE(0),sizeof(longint));
  2178. propertystream.write(NToLE(0),sizeof(longint));
  2179. propertystream.write(NToLE($C),sizeof(longint));
  2180. propertystream.write(NToLE(1),sizeof(longint));
  2181. propertystream.write(NToLE(1),sizeof(longint));
  2182. propertystream.write(NToLE(0),sizeof(longint));
  2183. propertystream.write(NToLE(0),sizeof(longint));
  2184. end;
  2185. IndexStream.Position:=0;
  2186. IndexStream.write(hdr,sizeof(hdr));
  2187. {$ifdef binindex}
  2188. logentry('before append '+inttostr(indexstream.size));
  2189. {$endif}
  2190. AppendBinaryIndexStream(IndexStream,datastream,MapStream,PropertyStream,chw);
  2191. IndexStream.Free;
  2192. PropertyStream.Free;
  2193. MapStream.Free;
  2194. DataStream.Free;
  2195. FHasKLinks:=TotalEntries>0;
  2196. {$ifdef binindex}
  2197. writeln('end index');
  2198. {$endif}
  2199. end;
  2200. procedure TChmWriter.AppendBinaryTOCStream(AStream: TStream);
  2201. begin
  2202. AddStreamToArchive('#TOCIDX', '/', AStream, True);
  2203. end;
  2204. procedure TChmWriter.AppendBinaryIndexStream(IndexStream,DataStream,MapStream,Propertystream: TStream;chw:boolean);
  2205. procedure stadd(fn:string;stream:TStream);
  2206. begin
  2207. Stream.Position:=0;
  2208. if CHW then
  2209. fn:=uppercase(fn);
  2210. {$ifdef binindex}
  2211. logentry('before append '+fn);
  2212. {$endif}
  2213. AddStreamToArchive(fn,'/$WWKeywordLinks/',stream,True);
  2214. end;
  2215. begin
  2216. AddDummyALink;
  2217. stadd('BTree',IndexStream);
  2218. stadd('Data', DataStream);
  2219. stadd('Map' , MapStream);
  2220. stadd('Property', PropertyStream);
  2221. end;
  2222. procedure TChmWriter.AppendIndex(AStream: TStream);
  2223. var tmpstr : string;
  2224. begin
  2225. FHasIndex := True;
  2226. if fIndexName = '' then
  2227. tmpstr:=defaulthhk
  2228. else
  2229. tmpstr:=fIndexName;
  2230. PostAddStreamToArchive(tmpstr, '/', AStream, True);
  2231. end;
  2232. procedure TChmWriter.AppendSearchDB(AName: String; AStream: TStream);
  2233. begin
  2234. PostAddStreamToArchive(AName, '/', AStream);
  2235. end;
  2236. procedure TChmWriter.AddContext(AContext: DWord; ATopic: String);
  2237. var
  2238. Offset: DWord;
  2239. begin
  2240. if FContextStream = nil then begin
  2241. FContextStream:=TMemoryStream.Create;
  2242. // #IVB starts with a dword which is the size of the stream - sizeof(dword)
  2243. FContextStream.WriteDWord(0);
  2244. // we will update this when we write the file to the final stream
  2245. end;
  2246. // an entry is a context id and then the offset of the name of the topic in the strings file
  2247. FContextStream.WriteDWord(NToLE(AContext));
  2248. Offset := NToLE(AddString(ATopic));
  2249. FContextStream.WriteDWord(Offset);
  2250. end;
  2251. procedure TChmWriter.AddDummyALink;
  2252. var stream : TMemoryStream;
  2253. begin
  2254. stream:=tmemorystream.create;
  2255. stream.WriteDWord(0);
  2256. stream.position:=0;
  2257. AddStreamToArchive('Property','/$WWAssociativeLinks/',stream,True);
  2258. stream.free;
  2259. end;
  2260. procedure TChmWriter.Setwindows(AWindowList: TObjectList);
  2261. var i : integer;
  2262. x : TCHMWindow;
  2263. begin
  2264. FWindows.Clear;
  2265. for i:=0 to AWindowList.count -1 do
  2266. begin
  2267. x:=TChmWindow.Create;
  2268. x.assign(TChmWindow(AWindowList[i]));
  2269. Fwindows.Add(x);
  2270. end;
  2271. end;
  2272. procedure TChmWriter.SetMergefiles(src:TStringList);
  2273. var i : integer;
  2274. begin
  2275. FMergeFiles.Clear;
  2276. for i:=0 to Src.count -1 do
  2277. FMergefiles.add(src[i]);
  2278. end;
  2279. end.