src/util/vfs/vfs-zip.c (view raw)
1/* Copyright (c) 2013-2014 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include <mgba-util/vfs.h>
7
8#include <mgba-util/math.h>
9#include <mgba-util/string.h>
10
11#ifdef USE_LIBZIP
12#include <zip.h>
13
14struct VDirEntryZip {
15 struct VDirEntry d;
16 struct zip* z;
17 zip_int64_t index;
18};
19
20struct VDirZip {
21 struct VDir d;
22 struct zip* z;
23 bool write;
24 struct VDirEntryZip dirent;
25};
26
27struct VFileZip {
28 struct VFile d;
29 struct zip* z;
30 struct zip_file* zf;
31 void* buffer;
32 size_t offset;
33 size_t bufferSize;
34 size_t readSize;
35 size_t writeSize;
36 size_t fileSize;
37 char* name;
38 bool write;
39};
40
41enum {
42 BLOCK_SIZE = 1024
43};
44#else
45#ifdef USE_MINIZIP
46#include <minizip/zip.h>
47#include <minizip/unzip.h>
48#else
49#include "third-party/zlib/contrib/minizip/zip.h"
50#include "third-party/zlib/contrib/minizip/unzip.h"
51#endif
52#include <mgba-util/memory.h>
53
54struct VDirEntryZip {
55 struct VDirEntry d;
56 char name[PATH_MAX];
57 size_t fileSize;
58 unzFile uz;
59 zipFile z;
60};
61
62struct VDirZip {
63 struct VDir d;
64 unzFile uz;
65 zipFile z;
66 struct VDirEntryZip dirent;
67 bool atStart;
68};
69
70struct VFileZip {
71 struct VFile d;
72 unzFile uz;
73 zipFile z;
74 void* buffer;
75 size_t bufferSize;
76 size_t fileSize;
77};
78#endif
79
80static bool _vfzClose(struct VFile* vf);
81static off_t _vfzSeek(struct VFile* vf, off_t offset, int whence);
82static ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t size);
83static ssize_t _vfzWrite(struct VFile* vf, const void* buffer, size_t size);
84static void* _vfzMap(struct VFile* vf, size_t size, int flags);
85static void _vfzUnmap(struct VFile* vf, void* memory, size_t size);
86static void _vfzTruncate(struct VFile* vf, size_t size);
87static ssize_t _vfzSize(struct VFile* vf);
88static bool _vfzSync(struct VFile* vf, void* buffer, size_t size);
89
90static bool _vdzClose(struct VDir* vd);
91static void _vdzRewind(struct VDir* vd);
92static struct VDirEntry* _vdzListNext(struct VDir* vd);
93static struct VFile* _vdzOpenFile(struct VDir* vd, const char* path, int mode);
94static struct VDir* _vdzOpenDir(struct VDir* vd, const char* path);
95static bool _vdzDeleteFile(struct VDir* vd, const char* path);
96
97static const char* _vdezName(struct VDirEntry* vde);
98static enum VFSType _vdezType(struct VDirEntry* vde);
99
100#ifndef USE_LIBZIP
101static voidpf _vfmzOpen(voidpf opaque, const char* filename, int mode) {
102 UNUSED(opaque);
103 int flags = 0;
104 switch (mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) {
105 case ZLIB_FILEFUNC_MODE_READ:
106 flags = O_RDONLY;
107 break;
108 case ZLIB_FILEFUNC_MODE_WRITE:
109 flags = O_WRONLY;
110 break;
111 case ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE:
112 flags = O_RDWR;
113 break;
114 }
115 if (mode & ZLIB_FILEFUNC_MODE_CREATE) {
116 flags |= O_CREAT;
117 if (!(mode & ZLIB_FILEFUNC_MODE_EXISTING)) {
118 flags |= O_TRUNC;
119 }
120 }
121 return VFileOpen(filename, flags);
122}
123
124static uLong _vfmzRead(voidpf opaque, voidpf stream, void* buf, uLong size) {
125 UNUSED(opaque);
126 struct VFile* vf = stream;
127 ssize_t r = vf->read(vf, buf, size);
128 if (r < 0) {
129 return 0;
130 }
131 return r;
132}
133
134static uLong _vfmzWrite(voidpf opaque, voidpf stream, const void* buf, uLong size) {
135 UNUSED(opaque);
136 struct VFile* vf = stream;
137 ssize_t r = vf->write(vf, buf, size);
138 if (r < 0) {
139 return 0;
140 }
141 return r;
142}
143
144int _vfmzClose(voidpf opaque, voidpf stream) {
145 UNUSED(opaque);
146 struct VFile* vf = stream;
147 return vf->close(vf);
148}
149
150int _vfmzError(voidpf opaque, voidpf stream) {
151 UNUSED(opaque);
152 struct VFile* vf = stream;
153 return vf->seek(vf, 0, SEEK_CUR) < 0;
154}
155
156long _vfmzTell(voidpf opaque, voidpf stream) {
157 UNUSED(opaque);
158 struct VFile* vf = stream;
159 return vf->seek(vf, 0, SEEK_CUR);
160}
161
162long _vfmzSeek(voidpf opaque, voidpf stream, uLong offset, int origin) {
163 UNUSED(opaque);
164 struct VFile* vf = stream;
165 return vf->seek(vf, offset, origin) < 0;
166}
167#endif
168
169struct VDir* VDirOpenZip(const char* path, int flags) {
170#ifndef USE_LIBZIP
171 UNUSED(flags);
172 zlib_filefunc_def ops = {
173 .zopen_file = _vfmzOpen,
174 .zread_file = _vfmzRead,
175 .zwrite_file = _vfmzWrite,
176 .ztell_file = _vfmzTell,
177 .zseek_file = _vfmzSeek,
178 .zclose_file = _vfmzClose,
179 .zerror_file = _vfmzError,
180 .opaque = 0
181 };
182 unzFile uz = NULL;
183 zipFile z = NULL;
184
185 if ((flags & O_ACCMODE) == O_RDWR) {
186 return 0; // Read/write not supported
187 }
188 if (flags & O_WRONLY) {
189 z = zipOpen2(path, 0, NULL, &ops);
190 if (!z) {
191 return 0;
192 }
193 } else {
194 uz = unzOpen2(path, &ops);
195 if (!uz) {
196 return 0;
197 }
198 }
199#else
200 int zflags = 0;
201 if (flags & O_CREAT) {
202 zflags |= ZIP_CREATE;
203 }
204 if (flags & O_TRUNC) {
205 zflags |= ZIP_TRUNCATE;
206 }
207 if (flags & O_EXCL) {
208 zflags |= ZIP_EXCL;
209 }
210 if (!(flags & O_WRONLY)) {
211 zflags |= ZIP_RDONLY;
212 }
213
214 struct zip* z = zip_open(path, zflags, 0);
215 if (!z) {
216 return 0;
217 }
218#endif
219 struct VDirZip* vd = malloc(sizeof(struct VDirZip));
220
221 vd->d.close = _vdzClose;
222 vd->d.rewind = _vdzRewind;
223 vd->d.listNext = _vdzListNext;
224 vd->d.openFile = _vdzOpenFile;
225 vd->d.openDir = _vdzOpenDir;
226 vd->d.deleteFile = _vdzDeleteFile;
227 vd->z = z;
228
229#ifdef USE_LIBZIP
230 vd->write = !!(flags & O_WRONLY);
231#else
232 vd->atStart = true;
233 vd->uz = uz;
234#endif
235
236 vd->dirent.d.name = _vdezName;
237 vd->dirent.d.type = _vdezType;
238#ifdef USE_LIBZIP
239 vd->dirent.index = -1;
240#else
241 vd->dirent.uz = uz;
242#endif
243 vd->dirent.z = z;
244
245 return &vd->d;
246}
247
248#ifdef USE_LIBZIP
249bool _vfzClose(struct VFile* vf) {
250 struct VFileZip* vfz = (struct VFileZip*) vf;
251 if (vfz->write) {
252 zip_source_t* source = zip_source_buffer(vfz->z, vfz->buffer, vfz->writeSize, 1);
253 vfz->buffer = NULL;
254 if (source && zip_file_add(vfz->z, vfz->name, source, ZIP_FL_OVERWRITE) < 0) {
255 zip_source_free(source);
256 return false;
257 }
258 free(vfz->name);
259 }
260 if (vfz->zf && zip_fclose(vfz->zf) < 0) {
261 return false;
262 }
263 if (vfz->buffer) {
264 free(vfz->buffer);
265 }
266 free(vfz);
267 return true;
268}
269
270off_t _vfzSeek(struct VFile* vf, off_t offset, int whence) {
271 struct VFileZip* vfz = (struct VFileZip*) vf;
272
273 size_t position;
274 switch (whence) {
275 case SEEK_SET:
276 position = offset;
277 break;
278 case SEEK_CUR:
279 if (offset < 0 && ((vfz->offset < (size_t) -offset) || (offset == INT_MIN))) {
280 return -1;
281 }
282 position = vfz->offset + offset;
283 break;
284 case SEEK_END:
285 if (offset < 0 && ((vfz->fileSize < (size_t) -offset) || (offset == INT_MIN))) {
286 return -1;
287 }
288 position = vfz->fileSize + offset;
289 break;
290 default:
291 return -1;
292 }
293
294 if (position <= vfz->offset) {
295 vfz->offset = position;
296 return position;
297 }
298
299 if (position <= vfz->fileSize) {
300 ssize_t read = vf->read(vf, 0, position - vfz->offset);
301 if (read < 0) {
302 return -1;
303 }
304 return vfz->offset;
305 }
306
307 return -1;
308}
309
310ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t size) {
311 struct VFileZip* vfz = (struct VFileZip*) vf;
312
313 size_t bytesRead = 0;
314 if (!vfz->buffer) {
315 vfz->bufferSize = BLOCK_SIZE;
316 vfz->buffer = malloc(BLOCK_SIZE);
317 }
318
319 while (bytesRead < size) {
320 if (vfz->offset < vfz->readSize) {
321 size_t diff = vfz->readSize - vfz->offset;
322 void* start = &((uint8_t*) vfz->buffer)[vfz->offset];
323 if (diff > size - bytesRead) {
324 diff = size - bytesRead;
325 }
326 if (buffer) {
327 void* bufferOffset = &((uint8_t*) buffer)[bytesRead];
328 memcpy(bufferOffset, start, diff);
329 }
330 vfz->offset += diff;
331 bytesRead += diff;
332 if (diff == size) {
333 break;
334 }
335 }
336 // offset == readSize
337 if (vfz->readSize == vfz->bufferSize) {
338 vfz->bufferSize *= 2;
339 if (vfz->bufferSize > vfz->fileSize) {
340 vfz->bufferSize = vfz->fileSize;
341 }
342 vfz->buffer = realloc(vfz->buffer, vfz->bufferSize);
343 }
344 if (vfz->readSize < vfz->bufferSize) {
345 void* start = &((uint8_t*) vfz->buffer)[vfz->readSize];
346 size_t toRead = vfz->bufferSize - vfz->readSize;
347 if (toRead > BLOCK_SIZE) {
348 toRead = BLOCK_SIZE;
349 }
350 ssize_t zipRead = zip_fread(vfz->zf, start, toRead);
351 if (zipRead < 0) {
352 if (bytesRead == 0) {
353 return -1;
354 }
355 break;
356 }
357 if (zipRead == 0) {
358 break;
359 }
360 vfz->readSize += zipRead;
361 } else {
362 break;
363 }
364 }
365 return bytesRead;
366}
367
368ssize_t _vfzWrite(struct VFile* vf, const void* buffer, size_t size) {
369 struct VFileZip* vfz = (struct VFileZip*) vf;
370
371 size_t bytesWritten = 0;
372 if (!vfz->buffer) {
373 vfz->bufferSize = toPow2(size);
374 vfz->buffer = malloc(vfz->bufferSize);
375 } else if (size > vfz->bufferSize || size > vfz->bufferSize - vfz->offset) {
376 vfz->bufferSize = toPow2(vfz->offset + size);
377 vfz->buffer = realloc(vfz->buffer, vfz->bufferSize);
378 }
379
380 void* start = &((uint8_t*) vfz->buffer)[vfz->offset];
381 if (buffer) {
382 memcpy(start, buffer, size);
383 } else {
384 memset(start, 0, size);
385 }
386 vfz->offset += size;
387 if (vfz->offset > vfz->writeSize) {
388 vfz->writeSize = vfz->offset;
389 }
390 bytesWritten += size;
391 return bytesWritten;
392}
393
394void* _vfzMap(struct VFile* vf, size_t size, int flags) {
395 struct VFileZip* vfz = (struct VFileZip*) vf;
396
397 UNUSED(flags);
398 if (size > vfz->readSize) {
399 vf->read(vf, 0, size - vfz->readSize);
400 }
401 return vfz->buffer;
402}
403
404void _vfzUnmap(struct VFile* vf, void* memory, size_t size) {
405 UNUSED(vf);
406 UNUSED(memory);
407 UNUSED(size);
408}
409
410void _vfzTruncate(struct VFile* vf, size_t size) {
411 // TODO
412 UNUSED(vf);
413 UNUSED(size);
414}
415
416ssize_t _vfzSize(struct VFile* vf) {
417 struct VFileZip* vfz = (struct VFileZip*) vf;
418 return vfz->fileSize;
419}
420
421bool _vdzClose(struct VDir* vd) {
422 struct VDirZip* vdz = (struct VDirZip*) vd;
423 if (zip_close(vdz->z) < 0) {
424 return false;
425 }
426 free(vdz);
427 return true;
428}
429
430void _vdzRewind(struct VDir* vd) {
431 struct VDirZip* vdz = (struct VDirZip*) vd;
432 vdz->dirent.index = -1;
433}
434
435struct VDirEntry* _vdzListNext(struct VDir* vd) {
436 struct VDirZip* vdz = (struct VDirZip*) vd;
437 zip_int64_t maxIndex = zip_get_num_entries(vdz->z, 0);
438 if (maxIndex <= vdz->dirent.index + 1) {
439 return 0;
440 }
441 ++vdz->dirent.index;
442 return &vdz->dirent.d;
443}
444
445struct VFile* _vdzOpenFile(struct VDir* vd, const char* path, int mode) {
446 UNUSED(mode);
447 // TODO: support truncating, appending and creating, and write
448 struct VDirZip* vdz = (struct VDirZip*) vd;
449
450 if ((mode & O_ACCMODE) == O_RDWR) {
451 // libzip doesn't allow for random access, so read/write is impossible without
452 // reading the entire file first. This approach will be supported eventually.
453 return 0;
454 }
455
456 struct zip_file* zf = NULL;
457 struct zip_stat s = {0};
458 if (mode & O_WRONLY) {
459 if (!vdz->write) {
460 return 0;
461 }
462 } else {
463 if (zip_stat(vdz->z, path, 0, &s) < 0) {
464 return 0;
465 }
466
467 zf = zip_fopen(vdz->z, path, 0);
468 if (!zf) {
469 return 0;
470 }
471 }
472
473 struct VFileZip* vfz = calloc(1, sizeof(struct VFileZip));
474 vfz->zf = zf;
475 vfz->z = vdz->z;
476 vfz->fileSize = s.size;
477 if (mode & O_WRONLY) {
478 vfz->name = strdup(path);
479 vfz->write = true;
480 }
481
482 vfz->d.close = _vfzClose;
483 vfz->d.seek = _vfzSeek;
484 vfz->d.read = _vfzRead;
485 vfz->d.readline = VFileReadline;
486 vfz->d.write = _vfzWrite;
487 vfz->d.map = _vfzMap;
488 vfz->d.unmap = _vfzUnmap;
489 vfz->d.truncate = _vfzTruncate;
490 vfz->d.size = _vfzSize;
491 vfz->d.sync = _vfzSync;
492
493 return &vfz->d;
494}
495
496struct VDir* _vdzOpenDir(struct VDir* vd, const char* path) {
497 UNUSED(vd);
498 UNUSED(path);
499 return 0;
500}
501
502bool _vdzDeleteFile(struct VDir* vd, const char* path) {
503 UNUSED(vd);
504 UNUSED(path);
505 // TODO
506 return false;
507}
508
509bool _vfzSync(struct VFile* vf, void* memory, size_t size) {
510 UNUSED(vf);
511 UNUSED(memory);
512 UNUSED(size);
513 return false;
514}
515
516const char* _vdezName(struct VDirEntry* vde) {
517 struct VDirEntryZip* vdez = (struct VDirEntryZip*) vde;
518 struct zip_stat s;
519 if (zip_stat_index(vdez->z, vdez->index, 0, &s) < 0) {
520 return 0;
521 }
522 return s.name;
523}
524
525static enum VFSType _vdezType(struct VDirEntry* vde) {
526 if (endswith(vde->name(vde), "/")) {
527 return VFS_DIRECTORY;
528 }
529 return VFS_FILE;
530}
531#else
532bool _vfzClose(struct VFile* vf) {
533 struct VFileZip* vfz = (struct VFileZip*) vf;
534 if (vfz->uz) {
535 unzCloseCurrentFile(vfz->uz);
536 }
537 if (vfz->z) {
538 zipCloseFileInZip(vfz->z);
539 }
540 if (vfz->buffer) {
541 mappedMemoryFree(vfz->buffer, vfz->bufferSize);
542 }
543 free(vfz);
544 return true;
545}
546
547off_t _vfzSeek(struct VFile* vf, off_t offset, int whence) {
548 struct VFileZip* vfz = (struct VFileZip*) vf;
549 if (!vfz->uz) {
550 return -1;
551 }
552
553 int64_t currentPos = unztell64(vfz->uz);
554 int64_t pos;
555 switch (whence) {
556 case SEEK_SET:
557 pos = 0;
558 break;
559 case SEEK_CUR:
560 pos = unztell64(vfz->uz);
561 break;
562 case SEEK_END:
563 pos = vfz->fileSize;
564 break;
565 default:
566 return -1;
567 }
568
569 if (pos < 0 || pos + offset < 0) {
570 return -1;
571 }
572 pos += offset;
573 if (currentPos > pos) {
574 unzCloseCurrentFile(vfz->uz);
575 unzOpenCurrentFile(vfz->uz);
576 currentPos = 0;
577 }
578 while (currentPos < pos) {
579 char tempBuf[1024];
580 ssize_t toRead = sizeof(tempBuf);
581 if (toRead > pos - currentPos) {
582 toRead = pos - currentPos;
583 }
584 ssize_t read = vf->read(vf, tempBuf, toRead);
585 if (read < toRead) {
586 return -1;
587 }
588 currentPos += read;
589 }
590
591 return unztell64(vfz->uz);
592}
593
594ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t size) {
595 struct VFileZip* vfz = (struct VFileZip*) vf;
596 return unzReadCurrentFile(vfz->uz, buffer, size);
597}
598
599ssize_t _vfzWrite(struct VFile* vf, const void* buffer, size_t size) {
600 struct VFileZip* vfz = (struct VFileZip*) vf;
601 return zipWriteInFileInZip(vfz->z, buffer, size);
602}
603
604void* _vfzMap(struct VFile* vf, size_t size, int flags) {
605 struct VFileZip* vfz = (struct VFileZip*) vf;
606
607 // TODO
608 UNUSED(flags);
609
610 off_t pos = vf->seek(vf, 0, SEEK_CUR);
611 if (pos < 0) {
612 return 0;
613 }
614
615 vfz->buffer = anonymousMemoryMap(size);
616 if (!vfz->buffer) {
617 return 0;
618 }
619
620 unzCloseCurrentFile(vfz->uz);
621 unzOpenCurrentFile(vfz->uz);
622 vf->read(vf, vfz->buffer, size);
623 unzCloseCurrentFile(vfz->uz);
624 unzOpenCurrentFile(vfz->uz);
625 vf->seek(vf, pos, SEEK_SET);
626
627 vfz->bufferSize = size;
628
629 return vfz->buffer;
630}
631
632void _vfzUnmap(struct VFile* vf, void* memory, size_t size) {
633 struct VFileZip* vfz = (struct VFileZip*) vf;
634
635 if (memory != vfz->buffer) {
636 return;
637 }
638
639 mappedMemoryFree(vfz->buffer, size);
640 vfz->buffer = 0;
641}
642
643void _vfzTruncate(struct VFile* vf, size_t size) {
644 // TODO
645 UNUSED(vf);
646 UNUSED(size);
647}
648
649ssize_t _vfzSize(struct VFile* vf) {
650 struct VFileZip* vfz = (struct VFileZip*) vf;
651 return vfz->fileSize;
652}
653
654bool _vdzClose(struct VDir* vd) {
655 struct VDirZip* vdz = (struct VDirZip*) vd;
656 if (vdz->uz && unzClose(vdz->uz) < 0) {
657 return false;
658 }
659 if (vdz->z && zipClose(vdz->z, NULL) < 0) {
660 return false;
661 }
662 free(vdz);
663 return true;
664}
665
666void _vdzRewind(struct VDir* vd) {
667 struct VDirZip* vdz = (struct VDirZip*) vd;
668 vdz->atStart = unzGoToFirstFile(vdz->uz) == UNZ_OK;
669}
670
671struct VDirEntry* _vdzListNext(struct VDir* vd) {
672 struct VDirZip* vdz = (struct VDirZip*) vd;
673 if (!vdz->atStart) {
674 if (unzGoToNextFile(vdz->uz) == UNZ_END_OF_LIST_OF_FILE) {
675 return 0;
676 }
677 } else {
678 vdz->atStart = false;
679 }
680 unz_file_info64 info;
681 int status = unzGetCurrentFileInfo64(vdz->uz, &info, vdz->dirent.name, sizeof(vdz->dirent.name), 0, 0, 0, 0);
682 if (status < 0) {
683 return 0;
684 }
685 vdz->dirent.fileSize = info.uncompressed_size;
686 return &vdz->dirent.d;
687}
688
689struct VFile* _vdzOpenFile(struct VDir* vd, const char* path, int mode) {
690 UNUSED(mode);
691 struct VDirZip* vdz = (struct VDirZip*) vd;
692
693 if ((mode & O_ACCMODE) == O_RDWR) {
694 // minizip implementation only supports read or write
695 return 0;
696 }
697
698 unz_file_info64 info = {0};
699 if (mode & O_RDONLY) {
700 if (unzLocateFile(vdz->uz, path, 0) != UNZ_OK) {
701 return 0;
702 }
703
704 if (unzOpenCurrentFile(vdz->uz) < 0) {
705 return 0;
706 }
707
708 int status = unzGetCurrentFileInfo64(vdz->uz, &info, 0, 0, 0, 0, 0, 0);
709 if (status < 0) {
710 return 0;
711 }
712 }
713 if (mode & O_WRONLY) {
714 if (zipOpenNewFileInZip(vdz->z, path, NULL, NULL, 0, NULL, 0, NULL, Z_DEFLATED, 3) < 0) {
715 return 0;
716 }
717 }
718
719 struct VFileZip* vfz = malloc(sizeof(struct VFileZip));
720 vfz->uz = vdz->uz;
721 vfz->z = vdz->z;
722 vfz->buffer = 0;
723 vfz->bufferSize = 0;
724 vfz->fileSize = info.uncompressed_size;
725
726 vfz->d.close = _vfzClose;
727 vfz->d.seek = _vfzSeek;
728 vfz->d.read = _vfzRead;
729 vfz->d.readline = VFileReadline;
730 vfz->d.write = _vfzWrite;
731 vfz->d.map = _vfzMap;
732 vfz->d.unmap = _vfzUnmap;
733 vfz->d.truncate = _vfzTruncate;
734 vfz->d.size = _vfzSize;
735 vfz->d.sync = _vfzSync;
736
737 return &vfz->d;
738}
739
740struct VDir* _vdzOpenDir(struct VDir* vd, const char* path) {
741 UNUSED(vd);
742 UNUSED(path);
743 return 0;
744}
745
746bool _vdzDeleteFile(struct VDir* vd, const char* path) {
747 UNUSED(vd);
748 UNUSED(path);
749 // TODO
750 return false;
751}
752
753bool _vfzSync(struct VFile* vf, void* memory, size_t size) {
754 UNUSED(vf);
755 UNUSED(memory);
756 UNUSED(size);
757 return false;
758}
759
760const char* _vdezName(struct VDirEntry* vde) {
761 struct VDirEntryZip* vdez = (struct VDirEntryZip*) vde;
762 return vdez->name;
763}
764
765static enum VFSType _vdezType(struct VDirEntry* vde) {
766 struct VDirEntryZip* vdez = (struct VDirEntryZip*) vde;
767 if (endswith(vdez->name, "/")) {
768 return VFS_DIRECTORY;
769 }
770 return VFS_FILE;
771}
772#endif