DEADSOFTWARE

more sfs refactoring
[d2df-sdl.git] / src / sfs / sfsZipFS.pas
index 5b6dafda3ee2e07b5a489e143f9f34798db725d5..892dd636802cbf3e3e3edbb605a6401468064ce8 100644 (file)
@@ -9,8 +9,9 @@
 //   dfwad   : D2D:F wad archives
 //
 {.$DEFINE SFS_DEBUG_ZIPFS}
-{$MODE DELPHI}
-{.$R-}
+{.$DEFINE SFS_ZIPFS_FULL}
+{$MODE OBJFPC}
+{$R+}
 unit sfsZipFS;
 
 interface
@@ -19,20 +20,30 @@ uses
   SysUtils, Classes, Contnrs, sfs;
 
 
-
 type
-  TSFSZipVolumeType = (sfszvNone, sfszvZIP, sfszvF2DAT, sfszvVTDB, sfszvDFWAD);
+  TSFSZipVolumeType = (
+    sfszvNone,
+    sfszvZIP,
+    {$IFDEF SFS_ZIPFS_FULL}
+    sfszvF2DAT,
+    sfszvVTDB,
+    {$ENDIF}
+    sfszvDFWAD
+  );
 
   TSFSZipVolume = class(TSFSVolume)
   protected
     fType: TSFSZipVolumeType;
 
     procedure ZIPReadDirectory ();
+    procedure DFWADReadDirectory ();
+    {$IFDEF SFS_ZIPFS_FULL}
     procedure F2DATReadDirectory ();
     procedure VTDBReadDirectory ();
-    procedure DFWADReadDirectory ();
+    {$ENDIF}
 
     procedure ReadDirectory (); override;
+    procedure removeCommonPath (); override;
 
   public
     function OpenFileByIndex (const index: Integer): TStream; override;
@@ -40,8 +51,8 @@ type
 
   TSFSZipVolumeFactory = class (TSFSVolumeFactory)
   public
-    function IsMyVolumePrefix (const prefix: TSFSString): Boolean; override;
-    function Produce (const prefix, fileName: TSFSString; st: TStream): TSFSVolume; override;
+    function IsMyVolumePrefix (const prefix: AnsiString): Boolean; override;
+    function Produce (const prefix, fileName: AnsiString; st: TStream): TSFSVolume; override;
     procedure Recycle (vol: TSFSVolume); override;
   end;
 
@@ -50,7 +61,7 @@ type
 implementation
 
 uses
-  zstream, xstreams;
+  zstream, xstreams, utils;
 
 
 type
@@ -88,6 +99,7 @@ begin
   result := true;
 end;
 
+{$IFDEF SFS_ZIPFS_FULL}
 function F2DATCheckMagic (st: TStream): Boolean;
 var
   dsize, fiSz: Integer;
@@ -114,6 +126,7 @@ begin
   if (fcnt < 0) or (dofs < 32) or (dofs+fcnt*8 > st.Size) then exit;
   result := true;
 end;
+{$ENDIF}
 
 function DFWADCheckMagic (st: TStream): Boolean;
 var
@@ -133,6 +146,63 @@ begin
   result := true;
 end;
 
+function maxPrefix (s0: string; s1: string): Integer;
+var
+  f: Integer;
+begin
+  for f := 1 to length(s0) do
+  begin
+    if f > length(s1) then begin result := f; exit; end;
+    if UpCase1251(s0[f]) <> UpCase1251(s1[f]) then begin result := f; exit; end;
+  end;
+  result := length(s0);
+end;
+
+procedure TSFSZipVolume.removeCommonPath ();
+var
+  f, pl, maxsc, sc, c: integer;
+  cp, s: string;
+  fi: TSFSZipFileInfo;
+begin
+  if fType <> sfszvZIP then exit;
+  maxsc := 0;
+  if fFiles.Count = 0 then exit;
+  cp := '';
+  for f := 0 to fFiles.Count-1 do
+  begin
+    fi := TSFSZipFileInfo(fFiles[f]);
+    s := fi.fPath;
+    if length(s) > 0 then begin cp := s; break; end;
+  end;
+  if length(cp) = 0 then exit;
+  for f := 0 to fFiles.Count-1 do
+  begin
+    fi := TSFSZipFileInfo(fFiles[f]);
+    s := fi.fPath;
+    if length(s) = 0 then continue;
+    pl := maxPrefix(cp, s);
+    //writeln('s=[', s, ']; cp=[', cp, ']; pl=', pl);
+    if pl = 0 then exit; // no common prefix at all
+    cp := Copy(cp, 1, pl);
+    sc := 0;
+    for c := 1 to length(s) do if s[c] = '/' then Inc(sc);
+    if sc > maxsc then maxsc := sc;
+  end;
+  if maxsc < 2 then exit; // alas
+  while (length(cp) > 0) and (cp[length(cp)] <> '/') do cp := Copy(cp, 1, length(cp)-1);
+  if length(cp) < 2 then exit; // nothing to do
+  for f := 0 to fFiles.Count-1 do
+  begin
+    fi := TSFSZipFileInfo(fFiles[f]);
+    if length(fi.fPath) >= length(cp) then
+    begin
+      s := fi.fPath;
+      fi.fPath := Copy(fi.fPath, length(cp)+1, length(fi.fPath));
+      //writeln('FIXED [', s, '] -> [', fi.fPath, ']');
+    end;
+  end;
+end;
+
 
 { TSFSZipVolume }
 procedure TSFSZipVolume.ZIPReadDirectory ();
@@ -145,6 +215,9 @@ var
   crc, psz, usz: LongWord;
   buf: packed array of Byte;
   bufPos, bufUsed: Integer;
+  efid, efsz: Word;
+  izver: Byte;
+  izcrc: LongWord;
 begin
   SetLength(buf, 0);
   // read local directory
@@ -153,17 +226,61 @@ begin
 
     if sign <> 'PK'#3#4 then break;
 
-    ignoreFile := false; skipped := false;
+    ignoreFile := false;
+    skipped := false;
+
     fi := TSFSZipFileInfo.Create(self);
     fi.fPackSz := 0;
     fi.fMethod := 0;
 
+    //fi.fOfs := fFileStream.Position;
+
     fFileStream.ReadBuffer(lhdr, SizeOf(lhdr));
     if lhdr.fnameSz > 255 then name[0] := #255 else name[0] := chr(lhdr.fnameSz);
     fFileStream.ReadBuffer(name[1], Length(name));
     fFileStream.Seek(lhdr.fnameSz-Length(name), soCurrent); // rest of the name (if any)
-    fi.fName := name;
-    fFileStream.Seek(lhdr.localExtraSz, soCurrent);
+    fi.fName := utf8to1251(name);
+    //writeln(Format('0x%08x : %s', [Integer(fi.fOfs), name]));
+
+    // here we should process extra field: it may contain utf8 filename
+    //fFileStream.Seek(lhdr.localExtraSz, soCurrent);
+    while lhdr.localExtraSz >= 4 do
+    begin
+      efid := 0;
+      efsz := 0;
+      fFileStream.ReadBuffer(efid, 2);
+      fFileStream.ReadBuffer(efsz, 2);
+      Dec(lhdr.localExtraSz, 4);
+      if efsz > lhdr.localExtraSz then break;
+      // Info-ZIP Unicode Path Extra Field?
+      if (efid = $7075) and (efsz <= 255+5) and (efsz > 5) then
+      begin
+        fFileStream.ReadBuffer(izver, 1);
+        if izver <> 1 then
+        begin
+          // skip it
+          Dec(efsz, 1);
+        end
+        else
+        begin
+          Dec(lhdr.localExtraSz, efsz);
+          fFileStream.ReadBuffer(izcrc, 4); // name crc, ignore it
+          Dec(efsz, 5);
+          name[0] := chr(efsz);
+          fFileStream.ReadBuffer(name[1], Length(name));
+          fi.fName := utf8to1251(name);
+          break;
+        end;
+      end;
+      // skip it
+      if efsz > 0 then
+      begin
+        fFileStream.Seek(efsz, soCurrent);
+        Dec(lhdr.localExtraSz, efsz);
+      end;
+    end;
+    // skip the rest
+    if lhdr.localExtraSz > 0 then fFileStream.Seek(lhdr.localExtraSz, soCurrent);
 
     if (lhdr.flags and 1) <> 0 then
     begin
@@ -212,8 +329,7 @@ begin
           begin
             bufPos := 0;
             // int64!
-            if fFileStream.Size-fFileStream.Position > Length(buf) then
-              bufUsed := Length(buf)
+            if fFileStream.Size-fFileStream.Position > Length(buf) then bufUsed := Length(buf)
             else bufUsed := fFileStream.Size-fFileStream.Position;
             if bufUsed = 0 then raise ESFSError.Create('invalid ZIP file');
             fFileStream.ReadBuffer(buf[0], bufUsed);
@@ -260,6 +376,7 @@ begin
   end;
 end;
 
+{$IFDEF SFS_ZIPFS_FULL}
 procedure TSFSZipVolume.F2DATReadDirectory ();
 var
   dsize: Integer;
@@ -333,6 +450,7 @@ begin
     fi.fMethod := 255;
   end;
 end;
+{$ENDIF}
 
 procedure TSFSZipVolume.DFWADReadDirectory ();
 // idiotic format
@@ -386,8 +504,10 @@ procedure TSFSZipVolume.ReadDirectory ();
 begin
   case fType of
     sfszvZIP: ZIPReadDirectory();
+    {$IFDEF SFS_ZIPFS_FULL}
     sfszvF2DAT: F2DATReadDirectory();
     sfszvVTDB: VTDBReadDirectory();
+    {$ENDIF}
     sfszvDFWAD: DFWADReadDirectory();
     else raise ESFSError.Create('invalid zipped SFS');
   end;
@@ -396,7 +516,7 @@ end;
 function TSFSZipVolume.OpenFileByIndex (const index: Integer): TStream;
 var
   zs: TZDecompressionStream;
-  fs: TStream;
+  fs, rs: TStream;
   gs: TSFSGuardStream;
   kill: Boolean;
   buf: packed array [0..1023] of Char;
@@ -406,6 +526,7 @@ begin
   zs := nil;
   fs := nil;
   gs := nil;
+  rs := nil;
   if fFiles = nil then exit;
   if (index < 0) or (index >= fFiles.Count) or (fFiles[index] = nil) then exit;
   kill := false;
@@ -430,37 +551,41 @@ begin
       fs.Seek(TSFSZipFileInfo(fFiles[index]).fOfs, soBeginning);
       if TSFSZipFileInfo(fFiles[index]).fMethod = 255 then
       begin
-        zs := TZDecompressionStream.Create(fs)
+        // sorry, pals, DFWAD is completely broken, so users of it should SUFFER
+        if TSFSZipFileInfo(fFiles[index]).fSize = -1 then
+        begin
+          TSFSZipFileInfo(fFiles[index]).fSize := 0;
+          //writeln('trying to determine file size for [', TSFSZipFileInfo(fFiles[index]).fPath, TSFSZipFileInfo(fFiles[index]).fName, ']');
+          zs := TZDecompressionStream.Create(fs);
+          try
+            while true do
+            begin
+              rd := zs.read(buf, 1024);
+              //writeln('  got ', rd, ' bytes');
+              if rd > 0 then Inc(TSFSZipFileInfo(fFiles[index]).fSize, rd);
+              if rd < 1024 then break;
+            end;
+            //writeln('  resulting size: ', TSFSZipFileInfo(fFiles[index]).fSize, ' bytes');
+            // recreate stream
+            FreeAndNil(zs);
+            fs.Seek(TSFSZipFileInfo(fFiles[index]).fOfs, soBeginning);
+          except
+            //writeln('*** CAN''T determine file size for [', TSFSZipFileInfo(fFiles[index]).fPath, TSFSZipFileInfo(fFiles[index]).fName, ']');
+            FreeAndNil(zs);
+            if kill then FreeAndNil(fs);
+            result := nil;
+            exit;
+          end;
+        end;
+        rs := TSFSPartialStream.Create(fs, TSFSZipFileInfo(fFiles[index]).fOfs, TSFSZipFileInfo(fFiles[index]).fPackSz, true);
+        zs := TZDecompressionStream.Create(rs);
+        rs := nil;
       end
       else
       begin
-        zs := TZDecompressionStream.Create(fs, true {-15}{MAX_WBITS});
-      end;
-      // sorry, pals, DFWAD is completely broken, so users of it should SUFFER
-      if TSFSZipFileInfo(fFiles[index]).fSize = -1 then
-      begin
-        TSFSZipFileInfo(fFiles[index]).fSize := 0;
-        //writeln('trying to determine file size for [', TSFSZipFileInfo(fFiles[index]).fPath, TSFSZipFileInfo(fFiles[index]).fName, ']');
-        try
-          while true do
-          begin
-            rd := zs.read(buf, 1024);
-            //writeln('  got ', rd, ' bytes');
-            if rd > 0 then Inc(TSFSZipFileInfo(fFiles[index]).fSize, rd);
-            if rd < 1024 then break;
-          end;
-          //writeln('  resulting size: ', TSFSZipFileInfo(fFiles[index]).fSize, ' bytes');
-          // recreate stream
-          FreeAndNil(zs);
-          fs.Seek(TSFSZipFileInfo(fFiles[index]).fOfs, soBeginning);
-          zs := TZDecompressionStream.Create(fs)
-        except
-          //writeln('*** CAN''T determine file size for [', TSFSZipFileInfo(fFiles[index]).fPath, TSFSZipFileInfo(fFiles[index]).fName, ']');
-          FreeAndNil(zs);
-          if kill then FreeAndNil(fs);
-          result := nil;
-          exit;
-        end;
+        rs := TSFSPartialStream.Create(fs, TSFSZipFileInfo(fFiles[index]).fOfs, TSFSZipFileInfo(fFiles[index]).fPackSz, true);
+        zs := TZDecompressionStream.Create(rs, true {-15}{MAX_WBITS});
+        rs := nil;
       end;
       gs := TSFSGuardStream.Create(zs, fs, true, kill, false);
       zs := nil;
@@ -468,6 +593,7 @@ begin
       result := TSFSPartialStream.Create(gs, 0, TSFSZipFileInfo(fFiles[index]).fSize, true);
     end;
   except
+    FreeAndNil(rs);
     FreeAndNil(gs);
     FreeAndNil(zs);
     if kill then FreeAndNil(fs);
@@ -478,15 +604,18 @@ end;
 
 
 { TSFSZipVolumeFactory }
-function TSFSZipVolumeFactory.IsMyVolumePrefix (const prefix: TSFSString): Boolean;
+function TSFSZipVolumeFactory.IsMyVolumePrefix (const prefix: AnsiString): Boolean;
 begin
   result :=
-    SFSStrEqu(prefix, 'zip') or
-    SFSStrEqu(prefix, 'jar') or
-    SFSStrEqu(prefix, 'fout2') or
-    SFSStrEqu(prefix, 'vtdb') or
-    SFSStrEqu(prefix, 'wad') or
-    SFSStrEqu(prefix, 'dfwad');
+    StrEquCI1251(prefix, 'zip') or
+    StrEquCI1251(prefix, 'dfwad')
+    {$IFDEF SFS_ZIPFS_FULL}
+    or StrEquCI1251(prefix, 'jar') or
+    StrEquCI1251(prefix, 'fout2') or
+    StrEquCI1251(prefix, 'vtdb') or
+    StrEquCI1251(prefix, 'wad')
+    {$ENDIF}
+    ;
 end;
 
 procedure TSFSZipVolumeFactory.Recycle (vol: TSFSVolume);
@@ -494,15 +623,18 @@ begin
   vol.Free();
 end;
 
-function TSFSZipVolumeFactory.Produce (const prefix, fileName: TSFSString; st: TStream): TSFSVolume;
+function TSFSZipVolumeFactory.Produce (const prefix, fileName: AnsiString; st: TStream): TSFSVolume;
 var
   vt: TSFSZipVolumeType;
 begin
   vt := sfszvNone;
-  if ZIPCheckMagic(st) then vt := sfszvZIP
+       if ZIPCheckMagic(st) then vt := sfszvZIP
   else if DFWADCheckMagic(st) then vt := sfszvDFWAD
+  {$IFDEF SFS_ZIPFS_FULL}
   else if F2DATCheckMagic(st) then vt := sfszvF2DAT
-  else if VTDBCheckMagic(st) then vt := sfszvVTDB;
+  else if VTDBCheckMagic(st) then vt := sfszvVTDB
+  {$ENDIF}
+  ;
 
   if vt <> sfszvNone then
   begin
@@ -530,6 +662,6 @@ var
 initialization
   zipf := TSFSZipVolumeFactory.Create();
   SFSRegisterVolumeFactory(zipf);
-finalization
-  SFSUnregisterVolumeFactory(zipf);
+//finalization
+//  SFSUnregisterVolumeFactory(zipf);
 end.