X-Git-Url: http://deadsoftware.ru/gitweb?a=blobdiff_plain;f=src%2Fsfs%2FsfsZipFS.pas;h=22235a866b02e95a86184cbcd1c0ac548278cc06;hb=ac201b02f10ef558087d50f6b03b4519ab567558;hp=9fb1137e0ca70d054282ddc68c6a01a190b67717;hpb=20428038ea09152f824e5947da1d550a4774207e;p=d2df-sdl.git diff --git a/src/sfs/sfsZipFS.pas b/src/sfs/sfsZipFS.pas index 9fb1137..22235a8 100644 --- a/src/sfs/sfsZipFS.pas +++ b/src/sfs/sfsZipFS.pas @@ -3,14 +3,12 @@ // See the file aplicense.txt for conditions of use. // // grouping files with packing: -// zip, jar: PKZIP-compatible archives (store, deflate) -// fout2 : Fallout II .DAT -// vtdb : Asphyre's VTDb +// zip, pk3: PKZIP-compatible archives (store, deflate) // dfwad : D2D:F wad archives // {.$DEFINE SFS_DEBUG_ZIPFS} {$MODE DELPHI} -{.$R-} +{$R+} unit sfsZipFS; interface @@ -19,17 +17,14 @@ uses SysUtils, Classes, Contnrs, sfs; - type - TSFSZipVolumeType = (sfszvNone, sfszvZIP, sfszvF2DAT, sfszvVTDB, sfszvDFWAD); + TSFSZipVolumeType = (sfszvNone, sfszvZIP, sfszvDFWAD); TSFSZipVolume = class(TSFSVolume) protected fType: TSFSZipVolumeType; procedure ZIPReadDirectory (); - procedure F2DATReadDirectory (); - procedure VTDBReadDirectory (); procedure DFWADReadDirectory (); procedure ReadDirectory (); override; @@ -39,29 +34,25 @@ type function OpenFileByIndex (const index: Integer): TStream; override; end; - TSFSZipVolumeFactory = class (TSFSVolumeFactory) + 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; - implementation uses - zstream, xstreams; + xstreams, utils; type - TZDecompressionStream = TDecompressionStream; - -type - TSFSZipFileInfo = class (TSFSFileInfo) + TSFSZipFileInfo = class(TSFSFileInfo) public fMethod: Byte; // 0: store; 8: deflate; 255: other - fPackSz: Int64; + fPackSz: Int64; // can be -1 end; TZLocalFileHeader = packed record @@ -89,32 +80,6 @@ begin result := true; end; -function F2DATCheckMagic (st: TStream): Boolean; -var - dsize, fiSz: Integer; -begin - result := false; - st.Position := st.Size-8; - st.ReadBuffer(dsize, 4); st.ReadBuffer(fiSz, 4); - st.Position := 0; - if (fiSz <> st.Size) or (dsize < 5+13) or (dsize > fiSz-4) then exit; - result := true; -end; - -function VTDBCheckMagic (st: TStream): Boolean; -var - sign: packed array [0..3] of Char; - fcnt, dofs: Integer; -begin - result := false; - if st.Size < 32 then exit; - st.ReadBuffer(sign[0], 4); - st.ReadBuffer(fcnt, 4); st.ReadBuffer(dofs, 4); - st.Seek(-12, soCurrent); - if sign <> 'vtdm' then exit; - if (fcnt < 0) or (dofs < 32) or (dofs+fcnt*8 > st.Size) then exit; - result := true; -end; function DFWADCheckMagic (st: TStream): Boolean; var @@ -134,6 +99,7 @@ begin result := true; end; + function maxPrefix (s0: string; s1: string): Integer; var f: Integer; @@ -141,11 +107,12 @@ begin for f := 1 to length(s0) do begin if f > length(s1) then begin result := f; exit; end; - if SFSUpCase(s0[f]) <> SFSUpCase(s1[f]) 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; @@ -191,6 +158,7 @@ begin end; end; + { TSFSZipVolume } procedure TSFSZipVolume.ZIPReadDirectory (); var @@ -202,6 +170,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 @@ -217,12 +188,54 @@ begin 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 @@ -271,8 +284,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); @@ -319,79 +331,6 @@ begin end; end; -procedure TSFSZipVolume.F2DATReadDirectory (); -var - dsize: Integer; - fi: TSFSZipFileInfo; - name: ShortString; - f: Integer; - b: Byte; -begin - fFileStream.Position := fFileStream.Size-8; - fFileStream.ReadBuffer(dsize, 4); - fFileStream.Seek(-dsize, soCurrent); Dec(dsize, 4); - while dsize > 0 do - begin - fi := TSFSZipFileInfo.Create(self); - fFileStream.ReadBuffer(f, 4); - if (f < 1) or (f > 255) then raise ESFSError.Create('invalid Fallout II .DAT file'); - Dec(dsize, 4+f+13); - if dsize < 0 then raise ESFSError.Create('invalid Fallout II .DAT file'); - name[0] := chr(f); if f > 0 then fFileStream.ReadBuffer(name[1], f); - f := 1; while (f <= ord(name[0])) and (name[f] <> #0) do Inc(f); name[0] := chr(f-1); - fi.fName := name; - fFileStream.ReadBuffer(b, 1); // packed? - if b = 0 then fi.fMethod := 0 else fi.fMethod := 255; - fFileStream.ReadBuffer(fi.fSize, 4); - fFileStream.ReadBuffer(fi.fPackSz, 4); - fFileStream.ReadBuffer(fi.fOfs, 4); - end; -end; - -procedure TSFSZipVolume.VTDBReadDirectory (); -// idiotic format -var - fcnt, dofs: Integer; - keys: array of record name: string; ofs: Integer; end; - fi: TSFSZipFileInfo; - f, c: Integer; - rtype: Word; -begin - fFileStream.Seek(4, soCurrent); // skip signature - fFileStream.ReadBuffer(fcnt, 4); - fFileStream.ReadBuffer(dofs, 4); - fFileStream.Seek(dofs, soBeginning); - - // read keys - SetLength(keys, fcnt); - for f := 0 to fcnt-1 do - begin - fFileStream.ReadBuffer(c, 4); - if (c < 0) or (c > 1023) then raise ESFSError.Create('invalid VTDB file'); - SetLength(keys[f].name, c); - if c > 0 then - begin - fFileStream.ReadBuffer(keys[f].name[1], c); - keys[f].name := SFSReplacePathDelims(keys[f].name, '/'); - if keys[f].name[1] = '/' then Delete(keys[f].name, 1, 1); - end; - fFileStream.ReadBuffer(keys[f].ofs, 4); - end; - - // read records (record type will be converted to directory name) - for f := 0 to fcnt-1 do - begin - fFileStream.Position := keys[f].ofs; - fi := TSFSZipFileInfo.Create(self); - fFileStream.ReadBuffer(rtype, 2); - fFileStream.ReadBuffer(fi.fSize, 4); - fFileStream.ReadBuffer(fi.fPackSz, 4); - fi.fOfs := fFileStream.Position+12; - fi.fName := keys[f].name; - fi.fPath := IntToHex(rtype, 4)+'/'; - fi.fMethod := 255; - end; -end; procedure TSFSZipVolume.DFWADReadDirectory (); // idiotic format @@ -445,91 +384,31 @@ procedure TSFSZipVolume.ReadDirectory (); begin case fType of sfszvZIP: ZIPReadDirectory(); - sfszvF2DAT: F2DATReadDirectory(); - sfszvVTDB: VTDBReadDirectory(); sfszvDFWAD: DFWADReadDirectory(); - else raise ESFSError.Create('invalid zipped SFS'); + else raise ESFSError.Create('invalid archive'); end; end; function TSFSZipVolume.OpenFileByIndex (const index: Integer): TStream; var - zs: TZDecompressionStream; - fs: TStream; - gs: TSFSGuardStream; - kill: Boolean; - buf: packed array [0..1023] of Char; - rd: LongInt; + rs: TStream; begin result := nil; - 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; try - { - try - fs := TFileStream.Create(fFileName, fmOpenRead or fmShareDenyWrite); - kill := true; - except - fs := fFileStream; - end; - } - fs := fFileStream; if TSFSZipFileInfo(fFiles[index]).fMethod = 0 then begin - result := TSFSPartialStream.Create(fs, - TSFSZipFileInfo(fFiles[index]).fOfs, - TSFSZipFileInfo(fFiles[index]).fSize, kill); + result := TSFSPartialStream.Create(fFileStream, TSFSZipFileInfo(fFiles[index]).fOfs, TSFSZipFileInfo(fFiles[index]).fSize, false); end else begin - fs.Seek(TSFSZipFileInfo(fFiles[index]).fOfs, soBeginning); - if TSFSZipFileInfo(fFiles[index]).fMethod = 255 then - begin - zs := TZDecompressionStream.Create(fs) - 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; - end; - gs := TSFSGuardStream.Create(zs, fs, true, kill, false); - zs := nil; - fs := nil; - result := TSFSPartialStream.Create(gs, 0, TSFSZipFileInfo(fFiles[index]).fSize, true); + rs := TSFSPartialStream.Create(fFileStream, TSFSZipFileInfo(fFiles[index]).fOfs, TSFSZipFileInfo(fFiles[index]).fPackSz, false); + result := TUnZStream.Create(rs, TSFSZipFileInfo(fFiles[index]).fSize, true, (TSFSZipFileInfo(fFiles[index]).fMethod <> 255)); end; except - FreeAndNil(gs); - FreeAndNil(zs); - if kill then FreeAndNil(fs); + FreeAndNil(rs); result := nil; exit; end; @@ -537,15 +416,12 @@ 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, 'pk3') or + StrEquCI1251(prefix, 'dfwad'); end; procedure TSFSZipVolumeFactory.Recycle (vol: TSFSVolume); @@ -553,15 +429,13 @@ 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 - else if DFWADCheckMagic(st) then vt := sfszvDFWAD - else if F2DATCheckMagic(st) then vt := sfszvF2DAT - else if VTDBCheckMagic(st) then vt := sfszvVTDB; + if ZIPCheckMagic(st) then vt := sfszvZIP + else if DFWADCheckMagic(st) then vt := sfszvDFWAD; if vt <> sfszvNone then begin @@ -589,6 +463,6 @@ var initialization zipf := TSFSZipVolumeFactory.Create(); SFSRegisterVolumeFactory(zipf); -finalization - SFSUnregisterVolumeFactory(zipf); +//finalization +// SFSUnregisterVolumeFactory(zipf); end.