2 * LZMA support routines for PhysicsFS.
4 * Please see the file LICENSE.txt in the source's root directory.
6 * This file is written by Dennis Schridde, with some peeking at "7zMain.c"
10 #if (defined PHYSFS_SUPPORTS_7Z)
17 #define __PHYSICSFS_INTERNAL__
18 #include "physfs_internal.h"
22 /* Use callback for input data */
25 /* #define _LZMA_OUT_READ */
26 /* Use read function for output data */
30 /* It can increase speed on some 32-bit CPUs,
31 but memory usage will be doubled in that case */
34 #ifndef _LZMA_SYSTEM_SIZE_T
35 #define _LZMA_SYSTEM_SIZE_T
36 /* Use system's size_t. You can use it to enable 64-bit sizes supporting */
41 #include "7zExtract.h"
44 /* 7z internal from 7zIn.c */
45 int TestSignatureCandidate(Byte *testBytes);
48 typedef struct _CFileInStream
55 * In LZMA the archive is splited in blocks, those are called folders
58 typedef struct _LZMAfolder
60 PHYSFS_uint8 *cache; /* Cached folder */
61 PHYSFS_uint32 size; /* Size of folder */
62 PHYSFS_uint32 index; /* Index of folder in archive */
63 PHYSFS_uint32 references; /* Number of files using this block */
67 * Set by LZMA_openArchive(), except folder which gets it's values
70 typedef struct _LZMAarchive
72 struct _LZMAentry *firstEntry; /* Used for cleanup on shutdown */
73 struct _LZMAentry *lastEntry;
74 LZMAfolder *folder; /* Array of folders */
75 CArchiveDatabaseEx db; /* For 7z: Database */
76 CFileInStream stream; /* For 7z: Input file incl. read and seek callbacks */
79 /* Set by LZMA_openRead(), except offset which is set by LZMA_read() */
80 typedef struct _LZMAentry
82 struct _LZMAentry *next; /* Used for cleanup on shutdown */
83 struct _LZMAentry *previous;
84 LZMAarchive *archive; /* Link to corresponding archive */
85 CFileItem *file; /* For 7z: File info, eg. name, size */
86 PHYSFS_uint32 fileIndex; /* Index of file in archive */
87 PHYSFS_uint32 folderIndex; /* Index of folder in archive */
88 size_t offset; /* Offset in folder */
89 PHYSFS_uint64 position; /* Current "virtual" position in file */
93 /* Memory management implementations to be passed to 7z */
95 static void *SzAllocPhysicsFS(size_t size)
97 return ((size == 0) ? NULL : allocator.Malloc(size));
98 } /* SzAllocPhysicsFS */
101 static void SzFreePhysicsFS(void *address)
104 allocator.Free(address);
105 } /* SzFreePhysicsFS */
108 /* Filesystem implementations to be passed to 7z */
112 #define kBufferSize (1 << 12)
113 static Byte g_Buffer[kBufferSize]; /* !!! FIXME: not thread safe! */
115 SZ_RESULT SzFileReadImp(void *object, void **buffer, size_t maxReqSize,
116 size_t *processedSize)
118 CFileInStream *s = (CFileInStream *)object;
119 PHYSFS_sint64 processedSizeLoc;
120 if (maxReqSize > kBufferSize)
121 maxReqSize = kBufferSize;
122 processedSizeLoc = __PHYSFS_platformRead(s->File, g_Buffer, 1, maxReqSize);
124 if (processedSize != NULL)
125 *processedSize = (size_t) processedSizeLoc;
127 } /* SzFileReadImp */
131 SZ_RESULT SzFileReadImp(void *object, void *buffer, size_t size,
132 size_t *processedSize)
134 CFileInStream *s = (CFileInStream *)object;
135 size_t processedSizeLoc = __PHYSFS_platformRead(s->File, buffer, 1, size);
136 if (processedSize != 0)
137 *processedSize = processedSizeLoc;
139 } /* SzFileReadImp */
143 SZ_RESULT SzFileSeekImp(void *object, CFileSize pos)
145 CFileInStream *s = (CFileInStream *) object;
146 if (__PHYSFS_platformSeek(s->File, (PHYSFS_uint64) pos))
149 } /* SzFileSeekImp */
153 * Find entry 'name' in 'archive' and report the 'index' back
155 static int lzma_find_entry(LZMAarchive *archive, const char *name,
156 PHYSFS_uint32 *index)
158 for (*index = 0; *index < archive->db.Database.NumFiles; (*index)++)
160 if (strcmp(archive->db.Database.Files[*index].Name, name) == 0)
164 BAIL_MACRO(ERR_NO_SUCH_FILE, 0);
165 } /* lzma_find_entry */
169 * Report the first file index of a directory
171 static PHYSFS_sint32 lzma_find_start_of_dir(LZMAarchive *archive,
173 int stop_on_first_find)
175 PHYSFS_sint32 lo = 0;
176 PHYSFS_sint32 hi = (PHYSFS_sint32) (archive->db.Database.NumFiles - 1);
177 PHYSFS_sint32 middle;
178 PHYSFS_uint32 dlen = strlen(path);
179 PHYSFS_sint32 retval = -1;
183 if (*path == '\0') /* root dir? */
186 if ((dlen > 0) && (path[dlen - 1] == '/')) /* ignore trailing slash. */
191 middle = lo + ((hi - lo) / 2);
192 name = archive->db.Database.Files[middle].Name;
193 rc = strncmp(path, name, dlen);
196 char ch = name[dlen];
197 if ('/' < ch) /* make sure this isn't just a substr match. */
203 if (stop_on_first_find) /* Just checking dir's existance? */
206 if (name[dlen + 1] == '\0') /* Skip initial dir entry. */
209 /* there might be more entries earlier in the list. */
222 } /* lzma_find_start_of_dir */
226 * Wrap all 7z calls in this, so the physfs error state is set appropriately.
228 static int lzma_err(SZ_RESULT rc)
232 case SZ_OK: /* Same as LZMA_RESULT_OK */
234 case SZE_DATA_ERROR: /* Same as LZMA_RESULT_DATA_ERROR */
235 __PHYSFS_setError(ERR_DATA_ERROR);
237 case SZE_OUTOFMEMORY:
238 __PHYSFS_setError(ERR_OUT_OF_MEMORY);
241 __PHYSFS_setError(ERR_CORRUPTED);
244 __PHYSFS_setError(ERR_NOT_IMPLEMENTED);
247 __PHYSFS_setError(ERR_UNKNOWN_ERROR); /* !!! FIXME: right? */
249 case SZE_ARCHIVE_ERROR:
250 __PHYSFS_setError(ERR_CORRUPTED); /* !!! FIXME: right? */
253 __PHYSFS_setError(ERR_UNKNOWN_ERROR);
260 static PHYSFS_sint64 LZMA_read(fvoid *opaque, void *outBuffer,
261 PHYSFS_uint32 objSize, PHYSFS_uint32 objCount)
263 LZMAentry *entry = (LZMAentry *) opaque;
265 PHYSFS_sint64 wantedSize = objSize*objCount;
266 PHYSFS_sint64 remainingSize = entry->file->Size - entry->position;
270 ISzAlloc allocTempImp;
272 BAIL_IF_MACRO(wantedSize == 0, NULL, 0); /* quick rejection. */
273 BAIL_IF_MACRO(remainingSize == 0, ERR_PAST_EOF, 0);
275 if (remainingSize < wantedSize)
277 wantedSize = remainingSize - (remainingSize % objSize);
278 objCount = (PHYSFS_uint32) (remainingSize / objSize);
279 BAIL_IF_MACRO(objCount == 0, ERR_PAST_EOF, 0); /* quick rejection. */
280 __PHYSFS_setError(ERR_PAST_EOF); /* this is always true here. */
283 /* Prepare callbacks for 7z */
284 allocImp.Alloc = SzAllocPhysicsFS;
285 allocImp.Free = SzFreePhysicsFS;
287 allocTempImp.Alloc = SzAllocPhysicsFS;
288 allocTempImp.Free = SzFreePhysicsFS;
290 /* Only decompress the folder if it is not allready cached */
291 if (entry->archive->folder[entry->folderIndex].cache == NULL)
293 size_t tmpsize = entry->archive->folder[entry->folderIndex].size;
294 int rc = lzma_err(SzExtract(
295 &entry->archive->stream.InStream, /* compressed data */
298 /* Index of cached folder, will be changed by SzExtract */
299 &entry->archive->folder[entry->folderIndex].index,
300 /* Cache for decompressed folder, allocated/freed by SzExtract */
301 &entry->archive->folder[entry->folderIndex].cache,
302 /* Size of cache, will be changed by SzExtract */
304 /* Offset of this file inside the cache, set by SzExtract */
306 &fileSize, /* Size of this file */
310 entry->archive->folder[entry->folderIndex].size = tmpsize;
315 /* Copy wanted bytes over from cache to outBuffer */
316 /* !!! FIXME: strncpy for non-string data? */
318 (void*) (entry->archive->folder[entry->folderIndex].cache +
319 entry->offset + entry->position),
320 (size_t) wantedSize);
321 entry->position += wantedSize;
326 static PHYSFS_sint64 LZMA_write(fvoid *opaque, const void *buf,
327 PHYSFS_uint32 objSize, PHYSFS_uint32 objCount)
329 BAIL_MACRO(ERR_NOT_SUPPORTED, -1);
333 static int LZMA_eof(fvoid *opaque)
335 LZMAentry *entry = (LZMAentry *) opaque;
336 return (entry->position >= entry->file->Size);
340 static PHYSFS_sint64 LZMA_tell(fvoid *opaque)
342 LZMAentry *entry = (LZMAentry *) opaque;
343 return (entry->position);
347 static int LZMA_seek(fvoid *opaque, PHYSFS_uint64 offset)
349 LZMAentry *entry = (LZMAentry *) opaque;
351 BAIL_IF_MACRO(offset < 0, ERR_SEEK_OUT_OF_RANGE, 0);
352 BAIL_IF_MACRO(offset > entry->file->Size, ERR_PAST_EOF, 0);
354 entry->position = offset;
359 static PHYSFS_sint64 LZMA_fileLength(fvoid *opaque)
361 LZMAentry *entry = (LZMAentry *) opaque;
362 return (entry->file->Size);
363 } /* LZMA_fileLength */
366 static int LZMA_fileClose(fvoid *opaque)
368 LZMAentry *entry = (LZMAentry *) opaque;
371 if (entry == entry->archive->firstEntry)
372 entry->archive->firstEntry = entry->next;
373 if (entry == entry->archive->lastEntry)
374 entry->archive->lastEntry = entry->previous;
377 if (entry->previous != NULL)
378 entry->previous->next = entry->next;
379 if (entry->next != NULL)
380 entry->next->previous = entry->previous;
382 entry->archive->folder[entry->folderIndex].references--;
383 if (entry->archive->folder[entry->folderIndex].references == 0)
385 allocator.Free(entry->archive->folder[entry->folderIndex].cache);
386 entry->archive->folder[entry->folderIndex].cache = NULL;
389 allocator.Free(entry);
393 } /* LZMA_fileClose */
396 static int LZMA_isArchive(const char *filename, int forWriting)
398 PHYSFS_uint8 sig[k7zSignatureSize];
402 BAIL_IF_MACRO(forWriting, ERR_ARC_IS_READ_ONLY, 0);
404 in = __PHYSFS_platformOpenRead(filename);
405 BAIL_IF_MACRO(in == NULL, NULL, 0);
407 if (__PHYSFS_platformRead(in, sig, k7zSignatureSize, 1) != 1)
410 /* Test whether sig is the 7z signature */
411 res = TestSignatureCandidate(sig);
413 __PHYSFS_platformClose(in);
416 } /* LZMA_isArchive */
419 static void *LZMA_openArchive(const char *name, int forWriting)
422 LZMAarchive *archive = NULL;
424 ISzAlloc allocTempImp;
426 BAIL_IF_MACRO(forWriting, ERR_ARC_IS_READ_ONLY, NULL);
427 BAIL_IF_MACRO(!LZMA_isArchive(name,forWriting), ERR_UNSUPPORTED_ARCHIVE, 0);
429 archive = (LZMAarchive *) allocator.Malloc(sizeof (LZMAarchive));
430 BAIL_IF_MACRO(archive == NULL, ERR_OUT_OF_MEMORY, NULL);
432 archive->firstEntry = NULL;
433 archive->lastEntry = NULL;
435 if ((archive->stream.File = __PHYSFS_platformOpenRead(name)) == NULL)
437 allocator.Free(archive);
441 /* Prepare structs for 7z */
442 archive->stream.InStream.Read = SzFileReadImp;
443 archive->stream.InStream.Seek = SzFileSeekImp;
445 allocImp.Alloc = SzAllocPhysicsFS;
446 allocImp.Free = SzFreePhysicsFS;
448 allocTempImp.Alloc = SzAllocPhysicsFS;
449 allocTempImp.Free = SzFreePhysicsFS;
452 SzArDbExInit(&archive->db);
453 if (lzma_err(SzArchiveOpen(&archive->stream.InStream, &archive->db,
454 &allocImp, &allocTempImp)) != SZ_OK)
456 __PHYSFS_platformClose(archive->stream.File);
457 allocator.Free(archive);
461 len = archive->db.Database.NumFolders * sizeof (LZMAfolder);
462 archive->folder = (LZMAfolder *) allocator.Malloc(len);
463 BAIL_IF_MACRO(archive->folder == NULL, ERR_OUT_OF_MEMORY, NULL);
466 * Init with 0 so we know when a folder is already cached
467 * Values will be set by LZMA_read()
469 memset(archive->folder, 0, (size_t) len);
472 } /* LZMA_openArchive */
476 * Moved to seperate function so we can use alloca then immediately throw
477 * away the allocated stack space...
479 static void doEnumCallback(PHYSFS_EnumFilesCallback cb, void *callbackdata,
480 const char *odir, const char *str, PHYSFS_sint32 ln)
482 char *newstr = __PHYSFS_smallAlloc(ln + 1);
486 memcpy(newstr, str, ln);
488 cb(callbackdata, odir, newstr);
489 __PHYSFS_smallFree(newstr);
490 } /* doEnumCallback */
493 static void LZMA_enumerateFiles(dvoid *opaque, const char *dname,
494 int omitSymLinks, PHYSFS_EnumFilesCallback cb,
495 const char *origdir, void *callbackdata)
497 LZMAarchive *archive = (LZMAarchive *) opaque;
499 PHYSFS_sint32 dlen_inc;
503 i = lzma_find_start_of_dir(archive, dname, 0);
504 if (i == -1) /* no such directory. */
507 dlen = strlen(dname);
508 if ((dlen > 0) && (dname[dlen - 1] == '/')) /* ignore trailing slash. */
511 dlen_inc = ((dlen > 0) ? 1 : 0) + dlen;
512 max = (PHYSFS_sint32) archive->db.Database.NumFiles;
518 char *e = archive->db.Database.Files[i].Name;
519 if ((dlen) && ((strncmp(e, dname, dlen)) || (e[dlen] != '/')))
520 break; /* past end of this dir; we're done. */
523 ptr = strchr(add, '/');
524 ln = (PHYSFS_sint32) ((ptr) ? ptr-add : strlen(add));
525 doEnumCallback(cb, callbackdata, origdir, add, ln);
526 ln += dlen_inc; /* point past entry to children... */
528 /* increment counter and skip children of subdirs... */
529 while ((++i < max) && (ptr != NULL))
531 char *e_new = archive->db.Database.Files[i].Name;
532 if ((strncmp(e, e_new, ln) != 0) || (e_new[ln] != '/'))
536 } /* LZMA_enumerateFiles */
539 static int LZMA_exists(dvoid *opaque, const char *name)
541 LZMAarchive *archive = (LZMAarchive *) opaque;
542 PHYSFS_uint32 index = 0;
543 return(lzma_find_entry(archive, name, &index));
547 static PHYSFS_sint64 LZMA_getLastModTime(dvoid *opaque,
551 /* !!! FIXME: Lacking support in the LZMA C SDK. */
552 BAIL_MACRO(ERR_NOT_IMPLEMENTED, -1);
553 } /* LZMA_getLastModTime */
556 static int LZMA_isDirectory(dvoid *opaque, const char *name, int *fileExists)
558 LZMAarchive *archive = (LZMAarchive *) opaque;
559 PHYSFS_uint32 index = 0;
561 *fileExists = lzma_find_entry(archive, name, &index);
563 return(archive->db.Database.Files[index].IsDirectory);
564 } /* LZMA_isDirectory */
567 static int LZMA_isSymLink(dvoid *opaque, const char *name, int *fileExists)
569 BAIL_MACRO(ERR_NOT_SUPPORTED, 0);
570 } /* LZMA_isSymLink */
573 static fvoid *LZMA_openRead(dvoid *opaque, const char *name, int *fileExists)
575 LZMAarchive *archive = (LZMAarchive *) opaque;
576 LZMAentry *entry = NULL;
577 PHYSFS_uint32 fileIndex = 0;
578 PHYSFS_uint32 folderIndex = 0;
580 *fileExists = lzma_find_entry(archive, name, &fileIndex);
581 BAIL_IF_MACRO(!*fileExists, ERR_NO_SUCH_FILE, NULL);
583 folderIndex = archive->db.FileIndexToFolderIndexMap[fileIndex];
584 BAIL_IF_MACRO(folderIndex == (PHYSFS_uint32)-1, ERR_UNKNOWN_ERROR, NULL);
586 entry = (LZMAentry *) allocator.Malloc(sizeof (LZMAentry));
587 BAIL_IF_MACRO(entry == NULL, ERR_OUT_OF_MEMORY, NULL);
589 entry->fileIndex = fileIndex;
590 entry->folderIndex = folderIndex;
591 entry->archive = archive;
592 entry->file = archive->db.Database.Files + entry->fileIndex;
593 entry->offset = 0; /* Offset will be set by LZMA_read() */
596 archive->folder[folderIndex].references++;
599 entry->previous = entry->archive->lastEntry;
600 if (entry->previous != NULL)
601 entry->previous->next = entry;
602 entry->archive->lastEntry = entry;
603 if (entry->archive->firstEntry == NULL)
604 entry->archive->firstEntry = entry;
607 } /* LZMA_openRead */
610 static fvoid *LZMA_openWrite(dvoid *opaque, const char *filename)
612 BAIL_MACRO(ERR_NOT_SUPPORTED, NULL);
613 } /* LZMA_openWrite */
616 static fvoid *LZMA_openAppend(dvoid *opaque, const char *filename)
618 BAIL_MACRO(ERR_NOT_SUPPORTED, NULL);
619 } /* LZMA_openAppend */
622 static void LZMA_dirClose(dvoid *opaque)
624 LZMAarchive *archive = (LZMAarchive *) opaque;
625 LZMAentry *entry = archive->firstEntry;
626 LZMAentry *tmpEntry = entry;
628 while (entry != NULL)
630 tmpEntry = entry->next;
631 LZMA_fileClose(entry);
635 SzArDbExFree(&archive->db, SzFreePhysicsFS);
636 __PHYSFS_platformClose(archive->stream.File);
638 /* Free the cache which might have been allocated by LZMA_read() */
639 allocator.Free(archive->folder);
640 allocator.Free(archive);
641 } /* LZMA_dirClose */
644 static int LZMA_remove(dvoid *opaque, const char *name)
646 BAIL_MACRO(ERR_NOT_SUPPORTED, 0);
650 static int LZMA_mkdir(dvoid *opaque, const char *name)
652 BAIL_MACRO(ERR_NOT_SUPPORTED, 0);
656 const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_LZMA =
659 LZMA_ARCHIVE_DESCRIPTION,
660 "Dennis Schridde <devurandom@gmx.net>",
661 "http://icculus.org/physfs/",
665 const PHYSFS_Archiver __PHYSFS_Archiver_LZMA =
667 &__PHYSFS_ArchiveInfo_LZMA,
668 LZMA_isArchive, /* isArchive() method */
669 LZMA_openArchive, /* openArchive() method */
670 LZMA_enumerateFiles, /* enumerateFiles() method */
671 LZMA_exists, /* exists() method */
672 LZMA_isDirectory, /* isDirectory() method */
673 LZMA_isSymLink, /* isSymLink() method */
674 LZMA_getLastModTime, /* getLastModTime() method */
675 LZMA_openRead, /* openRead() method */
676 LZMA_openWrite, /* openWrite() method */
677 LZMA_openAppend, /* openAppend() method */
678 LZMA_remove, /* remove() method */
679 LZMA_mkdir, /* mkdir() method */
680 LZMA_dirClose, /* dirClose() method */
681 LZMA_read, /* read() method */
682 LZMA_write, /* write() method */
683 LZMA_eof, /* eof() method */
684 LZMA_tell, /* tell() method */
685 LZMA_seek, /* seek() method */
686 LZMA_fileLength, /* fileLength() method */
687 LZMA_fileClose /* fileClose() method */
690 #endif /* defined PHYSFS_SUPPORTS_7Z */
692 /* end of lzma.c ... */