all repos — mgba @ 7652fe9f7ae13b4e3d5b29e0ebf869d7503b22cc

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