X-Git-Url: https://deadsoftware.ru/gitweb?a=blobdiff_plain;f=src%2Fshared%2FWADEDITOR_dfzip.pas;h=f9e54ad57f88f597a6a56fdf8e0fac7b8b1f4136;hb=107de9653cd3649b79ee0a1bda085c37e3154a96;hp=ce2ad6fb270e76ac7fb0f9aadafcc28eb92ae8b9;hpb=579d2579e57d0248db7dfab8ea4265b5f8bb9b52;p=d2df-editor.git diff --git a/src/shared/WADEDITOR_dfzip.pas b/src/shared/WADEDITOR_dfzip.pas index ce2ad6f..f9e54ad 100644 --- a/src/shared/WADEDITOR_dfzip.pas +++ b/src/shared/WADEDITOR_dfzip.pas @@ -6,7 +6,7 @@ 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 @@ -62,7 +62,7 @@ interface procedure ReadEOCD(s: TStream); procedure WriteLFH(s: TStream; comp, crc, csize, usize: UInt32; const afname: AnsiString); - procedure WriteCDR(s: TStream; comp, crc, csize, usize, attr, offset: UInt32; const afname: AnsiString); + procedure WriteCDR(s: TStream; comp, crc, csize, usize, eattr, offset: UInt32; const afname: AnsiString; cdrid: Integer); procedure SaveToStream(s: TStream); public @@ -92,7 +92,7 @@ interface implementation - uses SysUtils, StrUtils, zstream, crc, e_log; + uses SysUtils, StrUtils, Math, utils, zstream, crc, e_log; const ZIP_SIGN_CDR = 'PK'#1#2; @@ -127,9 +127,56 @@ 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_UTF8_MASK = (1 << 11); + + 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; + procedure ToSectionFile(fname: AnsiString; out section, name: AnsiString); inline; var i: SizeInt; begin @@ -382,7 +429,7 @@ implementation FSection := nil; FStream := nil; FLastError := DFWAD_NOERROR; - FVersion := ZIP_VERSION; + FVersion := 10; FreeWAD(); end; @@ -418,7 +465,7 @@ implementation FreeAndNil(FStream); end; FLastError := DFWAD_NOERROR; - FVersion := ZIP_VERSION; + FVersion := 10; end; function TZIPEditor.Preload(p: PResource): Boolean; @@ -717,7 +764,6 @@ 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; @@ -725,6 +771,8 @@ implementation var eattr, offset: UInt32; var mypos, next: UInt64; var name: PChar; + var aname: AnsiString; + var cvtbug, utf8: Boolean; begin mypos := s.Position; s.ReadBuffer(sig[0], 4); @@ -767,9 +815,18 @@ implementation e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Comment Length : ' + IntToStr(comlen), MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Disk : ' + IntToStr(disk), MSG_NOTIFY); e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Internal Attrib : $' + IntToHex(iattr, 4), MSG_NOTIFY); - e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': External Attrib : $' + IntToHex(iattr, 8), 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); 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 @@ -811,15 +868,24 @@ implementation 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; + aname := name; + utf8 := True; + if (flags and ZIP_UTF8_MASK = 0) and (IsUTF8(name) = False) then + begin + aname := win2utf(aname); + utf8 := False; + end; if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then - e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': Name : "' + name + '"', MSG_NOTIFY); + 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 : "' + aname + '"', MSG_NOTIFY); + end; s.Seek(offset, TSeekOrigin.soBeginning); - ReadLFH(s, name, csize, usize, comp, crc); + ReadLFH(s, aname, csize, usize, comp, crc); finally s.Seek(next, TSeekOrigin.soBeginning); FreeMem(name); @@ -1034,49 +1100,133 @@ implementation end; end; + 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; comp, crc, csize, usize: UInt32; const afname: AnsiString); - var fname: PChar; flen: UInt16; + var fname: PChar; version: UInt8; fnlen, flags: UInt16; mypos: UInt64; begin + mypos := s.Position; fname := PChar(afname); - flen := Length(fname); + fnlen := Length(fname); + flags := 0; + if IsASCII(afname) = False then + flags := flags or ZIP_UTF8_MASK; + version := GetZIPVersion(afname, flags, comp); + if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then + begin + e_WriteLog('==============================================', 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(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) + ': 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); + 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 - s.WriteWord(NtoLE(0)); // Flags - s.WriteWord(NtoLE(comp)); // Compression method - s.WriteDWord(NtoLE(0)); // Modification time/date - s.WriteDWord(NtoLE(crc)); // CRC-32 - s.WriteDWord(NtoLE(csize)); // Compressed size - s.WriteDWord(NtoLE(usize)); // Decompressed size - s.WriteWord(NtoLE(flen)); // Name field length - s.WriteWord(NtoLE(0)); // Extra field length - s.WriteBuffer(fname[0], flen); // File Name + WriteInt(s, UInt16(flags)); // Flags + WriteInt(s, UInt16(comp)); // Compression method + WriteInt(s, UInt32(0)); // 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 end; - procedure TZIPEditor.WriteCDR(s: TStream; comp, crc, csize, usize, attr, offset: UInt32; const afname: AnsiString); - var fname: PChar; flen: UInt16; + procedure TZIPEditor.WriteCDR(s: TStream; comp, crc, csize, usize, eattr, offset: UInt32; const afname: AnsiString; cdrid: Integer); + var fname: PChar; version: UInt8; fnlen, flags: UInt16; mypos: UInt64; begin + mypos := s.Position; fname := PChar(afname); - flen := Length(fname); + fnlen := Length(fname); + flags := 0; + if IsASCII(afname) = False then + flags := flags or ZIP_UTF8_MASK; + version := GetZIPVersion(afname, 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(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(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) + ': 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) + ': 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); + 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 - s.WriteWord(NtoLE(0)); // Flags - s.WriteWord(NtoLE(comp)); // Compression method - s.WriteDWord(NtoLE(0)); // Modification time/date - s.WriteDWord(NtoLE(crc)); // CRC-32 - s.WriteDWord(NtoLE(csize)); // Compressed size - s.WriteDWord(NtoLE(usize)); // Decompressed size - s.WriteWord(NtoLE(flen)); // Name field length - s.WriteWord(NtoLE(0)); // Extra field length - s.WriteWord(NtoLE(0)); // Comment field length - s.WriteWord(NtoLE(0)); // Disk - s.WriteWord(NtoLE(0)); // Internal attributes - s.WriteDWord(NtoLE(attr)); // External attributes - s.WriteDWord(NtoLE(offset)); // LFH offset - s.WriteBuffer(fname[0], flen); // File Name + WriteInt(s, UInt16(flags)); // Flags + WriteInt(s, UInt16(comp)); // Compression method + WriteInt(s, UInt32(0)); // 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(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 end; procedure TZIPEditor.SaveToStream(s: TStream); @@ -1084,6 +1234,7 @@ implementation var start, offset, loffset, size, zcrc, count: UInt32; var p: PResource; var afname: AnsiString; + var mypos: UInt64; begin // Write LFH headers and data start := s.Position; @@ -1124,7 +1275,7 @@ implementation end; // Write CDR headers count := 0; - loffset := start; + loffset := 0; offset := s.Position - start; if FSection <> nil then begin @@ -1136,7 +1287,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, 0, loffset - start, afname); + WriteCDR(s, p.comp, p.chksum, p.csize, p.usize, $00, loffset, afname, i); loffset := loffset + 30 + Length(afname) + p.csize; Inc(count); end; @@ -1144,7 +1295,7 @@ implementation else begin afname := GetFileName(FSection[i].name, ''); - WriteCDR(s, ZIP_COMP_STORE, zcrc, 0, 0, $10, loffset - start, afname); + WriteCDR(s, ZIP_COMP_STORE, zcrc, 0, 0, $10, loffset, afname, i); loffset := loffset + 30 + Length(afname) + 0; Inc(count); end; @@ -1154,14 +1305,27 @@ implementation Assert(count < $ffff); size := s.Position - start - offset; // Write EOCD header + mypos := s.Position; + if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then + begin + e_WriteLog('==============================================', MSG_NOTIFY); + e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Disk ID : ' + IntToStr(0), MSG_NOTIFY); + e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Disk ID with CD : ' + IntToStr(0), MSG_NOTIFY); + e_WriteLog('EOCD @' + IntToHex(mypos, 8) + ': Available CDR''s : ' + IntToStr(count), MSG_NOTIFY); + 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('==============================================', MSG_NOTIFY); + end; s.WriteBuffer(ZIP_SIGN_EOCD, 4); // EOCD Signature - s.WriteWord(NtoLE(0)); // Disk - s.WriteWord(NtoLE(0)); // Num of Disks - s.WriteWord(NtoLE(count)); // Num of CDRs - s.WriteWord(NtoLE(count)); // Total CDR entries - s.WriteDWord(NtoLE(size)); // Central Directory size - s.WriteDWord(NtoLE(offset)); // Central Directory offset - s.WriteWord(NtoLE(0)); // Comment field length + WriteInt(s, UInt16(0)); // Disk + WriteInt(s, UInt16(0)); // Num of Disks + WriteInt(s, UInt16(count)); // Num of CDRs + 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 end; procedure TZIPEditor.SaveTo(FileName: String);