7412af8a12aea7c08fb4a57ed902829e9ec76c03
1 {$INCLUDE ../shared/a_modes.inc}
5 // Implementation restrictions:
6 // - File must start with LFH or EOCD signature
7 // - EOCD must be located strictly at the end of file
8 // - Multi-disk ZIP files are not supported
9 // - UTF-8 not supported yet, expected WIN1251 encoding
10 // - ZIP64 not supported
11 // - Encryption not supported
12 // - Zero-length file names not supported
13 // - CDR holds most actual data about file, LFH mostly ignored
14 // - Attributes, comments and extra data are ignored and not saved
15 // - Store and Deflate compression supported
19 uses Classes
, WADEDITOR
;
29 stream
: TMemoryStream
;
34 list
: array of TResource
;
37 PResource
= ^TResource
;
40 TZIPEditor
= class sealed(WADEDITOR
.TWADEditor
)
42 FSection
: array of TSection
;
47 function FindSectionIDRAW(name
: AnsiString; caseSensitive
: Boolean): Integer;
48 function FindSectionRAW(name
: AnsiString; caseSensitive
: Boolean): PSection
;
49 function InsertSectionRAW(name
: AnsiString): PSection
;
51 function FindSectionID(name
: AnsiString): Integer;
52 function FindSection(name
: AnsiString): PSection
;
53 function InsertSection(name
: AnsiString): PSection
;
55 function InsertFileInfo(const section
, name
: AnsiString; pos
, csize
, usize
, comp
, crc
: UInt32
): PResource
;
56 function Preload(p
: PResource
): Boolean;
57 function GetSourceStream(p
: PResource
): TStream
;
59 procedure ReadLFH(s
: TStream
; fname
: AnsiString; xcsize
, xusize
, xcomp
, xcrc
: UInt32
);
60 procedure ReadCDR(s
: TStream
; cdrid
: Integer);
61 function FindEOCD(s
: TStream
): Boolean;
62 procedure ReadEOCD(s
: TStream
);
64 procedure WriteLFH(s
: TStream
; comp
, crc
, csize
, usize
: UInt32
; const afname
: AnsiString);
65 procedure WriteCDR(s
: TStream
; comp
, crc
, csize
, usize
, attr
, offset
: UInt32
; const afname
: AnsiString);
66 procedure SaveToStream(s
: TStream
);
70 destructor Destroy(); override;
71 procedure FreeWAD(); override;
72 function ReadFile2(FileName
: string): Boolean; override;
73 function ReadMemory(Data
: Pointer; Len
: LongWord): Boolean; override;
74 procedure CreateImage(); override;
75 function AddResource(Data
: Pointer; Len
: LongWord; Name
, Section
: String): Boolean; override; overload
;
76 function AddResource(FileName
, Name
, Section
: String): Boolean; override; overload
;
77 function AddAlias(Res
, Alias
: String): Boolean; override;
78 procedure AddSection(Name
: String); override;
79 procedure RemoveResource(Section
, Resource
: String); override;
80 procedure SaveTo(FileName
: String); override;
81 function HaveResource(Section
, Resource
: String): Boolean; override;
82 function HaveSection(Section
: string): Boolean; override;
83 function GetResource(Section
, Resource
: String; var pData
: Pointer; var Len
: Integer): Boolean; override;
84 function GetSectionList(): SArray
; override;
85 function GetResourcesList(Section
: String): SArray
; override;
87 function GetLastError
: Integer; override;
88 function GetLastErrorStr
: String; override;
89 function GetResourcesCount
: Word; override;
90 function GetVersion
: Byte; override;
95 uses SysUtils
, StrUtils
, zstream
, crc
, e_log
;
98 ZIP_SIGN_CDR
= 'PK'#1#2;
99 ZIP_SIGN_LFH
= 'PK'#3#4;
100 ZIP_SIGN_EOCD
= 'PK'#5#6;
105 ZIP_COMP_REDUCE1
= 2;
106 ZIP_COMP_REDUCE2
= 3;
107 ZIP_COMP_REDUCE3
= 4;
108 ZIP_COMP_REDUCE4
= 5;
109 ZIP_COMP_IMPLODE
= 6;
110 ZIP_COMP_TOKENIZED
= 7;
111 ZIP_COMP_DEFLATE
= 8;
112 ZIP_COMP_DEFLATE64
= 9;
113 ZIP_COMP_TERSE1
= 10;
117 ZIP_COMP_TERSE2
= 18;
124 ZIP_COMP_WAVPACK
= 97;
129 ZIP_SYSTEM
= 0; // DOS / FAT
130 ZIP_VERSION
= 20; // Min version
131 ZIP_MAXVERSION
= 63; // Max supported version
133 procedure ToSectionFile(fname
: AnsiString; out section
, name
: AnsiString); inline;
136 i
:= LastDelimiter('/', fname
);
137 section
:= Copy(fname
, 1, i
- 1);
138 name
:= Copy(fname
, i
+ 1)
141 function GetFileName(const Section
, Name
: AnsiString): AnsiString; inline;
146 Result
:= Section
+ '/' + Name
;
149 function PrepString(const s
: AnsiString; caseSensitive
, extSensitive
: Boolean): AnsiString; inline;
153 if caseSensitive
= False then
155 Result
:= UpperCase(Result
);
157 if extSensitive
= False then
159 i
:= Pos('.', Result
); // fix dotfiles
161 SetLength(Result
, i
- 1);
165 function FindResourceIDRAW(p
: PSection
; name
: AnsiString; caseSensitive
, extSensitive
: Boolean): Integer;
166 var i
: Integer; pname
: AnsiString;
170 pname
:= PrepString(name
, caseSensitive
, extSensitive
);
171 for i
:= 0 to High(p
.list
) do
173 if PrepString(p
.list
[i
].name
, caseSensitive
, extSensitive
) = pname
then
183 function FindResourceID(p
: PSection
; name
: AnsiString): Integer;
186 i
:= FindResourceIDRAW(p
, name
, True, True); // CaSeNaMe.Ext
189 i
:= FindResourceIDRAW(p
, name
, False, True); // CASENAME.EXT
192 i
:= FindResourceIDRAW(p
, name
, True, False); // CaSeNaMe
195 i
:= FindResourceIDRAW(p
, name
, False, False); // CASENAME
202 function FindResource(p
: PSection
; name
: AnsiString): PResource
;
205 i
:= FindResourceID(p
, name
);
214 function TZIPEditor
.FindSectionIDRAW(name
: AnsiString; caseSensitive
: Boolean): Integer;
215 var i
: Integer; pname
: AnsiString;
217 if FSection
<> nil then
219 pname
:= PrepString(name
, caseSensitive
, True);
220 for i
:= 0 to High(FSection
) do
222 if PrepString(FSection
[i
].name
, caseSensitive
, True) = pname
then
232 function TZIPEditor
.FindSectionRAW(name
: AnsiString; caseSensitive
: Boolean): PSection
;
235 i
:= FindSectionIDRAW(name
, caseSensitive
);
237 Result
:= @FSection
[i
]
242 function TZIPEditor
.InsertSectionRAW(name
: AnsiString): PSection
;
245 if FSection
= nil then i
:= 0 else i
:= Length(FSection
);
246 SetLength(FSection
, i
+ 1);
247 FSection
[i
] := Default(TSection
);
248 FSection
[i
].name
:= name
;
249 Result
:= @FSection
[i
];
254 function TZIPEditor
.FindSectionID(name
: AnsiString): Integer;
255 var fixName
: AnsiString;
257 fixName
:= StringReplace(name
, '\', '/', [rfReplaceAll
], TStringReplaceAlgorithm
.sraManySmall
);
258 Result
:= FindSectionIDRAW(fixName
, True); // CaSeNaMe
260 Result
:= FindSectionIDRAW(fixName
, False); // CASENAME
263 function TZIPEditor
.FindSection(name
: AnsiString): PSection
;
264 var fixName
: AnsiString;
266 fixName
:= StringReplace(name
, '\', '/', [rfReplaceAll
], TStringReplaceAlgorithm
.sraManySmall
);
267 Result
:= FindSectionRAW(fixName
, True); // CaSeNaMe
269 Result
:= FindSectionRAW(fixName
, False); // CASENAME
272 function TZIPEditor
.InsertSection(name
: AnsiString): PSection
;
274 Result
:= FindSection(name
);
276 Result
:= InsertSectionRAW(name
);
281 function TZIPEditor
.InsertFileInfo(const section
, name
: AnsiString; pos
, csize
, usize
, comp
, crc
: UInt32
): PResource
;
282 var p
: PSection
; i
: Integer;
284 p
:= FindSectionRAW(section
, True);
286 p
:= InsertSectionRAW(section
);
287 if p
.list
= nil then i
:= 0 else i
:= Length(p
.list
);
288 SetLength(p
.list
, i
+ 1);
289 p
.list
[i
] := Default(TResource
);
290 p
.list
[i
].name
:= name
;
291 p
.list
[i
].pos
:= pos
;
292 p
.list
[i
].csize
:= csize
;
293 p
.list
[i
].usize
:= usize
;
294 p
.list
[i
].comp
:= comp
;
295 p
.list
[i
].chksum
:= crc
;
296 p
.list
[i
].stream
:= nil;
297 Result
:= @p
.list
[i
];
302 function TZIPEditor
.AddAlias(Res
, Alias
: String): Boolean;
304 // Hard-links not supported in ZIP
305 // However, they never created by editor
309 function TZIPEditor
.AddResource(Data
: Pointer; Len
: LongWord; Name
, Section
: String): Boolean;
310 const compress
: Boolean = True;
311 const level
: TCompressionLevel
= TCompressionLevel
.clMax
;
312 var s
: TMemoryStream
; cs
: TCompressionStream
; p
: PResource
;
313 var comp
, crc
: UInt32
;
318 s
:= TMemoryStream
.Create();
320 if compress
and (Len
> 0) then
322 cs
:= TCompressionStream
.Create(level
, s
, True);
324 cs
.WriteBuffer(PByte(Data
)[0], Len
);
326 comp
:= ZIP_COMP_DEFLATE
;
331 if (Len
= 0) or (compress
= False) or (s
.Size
>= Len
) then
333 s
.Seek(0, TSeekOrigin
.soBeginning
);
335 s
.WriteBuffer(PByte(Data
)[0], Len
);
336 comp
:= ZIP_COMP_STORE
;
337 Assert(s
.Size
= Len
);
339 crc
:= crc32(0, nil, 0);
340 crc
:= crc32(crc
, data
, len
);
341 p
:= InsertFileInfo(Section
, Name
, $ffffffff, s
.Size
, Len
, comp
, crc
);
351 function TZIPEditor
.AddResource(FileName
, Name
, Section
: String): Boolean;
352 var s
: TFileStream
; ptr
: PByte;
355 FLastError
:= DFWAD_ERROR_READWAD
;
357 s
:= TFileStream
.Create(FileName
, fmOpenRead
or fmShareDenyWrite
);
361 s
.ReadBuffer(ptr
[0], s
.Size
);
362 Result
:= AddResource(ptr
, s
.Size
, Name
, Section
);
363 if Result
= True then FLastError
:= DFWAD_NOERROR
;
373 if gWADEditorLogLevel
>= DFWAD_LOG_INFO
then
374 e_WriteLog('DFZIP: AddResource: failed to open file ' + FileName
, MSG_NOTIFY
);
375 FLastError
:= DFWAD_ERROR_CANTOPENWAD
;
380 constructor TZIPEditor
.Create();
384 FLastError
:= DFWAD_NOERROR
;
385 FVersion
:= ZIP_VERSION
;
389 destructor TZIPEditor
.Destroy();
395 procedure TZIPEditor
.FreeWAD();
398 if FSection
<> nil then
400 for i
:= 0 to High(FSection
) do
402 if FSection
[i
].list
<> nil then
404 for j
:= 0 to High(FSection
[i
].list
) do
406 if FSection
[i
].list
[j
].stream
<> nil then
408 FreeAndNil(FSection
[i
].list
[j
].stream
);
411 SetLength(FSection
[i
].list
, 0);
414 SetLength(FSection
, 0);
416 if FStream
<> nil then
420 FLastError
:= DFWAD_NOERROR
;
421 FVersion
:= ZIP_VERSION
;
424 function TZIPEditor
.Preload(p
: PResource
): Boolean;
425 var s
: TMemoryStream
;
430 Result
:= p
.stream
<> nil;
431 if (p
.stream
= nil) and (FStream
<> nil) then
433 s
:= TMemoryStream
.Create();
437 FStream
.Seek(p
.pos
, TSeekOrigin
.soBeginning
);
438 s
.CopyFrom(FStream
, p
.csize
);
440 Assert(s
.Size
= p
.csize
); // wtf, random size if copied zero bytes!
451 procedure TZIPEditor
.CreateImage();
454 if FStream
= nil then
456 if gWADEditorLogLevel
>= DFWAD_LOG_DEBUG
then
457 e_WriteLog('DFZIP: CreateImage: File not assigned', MSG_NOTIFY
);
458 FLastError
:= DFWAD_ERROR_WADNOTLOADED
;
460 else if FStream
is TMemoryStream
then
462 if gWADEditorLogLevel
>= DFWAD_LOG_DEBUG
then
463 e_WriteLog('DFZIP: CreateImage: Memory stream', MSG_NOTIFY
);
464 FLastError
:= DFWAD_NOERROR
;
468 if FSection
<> nil then
470 for i
:= 0 to High(FSection
) do
472 if FSection
[i
].list
<> nil then
474 for j
:= 0 to High(FSection
[i
].list
) do
476 if Preload(@FSection
[i
].list
[j
]) = False then
478 if gWADEditorLogLevel
>= DFWAD_LOG_WARN
then
479 e_WriteLog('DFZIP: CreateImage: failed to preload resource [' + FSection
[i
].name
+ '][' + FSection
[i
].list
[j
].name
+ ']', MSG_WARNING
);
480 FLastError
:= DFWAD_ERROR_CANTOPENWAD
;
488 FLastError
:= DFWAD_NOERROR
;
492 procedure TZIPEditor
.AddSection(Name
: String);
494 if InsertSection(Name
) = nil then
495 raise Exception
.Create('DFZIP: AddSection[' + Name
+ ']: failed to insert');
498 function TZIPEditor
.HaveResource(Section
, Resource
: String): Boolean;
500 Result
:= FindResource(FindSection(Section
), Resource
) <> nil;
503 function TZIPEditor
.HaveSection(Section
: String): Boolean;
505 Result
:= FindSection(Section
) <> nil;
508 function TZIPEditor
.GetSourceStream(p
: PResource
): TStream
;
512 if p
.stream
<> nil then
515 src
.Seek(0, TSeekOrigin
.soBeginning
);
517 else if FStream
<> nil then
520 src
.Seek(p
.pos
, TSeekOrigin
.soBeginning
);
525 function TZIPEditor
.GetResource(Section
, Resource
: String; var pData
: Pointer; var Len
: Integer): Boolean;
526 var p
: PResource
; ptr
: PByte; src
: TStream
; tmp
: TDecompressionStream
; crc
: UInt32
;
528 FLastError
:= DFWAD_ERROR_CANTOPENWAD
;
532 p
:= FindResource(FindSection(Section
), Resource
);
535 src
:= GetSourceStream(p
);
541 Assert(p
.csize
= p
.usize
);
542 GetMem(ptr
, p
.usize
);
544 src
.ReadBuffer(ptr
[0], p
.usize
);
552 tmp
:= TDecompressionStream
.Create(src
, True);
554 GetMem(ptr
, p
.usize
);
556 tmp
.ReadBuffer(ptr
[0], p
.usize
);
567 if gWADEditorLogLevel
>= DFWAD_LOG_INFO
then
568 e_WriteLog('DFZIP: Failed to decompress by DEFLATE method, reason: ' + e
.Message, MSG_WARNING
);
573 raise Exception
.Create('Unknown compression method: ' + IntToStr(p
.comp
));
578 if gWADEditorLogLevel
>= DFWAD_LOG_WARN
then
579 e_WriteLog('DFZIP: No available source for file data', MSG_WARNING
);
580 FLastError
:= DFWAD_ERROR_WADNOTLOADED
;
582 if Result
= True then
584 crc
:= crc32(0, nil, 0);
585 crc
:= crc32(crc
, ptr
, p
.usize
);
586 Result
:= crc
= p
.chksum
;
587 if Result
= True then
591 FLastError
:= DFWAD_NOERROR
;
595 if gWADEditorLogLevel
>= DFWAD_LOG_INFO
then
596 e_WriteLog('DFZIP: File integrity check failed: expected CRC32 $' + IntToHex(p
.chksum
, 8) + ', calculated CRC32 $' + IntToHex(crc
, 8), MSG_WARNING
);
603 if gWADEditorLogLevel
>= DFWAD_LOG_DEBUG
then
604 e_WriteLog('DFZIP: Resource not found', MSG_NOTIFY
);
605 FLastError
:= DFWAD_ERROR_RESOURCENOTFOUND
;
609 function TZIPEditor
.GetResourcesList(Section
: String): SArray
;
610 var p
: PSection
; i
: Integer;
613 p
:= FindSection(Section
);
614 if (p
<> nil) and (p
.list
<> nil) then
616 SetLength(Result
, Length(p
.list
));
617 for i
:= 0 to High(p
.list
) do
619 Result
[i
] := p
.list
[i
].name
;
624 function TZIPEditor
.GetSectionList(): SArray
;
628 if FSection
<> nil then
630 SetLength(Result
, Length(FSection
));
631 for i
:= 0 to High(FSection
) do
633 Result
[i
] := FSection
[i
].name
;
638 procedure TZIPEditor
.ReadLFH(s
: TStream
; fname
: AnsiString; xcsize
, xusize
, xcomp
, xcrc
: UInt32
);
639 var sig
: packed array [0..3] of Char;
640 var va
, vb
, flags
, comp
: UInt16
;
641 var mtime
, crc
, csize
, usize
: UInt32
;
642 var fnlen
, extlen
: UInt16
;
643 var mypos
, datapos
: UInt64;
644 var section
, name
: AnsiString;
648 if mypos
+ 30 <= s
.Size
then
650 s
.ReadBuffer(sig
[0], 4);
651 if sig
= ZIP_SIGN_LFH
then
653 va
:= s
.ReadByte(); // Min Version
654 vb
:= s
.ReadByte(); // Min System
655 flags
:= LEtoN(s
.ReadWord());
656 comp
:= LEtoN(s
.ReadWord());
657 mtime
:= LEtoN(s
.ReadDWord());
658 crc
:= LEtoN(s
.ReadDWord());
659 csize
:= LEtoN(s
.ReadDWord());
660 usize
:= LEtoN(s
.ReadDWord());
661 fnlen
:= LEtoN(s
.ReadWord());
662 extlen
:= LEtoN(s
.ReadWord());
663 datapos
:= s
.Position
+ fnlen
+ extlen
;
664 if gWADEditorLogLevel
>= DFWAD_LOG_DEBUG
then
666 e_WriteLog('LFH @' + IntToHex(mypos
, 8) + ': Min Version : ' + IntToStr(va
), MSG_NOTIFY
);
667 e_WriteLog('LFH @' + IntToHex(mypos
, 8) + ': Min System : ' + IntToStr(vb
), MSG_NOTIFY
);
668 e_WriteLog('LFH @' + IntToHex(mypos
, 8) + ': Flags : $' + IntToHex(flags
, 4), MSG_NOTIFY
);
669 e_WriteLog('LFH @' + IntToHex(mypos
, 8) + ': Compression : ' + IntToStr(comp
), MSG_NOTIFY
);
670 e_WriteLog('LFH @' + IntToHex(mypos
, 8) + ': Modification Time : $' + IntToHex(mtime
, 8), MSG_NOTIFY
);
671 e_WriteLog('LFH @' + IntToHex(mypos
, 8) + ': CRC-32 : $' + IntToHex(crc
, 8), MSG_NOTIFY
);
672 e_WriteLog('LFH @' + IntToHex(mypos
, 8) + ': Compressed size : ' + IntToStr(csize
), MSG_NOTIFY
);
673 e_WriteLog('LFH @' + IntToHex(mypos
, 8) + ': Decompressed size : ' + IntToStr(usize
), MSG_NOTIFY
);
674 e_WriteLog('LFH @' + IntToHex(mypos
, 8) + ': Name Length : ' + IntToStr(fnlen
), MSG_NOTIFY
);
675 e_WriteLog('LFH @' + IntToHex(mypos
, 8) + ': Extension Length : ' + IntToStr(extlen
), MSG_NOTIFY
);
676 e_WriteLog('LFH @' + IntToHex(mypos
, 8) + ': <DATA OFFSET> : $' + IntToHex(datapos
, 8), MSG_NOTIFY
);
678 if (va
>= 10) and (va
<= ZIP_MAXVERSION
) then
680 if datapos
+ xcsize
<= s
.Size
then
682 ToSectionFile(fname
, section
, name
);
685 p
:= FindSectionRAW(section
, True);
687 p
:= InsertSectionRAW(section
)
691 p
:= InsertFileInfo(section
, name
, datapos
, xcsize
, xusize
, xcomp
, xcrc
);
694 raise Exception
.Create('Failed to register resource [' + fname
+ ']');
697 raise Exception
.Create('Invalid LFH size (corrupted file?)');
701 FLastError
:= DFWAD_ERROR_WRONGVERSION
;
702 raise Exception
.Create('Unsupported CDR version ' + IntToStr(va
) + ', not in range [10..' + IntToStr(ZIP_MAXVERSION
) + ']');
706 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?)');
709 raise Exception
.Create('Invalid LFH size (corrupted file?)');
712 procedure TZIPEditor
.ReadCDR(s
: TStream
; cdrid
: Integer);
713 const ZIP_ENCRYPTION_MASK
= (1 << 0) or (1 << 6) or (1 << 13);
714 var sig
: packed array [0..3] of Char;
715 var vva
, vvb
, va
, vb
, flags
, comp
: UInt16
;
716 var mtime
, crc
, csize
, usize
: UInt32
;
717 var fnlen
, extlen
, comlen
, disk
, iattr
: UInt16
;
718 var eattr
, offset
: UInt32
;
719 var mypos
, next
: UInt64;
723 s
.ReadBuffer(sig
[0], 4);
724 if sig
= ZIP_SIGN_CDR
then
726 // Valid Central Directory Signature
727 vva
:= s
.ReadByte(); // Writer Version
728 vvb
:= s
.ReadByte(); // Writer System
729 va
:= s
.ReadByte(); // Min Version
730 vb
:= s
.ReadByte(); // Min System
731 flags
:= LEtoN(s
.ReadWord());
732 comp
:= LEtoN(s
.ReadWord());
733 mtime
:= LEtoN(s
.ReadDWord());
734 crc
:= LEtoN(s
.ReadDWord());
735 csize
:= LEtoN(s
.ReadDWord());
736 usize
:= LEtoN(s
.ReadDWord());
737 fnlen
:= LEtoN(s
.ReadWord());
738 extlen
:= LEtoN(s
.ReadWord());
739 comlen
:= LEtoN(s
.ReadWord());
740 disk
:= LEtoN(s
.ReadWord());
741 iattr
:= LEtoN(s
.ReadWord());
742 eattr
:= LEtoN(s
.ReadDWord());
743 offset
:= LEtoN(s
.ReadDWord());
744 next
:= s
.Position
+ fnlen
+ extlen
+ comlen
;
746 if gWADEditorLogLevel
>= DFWAD_LOG_DEBUG
then
748 e_WriteLog('CDR#' + IntToStr(cdrid
) + ' @' + IntToHex(mypos
, 8) + ': Writer Version : ' + IntToStr(vva
), MSG_NOTIFY
);
749 e_WriteLog('CDR#' + IntToStr(cdrid
) + ' @' + IntToHex(mypos
, 8) + ': Writer System : ' + IntToStr(vvb
), MSG_NOTIFY
);
750 e_WriteLog('CDR#' + IntToStr(cdrid
) + ' @' + IntToHex(mypos
, 8) + ': Min Version : ' + IntToStr(va
), MSG_NOTIFY
);
751 e_WriteLog('CDR#' + IntToStr(cdrid
) + ' @' + IntToHex(mypos
, 8) + ': Min System : ' + IntToStr(vb
), MSG_NOTIFY
);
752 e_WriteLog('CDR#' + IntToStr(cdrid
) + ' @' + IntToHex(mypos
, 8) + ': Flags : $' + IntToHex(flags
, 4), MSG_NOTIFY
);
753 e_WriteLog('CDR#' + IntToStr(cdrid
) + ' @' + IntToHex(mypos
, 8) + ': Compression : ' + IntToStr(comp
), MSG_NOTIFY
);
754 e_WriteLog('CDR#' + IntToStr(cdrid
) + ' @' + IntToHex(mypos
, 8) + ': Modification Time : $' + IntToHex(mtime
, 8), MSG_NOTIFY
);
755 e_WriteLog('CDR#' + IntToStr(cdrid
) + ' @' + IntToHex(mypos
, 8) + ': CRC-32 : $' + IntToHex(crc
, 8), MSG_NOTIFY
);
756 e_WriteLog('CDR#' + IntToStr(cdrid
) + ' @' + IntToHex(mypos
, 8) + ': Compressed size : ' + IntToStr(csize
), MSG_NOTIFY
);
757 e_WriteLog('CDR#' + IntToStr(cdrid
) + ' @' + IntToHex(mypos
, 8) + ': Decompressed size : ' + IntToStr(usize
), MSG_NOTIFY
);
758 e_WriteLog('CDR#' + IntToStr(cdrid
) + ' @' + IntToHex(mypos
, 8) + ': Name Length : ' + IntToStr(fnlen
), MSG_NOTIFY
);
759 e_WriteLog('CDR#' + IntToStr(cdrid
) + ' @' + IntToHex(mypos
, 8) + ': Extension Length : ' + IntToStr(extlen
), MSG_NOTIFY
);
760 e_WriteLog('CDR#' + IntToStr(cdrid
) + ' @' + IntToHex(mypos
, 8) + ': Comment Length : ' + IntToStr(comlen
), MSG_NOTIFY
);
761 e_WriteLog('CDR#' + IntToStr(cdrid
) + ' @' + IntToHex(mypos
, 8) + ': Disk : ' + IntToStr(disk
), MSG_NOTIFY
);
762 e_WriteLog('CDR#' + IntToStr(cdrid
) + ' @' + IntToHex(mypos
, 8) + ': Internal Attrib : $' + IntToHex(iattr
, 4), MSG_NOTIFY
);
763 e_WriteLog('CDR#' + IntToStr(cdrid
) + ' @' + IntToHex(mypos
, 8) + ': External Attrib : $' + IntToHex(iattr
, 8), MSG_NOTIFY
);
764 e_WriteLog('CDR#' + IntToStr(cdrid
) + ' @' + IntToHex(mypos
, 8) + ': LFH Offset : $' + IntToHex(offset
, 8), MSG_NOTIFY
);
766 if (va
>= 10) and (va
<= ZIP_MAXVERSION
) then
768 if (flags
and ZIP_ENCRYPTION_MASK
) = 0 then
770 if (csize
<> $ffffffff) and (usize
<> $ffffffff) and (disk
<> $ffff) and (offset
<> $ffffffff) then
774 if (next
<= s
.Size
) and (fnlen
> 0) then
778 if csize
<> usize
then
779 raise Exception
.Create('Compressed size ' + IntToStr(csize
) + ' != Descompressed size ' + IntToStr(usize
) + 'for STORE method (corrupted file?)');
803 raise Exception
.Create('Encrypted archives not supported');
805 raise Exception
.Create('Unsupported compression method ' + IntToStr(comp
));
807 // TODO: check bit 11 (UTF8 name and comment)
808 GetMem(name
, UInt32(fnlen
) + 1);
810 s
.ReadBuffer(name
[0], fnlen
);
812 if gWADEditorLogLevel
>= DFWAD_LOG_DEBUG
then
813 e_WriteLog('CDR#' + IntToStr(cdrid
) + ' @' + IntToHex(mypos
, 8) + ': Name : "' + name
+ '"', MSG_NOTIFY
);
814 s
.Seek(offset
, TSeekOrigin
.soBeginning
);
815 ReadLFH(s
, name
, csize
, usize
, comp
, crc
);
817 s
.Seek(next
, TSeekOrigin
.soBeginning
);
822 raise Exception
.Create('Empty files names not supported');
825 raise Exception
.Create('Splitted archives not supported');
829 FLastError
:= DFWAD_ERROR_WRONGVERSION
;
830 raise Exception
.Create('ZIP64 not supported');
835 FLastError
:= DFWAD_ERROR_READWAD
;
836 raise Exception
.Create('Encrypted archives not supported');
841 FLastError
:= DFWAD_ERROR_WRONGVERSION
;
842 raise Exception
.Create('Unsupported CDR version ' + IntToStr(va
) + ', not in range [10..' + IntToStr(ZIP_MAXVERSION
) + ']');
846 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?)');
849 function TZIPEditor
.FindEOCD(s
: TStream
): Boolean;
850 const maxedir
= 20; // end of central directory entry
851 const maxecdir
= maxedir
+ 65536; // + comment
852 var sig
: packed array [0..3] of Char; off
, lim
: Int64;
855 if s
.Size
>= maxedir
then
857 if s
.Size
< maxecdir
then lim
:= s
.Size
else lim
:= maxecdir
;
858 lim
:= lim
- maxedir
;
860 while (off
<= lim
) and (Result
= False) do
862 s
.Seek(s
.Size
- off
, TSeekOrigin
.soBeginning
);
863 s
.ReadBuffer(sig
[0], 4);
864 Result
:= sig
= ZIP_SIGN_EOCD
;
870 procedure TZIPEditor
.ReadEOCD(s
: TStream
);
871 var sig
: packed array [0..3] of Char;
872 var idisk
, ndisk
, nrec
, total
, comlen
: UInt16
;
873 var csize
, cpos
, i
: UInt32
;
876 FLastError
:= DFWAD_ERROR_FILENOTWAD
;
878 s
.ReadBuffer(sig
[0], 4);
879 if (sig
= ZIP_SIGN_LFH
) or (sig
= ZIP_SIGN_EOCD
) then
883 // End of Central Directory found
884 FLastError
:= DFWAD_ERROR_READWAD
;
885 mypos
:= s
.Position
- 4;
886 idisk
:= LEtoN(s
.ReadWord());
887 ndisk
:= LEtoN(s
.ReadWord());
888 nrec
:= LEtoN(s
.ReadWord());
889 total
:= LEtoN(s
.ReadWord());
890 csize
:= LEtoN(s
.ReadDWord());
891 cpos
:= LEtoN(s
.ReadDWord());
892 comlen
:= LEtoN(s
.ReadWord());
893 if gWADEditorLogLevel
>= DFWAD_LOG_DEBUG
then
895 e_WriteLog('==============================================', MSG_NOTIFY
);
896 e_WriteLog('EOCD @' + IntToHex(mypos
, 8) + ': Disk ID : ' + IntToStr(idisk
), MSG_NOTIFY
);
897 e_WriteLog('EOCD @' + IntToHex(mypos
, 8) + ': Disk ID with CD : ' + IntToStr(ndisk
), MSG_NOTIFY
);
898 e_WriteLog('EOCD @' + IntToHex(mypos
, 8) + ': Available CDR''s : ' + IntToStr(nrec
), MSG_NOTIFY
);
899 e_WriteLog('EOCD @' + IntToHex(mypos
, 8) + ': Total CDR''s : ' + IntToStr(total
), MSG_NOTIFY
);
900 e_WriteLog('EOCD @' + IntToHex(mypos
, 8) + ': CD Length : ' + IntToStr(csize
), MSG_NOTIFY
);
901 e_WriteLog('EOCD @' + IntToHex(mypos
, 8) + ': CD Offset : $' + IntToHex(cpos
, 8), MSG_NOTIFY
);
902 e_WriteLog('EOCD @' + IntToHex(mypos
, 8) + ': Comment Length : ' + IntToStr(comlen
), MSG_NOTIFY
);
904 if (idisk
<> $ffff) and (ndisk
<> $ffff) and (nrec
<> $ffff) and (total
<> $ffff) and (csize
<> $ffffffff) and (cpos
<> $ffffffff) then
906 if s
.Position
+ comlen
= s
.Size
then
908 if (idisk
= 0) and (ndisk
= 0) and (nrec
= total
) then
910 if (nrec
* 46 <= csize
) and (UInt64(cpos
) + csize
<= s
.Size
) then
915 s
.Seek(cpos
, TSeekOrigin
.soBeginning
);
918 if gWADEditorLogLevel
>= DFWAD_LOG_DEBUG
then
919 e_WriteLog('==============================================', MSG_NOTIFY
);
923 if gWADEditorLogLevel
>= DFWAD_LOG_DEBUG
then
924 e_WriteLog('==============================================', MSG_NOTIFY
);
928 raise Exception
.Create('Central Directory too big (corrupted file?)');
931 raise Exception
.Create('Splitted archives not supported');
934 raise Exception
.Create('EOCD too big (corrupted file?)');
937 raise Exception
.Create('ZIP64 not supported');
940 raise Exception
.Create('EOCD not found (corrupted file?)');
943 raise Exception
.Create('Not DFZIP file');
946 function TZIPEditor
.ReadFile2(FileName
: String): Boolean;
953 s
:= TFileStream
.Create(FileName
, fmOpenRead
or fmShareDenyWrite
);
957 FLastError
:= DFWAD_NOERROR
;
966 if gWADEditorLogLevel
>= DFWAD_LOG_INFO
then
967 e_WriteLog('ZIP: Failed to read ZIP from file ' + FileName
+ ', reason: ' + e
.Message, MSG_WARNING
);
975 if gWADEditorLogLevel
>= DFWAD_LOG_INFO
then
976 e_WriteLog('ZIP: Failed to open file ' + FileName
+ ', reason: ' + e
.Message, MSG_WARNING
);
977 if FileExists(FileName
) then
978 FLastError
:= DFWAD_ERROR_CANTOPENWAD
980 FLastError
:= DFWAD_ERROR_WADNOTFOUND
;
985 function TZIPEditor
.ReadMemory(Data
: Pointer; Len
: LongWord): Boolean;
986 var s
: TMemoryStream
;
991 s
:= TMemoryStream
.Create
;
994 s
.WriteBuffer(PByte(Data
)[0], Len
);
995 s
.Seek(0, soBeginning
);
998 FLastError
:= DFWAD_NOERROR
;
1007 if gWADEditorLogLevel
>= DFWAD_LOG_INFO
then
1008 e_WriteLog('ZIP: Failed to read ZIP from memory, reason: ' + e
.Message, MSG_WARNING
);
1015 procedure TZIPEditor
.RemoveResource(Section
, Resource
: String);
1016 var p
: PSection
; i
: Integer;
1018 p
:= FindSection(Section
);
1019 i
:= FindResourceID(p
, Resource
);
1022 if p
.list
[i
].stream
<> nil then
1023 FreeAndNil(p
.list
[i
].stream
);
1024 for i
:= i
+ 1 to High(p
.list
) do
1026 p
.list
[i
- 1] := p
.list
[i
];
1028 SetLength(p
.list
, High(p
.list
));
1032 procedure TZIPEditor
.WriteLFH(s
: TStream
; comp
, crc
, csize
, usize
: UInt32
; const afname
: AnsiString);
1033 var fname
: PChar; flen
: UInt16
;
1035 fname
:= PChar(afname
);
1036 flen
:= Length(fname
);
1037 s
.WriteBuffer(ZIP_SIGN_LFH
, 4); // LFH Signature
1038 s
.WriteByte(ZIP_VERSION
); // Min version
1039 s
.WriteByte(ZIP_SYSTEM
); // System
1040 s
.WriteWord(NtoLE(0)); // Flags
1041 s
.WriteWord(NtoLE(comp
)); // Compression method
1042 s
.WriteDWord(NtoLE(0)); // Modification time/date
1043 s
.WriteDWord(NtoLE(crc
)); // CRC-32
1044 s
.WriteDWord(NtoLE(csize
)); // Compressed size
1045 s
.WriteDWord(NtoLE(usize
)); // Decompressed size
1046 s
.WriteWord(NtoLE(flen
)); // Name field length
1047 s
.WriteWord(NtoLE(0)); // Extra field length
1048 s
.WriteBuffer(fname
[0], flen
); // File Name
1051 procedure TZIPEditor
.WriteCDR(s
: TStream
; comp
, crc
, csize
, usize
, attr
, offset
: UInt32
; const afname
: AnsiString);
1052 var fname
: PChar; flen
: UInt16
;
1054 fname
:= PChar(afname
);
1055 flen
:= Length(fname
);
1056 s
.WriteBuffer(ZIP_SIGN_CDR
, 4); // CDR Signature
1057 s
.WriteByte(ZIP_MAXVERSION
); // Used version
1058 s
.WriteByte(ZIP_SYSTEM
); // Used system
1059 s
.WriteByte(ZIP_VERSION
); // Min version
1060 s
.WriteByte(ZIP_SYSTEM
); // Min system
1061 s
.WriteWord(NtoLE(0)); // Flags
1062 s
.WriteWord(NtoLE(comp
)); // Compression method
1063 s
.WriteDWord(NtoLE(0)); // Modification time/date
1064 s
.WriteDWord(NtoLE(crc
)); // CRC-32
1065 s
.WriteDWord(NtoLE(csize
)); // Compressed size
1066 s
.WriteDWord(NtoLE(usize
)); // Decompressed size
1067 s
.WriteWord(NtoLE(flen
)); // Name field length
1068 s
.WriteWord(NtoLE(0)); // Extra field length
1069 s
.WriteWord(NtoLE(0)); // Comment field length
1070 s
.WriteWord(NtoLE(0)); // Disk
1071 s
.WriteWord(NtoLE(0)); // Internal attributes
1072 s
.WriteDWord(NtoLE(attr
)); // External attributes
1073 s
.WriteDWord(NtoLE(offset
)); // LFH offset
1074 s
.WriteBuffer(fname
[0], flen
); // File Name
1077 procedure TZIPEditor
.SaveToStream(s
: TStream
);
1079 var start
, offset
, loffset
, size
, zcrc
, count
: UInt32
;
1081 var afname
: AnsiString;
1083 // Write LFH headers and data
1084 start
:= s
.Position
;
1085 zcrc
:= crc32(0, nil, 0);
1086 if FSection
<> nil then
1088 for i
:= 0 to High(FSection
) do
1090 if FSection
[i
].list
<> nil then
1092 for j
:= 0 to High(FSection
[i
].list
) do
1094 p
:= @FSection
[i
].list
[j
];
1095 afname
:= GetFileName(FSection
[i
].name
, p
.name
);
1096 WriteLFH(s
, p
.comp
, p
.chksum
, p
.csize
, p
.usize
, afname
);
1097 if p
.stream
<> nil then
1099 Assert(p
.stream
.Size
= p
.csize
);
1100 p
.stream
.SaveToStream(s
);
1102 else if FStream
<> nil then
1104 FStream
.Seek(p
.pos
, TSeekOrigin
.soBeginning
);
1105 s
.CopyFrom(FStream
, p
.csize
);
1109 raise Exception
.Create('No data source available (somethig very wrong)');
1115 afname
:= GetFileName(FSection
[i
].name
, '');
1116 WriteLFH(s
, ZIP_COMP_STORE
, zcrc
, 0, 0, afname
);
1120 // Write CDR headers
1123 offset
:= s
.Position
- start
;
1124 if FSection
<> nil then
1126 for i
:= 0 to High(FSection
) do
1128 if FSection
[i
].list
<> nil then
1130 for j
:= 0 to High(FSection
[i
].list
) do
1132 p
:= @FSection
[i
].list
[j
];
1133 afname
:= GetFileName(FSection
[i
].name
, p
.name
);
1134 WriteCDR(s
, p
.comp
, p
.chksum
, p
.csize
, p
.usize
, 0, loffset
- start
, afname
);
1135 loffset
:= loffset
+ 30 + Length(afname
) + p
.csize
;
1141 afname
:= GetFileName(FSection
[i
].name
, '');
1142 WriteCDR(s
, ZIP_COMP_STORE
, zcrc
, 0, 0, $10, loffset
- start
, afname
);
1143 loffset
:= loffset
+ 30 + Length(afname
) + 0;
1148 Assert(loffset
= offset
);
1149 Assert(count
< $ffff);
1150 size
:= s
.Position
- start
- offset
;
1151 // Write EOCD header
1152 s
.WriteBuffer(ZIP_SIGN_EOCD
, 4); // EOCD Signature
1153 s
.WriteWord(NtoLE(0)); // Disk
1154 s
.WriteWord(NtoLE(0)); // Num of Disks
1155 s
.WriteWord(NtoLE(count
)); // Num of CDRs
1156 s
.WriteWord(NtoLE(count
)); // Total CDR entries
1157 s
.WriteDWord(NtoLE(size
)); // Central Directory size
1158 s
.WriteDWord(NtoLE(offset
)); // Central Directory offset
1159 s
.WriteWord(NtoLE(0)); // Comment field length
1162 procedure TZIPEditor
.SaveTo(FileName
: String);
1166 s
:= TFileStream
.Create(FileName
, fmCreate
);
1175 if gWADEditorLogLevel
>= DFWAD_LOG_INFO
then
1176 e_WriteLog('ZIP: Failed to create file ' + FileName
+ ', reason: ' + e
.Message, MSG_WARNING
);
1182 function TZIPEditor
.GetLastError
: Integer;
1184 Result
:= FLastError
;
1187 function TZIPEditor
.GetLastErrorStr
: String;
1190 DFWAD_NOERROR
: Result
:= '';
1191 DFWAD_ERROR_WADNOTFOUND
: Result
:= 'DFZIP file not found';
1192 DFWAD_ERROR_CANTOPENWAD
: Result
:= 'Can''t open DFZIP file';
1193 DFWAD_ERROR_RESOURCENOTFOUND
: Result
:= 'Resource not found';
1194 DFWAD_ERROR_FILENOTWAD
: Result
:= 'File is not DFZIP';
1195 DFWAD_ERROR_WADNOTLOADED
: Result
:= 'DFZIP file is not loaded';
1196 DFWAD_ERROR_READRESOURCE
: Result
:= 'Read resource error';
1197 DFWAD_ERROR_READWAD
: Result
:= 'Read DFZIP error';
1198 otherwise Result
:= IntToStr(FLastError
);
1202 function TZIPEditor
.GetResourcesCount
: Word;
1206 if FSection
<> nil then
1208 Result
:= Result
+ Length(FSection
);
1209 for i
:= 0 to High(FSection
) do
1210 if FSection
[i
].list
<> nil then
1211 Result
:= Result
+ Length(FSection
[i
].list
);
1215 function TZIPEditor
.GetVersion
: Byte;
1221 gWADEditorFactory
.RegisterEditor('DFZIP', TZIPEditor
);