1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 // severely outdated ZIP archive interface
19 module iv
.ziparc
/*is aliced*/;
22 // ////////////////////////////////////////////////////////////////////////// //
23 final class ZipArchive
{
25 import std
.stdio
: File
;
28 align(1) static struct ZipFileHeader
{
30 char[4] sign
; // "PK\x03\x04"
31 ushort extrver
; // version needed to extract
32 ushort gflags
; // general purpose bit flag
33 ushort method
; // compression method
34 ushort mtime
; // last mod file time
35 ushort mdate
; // last mod file date
37 uint pksize
; // compressed size
38 uint size
; // uncompressed size
39 ushort namelen
; // file name length
40 ushort extlen
; // extra field length
43 align(1) static struct CDFileHeader
{
45 //char[4] sign; // "PK\x01\x02"
46 ushort madebyver
; // version made by
47 ushort extrver
; // version needed to extract
48 ushort gflags
; // general purpose bit flag
49 ushort method
; // compression method
50 ushort mtime
; // last mod file time
51 ushort mdate
; // last mod file date
53 uint pksize
; // compressed size
54 uint size
; // uncompressed size
55 ushort namelen
; // file name length
56 ushort extlen
; // extra field length
57 ushort cmtlen
; // file comment length
58 ushort disk
; // disk number start
59 ushort iattr
; // internal file attributes
60 uint attr
; // external file attributes
61 uint hdrofs
; // relative offset of local header
63 @property pure const nothrow @safe @nogc:
64 ubyte hour () { return (mtime
>>11); }
65 ubyte min () { return (mtime
>>5)&0x3f; }
66 ubyte sec () { return (mtime
&0x1f)*2; }
68 ushort year () { return cast(ushort)((mdate
>>9)+1980); }
69 ubyte month () { return (mdate
>>5)&0x0f; }
70 ubyte day () { return (mdate
&0x1f); }
73 align(1) static struct EOCDHeader
{
75 char[4] sign
; // "PK\x05\x06"
76 ushort diskno
; // number of this disk
77 ushort diskcd
; // number of the disk with the start of the central directory
78 ushort diskfileno
; // total number of entries in the central directory on this disk
79 ushort fileno
; // total number of entries in the central directory
80 uint cdsize
; // size of the central directory
81 uint cdofs
; // offset of start of central directory with respect to the starting disk number
82 ushort cmtsize
; // .ZIP file comment length
85 align(1) static struct EOCD64Header
{
87 char[4] sign
; // "PK\x06\x06"
88 ulong eocdsize
; // size of zip64 end of central directory record
89 ushort madebyver
; // version made by
90 ushort extrver
; // version needed to extract
91 uint diskno
; // number of this disk
92 uint diskcd
; // number of the disk with the start of the central directory
93 ulong diskfileno
; // total number of entries in the central directory
94 ulong fileno
; // total number of entries in the central directory
95 ulong cdsize
; // size of the central directory
96 ulong cdofs
; // offset of start of central directory with respect to the starting disk number
99 align(1) static struct Z64Locator
{
101 char[4] sign
; // "PK\x06\x07"
102 uint diskcd
; // number of the disk with the start of the zip64 end of central directory
103 long ecd64ofs
; // relative offset of the zip64 end of central directory record
104 uint diskno
; // total number of disks
107 align(1) static struct Z64Extra
{
112 uint disk
; // number of the disk on which this file starts
115 static struct FileInfo
{
116 bool packed
; // only "store" and "deflate" are supported
125 public static struct DirEntry
{
134 bool mNormNames
; // true: convert names to lower case, do case-insensitive comparison (ASCII only)
137 this (string fname
, bool normNames
=true) {
138 import std
.stdio
: File
;
139 mNormNames
= normNames
;
143 scope(failure
) { zfl
.close
; zfl
= zfl
.init
; }
146 // it now owns the file (if no exception was thrown)
147 this (File fl
, bool normNames
=true) {
148 mNormNames
= normNames
;
151 scope(success
) zfl
= fl
;
154 @property auto files () {
155 static struct Range
{
161 this (ZipArchive ame
, ulong aidx
=0) { me
= ame
; curindex
= aidx
; }
164 @property bool empty () const { return (curindex
>= me
.dir
.length
); }
165 @property DirEntry
front () const {
167 (curindex
< me
.dir
.length ? me
.dir
[cast(usize
)curindex
].path
: null),
168 (curindex
< me
.dir
.length ? me
.dir
[cast(usize
)curindex
].name
: null),
169 (curindex
< me
.dir
.length ? me
.dir
[cast(usize
)curindex
].size
: 0));
171 @property Range
save () { return Range(me
, curindex
); }
172 void popFront () { if (curindex
< me
.dir
.length
) ++curindex
; }
173 @property ulong length () const { return me
.dir
.length
; }
174 @property ulong position () const { return curindex
; } // current position
175 @property void position (ulong np
) { curindex
= np
; }
176 void rewind () { curindex
= 0; }
181 File
fopen (ref in DirEntry
de) {
182 static bool strequ() (const(char)[] s0
, const(char)[] s1
) {
183 if (s0
.length
!= s1
.length
) return false;
184 foreach (immutable idx
, char ch
; s0
) {
186 if (ch
>= 'A' && ch
<= 'Z') ch
+= 32; // poor man's `toLower()`
187 if (c1
>= 'A' && c1
<= 'Z') c1
+= 32; // poor man's `toLower()`
188 if (ch
!= c1
) return false;
193 foreach (immutable idx
, ref fi
; dir
) {
195 if (strequ(fi
.path
, de.path
) && strequ(fi
.name
, de.name
)) return openDirEntry(idx
, fi
.name
);
197 if (fi
.path
== de.path
&& fi
.name
== de.name
) return openDirEntry(idx
, fi
.name
);
201 throw new NamedException
!"ZipArchive"("file not found");
204 File
fopen (const(char)[] fname
) {
206 auto pos
= fname
.length
;
207 while (pos
> 0 && fname
[pos
-1] != '/') --pos
;
209 de.path
= cast(string
)fname
[0..pos
]; // it's safe here
210 de.name
= cast(string
)fname
[pos
..$]; // it's safe here
212 de.name
= cast(string
)fname
; // it's safe here
222 void open (File fl
) {
223 import core
.stdc
.stdio
: SEEK_CUR
, SEEK_END
;
224 debug import std
.stdio
: writeln
, writefln
;
225 scope(failure
) cleanup();
229 if (fl
.rawRead(data
[]).length
!= data
.length
) throw new NamedException
!"ZipArchive"("reading error");
230 return cast(ushort)(data
[0]+0x100*data
[1]);
233 if (fl
.size
> 0xffff_ffffu
) throw new NamedException
!"ZipArchive"("file too big");
234 ulong flsize
= fl
.size
;
235 if (flsize
< EOCDHeader
.sizeof
) throw new NamedException
!"ZipArchive"("file too small");
237 // search for "end of central dir"
238 auto cdbuf
= xalloc
!ubyte(65536+EOCDHeader
.sizeof
+Z64Locator
.sizeof
);
239 scope(exit
) xfree(cdbuf
);
242 if (flsize
< cdbuf
.length
) {
244 buf
= fl
.rawRead(cdbuf
[0..cast(usize
)flsize
]);
245 if (buf
.length
!= flsize
) throw new NamedException
!"ZipArchive"("reading error");
247 fl
.seek(-cast(ulong)cdbuf
.length
, SEEK_END
);
249 buf
= fl
.rawRead(cdbuf
[]);
250 if (buf
.length
!= cdbuf
.length
) throw new NamedException
!"ZipArchive"("reading error");
253 for (pos
= cast(int)(buf
.length
-EOCDHeader
.sizeof
); pos
>= 0; --pos
) {
254 if (buf
[pos
] == 'P' && buf
[pos
+1] == 'K' && buf
[pos
+2] == 5 && buf
[pos
+3] == 6) break;
256 if (pos
< 0) throw new NamedException
!"ZipArchive"("no central dir end marker found");
257 auto eocd
= cast(EOCDHeader
*)&buf
[pos
];
259 writeln("=== EOCD ===");
260 writeln("diskno: ", eocd
.diskno
);
261 writeln("diskcd: ", eocd
.diskcd
);
262 writeln("diskfileno: ", eocd
.diskfileno
);
263 writeln("fileno: ", eocd
.fileno
);
264 writeln("cdsize: ", eocd
.cdsize
);
265 writefln("cdofs: %s (0x%08x)", eocd
.cdofs
, eocd
.cdofs
);
266 writeln("cmtsize: ", eocd
.cmtsize
);
268 long cdofs
= -1, cdsize
= -1;
271 if (eocd
.cdofs
== 0xffff_ffffu
) {
273 if (pos
< Z64Locator
.sizeof
) throw new NamedException
!"ZipArchive"("corrupted archive");
274 auto lt64
= cast(Z64Locator
*)&buf
[pos
-Z64Locator
.sizeof
];
275 if (lt64
.sign
!= "PK\x06\x07") throw new NamedException
!"ZipArchive"("corrupted archive");
276 if (lt64
.diskcd
!= 0 || lt64
.diskno
> 1) throw new NamedException
!"ZipArchive"("multidisk archive");
277 debug writeln("ecd64ofs=", lt64
.ecd64ofs
);
278 if (lt64
.ecd64ofs
< 0 || lt64
.ecd64ofs
+EOCD64Header
.sizeof
> ubufpos
+pos
-Z64Locator
.sizeof
) throw new NamedException
!"ZipArchive"("corrupted archive");
279 EOCD64Header e64
= void;
280 fl
.seek(lt64
.ecd64ofs
);
281 if (fl
.rawRead((&e64
)[0..1]).length
!= 1) throw new NamedException
!"ZipArchive"("reading error");
282 if (e64
.sign
!= "PK\x06\x06") throw new NamedException
!"ZipArchive"("corrupted archive");
283 if (e64
.diskno
!= 0 || e64
.diskcd
!= 0) throw new NamedException
!"ZipArchive"("multidisk archive");
284 if (e64
.diskfileno
!= e64
.fileno
) throw new NamedException
!"ZipArchive"("corrupted archive");
285 if (e64
.cdsize
>= lt64
.ecd64ofs
) throw new NamedException
!"ZipArchive"("corrupted archive");
286 if (e64
.cdofs
>= lt64
.ecd64ofs || e64
.cdofs
+e64
.cdsize
> lt64
.ecd64ofs
) throw new NamedException
!"ZipArchive"("corrupted archive");
290 if (eocd
.diskno
!= 0 || eocd
.diskcd
!= 0) throw new NamedException
!"ZipArchive"("multidisk archive");
291 if (eocd
.diskfileno
!= eocd
.fileno || ubufpos
+pos
+EOCDHeader
.sizeof
+eocd
.cmtsize
!= flsize
) throw new NamedException
!"ZipArchive"("corrupted archive");
293 cdsize
= eocd
.cdsize
;
294 if (cdofs
>= ubufpos
+pos || flsize
-cdofs
< cdsize
) throw new NamedException
!"ZipArchive"("corrupted archive");
297 // now read central directory
298 auto namebuf
= xalloc
!char(0x10000);
299 scope(exit
) xfree(namebuf
);
301 uint[string
] knownNames
; // value is dir index
302 scope(exit
) knownNames
.destroy
;
306 CDFileHeader cdfh
= void;
308 dir
.assumeSafeAppend
; // yep
310 if (bleft
< 4) break;
311 if (fl
.rawRead(sign
[]).length
!= sign
.length
) throw new NamedException
!"ZipArchive"("reading error");
313 if (sign
[0] != 'P' || sign
[1] != 'K') throw new NamedException
!"ZipArchive"("invalid central directory entry");
314 // digital signature?
315 if (sign
[2] == 5 && sign
[3] == 5) {
317 if (bleft
< 2) throw new NamedException
!"ZipArchive"("reading error");
319 if (sz
> bleft
) throw new NamedException
!"ZipArchive"("invalid central directory entry");
320 fl
.seek(sz
, SEEK_CUR
);
325 if (sign
[2] == 1 && sign
[3] == 2) {
326 if (bleft
< cdfh
.sizeof
) throw new NamedException
!"ZipArchive"("reading error");
327 if (fl
.rawRead((&cdfh
)[0..1]).length
!= 1) throw new NamedException
!"ZipArchive"("reading error");
328 bleft
-= cdfh
.sizeof
;
329 if (cdfh
.disk
!= 0) throw new NamedException
!"ZipArchive"("invalid central directory entry (disk number)");
330 if (bleft
< cdfh
.namelen
+cdfh
.extlen
+cdfh
.cmtlen
) throw new NamedException
!"ZipArchive"("invalid central directory entry");
332 if ((cdfh
.method
!= 0 && cdfh
.method
!= 8) || cdfh
.namelen
== 0 ||
(cdfh
.gflags
&0b10_0000_0110_0001) != 0 ||
(cdfh
.attr
&0x58) != 0 ||
333 cast(long)cdfh
.hdrofs
+(cdfh
.method ? cdfh
.pksize
: cdfh
.size
) >= ubufpos
+pos
)
336 fl
.seek(cdfh
.namelen
+cdfh
.extlen
+cdfh
.cmtlen
, SEEK_CUR
);
337 bleft
-= cdfh
.namelen
+cdfh
.extlen
+cdfh
.cmtlen
;
341 fi
.packed
= (cdfh
.method
!= 0);
342 fi
.pksize
= cdfh
.pksize
;
344 fi
.hdrofs
= cdfh
.hdrofs
;
345 if (!fi
.packed
) fi
.pksize
= fi
.size
;
346 // now, this is valid file, so read it's name
347 if (fl
.rawRead(namebuf
[0..cdfh
.namelen
]).length
!= cdfh
.namelen
) throw new NamedException
!"ZipArchive"("reading error");
348 auto nb
= new char[](cdfh
.namelen
);
351 foreach (ref char ch
; namebuf
[0..cdfh
.namelen
]) {
352 if (ch
== '\\') ch
= '/'; // just in case
353 if (ch
== '/' && (nbpos
== 0 ||
(nbpos
> 0 && nb
[nbpos
-1] == '/'))) continue;
354 if (ch
== '/') lastSlash
= nbpos
+1;
355 if (mNormNames
&& ch
>= 'A' && ch
<= 'Z') ch
+= 32; // poor man's `toLower()`
359 // should we parse extra field?
360 debug writefln("size=0x%08x; pksize=0x%08x; packed=%s", fi
.size
, fi
.pksize
, (fi
.packed ?
"tan" : "ona"));
361 if (zip64
&& (fi
.size
== 0xffff_ffffu || fi
.pksize
== 0xffff_ffffu || fi
.hdrofs
== 0xffff_ffffu
)) {
364 //Z64Extra z64e = void;
365 debug writeln("extlen=", cdfh
.extlen
);
366 while (cdfh
.extlen
>= 4) {
367 auto eid
= readU16();
368 auto esize
= readU16();
369 debug writefln("0x%04x %s", eid
, esize
);
372 if (cdfh
.extlen
< esize
) break;
373 cdfh
.extlen
-= esize
;
376 if (eid
!= 1 || esize
< /*Z64Extra.sizeof*/8) {
377 fl
.seek(esize
, SEEK_CUR
);
381 if (fi
.size
== 0xffff_ffffu
) {
382 if (fl
.rawRead((&fi
.size
)[0..1]).length
!= 1) throw new NamedException
!"ZipArchive"("reading error");
384 //debug writeln(" size=", fi.size);
386 if (fi
.pksize
== 0xffff_ffffu
) {
388 //fi.pksize = ulong.max; // this means "get from local header"
389 // read local file header; it's slow, but i don't care
391 if (fi.hdrofs == 0xffff_ffffu) throw new NamedException!"ZipArchive"("invalid zip64 archive (3)");
392 CDFileHeader lfh = void;
393 auto oldpos = fl.tell;
395 if (fl.rawRead((&lfh)[0..1]).length != 1) throw new NamedException!"ZipArchive"("reading error");
398 throw new NamedException
!"ZipArchive"("invalid zip64 archive (4)");
400 if (esize
< 8) throw new NamedException
!"ZipArchive"("invalid zip64 archive (1)");
401 if (fl
.rawRead((&fi
.pksize
)[0..1]).length
!= 1) throw new NamedException
!"ZipArchive"("reading error");
405 if (fi
.hdrofs
== 0xffff_ffffu
) {
406 if (esize
< 8) throw new NamedException
!"ZipArchive"("invalid zip64 archive (2)");
407 if (fl
.rawRead((&fi
.hdrofs
)[0..1]).length
!= 1) throw new NamedException
!"ZipArchive"("reading error");
410 if (esize
> 0) fl
.seek(esize
, SEEK_CUR
); // skip possible extra data
411 //if (z64e.disk != 0) throw new NamedException!"ZipArchive"("invalid central directory entry (disk number)");
416 debug writeln("required zip64 record not found");
417 //throw new NamedException!"ZipArchive"("required zip64 record not found");
418 //fi.size = fi.pksize = 0x1_0000_0000Lu; // hack: skip it
422 if (!doSkip
&& nbpos
> 0 && nb
[nbpos
-1] != '/') {
423 if (auto idx
= nb
[0..nbpos
] in knownNames
) {
425 auto fip
= &dir
[*idx
];
426 fip
.packed
= fi
.packed
;
427 fip
.pksize
= fi
.pksize
;
429 fip
.hdrofs
= fi
.hdrofs
;
432 if (dir
.length
== uint.max
) throw new NamedException
!"ZipArchive"("directory too long");
434 fi
.path
= cast(string
)nb
[0..lastSlash
]; // this is safe
435 fi
.name
= cast(string
)nb
[lastSlash
..nbpos
]; // this is safe
438 fi
.name
= cast(string
)nb
[0..nbpos
]; // this is safe
440 knownNames
[fi
.name
] = cast(uint)dir
.length
;
443 //debug writefln("%10s %10s %s %04s/%02s/%02s %02s:%02s:%02s %s", fi.pksize, fi.size, (fi.packed ? "P" : "."), cdfh.year, cdfh.month, cdfh.day, cdfh.hour, cdfh.min, cdfh.sec, fi.name);
445 // skip extra and comments
446 fl
.seek(cdfh
.extlen
+cdfh
.cmtlen
, SEEK_CUR
);
447 bleft
-= cdfh
.namelen
+cdfh
.extlen
+cdfh
.cmtlen
;
451 throw new NamedException
!"ZipArchive"("unknown central directory entry");
453 debug writeln(dir
.length
, " files found");
457 // ////////////////////////////////////////////////////////////////////// //
458 static import core
.sync
.mutex
;
460 core
.sync
.mutex
.Mutex
lock;
463 lock = new core
.sync
.mutex
.Mutex
;
466 auto openDirEntry (uint idx
, string filename
) {
467 import core
.sys
.linux
.stdio
: fopencookie
;
468 import core
.stdc
.stdio
: FILE
;
469 import core
.stdc
.stdio
: fopen
, fclose
;
470 import core
.stdc
.stdlib
: calloc
, free
;
472 import std
.internal
.cstring
: tempCString
;
473 import core
.memory
: GC
;
475 if (!zfl
.isOpen
) throw new NamedException
!"ZipArchive"("archive wasn't opened");
476 if (zfl
.name
.length
== 0) throw new NamedException
!"ZipArchive"("archive has no name");
477 if (idx
>= dir
.length
) if (!zfl
.isOpen
) throw new NamedException
!"ZipArchive"("invalid dir index");
481 scope(exit
) lock.unlock();
483 ZipFileHeader zfh
= void;
484 zfl
.seek(dir
[idx
].hdrofs
);
485 if (zfl
.rawRead((&zfh
)[0..1]).length
!= 1) throw new NamedException
!"ZipArchive"("reading error");
486 if (zfh
.sign
!= "PK\x03\x04") throw new NamedException
!"ZipArchive"("invalid archive entry");
487 // skip name and extra
488 stofs
= zfl
.tell
+zfh
.namelen
+zfh
.extlen
;
491 // create cookied `FILE*`
492 auto fc
= cast(InnerFileCookied
*)calloc(1, InnerFileCookied
.sizeof
);
493 scope(exit
) if (fc
!is null) free(fc
);
495 import core
.exception
: onOutOfMemoryErrorNoGC
;
496 onOutOfMemoryErrorNoGC();
498 (*fc
) = InnerFileCookied
.init
;
500 (*fc
).size
= cast(uint)dir
[idx
].size
; //FIXME
501 (*fc
).pksize
= cast(uint)dir
[idx
].pksize
; //FIXME
502 (*fc
).mode
= (dir
[idx
].packed ? InnerFileCookied
.Mode
.Zip
: InnerFileCookied
.Mode
.Raw
);
504 GC
.addRange(fc
, InnerFileCookied
.sizeof
);
506 //(*fc).fl = //fopen(zfl.name.tempCString!char(), "r");
507 //if ((*fc).fl is null) throw new NamedException!"ZipArchive"("can't open archive file");
509 // open `cooked` file
510 FILE
* fres
= fopencookie(cast(void*)fc
, "r", fcdatpkCallbacks
);
513 if ((*fc
).fl
!is null) fclose((*fc
).fl
);
514 try { (*fc
).xfl
.detach(); } catch (Exception
) {}
515 throw new NamedException
!"ZipArchive"("can't open cookied file");
520 return File(fres
, filename
);
524 // ////////////////////////////////////////////////////////////////////// //
525 // "inner" file processor; processes both packed and unpacked files
526 // can be used as normal disk file processor too
527 static struct InnerFileCookied
{
528 private import etc
.c
.zlib
;
529 private import core
.sys
.posix
.sys
.types
: off64_t
= off_t
;
530 private import core
.stdc
.stdio
: FILE
;
534 enum Mode
{ Raw
, ZLib
, Zip
}
536 core
.sync
.mutex
.Mutex
lock;
537 // note that either one of `fl` or `xfl` must be opened and operational
538 FILE
* fl
; // disk file, can be `null`
539 File xfl
; // disk file, can be closed
541 long stpos
; // starting position
542 uint size
; // unpacked size
543 uint pksize
; // packed size
544 uint pos
; // current file position
545 uint prpos
; // previous file position
546 uint pkpos
; // current position in DAT
547 ubyte[] pkb
; // packed data
551 @disable this (this);
554 ~this () { close(); }
556 @property bool isOpen () @safe /*@nogc*/ { return (fl
!is null || xfl
.isOpen
); }
560 import core.memory : GC;
561 lock = new core.sync.mutex.Mutex;
562 GC.addRoot(*cast(void**)&lock);
567 import core
.memory
: GC
;
568 import core
.stdc
.stdlib
: free
;
570 //if (lock !is null) { import iv.writer; writeln("CLOSING!"); }
571 if (lock !is null) lock.lock();
572 scope(exit
) if (lock !is null) lock.unlock();
579 import core
.stdc
.stdio
: fclose
;
583 try { xfl
.detach(); } catch (Exception
) {} // it's safe to detach closed File
588 GC.removeRoot(*cast(void**)&lock);
595 private bool initZStream () {
596 import core
.stdc
.stdlib
: malloc
, free
;
597 if (mode
== Mode
.Raw || pkb
.ptr
!is null) return true;
598 // allocate buffer for packed data
599 auto pb
= cast(ubyte*)malloc(ibsize
);
600 if (pb
is null) return false;
604 // initialize unpacker
605 // -15 is a magic value used to decompress zip files:
606 // it has the effect of not requiring the 2 byte header and 4 byte trailer
607 if (inflateInit2(&zs
, (mode
== Mode
.Zip ?
-15 : 15)) != Z_OK
) {
616 private bool readPackedChunk () {
617 import core
.stdc
.stdio
: fread
;
618 import core
.sys
.posix
.stdio
: fseeko
;
619 if (zs
.avail_in
> 0) return true;
620 if (pkpos
>= pksize
) return false;
621 zs
.next_in
= cast(typeof(zs
.next_in
))pkb
.ptr
;
622 zs
.avail_in
= cast(uint)(pksize
-pkpos
> ibsize ? ibsize
: pksize
-pkpos
);
625 if (fseeko(fl
, stpos
+pkpos
, 0) < 0) return false;
626 if (fread(pkb
.ptr
, zs
.avail_in
, 1, fl
) != 1) return false;
630 xfl
.seek(stpos
+pkpos
, 0);
631 auto rd
= xfl
.rawRead(pkb
[0..zs
.avail_in
]);
632 if (rd
.length
!= zs
.avail_in
) return false;
633 } catch (Exception
) { return false; } //BAD DOGGY!
635 pkpos
+= zs
.avail_in
;
639 private bool unpackNextChunk () {
640 while (zs
.avail_out
> 0) {
641 if (eoz
) return false;
642 if (!readPackedChunk()) return false;
643 auto err
= inflate(&zs
, Z_SYNC_FLUSH
);
644 //if (err == Z_BUF_ERROR) { import iv.writer; writeln("*** OUT OF BUFFER!"); }
645 if (err
!= Z_STREAM_END
&& err
!= Z_OK
) return false;
646 if (err
== Z_STREAM_END
) eoz
= true;
652 ssize
read (void* buf
, size_t count
) {
653 if (buf
is null) return -1;
654 if (count
== 0 || size
== 0) return 0;
656 scope(exit
) lock.unlock();
657 if (!isOpen
) return -1; // read error
658 if (pos
>= size
) return 0; // EOF
659 if (mode
== Mode
.Raw
) {
660 import core
.stdc
.stdio
: ferror
, fread
;
661 import core
.sys
.posix
.stdio
: fseeko
;
662 if (size
-pos
< count
) count
= cast(size_t
)(size
-pos
);
665 if (fseeko(fl
, stpos
+pos
, 0) < 0) return -1;
666 auto rd
= fread(buf
, 1, count
, fl
);
667 if (rd
!= count
&& (rd
< 0 ||
ferror(fl
))) rd
= -1;
668 if (rd
> 0) pos
+= rd
;
673 xfl
.seek(stpos
+pos
, 0);
674 auto rd
= xfl
.rawRead(buf
[0..count
]);
676 return (rd
.length
== count ? rd
.length
: -1);
677 } catch (Exception
) {} //BAD DOGGY!
681 if (pkb
.ptr
is null && !initZStream()) return -1;
682 // do we want to seek backward?
688 if (!initZStream()) return -1;
691 // do we need to seek forward?
695 uint skp
= pos
-prpos
;
697 uint rd
= cast(uint)(skp
> tbuf
.length ? tbuf
.length
: skp
);
698 zs
.next_out
= cast(typeof(zs
.next_out
))tbuf
.ptr
;
700 if (!unpackNextChunk()) return -1;
706 if (size
-pos
< count
) count
= cast(size_t
)(size
-pos
);
707 zs
.next_out
= cast(typeof(zs
.next_out
))buf
;
708 zs
.avail_out
= cast(uint)count
;
709 if (!unpackNextChunk()) return -1;
710 prpos
= (pos
+= count
);
715 long seek (long ofs
, int whence
) {
717 scope(exit
) lock.unlock();
718 if (!isOpen
) return -1;
719 //TODO: overflow checks
727 if (ofs
> 0) ofs
= 0;
733 if (ofs
< 0) return -1;
734 if (ofs
> size
) ofs
= size
;
743 // ////////////////////////////////////////////////////////////////////// //
745 import core
.sys
.linux
.stdio
: cookie_io_functions_t
;
746 import core
.sys
.posix
.sys
.types
: ssize
, off64_t
= off_t
;
748 ssize
fcdatpkRead (void* cookie
, char* buf
, size_t count
) {
749 //{ import iv.writer; writeln("reading ", count, " bytes"); }
750 import core
.stdc
.errno
;
751 auto fc
= cast(InnerFileCookied
*)cookie
;
752 auto res
= fc
.read(buf
, count
);
753 if (res
< 0) { errno
= EIO
; return -1; }
757 ssize
fcdatpkWrite (void* cookie
, const(char)* buf
, size_t count
) {
758 //{ import iv.writer; writeln("writing ", count, " bytes"); }
759 import core
.stdc
.errno
;
760 errno
= EIO
; //FIXME: find better code
761 return 0; // error; write should not return `-1`
764 int fcdatpkSeek (void* cookie
, off64_t
* offset
, int whence
) {
765 //{ import iv.writer; writeln("seeking ", *offset, " bytes, whence=", whence); }
766 import core
.stdc
.errno
;
767 auto fc
= cast(InnerFileCookied
*)cookie
;
768 auto res
= fc
.seek(*offset
, whence
);
769 if (res
< 0) { errno
= EIO
; return -1; }
770 *offset
= cast(off64_t
)res
;
774 int fcdatpkClose (void* cookie
) {
775 import core
.memory
: GC
;
776 import core
.stdc
.stdlib
: free
;
777 //{ import iv.writer; writeln("closing"); }
778 auto fc
= cast(InnerFileCookied
*)cookie
;
780 GC
.removeRange(cookie
);
781 try { fc
.__dtor(); } catch (Exception
) {}
782 // no need to run finalizers, we SHOULD NOT have any
783 //try { GC.runFinalizers(cookie[0..InnerFileCookied.sizeof]); } catch (Exception) {}
786 //{ import iv.writer; writeln("closed"); }
791 __gshared cookie_io_functions_t fcdatpkCallbacks
= cookie_io_functions_t(
792 /*.read =*/ &fcdatpkRead
,
793 /*.write =*/ &fcdatpkWrite
,
794 /*.seek =*/ &fcdatpkSeek
,
795 /*.close =*/ &fcdatpkClose
,
799 T
[] xalloc(T
) (size_t len
) {
800 import core
.stdc
.stdlib
: malloc
;
801 if (len
< 1) return null;
802 auto res
= cast(T
*)malloc(len
*T
.sizeof
);
804 import core
.exception
: onOutOfMemoryErrorNoGC
;
805 onOutOfMemoryErrorNoGC();
807 res
[0..len
] = T
.init
;
811 void xfree(T
) (ref T
[] slc
) {
812 if (slc
.ptr
!is null) {
813 import core
.stdc
.stdlib
: free
;