DEADSOFTWARE

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