all repos — mgba @ ba0b7d9157aa1120f3b8340bc139d92772623a4f

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#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