1 // Streaming R/O Virtual File System v0.2.0
2 // Copyright (C) XL A.S. Ketmar. All rights reserved
3 // See the file aplicense.txt for conditions of use.
5 // grouping files with packing:
6 // zip, pk3: PKZIP-compatible archives (store, deflate)
7 // dfwad : D2D:F wad archives
9 {.$DEFINE SFS_DEBUG_ZIPFS}
17 SysUtils
, Classes
, Contnrs
, sfs
;
21 TSFSZipVolumeType
= (sfszvNone
, sfszvZIP
, sfszvDFWAD
);
23 TSFSZipVolume
= class(TSFSVolume
)
25 fType
: TSFSZipVolumeType
;
27 procedure ZIPReadDirectory ();
28 procedure DFWADReadDirectory ();
30 procedure ReadDirectory (); override;
33 function OpenFileByIndex (const index
: Integer): TStream
; override;
36 TSFSZipVolumeFactory
= class(TSFSVolumeFactory
)
38 function IsMyVolumePrefix (const prefix
: AnsiString): Boolean; override;
39 function Produce (const prefix
, fileName
: AnsiString; st
: TStream
): TSFSVolume
; override;
40 procedure Recycle (vol
: TSFSVolume
); override;
51 TSFSZipFileInfo
= class(TSFSFileInfo
)
53 fMethod
: Byte; // 0: store; 8: deflate; 255: other
54 fPackSz
: Int64; // can be -1
57 TZLocalFileHeader
= packed record
70 procedure readLFH (st
: TStream
; var hdr
: TZLocalFileHeader
);
71 {.$IFDEF ENDIAN_LITTLE}
73 hdr
.version
:= readByte(st
);
74 hdr
.hostOS
:= readByte(st
);
75 hdr
.flags
:= readWord(st
);
76 hdr
.method
:= readWord(st
);
77 hdr
.time
:= readLongWord(st
);
78 hdr
.crc
:= readLongWord(st
);
79 hdr
.packSz
:= readLongWord(st
);
80 hdr
.unpackSz
:= readLongWord(st
);
81 hdr
.fnameSz
:= readWord(st
);
82 hdr
.localExtraSz
:= readWord(st
);
86 function ZIPCheckMagic (st
: TStream
): Boolean;
88 sign
: packed array [0..3] of Char;
91 st
.ReadBuffer(sign
[0], 4);
92 st
.Seek(-4, soCurrent
);
93 if (sign
<> 'PK'#3#4) and (sign
<> 'PK'#5#6) then exit
;
98 function DFWADCheckMagic (st
: TStream
): Boolean;
100 sign
: packed array [0..5] of Char;
103 if st
.Size
< 10 then exit
;
104 st
.ReadBuffer(sign
[0], 6);
105 {fcnt :=} readWord(st
);
106 st
.Seek(-8, soCurrent
);
107 //writeln('trying DFWAD... [', sign, ']');
108 if (sign
[0] <> 'D') and (sign
[1] <> 'F') and (sign
[2] <> 'W') and
109 (sign
[3] <> 'A') and (sign
[4] <> 'D') and (sign
[5] <> #
$01) then exit
;
110 //writeln('DFWAD FOUND, with ', fcnt, ' files');
111 //if (fcnt < 0) then exit;
117 procedure TSFSZipVolume
.ZIPReadDirectory ();
121 sign
: packed array [0..3] of Char;
122 lhdr
: TZLocalFileHeader
;
128 // read local directory
130 fFileStream
.ReadBuffer(sign
[0], Length(sign
));
132 // skip data descriptor
133 if sign
= 'PK'#7#8 then
135 fFileStream
.seek(3*4, soCurrent
);
139 if sign
<> 'PK'#3#4 then break
;
143 readLFH(fFileStream
, lhdr
);
145 fi
:= TSFSZipFileInfo
.Create(self
);
149 if lhdr
.fnameSz
> 255 then name
[0] := #255 else name
[0] := chr(lhdr
.fnameSz
);
150 fFileStream
.ReadBuffer(name
[1], Length(name
));
151 fFileStream
.Seek(lhdr
.fnameSz
-Length(name
), soCurrent
); // rest of the name (if any)
152 fi
.fName
:= utf8to1251(name
);
154 // here we should process extra field: it may contain utf8 filename
155 while lhdr
.localExtraSz
>= 4 do
157 efid
:= readWord(fFileStream
);
158 efsz
:= readWord(fFileStream
);
159 Dec(lhdr
.localExtraSz
, 4);
160 if efsz
> lhdr
.localExtraSz
then break
;
161 // Info-ZIP Unicode Path Extra Field?
162 if (efid
= $7075) and (efsz
<= 255+5) and (efsz
> 5) then
164 fFileStream
.ReadBuffer(izver
, 1);
172 Dec(lhdr
.localExtraSz
, efsz
);
173 fFileStream
.ReadBuffer(izcrc
, 4); // name crc, ignore it
175 name
[0] := chr(efsz
);
176 fFileStream
.ReadBuffer(name
[1], Length(name
));
177 fi
.fName
:= utf8to1251(name
);
184 fFileStream
.Seek(efsz
, soCurrent
);
185 Dec(lhdr
.localExtraSz
, efsz
);
189 if lhdr
.localExtraSz
> 0 then fFileStream
.Seek(lhdr
.localExtraSz
, soCurrent
);
191 if (lhdr
.flags
and 1) <> 0 then
193 // encrypted file: skip it
197 if (lhdr
.method
<> 0) and (lhdr
.method
<> 8) then
199 // not stored. not deflated. skip.
203 fi
.fOfs
:= fFileStream
.Position
;
204 fi
.fSize
:= lhdr
.unpackSz
;
205 fi
.fPackSz
:= lhdr
.packSz
;
206 fi
.fMethod
:= lhdr
.method
;
209 fFileStream
.Seek(lhdr
.packSz
, soCurrent
);
210 if ignoreFile
then fi
.Free();
213 if (sign <> 'PK'#1#2) and (sign <> 'PK'#5#6) then
215 {$IFDEF SFS_DEBUG_ZIPFS}
216 WriteLn(ErrOutput, 'end: $', IntToHex(fFileStream.Position, 8));
217 WriteLn(ErrOutput, 'sign: $', sign[0], sign[1], '#', ord(sign[2]), '#', ord(sign[3]));
219 raise ESFSError.Create('invalid .ZIP archive (no central dir)');
225 procedure TSFSZipVolume
.DFWADReadDirectory ();
231 fofs
, fpksize
: LongWord;
232 curpath
, fname
: string;
233 name
: packed array [0..15] of Char;
236 fFileStream
.Seek(6, soCurrent
); // skip signature
237 fcnt
:= readWord(fFileStream
);
238 if fcnt
= 0 then exit
;
240 for f
:= 0 to fcnt
-1 do
242 fFileStream
.ReadBuffer(name
[0], 16);
243 fofs
:= readLongWord(fFileStream
);
244 fpksize
:= readLongWord(fFileStream
);
247 while (c
< 16) and (name
[c
] <> #0) do
249 if name
[c
] = '\' then name
[c
] := '/'
250 else if name
[c
] = '/' then name
[c
] := '_';
251 fname
:= fname
+name
[c
];
255 if (fofs
= 0) and (fpksize
= 0) then
257 if length(fname
) <> 0 then fname
:= fname
+'/';
261 if length(fname
) = 0 then continue
; // just in case
262 //writeln('DFWAD: [', curpath, '] [', fname, '] at ', fofs, ', size ', fpksize);
263 // create file record
264 fi
:= TSFSZipFileInfo
.Create(self
);
267 fi
.fPackSz
:= fpksize
;
274 procedure TSFSZipVolume
.ReadDirectory ();
277 sfszvZIP
: ZIPReadDirectory();
278 sfszvDFWAD
: DFWADReadDirectory();
279 else raise ESFSError
.Create('invalid archive');
283 function TSFSZipVolume
.OpenFileByIndex (const index
: Integer): TStream
;
289 if fFiles
= nil then exit
;
290 if (index
< 0) or (index
>= fFiles
.Count
) or (fFiles
[index
] = nil) then exit
;
292 if TSFSZipFileInfo(fFiles
[index
]).fMethod
= 0 then
294 result
:= TSFSPartialStream
.Create(fFileStream
, TSFSZipFileInfo(fFiles
[index
]).fOfs
, TSFSZipFileInfo(fFiles
[index
]).fSize
, false);
298 rs
:= TSFSPartialStream
.Create(fFileStream
, TSFSZipFileInfo(fFiles
[index
]).fOfs
, TSFSZipFileInfo(fFiles
[index
]).fPackSz
, false);
299 result
:= TUnZStream
.Create(rs
, TSFSZipFileInfo(fFiles
[index
]).fSize
, true, (TSFSZipFileInfo(fFiles
[index
]).fMethod
<> 255));
309 { TSFSZipVolumeFactory }
310 function TSFSZipVolumeFactory
.IsMyVolumePrefix (const prefix
: AnsiString): Boolean;
313 StrEquCI1251(prefix
, 'zip') or
314 StrEquCI1251(prefix
, 'pk3') or
315 StrEquCI1251(prefix
, 'dfwad');
318 procedure TSFSZipVolumeFactory
.Recycle (vol
: TSFSVolume
);
323 function TSFSZipVolumeFactory
.Produce (const prefix
, fileName
: AnsiString; st
: TStream
): TSFSVolume
;
325 vt
: TSFSZipVolumeType
;
328 if ZIPCheckMagic(st
) then vt
:= sfszvZIP
329 else if DFWADCheckMagic(st
) then vt
:= sfszvDFWAD
;
331 if vt
<> sfszvNone
then
333 result
:= TSFSZipVolume
.Create(fileName
, st
);
334 TSFSZipVolume(result
).fType
:= vt
;
336 result
.DoDirectoryRead();
337 except {$IFDEF SFS_DEBUG_ZIPFS} on e
: Exception
do begin
338 WriteLn(errOutput
, 'ZIP ERROR: [', e
.ClassName
, ']: ', e
.Message);
342 {$IFDEF SFS_DEBUG_ZIPFS}end;{$ENDIF}
353 zipf
: TSFSZipVolumeFactory
;
355 zipf
:= TSFSZipVolumeFactory
.Create();
356 SFSRegisterVolumeFactory(zipf
);
358 // SFSUnregisterVolumeFactory(zipf);