DEADSOFTWARE

dfzip: fix exceptions
[d2df-editor.git] / src / shared / WADEDITOR_dfzip.pas
1 {$INCLUDE ../shared/a_modes.inc}
3 unit WADEDITOR_dfzip;
5 // Implementation restrictions:
6 // - File must start with LFH or EOCD signature
7 // - EOCD must be located strictly at the end of file
8 // - Multi-disk ZIP files are not supported
9 // - UTF-8 not supported yet, expected WIN1251 encoding
10 // - ZIP64 not supported
11 // - Encryption not supported
12 // - Zero-length file names not supported
13 // - CDR holds most actual data about file, LFH mostly ignored
14 // - Attributes, comments and extra data are ignored and not saved
15 // - Store and Deflate compression supported
17 interface
19 uses Classes, WADEDITOR;
21 type
22 TResource = record
23 name: AnsiString;
24 pos: UInt32;
25 csize: UInt32;
26 usize: UInt32;
27 comp: UInt32;
28 chksum: UInt32;
29 stream: TMemoryStream;
30 end;
32 TSection = record
33 name: AnsiString;
34 list: array of TResource;
35 end;
37 PResource = ^TResource;
38 PSection = ^TSection;
40 TZIPEditor = class sealed(WADEDITOR.TWADEditor)
41 private
42 FSection: array of TSection;
43 FStream: TStream;
44 FLastError: Integer;
45 FVersion: Byte;
47 function FindSectionIDRAW(name: AnsiString; caseSensitive: Boolean): Integer;
48 function FindSectionRAW(name: AnsiString; caseSensitive: Boolean): PSection;
49 function InsertSectionRAW(name: AnsiString): PSection;
51 function FindSectionID(name: AnsiString): Integer;
52 function FindSection(name: AnsiString): PSection;
53 function InsertSection(name: AnsiString): PSection;
55 function InsertFileInfo(const section, name: AnsiString; pos, csize, usize, comp, crc: UInt32): PResource;
56 function Preload(p: PResource): Boolean;
57 function GetSourceStream(p: PResource): TStream;
59 procedure ReadLFH(s: TStream; fname: AnsiString; xcsize, xusize, xcomp, xcrc: UInt32);
60 procedure ReadCDR(s: TStream; cdrid: Integer);
61 function FindEOCD(s: TStream): Boolean;
62 procedure ReadEOCD(s: TStream);
64 procedure WriteLFH(s: TStream; comp, crc, csize, usize: UInt32; const afname: AnsiString);
65 procedure WriteCDR(s: TStream; comp, crc, csize, usize, attr, offset: UInt32; const afname: AnsiString);
66 procedure SaveToStream(s: TStream);
68 public
69 constructor Create();
70 destructor Destroy(); override;
71 procedure FreeWAD(); override;
72 function ReadFile2(FileName: string): Boolean; override;
73 function ReadMemory(Data: Pointer; Len: LongWord): Boolean; override;
74 procedure CreateImage(); override;
75 function AddResource(Data: Pointer; Len: LongWord; Name, Section: String): Boolean; override; overload;
76 function AddResource(FileName, Name, Section: String): Boolean; override; overload;
77 function AddAlias(Res, Alias: String): Boolean; override;
78 procedure AddSection(Name: String); override;
79 procedure RemoveResource(Section, Resource: String); override;
80 procedure SaveTo(FileName: String); override;
81 function HaveResource(Section, Resource: String): Boolean; override;
82 function HaveSection(Section: string): Boolean; override;
83 function GetResource(Section, Resource: String; var pData: Pointer; var Len: Integer): Boolean; override;
84 function GetSectionList(): SArray; override;
85 function GetResourcesList(Section: String): SArray; override;
87 function GetLastError: Integer; override;
88 function GetLastErrorStr: String; override;
89 function GetResourcesCount: Word; override;
90 function GetVersion: Byte; override;
91 end;
93 implementation
95 uses SysUtils, StrUtils, zstream, crc, e_log;
97 const
98 ZIP_SIGN_CDR = 'PK'#1#2;
99 ZIP_SIGN_LFH = 'PK'#3#4;
100 ZIP_SIGN_EOCD = 'PK'#5#6;
102 const
103 ZIP_COMP_STORE = 0;
104 ZIP_COMP_SHRUNK = 1;
105 ZIP_COMP_REDUCE1 = 2;
106 ZIP_COMP_REDUCE2 = 3;
107 ZIP_COMP_REDUCE3 = 4;
108 ZIP_COMP_REDUCE4 = 5;
109 ZIP_COMP_IMPLODE = 6;
110 ZIP_COMP_TOKENIZED = 7;
111 ZIP_COMP_DEFLATE = 8;
112 ZIP_COMP_DEFLATE64 = 9;
113 ZIP_COMP_TERSE1 = 10;
114 ZIP_COMP_BZIP2 = 12;
115 ZIP_COMP_LZMA = 14;
116 ZIP_COMP_CMPSC = 16;
117 ZIP_COMP_TERSE2 = 18;
118 ZIP_COMP_LZ77 = 19;
119 ZIP_COMP_ZSTD1 = 20;
120 ZIP_COMP_ZSTD2 = 93;
121 ZIP_COMP_MP3 = 94;
122 ZIP_COMP_XZ = 95;
123 ZIP_COMP_JPEG = 96;
124 ZIP_COMP_WAVPACK = 97;
125 ZIP_COMP_PPMD = 98;
126 ZIP_COMP_AE = 99;
128 const
129 ZIP_SYSTEM = 0; // DOS / FAT
130 ZIP_VERSION = 20; // Min version
131 ZIP_MAXVERSION = 63; // Max supported version
133 procedure ToSectionFile(fname: AnsiString; out section, name: AnsiString); inline;
134 var i: SizeInt;
135 begin
136 i := LastDelimiter('/', fname);
137 section := Copy(fname, 1, i - 1);
138 name := Copy(fname, i + 1)
139 end;
141 function GetFileName(const Section, Name: AnsiString): AnsiString; inline;
142 begin
143 if Section = '' then
144 Result := Name
145 else
146 Result := Section + '/' + Name;
147 end;
149 function PrepString(const s: AnsiString; caseSensitive, extSensitive: Boolean): AnsiString; inline;
150 var i: Integer;
151 begin
152 Result := s;
153 if caseSensitive = False then
154 begin
155 Result := UpperCase(Result);
156 end;
157 if extSensitive = False then
158 begin
159 i := Pos('.', Result); // fix dotfiles
160 if i > 1 then
161 SetLength(Result, i - 1);
162 end;
163 end;
165 function FindResourceIDRAW(p: PSection; name: AnsiString; caseSensitive, extSensitive: Boolean): Integer;
166 var i: Integer; pname: AnsiString;
167 begin
168 if p <> nil then
169 begin
170 pname := PrepString(name, caseSensitive, extSensitive);
171 for i := 0 to High(p.list) do
172 begin
173 if PrepString(p.list[i].name, caseSensitive, extSensitive) = pname then
174 begin
175 Result := i;
176 exit;
177 end;
178 end;
179 end;
180 Result := -1;
181 end;
183 function FindResourceID(p: PSection; name: AnsiString): Integer;
184 var i: Integer;
185 begin
186 i := FindResourceIDRAW(p, name, True, True); // CaSeNaMe.Ext
187 if i < 0 then
188 begin
189 i := FindResourceIDRAW(p, name, False, True); // CASENAME.EXT
190 if i < 0 then
191 begin
192 i := FindResourceIDRAW(p, name, True, False); // CaSeNaMe
193 if i < 0 then
194 begin
195 i := FindResourceIDRAW(p, name, False, False); // CASENAME
196 end;
197 end;
198 end;
199 Result := i;
200 end;
202 function FindResource(p: PSection; name: AnsiString): PResource;
203 var i: Integer;
204 begin
205 i := FindResourceID(p, name);
206 if i >= 0 then
207 Result := @p.list[i]
208 else
209 Result := nil;
210 end;
214 function TZIPEditor.FindSectionIDRAW(name: AnsiString; caseSensitive: Boolean): Integer;
215 var i: Integer; pname: AnsiString;
216 begin
217 if FSection <> nil then
218 begin
219 pname := PrepString(name, caseSensitive, True);
220 for i := 0 to High(FSection) do
221 begin
222 if PrepString(FSection[i].name, caseSensitive, True) = pname then
223 begin
224 Result := i;
225 exit;
226 end;
227 end;
228 end;
229 Result := -1;
230 end;
232 function TZIPEditor.FindSectionRAW(name: AnsiString; caseSensitive: Boolean): PSection;
233 var i: Integer;
234 begin
235 i := FindSectionIDRAW(name, caseSensitive);
236 if i >= 0 then
237 Result := @FSection[i]
238 else
239 Result := nil;
240 end;
242 function TZIPEditor.InsertSectionRAW(name: AnsiString): PSection;
243 var i: Integer;
244 begin
245 if FSection = nil then i := 0 else i := Length(FSection);
246 SetLength(FSection, i + 1);
247 FSection[i] := Default(TSection);
248 FSection[i].name := name;
249 Result := @FSection[i];
250 end;
254 function TZIPEditor.FindSectionID(name: AnsiString): Integer;
255 var fixName: AnsiString;
256 begin
257 fixName := StringReplace(name, '\', '/', [rfReplaceAll], TStringReplaceAlgorithm.sraManySmall);
258 Result := FindSectionIDRAW(fixName, True); // CaSeNaMe
259 if Result < 0 then
260 Result := FindSectionIDRAW(fixName, False); // CASENAME
261 end;
263 function TZIPEditor.FindSection(name: AnsiString): PSection;
264 var fixName: AnsiString;
265 begin
266 fixName := StringReplace(name, '\', '/', [rfReplaceAll], TStringReplaceAlgorithm.sraManySmall);
267 Result := FindSectionRAW(fixName, True); // CaSeNaMe
268 if Result = nil then
269 Result := FindSectionRAW(fixName, False); // CASENAME
270 end;
272 function TZIPEditor.InsertSection(name: AnsiString): PSection;
273 begin
274 Result := FindSection(name);
275 if Result = nil then
276 Result := InsertSectionRAW(name);
277 end;
281 function TZIPEditor.InsertFileInfo(const section, name: AnsiString; pos, csize, usize, comp, crc: UInt32): PResource;
282 var p: PSection; i: Integer;
283 begin
284 p := FindSectionRAW(section, True);
285 if p = nil then
286 p := InsertSectionRAW(section);
287 if p.list = nil then i := 0 else i := Length(p.list);
288 SetLength(p.list, i + 1);
289 p.list[i] := Default(TResource);
290 p.list[i].name := name;
291 p.list[i].pos := pos;
292 p.list[i].csize := csize;
293 p.list[i].usize := usize;
294 p.list[i].comp := comp;
295 p.list[i].chksum := crc;
296 p.list[i].stream := nil;
297 Result := @p.list[i];
298 end;
302 function TZIPEditor.AddAlias(Res, Alias: String): Boolean;
303 begin
304 // Hard-links not supported in ZIP
305 // However, they never created by editor
306 Result := False;
307 end;
309 function TZIPEditor.AddResource(Data: Pointer; Len: LongWord; Name, Section: String): Boolean;
310 const compress: Boolean = True;
311 const level: TCompressionLevel = TCompressionLevel.clMax;
312 var s: TMemoryStream; cs: TCompressionStream; p: PResource;
313 var comp, crc: UInt32;
314 begin
315 Result := False;
316 if Name <> '' then
317 begin
318 s := TMemoryStream.Create();
319 try
320 if compress and (Len > 0) then
321 begin
322 cs := TCompressionStream.Create(level, s, True);
323 try
324 cs.WriteBuffer(PByte(Data)[0], Len);
325 cs.Flush();
326 comp := ZIP_COMP_DEFLATE;
327 finally
328 cs.Free();
329 end;
330 end;
331 if (Len = 0) or (compress = False) or (s.Size >= Len) then
332 begin
333 s.Seek(0, TSeekOrigin.soBeginning);
334 s.SetSize(Len);
335 s.WriteBuffer(PByte(Data)[0], Len);
336 comp := ZIP_COMP_STORE;
337 Assert(s.Size = Len);
338 end;
339 crc := crc32(0, nil, 0);
340 crc := crc32(crc, data, len);
341 p := InsertFileInfo(Section, Name, $ffffffff, s.Size, Len, comp, crc);
342 p.stream := s;
343 Result := True;
344 except
345 s.Free();
346 raise;
347 end;
348 end;
349 end;
351 function TZIPEditor.AddResource(FileName, Name, Section: String): Boolean;
352 var s: TFileStream; ptr: PByte;
353 begin
354 Result := False;
355 FLastError := DFWAD_ERROR_READWAD;
356 try
357 s := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
358 try
359 GetMem(ptr, s.Size);
360 try
361 s.ReadBuffer(ptr[0], s.Size);
362 Result := AddResource(ptr, s.Size, Name, Section);
363 if Result = True then FLastError := DFWAD_NOERROR;
364 finally
365 FreeMem(ptr);
366 end;
367 finally
368 s.Free();
369 end;
370 except
371 on e: EFOpenError do
372 begin
373 if gWADEditorLogLevel >= DFWAD_LOG_INFO then
374 e_WriteLog('DFZIP: AddResource: failed to open file ' + FileName, MSG_NOTIFY);
375 FLastError := DFWAD_ERROR_CANTOPENWAD;
376 end;
377 end;
378 end;
380 constructor TZIPEditor.Create();
381 begin
382 FSection := nil;
383 FStream := nil;
384 FLastError := DFWAD_NOERROR;
385 FVersion := ZIP_VERSION;
386 FreeWAD();
387 end;
389 destructor TZIPEditor.Destroy();
390 begin
391 FreeWAD();
392 inherited;
393 end;
395 procedure TZIPEditor.FreeWAD();
396 var i, j: Integer;
397 begin
398 if FSection <> nil then
399 begin
400 for i := 0 to High(FSection) do
401 begin
402 if FSection[i].list <> nil then
403 begin
404 for j := 0 to High(FSection[i].list) do
405 begin
406 if FSection[i].list[j].stream <> nil then
407 begin
408 FreeAndNil(FSection[i].list[j].stream);
409 end;
410 end;
411 SetLength(FSection[i].list, 0);
412 end;
413 end;
414 SetLength(FSection, 0);
415 end;
416 if FStream <> nil then
417 begin
418 FreeAndNil(FStream);
419 end;
420 FLastError := DFWAD_NOERROR;
421 FVersion := ZIP_VERSION;
422 end;
424 function TZIPEditor.Preload(p: PResource): Boolean;
425 var s: TMemoryStream;
426 begin
427 Result := False;
428 if p <> nil then
429 begin
430 Result := p.stream <> nil;
431 if (p.stream = nil) and (FStream <> nil) then
432 begin
433 s := TMemoryStream.Create();
434 try
435 if p.csize > 0 then
436 begin
437 FStream.Seek(p.pos, TSeekOrigin.soBeginning);
438 s.CopyFrom(FStream, p.csize);
439 end;
440 Assert(s.Size = p.csize); // wtf, random size if copied zero bytes!
441 p.stream := s;
442 Result := True;
443 except
444 s.Free();
445 end;
446 end;
447 end;
448 end;
450 procedure TZIPEditor.CreateImage();
451 var i, j: Integer;
452 begin
453 if FStream = nil then
454 begin
455 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
456 e_WriteLog('DFZIP: CreateImage: File not assigned', MSG_NOTIFY);
457 FLastError := DFWAD_ERROR_WADNOTLOADED;
458 end
459 else if FStream is TMemoryStream then
460 begin
461 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
462 e_WriteLog('DFZIP: CreateImage: Memory stream', MSG_NOTIFY);
463 FLastError := DFWAD_NOERROR;
464 end
465 else
466 begin
467 if FSection <> nil then
468 begin
469 for i := 0 to High(FSection) do
470 begin
471 if FSection[i].list <> nil then
472 begin
473 for j := 0 to High(FSection[i].list) do
474 begin
475 if Preload(@FSection[i].list[j]) = False then
476 begin
477 if gWADEditorLogLevel >= DFWAD_LOG_WARN then
478 e_WriteLog('DFZIP: CreateImage: failed to preload resource [' + FSection[i].name + '][' + FSection[i].list[j].name + ']', MSG_WARNING);
479 FLastError := DFWAD_ERROR_CANTOPENWAD;
480 exit;
481 end;
482 end;
483 end;
484 end;
485 end;
486 FreeAndNil(FStream);
487 FLastError := DFWAD_NOERROR;
488 end;
489 end;
491 procedure TZIPEditor.AddSection(Name: String);
492 begin
493 if InsertSection(Name) = nil then
494 raise Exception.Create('DFZIP: AddSection[' + Name + ']: failed to insert');
495 end;
497 function TZIPEditor.HaveResource(Section, Resource: String): Boolean;
498 begin
499 Result := FindResource(FindSection(Section), Resource) <> nil;
500 end;
502 function TZIPEditor.HaveSection(Section: String): Boolean;
503 begin
504 Result := FindSection(Section) <> nil;
505 end;
507 function TZIPEditor.GetSourceStream(p: PResource): TStream;
508 var src: TStream;
509 begin
510 src := nil;
511 if p.stream <> nil then
512 begin
513 src := p.stream;
514 src.Seek(0, TSeekOrigin.soBeginning);
515 end
516 else if FStream <> nil then
517 begin
518 src := FStream;
519 src.Seek(p.pos, TSeekOrigin.soBeginning);
520 end;
521 Result := src;
522 end;
524 function TZIPEditor.GetResource(Section, Resource: String; var pData: Pointer; var Len: Integer): Boolean;
525 var p: PResource; ptr: PByte; src: TStream; tmp: TDecompressionStream; crc: UInt32;
526 begin
527 FLastError := DFWAD_ERROR_CANTOPENWAD;
528 Result := False;
529 pData := nil;
530 Len := 0;
531 p := FindResource(FindSection(Section), Resource);
532 if p <> nil then
533 begin
534 src := GetSourceStream(p);
535 if src <> nil then
536 begin
537 case p.comp of
538 ZIP_COMP_STORE:
539 begin
540 Assert(p.csize = p.usize);
541 GetMem(ptr, p.usize);
542 try
543 try
544 src.ReadBuffer(ptr[0], p.usize);
545 Result := True;
546 except
547 FreeMem(ptr);
548 raise;
549 end;
550 except on e: EReadError do
551 if gWADEditorLogLevel >= DFWAD_LOG_WARN then
552 e_WriteLog('DFZIP: Failed to read STOREd data, reason: ' + e.Message, MSG_WARNING);
553 end;
554 end;
555 ZIP_COMP_DEFLATE:
556 try
557 tmp := TDecompressionStream.Create(src, True);
558 try
559 GetMem(ptr, p.usize);
560 try
561 tmp.ReadBuffer(ptr[0], p.usize);
562 Result := True;
563 except
564 FreeMem(ptr);
565 raise;
566 end;
567 finally
568 tmp.Free();
569 end;
570 except
571 on e: EStreamError do
572 begin
573 if gWADEditorLogLevel >= DFWAD_LOG_INFO then
574 e_WriteLog('DFZIP: Failed to decompress DEFLATEd data, reason: ' + e.Message, MSG_WARNING);
575 raise e;
576 end;
577 end;
578 otherwise
579 if gWADEditorLogLevel >= DFWAD_LOG_INFO then
580 e_WriteLog('DFZIP: Unsupported compression method: ' + IntToStr(p.comp), MSG_WARNING);
581 end;
582 end
583 else
584 begin
585 if gWADEditorLogLevel >= DFWAD_LOG_WARN then
586 e_WriteLog('DFZIP: No available source for file data', MSG_WARNING);
587 FLastError := DFWAD_ERROR_WADNOTLOADED;
588 end;
589 if Result = True then
590 begin
591 crc := crc32(0, nil, 0);
592 crc := crc32(crc, ptr, p.usize);
593 Result := crc = p.chksum;
594 if Result = True then
595 begin
596 pData := ptr;
597 Len := p.usize;
598 FLastError := DFWAD_NOERROR;
599 end
600 else
601 begin
602 if gWADEditorLogLevel >= DFWAD_LOG_INFO then
603 e_WriteLog('DFZIP: File integrity check failed: expected CRC32 $' + IntToHex(p.chksum, 8) + ', calculated CRC32 $' + IntToHex(crc, 8), MSG_WARNING);
604 FreeMem(ptr);
605 end;
606 end;
607 end
608 else
609 begin
610 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
611 e_WriteLog('DFZIP: Resource not found', MSG_NOTIFY);
612 FLastError := DFWAD_ERROR_RESOURCENOTFOUND;
613 end;
614 end;
616 function TZIPEditor.GetResourcesList(Section: String): SArray;
617 var p: PSection; i: Integer;
618 begin
619 Result := nil;
620 p := FindSection(Section);
621 if (p <> nil) and (p.list <> nil) then
622 begin
623 SetLength(Result, Length(p.list));
624 for i := 0 to High(p.list) do
625 begin
626 Result[i] := p.list[i].name;
627 end;
628 end;
629 end;
631 function TZIPEditor.GetSectionList(): SArray;
632 var i: Integer;
633 begin
634 Result := nil;
635 if FSection <> nil then
636 begin
637 SetLength(Result, Length(FSection));
638 for i := 0 to High(FSection) do
639 begin
640 Result[i] := FSection[i].name;
641 end;
642 end;
643 end;
645 procedure TZIPEditor.ReadLFH(s: TStream; fname: AnsiString; xcsize, xusize, xcomp, xcrc: UInt32);
646 var sig: packed array [0..3] of Char;
647 var va, vb, flags, comp: UInt16;
648 var mtime, crc, csize, usize: UInt32;
649 var fnlen, extlen: UInt16;
650 var mypos, datapos: UInt64;
651 var section, name: AnsiString;
652 var p: Pointer;
653 begin
654 mypos := s.Position;
655 if mypos + 30 <= s.Size then
656 begin
657 s.ReadBuffer(sig[0], 4);
658 if sig = ZIP_SIGN_LFH then
659 begin
660 va := s.ReadByte(); // Min Version
661 vb := s.ReadByte(); // Min System
662 flags := LEtoN(s.ReadWord());
663 comp := LEtoN(s.ReadWord());
664 mtime := LEtoN(s.ReadDWord());
665 crc := LEtoN(s.ReadDWord());
666 csize := LEtoN(s.ReadDWord());
667 usize := LEtoN(s.ReadDWord());
668 fnlen := LEtoN(s.ReadWord());
669 extlen := LEtoN(s.ReadWord());
670 datapos := s.Position + fnlen + extlen;
671 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
672 begin
673 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Min Version : ' + IntToStr(va), MSG_NOTIFY);
674 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Min System : ' + IntToStr(vb), MSG_NOTIFY);
675 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Flags : $' + IntToHex(flags, 4), MSG_NOTIFY);
676 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Compression : ' + IntToStr(comp), MSG_NOTIFY);
677 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Modification Time : $' + IntToHex(mtime, 8), MSG_NOTIFY);
678 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': CRC-32 : $' + IntToHex(crc, 8), MSG_NOTIFY);
679 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Compressed size : ' + IntToStr(csize), MSG_NOTIFY);
680 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Decompressed size : ' + IntToStr(usize), MSG_NOTIFY);
681 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Name Length : ' + IntToStr(fnlen), MSG_NOTIFY);
682 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Extension Length : ' + IntToStr(extlen), MSG_NOTIFY);
683 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': <DATA OFFSET> : $' + IntToHex(datapos, 8), MSG_NOTIFY);
684 end;
685 if (va >= 10) and (va <= ZIP_MAXVERSION) then
686 begin
687 if datapos + xcsize <= s.Size then
688 begin
689 ToSectionFile(fname, section, name);
690 if name = '' then
691 begin
692 p := FindSectionRAW(section, True);
693 if p = nil then
694 p := InsertSectionRAW(section)
695 end
696 else
697 begin
698 p := InsertFileInfo(section, name, datapos, xcsize, xusize, xcomp, xcrc);
699 end;
700 if p = nil then
701 raise Exception.Create('Failed to register resource [' + fname + ']');
702 end
703 else
704 raise Exception.Create('Invalid LFH size (corrupted file?)');
705 end
706 else
707 begin
708 FLastError := DFWAD_ERROR_WRONGVERSION;
709 raise Exception.Create('Unsupported CDR version ' + IntToStr(va) + ', not in range [10..' + IntToStr(ZIP_MAXVERSION) + ']');
710 end;
711 end
712 else
713 raise Exception.Create('Invalid LFH signature $' +IntToHex(Ord(sig[0]), 2) + ' $' +IntToHex(Ord(sig[1]), 2) + ' $' +IntToHex(Ord(sig[2]), 2) + ' $' +IntToHex(Ord(sig[3]), 2) + ' (corrupted file?)');
714 end
715 else
716 raise Exception.Create('Invalid LFH size (corrupted file?)');
717 end;
719 procedure TZIPEditor.ReadCDR(s: TStream; cdrid: Integer);
720 const ZIP_ENCRYPTION_MASK = (1 << 0) or (1 << 6) or (1 << 13);
721 var sig: packed array [0..3] of Char;
722 var vva, vvb, va, vb, flags, comp: UInt16;
723 var mtime, crc, csize, usize: UInt32;
724 var fnlen, extlen, comlen, disk, iattr: UInt16;
725 var eattr, offset: UInt32;
726 var mypos, next: UInt64;
727 var name: PChar;
728 begin
729 mypos := s.Position;
730 s.ReadBuffer(sig[0], 4);
731 if sig = ZIP_SIGN_CDR then
732 begin
733 // Valid Central Directory Signature
734 vva := s.ReadByte(); // Writer Version
735 vvb := s.ReadByte(); // Writer System
736 va := s.ReadByte(); // Min Version
737 vb := s.ReadByte(); // Min System
738 flags := LEtoN(s.ReadWord());
739 comp := LEtoN(s.ReadWord());
740 mtime := LEtoN(s.ReadDWord());
741 crc := LEtoN(s.ReadDWord());
742 csize := LEtoN(s.ReadDWord());
743 usize := LEtoN(s.ReadDWord());
744 fnlen := LEtoN(s.ReadWord());
745 extlen := LEtoN(s.ReadWord());
746 comlen := LEtoN(s.ReadWord());
747 disk := LEtoN(s.ReadWord());
748 iattr := LEtoN(s.ReadWord());
749 eattr := LEtoN(s.ReadDWord());
750 offset := LEtoN(s.ReadDWord());
751 next := s.Position + fnlen + extlen + comlen;
752 FVersion := va;
753 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
754 begin
755 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Writer Version : ' + IntToStr(vva), MSG_NOTIFY);
756 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Writer System : ' + IntToStr(vvb), MSG_NOTIFY);
757 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Min Version : ' + IntToStr(va), MSG_NOTIFY);
758 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Min System : ' + IntToStr(vb), MSG_NOTIFY);
759 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Flags : $' + IntToHex(flags, 4), MSG_NOTIFY);
760 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Compression : ' + IntToStr(comp), MSG_NOTIFY);
761 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Modification Time : $' + IntToHex(mtime, 8), MSG_NOTIFY);
762 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': CRC-32 : $' + IntToHex(crc, 8), MSG_NOTIFY);
763 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Compressed size : ' + IntToStr(csize), MSG_NOTIFY);
764 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Decompressed size : ' + IntToStr(usize), MSG_NOTIFY);
765 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Name Length : ' + IntToStr(fnlen), MSG_NOTIFY);
766 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Extension Length : ' + IntToStr(extlen), MSG_NOTIFY);
767 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Comment Length : ' + IntToStr(comlen), MSG_NOTIFY);
768 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Disk : ' + IntToStr(disk), MSG_NOTIFY);
769 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Internal Attrib : $' + IntToHex(iattr, 4), MSG_NOTIFY);
770 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': External Attrib : $' + IntToHex(iattr, 8), MSG_NOTIFY);
771 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': LFH Offset : $' + IntToHex(offset, 8), MSG_NOTIFY);
772 end;
773 if (va >= 10) and (va <= ZIP_MAXVERSION) then
774 begin
775 if (flags and ZIP_ENCRYPTION_MASK) = 0 then
776 begin
777 if (csize <> $ffffffff) and (usize <> $ffffffff) and (disk <> $ffff) and (offset <> $ffffffff) then
778 begin
779 if disk = 0 then
780 begin
781 if (next <= s.Size) and (fnlen > 0) then
782 begin
783 case comp of
784 ZIP_COMP_STORE:
785 if csize <> usize then
786 raise Exception.Create('Compressed size ' + IntToStr(csize) + ' != Descompressed size ' + IntToStr(usize) + 'for STORE method (corrupted file?)');
787 ZIP_COMP_SHRUNK,
788 ZIP_COMP_REDUCE1,
789 ZIP_COMP_REDUCE2,
790 ZIP_COMP_REDUCE3,
791 ZIP_COMP_REDUCE4,
792 ZIP_COMP_IMPLODE,
793 ZIP_COMP_DEFLATE,
794 ZIP_COMP_DEFLATE64,
795 ZIP_COMP_TERSE1,
796 ZIP_COMP_BZIP2,
797 ZIP_COMP_LZMA,
798 ZIP_COMP_CMPSC,
799 ZIP_COMP_TERSE2,
800 ZIP_COMP_LZ77,
801 ZIP_COMP_ZSTD1,
802 ZIP_COMP_ZSTD2,
803 ZIP_COMP_MP3,
804 ZIP_COMP_XZ,
805 ZIP_COMP_JPEG,
806 ZIP_COMP_WAVPACK,
807 ZIP_COMP_PPMD:
808 ; // ok
809 ZIP_COMP_AE:
810 raise Exception.Create('Encrypted archives not supported');
811 otherwise
812 raise Exception.Create('Unknown compression method ' + IntToStr(comp));
813 end;
814 // TODO: check bit 11 (UTF8 name and comment)
815 GetMem(name, UInt32(fnlen) + 1);
816 try
817 s.ReadBuffer(name[0], fnlen);
818 name[fnlen] := #0;
819 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
820 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Name : "' + name + '"', MSG_NOTIFY);
821 s.Seek(offset, TSeekOrigin.soBeginning);
822 ReadLFH(s, name, csize, usize, comp, crc);
823 finally
824 s.Seek(next, TSeekOrigin.soBeginning);
825 FreeMem(name);
826 end;
827 end
828 else
829 raise Exception.Create('Empty files names not supported');
830 end
831 else
832 raise Exception.Create('Splitted archives not supported');
833 end
834 else
835 begin
836 FLastError := DFWAD_ERROR_WRONGVERSION;
837 raise Exception.Create('ZIP64 not supported');
838 end;
839 end
840 else
841 begin
842 FLastError := DFWAD_ERROR_READWAD;
843 raise Exception.Create('Encrypted archives not supported');
844 end;
845 end
846 else
847 begin
848 FLastError := DFWAD_ERROR_WRONGVERSION;
849 raise Exception.Create('Unsupported CDR version ' + IntToStr(va) + ', not in range [10..' + IntToStr(ZIP_MAXVERSION) + ']');
850 end;
851 end
852 else
853 raise Exception.Create('Invalid CDR signature $' + IntToHex(Ord(sig[0]), 2) + ' $' +IntToHex(Ord(sig[1]), 2) + ' $' +IntToHex(Ord(sig[2]), 2) + ' $' +IntToHex(Ord(sig[3]), 2) + ' (corrupted file?)');
854 end;
856 function TZIPEditor.FindEOCD(s: TStream): Boolean;
857 const maxedir = 20; // end of central directory entry
858 const maxecdir = maxedir + 65536; // + comment
859 var sig: packed array [0..3] of Char; off, lim: Int64;
860 begin
861 Result := False;
862 if s.Size >= maxedir then
863 begin
864 if s.Size < maxecdir then lim := s.Size else lim := maxecdir;
865 lim := lim - maxedir;
866 off := maxedir;
867 while (off <= lim) and (Result = False) do
868 begin
869 s.Seek(s.Size - off, TSeekOrigin.soBeginning);
870 s.ReadBuffer(sig[0], 4);
871 Result := sig = ZIP_SIGN_EOCD;
872 Inc(off);
873 end;
874 end;
875 end;
877 procedure TZIPEditor.ReadEOCD(s: TStream);
878 var sig: packed array [0..3] of Char;
879 var idisk, ndisk, nrec, total, comlen: UInt16;
880 var csize, cpos, i: UInt32;
881 var mypos: UInt64;
882 begin
883 FLastError := DFWAD_ERROR_FILENOTWAD;
884 FVersion := 0;
885 s.ReadBuffer(sig[0], 4);
886 if (sig = ZIP_SIGN_LFH) or (sig = ZIP_SIGN_EOCD) then
887 begin
888 if FindEOCD(s) then
889 begin
890 // End of Central Directory found
891 FLastError := DFWAD_ERROR_READWAD;
892 mypos := s.Position - 4;
893 idisk := LEtoN(s.ReadWord());
894 ndisk := LEtoN(s.ReadWord());
895 nrec := LEtoN(s.ReadWord());
896 total := LEtoN(s.ReadWord());
897 csize := LEtoN(s.ReadDWord());
898 cpos := LEtoN(s.ReadDWord());
899 comlen := LEtoN(s.ReadWord());
900 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
901 begin
902 e_WriteLog('==============================================', MSG_NOTIFY);
903 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Disk ID : ' + IntToStr(idisk), MSG_NOTIFY);
904 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Disk ID with CD : ' + IntToStr(ndisk), MSG_NOTIFY);
905 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Available CDR''s : ' + IntToStr(nrec), MSG_NOTIFY);
906 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Total CDR''s : ' + IntToStr(total), MSG_NOTIFY);
907 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': CD Length : ' + IntToStr(csize), MSG_NOTIFY);
908 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': CD Offset : $' + IntToHex(cpos, 8), MSG_NOTIFY);
909 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Comment Length : ' + IntToStr(comlen), MSG_NOTIFY);
910 end;
911 if (idisk <> $ffff) and (ndisk <> $ffff) and (nrec <> $ffff) and (total <> $ffff) and (csize <> $ffffffff) and (cpos <> $ffffffff) then
912 begin
913 if s.Position + comlen = s.Size then
914 begin
915 if (idisk = 0) and (ndisk = 0) and (nrec = total) then
916 begin
917 if (nrec * 46 <= csize) and (UInt64(cpos) + csize <= s.Size) then
918 begin
919 if total > 0 then
920 begin
921 i := 0;
922 s.Seek(cpos, TSeekOrigin.soBeginning);
923 while i < nrec do
924 begin
925 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
926 e_WriteLog('==============================================', MSG_NOTIFY);
927 ReadCDR(s, i);
928 Inc(i);
929 end;
930 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
931 e_WriteLog('==============================================', MSG_NOTIFY);
932 end;
933 end
934 else
935 raise Exception.Create('Central Directory too big (corrupted file?)');
936 end
937 else
938 raise Exception.Create('Splitted archives not supported');
939 end
940 else
941 raise Exception.Create('EOCD too big (corrupted file?)');
942 end
943 else
944 raise Exception.Create('ZIP64 not supported');
945 end
946 else
947 raise Exception.Create('EOCD not found (corrupted file?)');
948 end
949 else
950 raise Exception.Create('Not DFZIP formated file');
951 end;
953 function TZIPEditor.ReadFile2(FileName: String): Boolean;
954 var s: TFileStream;
955 begin
956 FreeWAD();
957 Result := False;
958 try
959 try
960 s := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
961 try
962 ReadEOCD(s);
963 FStream := s;
964 FLastError := DFWAD_NOERROR;
965 Result := True;
966 except
967 s.Free();
968 raise;
969 end;
970 except
971 on e: Exception do
972 begin
973 if gWADEditorLogLevel >= DFWAD_LOG_INFO then
974 e_WriteLog('ZIP: Failed to read ZIP from file ' + FileName + ', reason: ' + e.Message, MSG_WARNING);
975 FreeWAD();
976 end;
977 end;
978 except
979 on e: EFOpenError do
980 begin
981 if gWADEditorLogLevel >= DFWAD_LOG_INFO then
982 e_WriteLog('DFZIP: Failed to open file ' + FileName + ', reason: ' + e.Message, MSG_WARNING);
983 if FileExists(FileName) then
984 FLastError := DFWAD_ERROR_CANTOPENWAD
985 else
986 FLastError := DFWAD_ERROR_WADNOTFOUND;
987 end;
988 end;
989 end;
991 function TZIPEditor.ReadMemory(Data: Pointer; Len: LongWord): Boolean;
992 var s: TMemoryStream;
993 begin
994 FreeWAD();
995 Result := False;
996 try
997 s := TMemoryStream.Create;
998 try
999 s.SetSize(Len);
1000 s.WriteBuffer(PByte(Data)[0], Len);
1001 s.Seek(0, soBeginning);
1002 ReadEOCD(s);
1003 FStream := s;
1004 FLastError := DFWAD_NOERROR;
1005 Result := True;
1006 except
1007 s.Free();
1008 raise;
1009 end;
1010 except
1011 on e: Exception do
1012 begin
1013 if gWADEditorLogLevel >= DFWAD_LOG_INFO then
1014 e_WriteLog('DFZIP: Failed to read ZIP from memory, reason: ' + e.Message, MSG_WARNING);
1015 FreeWAD();
1016 end;
1017 end;
1018 end;
1020 procedure TZIPEditor.RemoveResource(Section, Resource: String);
1021 var p: PSection; i: Integer;
1022 begin
1023 p := FindSection(Section);
1024 i := FindResourceID(p, Resource);
1025 if i >= 0 then
1026 begin
1027 if p.list[i].stream <> nil then
1028 FreeAndNil(p.list[i].stream);
1029 for i := i + 1 to High(p.list) do
1030 begin
1031 p.list[i - 1] := p.list[i];
1032 end;
1033 SetLength(p.list, High(p.list));
1034 end;
1035 end;
1037 procedure TZIPEditor.WriteLFH(s: TStream; comp, crc, csize, usize: UInt32; const afname: AnsiString);
1038 var fname: PChar; flen: UInt16;
1039 begin
1040 fname := PChar(afname);
1041 flen := Length(fname);
1042 s.WriteBuffer(ZIP_SIGN_LFH, 4); // LFH Signature
1043 s.WriteByte(ZIP_VERSION); // Min version
1044 s.WriteByte(ZIP_SYSTEM); // System
1045 s.WriteWord(NtoLE(0)); // Flags
1046 s.WriteWord(NtoLE(comp)); // Compression method
1047 s.WriteDWord(NtoLE(0)); // Modification time/date
1048 s.WriteDWord(NtoLE(crc)); // CRC-32
1049 s.WriteDWord(NtoLE(csize)); // Compressed size
1050 s.WriteDWord(NtoLE(usize)); // Decompressed size
1051 s.WriteWord(NtoLE(flen)); // Name field length
1052 s.WriteWord(NtoLE(0)); // Extra field length
1053 s.WriteBuffer(fname[0], flen); // File Name
1054 end;
1056 procedure TZIPEditor.WriteCDR(s: TStream; comp, crc, csize, usize, attr, offset: UInt32; const afname: AnsiString);
1057 var fname: PChar; flen: UInt16;
1058 begin
1059 fname := PChar(afname);
1060 flen := Length(fname);
1061 s.WriteBuffer(ZIP_SIGN_CDR, 4); // CDR Signature
1062 s.WriteByte(ZIP_MAXVERSION); // Used version
1063 s.WriteByte(ZIP_SYSTEM); // Used system
1064 s.WriteByte(ZIP_VERSION); // Min version
1065 s.WriteByte(ZIP_SYSTEM); // Min system
1066 s.WriteWord(NtoLE(0)); // Flags
1067 s.WriteWord(NtoLE(comp)); // Compression method
1068 s.WriteDWord(NtoLE(0)); // Modification time/date
1069 s.WriteDWord(NtoLE(crc)); // CRC-32
1070 s.WriteDWord(NtoLE(csize)); // Compressed size
1071 s.WriteDWord(NtoLE(usize)); // Decompressed size
1072 s.WriteWord(NtoLE(flen)); // Name field length
1073 s.WriteWord(NtoLE(0)); // Extra field length
1074 s.WriteWord(NtoLE(0)); // Comment field length
1075 s.WriteWord(NtoLE(0)); // Disk
1076 s.WriteWord(NtoLE(0)); // Internal attributes
1077 s.WriteDWord(NtoLE(attr)); // External attributes
1078 s.WriteDWord(NtoLE(offset)); // LFH offset
1079 s.WriteBuffer(fname[0], flen); // File Name
1080 end;
1082 procedure TZIPEditor.SaveToStream(s: TStream);
1083 var i, j: Integer;
1084 var start, offset, loffset, size, zcrc, count: UInt32;
1085 var p: PResource;
1086 var afname: AnsiString;
1087 begin
1088 // Write LFH headers and data
1089 start := s.Position;
1090 zcrc := crc32(0, nil, 0);
1091 if FSection <> nil then
1092 begin
1093 for i := 0 to High(FSection) do
1094 begin
1095 if FSection[i].list <> nil then
1096 begin
1097 for j := 0 to High(FSection[i].list) do
1098 begin
1099 p := @FSection[i].list[j];
1100 afname := GetFileName(FSection[i].name, p.name);
1101 WriteLFH(s, p.comp, p.chksum, p.csize, p.usize, afname);
1102 if p.stream <> nil then
1103 begin
1104 Assert(p.stream.Size = p.csize);
1105 p.stream.SaveToStream(s);
1106 end
1107 else if FStream <> nil then
1108 begin
1109 FStream.Seek(p.pos, TSeekOrigin.soBeginning);
1110 s.CopyFrom(FStream, p.csize);
1111 end
1112 else
1113 begin
1114 raise Exception.Create('No data source available (somethig very wrong)');
1115 end;
1116 end;
1117 end
1118 else
1119 begin
1120 afname := GetFileName(FSection[i].name, '');
1121 WriteLFH(s, ZIP_COMP_STORE, zcrc, 0, 0, afname);
1122 end;
1123 end;
1124 end;
1125 // Write CDR headers
1126 count := 0;
1127 loffset := start;
1128 offset := s.Position - start;
1129 if FSection <> nil then
1130 begin
1131 for i := 0 to High(FSection) do
1132 begin
1133 if FSection[i].list <> nil then
1134 begin
1135 for j := 0 to High(FSection[i].list) do
1136 begin
1137 p := @FSection[i].list[j];
1138 afname := GetFileName(FSection[i].name, p.name);
1139 WriteCDR(s, p.comp, p.chksum, p.csize, p.usize, 0, loffset - start, afname);
1140 loffset := loffset + 30 + Length(afname) + p.csize;
1141 Inc(count);
1142 end;
1143 end
1144 else
1145 begin
1146 afname := GetFileName(FSection[i].name, '');
1147 WriteCDR(s, ZIP_COMP_STORE, zcrc, 0, 0, $10, loffset - start, afname);
1148 loffset := loffset + 30 + Length(afname) + 0;
1149 Inc(count);
1150 end;
1151 end;
1152 end;
1153 Assert(loffset = offset);
1154 Assert(count < $ffff);
1155 size := s.Position - start - offset;
1156 // Write EOCD header
1157 s.WriteBuffer(ZIP_SIGN_EOCD, 4); // EOCD Signature
1158 s.WriteWord(NtoLE(0)); // Disk
1159 s.WriteWord(NtoLE(0)); // Num of Disks
1160 s.WriteWord(NtoLE(count)); // Num of CDRs
1161 s.WriteWord(NtoLE(count)); // Total CDR entries
1162 s.WriteDWord(NtoLE(size)); // Central Directory size
1163 s.WriteDWord(NtoLE(offset)); // Central Directory offset
1164 s.WriteWord(NtoLE(0)); // Comment field length
1165 end;
1167 procedure TZIPEditor.SaveTo(FileName: String);
1168 var s: TFileStream;
1169 begin
1170 try
1171 s := TFileStream.Create(FileName, fmCreate);
1172 try
1173 SaveToStream(s);
1174 finally
1175 s.Free();
1176 end;
1177 except
1178 on e: Exception do
1179 begin
1180 if gWADEditorLogLevel >= DFWAD_LOG_INFO then
1181 e_WriteLog('ZIP: Failed to create file ' + FileName + ', reason: ' + e.Message, MSG_WARNING);
1182 raise e;
1183 end;
1184 end;
1185 end;
1187 function TZIPEditor.GetLastError: Integer;
1188 begin
1189 Result := FLastError;
1190 end;
1192 function TZIPEditor.GetLastErrorStr: String;
1193 begin
1194 case FLastError of
1195 DFWAD_NOERROR: Result := '';
1196 DFWAD_ERROR_WADNOTFOUND: Result := 'DFZIP file not found';
1197 DFWAD_ERROR_CANTOPENWAD: Result := 'Can''t open DFZIP file';
1198 DFWAD_ERROR_RESOURCENOTFOUND: Result := 'Resource not found';
1199 DFWAD_ERROR_FILENOTWAD: Result := 'File is not DFZIP';
1200 DFWAD_ERROR_WADNOTLOADED: Result := 'DFZIP file is not loaded';
1201 DFWAD_ERROR_READRESOURCE: Result := 'Read resource error';
1202 DFWAD_ERROR_READWAD: Result := 'Read DFZIP error';
1203 otherwise Result := IntToStr(FLastError);
1204 end;
1205 end;
1207 function TZIPEditor.GetResourcesCount: Word;
1208 var i: Integer;
1209 begin
1210 Result := 0;
1211 if FSection <> nil then
1212 begin
1213 Result := Result + Length(FSection);
1214 for i := 0 to High(FSection) do
1215 if FSection[i].list <> nil then
1216 Result := Result + Length(FSection[i].list);
1217 end;
1218 end;
1220 function TZIPEditor.GetVersion: Byte;
1221 begin
1222 Result := FVersion;
1223 end;
1225 begin
1226 gWADEditorFactory.RegisterEditor('DFZIP', TZIPEditor);
1227 end.