NSIS/Contrib/VPatch/Source/GUI/PatchClasses.pas
kichik 4bf6509225 VPatch 3.0
git-svn-id: https://svn.code.sf.net/p/nsis/code/NSIS/trunk@4271 212acab6-be3b-0410-9dea-997c60f758d6
2005-09-17 09:25:44 +00:00

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.