DEADSOFTWARE

dfzip: fix encoding in interface
[d2df-editor.git] / src / shared / WADEDITOR_dfzip.pas
index 1d319af191172c33d437120e2721cb470044225b..2b6960015305fb242653e590534aa1ff3b0cc28f 100644 (file)
@@ -26,11 +26,13 @@ interface
       usize: UInt32;
       comp: UInt32;
       chksum: UInt32;
+      mtime: UInt32;
       stream: TMemoryStream;
     end;
 
     TSection = record
       name: AnsiString;
+      mtime: UInt32;
       list: array of TResource;
     end;
 
@@ -46,23 +48,23 @@ interface
 
         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): PSection;
 
         function FindSectionID(name: AnsiString): Integer;
         function FindSection(name: AnsiString): PSection;
-        function InsertSection(name: AnsiString): PSection;
+        function InsertSection(name: AnsiString; mtime: UInt32): 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: UInt32): 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: AnsiString; xcsize, xusize, xcomp, xcrc, xtime: 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; comp, mtime, crc, csize, usize: UInt32; const afname: AnsiString);
+        procedure WriteCDR(s: TStream; comp, mtime, crc, csize, usize, eattr, offset: UInt32; const afname: AnsiString; cdrid: Integer);
         procedure SaveToStream(s: TStream);
 
       public
@@ -92,7 +94,7 @@ interface
 
 implementation
 
-  uses SysUtils, StrUtils, Math, utils, zstream, crc, e_log;
+  uses SysUtils, StrUtils, DateUtils, Math, utils, zstream, crc, e_log;
 
   const
     ZIP_SIGN_CDR  = 'PK'#1#2;
@@ -147,6 +149,45 @@ implementation
     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
@@ -256,13 +297,14 @@ implementation
       Result := nil;
   end;
 
-  function TZIPEditor.InsertSectionRAW(name: AnsiString): PSection;
+  function TZIPEditor.InsertSectionRAW(name: AnsiString; mtime: UInt32): 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;
     Result := @FSection[i];
   end;
 
@@ -286,21 +328,21 @@ implementation
       Result := FindSectionRAW(fixName, False); // CASENAME
   end;
 
-  function TZIPEditor.InsertSection(name: AnsiString): PSection;
+  function TZIPEditor.InsertSection(name: AnsiString; mtime: UInt32): PSection;
   begin
     Result := FindSection(name);
     if Result = nil then
-      Result := InsertSectionRAW(name);
+      Result := InsertSectionRAW(name, mtime);
   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: UInt32): 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);
@@ -310,6 +352,7 @@ implementation
     p.list[i].usize := usize;
     p.list[i].comp := comp;
     p.list[i].chksum := crc;
+    p.list[i].mtime := mtime;
     p.list[i].stream := nil;
     Result := @p.list[i];
   end;
@@ -329,6 +372,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
@@ -355,7 +400,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()));
         p.stream := s;
         Result := True;
       except
@@ -507,17 +552,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;
 
@@ -541,6 +590,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;
@@ -633,6 +684,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
@@ -640,7 +692,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;
@@ -654,12 +706,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: AnsiString; xcsize, xusize, xcomp, xcrc, xtime: UInt32);
     var sig: packed array [0..3] of Char;
     var va, vb, flags, comp: UInt16;
     var mtime, crc, csize, usize: UInt32;
@@ -691,7 +743,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);
@@ -708,11 +760,11 @@ implementation
             begin
               p := FindSectionRAW(section, True);
               if p = nil then
-                p := InsertSectionRAW(section)
+                p := InsertSectionRAW(section, xtime);
             end
             else
             begin
-              p := InsertFileInfo(section, name, datapos, xcsize, xusize, xcomp, xcrc);
+              p := InsertFileInfo(section, name, datapos, xcsize, xusize, xcomp, xcrc, xtime);
             end;
             if p = nil then
               raise Exception.Create('Failed to register resource [' + fname + ']');
@@ -742,6 +794,7 @@ implementation
     var mypos, next: UInt64;
     var name: PChar;
     var aname: AnsiString;
+    var cvtbug, utf8: Boolean;
   begin
     mypos := s.Position;
     s.ReadBuffer(sig[0], 4);
@@ -775,7 +828,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);
@@ -787,13 +840,15 @@ 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
-        if gWADEditorLogLevel >= DFWAD_LOG_DEBUG then
-          e_WriteLog('CDR#' + IntToStr(cdrid) + ' @' + IntToHex(mypos, 8) + ': WADCVT BUG        : YES', MSG_NOTIFY);
         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
@@ -840,19 +895,19 @@ implementation
                   s.ReadBuffer(name[0], fnlen);
                   name[fnlen] := #0;
                   aname := name;
-                  if (flags and ZIP_UTF8_MASK = 0) and (IsASCII(name) = False) then
+                  utf8 := True;
+                  if (flags and ZIP_UTF8_MASK = 0) and (IsUTF8(name) = False) then
                   begin
-                    // TODO: Detect UTF-8
-                    //       Older or invalid packers does not set bit 11
-                    //       Some newer packers may write UTF-8 names into extended data
-                    if gWADEditorLogLevel >= DFWAD_LOG_WARN then
-                      e_WriteLog('ZIP: Non UTF-8 encoded name, threat it as CP1251', MSG_WARNING);
                     aname := win2utf(aname);
+                    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              : "' + aname + '"', MSG_NOTIFY);
+                  end;
                   s.Seek(offset, TSeekOrigin.soBeginning);
-                  ReadLFH(s, aname, csize, usize, comp, crc);
+                  ReadLFH(s, aname, csize, usize, comp, crc, mtime);
                 finally
                   s.Seek(next, TSeekOrigin.soBeginning);
                   FreeMem(name);
@@ -1053,6 +1108,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
@@ -1104,7 +1161,7 @@ implementation
     Result := version;
   end;
 
-  procedure TZIPEditor.WriteLFH(s: TStream; comp, crc, csize, usize: UInt32; const afname: AnsiString);
+  procedure TZIPEditor.WriteLFH(s: TStream; comp, mtime, crc, csize, usize: UInt32; const afname: AnsiString);
     var fname: PChar; version: UInt8; fnlen, flags: UInt16; mypos: UInt64;
   begin
     mypos := s.Position;
@@ -1121,7 +1178,7 @@ implementation
       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) + ': 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);
@@ -1134,7 +1191,7 @@ implementation
     s.WriteByte(ZIP_SYSTEM);        // System
     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
@@ -1143,7 +1200,7 @@ implementation
     s.WriteBuffer(fname[0], fnlen); // File Name
   end;
 
-  procedure TZIPEditor.WriteCDR(s: TStream; comp, crc, csize, usize, eattr, offset: UInt32; const afname: AnsiString; cdrid: Integer);
+  procedure TZIPEditor.WriteCDR(s: TStream; comp, mtime, 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;
@@ -1162,7 +1219,7 @@ implementation
       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) + ': 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);
@@ -1182,7 +1239,7 @@ implementation
     s.WriteByte(ZIP_SYSTEM);        // Min system
     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
@@ -1216,7 +1273,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.comp, p.mtime, p.chksum, p.csize, p.usize, afname);
             if p.stream <> nil then
             begin
               Assert(p.stream.Size = p.csize);
@@ -1236,7 +1293,7 @@ implementation
         else
         begin
           afname := GetFileName(FSection[i].name, '');
-          WriteLFH(s, ZIP_COMP_STORE, zcrc, 0, 0, afname);
+          WriteLFH(s, ZIP_COMP_STORE, FSection[i].mtime, zcrc, 0, 0, afname);
         end;
       end;
     end;
@@ -1254,7 +1311,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.comp, p.mtime, p.chksum, p.csize, p.usize, $00, loffset, afname, i);
             loffset := loffset + 30 + Length(afname) + p.csize;
             Inc(count);
           end;
@@ -1262,7 +1319,7 @@ implementation
         else
         begin
           afname := GetFileName(FSection[i].name, '');
-          WriteCDR(s, ZIP_COMP_STORE, zcrc, 0, 0, $10, loffset, afname, i);
+          WriteCDR(s, ZIP_COMP_STORE, FSection[i].mtime, zcrc, 0, 0, $10, loffset, afname, i);
           loffset := loffset + 30 + Length(afname) + 0;
           Inc(count);
         end;