Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | daniel-mar | 1 | Unit ZipFix; |
2 | |||
3 | //--------------------------------------------------------------------- |
||
4 | // Component: TZipFix |
||
5 | // Author: Angus Johnson |
||
6 | // Version: 1.1 |
||
7 | // Delphi: Versions 3 - 5. |
||
8 | // C++ Builder: Versions 1, 3 and 4 (5 Untested). |
||
9 | // Date: 30 May 2000 |
||
10 | // Copyright: © 1999-2000 Angus Johnson |
||
11 | // Email: ajohnson@rpi.net.au |
||
12 | // Distribution: Freeware. |
||
13 | // |
||
14 | // Component to repair a Zip archive when the archive's |
||
15 | // directory structure has been damaged. |
||
16 | // It will *not* fix damaged zipped data nor will it |
||
17 | // solve 'forgotten' passwords. |
||
18 | // If some zipped data has been damaged, the remaining undamaged |
||
19 | // data can be rebuilt into a new archive. |
||
20 | // Multi-disk archives can also be repaired if the disks are first |
||
21 | // concatenated (maintaining order) into a single InStream. |
||
22 | // Can also extract a zip archive which has been embedded |
||
23 | // in another file (eg a self-extracting zip archive). |
||
24 | // |
||
25 | // 21-06-2000 Added ZipFix.res RCV |
||
26 | //--------------------------------------------------------------------- |
||
27 | (* |
||
28 | Copyright (C) 2009, 2010 by Russell J. Peters, Roger Aelbrecht, |
||
29 | Eric W. Engler and Chris Vleghert. |
||
30 | |||
31 | This file is part of TZipMaster Version 1.9. |
||
32 | |||
33 | TZipMaster is free software: you can redistribute it and/or modify |
||
34 | it under the terms of the GNU Lesser General Public License as published by |
||
35 | the Free Software Foundation, either version 3 of the License, or |
||
36 | (at your option) any later version. |
||
37 | |||
38 | TZipMaster is distributed in the hope that it will be useful, |
||
39 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
40 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
41 | GNU Lesser General Public License for more details. |
||
42 | |||
43 | You should have received a copy of the GNU Lesser General Public License |
||
44 | along with TZipMaster. If not, see <http://www.gnu.org/licenses/>. |
||
45 | |||
46 | contact: problems@delphizip.org (include ZipMaster in the subject). |
||
47 | updates: http://www.delphizip.org |
||
48 | DelphiZip maillist subscribe at http://www.freelists.org/list/delphizip |
||
49 | *) |
||
50 | // Acknowledgements for suggestions can be found within code. |
||
51 | |||
52 | {$IfDef VER110} |
||
53 | {$ObjExportAll On} |
||
54 | {$DEFINE CBUILDER3} |
||
55 | {$EndIf} |
||
56 | {$IfDef VER125} |
||
57 | {$ObjExportAll On} |
||
58 | {$DEFINE CBUILDER4} |
||
59 | {$EndIf} |
||
60 | {$IfDef VER130} |
||
61 | {$IfDef BCB} |
||
62 | {$ObjExportAll On} |
||
63 | {$DEFINE CBUILDER5} |
||
64 | {$EndIf} |
||
65 | {$EndIf} |
||
66 | |||
67 | Interface |
||
68 | |||
69 | uses |
||
70 | Windows, Messages, SysUtils, Classes; |
||
71 | |||
72 | type |
||
73 | |||
74 | TOnFileFoundEvent = procedure (Sender: TObject; |
||
75 | const Filename: string; FileInfoIsOK: boolean) of object; |
||
76 | |||
77 | TZipFix = class(TComponent) |
||
78 | private |
||
79 | fBuffer: pchar; |
||
80 | fBufferSize: integer; |
||
81 | fFileList: TList; |
||
82 | fOutStream: TStream; |
||
83 | fOnFileFound: TOnFileFoundEvent; |
||
84 | fJumpValue: array[#0..#255] of integer; //used to find Local Header records |
||
85 | fJumpValue2: array[#0..#255] of integer; //used to find DataDescriptor records |
||
86 | procedure InitializeArrays; |
||
87 | procedure GetLocalHeaderInfo; |
||
88 | procedure DoZipFix; |
||
89 | public |
||
90 | procedure Execute(InStream: TMemoryStream; OutStream: TStream); {$IFDEF VER130} overload; |
||
91 | procedure Execute(const InFilename, OutFilename: string); overload; {$ENDIF} |
||
92 | published |
||
93 | //30 May 2000 - OnFileFound event moved to published |
||
94 | property OnFileFound: TOnFileFoundEvent read fOnFileFound write fOnFileFound; |
||
95 | end; |
||
96 | |||
97 | procedure Register; |
||
98 | |||
99 | Implementation |
||
100 | |||
101 | {$R ZipFix.Res} |
||
102 | |||
103 | type |
||
104 | |||
105 | //--------------------------------------------------------------------- |
||
106 | //Record structures used in Zip files... |
||
107 | //--------------------------------------------------------------------- |
||
108 | TLocalHeader = packed record |
||
109 | HeaderSig : Cardinal; // $04034b50 (4) |
||
110 | VersionNeed : Word; |
||
111 | Flag : Word; |
||
112 | ComprMethod : Word; |
||
113 | FileTime : Word; |
||
114 | FileDate : Word; |
||
115 | CRC32 : Cardinal; |
||
116 | ComprSize : Cardinal; |
||
117 | UnComprSize : Cardinal; |
||
118 | FileNameLen : Word; |
||
119 | ExtraLen : Word; |
||
120 | end; |
||
121 | |||
122 | TDataDescriptor = packed record //Exists only if bit 3 of LocalHeader.Flag is set. |
||
123 | DescriptorSig : Cardinal; //field not defined in PKWare's docs but used by WinZip |
||
124 | CRC32 : Cardinal; |
||
125 | ComprSize : Cardinal; |
||
126 | UnComprSize : Cardinal; |
||
127 | end; |
||
128 | |||
129 | (* |
||
130 | CentralDirectory = |
||
131 | array [1..N] of TCentralFileHeader; |
||
132 | TEndOfCentralHeader; |
||
133 | *) |
||
134 | |||
135 | //array of TCentralFileHeaders constitute the Central Header directory... |
||
136 | TCentralFileHeader = packed record // fixed part size = 42 bytes |
||
137 | HeaderSig : Cardinal; // $02014b50 { 'PK'#1#2 } (4) |
||
138 | MadeByVersion : Byte; //(1) |
||
139 | HostVersionNo : Byte; //(1) |
||
140 | Version : Word; //(2) version needed to extract(2) |
||
141 | Flag : Word; //(2) |
||
142 | CompressionMethod : Word; //(2) |
||
143 | FileDate : Integer; //modification date & time (4) |
||
144 | CRC32 : Integer; //(4) |
||
145 | CompressedSize : Integer; //(4) |
||
146 | UncompressedSize : Integer; //(4) |
||
147 | FileNameLength : Word; //(2) |
||
148 | ExtraFieldLength : Word; //(2) |
||
149 | FileCommentLen : Word; //(2) |
||
150 | StartOnDisk : Word; //disk # on which file starts (2) |
||
151 | IntFileAttrib : Word; //internal file attributes ie: Text/Binary(2) |
||
152 | ExtFileAttrib : Cardinal;//external file attributes(4) |
||
153 | RelOffLocalHdr : Cardinal;//relative offset of local file header(4) |
||
154 | //FileName variable size |
||
155 | //ExtraField variable size |
||
156 | //FileComment variable size |
||
157 | end; |
||
158 | |||
159 | TEndOfCentralHeader = packed record //Fixed part size = 22 bytes |
||
160 | HeaderSig : Cardinal; //$06054B50 (4) |
||
161 | ThisDiskNo : Word; //This disk's number (zero based) (2) |
||
162 | CentralDiskNo : Word; //Disk number on which central dir starts (2) |
||
163 | ThisDiskEntries : Word; //Number of central dir entries on this disk (2) |
||
164 | TotalEntries : Word; //Total entries in central dir (2) |
||
165 | CentralSize : Cardinal; //Size of central directory (4) |
||
166 | CentralOffset : Cardinal; //offset of central dir on CentralDiskNo (4) |
||
167 | ZipCommentLen : Word; //(2) |
||
168 | // ZipComment variable size |
||
169 | end; |
||
170 | |||
171 | //--------------------------------------------------------------------- |
||
172 | //Record structure used internally by the TZipFix |
||
173 | //--------------------------------------------------------------------- |
||
174 | |||
175 | pFileInfo = ^TFileInfo; |
||
176 | TFileInfo = packed record //first 42 bytes identical to the Central Header File record |
||
177 | MadeByVersion : Byte; //(1) |
||
178 | HostVersionNo : Byte; //(1) |
||
179 | Version : Word; //(2) |
||
180 | Flag : Word; //(2) |
||
181 | CompressionMethod : Word; //(2) |
||
182 | FileDate : Integer; //modification datetime (4) |
||
183 | CRC32 : Integer; //(4) |
||
184 | CompressedSize : Integer; //(4) |
||
185 | UncompressedSize : Integer; //(4) |
||
186 | FileNameLength : Word; //(2) |
||
187 | ExtraFieldLength : Word; //(2) |
||
188 | FileCommentLen : Word; //(2) |
||
189 | StartOnDisk : Word; //disk # on which file starts (2) |
||
190 | IntFileAttrib : Word; //internal file attributes ie: Text/Binary(2) |
||
191 | ExtFileAttrib : Cardinal;//external file attributes(4) |
||
192 | RelOffLocalHdr : Cardinal;//relative offset of local file header(4) |
||
193 | //42 bytes above plus... |
||
194 | Filename: string; |
||
195 | //ExtraField: string; |
||
196 | //Comment: string; |
||
197 | end; |
||
198 | |||
199 | const |
||
200 | MULTIPLE_DISK_SIG = $08074b50; // 'PK'#7#8 |
||
201 | DATA_DESCRIPT_SIG = MULTIPLE_DISK_SIG; //!! |
||
202 | LOCAL_HEADERSIG = $04034b50; // 'PK'#3#4 |
||
203 | CENTRAL_HEADERSIG = $02014b50; // 'PK'#1#2 |
||
204 | EOC_HEADERSIG = $06054b50; // 'PK'#5#6 |
||
205 | |||
206 | MAX_FILENAME_LEN = 80; |
||
207 | |||
208 | procedure Register; |
||
209 | begin |
||
210 | RegisterComponents('Samples', [TZipFix]); |
||
211 | end; |
||
212 | |||
213 | //------------------------------------------------------------------------------ |
||
214 | // TZipFix Methods |
||
215 | //------------------------------------------------------------------------------ |
||
216 | |||
217 | //fill fFileList with records containing info from the Local Headers |
||
218 | procedure TZipFix.GetLocalHeaderInfo; |
||
219 | var |
||
220 | fileInfo: pFileInfo; |
||
221 | CurrentPos: integer; |
||
222 | i, HeaderStartPos: integer; |
||
223 | DataStart,DataEnd: integer; |
||
224 | label |
||
225 | LocalHeaderError; |
||
226 | //------------------------------- |
||
227 | |||
228 | //positions CurrentPos at start of LocalHeaderSig... |
||
229 | function FindNextHeader: boolean; |
||
230 | var |
||
231 | n,HeaderSig: integer; |
||
232 | begin |
||
233 | result := false; |
||
234 | while CurrentPos < fBufferSize do |
||
235 | begin |
||
236 | n := fJumpValue[fBuffer[CurrentPos]]; |
||
237 | if n = 0 then //looking for 'PK'#3#4, a #4 found at least... |
||
238 | begin |
||
239 | dec(CurrentPos,3); |
||
240 | move(fBuffer[CurrentPos],HeaderSig,4); |
||
241 | if (HeaderSig = LOCAL_HEADERSIG) and |
||
242 | (CurrentPos + Sizeof(TLocalHeader) < fBufferSize) then |
||
243 | begin |
||
244 | result := true; |
||
245 | exit; |
||
246 | end |
||
247 | else |
||
248 | inc(CurrentPos,7); |
||
249 | end |
||
250 | else |
||
251 | inc(CurrentPos,n); |
||
252 | end; |
||
253 | end; |
||
254 | //------------------------------- |
||
255 | |||
256 | //positions fCurrentPos at start of DataDescriptorSig... |
||
257 | function FindDataDescriptor: boolean; |
||
258 | var |
||
259 | n,HeaderSig: integer; |
||
260 | begin |
||
261 | result := false; |
||
262 | while CurrentPos < fBufferSize do |
||
263 | begin |
||
264 | n := fJumpValue2[fBuffer[CurrentPos]]; |
||
265 | if n = 0 then //looking for 'PK'#7#8, a #8 found at least... |
||
266 | begin |
||
267 | dec(CurrentPos,3); |
||
268 | move(fBuffer[CurrentPos],HeaderSig,4); |
||
269 | if (HeaderSig = DATA_DESCRIPT_SIG) and |
||
270 | (CurrentPos + Sizeof(TDataDescriptor) < fBufferSize) then |
||
271 | begin |
||
272 | result := true; |
||
273 | exit; |
||
274 | end |
||
275 | else |
||
276 | inc(CurrentPos,7); |
||
277 | end |
||
278 | else |
||
279 | inc(CurrentPos,n); |
||
280 | end; |
||
281 | end; |
||
282 | //------------------------------- |
||
283 | |||
284 | begin |
||
285 | |||
286 | //prepare for boyer-moore-horspool searches... |
||
287 | //this will be more than 3 times faster than a brute-force search |
||
288 | if fJumpValue[#0] = 0 then InitializeArrays; |
||
289 | |||
290 | CurrentPos := 3; |
||
291 | |||
292 | //get all local header info... |
||
293 | while FindNextHeader do |
||
294 | begin |
||
295 | HeaderStartPos := CurrentPos; |
||
296 | new(fileInfo); |
||
297 | with fileInfo^ do |
||
298 | begin |
||
299 | //ignore the following values, so zero initialize them. |
||
300 | //we could try and match them to the dud central directory records |
||
301 | //but i'm not sure it's worth the trouble. |
||
302 | MadeByVersion := $0; |
||
303 | HostVersionNo := $0; |
||
304 | IntFileAttrib := $0; |
||
305 | ExtFileAttrib := $0; |
||
306 | StartOnDisk := $0; |
||
307 | FileCommentLen := $0; |
||
308 | |||
309 | //copy - Version, Flag, CompressionMethod, FileDate, CRC32, |
||
310 | // CompressedSize, UncompressedSize, FileNameLength, ExtraFieldLength |
||
311 | move(fBuffer[HeaderStartPos+4],Version,Sizeof(TLocalHeader)-4); |
||
312 | //save current Local Header offset which will be updated later... |
||
313 | RelOffLocalHdr := HeaderStartPos; |
||
314 | if (fileInfo.FileNameLength < 1) or |
||
315 | (FileNameLength > MAX_FILENAME_LEN) then |
||
316 | goto LocalHeaderError; |
||
317 | inc(CurrentPos, Sizeof(TLocalHeader)); |
||
318 | setlength(Filename,FileNameLength); |
||
319 | move(fBuffer[CurrentPos],Filename[1],FileNameLength); |
||
320 | //and do an extra check to make sure the name is valid... |
||
321 | for i := 1 to FileNameLength do |
||
322 | if Filename[i] < #32 then |
||
323 | begin |
||
324 | Filename := ''; |
||
325 | goto LocalHeaderError; |
||
326 | end; |
||
327 | inc(CurrentPos, FileNameLength + ExtraFieldLength); |
||
328 | if (Flag and $8) = $8 then |
||
329 | begin |
||
330 | //a bit of a bummer but a TDataDescriptor record is used |
||
331 | //so we don't yet know the size of the data block. |
||
332 | //it's a little bit slower but it still works... |
||
333 | DataStart:= CurrentPos; |
||
334 | if not FindDataDescriptor then goto LocalHeaderError; |
||
335 | DataEnd:= CurrentPos; |
||
336 | //now update: CRC32, CompressedSize, UncompressedSize |
||
337 | move(fBuffer[CurrentPos+4],CRC32,12); |
||
338 | inc(CurrentPos, sizeof(TDataDescriptor)); //get ready for next LocalHeader |
||
339 | if (CompressedSize <> DataEnd - DataStart) then |
||
340 | goto LocalHeaderError; |
||
341 | end |
||
342 | else |
||
343 | inc(CurrentPos,CompressedSize); |
||
344 | end; //with fileInfo^ |
||
345 | //check for corrupted CompressedSize - suggested by Ramon Speets |
||
346 | if (CurrentPos > fBufferSize) or |
||
347 | (fileInfo.CompressedSize > fileInfo.UncompressedSize) then |
||
348 | goto LocalHeaderError; |
||
349 | fFileList.add(fileInfo); |
||
350 | if assigned(fOnFileFound) then fOnFileFound(self,fileInfo.filename,true); |
||
351 | continue; //avoid LocalHeaderError below |
||
352 | |||
353 | LocalHeaderError: |
||
354 | if assigned(fOnFileFound) then fOnFileFound(self,fileInfo.filename,false); |
||
355 | dispose(fileInfo); |
||
356 | CurrentPos := HeaderStartPos + 4; //ie: skip over this dud record |
||
357 | end; //while FindNextHeader |
||
358 | end; |
||
359 | //------------------------------------------------------------------------------ |
||
360 | |||
361 | procedure TZipFix.InitializeArrays; |
||
362 | var |
||
363 | i: char; |
||
364 | begin |
||
365 | for i := #0 to #255 do fJumpValue[i] := 4; |
||
366 | fJumpValue['P'] := 3; |
||
367 | fJumpValue['K'] := 2; |
||
368 | fJumpValue[#3] := 1; |
||
369 | fJumpValue[#4] := 0; |
||
370 | |||
371 | for i := #0 to #255 do fJumpValue2[i] := 4; |
||
372 | fJumpValue2['P'] := 3; |
||
373 | fJumpValue2['K'] := 2; |
||
374 | fJumpValue2[#7] := 1; |
||
375 | fJumpValue2[#8] := 0; |
||
376 | end; |
||
377 | //--------------------------------------------------------------------- |
||
378 | |||
379 | procedure TZipFix.DoZipFix; |
||
380 | var |
||
381 | i,CurrentPos, StartOfCentral: integer; |
||
382 | Eoc: TEndOfCentralHeader; |
||
383 | HeaderSig: Cardinal; |
||
384 | begin |
||
385 | //fOutStream.position := 0; //not essential... |
||
386 | //could theoretically append an SFX stub. |
||
387 | GetLocalHeaderInfo; |
||
388 | if fFileList.count = 0 then exit; //no files can be restored :( |
||
389 | |||
390 | //write all the local headers and data... |
||
391 | for i := 0 to fFileList.count-1 do |
||
392 | with pFileInfo(fFileList[i])^ do |
||
393 | begin |
||
394 | CurrentPos := RelOffLocalHdr; |
||
395 | RelOffLocalHdr := fOutStream.Position; //now update RelOffLocalHdr |
||
396 | fOutStream.write(fbuffer[CurrentPos], |
||
397 | sizeof(TLocalHeader)+FileNameLength+ExtraFieldLength+CompressedSize); |
||
398 | //i'm almost certain the Central Directory ExtraField is different |
||
399 | //from the local ExtraField so zero this out for the Central Directory. |
||
400 | ExtraFieldLength := 0; |
||
401 | end; |
||
402 | StartOfCentral := fOutStream.position; |
||
403 | //recreate the central directory... |
||
404 | HeaderSig := CENTRAL_HEADERSIG; |
||
405 | for i := 0 to fFileList.count-1 do |
||
406 | with pFileInfo(fFileList[i])^ do |
||
407 | begin |
||
408 | fOutStream.write(HeaderSig,sizeof(HeaderSig)); |
||
409 | //copy first 42 bytes starting at MadeByVersion... |
||
410 | fOutStream.write(MadeByVersion,42); |
||
411 | fOutStream.write(Filename[1],length(Filename)); |
||
412 | end; |
||
413 | //finally write the EndOfCentral header... |
||
414 | Eoc.HeaderSig := EOC_HEADERSIG; |
||
415 | Eoc.ThisDiskNo := 0; |
||
416 | Eoc.CentralDiskNo := 0; |
||
417 | Eoc.ThisDiskEntries := fFileList.count; |
||
418 | Eoc.TotalEntries := Eoc.ThisDiskEntries; |
||
419 | Eoc.CentralSize := fOutStream.position - StartOfCentral; |
||
420 | Eoc.CentralOffset := StartOfCentral; |
||
421 | Eoc.ZipCommentLen := 0; |
||
422 | fOutStream.write(Eoc,sizeof(Eoc)); |
||
423 | end; |
||
424 | //--------------------------------------------------------------------- |
||
425 | |||
426 | procedure TZipFix.Execute(InStream: TMemoryStream; OutStream: TStream); |
||
427 | var |
||
428 | i: integer; |
||
429 | begin |
||
430 | if (InStream = nil) or (OutStream = nil) then |
||
431 | raise Exception.create('No input or no output stream has been defined'); |
||
432 | fBuffer := InStream.memory; |
||
433 | fBufferSize := InStream.size; |
||
434 | fOutStream := OutStream; |
||
435 | |||
436 | fFileList:= TList.create; |
||
437 | try |
||
438 | DoZipFix; //do it here!! |
||
439 | finally |
||
440 | //cleanup... |
||
441 | for i := 0 to fFileList.count -1 do |
||
442 | dispose(pFileInfo(fFileList[i])); |
||
443 | fFileList.free; |
||
444 | end; |
||
445 | end; |
||
446 | //------------------------------------------------------------------------------ |
||
447 | |||
448 | {$IFDEF VER130} |
||
449 | procedure TZipFix.Execute(const InFilename, OutFilename: string); |
||
450 | var |
||
451 | InStream: TMemoryStream; |
||
452 | OutStream: TFileStream; |
||
453 | begin |
||
454 | OutStream:= TFileStream.create(OutFilename,fmCreate); |
||
455 | InStream := TMemoryStream.create; |
||
456 | try |
||
457 | Instream.LoadFromFile(InFilename); |
||
458 | Execute(InStream, OutStream); |
||
459 | finally |
||
460 | InStream.free; |
||
461 | OutStream.free; |
||
462 | end; |
||
463 | end; |
||
464 | |||
465 | //------------------------------------------------------------------------------ |
||
466 | {$ENDIF} |
||
467 | End. |
||
468 |