all repos — mgba @ 7d672a2215643d205e0edad39e1903e49edd70d0

mGBA Game Boy Advance Emulator

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