VPatch 2.0 final

git-svn-id: https://svn.code.sf.net/p/nsis/code/NSIS/trunk@2798 212acab6-be3b-0410-9dea-997c60f758d6
This commit is contained in:
joostverburg 2003-08-11 16:42:41 +00:00
parent 2449fbc6bd
commit 6dd280b24f
22 changed files with 3566 additions and 14 deletions

View file

@ -0,0 +1,125 @@
program GenPat2;
{
VPatch 2 - Patch Generator
===============================
(c) 2001-2003 Van de Sande Productions
This is the main program unit for the commandline version. It implements
commandline options (like /b=) and displays help if no options are given.
What's new
----------
2.0 20030811 Koen Initial documentation
}
{$APPTYPE CONSOLE}
uses
PatchGenerator in 'PatchGenerator.pas',
VDSP_CRC in 'VDSP_CRC.pas',
Sysutils,
TreeCode in 'TreeCode.pas';
type
TEventHandler = class
procedure PrintDebug(S: String);
end;
procedure TEventhandler.PrintDebug(S: String);
begin
WriteLn(S);
end;
{$DEFINE READCONFIG} //try to read genpat.ini?
{.$DEFINE AUTOWAIT} //have /wait command line switch on by default?
//useful when debugging
var
Config: TextFile;
T1,T2: TDateTime;
d: Integer;
S,Key: String;
ShowDebug: Boolean;
PG: TPatchGenerator;
EV: TEventHandler;
begin
EV:=TEventHandler.Create;
PG:=TPatchGenerator.Create;
PG.StartBlockSize:=64;
WriteLn('GenPat v2.0 final');
WriteLn('=================');
WriteLn;
WriteLn('(c) 2001-2003 Van de Sande Productions');
WriteLn('Website: http://www.tibed.net/vpatch');
WriteLn('E-mail: koen@tibed.net');
WriteLn;
ShowDebug:=FindCmdLineSwitch('debug',['/'],True);
if ShowDebug then
DebugEvent:=EV.PrintDebug;
{$IFDEF READCONFIG}
if FileExists('genpat.ini') then begin
AssignFile(Config,'genpat.ini');
Reset(Config);
while not eof(Config) do begin
ReadLn(Config,S);
d:=Pos('=',S);
if not (d=0) then begin
Key:=LowerCase(Copy(S,1,d-1));
S:=Copy(S,d+1,Length(S));
if CompareStr(Key,'startblocksize')=0 then PG.StartBlockSize:=StrToInt(S);
end;
end;
CloseFile(Config);
end;
{$ENDIF}
for d:=1 to ParamCount do begin
if CompareStr(LowerCase(Copy(ParamStr(d),1,3)),'/b=')=0 then begin
PG.StartBlockSize:=StrToInt(Copy(ParamStr(d),4,10));
end;
end;
if (CompareStr(ParamStr(1),'')=0) or (CompareStr(ParamStr(2),'')=0) or (CompareStr(ParamStr(3),'')=0) then begin
WriteLn('This program will take (sourcefile) as input and create a (patchfile).');
WriteLn('With this patchfile, you can convert a (sourcefile) into (targetfile).');
WriteLn;
WriteLn('Command line info:');
WriteLn(' GENPAT (sourcefile) (targetfile) (patchfile)');
WriteLn;
WriteLn('Command line options (you do not need them):');
WriteLn('/B=(BlockSize) Set blocksize (def=64), multiple of 2');
WriteLn('/WAIT Wait for a keypress after program complete');
WriteLn('/DEBUG Show runtime debug information');
WriteLn('Note: all these parameters must be *after* the filenames!');
WriteLn;
Write('Press a enter to exit ');
ReadLn(S);
Exit;
end;
if FileExists(ParamStr(3)) then begin
WriteLn('Using existing file to include patches in: '+ParamStr(3));
PG.LoadFromFile(ParamStr(3));
end;
T1:=Now;
WriteLn('Patch body size: '+IntToStr(PG.CreatePatch(ParamStr(1),ParamStr(2))));
PG.WriteToFile(ParamStr(3));
T2:=Now;
Write('GenPat.exe finished execution in: ');
WriteLn(FloatToStr((T2-T1)*24*60*60),'s');
WriteLn;
{$IFNDEF AUTOWAIT}
if FindCmdLineSwitch('wait',['/'],True) then begin
{$ENDIF}
WriteLn;
WriteLn('Press a key');
ReadLn(S);
{$IFNDEF AUTOWAIT}
end;
{$ENDIF}
PG.Free;
end.

View file

@ -0,0 +1,591 @@
unit PatchGenerator;
{
VPatch 2 - Patch Generator
==========================
(c) 2002-2003 Van de Sande Productions
This unit contains the 'core' functionality of VPatch. TPatchGenerator can
load/create/save .PAT files and supports CreatePatch(Old, New) to generate
new patches. The only configurable parameter is StartBlockSize.
Though I cleaned up the code a little bit, there is very little documentation.
That's why I will briefly explain the general workings of the current VPatch
algoritm.
There is a source file, which is divided into blocks of BlockSize. Block 1
spans bytes 0-15, block 2 16-31, etc if blocksize = 16. For every block, a
checksum is calculated and then the block is inserted into a binary search
tree, which is sorted on this checksum.
Now, the target file (new version) is traversed linearly. For a block at a
certain position, the checksum is calculated. Then, a lookup is performed in
the binary search tree to find all blocks in the source file which match this
checksum. For every occurence, it is checked how many consecutive bytes match
with this block (note: since the checksum is not unique, this can be 0 as well,
but since all occurences are checked, the largest match is selected). Note also
that this match length is not limited to BlockSize but can be larger as well;
everything beyond the block end is checked as well (the block is merely used
as a starting position for checking the match).
For those biggest block matches between source/target files, a copy instruction
will be generated in the patch file. For 'inbetween' or unmatchable blocks, the
data of the new file is placed in the patch file. This involves some
housekeeping, which is what most of the other code does.
What's new
----------
2.0 20030811 Koen Initial documentation
}
interface
uses
Classes,
Sysutils,
TreeCode,
VDSP_CRC;
type
TStatusNotifyEvent = procedure(S: String; Current, Total, Savings: Integer) of object;
TDebugNotifyEvent = procedure(S: String) of object;
PDataBlock = ^TDataBlock;
TDataBlock = record
SourceOffset: Integer;
TargetOffset: Integer;
Size: Integer;
Next: PDataBlock;
end;
//internal structure for FindBlock
TBlock = record
Offset: Integer;
Size: Integer;
end;
TPatchGenerator = class
private
noPat: Integer;
PRay: Array of TDataBlock;
NRay: Array of TDataBlock;
FPatchData: TMemoryStream;
FStartBlockSize: Integer; //initial block size
FBlockDivider: Integer; //... block size is divided by this
FMinimumBlockSize: Integer;//until this minimum is reached
FStepSize: Integer;
//input: ASubBlock, which is a pointer to the start of the block to look
//for in ABlock. The entire ABlock is searched. The function returns the
//offset of the block, when it is found. The ASize parameter contains the
//size of this block
procedure ShowDebug(S: String);
function FindBlock(ASubBlock, ABlock, ABlockTree: Pointer;
var ASubBlockStart: Integer; ASubBlockSize, ABlockSize,
AMatchSize, ABlockTreeNodeCount: Integer; var ASize: Integer): Integer;
procedure FindBlockSize(ASubBlock, ABlock: Pointer; ASubBlockSize,
ABlockSize: Integer; var ASubStart, AStart, AFoundSize: Integer);
function WritePatchToStream(Target: Pointer; SourceCRC,
TargetCRC: Integer): Integer;
procedure RemoveExistingPatch(ACRC: Integer);
public
constructor Create;
destructor Destroy; override;
procedure Clear;
function CreatePatch(SourceFileName, TargetFileName: String): Integer;
property StartBlockSize: Integer read FStartBlockSize write FStartBlockSize;
property BlockDivider: Integer read FBlockDivider write FBlockDivider;
property MinimumBlockSize: Integer read FMinimumBlockSize write FMinimumBlockSize;
property StepSize: Integer read FStepSize write FStepSize;
function Size: Integer;
procedure WriteToFile(AFileName: String);
procedure WriteToStream(AStream: TStream);
procedure LoadFromFile(AFileName: String);
end;
const
BUF_BLOCK_SIZE = 4096;
INIT_BLOCK_COUNT=10000;
var
DebugEvent: TDebugNotifyEvent = nil;
implementation
{ TPatchGenerator }
procedure TPatchGenerator.Clear;
begin
FPatchData.Clear;
end;
constructor TPatchGenerator.Create;
begin
inherited;
FPatchData:=TMemoryStream.Create;
end;
function TPatchGenerator.CreatePatch(SourceFileName,
TargetFileName: String): Integer;
var
fsSource, fsTarget: TFileStream;
fm: TMemoryStream;
Source, Target: Pointer;
SourceSize, TargetSize: Integer;
SourceCRC, TargetCRC: Integer;
SourceTree: Pointer;
SourceTreeNodeCount: Cardinal;
cBlockSize: Integer;
o,i,lastO: Integer;
Start,Siz,BetweenSiz: Integer;
retTO: Integer;
noN: Integer;
begin
fsSource:=TFileStream.Create(SourceFileName,fmOpenRead);
fsTarget:=TFileStream.Create(TargetFileName,fmOpenRead);
fm:=TMemoryStream.Create;
SetLength(PRay,INIT_BLOCK_COUNT);
SetLength(NRay,INIT_BLOCK_COUNT);
//Load those files into memory!
SourceSize:=fsSource.Size;
GetMem(Source,SourceSize);
fm.CopyFrom(fsSource,SourceSize);
Move(fm.Memory^,Source^,SourceSize);
SourceCRC:=FileCRC(fsSource);
fsSource.Free;
fm.Clear;
TargetSize:=fsTarget.Size;
GetMem(Target,TargetSize);
fm.CopyFrom(fsTarget,TargetSize);
Move(fm.Memory^,Target^,TargetSize);
TargetCRC:=FileCRC(fsTarget);
fsTarget.Free;
fm.Free;
PRay[0].TargetOffset:=0;
PRay[0].SourceOffset:=0;
PRay[0].Size:=0;
noPat:=1;
//termination block
PRay[noPat].SourceOffset:=0;
PRay[noPat].TargetOffset:=TargetSize;
PRay[noPat].Size:=0;
//we only have one pass in this mode
// StartBlockSize:=16;
MinimumBlockSize:=StartBlockSize;
StepSize:=1;
BlockDivider:=2;
//because we are dividing first inside.
cBlockSize:=StartBlockSize*BlockDivider;
SourceTree:=nil;
SourceTreeNodeCount:=BuildTree(Source,SourceTree,SourceSize,cBlockSize div BlockDivider);
SortTree(SourceTree,SourceTreeNodeCount);
//now, we must do the above again - with a smaller block size
repeat
if cBlockSize<=MinimumBlockSize then break;
cBlockSize:=cBlockSize div BlockDivider;
noN:=0;
for i:=1 to noPat do begin
//calculate location of the inbetween parts
Start:=PRay[i-1].TargetOffset+PRay[i-1].Size;
BetweenSiz:=PRay[i].TargetOffset-Start;
NRay[noN].SourceOffset:=PRay[i-1].SourceOffset;
NRay[noN].TargetOffset:=PRay[i-1].TargetOffset;
NRay[noN].Size:=PRay[i-1].Size;
Inc(noN);
if BetweenSiz>0 then begin
o:=Start;
repeat
//ShowDebug(PChar('DoFind '+IntToStr(o)));
LastO:=o;
retTO:=FindBlock(Target,Source,SourceTree,o,TargetSize,SourceSize,cBlockSize,SourceTreeNodeCount,Siz);
if not (Siz=0) then
ShowDebug(IntToStr(LastO)+' -> Source='+IntToStr(retTO)+' Target='+IntToStr(o)+' Size='+IntToStr(Siz));
if Siz=0 then begin
o:=LastO+StepSize;
end else begin
//we have found a block, let's add it!
NRay[noN].SourceOffset:=retTO;
NRay[noN].TargetOffset:=o;
NRay[noN].Size:=Siz;
Inc(noN);
if noN>=Length(NRay) then begin
SetLength(NRay,Length(NRay)*2);
SetLength(PRay,Length(PRay)*2);
end;
Inc(o,Siz);
end;
//check to see if we're not inside another one.
Siz:=NRay[noN].TargetOffset-NRay[noN-1].TargetOffset-NRay[noN-1].Size;
If Siz<0 then begin //that's impossible! (overlapping should be eliminated)
NRay[noN].TargetOffset:=NRay[noN].TargetOffset-Siz;
NRay[noN].Size:=NRay[noN].Size+Siz;
NRay[noN].SourceOffset:=NRay[noN].SourceOffset-Siz;
end;
until o>Start+BetweenSiz;
end;
end;
//I think the last termination block isn't copied: do so now.
NRay[noN].SourceOffset:=PRay[noPat].SourceOffset;
NRay[noN].TargetOffset:=PRay[noPat].TargetOffset;
NRay[noN].Size:=PRay[noPat].Size;
//copy back into PRay
for i:=0 to noN do begin
PRay[i].SourceOffset:=NRay[i].SourceOffset;
PRay[i].TargetOffset:=NRay[i].TargetOffset;
PRay[i].Size:=NRay[i].Size;
end;
noPat:=noN;
until false;
//writing is next!
ShowDebug('Writing patch');
Result:=WritePatchToStream(Target, SourceCRC, TargetCRC);
ClearTree(SourceTree,SourceTreeNodeCount);
FreeMem(Source,SourceSize);
FreeMem(Target,TargetSize);
ShowDebug('Done');
end;
destructor TPatchGenerator.Destroy;
begin
FPatchData.Free;
inherited;
end;
function TPatchGenerator.FindBlock(ASubBlock, ABlock, ABlockTree: Pointer; var ASubBlockStart: Integer;
ASubBlockSize, ABlockSize, AMatchSize, ABlockTreeNodeCount: Integer; var ASize: Integer): Integer;
//This procedure locates location of a block in the target file
//Then, it calls FindBlockSize to determine size of this block
var
MatchSize, FoundSize: Integer;
q,r,i: Integer;
FoundCache_SubOffset, FoundCache_Size, FoundCache_Offset: Integer;
Checksum: Cardinal;
PFound: PTreeNode;
FoundCount: Integer;
begin
//if we find nothing...
FoundCache_Size:=0;
FoundCache_Offset:=0;
FoundCache_SubOffset:=ASubBlockStart;
FindBlock:=0;
ASize:=0;
MatchSize:=AMatchSize;
//we can only find MatchSize sized blocks in the tree!
if MatchSize > ASubBlockSize - ASubBlockStart then Exit;
if MatchSize = 0 then Exit;
Checksum:=0;
calculateChecksum(ASubBlock,ASubBlockStart,MatchSize,Checksum);
PFound:=TreeFind(Checksum,ABlockTree,ABlockTreeNodeCount,FoundCount);
for i:=0 to Pred(FoundCount) do begin
FoundSize:=MatchSize;
//q = offset in Block
q:=PFound^.Offset;
//r = offset in SubBlock
r:=ASubBlockStart;
FindBlockSize(ASubBlock, ABlock, ASubBlockSize, ABlockSize, r, q, FoundSize);
if FoundSize>FoundCache_Size then begin
FoundCache_SubOffset:=r;
FoundCache_Offset:=q;
FoundCache_Size:=FoundSize;
end;
ShowDebug(' Block Size Start='+IntToStr(r)+' tarStart='+IntToStr(q)+' Size='+IntToStr(FoundSize));
PFound:=PTreeNode(Integer(PFound)+SizeOf(TTreeNode));
end;
FindBlock:=FoundCache_Offset;
ASize:=FoundCache_Size;
ASubBlockStart:=FoundCache_SubOffset;
end;
procedure TPatchGenerator.FindBlockSize(ASubBlock, ABlock: Pointer; ASubBlockSize, ABlockSize: Integer; var ASubStart,AStart,AFoundSize: Integer);
var
FoundSize: Integer;
a,c,d,i: Integer;
f1p,f2p,f1Size,f2Size: Integer;
beforeSize: Integer;
CurBufSize: Integer;
begin
//OK, now let's go...
//Trace after -> how long does this go on?
f1p:=Integer(ASubBlock)+ASubStart;
f2p:=Integer(ABlock)+AStart;
f1Size:=ASubBlockSize-ASubStart;
f2Size:=ABlockSize-AStart;
FoundSize:=0;
CurBufSize := BUF_BLOCK_SIZE; //size of the block we're checking
while not (CurBufSize = 0) do begin
//we need equal bytes from both... so if one of them EOF, it's the end.
if FoundSize+CurBufSize>f1Size then CurBufSize:=f1Size - FoundSize;
if FoundSize+CurBufSize>f2Size then CurBufSize:=f2Size - FoundSize;
if CompareMem(Pointer(f1p),Pointer(f2p),CurBufSize) then begin
Inc(FoundSize,CurBufSize);
Inc(f1p,CurBufSize);
Inc(f2p,CurBufSize);
end
else begin
CurBufSize:=CurBufSize div 2;
end;
end;
if FoundSize = 0 then begin AFoundSize:=0; Exit; end;
//Trace before -> how much bytes are still the same before the block?
//First, read 1 block from source and 1 block from target, start from back to compare how much they differ
//just take BUF_BLOCK_SIZE as maximum size for the block before - that's surely
//big enough!
beforeSize:=BUF_BLOCK_SIZE;
a:=ASubStart-beforeSize;
if a<0 then begin
a:=0;
beforeSize:=ASubStart;
end;
//b is the current before block size
c:=AStart-beforeSize;
if c<0 then begin
c:=0;
beforeSize:=AStart;
a:=ASubStart-beforeSize;
end;
//a=Offset in source
//b=Size of beforeblock
//c=offset in target
d:=0;
for i:=beforeSize-1 downto 0 do begin
//if not (f1^[a+i]=f2^[c+i]) then begin
if not (PByte(Integer(ASubBlock)+a+i)^=PByte(Integer(ABlock)+c+i)^) then begin
//d=how many bytes before are the same?
Break;
end;
Inc(d);
end;
Inc(FoundSize,d);
Dec(ASubStart,d);
Dec(AStart,d);
AFoundSize:=FoundSize;
end;
function TPatchGenerator.Size: Integer;
begin
Result:=FPatchData.Size;
end;
procedure TPatchGenerator.ShowDebug(S: String);
begin
if Assigned(DebugEvent) then DebugEvent(S);
end;
function TPatchGenerator.WritePatchToStream(Target: Pointer; SourceCRC, TargetCRC: Integer): Integer;
var
HeadID: Array[0..3] of Char;
NoBlocks, NoBlocksOffset, BodySize, BodySizeOffset: Integer;
b: Byte;
w: Word;
i, j: Integer;
l: LongWord;
Start, Siz: Integer;
PTarget: Pointer;
begin
RemoveExistingPatch(SourceCRC);
with FPatchData do begin
Seek(0,soFromEnd);
if Size = 0 then begin
HeadID:='VPAT';
Write(HeadID,SizeOf(HeadID));
l:=0;
Write(l,SizeOf(l)); //NoFiles
end;
l:=0;
NoBlocksOffset:=Position;
Write(l,SizeOf(l)); //should become NoBlocks later
Write(SourceCRC,SizeOf(SourceCRC)); //source CRC
Write(TargetCRC,SizeOf(TargetCRC)); //target CRC
BodySizeOffset:=Position;
Write(l,SizeOf(l)); //should become BodySize (of this patch)
NoBlocks:=0;
BodySize:=0;
//Write the patch...
for i:=0 to noPat - 1 do begin
//write char 1 - integer/copysource
//write char 2 - long/copysource
//write char 5 - integer/insidepatch
//write char 6 - long/insidepatch
Start:=PRay[i].TargetOffset+PRay[i].Size;
Siz:= PRay[i+1].TargetOffset-Start;
If Siz<0 then begin //that's impossible! (overlapping should be eliminated)
PRay[i+1].TargetOffset:=PRay[i+1].TargetOffset-Siz;
PRay[i+1].Size:=PRay[i+1].Size+Siz;
PRay[i+1].SourceOffset:=PRay[i+1].SourceOffset-Siz;
Siz:=0;
end;
if not (PRay[i].Size=0) then begin
if (PRay[i].Size<=255) then begin
b:=1;
Write(b,SizeOf(b));
b:=PRay[i].Size;
Write(b,SizeOf(b));
Inc(BodySize,2);
end else if PRay[i].Size<=65535 then begin
b:=2;
Write(b,SizeOf(b));
w:=PRay[i].Size;
Write(w,SizeOf(w));
Inc(BodySize,3);
end else begin
b:=3;
Write(b,SizeOf(b));
Write(PRay[i].Size,SizeOf(Integer));
Inc(BodySize,5);
end;
Write(PRay[i].SourceOffset,SizeOf(Integer));
Inc(BodySize,SizeOf(Integer));
Inc(NoBlocks);
end;
//Now write the writeblock
If Not (Siz = 0) Then begin
if Siz<=255 then begin
b:=5;
Write(b,SizeOf(b));
b:=Siz;
Write(b,SizeOf(b));
Inc(BodySize,2);
end else if Siz<=65535 then begin
b:=6;
Write(b,1);
w:=Siz;
Write(w,2);
Inc(BodySize,3);
end else begin
b:=7;
Write(b,1);
Write(Siz,4);
Inc(BodySize,5);
end;
PTarget:=Pointer(Integer(Target)+Start);
j:=Start;
repeat
//read
if (j+4096>Start+Siz) then begin
Write(PTarget^,Start+Siz-j);
break;
end;
Write(PTarget^,4096);
Inc(j,4096);
PTarget:=Pointer(Integer(PTarget)+4096);
until false;
Inc(BodySize,Siz);
Inc(NoBlocks);
end;
end;
Seek(NoBlocksOffset,soFromBeginning);
Write(NoBlocks,SizeOf(NoBlocks));
Seek(BodySizeOffset,soFromBeginning);
Write(BodySize,SizeOf(BodySize));
ShowDebug('Patch body size: '+IntToStr(BodySize));
ShowDebug('Total patch size:'+IntToStr(Size));
//now increase file count
Seek(4,soFromBeginning);
Read(i,SizeOf(i));
Inc(i);
Seek(4,soFromBeginning);
Write(i,SizeOf(i));
Seek(0,soFromEnd);
Result:=BodySize;
end;
end;
procedure TPatchGenerator.WriteToFile(AFileName: String);
var
fs: TFileStream;
begin
fs:=TFileStream.Create(AFileName,fmCreate);
FPatchData.Seek(0,soFromBeginning);
fs.CopyFrom(FPatchData,FPatchData.Size);
fs.Free;
end;
procedure TPatchGenerator.LoadFromFile(AFileName: String);
var
fs: TFileStream;
begin
fs:=TFileStream.Create(AFileName,fmOpenRead);
FPatchData.Clear;
FPatchData.CopyFrom(fs,fs.Size);
fs.Free;
end;
procedure TPatchGenerator.RemoveExistingPatch(ACRC: Integer);
var
HeadID: Array[0..3] of Char;
NoFiles, i, j, SourceCRC, MSize: Integer;
StartPos: Integer;
ms: TMemoryStream;
begin
with FPatchData do begin
if Size = 0 then Exit;
Seek(0,soFromBeginning);
Read(HeadID,SizeOf(HeadID));
if HeadID = 'VPAT' then begin
Read(NoFiles,SizeOf(NoFiles));
for i:=0 to Pred(NoFiles) do begin
if Position >= Size then Break;
StartPos:=Position;
Read(j,SizeOf(j)); //NoBlocks
Read(SourceCRC,SizeOf(SourceCRC)); //SourceCRC
Read(j,SizeOf(j)); //TargetCRC
Read(j,SizeOf(j)); //BodySize
Seek(j,soFromCurrent);
if SourceCRC = ACRC then begin
ms:=TMemoryStream.Create;
MSize:=Size-Position;
if MSize > 0 then ms.CopyFrom(FPatchData,MSize);
ms.Seek(0, soFromBeginning);
FPatchData.Seek(StartPos,soFromBeginning);
FPatchData.SetSize(Size - j - SizeOf(Integer) * 4);
FPatchData.CopyFrom(ms,ms.Size);
ms.Free;
Dec(NoFiles);
Seek(4,soFromBeginning);
Write(NoFiles,SizeOf(NoFiles));
Break;
end;
end;
end;
end;
end;
procedure TPatchGenerator.WriteToStream(AStream: TStream);
begin
FPatchData.Seek(0,soFromBeginning);
AStream.CopyFrom(FPatchData,FPatchData.Size);
end;
end.

View file

@ -0,0 +1,245 @@
unit TreeCode;
{
VPatch 2 - Binary Checksum Tree
===============================
(c) 2002-2003 Van de Sande Productions
This unit implements a binary search tree, which is constructed from a memory
block by BuildTree. This memory block is divided into equal-sized blocks of
BlockSize, and for every block a checksum is calculated. Then, it is inserted
in the binary tree, which is sorted on the checksum.
The patch generator will search for the checksums using a binary search, which
is O(log n) (much better than the old 1.x algoritm, which was O(n)).
What's new
----------
2.0 20030811 Koen Initial documentation
}
interface
type
TSortStack=record
lo,hi: Integer;
end;
PTreeNode = ^TTreeNode;
TTreeNode = record
Checksum: Cardinal;
Offset: Cardinal;
end;
const
TREENODE_SIZE = SizeOf(TTreeNode);
procedure calculateChecksum(AData: Pointer; AStart, ASize: Cardinal; var K: Cardinal);
procedure calculateNext(AData: Pointer; AStart, ASize: Cardinal; var K: Cardinal);
function BuildTree(ASource: Pointer; var ATree: Pointer; ASourceSize, ABLOCKSIZE: Cardinal): Cardinal;
procedure SortTree(ATree: Pointer; ANodeCount: Cardinal);
procedure ClearTree(var ATree: Pointer; ANodeCount: Cardinal);
function TreeFind(AChecksum: Cardinal; ABlockTree: Pointer; ABlockTreeNodeCount: Integer; var FoundCount: Integer): PTreeNode;
function GetItem(ATree: Pointer; Index, ANodeCount: Cardinal): TTreeNode;
procedure Test;
implementation
uses SysUtils;
procedure calculateChecksum(AData: Pointer; AStart, ASize: Cardinal; var K: Cardinal);
var
A,B,i,j: Cardinal;
begin
A:=K and $0000FFFF;
B:=(K and $FFFF0000) shr 16;
j:=Cardinal(AData)+AStart;
for i:=1 to ASize do begin
A:=A + PByte(j)^;
B:=B + (ASize-i+1)*PByte(j)^;
Inc(j);
end;
K:=(A and $0000FFFF) or ((B and $0000FFFF) shl 16);
end;
procedure calculateNext(AData: Pointer; AStart, ASize: Cardinal; var K: Cardinal);
var
A,B,j: Cardinal;
begin
j:=Cardinal(AData)+AStart;
A:=(K-PByte(j-1)^+PByte(j+ASize-1)^) and $0000FFFF;
B:=((K shr 16)-ASize*PByte(j-1)^+A) and $0000FFFF;
K:=A or (B shl 16);
end;
function BuildTree(ASource: Pointer; var ATree: Pointer; ASourceSize, ABLOCKSIZE: Cardinal): Cardinal;
var
i, NodeCount: Cardinal;
Node: TTreeNode;
begin
Assert(not Assigned(ATree),'Cannot use initialized tree in BuildTree!');
NodeCount:=ASourceSize div ABLOCKSIZE;
GetMem(ATree,NodeCount*TREENODE_SIZE);
if NodeCount > 0 then begin
for i:=0 to Pred(NodeCount) do begin
Node.Offset:=i*ABLOCKSIZE;
Node.Checksum:=0;
calculateChecksum(ASource,Node.Offset,ABLOCKSIZE,Node.Checksum);
Move(Node,Pointer(Cardinal(ATree)+i*TREENODE_SIZE)^,TREENODE_SIZE);
end;
end;
Result:=NodeCount;
end;
procedure SetItem(ATree: Pointer; Index, ANodeCount: Cardinal; New: TTreeNode);
var
p: PTreeNode;
begin
Assert(Index<ANodeCount,'Tree/GetItem: Index too big');
p:=PTreeNode(Cardinal(ATree)+Index*TREENODE_SIZE);
p^:=New;
end;
function GetItem(ATree: Pointer; Index, ANodeCount: Cardinal): TTreeNode;
var
p: PTreeNode;
begin
Assert(Index<ANodeCount,'Tree/GetItem: Index too big '+IntToStr(Index));
p:=PTreeNode(Cardinal(ATree)+Index*TREENODE_SIZE);
Result:=p^;
end;
procedure SortTree(ATree: Pointer; ANodeCount: Cardinal);
var
compare: Cardinal;
aStack: Array[1..128] of TSortStack;
StackPtr: Integer;
Mid,i,j,low,hi: Integer;
Switcher: TTreeNode;
begin
If ANodeCount = 0 Then Exit;
StackPtr:=1;
aStack[StackPtr].lo:=0;
aStack[StackPtr].hi:=ANodeCount - 1;
Inc(StackPtr);
while not (StackPtr=1) do begin
StackPtr:=StackPtr-1;
low:=aStack[StackPtr].lo;
hi:=aStack[StackPtr].hi;
while true do begin
i:=low;
j:=hi;
Mid:=(low + hi) div 2;
compare:=PTreeNode(Integer(ATree)+Mid*TREENODE_SIZE)^.Checksum;
while true do begin
While PTreeNode(Integer(ATree)+i*TREENODE_SIZE)^.Checksum < compare do begin
Inc(i);
end;
While PTreeNode(Integer(ATree)+j*TREENODE_SIZE)^.Checksum > compare do begin
j:=j-1;
end;
If (i <= j) Then begin
Move(Pointer(Integer(ATree)+j*TREENODE_SIZE)^,Switcher,TREENODE_SIZE);
Move(Pointer(Integer(ATree)+i*TREENODE_SIZE)^,Pointer(Integer(ATree)+j*TREENODE_SIZE)^,TREENODE_SIZE);
Move(Switcher,Pointer(Integer(ATree)+i*TREENODE_SIZE)^,TREENODE_SIZE);
Inc(i);
Dec(j);
End;
if not (i <= j) then break;
end;
If j - low < hi - i Then begin
If i < hi Then begin
aStack[StackPtr].lo:=i;
aStack[StackPtr].hi:=hi;
Inc(StackPtr);
End;
hi:=j;
end Else begin
If low < j Then begin
aStack[StackPtr].lo:=low;
aStack[StackPtr].hi:=j;
Inc(StackPtr);
End;
low:=i;
End;
if not (low<hi) then break;
end;
if StackPtr=1 then break;
end;
end;
procedure ClearTree(var ATree: Pointer; ANodeCount: Cardinal);
begin
FreeMem(ATree,ANodeCount*TREENODE_SIZE);
ATree:=nil;
end;
function TreeFind(AChecksum: Cardinal; ABlockTree: Pointer; ABlockTreeNodeCount: Integer; var FoundCount: Integer): PTreeNode;
var
lo,mid,hi,m: Integer;
tmp: Cardinal;
begin
lo:=0;
hi:=ABlockTreeNodeCount-1;
while true do begin
mid:=(lo+hi) div 2;
tmp:=PCardinal(Integer(ABlockTree)+mid*TREENODE_SIZE)^;
if tmp = AChecksum then begin
FoundCount:=1;
m:=mid;
Result:=PTreeNode(Integer(ABlockTree)+m*TREENODE_SIZE);
while m > 0 do begin
Dec(m);
if PCardinal(Integer(ABlockTree)+m*TREENODE_SIZE)^ = tmp then begin
Result:=PTreeNode(Integer(ABlockTree)+m*TREENODE_SIZE);
Inc(FoundCount);
end else
Break;
end;
m:=mid;
while m < ABlockTreeNodeCount-1 do begin
Inc(m);
if PCardinal(Integer(ABlockTree)+m*TREENODE_SIZE)^ = tmp then begin
Inc(FoundCount);
end else
Break;
end;
Exit;
end;
if lo>=hi then Break;
if AChecksum < tmp then begin
hi:=mid-1;
end else begin
lo:=mid+1;
end;
end;
FoundCount:=0; Result:=nil;
end;
procedure Test;
var
p: Pointer;
t: TTreeNode;
r: PTreeNode;
i,q: Integer;
NC: Integer;
begin
NC:=100;
GetMem(p,800);
for i:=0 to 99 do begin
t.Offset:=i*100;
t.Checksum:=i div 2;
SetItem(p,i,NC,t);
end;
SortTree(p,NC);
for i:=0 to 99 do begin
t:=GetItem(p,i,NC);
Write(IntToStr(t.Checksum)+' ');
end;
r:=TreeFind(7,p,NC,q);
WriteLn(IntToStr(q));
t:=r^;
WriteLn(IntToStr(t.Checksum)+' ');
end;
end.

View file

@ -0,0 +1,77 @@
program VAppend;
{$APPTYPE CONSOLE}
uses
SysUtils;
var
fs, fo: File;
Patch: String;
OutFile: String = 'VPATCH.EXE';
Runtime: String = 'VPATCH.BIN';
o: LongWord;
Buf: Array[0..4095] of Byte;
Size, BufSize: Integer;
begin
WriteLn('VAppend v2.0');
WriteLn('============');
WriteLn;
WriteLn('(c) 2001-2002 Van de Sande Productions');
WriteLn('Website: http://www.tibed.net/vpatch');
WriteLn('E-mail: koen@tibed.net');
WriteLn;
if ParamCount = 0 then begin
WriteLn('Use this program to append .PAT files to the VPatch runtime.');
WriteLn;
WriteLn(' VAPPEND (patch file) [output file] [runtime]');
WriteLn;
WriteLn('By default, the output file is VPATCH.EXE and the runtime is VPATCH.BIN');
end;
if not FileExists(ParamStr(1)) then begin
WriteLn('ERROR: Patch file not found');
Exit;
end;
Patch := ParamStr(1);
if ParamCount > 1 then OutFile := ParamStr(2);
if ParamCount > 2 then Runtime := ParamStr(3);
WriteLn('Patch: '+Patch);
WriteLn('Runtime: '+Runtime);
WriteLn('Output: '+OutFile);
AssignFile(fo,OutFile);
Rewrite(fo,1);
//copy the runtime
AssignFile(fs,Runtime);
FileMode:=fmOpenRead;
Reset(fs,1);
BufSize:=4096;
o:=FileSize(fs); //patch start offset
Size:=FileSize(fs);
while Size>0 do begin
if Size-BufSize<0 then BufSize:=Size;
BlockRead(fs,Buf,BufSize);
BlockWrite(fo,Buf,BufSize);
Dec(Size,BufSize);
end;
CloseFile(fs);
//do the patch
AssignFile(fs,Patch);
FileMode:=fmOpenRead;
Reset(fs,1);
BufSize:=4096;
Size:=FileSize(fs);
while Size>0 do begin
if Size-BufSize<0 then BufSize:=Size;
BlockRead(fs,Buf,BufSize);
BlockWrite(fo,Buf,BufSize);
Dec(Size,BufSize);
end;
CloseFile(fs);
BlockWrite(fo,o,SizeOf(o));
CloseFile(fo);
WriteLn('Created.');
end.

View file

@ -0,0 +1,26 @@
#------------------------------------------------------------------------------
VERSION = BWS.01
#------------------------------------------------------------------------------
!ifndef ROOT
ROOT = $(MAKEDIR)\..
!endif
#------------------------------------------------------------------------------
MAKE = $(ROOT)\bin\make.exe -$(MAKEFLAGS) -f$**
DCC = $(ROOT)\bin\dcc32.exe $**
BRCC = $(ROOT)\bin\brcc32.exe $**
#------------------------------------------------------------------------------
PROJECTS = GenPat2.exe VAppend.exe VPatchGUI.exe
#------------------------------------------------------------------------------
default: $(PROJECTS)
#------------------------------------------------------------------------------
GenPat2.exe: GenPat2.dpr
$(DCC)
VAppend.exe: VAppend.dpr
$(DCC)
VPatchGUI.exe: gui\VPatchGUI.dpr
$(DCC)

View file

@ -0,0 +1,115 @@
unit VDSP_CRC;
{
VPatch 2 - CRC
==============
(c) 2002-2003 Van de Sande Productions
This unit can calculate the standard ZIP CRC32 for a filestream.
What's new
----------
2.0 20030811 Koen Initial documentation
}
interface
uses Classes;
//var
//this is the CRC32 table
// CRCTable: Array[0..255] of LongWord;
{= (
//this table used to be inside the exe, but now it has been replaced by a calculation routine which saves 1 KB
$0,$77073096,$EE0E612C,$990951BA,$76DC419,
$706AF48F,$E963A535,$9E6495A3,$EDB8832,$79DCB8A4,
$E0D5E91E,$97D2D988,$9B64C2B,$7EB17CBD,$E7B82D07,
$90BF1D91,$1DB71064,$6AB020F2,$F3B97148,$84BE41DE,
$1ADAD47D,$6DDDE4EB,$F4D4B551,$83D385C7,$136C9856,
$646BA8C0,$FD62F97A,$8A65C9EC,$14015C4F,$63066CD9,
$FA0F3D63,$8D080DF5,$3B6E20C8,$4C69105E,$D56041E4,
$A2677172,$3C03E4D1,$4B04D447,$D20D85FD,$A50AB56B,
$35B5A8FA,$42B2986C,$DBBBC9D6,$ACBCF940,$32D86CE3,
$45DF5C75,$DCD60DCF,$ABD13D59,$26D930AC,$51DE003A,
$C8D75180,$BFD06116,$21B4F4B5,$56B3C423,$CFBA9599,
$B8BDA50F,$2802B89E,$5F058808,$C60CD9B2,$B10BE924,
$2F6F7C87,$58684C11,$C1611DAB,$B6662D3D,$76DC4190,
$1DB7106,$98D220BC,$EFD5102A,$71B18589,$6B6B51F,
$9FBFE4A5,$E8B8D433,$7807C9A2,$F00F934,$9609A88E,
$E10E9818,$7F6A0DBB,$86D3D2D,$91646C97,$E6635C01,
$6B6B51F4,$1C6C6162,$856530D8,$F262004E,$6C0695ED,
$1B01A57B,$8208F4C1,$F50FC457,$65B0D9C6,$12B7E950,
$8BBEB8EA,$FCB9887C,$62DD1DDF,$15DA2D49,$8CD37CF3,
$FBD44C65,$4DB26158,$3AB551CE,$A3BC0074,$D4BB30E2,
$4ADFA541,$3DD895D7,$A4D1C46D,$D3D6F4FB,$4369E96A,
$346ED9FC,$AD678846,$DA60B8D0,$44042D73,$33031DE5,
$AA0A4C5F,$DD0D7CC9,$5005713C,$270241AA,$BE0B1010,
$C90C2086,$5768B525,$206F85B3,$B966D409,$CE61E49F,
$5EDEF90E,$29D9C998,$B0D09822,$C7D7A8B4,$59B33D17,
$2EB40D81,$B7BD5C3B,$C0BA6CAD,$EDB88320,$9ABFB3B6,
$3B6E20C,$74B1D29A,$EAD54739,$9DD277AF,$4DB2615,
$73DC1683,$E3630B12,$94643B84,$D6D6A3E,$7A6A5AA8,
$E40ECF0B,$9309FF9D,$A00AE27,$7D079EB1,$F00F9344,
$8708A3D2,$1E01F268,$6906C2FE,$F762575D,$806567CB,
$196C3671,$6E6B06E7,$FED41B76,$89D32BE0,$10DA7A5A,
$67DD4ACC,$F9B9DF6F,$8EBEEFF9,$17B7BE43,$60B08ED5,
$D6D6A3E8,$A1D1937E,$38D8C2C4,$4FDFF252,$D1BB67F1,
$A6BC5767,$3FB506DD,$48B2364B,$D80D2BDA,$AF0A1B4C,
$36034AF6,$41047A60,$DF60EFC3,$A867DF55,$316E8EEF,
$4669BE79,$CB61B38C,$BC66831A,$256FD2A0,$5268E236,
$CC0C7795,$BB0B4703,$220216B9,$5505262F,$C5BA3BBE,
$B2BD0B28,$2BB45A92,$5CB36A04,$C2D7FFA7,$B5D0CF31,
$2CD99E8B,$5BDEAE1D,$9B64C2B0,$EC63F226,$756AA39C,
$26D930A,$9C0906A9,$EB0E363F,$72076785,$5005713,
$95BF4A82,$E2B87A14,$7BB12BAE,$CB61B38,$92D28E9B,
$E5D5BE0D,$7CDCEFB7,$BDBDF21,$86D3D2D4,$F1D4E242,
$68DDB3F8,$1FDA836E,$81BE16CD,$F6B9265B,$6FB077E1,
$18B74777,$88085AE6,$FF0F6A70,$66063BCA,$11010B5C,
$8F659EFF,$F862AE69,$616BFFD3,$166CCF45,$A00AE278,
$D70DD2EE,$4E048354,$3903B3C2,$A7672661,$D06016F7,
$4969474D,$3E6E77DB,$AED16A4A,$D9D65ADC,$40DF0B66,
$37D83BF0,$A9BCAE53,$DEBB9EC5,$47B2CF7F,$30B5FFE9,
$BDBDF21C,$CABAC28A,$53B39330,$24B4A3A6,$BAD03605,
$CDD70693,$54DE5729,$23D967BF,$B3667A2E,$C4614AB8,
$5D681B02,$2A6F2B94,$B40BBE37,$C30C8EA1,$5A05DF1B,
$2D02EF8D}
function FileCRC(fs: TFileStream): Integer;
implementation
function FileCRC(fs: TFileStream): Integer;
const
CRCBlock = 4096;
var
CRCTable: Array[0..255] of LongWord;
c: LongWord; //!!! this must be an unsigned 32-bits var!
Block: Array[0..CRCBlock-1] of Byte;
i,j,bytesread: Integer;
begin
//this used to be the InitCRC procedure
For i:= 0 To 255 do begin
c:= i;
For j:= 0 To 7 do begin
If (c And 1)=0 Then begin
c:= (c div 2);
end Else begin
c:= (c div 2) Xor $EDB88320;
End;
end;
CRCTable[i]:= c;
end;
// InitCRC procedure end;
c:=$FFFFFFFF;
fs.Seek(0,soFromBeginning);
for i:=0 to (fs.Size div CRCBlock)+1 do begin
bytesread:=fs.Read(Block,CRCBlock);
for j:=0 to bytesread-1 do begin
c:=CRCTable[(c and $FF) xor Block[j]] xor (((c and $FFFFFF00) div 256) and $FFFFFF);
end;
end;
FileCRC:=c xor $FFFFFFFF;
end;
end.