chmls.lpr 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140
  1. { Copyright (C) <2005> <Andrew Haines> chmls.lpr
  2. Mostly rewritten by Marco van de Voort 2009-2012
  3. An util that concentrates on listing and decompiling various sections
  4. of a CHM.
  5. This library is free software; you can redistribute it and/or modify it
  6. under the terms of the GNU Library General Public License as published by
  7. the Free Software Foundation; either version 2 of the License, or (at your
  8. option) any later version.
  9. This program is distributed in the hope that it will be useful, but WITHOUT
  10. ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11. FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
  12. for more details.
  13. You should have received a copy of the GNU Library General Public License
  14. along with this library; if not, write to the Free Software Foundation,
  15. Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. See the file COPYING, included in this distribution,
  17. for details about the copyright.
  18. }
  19. program chmls;
  20. {$IFDEF MSWINDOWS}
  21. {$apptype console}
  22. {$ENDIF}
  23. {$mode objfpc}{$H+}
  24. {$IFDEF FPC_DOTTEDUNITS}
  25. uses
  26. System.Classes, System.GetOpts, System.SysUtils, System.Types,
  27. Fcl.Streams.Extra, Chm.Reader, Chm.Base, Chm.Sitemap;
  28. {$ELSE FPC_DOTTEDUNITS}
  29. uses
  30. Classes, GetOpts, SysUtils, Types,
  31. StreamEx,
  32. chmreader, chmbase, chmsitemap;
  33. {$ENDIF FPC_DOTTEDUNITS}
  34. {$R-} // CHM spec puts "-1" in dwords etc.
  35. type
  36. { TListObject }
  37. TListObject = class
  38. _Section : Integer;
  39. count : integer;
  40. donotpage: boolean;
  41. nameonly : boolean;
  42. procedure OnFileEntry(Name: RTLString; Offset, UncompressedSize, ASection: Integer);
  43. end;
  44. TExtractAllObject = class
  45. basedir : string;
  46. r : TChmReader;
  47. lastone_was_point : boolean;
  48. procedure OnFileEntry(Name: RTLString; Offset, UncompressedSize, ASection: Integer);
  49. end;
  50. Type
  51. TCmdEnum = (cmdList,cmdExtract,cmdExtractall,cmdUnblock,cmdextractalias,cmdextracttoc,cmdextractindex,cmdprintidxhdr,cmdprintsystem,cmdprintwindows,cmdprinttopics,cmdNone); // One dummy element at the end avoids rangecheck errors.
  52. Const
  53. CmdNames : array [TCmdEnum] of String = ('LIST','EXTRACT','EXTRACTALL','UNBLOCK','EXTRACTALIAS','EXTRACTTOC','EXTRACTINDEX','PRINTIDXHDR','PRINTSYSTEM','PRINTWINDOWS','PRINTTOPICS','');
  54. var
  55. theopts : array[1..5] of TOption;
  56. Procedure Usage;
  57. begin
  58. Writeln(StdErr,'Usage: chmls [switches] [command] [command specific parameters]');
  59. writeln(stderr);
  60. writeln(stderr,'Switches : ');
  61. writeln(stderr,' -h, --help : this screen');
  62. writeln(stderr,' -p, --no-page : do not page list output');
  63. writeln(stderr,' --no-offset : do not show "offset" column in list output');
  64. writeln(stderr,' -n,--name-only : only show "name" column in list output');
  65. writeln(stderr);
  66. writeln(stderr,'Where command is one of the following or if omitted, equal to LIST.');
  67. writeln(stderr,' list <filename> [section number] ');
  68. writeln(stderr,' Shows contents of the archive''s directory');
  69. writeln(stderr,' extract <chm filename> <filename to extract> [saveasname]');
  70. writeln(stderr,' Extracts file "filename to get" from archive "filename",');
  71. writeln(stderr,' and, if specified, saves it to [saveasname]');
  72. writeln(stderr,' extractall <chm filename> [directory]');
  73. writeln(stderr,' Extracts all files from archive "filename" to directory ');
  74. writeln(stderr,' "directory"');
  75. writeln(stderr,' unblockchm <filespec1> [filespec2] ..' );
  76. writeln(stderr,' Mass unblocks (XPsp2+) the relevant CHMs. Multiple files');
  77. writeln(stderr,' and wildcards allowed');
  78. writeln(stderr,' extractalias <chmfilename> [basefilename] [symbolprefix]' );
  79. writeln(stderr,' Extracts context info from file "chmfilename" ');
  80. writeln(stderr,' to a "basefilename".h and "basefilename".ali,');
  81. writeln(stderr,' using symbols "symbolprefix"contextnr');
  82. writeln(stderr,' extracttoc <chmfilename> [filename]');
  83. writeln(stderr,' Extracts the toc (mainly to check binary TOC)');
  84. writeln(stderr,' extractindex <chmfilename> [filename]');
  85. writeln(stderr,' Extracts the index (mainly to check binary index)');
  86. writeln(stderr,' printidxhdr <chmfilename>');
  87. writeln(stderr,' prints #IDXHDR in readable format ');
  88. writeln(stderr,' printsystem <chmfilename>');
  89. writeln(stderr,' prints #SYSTEM in readable format ');
  90. writeln(stderr,' printwindows <chmfilename>');
  91. writeln(stderr,' prints #WINDOWS in readable format ');
  92. writeln(stderr,' printtopics <chmfilename>');
  93. writeln(stderr,' prints #TOPICS in readable format ');
  94. Halt(1);
  95. end;
  96. procedure WrongNrParam(cmd:string;number:integer);
  97. begin
  98. writeln(stderr,' Wrong number of parameters for ',cmd,' ',number);
  99. usage;
  100. halt(1);
  101. end;
  102. procedure InitOptions;
  103. begin
  104. with theopts[1] do
  105. begin
  106. name:='help';
  107. has_arg:=0;
  108. flag:=nil;
  109. value:=#0;
  110. end;
  111. with theopts[2] do
  112. begin
  113. name:='name-only';
  114. has_arg:=0;
  115. flag:=nil;
  116. end;
  117. with theopts[3] do
  118. begin
  119. name:='no-page';
  120. has_arg:=0;
  121. flag:=nil;
  122. end;
  123. with theopts[4] do
  124. begin
  125. name:='no-offset';
  126. has_arg:=0;
  127. flag:=nil;
  128. end;
  129. with theopts[5] do
  130. begin
  131. name:='';
  132. has_arg:=0;
  133. flag:=nil;
  134. end;
  135. end;
  136. procedure WriteStrAdj(Str: String; CharWidth: Integer);
  137. // Changed to WriteStrADJ (for adjust), since 2.4.0 writestr is a builtin
  138. // Why doesn't Write() allow left aligned columns?, sigh.
  139. var
  140. OutString: String;
  141. Len: Integer;
  142. begin
  143. Len := Length(Str);
  144. SetLength(OutString, CharWidth-Len);
  145. FillChar(OutString[1], CharWidth-Len, ' ');
  146. Write(OutString + Str); // to stdout
  147. end;
  148. function craftpath(pth:string;filename:String):string;
  149. var lenpth,lenfn:integer;
  150. pthends,filenameends : Boolean;
  151. begin
  152. lenpth:=length(pth); lenfn :=length(filename);
  153. if lenpth=0 then
  154. exit(filename);
  155. pthends:=false; filenameends:=false;
  156. if (lenpth>0) and (pth[lenpth] in ['/','\']) then
  157. pthends:=true;
  158. if (lenfn>0) and (filename[1] in ['/','\']) then
  159. filenameends:=true;
  160. if pthends and filenameends then
  161. result:=copy(pth,1,lenpth-1)+filename
  162. else
  163. if pthends or filenameends then
  164. result:=pth+filename
  165. else
  166. result:=pth+pathsep+filename;
  167. end;
  168. var donotshowoffset : boolean=false;
  169. procedure TListObject.OnFileEntry(Name: RTLString; Offset, UncompressedSize,
  170. ASection: Integer);
  171. begin
  172. Inc(Count);
  173. if (_Section > -1) and (ASection <> _Section) then Exit;
  174. if (Count = 1) or ((Count mod 40 = 0) and not donotpage) then
  175. begin
  176. Write(StdErr, '<Section> ');
  177. if not donotshowoffset then
  178. Write(StdErr, '<Offset> ');
  179. Writeln(StdErr, '<UnCompSize> <Name>');
  180. end;
  181. if not nameonly then
  182. begin
  183. Write(' ');
  184. Write(ASection);
  185. Write(' ');
  186. if not donotshowoffset then
  187. begin
  188. WriteStrAdj(IntToStr(Offset), 10);
  189. Write(' ');
  190. end;
  191. WriteStrAdj(IntToStr(UncompressedSize), 11);
  192. Write(' ');
  193. end;
  194. WriteLn(Name);
  195. end;
  196. procedure TExtractAllObject.OnFileEntry(Name: RTLString; Offset, UncompressedSize,
  197. ASection: Integer);
  198. var mem : TMemoryStream;
  199. s : String;
  200. len : integer;
  201. procedure wrpoint;
  202. begin
  203. if lastone_was_point then
  204. writeln;
  205. lastone_was_point:=false;
  206. end;
  207. begin
  208. len:=Length(Name);
  209. if ((Len>0) and (name[len]='/')) then
  210. exit; // directory or empty file
  211. if (UncompressedSize=0) Then
  212. begin
  213. WrPoint;
  214. Writeln(stderr,'Skipping empty file ',Name);
  215. exit;
  216. end;
  217. if ((Len>0) and (name[1]=':')) then
  218. begin
  219. WrPoint;
  220. Writeln(stderr,'Skipping internal file : ',Name);
  221. exit;
  222. end;
  223. mem:=r.getobject(name);
  224. if assigned(mem) then
  225. begin
  226. s:=craftpath(basedir,name);
  227. ForceDirectories(extractfiledir(s));
  228. try
  229. mem.savetofile(s);
  230. write('.');
  231. lastone_was_point:=true;
  232. except
  233. on e : exception do
  234. begin
  235. WrPoint;
  236. Writeln(Stderr,'Error saving ',name,' to ',s,'.' );
  237. end;
  238. end;
  239. end
  240. else
  241. begin
  242. Writeln(Stderr,'Can''t extract ',name);
  243. end;
  244. end;
  245. var donotpage:boolean=false;
  246. name_only :boolean=false;
  247. procedure ListChm(Const Name:string;_Section:Integer);
  248. var
  249. ITS: TITSFReader;
  250. Stream: TFileStream;
  251. JunkObject: TListObject;
  252. begin
  253. if not Fileexists(name) then
  254. begin
  255. writeln(stderr,' Can''t find file ',name);
  256. halt(1);
  257. end;
  258. Stream := TFileStream.Create(name, fmOpenRead);
  259. JunkObject := TListObject.Create;
  260. JunkObject._Section:=_Section;
  261. JunkObject.Count:=0;
  262. JunkObject.DoNotPage:=DoNotPage;
  263. JunkObject.NameOnly:=Name_Only;
  264. ITS:= TITSFReader.Create(Stream, True);
  265. ITS.GetCompleteFileList(@JunkObject.OnFileEntry);
  266. WriteLn('Total Files in chm: ', JunkObject.Count);
  267. ITS.Free;
  268. JunkObject.Free;
  269. end;
  270. procedure ExtractFile(chm,readfrom,saveto:string);
  271. var
  272. fs: TFileStream;
  273. m : TMemoryStream;
  274. r : TChmReader;
  275. begin
  276. if not Fileexists(chm) then
  277. begin
  278. writeln(stderr,' Can''t find file ',chm);
  279. halt(1);
  280. end;
  281. if (length(readfrom)>1) and (readfrom[1]<>'/') then
  282. readfrom:='/'+readfrom;
  283. fs:=TFileStream.create(chm,fmOpenRead or fmShareDenyNone);
  284. r:=TChmReader.Create(fs,True);
  285. m:=r.getobject(readfrom);
  286. if assigned(m) then
  287. begin
  288. try
  289. Writeln('Extracting ms-its:/',chm,'::',readfrom,' to ',saveto);
  290. m.savetofile(saveto);
  291. except
  292. on e : exception do
  293. writeln('Can''t write to file ',saveto);
  294. end;
  295. end
  296. else
  297. begin
  298. writeln(stderr,'Can''t find file ',readfrom,' in ',chm);
  299. halt(1);
  300. end;
  301. end;
  302. procedure ExtractFileAll(chm,dirto2:string);
  303. var
  304. fs: TFileStream;
  305. m : TMemoryStream;
  306. r : TChmReader;
  307. fl : boolean;
  308. ListAll : TExtractAllObject;
  309. begin
  310. if not Fileexists(chm) then
  311. begin
  312. writeln(stderr,' Can''t find file ',chm);
  313. halt(1);
  314. end;
  315. if not directoryexists(dirto2) then
  316. begin
  317. fl:=false;
  318. try
  319. mkdir(dirto2);
  320. fl:=directoryexists(dirto2);
  321. except
  322. on e : exception do ;
  323. end;
  324. if not fl then
  325. begin
  326. writeln(stderr,'Directory ',dirto2,' doesn''t exist, and trying to create it fails');
  327. halt(1);
  328. end;
  329. end;
  330. fs:=TFileStream.create(chm,fmOpenRead);
  331. r:=TCHMReader.create(fs,true);
  332. Listall:= TExtractAllObject.Create;
  333. ListAll.basedir:=dirto2;
  334. ListAll.r:=r;
  335. ListAll.lastone_was_point:=false;
  336. r.GetCompleteFileList(@ListAll.OnFileEntry);
  337. r.free;
  338. end;
  339. procedure ExtractAlias(filespec:TStringDynArray);
  340. var s,
  341. chm,
  342. prefixfn,
  343. symbolname : string;
  344. i,cnt: integer;
  345. cl : TList;
  346. x : PcontextItem;
  347. f : textfile;
  348. fs: TFileStream;
  349. r : TChmReader;
  350. begin
  351. symbolname:='helpid';
  352. chm:=filespec[0];
  353. prefixfn:=changefileext(chm,'');
  354. if length(filespec)>1 then
  355. prefixfn:=filespec[1];
  356. if length(filespec)>2 then
  357. symbolname:=filespec[2];
  358. if not Fileexists(chm) then
  359. begin
  360. writeln(stderr,' Can''t find file ',chm);
  361. halt(1);
  362. end;
  363. fs:=TFileStream.create(chm,fmOpenRead);
  364. r:=TCHMReader.create(fs,true);
  365. cl:=r.contextlist;
  366. if assigned(cl) and (cl.count>0) then
  367. begin
  368. cnt:=cl.count;
  369. assignfile(f,changefileext(chm,'.ali'));
  370. rewrite(f);
  371. for i:=0 to cnt-1 do
  372. begin
  373. x:=pcontextitem(cl[i]);
  374. s:=x^.url;
  375. if (length(s)>0) and (s[1]='/') then
  376. delete(s,1,1);
  377. writeln(f,symbolname,x^.context,'=',s);
  378. end;
  379. closefile(f);
  380. assignfile(f,changefileext(chm,'.h'));
  381. rewrite(f);
  382. for i:=0 to cnt-1 do
  383. begin
  384. x:=pcontextitem(cl[i]);
  385. writeln(f,'#define ',symbolname,x^.context,' ',x^.context);
  386. end;
  387. closefile(f);
  388. end;
  389. r.free;
  390. end;
  391. procedure unblockchm(s:string);
  392. var f : file;
  393. begin
  394. writeln('unblocking ',s);
  395. assignfile(f,s+':Zone.Identifier');
  396. rewrite(f,1);
  397. truncate(f);
  398. closefile(f);
  399. end;
  400. procedure populatefiles(files:TStringlist;filespec:string);
  401. var
  402. searchResult : TSearchRec;
  403. begin
  404. if FindFirst(filespec, faAnyFile, searchResult) = 0 then
  405. begin
  406. repeat
  407. files.add(searchresult.name);
  408. until FindNext(searchResult) <> 0;
  409. // Must free up resources used by these successful finds
  410. FindClose(searchResult);
  411. end;
  412. end;
  413. procedure unblockchms(filespec:TStringDynArray);
  414. var files : TStringList;
  415. i : Integer;
  416. begin
  417. files :=TStringList.create;
  418. try
  419. for i:=0 to length(filespec)-1 do
  420. populatefiles(files,filespec[i]);
  421. except
  422. writeln(stderr,'Error while scanning directory ',filespec[i]);
  423. writeln(stderr,'Exiting....');
  424. halt(1);
  425. end;
  426. if files.count>0 then
  427. for i:=0 to files.count-1 do
  428. unblockchm(files[i]);
  429. Files.Free;
  430. end;
  431. procedure readchunk13(m:TMemoryStream;r:TChmReader);
  432. var i,cnt,cnt2: integer;
  433. s : ansistring;
  434. procedure fetchstring;
  435. begin
  436. cnt:=m.ReadDWordLE;
  437. s:='';
  438. if (cnt>0) then
  439. s:=r.readstringsentry(cnt);
  440. end;
  441. var dx : dword;
  442. begin
  443. setlength(s,4);
  444. for i:=1 to 4 do
  445. s[i]:=ansichar(m.readbyte);
  446. Writeln('Identifier tag :',s);
  447. Writeln('Unknown timestamp/checksum :',leton(m.readdword));
  448. Writeln('Always 1 :',leton(m.readdword));
  449. Writeln('Number of topic nodes incl. contents & index :',leton(m.readdword));
  450. Writeln(' The following are mostly parameters of the "text/site properties" object of the sitemap contents');
  451. Writeln('0 (meaning unknown) :',leton(m.readdword));
  452. fetchstring;
  453. Writeln('Imagelist param index in #strings (0,-1=none) :',cnt);
  454. if (cnt>0) then
  455. writeln(' = ',s);
  456. Writeln('0 (meaning unknown) :',leton(m.readdword));
  457. cnt:=m.ReadDWordLE;
  458. if cnt=1 then
  459. s:='Folder'
  460. else
  461. if cnt=0 then
  462. s:='None'
  463. else
  464. s:='unknown value!';
  465. Writeln('imagetype param text/site. :',cnt,' = ',s);
  466. Writeln('Background value :',inttohex(leton(m.readdword),8));
  467. Writeln('Foreground value :',inttohex(leton(m.readdword),8));
  468. fetchstring;
  469. Writeln('Font param index in #strings (0,-1=none) :',cnt);
  470. if (cnt>0) then
  471. writeln(' = ',s);
  472. Writeln('Windows Styles :',inttohex(leton(m.readdword),8));
  473. Writeln('ExWindows Styles :',inttohex(leton(m.readdword),8));
  474. Writeln('Unknown, often -1 or 0 :',leton(m.readdword));
  475. FetchString;
  476. Write ('Framename :',cnt);
  477. if (cnt>0) then
  478. write(' = ',s);
  479. Writeln;
  480. FetchString;
  481. Writeln('Windowname :',cnt);
  482. if (cnt>0) then
  483. writeln(' = ',s);
  484. Writeln('Number of Information Types :',leton(m.readdword));
  485. Writeln('Unknown. Often 1. Also 0, 3. :',leton(m.readdword));
  486. cnt2:=m.ReadDWordLE;
  487. Writeln('Number of files in the [MERGE FILES] list :',cnt2);
  488. dx:=leton(m.readdword);
  489. Writeln('Unknown. Often 0. :',dx,' =$',inttohex(dx,8),'(Non-zero mostly in files with some files in the merge files list)');
  490. if cnt2>0 then
  491. for i:=0 to cnt2-1 do
  492. begin
  493. fetchstring;
  494. Writeln(' Offset ', cnt, ' = ',s);
  495. end;
  496. end;
  497. procedure PrintIDXHDR(filespec:TStringDynArray);
  498. var s,
  499. chm,
  500. prefixfn,
  501. symbolname : ansistring;
  502. i,cnt,cnt2: integer;
  503. cl : TList;
  504. x : PcontextItem;
  505. f : textfile;
  506. fs: TFileStream;
  507. r : TChmReader;
  508. m : TMemorystream;
  509. begin
  510. symbolname:='helpid';
  511. chm:=filespec[0];
  512. prefixfn:=changefileext(chm,RTLString(''));
  513. if not Fileexists(chm) then
  514. begin
  515. writeln(stderr,' Can''t find file ',chm);
  516. halt(1);
  517. end;
  518. fs:=TFileStream.create(chm,fmOpenRead);
  519. r:=TCHMReader.create(fs,true);
  520. m:=r.getobject('/#IDXHDR');
  521. if not assigned(m) then
  522. begin
  523. writeln(stderr,'This CHM doesn''t contain a #IDXHDR internal file');
  524. halt(1);
  525. end;
  526. m.position:=0;
  527. Writeln(' --- #IDXHDR ---');
  528. readchunk13(m,r);
  529. m.free;
  530. r.free;
  531. end;
  532. procedure PrintWindows(filespec:TStringDynArray);
  533. var s,
  534. chm,
  535. prefixfn,
  536. symbolname : ansistring;
  537. i,cnt,cnt2: integer;
  538. x : PcontextItem;
  539. f : textfile;
  540. fs: TFileStream;
  541. r : TChmReader;
  542. m : TMemorystream;
  543. function fetchstring:string;
  544. var xx : longint;
  545. begin
  546. xx:=m.ReadDWordLE;
  547. if (xx>0) then
  548. result:=r.readstringsentry(xx)+ ' (index value = '+inttostr(xx)+')'
  549. else
  550. result:='(0)';
  551. end;
  552. function printstructsize(sz:integer):string;
  553. begin
  554. case sz of
  555. 188 : result:='Compatibility 1.0';
  556. 196 : result:='Compatibility 1.1 or later';
  557. else
  558. result:='unknown';
  559. end;
  560. end;
  561. begin
  562. chm:=filespec[0];
  563. prefixfn:=changefileext(chm,RTLString(''));
  564. if not Fileexists(chm) then
  565. begin
  566. writeln(stderr,' Can''t find file ',chm);
  567. halt(1);
  568. end;
  569. fs:=TFileStream.create(chm,fmOpenRead);
  570. r:=TCHMReader.create(fs,true);
  571. m:=r.getobject('/#WINDOWS');
  572. if not assigned(m) then
  573. begin
  574. writeln(stderr,'This CHM doesn''t contain a #WINDOWS internal file. Odd.');
  575. halt(1);
  576. end;
  577. m.position:=0;
  578. cnt:=m.ReadDWordLE;
  579. Writeln('Entries in #Windows : ',Cnt);
  580. cnt2:=m.ReadDWordLE;
  581. Writeln('Structure size : ',cnt2, ' = ',printstructsize(Cnt2));
  582. writeln;
  583. i:=0;
  584. while (i<cnt) do
  585. begin
  586. cnt2:=m.ReadDWordLE;
  587. Writeln('00 Structure size : ',cnt2, ' = ',printstructsize(Cnt2));
  588. Writeln('04 htmlhelp.h indicates "BOOL fUniCodeStrings: ',m.ReadDWordLE);
  589. Writeln('08 WindowType : ',fetchstring);
  590. cnt2:=m.ReadDWordLE;
  591. Write ('0C Which window properties are valid : ');
  592. if (cnt2 and $00002)>0 then Write(' "Navigation pane style"');
  593. if (cnt2 and $00004)>0 then Write(' "Window style flags"');
  594. if (cnt2 and $00008)>0 then Write(' "Window extended style flags"');
  595. if (cnt2 and $00010)>0 then Write(' "Initial window position"');
  596. if (cnt2 and $00020)>0 then Write(' "Navigation pane width"');
  597. if (cnt2 and $00040)>0 then Write(' "Window show state"');
  598. if (cnt2 and $00080)>0 then Write(' "Info types"');
  599. if (cnt2 and $00100)>0 then Write(' "Buttons"');
  600. if (cnt2 and $00200)>0 then Write(' "Navigation Pane initially closed state"');
  601. if (cnt2 and $00400)>0 then Write(' "Tab position"');
  602. if (cnt2 and $00800)>0 then Write(' "Tab order"');
  603. if (cnt2 and $01000)>0 then Write(' "History count"');
  604. if (cnt2 and $02000)>0 then Write(' "Default Pane"');
  605. writeln(' ( = ',inttohex(cnt2,8),')');
  606. Writeln('10 A bit field of navigation pane styles : ',inttohex(m.readdwordLE,8));
  607. Writeln('14 Title Bar Text : ',fetchstring);
  608. Writeln('18 Style Flags : ',inttohex(m.readdwordLE,8));
  609. Writeln('1C Extended Style Flags : ',inttohex(m.readdwordLE,8));
  610. Writeln('20 Initial position (left,top,right,bottom : ',m.readdwordLE,' ' ,m.readdwordLE,' ',m.readdwordLE,' ',m.readdwordLE);
  611. Writeln('30 Window ShowState : ',inttohex(m.readdwordLE,8));
  612. Writeln('34 HWND hwndHelp; OUT: window handle" : ',inttohex(m.readdwordLE,8));
  613. Writeln('38 HWND hwndCaller; OUT: who called window" : ',inttohex(m.readdwordLE,8));
  614. Writeln('3C HH_INFOTYPE* paInfoTypes : ',inttohex(m.readdwordLE,8));
  615. Writeln('40 HWND hwndToolBar; : ',inttohex(m.readdwordLE,8));
  616. Writeln('44 HWND hwndNavigation; : ',inttohex(m.readdwordLE,8));
  617. Writeln('48 HWND hwndHTML; : ',inttohex(m.readdwordLE,8));
  618. Writeln('4C Width of the navigation pane in pixels : ',inttohex(m.readdwordLE,8));
  619. Writeln('50 Topic panel coordinates left,top,right,bottom : ',m.readdwordLE,' ' ,m.readdwordLE,' ',m.readdwordLE,' ',m.readdwordLE);
  620. Writeln('60 TOC File : ',fetchstring);
  621. Writeln('64 Index File : ',fetchstring);
  622. Writeln('68 Default File : ',fetchstring);
  623. Writeln('6C File when Home button is pressed : ',fetchstring);
  624. inc(i);
  625. end;
  626. m.free;
  627. r.free;
  628. end;
  629. procedure PrintTopics(filespec:TStringDynArray);
  630. var s,
  631. chm,
  632. prefixfn,
  633. symbolname : ansistring;
  634. i,cnt,cnt2: integer;
  635. cl : TList;
  636. x : PcontextItem;
  637. f : textfile;
  638. fs: TFileStream;
  639. r : TChmReader;
  640. m : TMemorystream;
  641. chunktype,
  642. chunksize : Word;
  643. entries : integer;
  644. begin
  645. chm:=filespec[0];
  646. prefixfn:=changefileext(chm,RTLString(''));
  647. if not Fileexists(chm) then
  648. begin
  649. writeln(stderr,' Can''t find file ',chm);
  650. halt(1);
  651. end;
  652. fs:=TFileStream.create(chm,fmOpenRead);
  653. r:=TCHMReader.create(fs,true);
  654. m:=r.getobject('/#TOPICS');
  655. if not assigned(m) then
  656. begin
  657. writeln(stderr,'This CHM doesn''t contain a #SYSTEM internal file. Odd.');
  658. halt(1);
  659. end;
  660. m.position:=0;
  661. entries:=m.size div 16;
  662. if entries>0 then
  663. for i:=0 to entries-1 do
  664. begin
  665. writeln('#TOPICS entry : ',i);
  666. cnt:=m.ReadDWordLE;
  667. writeln(' TOCIDX index:',cnt,5);
  668. write (' Tag name :');
  669. cnt2:=m.ReadDWordLE;
  670. if cnt2=-1 then
  671. writeln(cnt2)
  672. else
  673. begin
  674. s:=r.ReadStringsEntry(cnt2);
  675. writeln(s,'(',cnt2,')');
  676. end;
  677. write (' Tag value :');
  678. cnt2:=m.ReadDWordLE;
  679. if cnt2=-1 then
  680. writeln(cnt2)
  681. else
  682. begin
  683. s:=r.ReadUrlStr(cnt2);
  684. writeln(s,'(',cnt2,')');
  685. end;
  686. cnt2:=m.ReadWordLE;
  687. writeln(' contents val:',cnt2, '(2=not in contents, 6 in contents, 0/4 unknown)');
  688. cnt2:=m.ReadWordLE;
  689. writeln(' unknown val :',cnt2, '(0,2,4,8,10,12,16,32)');
  690. end;
  691. m.free;
  692. r.free;
  693. end;
  694. procedure PrintSystem(filespec:TStringDynArray);
  695. var s,
  696. chm,
  697. prefixfn,
  698. symbolname : ansistring;
  699. i,cnt,cnt2: integer;
  700. cl : TList;
  701. x : PcontextItem;
  702. f : textfile;
  703. fs: TFileStream;
  704. r : TChmReader;
  705. m : TMemorystream;
  706. chunktype,
  707. chunksize : Word;
  708. procedure fetchstring;
  709. begin
  710. cnt:=m.ReadDWordLE;
  711. s:='';
  712. if (cnt>0) then
  713. s:=r.readstringsentry(cnt);
  714. end;
  715. function printnulterminated(sz:word):string;
  716. begin
  717. setlength(result,sz);
  718. if sz>0 then
  719. begin
  720. m.read(result[1],sz);
  721. end;
  722. end;
  723. procedure printentry4(m:TMemoryStream;chsz:dword);
  724. var q : QWord;
  725. ts : TFileTime;
  726. begin
  727. writeln('(4)');
  728. if chsz<32 then
  729. begin
  730. Writeln(' is too small', chsz, ' bytes instead of 32');
  731. m.position:=m.position+chsz;
  732. exit;
  733. end;
  734. writeln(' LCID from HHP file : ',m.readdwordLE );
  735. writeln(' One if DBCS in use : ',m.readdwordLE );
  736. writeln(' one if fullttext search is on : ',m.readdwordLE );
  737. writeln(' Non zero if there are KLinks : ',m.readdwordLE );
  738. writeln(' Non zero if there are ALinks : ',m.readdwordLE );
  739. ts.dwlowdatetime:=m.readdwordLE;
  740. ts.dwhighdatetime:=m.readdwordLE;
  741. writeln(' Timestamp : ',ts.dwhighdatetime,':', ts.dwlowdatetime, ' = $',inttohex(ts.dwhighdatetime,8),': $', inttohex(ts.dwlowdatetime,8));
  742. writeln(' 0/1 except in dsmsdn.chi has 1 : ',m.readdwordLE );
  743. writeln(' 0 (unknown) : ',m.readdwordLE );
  744. end;
  745. procedure printentry8(m:TMemoryStream;chsz:dword);
  746. var q : QWord;
  747. ts : TFileTime;
  748. begin
  749. writeln('(8)');
  750. if chsz<16 then
  751. begin
  752. Writeln(' is too small', chsz, ' bytes instead of 16');
  753. m.position:=m.position+chsz;
  754. exit;
  755. end;
  756. writeln(' 0 (or 4 in some) : ',m.readdwordLE );
  757. fetchstring;
  758. writeln(' Abbreviation : ',cnt,' = ',s);
  759. writeln(' 3 or 5 depending on 1st field : ',m.readdwordLE );
  760. fetchstring;
  761. writeln(' Abbreviation explanation : ',cnt,' = ',s);
  762. if chsz>16 then
  763. writeln(' x size is larger than 16');
  764. m.position:=m.position+chsz-16;
  765. end;
  766. var dx : dword;
  767. begin
  768. symbolname:='helpid';
  769. chm:=filespec[0];
  770. prefixfn:=changefileext(chm,RTLString(''));
  771. if not Fileexists(chm) then
  772. begin
  773. writeln(stderr,' Can''t find file ',chm);
  774. halt(1);
  775. end;
  776. fs:=TFileStream.create(chm,fmOpenRead);
  777. r:=TCHMReader.create(fs,true);
  778. m:=r.getobject('/#SYSTEM');
  779. if not assigned(m) then
  780. begin
  781. writeln(stderr,'This CHM doesn''t contain a #SYSTEM internal file. Odd.');
  782. halt(1);
  783. end;
  784. m.position:=0;
  785. cnt:=m.ReadDWordLE;
  786. case cnt of
  787. 2 : s:='Compatibility 1.0';
  788. 3 : s:='Compatibility 1.1 or later';
  789. else
  790. s:='unknown';
  791. end;
  792. Writeln(' --- #SYSTEM---');
  793. while (m.size-m.position)>=8 do
  794. begin
  795. chunktype := m.readwordle;
  796. Chunksize := m.readwordle;
  797. if (m.size-m.position)>=chunksize then
  798. begin
  799. case chunktype of
  800. 0 : Writeln('(0) Contents file from [options] :',printnulterminated(chunksize));
  801. 1 : Writeln('(1) Index file from [options] :',printnulterminated(chunksize));
  802. 2 : Writeln('(2) Default topic from [options] :',printnulterminated(chunksize));
  803. 3 : Writeln('(3) Title from [options] :',printnulterminated(chunksize));
  804. 4 : printentry4(m,chunksize);
  805. 5 : Writeln('(5) Default Window from [options] :',printnulterminated(chunksize));
  806. 6 : Writeln('(6) Compiled file from [options] :',printnulterminated(chunksize));
  807. 7 : Writeln('(7) DWord when Binary Index is on :',m.readdwordle, '(= entry in #urltbl has same first dword');
  808. 8 : printentry8(m,chunksize);
  809. 9 : Writeln('(9) CHM compiler version :',printnulterminated(chunksize));
  810. 10: begin
  811. dx:=m.readdwordle;
  812. writeln('(10) Timestamp (32-bit?) :',dx,' , = $',inttohex(dx,8));
  813. m.position:=m.position+chunksize-4;
  814. end;
  815. 11: Writeln('(11) DWord when Binary TOC is on :',m.readdwordle, '(= entry in #urltbl has same first dword');
  816. 12: begin
  817. writeln('(12) Number of Information files :',m.readdwordle);
  818. m.position:=m.position+chunksize-4;
  819. end;
  820. 13: begin
  821. cnt:=m.position;
  822. Writeln('(13)');
  823. readchunk13(m,r);
  824. m.position:=chunksize+cnt;
  825. end;
  826. 14: begin
  827. writeln('(14) MS Office related windowing constants ', chunksize,' bytes');
  828. m.position:=m.position+chunksize;
  829. end;
  830. 15: Writeln('(15) Information type checksum :',m.readdwordle,' (Unknown algorithm & data source)');
  831. 16: Writeln('(16) Default Font from [options] :',printnulterminated(chunksize));
  832. else
  833. begin
  834. writeln('Not (yet) handled chunk, type ',chunktype,' of size ',chunksize);
  835. m.position:=m.position+chunksize;
  836. end;
  837. end;
  838. end;
  839. end;
  840. m.free;
  841. r.free;
  842. end;
  843. const
  844. siteext : array[TSiteMapType] of string = ('.hhc','.hhk');
  845. procedure extracttocindex(filespec:TStringDynArray;sttype:TSiteMapType);
  846. var s,
  847. chm,
  848. extractfn : string;
  849. i,cnt: integer;
  850. cl : TList;
  851. f : textfile;
  852. fs: TFileStream;
  853. r : TChmReader;
  854. x : TCHMSitemap;
  855. begin
  856. chm:=filespec[0];
  857. extractfn:=changefileext(chm,siteext[sttype]);
  858. if length(filespec)>1 then
  859. extractfn:=filespec[1];
  860. if not Fileexists(chm) then
  861. begin
  862. writeln(stderr,' Can''t find file ',chm);
  863. halt(1);
  864. end;
  865. fs:=TFileStream.create(chm,fmOpenRead);
  866. r:=TCHMReader.create(fs,true);
  867. case sttype of
  868. stindex: x:=r.GetIndexSitemap(false);
  869. sttoc : x:=r.gettocsitemap(false);
  870. end;
  871. if assigned(x) then
  872. begin
  873. x.savetofile( extractfn);
  874. x.free;
  875. end;
  876. r.free;
  877. end;
  878. procedure buildarglist(var params: TStringDynArray;var cmd :TCmdEnum);
  879. var s : ansistring;
  880. j,k : Integer;
  881. begin
  882. s:=uppercase(paramstr(optind));
  883. cmd:=Low(TCMDEnum);
  884. While (cmd<>high(TCmdEnum)) and (s<>CmdNames[cmd]) do
  885. inc(cmd);
  886. if cmd=CmdNone then
  887. begin
  888. writeln(stderr,' Using cmdls without command is deprecated, this may be removed in a future version');
  889. writeln(stderr,' Please consider using the "list" command');
  890. cmd:=CmdList; // no cmd found -> list In the future we can also do a name check here for the default (symlink on unix)
  891. k:=optind;
  892. end
  893. else
  894. begin
  895. k:=optind+1;
  896. end;
  897. setlength(params,paramcount-k+1);
  898. for j:=k to paramcount do
  899. params[j-k]:=paramstr(j);
  900. end;
  901. Var
  902. LocalParams : TStringDynArray;
  903. c: char;
  904. i,
  905. Params,
  906. OptionIndex : Longint;
  907. cmd : TCmdEnum;
  908. _section : Integer = -1;
  909. // Start of program
  910. begin
  911. InitOptions;
  912. Writeln(stderr,'chmls, a CHM utility. (c) 2010 Free Pascal core.');
  913. Writeln(Stderr);
  914. repeat
  915. c:=getlongopts('hnp',@theopts[1],optionindex);
  916. case c of
  917. #0 : begin
  918. case optionindex-1 of
  919. 0 : begin;
  920. Usage;
  921. Halt;
  922. end;
  923. 1 : name_only:=true;
  924. 2 : donotpage:=true;
  925. 3 : donotshowoffset:=true;
  926. end;
  927. end;
  928. 'p' : donotpage:=true;
  929. 'n' : name_only:=true;
  930. '?','h' :
  931. begin
  932. writeln('unknown option',optopt);
  933. usage;
  934. halt;
  935. end;
  936. end; { case }
  937. until c=endofoptions;
  938. params:=Paramcount-optind+1;
  939. if params>0 then
  940. begin
  941. BuildArgList(localparams,cmd);
  942. case cmd of
  943. cmdlist : begin
  944. case length(localparams) of
  945. 1 : ListChm(localparams[0],_Section);
  946. 2 : begin
  947. if not TryStrToInt(localparams[1],_section) then
  948. begin
  949. writeln(stderr,' Invalid value for section ',localparams[2]);
  950. usage;
  951. halt(1);
  952. end;
  953. ListChm(localparams[0],_Section);
  954. end;
  955. else
  956. WrongNrParam(cmdnames[cmd],length(localparams));
  957. end; {case}
  958. end; { cmdlist}
  959. cmdextract : begin
  960. case length(localparams) of
  961. 2: ExtractFile(localparams[0],localparams[1],extractfilename(localparams[1]));
  962. 3: ExtractFile(localparams[0],localparams[1],localparams[2]);
  963. else
  964. WrongNrParam(cmdnames[cmd],length(localparams));
  965. end;
  966. end;
  967. cmdextractall: begin
  968. if length(localparams)=1 then //extract into current directory
  969. ExtractFileAll(localparams[0],GetCurrentDir)
  970. else if length(localparams)=2 then //extract into specified dir
  971. ExtractFileall(localparams[0],localparams[1])
  972. else
  973. WrongNrParam(cmdnames[cmd],length(localparams));
  974. end;
  975. cmdunblock : begin
  976. if length(localparams)>0 then
  977. Unblockchms(localparams)
  978. else
  979. WrongNrParam(cmdnames[cmd],length(localparams));
  980. end;
  981. cmdextractalias: begin
  982. if length(localparams)>0 then
  983. extractalias(localparams)
  984. else
  985. WrongNrParam(cmdnames[cmd],length(localparams));
  986. end;
  987. cmdextracttoc : begin
  988. if length(localparams)>0 then
  989. extracttocindex(localparams,sttoc)
  990. else
  991. WrongNrParam(cmdnames[cmd],length(localparams));
  992. end;
  993. cmdextractindex: begin
  994. if length(localparams)>0 then
  995. extracttocindex(localparams,stindex)
  996. else
  997. WrongNrParam(cmdnames[cmd],length(localparams));
  998. end;
  999. cmdprintidxhdr: begin
  1000. if length(localparams)=1 then
  1001. printidxhdr(localparams)
  1002. else
  1003. WrongNrParam(cmdnames[cmd],length(localparams));
  1004. end;
  1005. cmdprintsystem : begin
  1006. if length(localparams)=1 then
  1007. printsystem(localparams)
  1008. else
  1009. WrongNrParam(cmdnames[cmd],length(localparams));
  1010. end;
  1011. cmdprintwindows : begin
  1012. if length(localparams)=1 then
  1013. printwindows(localparams)
  1014. else
  1015. WrongNrParam(cmdnames[cmd],length(localparams));
  1016. end;
  1017. cmdprinttopics : begin
  1018. if length(localparams)=1 then
  1019. printtopics(localparams)
  1020. else
  1021. WrongNrParam(cmdnames[cmd],length(localparams));
  1022. end;
  1023. end; {case cmd of}
  1024. end
  1025. else
  1026. begin
  1027. Usage;
  1028. halt;
  1029. end;
  1030. end.