|
@@ -28,18 +28,18 @@ unit chmreader;
|
|
interface
|
|
interface
|
|
|
|
|
|
uses
|
|
uses
|
|
- Classes, SysUtils, chmbase, paslzx, chmFIftiMain, chmsitemap;
|
|
|
|
-
|
|
|
|
|
|
+ Classes, SysUtils, Contnrs, chmbase, paslzx, chmFIftiMain, chmsitemap;
|
|
|
|
+
|
|
type
|
|
type
|
|
|
|
|
|
TLZXResetTableArr = array of QWord;
|
|
TLZXResetTableArr = array of QWord;
|
|
-
|
|
|
|
|
|
+
|
|
PContextItem = ^TContextItem;
|
|
PContextItem = ^TContextItem;
|
|
TContextItem = record
|
|
TContextItem = record
|
|
Context: THelpContext;
|
|
Context: THelpContext;
|
|
Url: String;
|
|
Url: String;
|
|
end;
|
|
end;
|
|
-
|
|
|
|
|
|
+
|
|
TContextList = class(TList)
|
|
TContextList = class(TList)
|
|
public
|
|
public
|
|
procedure AddContext(Context: THelpContext; Url: String);
|
|
procedure AddContext(Context: THelpContext; Url: String);
|
|
@@ -54,25 +54,26 @@ type
|
|
protected
|
|
protected
|
|
fStream: TStream;
|
|
fStream: TStream;
|
|
fFreeStreamOnDestroy: Boolean;
|
|
fFreeStreamOnDestroy: Boolean;
|
|
- fChmHeader: TITSFHeader;
|
|
|
|
|
|
+ fITSFHeader: TITSFHeader;
|
|
fHeaderSuffix: TITSFHeaderSuffix;
|
|
fHeaderSuffix: TITSFHeaderSuffix;
|
|
fDirectoryHeader: TITSPHeader;
|
|
fDirectoryHeader: TITSPHeader;
|
|
fDirectoryHeaderPos: QWord;
|
|
fDirectoryHeaderPos: QWord;
|
|
fDirectoryHeaderLength: QWord;
|
|
fDirectoryHeaderLength: QWord;
|
|
fDirectoryEntriesStartPos: QWord;
|
|
fDirectoryEntriesStartPos: QWord;
|
|
- fDirectoryEntries: array of TPMGListChunkEntry;
|
|
|
|
fCachedEntry: TPMGListChunkEntry; //contains the last entry found by ObjectExists
|
|
fCachedEntry: TPMGListChunkEntry; //contains the last entry found by ObjectExists
|
|
fDirectoryEntriesCount: LongWord;
|
|
fDirectoryEntriesCount: LongWord;
|
|
|
|
+ procedure ReadHeader; virtual;
|
|
|
|
+ procedure ReadHeaderEntries; virtual;
|
|
|
|
+ function GetChunkType(Stream: TMemoryStream; ChunkIndex: LongInt): TDirChunkType;
|
|
|
|
+ procedure GetSections(out Sections: TStringList);
|
|
private
|
|
private
|
|
- procedure ReadHeader;
|
|
|
|
- function GetChunkType(Stream: TMemoryStream; ChunkIndex: LongInt): TPMGchunktype;
|
|
|
|
function GetDirectoryChunk(Index: Integer; OutStream: TStream): Integer;
|
|
function GetDirectoryChunk(Index: Integer; OutStream: TStream): Integer;
|
|
function ReadPMGLchunkEntryFromStream(Stream: TMemoryStream; var PMGLEntry: TPMGListChunkEntry): Boolean;
|
|
function ReadPMGLchunkEntryFromStream(Stream: TMemoryStream; var PMGLEntry: TPMGListChunkEntry): Boolean;
|
|
function ReadPMGIchunkEntryFromStream(Stream: TMemoryStream; var PMGIEntry: TPMGIIndexChunkEntry): Boolean;
|
|
function ReadPMGIchunkEntryFromStream(Stream: TMemoryStream; var PMGIEntry: TPMGIIndexChunkEntry): Boolean;
|
|
procedure LookupPMGLchunk(Stream: TMemoryStream; out PMGLChunk: TPMGListChunk);
|
|
procedure LookupPMGLchunk(Stream: TMemoryStream; out PMGLChunk: TPMGListChunk);
|
|
procedure LookupPMGIchunk(Stream: TMemoryStream; out PMGIChunk: TPMGIIndexChunk);
|
|
procedure LookupPMGIchunk(Stream: TMemoryStream; out PMGIChunk: TPMGIIndexChunk);
|
|
|
|
|
|
- procedure GetSections(out Sections: TStringList);
|
|
|
|
|
|
+
|
|
function GetBlockFromSection(SectionPrefix: String; StartPos: QWord; BlockLength: QWord): TMemoryStream;
|
|
function GetBlockFromSection(SectionPrefix: String; StartPos: QWord; BlockLength: QWord): TMemoryStream;
|
|
function FindBlocksFromUnCompressedAddr(var ResetTableEntry: TPMGListChunkEntry;
|
|
function FindBlocksFromUnCompressedAddr(var ResetTableEntry: TPMGListChunkEntry;
|
|
out CompressedSize: QWord; out UnCompressedSize: QWord; out LZXResetTable: TLZXResetTableArr): QWord; // Returns the blocksize
|
|
out CompressedSize: QWord; out UnCompressedSize: QWord; out LZXResetTable: TLZXResetTableArr): QWord; // Returns the blocksize
|
|
@@ -82,13 +83,13 @@ type
|
|
public
|
|
public
|
|
ChmLastError: LongInt;
|
|
ChmLastError: LongInt;
|
|
function IsValidFile: Boolean;
|
|
function IsValidFile: Boolean;
|
|
- procedure GetCompleteFileList(ForEach: TFileEntryForEach);
|
|
|
|
- function ObjectExists(Name: String): QWord; // zero if no. otherwise it is the size of the object
|
|
|
|
|
|
+ procedure GetCompleteFileList(ForEach: TFileEntryForEach; AIncludeInternalFiles: Boolean = True); virtual;
|
|
|
|
+ function ObjectExists(Name: String): QWord; virtual; // zero if no. otherwise it is the size of the object
|
|
// NOTE directories will return zero size even if they exist
|
|
// NOTE directories will return zero size even if they exist
|
|
- function GetObject(Name: String): TMemoryStream; // YOU must Free the stream
|
|
|
|
|
|
+ function GetObject(Name: String): TMemoryStream; virtual; // YOU must Free the stream
|
|
property CachedEntry: TPMGListChunkEntry read fCachedEntry;
|
|
property CachedEntry: TPMGListChunkEntry read fCachedEntry;
|
|
end;
|
|
end;
|
|
-
|
|
|
|
|
|
+
|
|
{ TChmReader }
|
|
{ TChmReader }
|
|
|
|
|
|
TChmReader = class(TITSFReader)
|
|
TChmReader = class(TITSFReader)
|
|
@@ -104,12 +105,16 @@ type
|
|
fURLTBLStream,
|
|
fURLTBLStream,
|
|
fStringsStream: TMemoryStream;
|
|
fStringsStream: TMemoryStream;
|
|
fLocaleID: DWord;
|
|
fLocaleID: DWord;
|
|
|
|
+ fWindowsList : TObjectList;
|
|
|
|
+ fDefaultWindow: String;
|
|
private
|
|
private
|
|
FSearchReader: TChmSearchReader;
|
|
FSearchReader: TChmSearchReader;
|
|
procedure ReadCommonData;
|
|
procedure ReadCommonData;
|
|
function ReadStringsEntry(APosition: DWord): String;
|
|
function ReadStringsEntry(APosition: DWord): String;
|
|
|
|
+ function ReadStringsEntryFromStream ( strm:TStream ) : String;
|
|
function ReadURLSTR(APosition: DWord): String;
|
|
function ReadURLSTR(APosition: DWord): String;
|
|
function CheckCommonStreams: Boolean;
|
|
function CheckCommonStreams: Boolean;
|
|
|
|
+ procedure ReadWindows(mem:TMemoryStream);
|
|
public
|
|
public
|
|
constructor Create(AStream: TStream; FreeStreamOnDestroy: Boolean); override;
|
|
constructor Create(AStream: TStream; FreeStreamOnDestroy: Boolean); override;
|
|
destructor Destroy; override;
|
|
destructor Destroy; override;
|
|
@@ -127,6 +132,8 @@ type
|
|
property LocaleID: dword read fLocaleID;
|
|
property LocaleID: dword read fLocaleID;
|
|
property SearchReader: TChmSearchReader read FSearchReader write FSearchReader;
|
|
property SearchReader: TChmSearchReader read FSearchReader write FSearchReader;
|
|
property contextlist : tcontextlist read fcontextlist;
|
|
property contextlist : tcontextlist read fcontextlist;
|
|
|
|
+ property Windows : TObjectlist read fWindowsList;
|
|
|
|
+ property DefaultWindow : string read fdefaultwindow;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TChmFileList }
|
|
{ TChmFileList }
|
|
@@ -156,7 +163,7 @@ type
|
|
property FileName[Index: Integer]: String read GetFileName;
|
|
property FileName[Index: Integer]: String read GetFileName;
|
|
property OnOpenNewFile: TChmFileOpenEvent read fOnOpenNewFile write SetOnOpenNewFile;
|
|
property OnOpenNewFile: TChmFileOpenEvent read fOnOpenNewFile write SetOnOpenNewFile;
|
|
end;
|
|
end;
|
|
-
|
|
|
|
|
|
+
|
|
//ErrorCodes
|
|
//ErrorCodes
|
|
const
|
|
const
|
|
ERR_NO_ERR = 0;
|
|
ERR_NO_ERR = 0;
|
|
@@ -164,7 +171,7 @@ const
|
|
ERR_NOT_SUPPORTED_VERSION = 2;
|
|
ERR_NOT_SUPPORTED_VERSION = 2;
|
|
ERR_NOT_VALID_FILE = 3;
|
|
ERR_NOT_VALID_FILE = 3;
|
|
ERR_UNKNOWN_ERROR = 10;
|
|
ERR_UNKNOWN_ERROR = 10;
|
|
-
|
|
|
|
|
|
+
|
|
function ChmErrorToStr(Error: Integer): String;
|
|
function ChmErrorToStr(Error: Integer): String;
|
|
|
|
|
|
implementation
|
|
implementation
|
|
@@ -181,7 +188,7 @@ begin
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
-function ChunkType(Stream: TMemoryStream): TPMGchunktype;
|
|
|
|
|
|
+function ChunkType(Stream: TMemoryStream): TDirChunkType;
|
|
var
|
|
var
|
|
ChunkID: array[0..3] of char;
|
|
ChunkID: array[0..3] of char;
|
|
begin
|
|
begin
|
|
@@ -189,39 +196,46 @@ begin
|
|
if Stream.Size< 4 then exit;
|
|
if Stream.Size< 4 then exit;
|
|
Move(Stream.Memory^, ChunkId[0], 4);
|
|
Move(Stream.Memory^, ChunkId[0], 4);
|
|
if ChunkID = 'PMGL' then Result := ctPMGL
|
|
if ChunkID = 'PMGL' then Result := ctPMGL
|
|
- else if ChunkID = 'PMGI' then Result := ctPMGI;
|
|
|
|
|
|
+ else if ChunkID = 'PMGI' then Result := ctPMGI
|
|
|
|
+ else if ChunkID = 'AOLL' then Result := ctAOLL
|
|
|
|
+ else if ChunkID = 'AOLI' then Result := ctAOLI;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TITSFReader }
|
|
{ TITSFReader }
|
|
|
|
|
|
procedure TITSFReader.ReadHeader;
|
|
procedure TITSFReader.ReadHeader;
|
|
-var
|
|
|
|
-fHeaderEntries: array [0..1] of TITSFHeaderEntry;
|
|
|
|
begin
|
|
begin
|
|
- fStream.Position := 0;
|
|
|
|
- fStream.Read(fChmHeader,SizeOf(fChmHeader));
|
|
|
|
|
|
+ fStream.Read(fITSFHeader,SizeOf(fITSFHeader));
|
|
|
|
|
|
// Fix endian issues
|
|
// Fix endian issues
|
|
{$IFDEF ENDIAN_BIG}
|
|
{$IFDEF ENDIAN_BIG}
|
|
- fChmHeader.Version := LEtoN(fChmHeader.Version);
|
|
|
|
- fChmHeader.HeaderLength := LEtoN(fChmHeader.HeaderLength);
|
|
|
|
|
|
+ fITSFHeader.Version := LEtoN(fITSFHeader.Version);
|
|
|
|
+ fITSFHeader.HeaderLength := LEtoN(fITSFHeader.HeaderLength);
|
|
//Unknown_1
|
|
//Unknown_1
|
|
- fChmHeader.TimeStamp := BEtoN(fChmHeader.TimeStamp);//bigendian
|
|
|
|
- fChmHeader.LanguageID := LEtoN(fChmHeader.LanguageID);
|
|
|
|
- //Guid1
|
|
|
|
- //Guid2
|
|
|
|
|
|
+ fITSFHeader.TimeStamp := BEtoN(fITSFHeader.TimeStamp);//bigendian
|
|
|
|
+ fITSFHeader.LanguageID := LEtoN(fITSFHeader.LanguageID);
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
-
|
|
|
|
|
|
+
|
|
|
|
+ if fITSFHeader.Version < 4 then
|
|
|
|
+ fStream.Seek(SizeOf(TGuid)*2, soCurrent);
|
|
|
|
+
|
|
if not IsValidFile then Exit;
|
|
if not IsValidFile then Exit;
|
|
-
|
|
|
|
|
|
+
|
|
|
|
+ ReadHeaderEntries;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TITSFReader.ReadHeaderEntries;
|
|
|
|
+var
|
|
|
|
+fHeaderEntries: array [0..1] of TITSFHeaderEntry;
|
|
|
|
+begin
|
|
// Copy EntryData into memory
|
|
// Copy EntryData into memory
|
|
fStream.Read(fHeaderEntries[0], SizeOf(fHeaderEntries));
|
|
fStream.Read(fHeaderEntries[0], SizeOf(fHeaderEntries));
|
|
|
|
|
|
- if fChmHeader.Version > 2 then
|
|
|
|
|
|
+ if fITSFHeader.Version = 3 then
|
|
fStream.Read(fHeaderSuffix.Offset, SizeOf(QWord));
|
|
fStream.Read(fHeaderSuffix.Offset, SizeOf(QWord));
|
|
fHeaderSuffix.Offset := LEtoN(fHeaderSuffix.Offset);
|
|
fHeaderSuffix.Offset := LEtoN(fHeaderSuffix.Offset);
|
|
// otherwise this is set in fill directory entries
|
|
// otherwise this is set in fill directory entries
|
|
-
|
|
|
|
|
|
+
|
|
fStream.Position := LEtoN(fHeaderEntries[1].PosFromZero);
|
|
fStream.Position := LEtoN(fHeaderEntries[1].PosFromZero);
|
|
fDirectoryHeaderPos := LEtoN(fHeaderEntries[1].PosFromZero);
|
|
fDirectoryHeaderPos := LEtoN(fHeaderEntries[1].PosFromZero);
|
|
fStream.Read(fDirectoryHeader, SizeOf(fDirectoryHeader));
|
|
fStream.Read(fDirectoryHeader, SizeOf(fDirectoryHeader));
|
|
@@ -408,6 +422,7 @@ procedure TChmReader.ReadCommonData;
|
|
fDefaultPage := '/'+ReadString(fStrings);
|
|
fDefaultPage := '/'+ReadString(fStrings);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
+ ReadWindows(FWindows);
|
|
end;
|
|
end;
|
|
procedure ReadContextIds;
|
|
procedure ReadContextIds;
|
|
var
|
|
var
|
|
@@ -439,7 +454,7 @@ begin
|
|
ReadFromSystem;
|
|
ReadFromSystem;
|
|
ReadFromWindows;
|
|
ReadFromWindows;
|
|
ReadContextIds;
|
|
ReadContextIds;
|
|
- {$IFDEF CHM_DEBUG}
|
|
|
|
|
|
+ {$IFDEF CHM_DEBUG}
|
|
WriteLn('TOC=',fTocfile);
|
|
WriteLn('TOC=',fTocfile);
|
|
WriteLn('DefaultPage=',fDefaultPage);
|
|
WriteLn('DefaultPage=',fDefaultPage);
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
@@ -458,6 +473,13 @@ begin
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
+function TChmReader.ReadStringsEntryFromStream ( strm:TStream ) : String;
|
|
|
|
+var APosition : DWord;
|
|
|
|
+begin
|
|
|
|
+ APosition:=LEtoN(strm.ReadDWord);
|
|
|
|
+ result:=ReadStringsEntry(APosition);
|
|
|
|
+end;
|
|
|
|
+
|
|
function TChmReader.ReadURLSTR ( APosition: DWord ) : String;
|
|
function TChmReader.ReadURLSTR ( APosition: DWord ) : String;
|
|
var
|
|
var
|
|
URLStrURLOffset: DWord;
|
|
URLStrURLOffset: DWord;
|
|
@@ -489,18 +511,91 @@ begin
|
|
and (fURLTBLStream <> nil);
|
|
and (fURLTBLStream <> nil);
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
+procedure TChmReader.ReadWindows(mem:TMemoryStream);
|
|
|
|
+
|
|
|
|
+var
|
|
|
|
+ i,cnt,
|
|
|
|
+ version : integer;
|
|
|
|
+ x : TChmWindow;
|
|
|
|
+begin
|
|
|
|
+ if not assigned(fwindowslist) then
|
|
|
|
+ fWindowsList.Clear;
|
|
|
|
+ mem.Position:=0;
|
|
|
|
+ cnt := LEtoN(mem.ReadDWord);
|
|
|
|
+ version := LEtoN(mem.ReadDWord);
|
|
|
|
+ while (cnt>0) do
|
|
|
|
+ begin
|
|
|
|
+ x:=TChmWindow.Create;
|
|
|
|
+ version := LEtoN(mem.ReadDWord); // 0 size of entry.
|
|
|
|
+ mem.readDWord; // 4 unknown (bool Unicodestrings?)
|
|
|
|
+ x.window_type :=ReadStringsEntryFromStream(mem); // 8 Arg 0, name of window
|
|
|
|
+ x.flags := TValidWindowFields(LEtoN(mem.ReadDWord)); // C valid fields
|
|
|
|
+ x.nav_style := LEtoN(mem.ReadDWord); // 10 arg 10 navigation pane style
|
|
|
|
+ x.title_bar_text :=ReadStringsEntryFromStream(mem); // 14 Arg 1, title bar text
|
|
|
|
+ x.styleflags := LEtoN(mem.ReadDWord); // 18 Arg 14, style flags
|
|
|
|
+ x.xtdstyleflags := LEtoN(mem.ReadDWord); // 1C Arg 15, xtd style flags
|
|
|
|
+ x.left := LEtoN(mem.ReadDWord); // 20 Arg 13, rect.left
|
|
|
|
+ x.right := LEtoN(mem.ReadDWord); // 24 Arg 13, rect.top
|
|
|
|
+ x.top := LEtoN(mem.ReadDWord); // 28 Arg 13, rect.right
|
|
|
|
+ x.bottom := LEtoN(mem.ReadDWord); // 2C Arg 13, rect.bottom
|
|
|
|
+ x.window_show_state:= LEtoN(mem.ReadDWord); // 30 Arg 16, window show state
|
|
|
|
+ mem.readdword; // 34 - , HWND hwndhelp OUT: window handle"
|
|
|
|
+ mem.readdword; // 38 - , HWND hwndcaller OUT: who called this window"
|
|
|
|
+ mem.readdword; // 3C - , HH_INFO_TYPE paINFO_TYPES IN: Pointer to an array of Information Types"
|
|
|
|
+ mem.readdword; // 40 - , HWND hwndtoolbar OUT: toolbar window in tri-pane window"
|
|
|
|
+ mem.readdword; // 44 - , HWND hwndnavigation OUT: navigation window in tri-pane window"
|
|
|
|
+ mem.readdword; // 48 - , HWND hwndhtml OUT: window displaying HTML in tri-pane window"
|
|
|
|
+ x.navpanewidth := LEtoN(mem.ReadDWord); // 4C Arg 11, width of nav pane
|
|
|
|
+ mem.readdword; // 50 - , rect.left, OUT:Specifies the coordinates of the Topic pane
|
|
|
|
+ mem.readdword; // 54 - , rect.top , OUT:Specifies the coordinates of the Topic pane
|
|
|
|
+ mem.readdword; // 58 - , rect.right, OUT:Specifies the coordinates of the Topic pane
|
|
|
|
+ mem.readdword; // 5C - , rect.bottom, OUT:Specifies the coordinates of the Topic pane
|
|
|
|
+ x.toc_file :=ReadStringsEntryFromStream(mem); // 60 Arg 2, toc file
|
|
|
|
+ x.index_file :=ReadStringsEntryFromStream(mem); // 64 Arg 3, index file
|
|
|
|
+ x.default_file :=ReadStringsEntryFromStream(mem); // 68 Arg 4, default file
|
|
|
|
+ x.home_button_file :=ReadStringsEntryFromStream(mem); // 6c Arg 5, home button file.
|
|
|
|
+ x.buttons := LEtoN(mem.ReadDWord); // 70 arg 12,
|
|
|
|
+ x.navpane_initially_closed := LEtoN(mem.ReadDWord); // 74 arg 17
|
|
|
|
+ x.navpane_default := LEtoN(mem.ReadDWord); // 78 arg 18,
|
|
|
|
+ x.navpane_location := LEtoN(mem.ReadDWord); // 7C arg 19,
|
|
|
|
+ x.wm_notify_id := LEtoN(mem.ReadDWord); // 80 arg 20,
|
|
|
|
+ for i:=0 to 4 do
|
|
|
|
+ mem.ReadDWord; // 84 - byte[20] unknown - "BYTE tabOrder[HH_MAX_TABS + 1]; // IN/OUT: tab order: Contents, Index, Search, History, Favorites, Reserved 1-5, Custom tabs"
|
|
|
|
+ mem.ReadDWord; // 94 - int cHistory; // IN/OUT: number of history items to keep (default is 30)
|
|
|
|
+ x.jumpbutton_1_text:=ReadStringsEntryFromStream(mem); // 9C Arg 7, The text of the Jump 1 button.
|
|
|
|
+ x.jumpbutton_2_text:=ReadStringsEntryFromStream(mem); // A0 Arg 9, The text of the Jump 2 button.
|
|
|
|
+ x.jumpbutton_1_file:=ReadStringsEntryFromStream(mem); // A4 Arg 6, The file shown for Jump 1 button.
|
|
|
|
+ x.jumpbutton_2_file:=ReadStringsEntryFromStream(mem); // A8 Arg 8, The file shown for Jump 1 button.
|
|
|
|
+ for i:=0 to 3 do
|
|
|
|
+ mem.ReadDWord;
|
|
|
|
+ dec(version,188); // 1.1 specific onesf
|
|
|
|
+ while (version>=4) do
|
|
|
|
+ begin
|
|
|
|
+ mem.readdword;
|
|
|
|
+ dec(version,4);
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ fWindowslist.Add(x);
|
|
|
|
+ dec(cnt);
|
|
|
|
+ end;
|
|
|
|
+end;
|
|
|
|
+
|
|
constructor TChmReader.Create(AStream: TStream; FreeStreamOnDestroy: Boolean);
|
|
constructor TChmReader.Create(AStream: TStream; FreeStreamOnDestroy: Boolean);
|
|
begin
|
|
begin
|
|
|
|
+ fContextList := TContextList.Create;
|
|
|
|
+ fWindowslist := TObjectlist.Create(True);
|
|
|
|
+ fDefaultWindow:='';
|
|
|
|
+
|
|
inherited Create(AStream, FreeStreamOnDestroy);
|
|
inherited Create(AStream, FreeStreamOnDestroy);
|
|
if not IsValidFile then exit;
|
|
if not IsValidFile then exit;
|
|
|
|
|
|
- fContextList := TContextList.Create;
|
|
|
|
ReadCommonData;
|
|
ReadCommonData;
|
|
end;
|
|
end;
|
|
|
|
|
|
destructor TChmReader.Destroy;
|
|
destructor TChmReader.Destroy;
|
|
begin
|
|
begin
|
|
- fContextList.Free;
|
|
|
|
|
|
+ FreeAndNil(fContextList);
|
|
|
|
+ FreeAndNil(FWindowslist);
|
|
FreeAndNil(FSearchReader);
|
|
FreeAndNil(FSearchReader);
|
|
FreeAndNil(fTOPICSStream);
|
|
FreeAndNil(fTOPICSStream);
|
|
FreeAndNil(fURLSTRStream);
|
|
FreeAndNil(fURLSTRStream);
|
|
@@ -509,7 +604,7 @@ begin
|
|
inherited Destroy;
|
|
inherited Destroy;
|
|
end;
|
|
end;
|
|
|
|
|
|
-function TITSFReader.GetChunkType(Stream: TMemoryStream; ChunkIndex: LongInt): TPMGchunktype;
|
|
|
|
|
|
+function TITSFReader.GetChunkType(Stream: TMemoryStream; ChunkIndex: LongInt): TDirChunkType;
|
|
var
|
|
var
|
|
Sig: array[0..3] of char;
|
|
Sig: array[0..3] of char;
|
|
begin
|
|
begin
|
|
@@ -518,7 +613,9 @@ begin
|
|
|
|
|
|
Stream.Read(Sig, 4);
|
|
Stream.Read(Sig, 4);
|
|
if Sig = 'PMGL' then Result := ctPMGL
|
|
if Sig = 'PMGL' then Result := ctPMGL
|
|
- else if Sig = 'PMGI' then Result := ctPMGI;
|
|
|
|
|
|
+ else if Sig = 'PMGI' then Result := ctPMGI
|
|
|
|
+ else if Sig = 'AOLL' then Result := ctAOLL
|
|
|
|
+ else if Sig = 'AOLI' then Result := ctAOLI;
|
|
end;
|
|
end;
|
|
|
|
|
|
function TITSFReader.GetDirectoryChunk(Index: Integer; OutStream: TStream): Integer;
|
|
function TITSFReader.GetDirectoryChunk(Index: Integer; OutStream: TStream): Integer;
|
|
@@ -590,7 +687,7 @@ begin
|
|
|
|
|
|
buf[NameLength] := #0;
|
|
buf[NameLength] := #0;
|
|
PMGIEntry.Name := buf;
|
|
PMGIEntry.Name := buf;
|
|
-
|
|
|
|
|
|
+
|
|
PMGIEntry.ListingChunk := GetCompressedInteger(Stream);
|
|
PMGIEntry.ListingChunk := GetCompressedInteger(Stream);
|
|
if NameLength = 0 then Exit; // failed GetCompressedInteger sanity check
|
|
if NameLength = 0 then Exit; // failed GetCompressedInteger sanity check
|
|
Result := True;
|
|
Result := True;
|
|
@@ -599,6 +696,7 @@ end;
|
|
constructor TITSFReader.Create(AStream: TStream; FreeStreamOnDestroy: Boolean);
|
|
constructor TITSFReader.Create(AStream: TStream; FreeStreamOnDestroy: Boolean);
|
|
begin
|
|
begin
|
|
fStream := AStream;
|
|
fStream := AStream;
|
|
|
|
+ fStream.Position := 0;
|
|
fFreeStreamOnDestroy := FreeStreamOnDestroy;
|
|
fFreeStreamOnDestroy := FreeStreamOnDestroy;
|
|
ReadHeader;
|
|
ReadHeader;
|
|
if not IsValidFile then Exit;
|
|
if not IsValidFile then Exit;
|
|
@@ -606,7 +704,6 @@ end;
|
|
|
|
|
|
destructor TITSFReader.Destroy;
|
|
destructor TITSFReader.Destroy;
|
|
begin
|
|
begin
|
|
- SetLength(fDirectoryEntries, 0);
|
|
|
|
if fFreeStreamOnDestroy then FreeAndNil(fStream);
|
|
if fFreeStreamOnDestroy then FreeAndNil(fStream);
|
|
|
|
|
|
inherited Destroy;
|
|
inherited Destroy;
|
|
@@ -615,13 +712,15 @@ end;
|
|
function TITSFReader.IsValidFile: Boolean;
|
|
function TITSFReader.IsValidFile: Boolean;
|
|
begin
|
|
begin
|
|
if (fStream = nil) then ChmLastError := ERR_STREAM_NOT_ASSIGNED
|
|
if (fStream = nil) then ChmLastError := ERR_STREAM_NOT_ASSIGNED
|
|
- else if (fChmHeader.ITSFsig <> 'ITSF') then ChmLastError := ERR_NOT_VALID_FILE
|
|
|
|
- else if (fChmHeader.Version <> 2) and (fChmHeader.Version <> 3) then
|
|
|
|
|
|
+ else if (fITSFHeader.ITSFsig <> 'ITSF') then ChmLastError := ERR_NOT_VALID_FILE
|
|
|
|
+ //else if (fITSFHeader.Version <> 2) and (fITSFHeader.Version <> 3)
|
|
|
|
+ else if not (fITSFHeader.Version in [2..4])
|
|
|
|
+ then
|
|
ChmLastError := ERR_NOT_SUPPORTED_VERSION;
|
|
ChmLastError := ERR_NOT_SUPPORTED_VERSION;
|
|
Result := ChmLastError = ERR_NO_ERR;
|
|
Result := ChmLastError = ERR_NO_ERR;
|
|
end;
|
|
end;
|
|
|
|
|
|
-procedure TITSFReader.GetCompleteFileList(ForEach: TFileEntryForEach);
|
|
|
|
|
|
+procedure TITSFReader.GetCompleteFileList(ForEach: TFileEntryForEach; AIncludeInternalFiles: Boolean = True);
|
|
var
|
|
var
|
|
ChunkStream: TMemoryStream;
|
|
ChunkStream: TMemoryStream;
|
|
I : Integer;
|
|
I : Integer;
|
|
@@ -662,7 +761,12 @@ begin
|
|
Entry.DecompressedLength := GetCompressedInteger(ChunkStream);
|
|
Entry.DecompressedLength := GetCompressedInteger(ChunkStream);
|
|
if ChunkStream.Position > CutOffPoint then Break; // we have entered the quickref section
|
|
if ChunkStream.Position > CutOffPoint then Break; // we have entered the quickref section
|
|
fCachedEntry := Entry; // if the caller trys to get this data we already know where it is :)
|
|
fCachedEntry := Entry; // if the caller trys to get this data we already know where it is :)
|
|
- ForEach(Entry.Name, Entry.ContentOffset, Entry.DecompressedLength, Entry.ContentSection);
|
|
|
|
|
|
+ if (Length(Entry.Name) = 1)
|
|
|
|
+ or (AIncludeInternalFiles
|
|
|
|
+ or
|
|
|
|
+ ((Length(Entry.Name) > 1) and (not(Entry.Name[2] in ['#','$',':']))))
|
|
|
|
+ then
|
|
|
|
+ ForEach(Entry.Name, Entry.ContentOffset, Entry.DecompressedLength, Entry.ContentSection);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
{$IFDEF CHM_DEBUG_CHUNKS}
|
|
{$IFDEF CHM_DEBUG_CHUNKS}
|
|
@@ -700,7 +804,7 @@ var
|
|
OldPosn := ChunkStream.Position;
|
|
OldPosn := ChunkStream.Position;
|
|
Posn := ChunkStream.Size-SizeOf(Word);
|
|
Posn := ChunkStream.Size-SizeOf(Word);
|
|
ChunkStream.Position := Posn;
|
|
ChunkStream.Position := Posn;
|
|
-
|
|
|
|
|
|
+
|
|
ItemCount := LEToN(ChunkStream.ReadWord);
|
|
ItemCount := LEToN(ChunkStream.ReadWord);
|
|
//WriteLn('Max ITems for next block = ', ItemCount-1);
|
|
//WriteLn('Max ITems for next block = ', ItemCount-1);
|
|
QuickRefCount := ItemCount div (1 + (1 shl fDirectoryHeader.Density));
|
|
QuickRefCount := ItemCount div (1 + (1 shl fDirectoryHeader.Density));
|
|
@@ -728,10 +832,10 @@ var
|
|
var
|
|
var
|
|
PMGLChunk: TPMGListChunk;
|
|
PMGLChunk: TPMGListChunk;
|
|
PMGIChunk: TPMGIIndexChunk;
|
|
PMGIChunk: TPMGIIndexChunk;
|
|
- //ChunkStream: TMemoryStream; declared above
|
|
|
|
|
|
+ //ChunkStream: TMemoryStream; declared above
|
|
Entry: TPMGListChunkEntry;
|
|
Entry: TPMGListChunkEntry;
|
|
NextIndex: Integer;
|
|
NextIndex: Integer;
|
|
- EntryName: String;
|
|
|
|
|
|
+ EntryName: String;
|
|
CRes: Integer;
|
|
CRes: Integer;
|
|
I: Integer;
|
|
I: Integer;
|
|
begin
|
|
begin
|
|
@@ -747,10 +851,10 @@ begin
|
|
ChunkStream := TMemoryStream.Create;
|
|
ChunkStream := TMemoryStream.Create;
|
|
|
|
|
|
try
|
|
try
|
|
-
|
|
|
|
|
|
+
|
|
NextIndex := fDirectoryHeader.IndexOfRootChunk;
|
|
NextIndex := fDirectoryHeader.IndexOfRootChunk;
|
|
if NextIndex < 0 then NextIndex := 0; // no PMGI chunks
|
|
if NextIndex < 0 then NextIndex := 0; // no PMGI chunks
|
|
-
|
|
|
|
|
|
+
|
|
while NextIndex > -1 do begin
|
|
while NextIndex > -1 do begin
|
|
GetDirectoryChunk(NextIndex, ChunkStream);
|
|
GetDirectoryChunk(NextIndex, ChunkStream);
|
|
NextIndex := -1;
|
|
NextIndex := -1;
|
|
@@ -766,7 +870,7 @@ begin
|
|
end;
|
|
end;
|
|
ctPMGI: // we must follow the PMGI tree until we reach a PMGL block
|
|
ctPMGI: // we must follow the PMGI tree until we reach a PMGL block
|
|
begin
|
|
begin
|
|
- LookupPMGIchunk(ChunkStream, PMGIChunk);
|
|
|
|
|
|
+ LookupPMGIchunk(ChunkStream, PMGIChunk);
|
|
|
|
|
|
//QuickRefIndex[0] := ChunkStream.Position;
|
|
//QuickRefIndex[0] := ChunkStream.Position;
|
|
|
|
|
|
@@ -777,13 +881,13 @@ begin
|
|
if ChunkStream.Position >= ChunkStream.Size - PMGIChunk.UnusedSpace then break;
|
|
if ChunkStream.Position >= ChunkStream.Size - PMGIChunk.UnusedSpace then break;
|
|
CRes := ChmCompareText(Name, EntryName);
|
|
CRes := ChmCompareText(Name, EntryName);
|
|
if CRes = 0 then begin
|
|
if CRes = 0 then begin
|
|
- // no more need of this block. onto the next!
|
|
|
|
|
|
+ // no more need of this block. onto the next!
|
|
NextIndex := GetCompressedInteger(ChunkStream);
|
|
NextIndex := GetCompressedInteger(ChunkStream);
|
|
Break;
|
|
Break;
|
|
end;
|
|
end;
|
|
if CRes < 0 then begin
|
|
if CRes < 0 then begin
|
|
if I = 0 then Break; // File doesn't exist
|
|
if I = 0 then Break; // File doesn't exist
|
|
- // file is in previous entry
|
|
|
|
|
|
+ // file is in previous entry
|
|
Break;
|
|
Break;
|
|
end;
|
|
end;
|
|
NextIndex := GetCompressedInteger(ChunkStream);
|
|
NextIndex := GetCompressedInteger(ChunkStream);
|
|
@@ -796,7 +900,7 @@ begin
|
|
QuickRefIndex[0] := ChunkStream.Position;
|
|
QuickRefIndex[0] := ChunkStream.Position;
|
|
I := 0;
|
|
I := 0;
|
|
while ChunkStream.Position <= ChunkStream.Size - PMGLChunk.UnusedSpace do begin
|
|
while ChunkStream.Position <= ChunkStream.Size - PMGLChunk.UnusedSpace do begin
|
|
- // we consume the entry by reading it
|
|
|
|
|
|
+ // we consume the entry by reading it
|
|
Entry.Name := ReadString;
|
|
Entry.Name := ReadString;
|
|
if Entry.Name = '' then break;
|
|
if Entry.Name = '' then break;
|
|
if ChunkStream.Position >= ChunkStream.Size - PMGLChunk.UnusedSpace then break;
|
|
if ChunkStream.Position >= ChunkStream.Size - PMGLChunk.UnusedSpace then break;
|
|
@@ -808,7 +912,7 @@ begin
|
|
CRes := ChmCompareText(Name, Entry.Name);
|
|
CRes := ChmCompareText(Name, Entry.Name);
|
|
if CRes = 0 then begin
|
|
if CRes = 0 then begin
|
|
fCachedEntry := Entry;
|
|
fCachedEntry := Entry;
|
|
- Result := Entry.DecompressedLength;
|
|
|
|
|
|
+ Result := Entry.DecompressedLength;
|
|
Break;
|
|
Break;
|
|
end;
|
|
end;
|
|
Inc(I);
|
|
Inc(I);
|
|
@@ -841,7 +945,7 @@ begin
|
|
end
|
|
end
|
|
else begin // we have to get it from ::DataSpace/Storage/[MSCompressed,Uncompressed]/ControlData
|
|
else begin // we have to get it from ::DataSpace/Storage/[MSCompressed,Uncompressed]/ControlData
|
|
GetSections(SectionNames);
|
|
GetSections(SectionNames);
|
|
- FmtStr(SectionName, '::DataSpace/Storage/%s/',[SectionNames[Entry.ContentSection-1]]);
|
|
|
|
|
|
+ FmtStr(SectionName, '::DataSpace/Storage/%s/',[SectionNames[Entry.ContentSection]]);
|
|
Result := GetBlockFromSection(SectionName, Entry.ContentOffset, Entry.DecompressedLength);
|
|
Result := GetBlockFromSection(SectionName, Entry.ContentOffset, Entry.DecompressedLength);
|
|
SectionNames.Free;
|
|
SectionNames.Free;
|
|
end;
|
|
end;
|
|
@@ -927,7 +1031,7 @@ function TChmReader.GetIndexSitemap(ForceXML:boolean=false): TChmSiteMap;
|
|
var Index : TMemoryStream;
|
|
var Index : TMemoryStream;
|
|
sitemap : TChmSiteMap;
|
|
sitemap : TChmSiteMap;
|
|
Item : TChmSiteMapItem;
|
|
Item : TChmSiteMapItem;
|
|
-
|
|
|
|
|
|
+
|
|
function AbortAndTryTextual:tchmsitemap;
|
|
function AbortAndTryTextual:tchmsitemap;
|
|
|
|
|
|
begin
|
|
begin
|
|
@@ -978,7 +1082,7 @@ begin
|
|
litem.local:=topic;
|
|
litem.local:=topic;
|
|
litem.text :=Title; // recursively split this? No examples.
|
|
litem.text :=Title; // recursively split this? No examples.
|
|
end;
|
|
end;
|
|
- end;
|
|
|
|
|
|
+ end;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure parselistingblock(p:pbyte);
|
|
procedure parselistingblock(p:pbyte);
|
|
@@ -1004,7 +1108,7 @@ begin
|
|
|
|
|
|
tail:=p+(2048-hdr^.length);
|
|
tail:=p+(2048-hdr^.length);
|
|
head:=p+sizeof(TBtreeBlockHeader);
|
|
head:=p+sizeof(TBtreeBlockHeader);
|
|
-
|
|
|
|
|
|
+
|
|
{$ifdef binindex}
|
|
{$ifdef binindex}
|
|
writeln('previndex : ',hdr^.IndexOfPrevBlock);
|
|
writeln('previndex : ',hdr^.IndexOfPrevBlock);
|
|
writeln('nextindex : ',hdr^.IndexOfNextBlock);
|
|
writeln('nextindex : ',hdr^.IndexOfNextBlock);
|
|
@@ -1084,12 +1188,12 @@ begin
|
|
Exit;
|
|
Exit;
|
|
end;
|
|
end;
|
|
SiteMap:=TChmSitemap.Create(StIndex);
|
|
SiteMap:=TChmSitemap.Create(StIndex);
|
|
- Item :=Nil; // cached last created item, in case we need to make
|
|
|
|
|
|
+ Item :=Nil; // cached last created item, in case we need to make
|
|
// a child.
|
|
// a child.
|
|
TryTextual:=True;
|
|
TryTextual:=True;
|
|
BHdr.LastLstBlock:=0;
|
|
BHdr.LastLstBlock:=0;
|
|
if LoadBtreeHeader(index,BHdr) and (BHdr.LastLstBlock>0) Then
|
|
if LoadBtreeHeader(index,BHdr) and (BHdr.LastLstBlock>0) Then
|
|
- begin
|
|
|
|
|
|
+ begin
|
|
if BHdr.BlockSize=defblocksize then
|
|
if BHdr.BlockSize=defblocksize then
|
|
begin
|
|
begin
|
|
for i:=0 to BHdr.lastlstblock do
|
|
for i:=0 to BHdr.lastlstblock do
|
|
@@ -1101,8 +1205,8 @@ begin
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
trytextual:=false;
|
|
trytextual:=false;
|
|
- result:=sitemap;
|
|
|
|
- end;
|
|
|
|
|
|
+ result:=sitemap;
|
|
|
|
+ end;
|
|
end;
|
|
end;
|
|
if trytextual then
|
|
if trytextual then
|
|
begin
|
|
begin
|
|
@@ -1222,7 +1326,7 @@ begin
|
|
//WriteLn('Failed to get ::DataSpace/NameList!');
|
|
//WriteLn('Failed to get ::DataSpace/NameList!');
|
|
exit;
|
|
exit;
|
|
end;
|
|
end;
|
|
-
|
|
|
|
|
|
+
|
|
Stream.Position := 2;
|
|
Stream.Position := 2;
|
|
EntryCount := LEtoN(Stream.ReadWord);
|
|
EntryCount := LEtoN(Stream.ReadWord);
|
|
for X := 0 to EntryCount -1 do begin
|
|
for X := 0 to EntryCount -1 do begin
|
|
@@ -1235,8 +1339,6 @@ begin
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
Sections.Add(WString);
|
|
Sections.Add(WString);
|
|
end;
|
|
end;
|
|
- // the sections are sorted alphabetically, this way section indexes will jive
|
|
|
|
- Sections.Sort;
|
|
|
|
Stream.Free;
|
|
Stream.Free;
|
|
end;
|
|
end;
|
|
|
|
|
|
@@ -1340,7 +1442,7 @@ begin
|
|
ReadCount := ResetTable[X+1] - ResetTable[X];
|
|
ReadCount := ResetTable[X+1] - ResetTable[X];
|
|
|
|
|
|
BlockWriteLength := BlockSize;
|
|
BlockWriteLength := BlockSize;
|
|
-
|
|
|
|
|
|
+
|
|
if FirstBlock = LastBlock then begin
|
|
if FirstBlock = LastBlock then begin
|
|
WriteCount := BlockLength;
|
|
WriteCount := BlockLength;
|
|
end
|
|
end
|
|
@@ -1366,7 +1468,7 @@ begin
|
|
LZXteardown(LZXState);
|
|
LZXteardown(LZXState);
|
|
Exit;
|
|
Exit;
|
|
end;
|
|
end;
|
|
-
|
|
|
|
|
|
+
|
|
// if the next block is an even numbered block we have to reset the decompressor state
|
|
// if the next block is an even numbered block we have to reset the decompressor state
|
|
if (X < LastBlock) and (X and 1 = 1) then LZXreset(LZXState);
|
|
if (X < LastBlock) and (X and 1 = 1) then LZXreset(LZXState);
|
|
|
|
|
|
@@ -1486,7 +1588,7 @@ end;
|
|
function TChmFileList.CheckOpenFile(AFileName: String): Boolean;
|
|
function TChmFileList.CheckOpenFile(AFileName: String): Boolean;
|
|
var
|
|
var
|
|
X: Integer;
|
|
X: Integer;
|
|
-
|
|
|
|
|
|
+
|
|
begin
|
|
begin
|
|
Result := False;
|
|
Result := False;
|
|
for X := 0 to Count-1 do begin
|
|
for X := 0 to Count-1 do begin
|