
git-svn-id: https://svn.code.sf.net/p/nsis/code/NSIS/trunk@4271 212acab6-be3b-0410-9dea-997c60f758d6
548 lines
No EOL
13 KiB
ObjectPascal
548 lines
No EOL
13 KiB
ObjectPascal
unit PatchClasses;
|
|
|
|
interface
|
|
|
|
uses Classes, sysutils, VDSP_CRC, DLLWrapper, Dialogs;
|
|
|
|
const
|
|
DEFAULT_CONFIG = '64';
|
|
|
|
type
|
|
TAbstractFile = record
|
|
FileName: String;
|
|
FriendlyName: String;
|
|
CRC32: LongWord; //the longword/integer sign is going to give problems again...
|
|
Size: Integer;
|
|
//not sure about this one yet...
|
|
Cached: Boolean; //True: we have cached the patch, using latest config
|
|
//False: a) we have nothing cached (size and start are -1)
|
|
// b) we still have cache (start>0 and size too), but it's not generated using the latest config (we can keep it of course because the new config might be worse)
|
|
Cache: TMemoryStream;
|
|
end;
|
|
|
|
TPatchFile = class (TObject)
|
|
private
|
|
FIndex: Integer;
|
|
ConfigID: String;
|
|
FNew: TAbstractFile;
|
|
FOld: Array of TAbstractFile;
|
|
protected
|
|
procedure SetNewFN(Value: String);
|
|
function GetNewFN: String;
|
|
procedure SetOldFN(i: Integer; FileName: String);
|
|
function GetOldFN(Index: Integer): String;
|
|
function GetOldVersionCount: Integer;
|
|
procedure ResetCache; overload;
|
|
procedure ResetCache(OldIndex: Integer); overload;
|
|
procedure InvalidateCache; overload;
|
|
procedure InvalidateCache(Index: Integer); overload;
|
|
function GetCached(Index: Integer): Boolean;
|
|
function GetConfig: String;
|
|
procedure SetConfig(Value: String);
|
|
public
|
|
constructor Create(Index: Integer; FileName: String); overload;
|
|
constructor Create(Index: Integer; Stream: TStream); overload;
|
|
destructor Destroy(); override;
|
|
|
|
procedure AddOldVersion(const FileName: String);
|
|
procedure RemoveOldVersion(const Index: Integer);
|
|
property OldVersions[Index: Integer]: String read GetOldFN write SetOldFN;
|
|
|
|
procedure Generate; overload;
|
|
procedure Generate(const Index: Integer); overload;
|
|
property Generated[Index: Integer]: Boolean read GetCached;
|
|
function GetPatchSize(Index: Integer): Integer;
|
|
|
|
procedure WritePatch(Index: Integer; Stream: TStream);
|
|
|
|
// LoadFromStream not supported: Use Create(Index,Stream) instead!
|
|
// procedure LoadFromStream(Stream: TStream);
|
|
procedure SaveToStream(Stream: TStream);
|
|
published
|
|
property NewVersion: String read GetNewFN write SetNewFN;
|
|
property OldVersionCount: Integer read GetOldVersionCount;
|
|
property Index: Integer read FIndex;
|
|
property Config: String read GetConfig write SetConfig;
|
|
end;
|
|
|
|
TPatchProject = class (TObject)
|
|
private
|
|
FPat: Array of TPatchFile;
|
|
public
|
|
procedure LoadFromStream(Stream: TStream);
|
|
procedure SaveToStream(Stream: TStream);
|
|
constructor Create();
|
|
destructor Destroy(); override;
|
|
procedure AddNewVersion(FileName: String);
|
|
function PatchFile(FileName: String): TPatchFile; overload;
|
|
function PatchFile(Index: Integer): TPatchFile; overload;
|
|
function GetPatchCount: Integer;
|
|
procedure WritePatches(Stream: TStream);
|
|
procedure Generate;
|
|
procedure ResetCache;
|
|
end;
|
|
|
|
implementation
|
|
|
|
function ReadStreamString(Stream: TStream): String;
|
|
var
|
|
Buf: Array[0..512] of Char;
|
|
i: LongInt;
|
|
S: String;
|
|
j: Integer;
|
|
begin
|
|
Stream.Read(i,SizeOf(i));
|
|
if i>512 then raise Exception.Create('VPJ damaged: String too long (>512)');
|
|
Stream.Read(Buf,i);
|
|
for j:=1 to i do
|
|
S:=S+Buf[j-1];
|
|
ReadStreamString:=S;
|
|
end;
|
|
|
|
//a private wrapper for the FileCRC function
|
|
function CalcCRC(FileName: String): Integer;
|
|
var
|
|
fs: TFileStream;
|
|
begin
|
|
CalcCRC:=0;
|
|
fs:=nil;
|
|
try
|
|
fs:=TFileStream.Create(FileName,fmOpenRead);
|
|
CalcCRC:=FileCRC(fs);
|
|
finally
|
|
fs.Free;
|
|
end;
|
|
end;
|
|
|
|
function GetFileSize(FileName: String): Integer;
|
|
var
|
|
fs: TFileStream;
|
|
begin
|
|
GetFileSize:=0;
|
|
fs:=nil;
|
|
try
|
|
fs:=TFileStream.Create(FileName,fmOpenRead);
|
|
GetFileSize:=fs.Size;
|
|
finally
|
|
fs.Free;
|
|
end;
|
|
end;
|
|
|
|
{ TPatchFile }
|
|
|
|
procedure TPatchFile.AddOldVersion(const FileName: String);
|
|
var
|
|
i: Integer;
|
|
// fs: TFileStream;
|
|
begin
|
|
i:=Length(FOld);
|
|
SetLength(FOld,i+1);
|
|
FOld[i].Cache:=TMemoryStream.Create;
|
|
SetOldFN(i,FileName);
|
|
end;
|
|
|
|
constructor TPatchFile.Create(Index: Integer; FileName: String);
|
|
//var
|
|
// fs: TFileStream;
|
|
begin
|
|
inherited Create();
|
|
FIndex:=Index;
|
|
SetLength(FOld,0);
|
|
FNew.CRC32:=0;
|
|
FNew.Size:=-1;
|
|
SetNewFN(FileName);
|
|
ConfigID:=DEFAULT_CONFIG;
|
|
//just to be on the safe side
|
|
//following is now done by SetNewFN :)
|
|
//no it's not - because that one resets the cache!!!
|
|
//doesn't matter, because we're not loading from stream!!!
|
|
{ FNew.FileName:=FileName;
|
|
FNew.FriendlyName:=ExtractFileName(FileName);
|
|
FNew.CRC32:=CalcCRC(FileName);
|
|
FNew.Size:=GetFileSize(FileName);}
|
|
end;
|
|
|
|
constructor TPatchFile.Create(Index: Integer; Stream: TStream);
|
|
var
|
|
i,q: LongInt;
|
|
CSize: Integer;
|
|
j: Integer;
|
|
begin
|
|
inherited Create();
|
|
FIndex:=Index;
|
|
SetLength(FOld,0);
|
|
FNew.CRC32:=0;
|
|
FNew.Size:=-1; //just to be on the safe side
|
|
|
|
//read configuration
|
|
ConfigID:=ReadStreamString(Stream);
|
|
|
|
//now load everything...
|
|
FNew.FileName:=ReadStreamString(Stream);
|
|
FNew.FriendlyName:=ReadStreamString(Stream);
|
|
Stream.Read(FNew.CRC32,SizeOf(FNew.CRC32));
|
|
Stream.Read(FNew.Size,SizeOf(FNew.Size));
|
|
Stream.Read(i,SizeOf(i));
|
|
SetLength(FOld,i);
|
|
for j:=0 to i - 1 do begin
|
|
FOld[j].FileName:=ReadStreamString(Stream);
|
|
FOld[j].FriendlyName:=ReadStreamString(Stream);
|
|
Stream.Read(FOld[j].CRC32,SizeOf(FOld[j].CRC32));
|
|
Stream.Read(FOld[j].Size,SizeOf(FOld[j].Size));
|
|
Stream.Read(q,SizeOf(q));
|
|
FOld[j].Cached:=not (q=0);
|
|
if FOld[j].Cached then begin
|
|
Stream.Read(CSize,SizeOf(CSize));
|
|
FOld[j].Cache:=TMemoryStream.Create;
|
|
FOld[j].Cache.CopyFrom(Stream,CSize);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
destructor TPatchFile.Destroy;
|
|
begin
|
|
SetLength(FOld,0);
|
|
inherited;
|
|
end;
|
|
|
|
function TPatchFile.GetNewFN: String;
|
|
begin
|
|
GetNewFN:=FNew.FileName;
|
|
end;
|
|
|
|
function TPatchFile.GetOldFN(Index: Integer): String;
|
|
begin
|
|
Result:=FOld[Index].FileName;
|
|
if FOld[Index].Cached then
|
|
if FOld[Index].Cache.Size>0 then begin
|
|
Result:=Result + ' ('+IntToStr(FOld[Index].Cache.Size)+' bytes to patch)';
|
|
end;
|
|
end;
|
|
|
|
function TPatchFile.GetOldVersionCount: Integer;
|
|
begin
|
|
GetOldVersionCount:=Length(FOld);
|
|
end;
|
|
|
|
procedure TPatchFile.ResetCache;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i:=0 to Length(FOld)-1 do
|
|
ResetCache(i);
|
|
end;
|
|
|
|
procedure TPatchFile.RemoveOldVersion(const Index: Integer);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
FOld[Index].Cache.Free;
|
|
for i:=Index to Length(FOld)-2 do begin
|
|
FOld[i]:=FOld[i+1];
|
|
end;
|
|
SetLength(FOld,Length(FOld)-1);
|
|
end;
|
|
|
|
procedure TPatchFile.ResetCache(OldIndex: Integer);
|
|
begin
|
|
FOld[OldIndex].Cached:=False;
|
|
FOld[OldIndex].Size:=-1;
|
|
FOld[OldIndex].Cache.Clear;
|
|
end;
|
|
|
|
procedure TPatchFile.SaveToStream(Stream: TStream);
|
|
procedure WriteStreamString(Stream: TStream; const S: String);
|
|
var
|
|
i: LongInt;
|
|
j: Integer;
|
|
Buf: Array[0..512] of Char;
|
|
begin
|
|
i:=Length(S);
|
|
Stream.Write(i,SizeOf(i));
|
|
for j:=1 to i do
|
|
Buf[j-1]:=S[j];
|
|
Buf[i]:=#0;
|
|
Stream.Write(Buf,i);
|
|
end;
|
|
var
|
|
i,q: LongInt;
|
|
j: Integer;
|
|
tmp: Integer;
|
|
begin
|
|
//write config ID
|
|
WriteStreamString(Stream,ConfigID);
|
|
|
|
WriteStreamString(Stream,FNew.FileName);
|
|
WriteStreamString(Stream,FNew.FriendlyName);
|
|
Stream.Write(FNew.CRC32,SizeOf(FNew.CRC32));
|
|
Stream.Write(FNew.Size,SizeOf(FNew.Size));
|
|
|
|
i:=Length(FOld);
|
|
Stream.Write(i,SizeOf(i));
|
|
|
|
for j:=0 to i - 1 do begin
|
|
WriteStreamString(Stream,FOld[j].FileName);
|
|
WriteStreamString(Stream,FOld[j].FriendlyName);
|
|
Stream.Write(FOld[j].CRC32,SizeOf(FOld[j].CRC32));
|
|
Stream.Write(FOld[j].Size,SizeOf(FOld[j].Size));
|
|
if FOld[j].Cached then q:=1 else q:=0;
|
|
Stream.Write(q,SizeOf(q));
|
|
if FOld[j].Cached then begin
|
|
tmp:=FOld[j].Cache.Size;
|
|
Stream.Write(tmp,SizeOf(tmp));
|
|
FOld[j].Cache.Seek(0,soFromBeginning);
|
|
Stream.CopyFrom(FOld[j].Cache,tmp);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TPatchFile.SetNewFN(Value: String);
|
|
var
|
|
NewSize: Integer;
|
|
NewCRC: LongWord;
|
|
begin
|
|
FNew.FileName:=Value;
|
|
FNew.Friendlyname:=ExtractFileName(Value);
|
|
NewCRC:=CalcCRC(Value);
|
|
NewSize:=GetFileSize(Value);
|
|
//if any changes, then reset cache :)
|
|
if not ((FNew.CRC32=NewCRC) and (FNew.Size=NewSize)) then begin
|
|
FNew.CRC32:=NewCRC;
|
|
FNew.Size:=NewSize;
|
|
ResetCache;
|
|
end;
|
|
end;
|
|
|
|
procedure TPatchFile.SetOldFN(i: Integer; FileName: String);
|
|
begin
|
|
if((i>=0) and (i<Length(FOld))) then begin
|
|
FOld[i].FileName:=FileName;
|
|
FOld[i].FriendlyName:=ExtractFileName(FileName);
|
|
FOld[i].CRC32:=CalcCRC(FileName);
|
|
FOld[i].Size:=GetFileSize(FileName);
|
|
ResetCache(i);
|
|
end;
|
|
end;
|
|
|
|
procedure TPatchFile.Generate;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
//generate all of them into the cache?
|
|
for i:=0 to OldVersionCount - 1 do
|
|
Generate(i);
|
|
end;
|
|
|
|
procedure TPatchFile.Generate(const Index: Integer);
|
|
var
|
|
Size: Integer;
|
|
fm: TMemoryStream;
|
|
begin
|
|
fm:=TMemoryStream.Create;
|
|
Size:=DoGenerate(FOld[Index].FileName,FNew.FileName,fm,ConfigID);
|
|
if not (Size=-1) then begin
|
|
if (FOld[Index].Cache.Size>Size) or (not FOld[Index].Cached) then begin //the new one is better
|
|
FOld[Index].Cache.Clear;
|
|
fm.Seek(8,soFromBeginning);
|
|
FOld[Index].Cache.CopyFrom(fm,fm.Size-8);
|
|
end;
|
|
FOld[Index].Cached:=True;
|
|
end;
|
|
fm.Free;
|
|
end;
|
|
|
|
function TPatchFile.GetCached(Index: Integer): Boolean;
|
|
begin
|
|
GetCached:=FOld[Index].Cached;
|
|
end;
|
|
|
|
function TPatchFile.GetConfig: String;
|
|
begin
|
|
GetConfig:=ConfigID;
|
|
end;
|
|
|
|
procedure TPatchFile.SetConfig(Value: String);
|
|
begin
|
|
if not Assigned(Self) then Exit;
|
|
if not SameText(Value,ConfigID) then begin
|
|
InvalidateCache; //configuration changed, invalidate cache
|
|
end;
|
|
ConfigID:=Value;
|
|
end;
|
|
|
|
function TPatchFile.GetPatchSize(Index: Integer): Integer;
|
|
begin
|
|
if Generated[Index] then begin
|
|
GetPatchSize:=FOld[Index].Cache.Size;
|
|
end else
|
|
GetPatchSize:=-1;
|
|
end;
|
|
|
|
procedure TPatchFile.InvalidateCache;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i:=0 to Length(FOld)-1 do
|
|
InvalidateCache(i);
|
|
end;
|
|
|
|
procedure TPatchFile.InvalidateCache(Index: Integer);
|
|
begin
|
|
FOld[Index].Cached:=False;
|
|
end;
|
|
|
|
procedure TPatchFile.WritePatch(Index: Integer; Stream: TStream);
|
|
begin
|
|
if not FOld[Index].Cached then
|
|
Generate(Index);
|
|
if not FOld[Index].Cached then
|
|
raise Exception.Create('Writing of patch failed: Could not generate all patches');
|
|
FOld[Index].Cache.Seek(0,soFromBeginning);
|
|
Stream.CopyFrom(FOld[Index].Cache,FOld[Index].Cache.Size);
|
|
end;
|
|
|
|
{ TPatchProject }
|
|
|
|
procedure TPatchProject.AddNewVersion(FileName: String);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
i:=Length(FPat);
|
|
SetLength(FPat,i+1);
|
|
FPat[i]:=TPatchFile.Create(i,FileName);
|
|
FPat[i].SetConfig(DEFAULT_CONFIG);
|
|
end;
|
|
|
|
constructor TPatchProject.Create;
|
|
begin
|
|
inherited;
|
|
SetLength(FPat,0);
|
|
end;
|
|
|
|
destructor TPatchProject.Destroy;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i:=0 to Length(FPat)-1 do begin
|
|
FPat[i].Free;
|
|
end;
|
|
SetLength(FPat,0);
|
|
inherited;
|
|
end;
|
|
|
|
procedure TPatchProject.Generate;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i:=0 to GetPatchCount - 1 do
|
|
FPat[i].Generate;
|
|
end;
|
|
|
|
function TPatchProject.GetPatchCount: Integer;
|
|
begin
|
|
GetPatchCount:=Length(FPat);
|
|
end;
|
|
|
|
procedure TPatchProject.LoadFromStream(Stream: TStream);
|
|
var
|
|
MagicWord: Array[0..15] of Char;
|
|
i: LongInt;
|
|
j: Integer;
|
|
begin
|
|
//first free all patchfiles
|
|
for j:=0 to Length(FPat)-1 do begin
|
|
FPat[j].Free;
|
|
FPat[j]:=nil;
|
|
end;
|
|
Stream.Read(MagicWord,SizeOf(MagicWord));
|
|
if SameText('VPatchProject 3'#26,MagicWord) then begin
|
|
Stream.Read(i,SizeOf(i)); //4 dummy bytes
|
|
end else
|
|
raise Exception.Create('Error: file format incompatible (only version 3 and newer are supported).');
|
|
|
|
Stream.Read(i,SizeOf(i)); // file count
|
|
SetLength(FPat,i);
|
|
for j:=0 to i - 1 do begin
|
|
FPat[j]:=TPatchFile.Create(j,Stream);
|
|
end;
|
|
end;
|
|
|
|
function TPatchProject.PatchFile(FileName: String): TPatchFile;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
PatchFile:=nil;
|
|
for i:=0 to Length(FPat) - 1 do begin
|
|
if(CompareText(FPat[i].FNew.FileName,FileName)=0) then begin
|
|
PatchFile:=FPat[i];
|
|
end;
|
|
end;
|
|
for i:=0 to Length(FPat) - 1 do begin
|
|
if(CompareText(FPat[i].FNew.FriendlyName,FileName)=0) then begin
|
|
PatchFile:=FPat[i];
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TPatchProject.PatchFile(Index: Integer): TPatchFile;
|
|
begin
|
|
if (Index<Length(FPat)) and (Index>=0) then
|
|
PatchFile:=FPat[Index]
|
|
else
|
|
PatchFile:=nil;
|
|
end;
|
|
|
|
procedure TPatchProject.ResetCache;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i:=0 to Pred(Length(FPat)) do
|
|
FPat[i].ResetCache;
|
|
end;
|
|
|
|
procedure TPatchProject.SaveToStream(Stream: TStream);
|
|
var
|
|
HeadID: Array[0..15] of Char;
|
|
i: LongInt;
|
|
j: Integer;
|
|
begin
|
|
HeadID:='VPatchProject 3'+#26;
|
|
Stream.Write(HeadID,SizeOf(HeadID));
|
|
//4 dummy bytes left
|
|
i:=0;
|
|
Stream.Write(i,SizeOf(i));
|
|
i:=Length(FPat);
|
|
Stream.Write(i,SizeOf(i));
|
|
for j:=0 to i - 1 do begin
|
|
FPat[j].SaveToStream(Stream);
|
|
end;
|
|
end;
|
|
|
|
procedure TPatchProject.WritePatches(Stream: TStream);
|
|
var
|
|
i,j,k,o: LongInt;
|
|
q: LongWord;
|
|
begin
|
|
k:=$54415056;
|
|
o:=Stream.Position;
|
|
Stream.Write(k,SizeOf(k));
|
|
k:=0;
|
|
Stream.Write(k,SizeOf(k));
|
|
k:=0;
|
|
for i:=0 to Length(FPat)-1 do begin
|
|
for j:=0 to FPat[i].GetOldVersionCount - 1 do begin
|
|
FPat[i].WritePatch(j,Stream);
|
|
Inc(k);
|
|
end;
|
|
end;
|
|
Stream.Seek(o+4,soFromBeginning);
|
|
q:=k;
|
|
// set the MD5 flag
|
|
q:=q or $80000000;
|
|
Stream.Write(q,SizeOf(q));
|
|
Stream.Seek(Stream.Size,soFromBeginning);
|
|
Stream.Write(o,SizeOf(o));
|
|
end;
|
|
|
|
end.
|
|
|