DEADSOFTWARE

dfzip: preserve comments
[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 // - Expect UTF-8 or CP1251 encoded names
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 mtime: UInt32;
30 flags: UInt32;
31 comment: AnsiString;
32 stream: TMemoryStream;
33 end;
35 TSection = record
36 name: AnsiString;
37 mtime: UInt32;
38 comment: AnsiString;
39 list: array of TResource;
40 end;
42 PResource = ^TResource;
43 PSection = ^TSection;
45 TZIPEditor = class sealed(WADEDITOR.TWADEditor)
46 private
47 FSection: array of TSection;
48 FStream: TStream;
49 FComment: AnsiString;
50 FLastError: Integer;
51 FVersion: Byte;
53 function FindSectionIDRAW(name: AnsiString; caseSensitive: Boolean): Integer;
54 function FindSectionRAW(name: AnsiString; caseSensitive: Boolean): PSection;
55 function InsertSectionRAW(name: AnsiString; mtime: UInt32; comment: AnsiString): PSection;
57 function FindSectionID(name: AnsiString): Integer;
58 function FindSection(name: AnsiString): PSection;
59 function InsertSection(name: AnsiString; mtime: UInt32; comment: AnsiString): PSection;
61 function InsertFileInfo(const section, name: AnsiString; pos, csize, usize, comp, crc, mtime, flags: UInt32; comment: AnsiString): PResource;
62 function Preload(p: PResource): Boolean;
63 function GetSourceStream(p: PResource): TStream;
65 procedure ReadLFH(s: TStream; fname, xcomment: AnsiString; xcsize, xusize, xcomp, xcrc, xtime, xflags: UInt32);
66 procedure ReadCDR(s: TStream; cdrid: Integer);
67 function FindEOCD(s: TStream): Boolean;
68 procedure ReadEOCD(s: TStream);
70 procedure WriteLFH(s: TStream; flags, comp, mtime, crc, csize, usize: UInt32; const name: AnsiString);
71 procedure WriteCDR(s: TStream; flags, comp, mtime, crc, csize, usize, eattr, offset: UInt32; const name, com: AnsiString; cdrid: Integer);
72 procedure SaveToStream(s: TStream);
74 public
75 constructor Create();
76 destructor Destroy(); override;
77 procedure FreeWAD(); override;
78 function ReadFile2(FileName: string): Boolean; override;
79 function ReadMemory(Data: Pointer; Len: LongWord): Boolean; override;
80 procedure CreateImage(); override;
81 function AddResource(Data: Pointer; Len: LongWord; Name, Section: String): Boolean; override; overload;
82 function AddResource(FileName, Name, Section: String): Boolean; override; overload;
83 function AddAlias(Res, Alias: String): Boolean; override;
84 procedure AddSection(Name: String); override;
85 procedure RemoveResource(Section, Resource: String); override;
86 procedure SaveTo(FileName: String); override;
87 function HaveResource(Section, Resource: String): Boolean; override;
88 function HaveSection(Section: string): Boolean; override;
89 function GetResource(Section, Resource: String; var pData: Pointer; var Len: Integer): Boolean; override;
90 function GetSectionList(): SArray; override;
91 function GetResourcesList(Section: String): SArray; override;
93 function GetLastError: Integer; override;
94 function GetLastErrorStr: String; override;
95 function GetResourcesCount: Word; override;
96 function GetVersion: Byte; override;
97 end;
99 implementation
101 uses SysUtils, StrUtils, DateUtils, Math, utils, zstream, crc, e_log;
103 const
104 ZIP_SIGN_CDR = 'PK'#1#2;
105 ZIP_SIGN_LFH = 'PK'#3#4;
106 ZIP_SIGN_EOCD = 'PK'#5#6;
108 const
109 ZIP_COMP_STORE = 0;
110 ZIP_COMP_SHRUNK = 1;
111 ZIP_COMP_REDUCE1 = 2;
112 ZIP_COMP_REDUCE2 = 3;
113 ZIP_COMP_REDUCE3 = 4;
114 ZIP_COMP_REDUCE4 = 5;
115 ZIP_COMP_IMPLODE = 6;
116 ZIP_COMP_TOKENIZED = 7;
117 ZIP_COMP_DEFLATE = 8;
118 ZIP_COMP_DEFLATE64 = 9;
119 ZIP_COMP_TERSE1 = 10;
120 ZIP_COMP_BZIP2 = 12;
121 ZIP_COMP_LZMA = 14;
122 ZIP_COMP_CMPSC = 16;
123 ZIP_COMP_TERSE2 = 18;
124 ZIP_COMP_LZ77 = 19;
125 ZIP_COMP_ZSTD1 = 20;
126 ZIP_COMP_ZSTD2 = 93;
127 ZIP_COMP_MP3 = 94;
128 ZIP_COMP_XZ = 95;
129 ZIP_COMP_JPEG = 96;
130 ZIP_COMP_WAVPACK = 97;
131 ZIP_COMP_PPMD = 98;
132 ZIP_COMP_AE = 99;
134 const
135 ZIP_SYSTEM = 0; // DOS / FAT
136 ZIP_MAXVERSION = 63; // Max supported version
138 const
139 ZIP_ENCRYPTION_MASK = (1 << 0) or (1 << 6) or (1 << 13);
140 ZIP_COMP_MASK = (1 << 1) or (1 << 2) or (1 << 4) or (1 << 12);
141 ZIP_DATA_MASK = (1 << 3);
142 ZIP_PATCH_MASK = (1 << 5);
143 ZIP_UTF8_MASK = (1 << 11);
144 ZIP_STREAM_MASK = (1 << 14);
146 function IsASCII(const s: AnsiString): Boolean;
147 var i: Integer;
148 begin
149 for i := 1 to Length(s) do
150 begin
151 if s[i] >= #$80 then
152 begin
153 Result := False;
154 exit;
155 end;
156 end;
157 Result := True;
158 end;
160 function IsUTF8(const s: AnsiString): Boolean;
161 var i, j, len: Integer;
162 begin
163 Result := False;
164 i := 1; len := Length(s);
165 while i <= len do
166 begin
167 case Ord(s[i]) of
168 $00..$7F: j := 0;
169 $80..$BF: exit; // invalid encoding
170 $C0..$DF: j := 1;
171 $E0..$EF: j := 2;
172 $F0..$F7: j := 3;
173 otherwise exit; // invalid encoding
174 end;
175 Inc(i);
176 while j > 0 do
177 begin
178 if i > len then exit; // invlid length
179 case Ord(s[i]) of
180 $80..$BF: ; // ok
181 else exit; // invalid encoding
182 end;
183 Inc(i);
184 Dec(j);
185 end;
186 end;
187 Result := True;
188 end;
190 function DosToStr(dostime: UInt32): AnsiString;
191 begin
192 try
193 DateTimeToString(Result, 'yyyy/mm/dd hh:nn:ss', DosDateTimeToDateTime(dostime));
194 except on e: EConvertError do
195 Result := 'INVALID ($' + IntToHex(dostime, 8) + ')';
196 end;
197 end;
199 procedure ToSectionFile(fname: AnsiString; out section, name: AnsiString); inline;
200 var i: SizeInt;
201 begin
202 i := LastDelimiter('/', fname);
203 section := Copy(fname, 1, i - 1);
204 name := Copy(fname, i + 1)
205 end;
207 function GetFileName(const Section, Name: AnsiString): AnsiString; inline;
208 begin
209 if Section = '' then
210 Result := Name
211 else
212 Result := Section + '/' + Name;
213 end;
215 function PrepString(const s: AnsiString; caseSensitive, extSensitive: Boolean): AnsiString; inline;
216 var i: Integer;
217 begin
218 Result := s;
219 if caseSensitive = False then
220 begin
221 Result := UpperCase(Result);
222 end;
223 if extSensitive = False then
224 begin
225 i := Pos('.', Result); // fix dotfiles
226 if i > 1 then
227 SetLength(Result, i - 1);
228 end;
229 end;
231 function FindResourceIDRAW(p: PSection; name: AnsiString; caseSensitive, extSensitive: Boolean): Integer;
232 var i: Integer; pname: AnsiString;
233 begin
234 if p <> nil then
235 begin
236 pname := PrepString(name, caseSensitive, extSensitive);
237 for i := 0 to High(p.list) do
238 begin
239 if PrepString(p.list[i].name, caseSensitive, extSensitive) = pname then
240 begin
241 Result := i;
242 exit;
243 end;
244 end;
245 end;
246 Result := -1;
247 end;
249 function FindResourceID(p: PSection; name: AnsiString): Integer;
250 var i: Integer;
251 begin
252 i := FindResourceIDRAW(p, name, True, True); // CaSeNaMe.Ext
253 if i < 0 then
254 begin
255 i := FindResourceIDRAW(p, name, False, True); // CASENAME.EXT
256 if i < 0 then
257 begin
258 i := FindResourceIDRAW(p, name, True, False); // CaSeNaMe
259 if i < 0 then
260 begin
261 i := FindResourceIDRAW(p, name, False, False); // CASENAME
262 end;
263 end;
264 end;
265 Result := i;
266 end;
268 function FindResource(p: PSection; name: AnsiString): PResource;
269 var i: Integer;
270 begin
271 i := FindResourceID(p, name);
272 if i >= 0 then
273 Result := @p.list[i]
274 else
275 Result := nil;
276 end;
280 function TZIPEditor.FindSectionIDRAW(name: AnsiString; caseSensitive: Boolean): Integer;
281 var i: Integer; pname: AnsiString;
282 begin
283 if FSection <> nil then
284 begin
285 pname := PrepString(name, caseSensitive, True);
286 for i := 0 to High(FSection) do
287 begin
288 if PrepString(FSection[i].name, caseSensitive, True) = pname then
289 begin
290 Result := i;
291 exit;
292 end;
293 end;
294 end;
295 Result := -1;
296 end;
298 function TZIPEditor.FindSectionRAW(name: AnsiString; caseSensitive: Boolean): PSection;
299 var i: Integer;
300 begin
301 i := FindSectionIDRAW(name, caseSensitive);
302 if i >= 0 then
303 Result := @FSection[i]
304 else
305 Result := nil;
306 end;
308 function TZIPEditor.InsertSectionRAW(name: AnsiString; mtime: UInt32; comment: AnsiString): PSection;
309 var i: Integer;
310 begin
311 if FSection = nil then i := 0 else i := Length(FSection);
312 SetLength(FSection, i + 1);
313 FSection[i] := Default(TSection);
314 FSection[i].name := name;
315 FSection[i].mtime := mtime;
316 FSection[i].comment := comment;
317 Result := @FSection[i];
318 end;
322 function TZIPEditor.FindSectionID(name: AnsiString): Integer;
323 var fixName: AnsiString;
324 begin
325 fixName := StringReplace(name, '\', '/', [rfReplaceAll], TStringReplaceAlgorithm.sraManySmall);
326 Result := FindSectionIDRAW(fixName, True); // CaSeNaMe
327 if Result < 0 then
328 Result := FindSectionIDRAW(fixName, False); // CASENAME
329 end;
331 function TZIPEditor.FindSection(name: AnsiString): PSection;
332 var fixName: AnsiString;
333 begin
334 fixName := StringReplace(name, '\', '/', [rfReplaceAll], TStringReplaceAlgorithm.sraManySmall);
335 Result := FindSectionRAW(fixName, True); // CaSeNaMe
336 if Result = nil then
337 Result := FindSectionRAW(fixName, False); // CASENAME
338 end;
340 function TZIPEditor.InsertSection(name: AnsiString; mtime: UInt32; comment: AnsiString): PSection;
341 begin
342 Result := FindSection(name);
343 if Result = nil then
344 Result := InsertSectionRAW(name, mtime, comment);
345 end;
349 function TZIPEditor.InsertFileInfo(const section, name: AnsiString; pos, csize, usize, comp, crc, mtime, flags: UInt32; comment: AnsiString): PResource;
350 var p: PSection; i: Integer;
351 begin
352 p := FindSectionRAW(section, True);
353 if p = nil then
354 p := InsertSectionRAW(section, mtime, '');
355 if p.list = nil then i := 0 else i := Length(p.list);
356 SetLength(p.list, i + 1);
357 p.list[i] := Default(TResource);
358 p.list[i].name := name;
359 p.list[i].pos := pos;
360 p.list[i].csize := csize;
361 p.list[i].usize := usize;
362 p.list[i].comp := comp;
363 p.list[i].chksum := crc;
364 p.list[i].mtime := mtime;
365 p.list[i].flags := flags;
366 p.list[i].comment := comment;
367 p.list[i].stream := nil;
368 Result := @p.list[i];
369 end;
373 function TZIPEditor.AddAlias(Res, Alias: String): Boolean;
374 begin
375 // Hard-links not supported in ZIP
376 // However, they never created by editor
377 Result := False;
378 end;
380 function TZIPEditor.AddResource(Data: Pointer; Len: LongWord; Name, Section: String): Boolean;
381 const compress: Boolean = True;
382 const level: TCompressionLevel = TCompressionLevel.clMax;
383 var s: TMemoryStream; cs: TCompressionStream; p: PResource;
384 var comp, crc: UInt32;
385 begin
386 Name := win2utf(Name);
387 Section := win2utf(Section);
388 Result := False;
389 if Name <> '' then
390 begin
391 s := TMemoryStream.Create();
392 try
393 if compress and (Len > 0) then
394 begin
395 cs := TCompressionStream.Create(level, s, True);
396 try
397 cs.WriteBuffer(PByte(Data)[0], Len);
398 cs.Flush();
399 comp := ZIP_COMP_DEFLATE;
400 finally
401 cs.Free();
402 end;
403 end;
404 if (Len = 0) or (compress = False) or (s.Size >= Len) then
405 begin
406 s.Seek(0, TSeekOrigin.soBeginning);
407 s.SetSize(Len);
408 s.WriteBuffer(PByte(Data)[0], Len);
409 comp := ZIP_COMP_STORE;
410 Assert(s.Size = Len);
411 end;
412 crc := crc32(0, nil, 0);
413 crc := crc32(crc, data, len);
414 p := InsertFileInfo(Section, Name, $ffffffff, s.Size, Len, comp, crc, DateTimeToDosDateTime(Now()), 0, '');
415 p.stream := s;
416 Result := True;
417 except
418 s.Free();
419 raise;
420 end;
421 end;
422 end;
424 function TZIPEditor.AddResource(FileName, Name, Section: String): Boolean;
425 var s: TFileStream; ptr: PByte;
426 begin
427 Result := False;
428 FLastError := DFWAD_ERROR_READWAD;
429 try
430 s := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
431 try
432 GetMem(ptr, s.Size);
433 try
434 s.ReadBuffer(ptr[0], s.Size);
435 Result := AddResource(ptr, s.Size, Name, Section);
436 if Result = True then FLastError := DFWAD_NOERROR;
437 finally
438 FreeMem(ptr);
439 end;
440 finally
441 s.Free();
442 end;
443 except
444 on e: EFOpenError do
445 begin
446 if gWADEditorLogLevel >= DFWAD_LOG_INFO then
447 e_WriteLog('DFZIP: AddResource: failed to open file ' + FileName, MSG_NOTIFY);
448 FLastError := DFWAD_ERROR_CANTOPENWAD;
449 end;
450 end;
451 end;
453 constructor TZIPEditor.Create();
454 begin
455 FSection := nil;
456 FStream := nil;
457 FComment := '';
458 FLastError := DFWAD_NOERROR;
459 FVersion := 10;
460 FreeWAD();
461 end;
463 destructor TZIPEditor.Destroy();
464 begin
465 FreeWAD();
466 inherited;
467 end;
469 procedure TZIPEditor.FreeWAD();
470 var i, j: Integer;
471 begin
472 if FSection <> nil then
473 begin
474 for i := 0 to High(FSection) do
475 begin
476 if FSection[i].list <> nil then
477 begin
478 for j := 0 to High(FSection[i].list) do
479 begin
480 if FSection[i].list[j].stream <> nil then
481 begin
482 FreeAndNil(FSection[i].list[j].stream);
483 end;
484 end;
485 SetLength(FSection[i].list, 0);
486 end;
487 end;
488 SetLength(FSection, 0);
489 end;
490 if FStream <> nil then
491 begin
492 FreeAndNil(FStream);
493 end;
494 FComment := '';
495 FLastError := DFWAD_NOERROR;
496 FVersion := 10;
497 end;
499 function TZIPEditor.Preload(p: PResource): Boolean;
500 var s: TMemoryStream;
501 begin
502 Result := False;
503 if p <> nil then
504 begin
505 Result := p.stream <> nil;
506 if (p.stream = nil) and (FStream <> nil) then
507 begin
508 s := TMemoryStream.Create();
509 try
510 if p.csize > 0 then
511 begin
512 FStream.Seek(p.pos, TSeekOrigin.soBeginning);
513 s.CopyFrom(FStream, p.csize);
514 end;
515 Assert(s.Size = p.csize); // wtf, random size if copied zero bytes!
516 p.stream := s;
517 Result := True;
518 except
519 s.Free();
520 end;
521 end;
522 end;
523 end;
525 procedure TZIPEditor.CreateImage();
526 var i, j: Integer;
527 begin
528 if FStream = nil then
529 begin
530 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
531 e_WriteLog('DFZIP: CreateImage: File not assigned', MSG_NOTIFY);
532 FLastError := DFWAD_ERROR_WADNOTLOADED;
533 end
534 else if FStream is TMemoryStream then
535 begin
536 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
537 e_WriteLog('DFZIP: CreateImage: Memory stream', MSG_NOTIFY);
538 FLastError := DFWAD_NOERROR;
539 end
540 else
541 begin
542 if FSection <> nil then
543 begin
544 for i := 0 to High(FSection) do
545 begin
546 if FSection[i].list <> nil then
547 begin
548 for j := 0 to High(FSection[i].list) do
549 begin
550 if Preload(@FSection[i].list[j]) = False then
551 begin
552 if gWADEditorLogLevel >= DFWAD_LOG_WARN then
553 e_WriteLog('DFZIP: CreateImage: failed to preload resource [' + FSection[i].name + '][' + FSection[i].list[j].name + ']', MSG_WARNING);
554 FLastError := DFWAD_ERROR_CANTOPENWAD;
555 exit;
556 end;
557 end;
558 end;
559 end;
560 end;
561 FreeAndNil(FStream);
562 FLastError := DFWAD_NOERROR;
563 end;
564 end;
566 procedure TZIPEditor.AddSection(Name: String);
567 begin
568 Name := win2utf(Name);
569 if InsertSection(Name, DateTimeToDosDateTime(Now()), '') = nil then
570 raise Exception.Create('DFZIP: AddSection[' + Name + ']: failed to insert');
571 end;
573 function TZIPEditor.HaveResource(Section, Resource: String): Boolean;
574 begin
575 Section := win2utf(Section);
576 Resource := win2utf(Resource);
577 Result := FindResource(FindSection(Section), Resource) <> nil;
578 end;
580 function TZIPEditor.HaveSection(Section: String): Boolean;
581 begin
582 Section := win2utf(Section);
583 Result := FindSection(Section) <> nil;
584 end;
586 function TZIPEditor.GetSourceStream(p: PResource): TStream;
587 var src: TStream;
588 begin
589 src := nil;
590 if p.stream <> nil then
591 begin
592 src := p.stream;
593 src.Seek(0, TSeekOrigin.soBeginning);
594 end
595 else if FStream <> nil then
596 begin
597 src := FStream;
598 src.Seek(p.pos, TSeekOrigin.soBeginning);
599 end;
600 Result := src;
601 end;
603 function TZIPEditor.GetResource(Section, Resource: String; var pData: Pointer; var Len: Integer): Boolean;
604 var p: PResource; ptr: PByte; src: TStream; tmp: TDecompressionStream; crc: UInt32;
605 begin
606 Section := win2utf(Section);
607 Resource := win2utf(Resource);
608 FLastError := DFWAD_ERROR_CANTOPENWAD;
609 Result := False;
610 pData := nil;
611 Len := 0;
612 p := FindResource(FindSection(Section), Resource);
613 if p <> nil then
614 begin
615 src := GetSourceStream(p);
616 if src <> nil then
617 begin
618 case p.comp of
619 ZIP_COMP_STORE:
620 begin
621 Assert(p.csize = p.usize);
622 GetMem(ptr, p.usize);
623 try
624 try
625 src.ReadBuffer(ptr[0], p.usize);
626 Result := True;
627 except
628 FreeMem(ptr);
629 raise;
630 end;
631 except on e: EReadError do
632 if gWADEditorLogLevel >= DFWAD_LOG_WARN then
633 e_WriteLog('DFZIP: Failed to read STOREd data, reason: ' + e.Message, MSG_WARNING);
634 end;
635 end;
636 ZIP_COMP_DEFLATE:
637 try
638 tmp := TDecompressionStream.Create(src, True);
639 try
640 GetMem(ptr, p.usize);
641 try
642 tmp.ReadBuffer(ptr[0], p.usize);
643 Result := True;
644 except
645 FreeMem(ptr);
646 raise;
647 end;
648 finally
649 tmp.Free();
650 end;
651 except
652 on e: EStreamError do
653 begin
654 if gWADEditorLogLevel >= DFWAD_LOG_INFO then
655 e_WriteLog('DFZIP: Failed to decompress DEFLATEd data, reason: ' + e.Message, MSG_WARNING);
656 raise e;
657 end;
658 end;
659 otherwise
660 if gWADEditorLogLevel >= DFWAD_LOG_INFO then
661 e_WriteLog('DFZIP: Unsupported compression method: ' + IntToStr(p.comp), MSG_WARNING);
662 end;
663 end
664 else
665 begin
666 if gWADEditorLogLevel >= DFWAD_LOG_WARN then
667 e_WriteLog('DFZIP: No available source for file data', MSG_WARNING);
668 FLastError := DFWAD_ERROR_WADNOTLOADED;
669 end;
670 if Result = True then
671 begin
672 crc := crc32(0, nil, 0);
673 crc := crc32(crc, ptr, p.usize);
674 Result := crc = p.chksum;
675 if Result = True then
676 begin
677 pData := ptr;
678 Len := p.usize;
679 FLastError := DFWAD_NOERROR;
680 end
681 else
682 begin
683 if gWADEditorLogLevel >= DFWAD_LOG_INFO then
684 e_WriteLog('DFZIP: File integrity check failed: expected CRC32 $' + IntToHex(p.chksum, 8) + ', calculated CRC32 $' + IntToHex(crc, 8), MSG_WARNING);
685 FreeMem(ptr);
686 end;
687 end;
688 end
689 else
690 begin
691 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
692 e_WriteLog('DFZIP: Resource not found', MSG_NOTIFY);
693 FLastError := DFWAD_ERROR_RESOURCENOTFOUND;
694 end;
695 end;
697 function TZIPEditor.GetResourcesList(Section: String): SArray;
698 var p: PSection; i: Integer;
699 begin
700 Section := win2utf(Section);
701 Result := nil;
702 p := FindSection(Section);
703 if (p <> nil) and (p.list <> nil) then
704 begin
705 SetLength(Result, Length(p.list));
706 for i := 0 to High(p.list) do
707 begin
708 Result[i] := utf2win(p.list[i].name);
709 end;
710 end;
711 end;
713 function TZIPEditor.GetSectionList(): SArray;
714 var i: Integer;
715 begin
716 Result := nil;
717 if FSection <> nil then
718 begin
719 SetLength(Result, Length(FSection));
720 for i := 0 to High(FSection) do
721 begin
722 Result[i] := utf2win(FSection[i].name);
723 end;
724 end;
725 end;
727 procedure TZIPEditor.ReadLFH(s: TStream; fname, xcomment: AnsiString; xcsize, xusize, xcomp, xcrc, xtime, xflags: UInt32);
728 var sig: packed array [0..3] of Char;
729 var va, vb, flags, comp: UInt16;
730 var mtime, crc, csize, usize: UInt32;
731 var fnlen, extlen: UInt16;
732 var mypos, datapos: UInt64;
733 var section, name: AnsiString;
734 var p: Pointer;
735 begin
736 mypos := s.Position;
737 if mypos + 30 <= s.Size then
738 begin
739 s.ReadBuffer(sig[0], 4);
740 if sig = ZIP_SIGN_LFH then
741 begin
742 va := s.ReadByte(); // Min Version
743 vb := s.ReadByte(); // Min System
744 flags := LEtoN(s.ReadWord());
745 comp := LEtoN(s.ReadWord());
746 mtime := LEtoN(s.ReadDWord());
747 crc := LEtoN(s.ReadDWord());
748 csize := LEtoN(s.ReadDWord());
749 usize := LEtoN(s.ReadDWord());
750 fnlen := LEtoN(s.ReadWord());
751 extlen := LEtoN(s.ReadWord());
752 datapos := s.Position + fnlen + extlen;
753 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
754 begin
755 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Min Version : ' + IntToStr(va), MSG_NOTIFY);
756 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Min System : ' + IntToStr(vb), MSG_NOTIFY);
757 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Flags : $' + IntToHex(flags, 4), MSG_NOTIFY);
758 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Compression : ' + IntToStr(comp), MSG_NOTIFY);
759 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Modification Time : ' + DosToStr(mtime), MSG_NOTIFY);
760 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': CRC-32 : $' + IntToHex(crc, 8), MSG_NOTIFY);
761 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Compressed size : ' + IntToStr(csize), MSG_NOTIFY);
762 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Decompressed size : ' + IntToStr(usize), MSG_NOTIFY);
763 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Name Length : ' + IntToStr(fnlen), MSG_NOTIFY);
764 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Extension Length : ' + IntToStr(extlen), MSG_NOTIFY);
765 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': <DATA OFFSET> : $' + IntToHex(datapos, 8), MSG_NOTIFY);
766 end;
767 if (va >= 10) and (va <= ZIP_MAXVERSION) then
768 begin
769 if datapos + xcsize <= s.Size then
770 begin
771 ToSectionFile(fname, section, name);
772 if name = '' then
773 begin
774 p := FindSectionRAW(section, True);
775 if p = nil then
776 p := InsertSectionRAW(section, xtime, xcomment);
777 end
778 else
779 begin
780 p := InsertFileInfo(section, name, datapos, xcsize, xusize, xcomp, xcrc, xtime, xflags and ZIP_COMP_MASK, xcomment);
781 end;
782 if p = nil then
783 raise Exception.Create('Failed to register resource [' + fname + ']');
784 end
785 else
786 raise Exception.Create('Invalid LFH size (corrupted file?)');
787 end
788 else
789 begin
790 FLastError := DFWAD_ERROR_WRONGVERSION;
791 raise Exception.Create('Unsupported CDR version ' + IntToStr(va) + ', not in range [10..' + IntToStr(ZIP_MAXVERSION) + ']');
792 end;
793 end
794 else
795 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?)');
796 end
797 else
798 raise Exception.Create('Invalid LFH size (corrupted file?)');
799 end;
801 procedure TZIPEditor.ReadCDR(s: TStream; cdrid: Integer);
802 var sig: packed array [0..3] of Char;
803 var vva, vvb, va, vb, flags, comp: UInt16;
804 var mtime, crc, csize, usize: UInt32;
805 var fnlen, extlen, comlen, disk, iattr: UInt16;
806 var eattr, offset: UInt32;
807 var mypos, next: UInt64;
808 var tmp: PChar;
809 var name, comment: AnsiString;
810 var cvtbug, utf8: Boolean;
811 begin
812 mypos := s.Position;
813 s.ReadBuffer(sig[0], 4);
814 if sig = ZIP_SIGN_CDR then
815 begin
816 // Valid Central Directory Signature
817 vva := s.ReadByte(); // Writer Version
818 vvb := s.ReadByte(); // Writer System
819 va := s.ReadByte(); // Min Version
820 vb := s.ReadByte(); // Min System
821 flags := LEtoN(s.ReadWord());
822 comp := LEtoN(s.ReadWord());
823 mtime := LEtoN(s.ReadDWord());
824 crc := LEtoN(s.ReadDWord());
825 csize := LEtoN(s.ReadDWord());
826 usize := LEtoN(s.ReadDWord());
827 fnlen := LEtoN(s.ReadWord());
828 extlen := LEtoN(s.ReadWord());
829 comlen := LEtoN(s.ReadWord());
830 disk := LEtoN(s.ReadWord());
831 iattr := LEtoN(s.ReadWord());
832 eattr := LEtoN(s.ReadDWord());
833 offset := LEtoN(s.ReadDWord());
834 next := s.Position + fnlen + extlen + comlen;
835 FVersion := va;
836 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
837 begin
838 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Writer Version : ' + IntToStr(vva), MSG_NOTIFY);
839 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Writer System : ' + IntToStr(vvb), MSG_NOTIFY);
840 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Min Version : ' + IntToStr(va), MSG_NOTIFY);
841 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Min System : ' + IntToStr(vb), MSG_NOTIFY);
842 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Flags : $' + IntToHex(flags, 4), MSG_NOTIFY);
843 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Compression : ' + IntToStr(comp), MSG_NOTIFY);
844 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Modification Time : ' + DosToStr(mtime), MSG_NOTIFY);
845 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': CRC-32 : $' + IntToHex(crc, 8), MSG_NOTIFY);
846 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Compressed size : ' + IntToStr(csize), MSG_NOTIFY);
847 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Decompressed size : ' + IntToStr(usize), MSG_NOTIFY);
848 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Name Length : ' + IntToStr(fnlen), MSG_NOTIFY);
849 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Extension Length : ' + IntToStr(extlen), MSG_NOTIFY);
850 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Comment Length : ' + IntToStr(comlen), MSG_NOTIFY);
851 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Disk : ' + IntToStr(disk), MSG_NOTIFY);
852 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Internal Attrib : $' + IntToHex(iattr, 4), MSG_NOTIFY);
853 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': External Attrib : $' + IntToHex(eattr, 8), MSG_NOTIFY);
854 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': LFH Offset : $' + IntToHex(offset, 8), MSG_NOTIFY);
855 end;
856 cvtbug := False;
857 if (vva = $10) and (vvb = $0A) and (va = $10) and (vb = $00) and (flags = (1 << 10)) and (mtime = 0) and (iattr = 0) and (eattr = 0) then
858 begin
859 // HACK: Editor and wadcvt for long time sets incorrent flag for UTF-8
860 flags := ZIP_UTF8_MASK;
861 cvtbug := True;
862 end;
863 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
864 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': WADCVT BUG : ' + BoolToStr(cvtbug, True), MSG_NOTIFY);
865 if (va >= 10) and (va <= ZIP_MAXVERSION) then
866 begin
867 if (flags and ZIP_ENCRYPTION_MASK) = 0 then
868 begin
869 if (csize <> $ffffffff) and (usize <> $ffffffff) and (disk <> $ffff) and (offset <> $ffffffff) then
870 begin
871 if disk = 0 then
872 begin
873 if (next <= s.Size) and (fnlen > 0) then
874 begin
875 case comp of
876 ZIP_COMP_STORE:
877 if csize <> usize then
878 raise Exception.Create('Compressed size ' + IntToStr(csize) + ' != Descompressed size ' + IntToStr(usize) + 'for STORE method (corrupted file?)');
879 ZIP_COMP_SHRUNK,
880 ZIP_COMP_REDUCE1,
881 ZIP_COMP_REDUCE2,
882 ZIP_COMP_REDUCE3,
883 ZIP_COMP_REDUCE4,
884 ZIP_COMP_IMPLODE,
885 ZIP_COMP_DEFLATE,
886 ZIP_COMP_DEFLATE64,
887 ZIP_COMP_TERSE1,
888 ZIP_COMP_BZIP2,
889 ZIP_COMP_LZMA,
890 ZIP_COMP_CMPSC,
891 ZIP_COMP_TERSE2,
892 ZIP_COMP_LZ77,
893 ZIP_COMP_ZSTD1,
894 ZIP_COMP_ZSTD2,
895 ZIP_COMP_MP3,
896 ZIP_COMP_XZ,
897 ZIP_COMP_JPEG,
898 ZIP_COMP_WAVPACK,
899 ZIP_COMP_PPMD:
900 ; // ok
901 ZIP_COMP_AE:
902 raise Exception.Create('Encrypted archives not supported');
903 otherwise
904 raise Exception.Create('Unknown compression method ' + IntToStr(comp));
905 end;
907 // Read Name
908 GetMem(tmp, UInt32(fnlen) + 1);
909 try
910 s.ReadBuffer(tmp[0], fnlen);
911 tmp[fnlen] := #0;
912 name := tmp;
913 finally
914 FreeMem(tmp);
915 end;
916 // Skip ZIP extensions
917 s.Seek(extlen, TSeekOrigin.soCurrent);
918 // Read Comment
919 comment := '';
920 if comlen > 0 then
921 begin
922 GetMem(tmp, UInt32(comlen) + 1);
923 try
924 s.ReadBuffer(tmp[0], comlen);
925 tmp[comlen] := #0;
926 comment := tmp;
927 finally
928 FreeMem(tmp);
929 end;
930 end;
932 utf8 := True;
933 if (utf8 = False) or (flags and ZIP_UTF8_MASK = 0) and (IsUTF8(name) = False) then
934 begin
935 name := win2utf(name);
936 utf8 := False;
937 end;
938 if (utf8 = False) or (flags and ZIP_UTF8_MASK = 0) and (IsUTF8(comment) = False) then
939 begin
940 comment := win2utf(comment);
941 utf8 := False;
942 end;
943 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
944 begin
945 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': UTF-8 Comatible : ' + BoolToStr(utf8, True), MSG_NOTIFY);
946 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Name : "' + name + '"', MSG_NOTIFY);
947 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Comment : "' + comment + '"', MSG_NOTIFY);
948 end;
949 s.Seek(offset, TSeekOrigin.soBeginning);
950 ReadLFH(s, name, comment, csize, usize, comp, crc, mtime, flags);
951 s.Seek(next, TSeekOrigin.soBeginning);
952 end
953 else
954 raise Exception.Create('Empty files names not supported');
955 end
956 else
957 raise Exception.Create('Splitted archives not supported');
958 end
959 else
960 begin
961 FLastError := DFWAD_ERROR_WRONGVERSION;
962 raise Exception.Create('ZIP64 not supported');
963 end;
964 end
965 else
966 begin
967 FLastError := DFWAD_ERROR_READWAD;
968 raise Exception.Create('Encrypted archives not supported');
969 end;
970 end
971 else
972 begin
973 FLastError := DFWAD_ERROR_WRONGVERSION;
974 raise Exception.Create('Unsupported CDR version ' + IntToStr(va) + ', not in range [10..' + IntToStr(ZIP_MAXVERSION) + ']');
975 end;
976 end
977 else
978 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?)');
979 end;
981 function TZIPEditor.FindEOCD(s: TStream): Boolean;
982 const maxedir = 20; // end of central directory entry
983 const maxecdir = maxedir + 65536; // + comment
984 var sig: packed array [0..3] of Char; off, lim: Int64;
985 begin
986 Result := False;
987 if s.Size >= maxedir then
988 begin
989 if s.Size < maxecdir then lim := s.Size else lim := maxecdir;
990 lim := lim - maxedir;
991 off := maxedir;
992 while (off <= lim) and (Result = False) do
993 begin
994 s.Seek(s.Size - off, TSeekOrigin.soBeginning);
995 s.ReadBuffer(sig[0], 4);
996 Result := sig = ZIP_SIGN_EOCD;
997 Inc(off);
998 end;
999 end;
1000 end;
1002 procedure TZIPEditor.ReadEOCD(s: TStream);
1003 var sig: packed array [0..3] of Char;
1004 var idisk, ndisk, nrec, total, comlen: UInt16;
1005 var csize, cpos, i: UInt32;
1006 var mypos: UInt64;
1007 var tmp: PChar;
1008 var utf8: Boolean;
1009 begin
1010 FLastError := DFWAD_ERROR_FILENOTWAD;
1011 FVersion := 0;
1012 s.ReadBuffer(sig[0], 4);
1013 if (sig = ZIP_SIGN_LFH) or (sig = ZIP_SIGN_EOCD) then
1014 begin
1015 if FindEOCD(s) then
1016 begin
1017 // End of Central Directory found
1018 FLastError := DFWAD_ERROR_READWAD;
1019 mypos := s.Position - 4;
1020 idisk := LEtoN(s.ReadWord());
1021 ndisk := LEtoN(s.ReadWord());
1022 nrec := LEtoN(s.ReadWord());
1023 total := LEtoN(s.ReadWord());
1024 csize := LEtoN(s.ReadDWord());
1025 cpos := LEtoN(s.ReadDWord());
1026 comlen := LEtoN(s.ReadWord());
1027 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
1028 begin
1029 e_WriteLog('==============================================', MSG_NOTIFY);
1030 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Disk ID : ' + IntToStr(idisk), MSG_NOTIFY);
1031 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Disk ID with CD : ' + IntToStr(ndisk), MSG_NOTIFY);
1032 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Available CDR''s : ' + IntToStr(nrec), MSG_NOTIFY);
1033 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Total CDR''s : ' + IntToStr(total), MSG_NOTIFY);
1034 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': CD Length : ' + IntToStr(csize), MSG_NOTIFY);
1035 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': CD Offset : $' + IntToHex(cpos, 8), MSG_NOTIFY);
1036 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Comment Length : ' + IntToStr(comlen), MSG_NOTIFY);
1037 end;
1038 if (idisk <> $ffff) and (ndisk <> $ffff) and (nrec <> $ffff) and (total <> $ffff) and (csize <> $ffffffff) and (cpos <> $ffffffff) then
1039 begin
1040 if s.Position + comlen = s.Size then
1041 begin
1042 if (idisk = 0) and (ndisk = 0) and (nrec = total) then
1043 begin
1044 if (nrec * 46 <= csize) and (UInt64(cpos) + csize <= s.Size) then
1045 begin
1046 if total > 0 then
1047 begin
1048 utf8 := True;
1049 if comlen > 0 then
1050 begin
1051 GetMem(tmp, UInt32(comlen) + 1);
1052 try
1053 s.ReadBuffer(tmp[0], comlen);
1054 tmp[comlen] := #0;
1055 FComment := tmp;
1056 if IsUTF8(FComment) = False then
1057 begin
1058 FComment := win2utf(FComment);
1059 utf8 := False;
1060 end;
1061 finally
1062 FreeMem(tmp);
1063 end;
1064 end;
1065 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
1066 begin
1067 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': UTF8 Comaptible : ' + BoolToStr(utf8, True), MSG_NOTIFY);
1068 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Comment : "' + FComment + '"', MSG_NOTIFY);
1069 end;
1070 i := 0;
1071 s.Seek(cpos, TSeekOrigin.soBeginning);
1072 while i < nrec do
1073 begin
1074 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
1075 e_WriteLog('==============================================', MSG_NOTIFY);
1076 ReadCDR(s, i);
1077 Inc(i);
1078 end;
1079 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
1080 e_WriteLog('==============================================', MSG_NOTIFY);
1081 end;
1082 end
1083 else
1084 raise Exception.Create('Central Directory too big (corrupted file?)');
1085 end
1086 else
1087 raise Exception.Create('Splitted archives not supported');
1088 end
1089 else
1090 raise Exception.Create('EOCD too big (corrupted file?)');
1091 end
1092 else
1093 raise Exception.Create('ZIP64 not supported');
1094 end
1095 else
1096 raise Exception.Create('EOCD not found (corrupted file?)');
1097 end
1098 else
1099 raise Exception.Create('Not DFZIP formated file');
1100 end;
1102 function TZIPEditor.ReadFile2(FileName: String): Boolean;
1103 var s: TFileStream;
1104 begin
1105 FreeWAD();
1106 Result := False;
1107 try
1108 try
1109 s := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
1110 try
1111 ReadEOCD(s);
1112 FStream := s;
1113 FLastError := DFWAD_NOERROR;
1114 Result := True;
1115 except
1116 s.Free();
1117 raise;
1118 end;
1119 except
1120 on e: Exception do
1121 begin
1122 if gWADEditorLogLevel >= DFWAD_LOG_INFO then
1123 e_WriteLog('ZIP: Failed to read ZIP from file ' + FileName + ', reason: ' + e.Message, MSG_WARNING);
1124 FreeWAD();
1125 end;
1126 end;
1127 except
1128 on e: EFOpenError do
1129 begin
1130 if gWADEditorLogLevel >= DFWAD_LOG_INFO then
1131 e_WriteLog('DFZIP: Failed to open file ' + FileName + ', reason: ' + e.Message, MSG_WARNING);
1132 if FileExists(FileName) then
1133 FLastError := DFWAD_ERROR_CANTOPENWAD
1134 else
1135 FLastError := DFWAD_ERROR_WADNOTFOUND;
1136 end;
1137 end;
1138 end;
1140 function TZIPEditor.ReadMemory(Data: Pointer; Len: LongWord): Boolean;
1141 var s: TMemoryStream;
1142 begin
1143 FreeWAD();
1144 Result := False;
1145 try
1146 s := TMemoryStream.Create;
1147 try
1148 s.SetSize(Len);
1149 s.WriteBuffer(PByte(Data)[0], Len);
1150 s.Seek(0, soBeginning);
1151 ReadEOCD(s);
1152 FStream := s;
1153 FLastError := DFWAD_NOERROR;
1154 Result := True;
1155 except
1156 s.Free();
1157 raise;
1158 end;
1159 except
1160 on e: Exception do
1161 begin
1162 if gWADEditorLogLevel >= DFWAD_LOG_INFO then
1163 e_WriteLog('DFZIP: Failed to read ZIP from memory, reason: ' + e.Message, MSG_WARNING);
1164 FreeWAD();
1165 end;
1166 end;
1167 end;
1169 procedure TZIPEditor.RemoveResource(Section, Resource: String);
1170 var p: PSection; i: Integer;
1171 begin
1172 Section := win2utf(Section);
1173 Resource := win2utf(Resource);
1174 p := FindSection(Section);
1175 i := FindResourceID(p, Resource);
1176 if i >= 0 then
1177 begin
1178 if p.list[i].stream <> nil then
1179 FreeAndNil(p.list[i].stream);
1180 for i := i + 1 to High(p.list) do
1181 begin
1182 p.list[i - 1] := p.list[i];
1183 end;
1184 SetLength(p.list, High(p.list));
1185 end;
1186 end;
1188 function GetZIPVersion(const afname: AnsiString; flags, comp: UInt16): UInt8;
1189 var version: UInt8;
1190 begin
1191 version := 10; // Base version
1192 case comp of
1193 ZIP_COMP_STORE: version := 10;
1194 ZIP_COMP_SHRUNK: version := 10;
1195 ZIP_COMP_REDUCE1: version := 10;
1196 ZIP_COMP_REDUCE2: version := 10;
1197 ZIP_COMP_REDUCE3: version := 10;
1198 ZIP_COMP_REDUCE4: version := 10;
1199 ZIP_COMP_IMPLODE: version := 10;
1200 ZIP_COMP_TOKENIZED: version := 20;
1201 ZIP_COMP_DEFLATE: version := 20;
1202 ZIP_COMP_DEFLATE64: version := 21;
1203 ZIP_COMP_TERSE1: version := 25; // PKWARE DCL Implode
1204 ZIP_COMP_BZIP2: version := 46;
1205 ZIP_COMP_LZMA: version := 63;
1206 ZIP_COMP_CMPSC: version := 63;
1207 ZIP_COMP_TERSE2: version := 63;
1208 ZIP_COMP_LZ77: version := 63;
1209 ZIP_COMP_ZSTD1: version := 63;
1210 ZIP_COMP_ZSTD2: version := 63;
1211 ZIP_COMP_MP3: version := 63;
1212 ZIP_COMP_XZ: version := 63;
1213 ZIP_COMP_JPEG: version := 63;
1214 ZIP_COMP_WAVPACK: version := 63;
1215 ZIP_COMP_PPMD: version := 63;
1216 ZIP_COMP_AE: version := 63;
1217 end;
1218 if afname[Length(afname)] = '/' then
1219 version := Max(20, version); // Folder
1220 if flags and ZIP_UTF8_MASK <> 0 then
1221 version := Max(63, version); // UTF-8 name
1222 Result := version;
1223 end;
1225 procedure TZIPEditor.WriteLFH(s: TStream; flags, comp, mtime, crc, csize, usize: UInt32; const name: AnsiString);
1226 var version: UInt8; fnlen: UInt16; mypos: UInt64;
1227 begin
1228 mypos := s.Position;
1229 fnlen := Length(name);
1230 if IsASCII(name) = False then
1231 flags := flags or ZIP_UTF8_MASK;
1232 version := GetZIPVersion(name, flags, comp);
1233 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
1234 begin
1235 e_WriteLog('==============================================', MSG_NOTIFY);
1236 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Min Version : ' + IntToStr(version), MSG_NOTIFY);
1237 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Min System : ' + IntToStr(ZIP_SYSTEM), MSG_NOTIFY);
1238 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Flags : $' + IntToHex(flags, 4), MSG_NOTIFY);
1239 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Compression : ' + IntToStr(comp), MSG_NOTIFY);
1240 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Modification Time : ' + DosToStr(mtime), MSG_NOTIFY);
1241 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': CRC-32 : $' + IntToHex(crc, 8), MSG_NOTIFY);
1242 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Compressed size : ' + IntToStr(csize), MSG_NOTIFY);
1243 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Decompressed size : ' + IntToStr(usize), MSG_NOTIFY);
1244 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Name Length : ' + IntToStr(fnlen), MSG_NOTIFY);
1245 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Extension Length : ' + IntToStr(0), MSG_NOTIFY);
1246 e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Name : "' + name + '"', MSG_NOTIFY);
1247 end;
1248 s.WriteBuffer(ZIP_SIGN_LFH, 4); // LFH Signature
1249 s.WriteByte(version); // Min version
1250 s.WriteByte(ZIP_SYSTEM); // System
1251 WriteInt(s, UInt16(flags)); // Flags
1252 WriteInt(s, UInt16(comp)); // Compression method
1253 WriteInt(s, UInt32(mtime)); // Modification time/date
1254 WriteInt(s, UInt32(crc)); // CRC-32
1255 WriteInt(s, UInt32(csize)); // Compressed size
1256 WriteInt(s, UInt32(usize)); // Decompressed size
1257 WriteInt(s, UInt16(fnlen)); // Name field length
1258 WriteInt(s, UInt16(0)); // Extra field length
1259 s.WriteBuffer(name[1], fnlen); // File Name
1260 end;
1262 procedure TZIPEditor.WriteCDR(s: TStream; flags, comp, mtime, crc, csize, usize, eattr, offset: UInt32; const name, com: AnsiString; cdrid: Integer);
1263 var version: UInt8; fnlen, fclen: UInt16; mypos: UInt64;
1264 begin
1265 mypos := s.Position;
1266 fnlen := Length(name);
1267 fclen := Length(com);
1268 if (IsASCII(name) = False) or (IsASCII(com) = False) then
1269 flags := flags or ZIP_UTF8_MASK;
1270 version := GetZIPVersion(name, flags, comp);
1271 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
1272 begin
1273 e_WriteLog('==============================================', MSG_NOTIFY);
1274 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Writer Version : ' + IntToStr(ZIP_MAXVERSION), MSG_NOTIFY);
1275 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Writer System : ' + IntToStr(ZIP_SYSTEM), MSG_NOTIFY);
1276 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Min Version : ' + IntToStr(version), MSG_NOTIFY);
1277 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Min System : ' + IntToStr(ZIP_SYSTEM), MSG_NOTIFY);
1278 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Flags : $' + IntToHex(flags, 4), MSG_NOTIFY);
1279 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Compression : ' + IntToStr(comp), MSG_NOTIFY);
1280 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Modification Time : ' + DosToStr(mtime), MSG_NOTIFY);
1281 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': CRC-32 : $' + IntToHex(crc, 8), MSG_NOTIFY);
1282 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Compressed size : ' + IntToStr(csize), MSG_NOTIFY);
1283 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Decompressed size : ' + IntToStr(usize), MSG_NOTIFY);
1284 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Name Length : ' + IntToStr(fnlen), MSG_NOTIFY);
1285 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Extension Length : ' + IntToStr(0), MSG_NOTIFY);
1286 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Comment Length : ' + IntToStr(fclen), MSG_NOTIFY);
1287 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Disk : ' + IntToStr(0), MSG_NOTIFY);
1288 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Internal Attrib : $' + IntToHex(0, 4), MSG_NOTIFY);
1289 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': External Attrib : $' + IntToHex(eattr, 8), MSG_NOTIFY);
1290 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': LFH Offset : $' + IntToHex(offset, 8), MSG_NOTIFY);
1291 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Name : "' + name + '"', MSG_NOTIFY);
1292 e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Comment : "' + com + '"', MSG_NOTIFY);
1293 end;
1294 s.WriteBuffer(ZIP_SIGN_CDR, 4); // CDR Signature
1295 s.WriteByte(ZIP_MAXVERSION); // Used version
1296 s.WriteByte(ZIP_SYSTEM); // Used system
1297 s.WriteByte(version); // Min version
1298 s.WriteByte(ZIP_SYSTEM); // Min system
1299 WriteInt(s, UInt16(flags)); // Flags
1300 WriteInt(s, UInt16(comp)); // Compression method
1301 WriteInt(s, UInt32(mtime)); // Modification time/date
1302 WriteInt(s, UInt32(crc)); // CRC-32
1303 WriteInt(s, UInt32(csize)); // Compressed size
1304 WriteInt(s, UInt32(usize)); // Decompressed size
1305 WriteInt(s, UInt16(fnlen)); // Name field length
1306 WriteInt(s, UInt16(0)); // Extra field length
1307 WriteInt(s, UInt16(fclen)); // Comment field length
1308 WriteInt(s, UInt16(0)); // Disk
1309 WriteInt(s, UInt16(0)); // Internal attributes
1310 WriteInt(s, UInt32(eattr)); // External attributes
1311 WriteInt(s, UInt32(offset)); // LFH offset
1312 s.WriteBuffer(name[1], fnlen); // File Name
1313 s.WriteBuffer(com[1], fclen); // Comment
1314 end;
1316 procedure TZIPEditor.SaveToStream(s: TStream);
1317 var i, j: Integer;
1318 var start, offset, loffset, size, zcrc, count, comlen: UInt32;
1319 var p: PResource;
1320 var afname: AnsiString;
1321 var mypos: UInt64;
1322 begin
1323 // Write LFH headers and data
1324 start := s.Position;
1325 zcrc := crc32(0, nil, 0);
1326 if FSection <> nil then
1327 begin
1328 for i := 0 to High(FSection) do
1329 begin
1330 if FSection[i].list <> nil then
1331 begin
1332 for j := 0 to High(FSection[i].list) do
1333 begin
1334 p := @FSection[i].list[j];
1335 afname := GetFileName(FSection[i].name, p.name);
1336 WriteLFH(s, p.flags, p.comp, p.mtime, p.chksum, p.csize, p.usize, afname);
1337 if p.stream <> nil then
1338 begin
1339 Assert(p.stream.Size = p.csize);
1340 p.stream.SaveToStream(s);
1341 end
1342 else if FStream <> nil then
1343 begin
1344 FStream.Seek(p.pos, TSeekOrigin.soBeginning);
1345 s.CopyFrom(FStream, p.csize);
1346 end
1347 else
1348 begin
1349 raise Exception.Create('No data source available (somethig very wrong)');
1350 end;
1351 end;
1352 end
1353 else
1354 begin
1355 afname := GetFileName(FSection[i].name, '');
1356 WriteLFH(s, 0, ZIP_COMP_STORE, FSection[i].mtime, zcrc, 0, 0, afname);
1357 end;
1358 end;
1359 end;
1360 // Write CDR headers
1361 count := 0;
1362 loffset := 0;
1363 offset := s.Position - start;
1364 if FSection <> nil then
1365 begin
1366 for i := 0 to High(FSection) do
1367 begin
1368 if FSection[i].list <> nil then
1369 begin
1370 for j := 0 to High(FSection[i].list) do
1371 begin
1372 p := @FSection[i].list[j];
1373 afname := GetFileName(FSection[i].name, p.name);
1374 WriteCDR(s, p.flags, p.comp, p.mtime, p.chksum, p.csize, p.usize, $00, loffset, afname, p.comment, i);
1375 loffset := loffset + 30 + Length(afname) + p.csize;
1376 Inc(count);
1377 end;
1378 end
1379 else
1380 begin
1381 afname := GetFileName(FSection[i].name, '');
1382 WriteCDR(s, 0, ZIP_COMP_STORE, FSection[i].mtime, zcrc, 0, 0, $10, loffset, afname, FSection[i].comment, i);
1383 loffset := loffset + 30 + Length(afname) + 0;
1384 Inc(count);
1385 end;
1386 end;
1387 end;
1388 Assert(loffset = offset);
1389 Assert(count < $ffff);
1390 size := s.Position - start - offset;
1391 // Write EOCD header
1392 mypos := s.Position;
1393 comlen := Length(FComment);
1394 if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
1395 begin
1396 e_WriteLog('==============================================', MSG_NOTIFY);
1397 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Disk ID : ' + IntToStr(0), MSG_NOTIFY);
1398 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Disk ID with CD : ' + IntToStr(0), MSG_NOTIFY);
1399 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Available CDR''s : ' + IntToStr(count), MSG_NOTIFY);
1400 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Total CDR''s : ' + IntToStr(count), MSG_NOTIFY);
1401 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': CD Length : ' + IntToStr(size), MSG_NOTIFY);
1402 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': CD Offset : $' + IntToHex(offset, 8), MSG_NOTIFY);
1403 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Comment Length : ' + IntToStr(comlen), MSG_NOTIFY);
1404 e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Comment : "' + FComment + '"', MSG_NOTIFY);
1405 e_WriteLog('==============================================', MSG_NOTIFY);
1406 end;
1407 s.WriteBuffer(ZIP_SIGN_EOCD, 4); // EOCD Signature
1408 WriteInt(s, UInt16(0)); // Disk
1409 WriteInt(s, UInt16(0)); // Num of Disks
1410 WriteInt(s, UInt16(count)); // Num of CDRs
1411 WriteInt(s, UInt16(count)); // Total CDR entries
1412 WriteInt(s, UInt32(size)); // Central Directory size
1413 WriteInt(s, UInt32(offset)); // Central Directory offset
1414 WriteInt(s, UInt16(comlen)); // Comment field length
1415 s.WriteBuffer(FComment[1], comlen); // Comment
1416 end;
1418 procedure TZIPEditor.SaveTo(FileName: String);
1419 var s: TFileStream;
1420 begin
1421 try
1422 s := TFileStream.Create(FileName, fmCreate);
1423 try
1424 SaveToStream(s);
1425 finally
1426 s.Free();
1427 end;
1428 except
1429 on e: Exception do
1430 begin
1431 if gWADEditorLogLevel >= DFWAD_LOG_INFO then
1432 e_WriteLog('ZIP: Failed to create file ' + FileName + ', reason: ' + e.Message, MSG_WARNING);
1433 raise e;
1434 end;
1435 end;
1436 end;
1438 function TZIPEditor.GetLastError: Integer;
1439 begin
1440 Result := FLastError;
1441 end;
1443 function TZIPEditor.GetLastErrorStr: String;
1444 begin
1445 case FLastError of
1446 DFWAD_NOERROR: Result := '';
1447 DFWAD_ERROR_WADNOTFOUND: Result := 'DFZIP file not found';
1448 DFWAD_ERROR_CANTOPENWAD: Result := 'Can''t open DFZIP file';
1449 DFWAD_ERROR_RESOURCENOTFOUND: Result := 'Resource not found';
1450 DFWAD_ERROR_FILENOTWAD: Result := 'File is not DFZIP';
1451 DFWAD_ERROR_WADNOTLOADED: Result := 'DFZIP file is not loaded';
1452 DFWAD_ERROR_READRESOURCE: Result := 'Read resource error';
1453 DFWAD_ERROR_READWAD: Result := 'Read DFZIP error';
1454 otherwise Result := IntToStr(FLastError);
1455 end;
1456 end;
1458 function TZIPEditor.GetResourcesCount: Word;
1459 var i: Integer;
1460 begin
1461 Result := 0;
1462 if FSection <> nil then
1463 begin
1464 Result := Result + Length(FSection);
1465 for i := 0 to High(FSection) do
1466 if FSection[i].list <> nil then
1467 Result := Result + Length(FSection[i].list);
1468 end;
1469 end;
1471 function TZIPEditor.GetVersion: Byte;
1472 begin
1473 Result := FVersion;
1474 end;
1476 begin
1477 gWADEditorFactory.RegisterEditor('DFZIP', TZIPEditor);
1478 end.