all repos — mgba @ 08b0a7c60f9129c06314d2dc9de3eb6eadbb2b15

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