chmls.lpr 32 KB

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