X-Git-Url: https://deadsoftware.ru/gitweb?a=blobdiff_plain;f=src%2Fshared%2FWADEDITOR_dfzip.pas;h=c01fe7fa5bca8d7ebaa01d62a52cfb48467dc65c;hb=54c1d58fb3e3b6b99b2ad73de6a9eb6509ef0bc4;hp=7f310d4ea4877588aa5308d5724166c923f92c46;hpb=b5cff7c2adac12d222e098a74583bf99dd531e98;p=d2df-editor.git diff --git a/src/shared/WADEDITOR_dfzip.pas b/src/shared/WADEDITOR_dfzip.pas index 7f310d4..c01fe7f 100644 --- a/src/shared/WADEDITOR_dfzip.pas +++ b/src/shared/WADEDITOR_dfzip.pas @@ -6,12 +6,12 @@ unit WADEDITOR_dfzip; // - File must start with LFH or EOCD signature // - EOCD must be located strictly at the end of file // - Multi-disk ZIP files are not supported -// - UTF-8 not supported yet, expected WIN1251 encoding +// - Expect UTF-8 or CP1251 encoded names // - ZIP64 not supported // - Encryption not supported // - Zero-length file names not supported // - CDR holds most actual data about file, LFH mostly ignored -// - Attributes, comments and extra data are ignored and not saved +// - Attributes and extra data are ignored and not preserved // - Store and Deflate compression supported interface @@ -26,11 +26,16 @@ interface usize: UInt32; comp: UInt32; chksum: UInt32; + mtime: UInt32; + flags: UInt32; + comment: AnsiString; stream: TMemoryStream; end; TSection = record name: AnsiString; + mtime: UInt32; + comment: AnsiString; list: array of TResource; end; @@ -41,28 +46,29 @@ interface private FSection: array of TSection; FStream: TStream; + FComment: AnsiString; FLastError: Integer; FVersion: Byte; function FindSectionIDRAW(name: AnsiString; caseSensitive: Boolean): Integer; function FindSectionRAW(name: AnsiString; caseSensitive: Boolean): PSection; - function InsertSectionRAW(name: AnsiString): PSection; + function InsertSectionRAW(name: AnsiString; mtime: UInt32; comment: AnsiString): PSection; function FindSectionID(name: AnsiString): Integer; function FindSection(name: AnsiString): PSection; - function InsertSection(name: AnsiString): PSection; + function InsertSection(name: AnsiString; mtime: UInt32; comment: AnsiString): PSection; - function InsertFileInfo(const section, name: AnsiString; pos, csize, usize, comp, crc: UInt32): PResource; + function InsertFileInfo(const section, name: AnsiString; pos, csize, usize, comp, crc, mtime, flags: UInt32; comment: AnsiString): PResource; function Preload(p: PResource): Boolean; function GetSourceStream(p: PResource): TStream; - procedure ReadLFH(s: TStream; fname: AnsiString; xcsize, xusize, xcomp, xcrc: UInt32); + procedure ReadLFH(s: TStream; fname, xcomment: AnsiString; xcsize, xusize, xcomp, xcrc, xtime, xflags: UInt32); procedure ReadCDR(s: TStream; cdrid: Integer); function FindEOCD(s: TStream): Boolean; procedure ReadEOCD(s: TStream); - procedure WriteLFH(s: TStream; comp, crc, csize, usize: UInt32; const afname: AnsiString); - procedure WriteCDR(s: TStream; comp, crc, csize, usize, eattr, offset: UInt32; const afname: AnsiString; cdrid: Integer); + procedure WriteLFH(s: TStream; flags, comp, mtime, crc, csize, usize: UInt32; const name: AnsiString); + procedure WriteCDR(s: TStream; flags, comp, mtime, crc, csize, usize, eattr, offset: UInt32; const name, com: AnsiString; cdrid: Integer); procedure SaveToStream(s: TStream); public @@ -92,7 +98,7 @@ interface implementation - uses SysUtils, StrUtils, utils, zstream, crc, e_log; + uses SysUtils, StrUtils, DateUtils, Math, utils, zstream, crc, e_log; const ZIP_SIGN_CDR = 'PK'#1#2; @@ -127,9 +133,69 @@ implementation const ZIP_SYSTEM = 0; // DOS / FAT - ZIP_VERSION = 20; // Min version ZIP_MAXVERSION = 63; // Max supported version + const + ZIP_ENCRYPTION_MASK = (1 << 0) or (1 << 6) or (1 << 13); + ZIP_COMP_MASK = (1 << 1) or (1 << 2) or (1 << 4) or (1 << 12); + ZIP_DATA_MASK = (1 << 3); + ZIP_PATCH_MASK = (1 << 5); + ZIP_UTF8_MASK = (1 << 11); + ZIP_STREAM_MASK = (1 << 14); + + function IsASCII(const s: AnsiString): Boolean; + var i: Integer; + begin + for i := 1 to Length(s) do + begin + if s[i] >= #$80 then + begin + Result := False; + exit; + end; + end; + Result := True; + end; + + function IsUTF8(const s: AnsiString): Boolean; + var i, j, len: Integer; + begin + Result := False; + i := 1; len := Length(s); + while i <= len do + begin + case Ord(s[i]) of + $00..$7F: j := 0; + $80..$BF: exit; // invalid encoding + $C0..$DF: j := 1; + $E0..$EF: j := 2; + $F0..$F7: j := 3; + otherwise exit; // invalid encoding + end; + Inc(i); + while j > 0 do + begin + if i > len then exit; // invlid length + case Ord(s[i]) of + $80..$BF: ; // ok + else exit; // invalid encoding + end; + Inc(i); + Dec(j); + end; + end; + Result := True; + end; + + function DosToStr(dostime: UInt32): AnsiString; + begin + try + DateTimeToString(Result, 'yyyy/mm/dd hh:nn:ss', DosDateTimeToDateTime(dostime)); + except on e: EConvertError do + Result := 'INVALID ($' + IntToHex(dostime, 8) + ')'; + end; + end; + procedure ToSectionFile(fname: AnsiString; out section, name: AnsiString); inline; var i: SizeInt; begin @@ -239,13 +305,15 @@ implementation Result := nil; end; - function TZIPEditor.InsertSectionRAW(name: AnsiString): PSection; + function TZIPEditor.InsertSectionRAW(name: AnsiString; mtime: UInt32; comment: AnsiString): PSection; var i: Integer; begin if FSection = nil then i := 0 else i := Length(FSection); SetLength(FSection, i + 1); FSection[i] := Default(TSection); FSection[i].name := name; + FSection[i].mtime := mtime; + FSection[i].comment := comment; Result := @FSection[i]; end; @@ -269,21 +337,21 @@ implementation Result := FindSectionRAW(fixName, False); // CASENAME end; - function TZIPEditor.InsertSection(name: AnsiString): PSection; + function TZIPEditor.InsertSection(name: AnsiString; mtime: UInt32; comment: AnsiString): PSection; begin Result := FindSection(name); if Result = nil then - Result := InsertSectionRAW(name); + Result := InsertSectionRAW(name, mtime, comment); end; - function TZIPEditor.InsertFileInfo(const section, name: AnsiString; pos, csize, usize, comp, crc: UInt32): PResource; + function TZIPEditor.InsertFileInfo(const section, name: AnsiString; pos, csize, usize, comp, crc, mtime, flags: UInt32; comment: AnsiString): PResource; var p: PSection; i: Integer; begin p := FindSectionRAW(section, True); if p = nil then - p := InsertSectionRAW(section); + p := InsertSectionRAW(section, mtime, ''); if p.list = nil then i := 0 else i := Length(p.list); SetLength(p.list, i + 1); p.list[i] := Default(TResource); @@ -293,6 +361,9 @@ implementation p.list[i].usize := usize; p.list[i].comp := comp; p.list[i].chksum := crc; + p.list[i].mtime := mtime; + p.list[i].flags := flags; + p.list[i].comment := comment; p.list[i].stream := nil; Result := @p.list[i]; end; @@ -312,6 +383,8 @@ implementation var s: TMemoryStream; cs: TCompressionStream; p: PResource; var comp, crc: UInt32; begin + Name := win2utf(Name); + Section := win2utf(Section); Result := False; if Name <> '' then begin @@ -338,7 +411,7 @@ implementation end; crc := crc32(0, nil, 0); crc := crc32(crc, data, len); - p := InsertFileInfo(Section, Name, $ffffffff, s.Size, Len, comp, crc); + p := InsertFileInfo(Section, Name, $ffffffff, s.Size, Len, comp, crc, DateTimeToDosDateTime(Now()), 0, ''); p.stream := s; Result := True; except @@ -381,8 +454,9 @@ implementation begin FSection := nil; FStream := nil; + FComment := ''; FLastError := DFWAD_NOERROR; - FVersion := ZIP_VERSION; + FVersion := 10; FreeWAD(); end; @@ -417,8 +491,9 @@ implementation begin FreeAndNil(FStream); end; + FComment := ''; FLastError := DFWAD_NOERROR; - FVersion := ZIP_VERSION; + FVersion := 10; end; function TZIPEditor.Preload(p: PResource): Boolean; @@ -490,17 +565,21 @@ implementation procedure TZIPEditor.AddSection(Name: String); begin - if InsertSection(Name) = nil then + Name := win2utf(Name); + if InsertSection(Name, DateTimeToDosDateTime(Now()), '') = nil then raise Exception.Create('DFZIP: AddSection[' + Name + ']: failed to insert'); end; function TZIPEditor.HaveResource(Section, Resource: String): Boolean; begin + Section := win2utf(Section); + Resource := win2utf(Resource); Result := FindResource(FindSection(Section), Resource) <> nil; end; function TZIPEditor.HaveSection(Section: String): Boolean; begin + Section := win2utf(Section); Result := FindSection(Section) <> nil; end; @@ -524,6 +603,8 @@ implementation function TZIPEditor.GetResource(Section, Resource: String; var pData: Pointer; var Len: Integer): Boolean; var p: PResource; ptr: PByte; src: TStream; tmp: TDecompressionStream; crc: UInt32; begin + Section := win2utf(Section); + Resource := win2utf(Resource); FLastError := DFWAD_ERROR_CANTOPENWAD; Result := False; pData := nil; @@ -616,6 +697,7 @@ implementation function TZIPEditor.GetResourcesList(Section: String): SArray; var p: PSection; i: Integer; begin + Section := win2utf(Section); Result := nil; p := FindSection(Section); if (p <> nil) and (p.list <> nil) then @@ -623,7 +705,7 @@ implementation SetLength(Result, Length(p.list)); for i := 0 to High(p.list) do begin - Result[i] := p.list[i].name; + Result[i] := utf2win(p.list[i].name); end; end; end; @@ -637,12 +719,12 @@ implementation SetLength(Result, Length(FSection)); for i := 0 to High(FSection) do begin - Result[i] := FSection[i].name; + Result[i] := utf2win(FSection[i].name); end; end; end; - procedure TZIPEditor.ReadLFH(s: TStream; fname: AnsiString; xcsize, xusize, xcomp, xcrc: UInt32); + procedure TZIPEditor.ReadLFH(s: TStream; fname, xcomment: AnsiString; xcsize, xusize, xcomp, xcrc, xtime, xflags: UInt32); var sig: packed array [0..3] of Char; var va, vb, flags, comp: UInt16; var mtime, crc, csize, usize: UInt32; @@ -674,7 +756,7 @@ implementation e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Min System : ' + IntToStr(vb), MSG_NOTIFY); e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Flags : $' + IntToHex(flags, 4), MSG_NOTIFY); e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Compression : ' + IntToStr(comp), MSG_NOTIFY); - e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Modification Time : $' + IntToHex(mtime, 8), MSG_NOTIFY); + e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Modification Time : ' + DosToStr(mtime), MSG_NOTIFY); e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': CRC-32 : $' + IntToHex(crc, 8), MSG_NOTIFY); e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Compressed size : ' + IntToStr(csize), MSG_NOTIFY); e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Decompressed size : ' + IntToStr(usize), MSG_NOTIFY); @@ -691,11 +773,11 @@ implementation begin p := FindSectionRAW(section, True); if p = nil then - p := InsertSectionRAW(section) + p := InsertSectionRAW(section, xtime, xcomment); end else begin - p := InsertFileInfo(section, name, datapos, xcsize, xusize, xcomp, xcrc); + p := InsertFileInfo(section, name, datapos, xcsize, xusize, xcomp, xcrc, xtime, xflags and ZIP_COMP_MASK, xcomment); end; if p = nil then raise Exception.Create('Failed to register resource [' + fname + ']'); @@ -717,14 +799,15 @@ implementation end; procedure TZIPEditor.ReadCDR(s: TStream; cdrid: Integer); - const ZIP_ENCRYPTION_MASK = (1 << 0) or (1 << 6) or (1 << 13); var sig: packed array [0..3] of Char; var vva, vvb, va, vb, flags, comp: UInt16; var mtime, crc, csize, usize: UInt32; var fnlen, extlen, comlen, disk, iattr: UInt16; var eattr, offset: UInt32; var mypos, next: UInt64; - var name: PChar; + var tmp: PChar; + var name, comment: AnsiString; + var cvtbug, utf8: Boolean; begin mypos := s.Position; s.ReadBuffer(sig[0], 4); @@ -758,7 +841,7 @@ implementation e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Min System : ' + IntToStr(vb), MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Flags : $' + IntToHex(flags, 4), MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Compression : ' + IntToStr(comp), MSG_NOTIFY); - e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Modification Time : $' + IntToHex(mtime, 8), MSG_NOTIFY); + e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Modification Time : ' + DosToStr(mtime), MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': CRC-32 : $' + IntToHex(crc, 8), MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Compressed size : ' + IntToStr(csize), MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Decompressed size : ' + IntToStr(usize), MSG_NOTIFY); @@ -770,71 +853,121 @@ implementation e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': External Attrib : $' + IntToHex(eattr, 8), MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': LFH Offset : $' + IntToHex(offset, 8), MSG_NOTIFY); end; + cvtbug := False; + 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 + begin + // HACK: Editor and wadcvt for long time sets incorrent flag for UTF-8 + flags := ZIP_UTF8_MASK; + cvtbug := True; + end; + if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then + e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': WADCVT BUG : ' + BoolToStr(cvtbug, True), MSG_NOTIFY); if (va >= 10) and (va <= ZIP_MAXVERSION) then begin if (flags and ZIP_ENCRYPTION_MASK) = 0 then begin - if (csize <> $ffffffff) and (usize <> $ffffffff) and (disk <> $ffff) and (offset <> $ffffffff) then + if (flags and ZIP_PATCH_MASK) = 0 then begin - if disk = 0 then + if (csize <> $ffffffff) and (usize <> $ffffffff) and (disk <> $ffff) and (offset <> $ffffffff) then begin - if (next <= s.Size) and (fnlen > 0) then + if disk = 0 then begin - case comp of - ZIP_COMP_STORE: - if csize <> usize then - raise Exception.Create('Compressed size ' + IntToStr(csize) + ' != Descompressed size ' + IntToStr(usize) + 'for STORE method (corrupted file?)'); - ZIP_COMP_SHRUNK, - ZIP_COMP_REDUCE1, - ZIP_COMP_REDUCE2, - ZIP_COMP_REDUCE3, - ZIP_COMP_REDUCE4, - ZIP_COMP_IMPLODE, - ZIP_COMP_DEFLATE, - ZIP_COMP_DEFLATE64, - ZIP_COMP_TERSE1, - ZIP_COMP_BZIP2, - ZIP_COMP_LZMA, - ZIP_COMP_CMPSC, - ZIP_COMP_TERSE2, - ZIP_COMP_LZ77, - ZIP_COMP_ZSTD1, - ZIP_COMP_ZSTD2, - ZIP_COMP_MP3, - ZIP_COMP_XZ, - ZIP_COMP_JPEG, - ZIP_COMP_WAVPACK, - ZIP_COMP_PPMD: - ; // ok - ZIP_COMP_AE: - raise Exception.Create('Encrypted archives not supported'); - otherwise - raise Exception.Create('Unknown compression method ' + IntToStr(comp)); - end; - // TODO: check bit 11 (UTF8 name and comment) - GetMem(name, UInt32(fnlen) + 1); - try - s.ReadBuffer(name[0], fnlen); - name[fnlen] := #0; + if (next <= s.Size) and (fnlen > 0) then + begin + case comp of + ZIP_COMP_STORE: + if csize <> usize then + raise Exception.Create('Compressed size ' + IntToStr(csize) + ' != Descompressed size ' + IntToStr(usize) + 'for STORE method (corrupted file?)'); + ZIP_COMP_SHRUNK, + ZIP_COMP_REDUCE1, + ZIP_COMP_REDUCE2, + ZIP_COMP_REDUCE3, + ZIP_COMP_REDUCE4, + ZIP_COMP_IMPLODE, + ZIP_COMP_DEFLATE, + ZIP_COMP_DEFLATE64, + ZIP_COMP_TERSE1, + ZIP_COMP_BZIP2, + ZIP_COMP_LZMA, + ZIP_COMP_CMPSC, + ZIP_COMP_TERSE2, + ZIP_COMP_LZ77, + ZIP_COMP_ZSTD1, + ZIP_COMP_ZSTD2, + ZIP_COMP_MP3, + ZIP_COMP_XZ, + ZIP_COMP_JPEG, + ZIP_COMP_WAVPACK, + ZIP_COMP_PPMD: + ; // ok + ZIP_COMP_AE: + raise Exception.Create('Encrypted archives not supported'); + otherwise + raise Exception.Create('Unknown compression method ' + IntToStr(comp)); + end; + + // Read Name + GetMem(tmp, UInt32(fnlen) + 1); + try + s.ReadBuffer(tmp[0], fnlen); + tmp[fnlen] := #0; + name := tmp; + finally + FreeMem(tmp); + end; + // Skip ZIP extensions + s.Seek(extlen, TSeekOrigin.soCurrent); + // Read Comment + comment := ''; + if comlen > 0 then + begin + GetMem(tmp, UInt32(comlen) + 1); + try + s.ReadBuffer(tmp[0], comlen); + tmp[comlen] := #0; + comment := tmp; + finally + FreeMem(tmp); + end; + end; + + utf8 := True; + if (utf8 = False) or (flags and ZIP_UTF8_MASK = 0) and (IsUTF8(name) = False) then + begin + name := win2utf(name); + utf8 := False; + end; + if (utf8 = False) or (flags and ZIP_UTF8_MASK = 0) and (IsUTF8(comment) = False) then + begin + comment := win2utf(comment); + utf8 := False; + end; if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then + begin + e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': UTF-8 Comatible : ' + BoolToStr(utf8, True), MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Name : "' + name + '"', MSG_NOTIFY); + e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Comment : "' + comment + '"', MSG_NOTIFY); + end; s.Seek(offset, TSeekOrigin.soBeginning); - ReadLFH(s, name, csize, usize, comp, crc); - finally + ReadLFH(s, name, comment, csize, usize, comp, crc, mtime, flags); s.Seek(next, TSeekOrigin.soBeginning); - FreeMem(name); - end; + end + else + raise Exception.Create('Empty files names not supported'); end else - raise Exception.Create('Empty files names not supported'); + raise Exception.Create('Splitted archives not supported'); end else - raise Exception.Create('Splitted archives not supported'); + begin + FLastError := DFWAD_ERROR_WRONGVERSION; + raise Exception.Create('ZIP64 archives not supported'); + end; end else begin - FLastError := DFWAD_ERROR_WRONGVERSION; - raise Exception.Create('ZIP64 not supported'); + FLastError := DFWAD_ERROR_READWAD; + raise Exception.Create('Patch archives not supported'); end; end else @@ -879,6 +1012,8 @@ implementation var idisk, ndisk, nrec, total, comlen: UInt16; var csize, cpos, i: UInt32; var mypos: UInt64; + var tmp: PChar; + var utf8: Boolean; begin FLastError := DFWAD_ERROR_FILENOTWAD; FVersion := 0; @@ -918,6 +1053,28 @@ implementation begin if total > 0 then begin + utf8 := True; + if comlen > 0 then + begin + GetMem(tmp, UInt32(comlen) + 1); + try + s.ReadBuffer(tmp[0], comlen); + tmp[comlen] := #0; + FComment := tmp; + if IsUTF8(FComment) = False then + begin + FComment := win2utf(FComment); + utf8 := False; + end; + finally + FreeMem(tmp); + end; + end; + if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then + begin + e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': UTF8 Comaptible : ' + BoolToStr(utf8, True), MSG_NOTIFY); + e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Comment : "' + FComment + '"', MSG_NOTIFY); + end; i := 0; s.Seek(cpos, TSeekOrigin.soBeginning); while i < nrec do @@ -1020,6 +1177,8 @@ implementation procedure TZIPEditor.RemoveResource(Section, Resource: String); var p: PSection; i: Integer; begin + Section := win2utf(Section); + Resource := win2utf(Resource); p := FindSection(Section); i := FindResourceID(p, Resource); if i >= 0 then @@ -1034,93 +1193,137 @@ implementation end; end; - procedure TZIPEditor.WriteLFH(s: TStream; comp, crc, csize, usize: UInt32; const afname: AnsiString); - var fname: PChar; fnlen: UInt16; mypos: UInt64; + function GetZIPVersion(const afname: AnsiString; flags, comp: UInt16): UInt8; + var version: UInt8; + begin + version := 10; // Base version + case comp of + ZIP_COMP_STORE: version := 10; + ZIP_COMP_SHRUNK: version := 10; + ZIP_COMP_REDUCE1: version := 10; + ZIP_COMP_REDUCE2: version := 10; + ZIP_COMP_REDUCE3: version := 10; + ZIP_COMP_REDUCE4: version := 10; + ZIP_COMP_IMPLODE: version := 10; + ZIP_COMP_TOKENIZED: version := 20; + ZIP_COMP_DEFLATE: version := 20; + ZIP_COMP_DEFLATE64: version := 21; + ZIP_COMP_TERSE1: version := 25; // PKWARE DCL Implode + ZIP_COMP_BZIP2: version := 46; + ZIP_COMP_LZMA: version := 63; + ZIP_COMP_CMPSC: version := 63; + ZIP_COMP_TERSE2: version := 63; + ZIP_COMP_LZ77: version := 63; + ZIP_COMP_ZSTD1: version := 63; + ZIP_COMP_ZSTD2: version := 63; + ZIP_COMP_MP3: version := 63; + ZIP_COMP_XZ: version := 63; + ZIP_COMP_JPEG: version := 63; + ZIP_COMP_WAVPACK: version := 63; + ZIP_COMP_PPMD: version := 63; + ZIP_COMP_AE: version := 63; + end; + if afname[Length(afname)] = '/' then + version := Max(20, version); // Folder + if flags and ZIP_UTF8_MASK <> 0 then + version := Max(63, version); // UTF-8 name + Result := version; + end; + + procedure TZIPEditor.WriteLFH(s: TStream; flags, comp, mtime, crc, csize, usize: UInt32; const name: AnsiString); + var version: UInt8; fnlen: UInt16; mypos: UInt64; begin mypos := s.Position; - fname := PChar(afname); - fnlen := Length(fname); + fnlen := Length(name); + if IsASCII(name) = False then + flags := flags or ZIP_UTF8_MASK; + version := GetZIPVersion(name, flags, comp); if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then begin e_WriteLog('==============================================', MSG_NOTIFY); - e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Min Version : ' + IntToStr(ZIP_VERSION), MSG_NOTIFY); + e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Min Version : ' + IntToStr(version), MSG_NOTIFY); e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Min System : ' + IntToStr(ZIP_SYSTEM), MSG_NOTIFY); - e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Flags : $' + IntToHex(0, 4), MSG_NOTIFY); + e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Flags : $' + IntToHex(flags, 4), MSG_NOTIFY); e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Compression : ' + IntToStr(comp), MSG_NOTIFY); - e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Modification Time : $' + IntToHex(0, 8), MSG_NOTIFY); + e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Modification Time : ' + DosToStr(mtime), MSG_NOTIFY); e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': CRC-32 : $' + IntToHex(crc, 8), MSG_NOTIFY); e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Compressed size : ' + IntToStr(csize), MSG_NOTIFY); e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Decompressed size : ' + IntToStr(usize), MSG_NOTIFY); e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Name Length : ' + IntToStr(fnlen), MSG_NOTIFY); e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Extension Length : ' + IntToStr(0), MSG_NOTIFY); - e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Name : "' + fname + '"', MSG_NOTIFY); + e_WriteLog('LFH @' + IntToHex(mypos, 8) + ': Name : "' + name + '"', MSG_NOTIFY); end; s.WriteBuffer(ZIP_SIGN_LFH, 4); // LFH Signature - s.WriteByte(ZIP_VERSION); // Min version + s.WriteByte(version); // Min version s.WriteByte(ZIP_SYSTEM); // System - WriteInt(s, UInt16(0)); // Flags + WriteInt(s, UInt16(flags)); // Flags WriteInt(s, UInt16(comp)); // Compression method - WriteInt(s, UInt32(0)); // Modification time/date + WriteInt(s, UInt32(mtime)); // Modification time/date WriteInt(s, UInt32(crc)); // CRC-32 WriteInt(s, UInt32(csize)); // Compressed size WriteInt(s, UInt32(usize)); // Decompressed size WriteInt(s, UInt16(fnlen)); // Name field length WriteInt(s, UInt16(0)); // Extra field length - s.WriteBuffer(fname[0], fnlen); // File Name + s.WriteBuffer(name[1], fnlen); // File Name end; - procedure TZIPEditor.WriteCDR(s: TStream; comp, crc, csize, usize, eattr, offset: UInt32; const afname: AnsiString; cdrid: Integer); - var fname: PChar; fnlen: UInt16; mypos: UInt64; + procedure TZIPEditor.WriteCDR(s: TStream; flags, comp, mtime, crc, csize, usize, eattr, offset: UInt32; const name, com: AnsiString; cdrid: Integer); + var version: UInt8; fnlen, fclen: UInt16; mypos: UInt64; begin mypos := s.Position; - fname := PChar(afname); - fnlen := Length(fname); + fnlen := Length(name); + fclen := Length(com); + if (IsASCII(name) = False) or (IsASCII(com) = False) then + flags := flags or ZIP_UTF8_MASK; + version := GetZIPVersion(name, flags, comp); if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then begin e_WriteLog('==============================================', MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Writer Version : ' + IntToStr(ZIP_MAXVERSION), MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Writer System : ' + IntToStr(ZIP_SYSTEM), MSG_NOTIFY); - e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Min Version : ' + IntToStr(ZIP_VERSION), MSG_NOTIFY); + e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Min Version : ' + IntToStr(version), MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Min System : ' + IntToStr(ZIP_SYSTEM), MSG_NOTIFY); - e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Flags : $' + IntToHex(0, 4), MSG_NOTIFY); + e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Flags : $' + IntToHex(flags, 4), MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Compression : ' + IntToStr(comp), MSG_NOTIFY); - e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Modification Time : $' + IntToHex(0, 8), MSG_NOTIFY); + e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Modification Time : ' + DosToStr(mtime), MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': CRC-32 : $' + IntToHex(crc, 8), MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Compressed size : ' + IntToStr(csize), MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Decompressed size : ' + IntToStr(usize), MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Name Length : ' + IntToStr(fnlen), MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Extension Length : ' + IntToStr(0), MSG_NOTIFY); - e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Comment Length : ' + IntToStr(0), MSG_NOTIFY); + e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Comment Length : ' + IntToStr(fclen), MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Disk : ' + IntToStr(0), MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Internal Attrib : $' + IntToHex(0, 4), MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': External Attrib : $' + IntToHex(eattr, 8), MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': LFH Offset : $' + IntToHex(offset, 8), MSG_NOTIFY); - e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Name : "' + fname + '"', MSG_NOTIFY); + e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Name : "' + name + '"', MSG_NOTIFY); + e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Comment : "' + com + '"', MSG_NOTIFY); end; s.WriteBuffer(ZIP_SIGN_CDR, 4); // CDR Signature s.WriteByte(ZIP_MAXVERSION); // Used version s.WriteByte(ZIP_SYSTEM); // Used system - s.WriteByte(ZIP_VERSION); // Min version + s.WriteByte(version); // Min version s.WriteByte(ZIP_SYSTEM); // Min system - WriteInt(s, UInt16(0)); // Flags + WriteInt(s, UInt16(flags)); // Flags WriteInt(s, UInt16(comp)); // Compression method - WriteInt(s, UInt32(0)); // Modification time/date + WriteInt(s, UInt32(mtime)); // Modification time/date WriteInt(s, UInt32(crc)); // CRC-32 WriteInt(s, UInt32(csize)); // Compressed size WriteInt(s, UInt32(usize)); // Decompressed size WriteInt(s, UInt16(fnlen)); // Name field length WriteInt(s, UInt16(0)); // Extra field length - WriteInt(s, UInt16(0)); // Comment field length + WriteInt(s, UInt16(fclen)); // Comment field length WriteInt(s, UInt16(0)); // Disk WriteInt(s, UInt16(0)); // Internal attributes WriteInt(s, UInt32(eattr)); // External attributes WriteInt(s, UInt32(offset)); // LFH offset - s.WriteBuffer(fname[0], fnlen); // File Name + s.WriteBuffer(name[1], fnlen); // File Name + s.WriteBuffer(com[1], fclen); // Comment end; procedure TZIPEditor.SaveToStream(s: TStream); var i, j: Integer; - var start, offset, loffset, size, zcrc, count: UInt32; + var start, offset, loffset, size, zcrc, count, comlen: UInt32; var p: PResource; var afname: AnsiString; var mypos: UInt64; @@ -1138,7 +1341,7 @@ implementation begin p := @FSection[i].list[j]; afname := GetFileName(FSection[i].name, p.name); - WriteLFH(s, p.comp, p.chksum, p.csize, p.usize, afname); + WriteLFH(s, p.flags, p.comp, p.mtime, p.chksum, p.csize, p.usize, afname); if p.stream <> nil then begin Assert(p.stream.Size = p.csize); @@ -1158,7 +1361,7 @@ implementation else begin afname := GetFileName(FSection[i].name, ''); - WriteLFH(s, ZIP_COMP_STORE, zcrc, 0, 0, afname); + WriteLFH(s, 0, ZIP_COMP_STORE, FSection[i].mtime, zcrc, 0, 0, afname); end; end; end; @@ -1176,7 +1379,7 @@ implementation begin p := @FSection[i].list[j]; afname := GetFileName(FSection[i].name, p.name); - WriteCDR(s, p.comp, p.chksum, p.csize, p.usize, $00, loffset, afname, i); + WriteCDR(s, p.flags, p.comp, p.mtime, p.chksum, p.csize, p.usize, $00, loffset, afname, p.comment, i); loffset := loffset + 30 + Length(afname) + p.csize; Inc(count); end; @@ -1184,7 +1387,7 @@ implementation else begin afname := GetFileName(FSection[i].name, ''); - WriteCDR(s, ZIP_COMP_STORE, zcrc, 0, 0, $10, loffset, afname, i); + WriteCDR(s, 0, ZIP_COMP_STORE, FSection[i].mtime, zcrc, 0, 0, $10, loffset, afname, FSection[i].comment, i); loffset := loffset + 30 + Length(afname) + 0; Inc(count); end; @@ -1195,6 +1398,7 @@ implementation size := s.Position - start - offset; // Write EOCD header mypos := s.Position; + comlen := Length(FComment); if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then begin e_WriteLog('==============================================', MSG_NOTIFY); @@ -1204,7 +1408,8 @@ implementation e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Total CDR''s : ' + IntToStr(count), MSG_NOTIFY); e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': CD Length : ' + IntToStr(size), MSG_NOTIFY); e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': CD Offset : $' + IntToHex(offset, 8), MSG_NOTIFY); - e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Comment Length : ' + IntToStr(0), MSG_NOTIFY); + e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Comment Length : ' + IntToStr(comlen), MSG_NOTIFY); + e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Comment : "' + FComment + '"', MSG_NOTIFY); e_WriteLog('==============================================', MSG_NOTIFY); end; s.WriteBuffer(ZIP_SIGN_EOCD, 4); // EOCD Signature @@ -1214,7 +1419,8 @@ implementation WriteInt(s, UInt16(count)); // Total CDR entries WriteInt(s, UInt32(size)); // Central Directory size WriteInt(s, UInt32(offset)); // Central Directory offset - WriteInt(s, UInt16(0)); // Comment field length + WriteInt(s, UInt16(comlen)); // Comment field length + s.WriteBuffer(FComment[1], comlen); // Comment end; procedure TZIPEditor.SaveTo(FileName: String);