chmls.lpr 33 KB

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