-
Notifications
You must be signed in to change notification settings - Fork 15
/
UPatcher.pas
255 lines (228 loc) · 7.94 KB
/
UPatcher.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
{
* Class that applies patch to source file to re-create destination file.
*
* Based on bpatch.c by Stefan Reuther, copyright (c) 1999 Stefan Reuther
* <Streu@gmx.de>.
}
unit UPatcher;
interface
type
TPatcher = class(TObject)
private
{ Compute simple checksum }
class function CheckSum(Data: PAnsiChar; DataSize: Cardinal;
const BFCheckSum: Longint): Longint;
{ Get 32-bit quantity from char array }
class function GetLong(PCh: PAnsiChar): Longint;
{ Copy data from one stream to another, computing checksums
@param SourceFileHandle [in] Handle to file containing data to be copied.
@param DestFileHandle [in] Handle to file to receive copied data.
@param Count [in] Number of bytes to copy.
@param SourceCheckSum [in] Checksum for data to be copied
@param SourceIsPatch [in] Flag True when SourceFileHandle is patch file and
False when SourceFileHandle is source file.
}
class procedure CopyData(const SourceFileHandle, DestFileHandle: Integer;
Count, SourceCheckSum: Longint; const SourceIsPatch: Boolean);
{ Creates a temporary file in user's temp directory and returns its name }
class function GetTempFileName: string;
public
{ Apply patch from standard input to SourceFileName and regenerate
DestFileName. }
class procedure Apply(const SourceFileName, DestFileName: string);
end;
implementation
{$IOCHECKS OFF}
uses
// Delphi
Windows, SysUtils,
// Project
UAppInfo, UBPatchInfoWriter, UBPatchParams, UBPatchUtils, UErrors;
const
FORMAT_VERSION = '02'; // binary diff file format version
BUFFER_SIZE = 4096; // size of buffer used to read files
{ TPatcher }
class procedure TPatcher.Apply(const SourceFileName, DestFileName: string);
var
SourceFileHandle: Integer; // source file handle
DestFileHandle: Integer; // destination file handle
TempFileName: string; // temporary file name
Header: array[0..15] of AnsiChar; // patch file header
SourceLen: Longint; // expected length of source file
DestLen: Longint; // expected length of destination file
DataSize: Longint; // size of data to be copied to destination
SourceFilePos: Longint; // position in source file
Ch: Integer; // next character from patch, or EOF
const
ErrorMsg = 'Patch garbled - invalid section ''%''';
begin
try
// read header from patch file on standard input
if FileRead(TIO.StdIn, Header, 16) <> 16 then
Error('Patch not in BINARY format');
if StrLComp(Header, PAnsiChar('bdiff' + FORMAT_VERSION + #$1A), 8) <> 0 then
Error('Patch not in BINARY format');
// get length of source and destination files from header
SourceLen := GetLong(@Header[8]);
DestLen := GetLong(@Header[12]);
DestFileHandle := 0;
// open source file
SourceFileHandle := FileOpen(SourceFileName, fmOpenRead + fmShareDenyNone);
try
if SourceFileHandle <= 0 then
OSError;
// check destination file name
if Length(DestFileName) = 0 then
Error('Empty destination file name');
// create temporary file
TempFileName := GetTempFileName;
DestFileHandle := FileCreate(TempFileName);
if DestFileHandle <= 0 then
Error('Can''t create temporary file');
{ apply patch }
while True do
begin
Ch := TIO.GetCh(TIO.StdIn);
if Ch = EOF then
Break;
case Ch of
Integer('@'):
begin
// common block: copy from source
if FileRead(TIO.StdIn, Header, 12) <> 12 then
Error('Patch garbled - unexpected end of data');
DataSize := GetLong(@Header[4]);
SourceFilePos := GetLong(@Header[0]);
if (SourceFilePos < 0) or (DataSize <= 0)
or (SourceFilePos > SourceLen) or (DataSize > SourceLen)
or (DataSize + SourceFilePos > SourceLen) then
Error('Patch garbled - invalid change request');
if not TIO.Seek(SourceFileHandle, SourceFilePos, SEEK_SET) then
Error('Seek on source file failed');
CopyData(
SourceFileHandle,
DestFileHandle,
DataSize,
GetLong(@Header[8]),
False
);
Dec(DestLen, DataSize);
end;
Integer('+'):
begin
// add data from patch file
if FileRead(TIO.StdIn, Header, 4) <> 4 then
Error('Patch garbled - unexpected end of data');
DataSize := GetLong(@Header[0]);
CopyData(TIO.StdIn, DestFileHandle, DataSize, 0, True);
Dec(DestLen, DataSize);
end;
else
Error('Patch garbled - invalid section ''%s''', [Char(Ch)]);
end;
if DestLen < 0 then
Error('Patch garbled - patch file longer than announced in header');
end;
if DestLen <> 0 then
Error(
'Patch garbled - destination file shorter than announced in header'
);
finally
FileClose(SourceFileHandle);
FileClose(DestFileHandle);
end;
// create destination file: overwrites any existing dest file with same name
SysUtils.DeleteFile(DestFileName);
if not RenameFile(TempFileName, DestFileName) then
Error('Can''t rename temporary file');
except
on E: Exception do
begin
SysUtils.DeleteFile(TempFileName);
raise;
end;
end;
end;
class function TPatcher.CheckSum(Data: PAnsiChar; DataSize: Cardinal;
const BFCheckSum: Integer): Longint;
begin
Result := BFCheckSum;
while DataSize <> 0 do
begin
Dec(DataSize);
Result := ((Result shr 30) and 3) or (Result shl 2);
Result := Result xor PShortInt(Data)^;
Inc(Data);
end;
end;
class procedure TPatcher.CopyData(const SourceFileHandle,
DestFileHandle: Integer; Count, SourceCheckSum: Integer;
const SourceIsPatch: Boolean);
var
DestCheckSum: Longint;
Buffer: array[0..BUFFER_SIZE-1] of AnsiChar;
BytesToCopy: Cardinal;
begin
DestCheckSum := 0;
while Count <> 0 do
begin
if Count > BUFFER_SIZE then
BytesToCopy := BUFFER_SIZE
else
BytesToCopy := Count;
if FileRead(SourceFileHandle, Buffer, BytesToCopy)
<> Integer(BytesToCopy) then
begin
if TIO.AtEOF(SourceFileHandle) then
begin
if SourceIsPatch then
Error('Patch garbled - unexpected end of data')
else
Error('Source file does not match patch');
end
else
begin
if SourceIsPatch then
Error('Error reading patch file')
else
Error('Error reading source file');
end;
end;
if DestFileHandle <> 0 then
if FileWrite(DestFileHandle, Buffer, BytesToCopy)
<> Integer(BytesToCopy) then
Error('Error writing temporary file');
DestCheckSum := CheckSum(Buffer, BytesToCopy, DestCheckSum);
Dec(Count, BytesToCopy);
end;
if not SourceIsPatch and (DestCheckSum <> SourceCheckSum) then
Error('Source file does not match patch');
end;
class function TPatcher.GetLong(PCh: PAnsiChar): Longint;
var
PB: PByte;
LW: LongWord;
begin
PB := PByte(PCh);
LW := PB^;
Inc(PB);
LW := LW + 256 * PB^;
Inc(PB);
LW := LW + 65536 * PB^;
Inc(PB);
LW := LW + 16777216 * PB^;
Result := LW;
end;
class function TPatcher.GetTempFileName: string;
begin
// Get temporary folder
SetLength(Result, Windows.MAX_PATH);
Windows.GetTempPath(Windows.MAX_PATH, PChar(Result));
// Get unique temporary file name (it is created as side effect of this call)
if Windows.GetTempFileName(
PChar(Result), '', 0, PChar(Result)
) = 0 then
Error('Can''t create temporary file');
Result := PChar(Result)
end;
end.