1/*
2 * 7zip support routines for PhysicsFS.
3 *
4 * Please see the file LICENSE.txt in the source's root directory.
5 *
6 * This file was written by Ryan C. Gordon.
7 */
8
9#define __PHYSICSFS_INTERNAL__
10#include "physfs_internal.h"
11
12#if PHYSFS_SUPPORTS_7Z
13
14#include "physfs_lzmasdk.h"
15
16typedef struct
17{
18 ISeekInStream seekStream; /* lzma sdk i/o interface (lower level). */
19 PHYSFS_Io *io; /* physfs i/o interface for this archive. */
20 CLookToRead lookStream; /* lzma sdk i/o interface (higher level). */
21} SZIPLookToRead;
22
23/* One SZIPentry is kept for each file in an open 7zip archive. */
24typedef struct
25{
26 __PHYSFS_DirTreeEntry tree; /* manages directory tree */
27 PHYSFS_uint32 dbidx; /* index into lzma sdk database */
28} SZIPentry;
29
30/* One SZIPinfo is kept for each open 7zip archive. */
31typedef struct
32{
33 __PHYSFS_DirTree tree; /* manages directory tree. */
34 PHYSFS_Io *io; /* physfs i/o interface for this archive. */
35 CSzArEx db; /* lzma sdk archive database object. */
36} SZIPinfo;
37
38
39static PHYSFS_ErrorCode szipErrorCode(const SRes rc)
40{
41 switch (rc)
42 {
43 case SZ_OK: return PHYSFS_ERR_OK;
44 case SZ_ERROR_DATA: return PHYSFS_ERR_CORRUPT;
45 case SZ_ERROR_MEM: return PHYSFS_ERR_OUT_OF_MEMORY;
46 case SZ_ERROR_CRC: return PHYSFS_ERR_CORRUPT;
47 case SZ_ERROR_UNSUPPORTED: return PHYSFS_ERR_UNSUPPORTED;
48 case SZ_ERROR_INPUT_EOF: return PHYSFS_ERR_CORRUPT;
49 case SZ_ERROR_OUTPUT_EOF: return PHYSFS_ERR_IO;
50 case SZ_ERROR_READ: return PHYSFS_ERR_IO;
51 case SZ_ERROR_WRITE: return PHYSFS_ERR_IO;
52 case SZ_ERROR_ARCHIVE: return PHYSFS_ERR_CORRUPT;
53 case SZ_ERROR_NO_ARCHIVE: return PHYSFS_ERR_UNSUPPORTED;
54 default: break;
55 } /* switch */
56
57 return PHYSFS_ERR_OTHER_ERROR;
58} /* szipErrorCode */
59
60
61/* LZMA SDK's ISzAlloc interface ... */
62
63static void *SZIP_ISzAlloc_Alloc(void *p, size_t size)
64{
65 return allocator.Malloc(size ? size : 1);
66} /* SZIP_ISzAlloc_Alloc */
67
68static void SZIP_ISzAlloc_Free(void *p, void *address)
69{
70 if (address)
71 allocator.Free(address);
72} /* SZIP_ISzAlloc_Free */
73
74static ISzAlloc SZIP_SzAlloc = {
75 SZIP_ISzAlloc_Alloc, SZIP_ISzAlloc_Free
76};
77
78
79/* we implement ISeekInStream, and then wrap that in LZMA SDK's CLookToRead,
80 which implements the higher-level ILookInStream on top of that, handling
81 buffering and such for us. */
82
83/* LZMA SDK's ISeekInStream interface ... */
84
85static SRes SZIP_ISeekInStream_Read(void *p, void *buf, size_t *size)
86{
87 SZIPLookToRead *stream = (SZIPLookToRead *) p;
88 PHYSFS_Io *io = stream->io;
89 const PHYSFS_uint64 len = (PHYSFS_uint64) *size;
90 const PHYSFS_sint64 rc = (len == 0) ? 0 : io->read(io, buf, len);
91
92 if (rc < 0)
93 {
94 *size = 0;
95 return SZ_ERROR_READ;
96 } /* if */
97
98 *size = (size_t) rc;
99 return SZ_OK;
100} /* SZIP_ISeekInStream_Read */
101
102static SRes SZIP_ISeekInStream_Seek(void *p, Int64 *pos, ESzSeek origin)
103{
104 SZIPLookToRead *stream = (SZIPLookToRead *) p;
105 PHYSFS_Io *io = stream->io;
106 PHYSFS_sint64 base;
107 PHYSFS_uint64 newpos;
108
109 switch (origin)
110 {
111 case SZ_SEEK_SET:
112 base = 0;
113 break;
114
115 case SZ_SEEK_CUR:
116 base = io->tell(io);
117 break;
118
119 case SZ_SEEK_END:
120 base = io->length(io);
121 break;
122
123 default:
124 return SZ_ERROR_FAIL;
125 } /* switch */
126
127 if (base < 0)
128 return SZ_ERROR_FAIL;
129 else if ((*pos < 0) && (((Int64) base) < -*pos))
130 return SZ_ERROR_FAIL;
131
132 newpos = (PHYSFS_uint64) (((Int64) base) + *pos);
133 if (!io->seek(io, newpos))
134 return SZ_ERROR_FAIL;
135
136 *pos = (Int64) newpos;
137 return SZ_OK;
138} /* SZIP_ISeekInStream_Seek */
139
140
141static void szipInitStream(SZIPLookToRead *stream, PHYSFS_Io *io)
142{
143 stream->seekStream.Read = SZIP_ISeekInStream_Read;
144 stream->seekStream.Seek = SZIP_ISeekInStream_Seek;
145
146 stream->io = io;
147
148 /* !!! FIXME: can we use lookahead? Is there value to it? */
149 LookToRead_Init(&stream->lookStream);
150 LookToRead_CreateVTable(&stream->lookStream, False);
151 stream->lookStream.realStream = &stream->seekStream;
152} /* szipInitStream */
153
154
155/* Do this in a separate function so we can smallAlloc without looping. */
156static int szipLoadEntry(SZIPinfo *info, const PHYSFS_uint32 idx)
157{
158 const size_t utf16len = SzArEx_GetFileNameUtf16(&info->db, idx, NULL);
159 const size_t utf16buflen = utf16len * 2;
160 PHYSFS_uint16 *utf16 = (PHYSFS_uint16 *) __PHYSFS_smallAlloc(utf16buflen);
161 const size_t utf8buflen = utf16len * 4;
162 char *utf8 = (char *) __PHYSFS_smallAlloc(utf8buflen);
163 int retval = 0;
164
165 if (utf16 && utf8)
166 {
167 const int isdir = SzArEx_IsDir(&info->db, idx) != 0;
168 SZIPentry *entry;
169 SzArEx_GetFileNameUtf16(&info->db, idx, (UInt16 *) utf16);
170 PHYSFS_utf8FromUtf16(utf16, utf8, utf8buflen);
171 entry = (SZIPentry*) __PHYSFS_DirTreeAdd(&info->tree, utf8, isdir);
172 retval = (entry != NULL);
173 if (retval)
174 entry->dbidx = idx;
175 } /* if */
176
177 __PHYSFS_smallFree(utf8);
178 __PHYSFS_smallFree(utf16);
179
180 return retval;
181} /* szipLoadEntry */
182
183
184static int szipLoadEntries(SZIPinfo *info)
185{
186 int retval = 0;
187
188 if (__PHYSFS_DirTreeInit(&info->tree, sizeof (SZIPentry), 1, 0))
189 {
190 const PHYSFS_uint32 count = info->db.NumFiles;
191 PHYSFS_uint32 i;
192 for (i = 0; i < count; i++)
193 BAIL_IF_ERRPASS(!szipLoadEntry(info, i), 0);
194 retval = 1;
195 } /* if */
196
197 return retval;
198} /* szipLoadEntries */
199
200
201static void SZIP_closeArchive(void *opaque)
202{
203 SZIPinfo *info = (SZIPinfo *) opaque;
204 if (info)
205 {
206 if (info->io)
207 info->io->destroy(info->io);
208 SzArEx_Free(&info->db, &SZIP_SzAlloc);
209 __PHYSFS_DirTreeDeinit(&info->tree);
210 allocator.Free(info);
211 } /* if */
212} /* SZIP_closeArchive */
213
214
215static void *SZIP_openArchive(PHYSFS_Io *io, const char *name,
216 int forWriting, int *claimed)
217{
218 static const PHYSFS_uint8 wantedsig[] = { '7','z',0xBC,0xAF,0x27,0x1C };
219 SZIPLookToRead stream;
220 ISzAlloc *alloc = &SZIP_SzAlloc;
221 SZIPinfo *info = NULL;
222 SRes rc;
223 PHYSFS_uint8 sig[6];
224 PHYSFS_sint64 pos;
225
226 BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL);
227 pos = io->tell(io);
228 BAIL_IF_ERRPASS(pos == -1, NULL);
229 BAIL_IF_ERRPASS(io->read(io, sig, 6) != 6, NULL);
230 *claimed = (memcmp(sig, wantedsig, 6) == 0);
231 BAIL_IF_ERRPASS(!io->seek(io, pos), NULL);
232
233 info = (SZIPinfo *) allocator.Malloc(sizeof (SZIPinfo));
234 BAIL_IF(!info, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
235 memset(info, '\0', sizeof (*info));
236
237 SzArEx_Init(&info->db);
238
239 info->io = io;
240
241 szipInitStream(&stream, io);
242 rc = SzArEx_Open(&info->db, &stream.lookStream.s, alloc, alloc);
243 GOTO_IF(rc != SZ_OK, szipErrorCode(rc), failed);
244
245 GOTO_IF_ERRPASS(!szipLoadEntries(info), failed);
246
247 return info;
248
249failed:
250 info->io = NULL; /* don't let cleanup destroy the PHYSFS_Io. */
251 SZIP_closeArchive(info);
252 return NULL;
253} /* SZIP_openArchive */
254
255
256static PHYSFS_Io *SZIP_openRead(void *opaque, const char *path)
257{
258 /* !!! FIXME: the current lzma sdk C API only allows you to decompress
259 !!! FIXME: the entire file at once, which isn't ideal. Fix this in the
260 !!! FIXME: SDK and then convert this all to a streaming interface. */
261
262 SZIPinfo *info = (SZIPinfo *) opaque;
263 SZIPentry *entry = (SZIPentry *) __PHYSFS_DirTreeFind(&info->tree, path);
264 ISzAlloc *alloc = &SZIP_SzAlloc;
265 SZIPLookToRead stream;
266 PHYSFS_Io *retval = NULL;
267 PHYSFS_Io *io = NULL;
268 UInt32 blockIndex = 0xFFFFFFFF;
269 Byte *outBuffer = NULL;
270 size_t outBufferSize = 0;
271 size_t offset = 0;
272 size_t outSizeProcessed = 0;
273 void *buf = NULL;
274 SRes rc;
275
276 BAIL_IF_ERRPASS(!entry, NULL);
277 BAIL_IF(entry->tree.isdir, PHYSFS_ERR_NOT_A_FILE, NULL);
278
279 io = info->io->duplicate(info->io);
280 GOTO_IF_ERRPASS(!io, SZIP_openRead_failed);
281
282 szipInitStream(&stream, io);
283
284 rc = SzArEx_Extract(&info->db, &stream.lookStream.s, entry->dbidx,
285 &blockIndex, &outBuffer, &outBufferSize, &offset,
286 &outSizeProcessed, alloc, alloc);
287 GOTO_IF(rc != SZ_OK, szipErrorCode(rc), SZIP_openRead_failed);
288 GOTO_IF(outBuffer == NULL, PHYSFS_ERR_OUT_OF_MEMORY, SZIP_openRead_failed);
289
290 io->destroy(io);
291 io = NULL;
292
293 buf = allocator.Malloc(outSizeProcessed ? outSizeProcessed : 1);
294 GOTO_IF(buf == NULL, PHYSFS_ERR_OUT_OF_MEMORY, SZIP_openRead_failed);
295
296 if (outSizeProcessed > 0)
297 memcpy(buf, outBuffer + offset, outSizeProcessed);
298
299 alloc->Free(alloc, outBuffer);
300 outBuffer = NULL;
301
302 retval = __PHYSFS_createMemoryIo(buf, outSizeProcessed, allocator.Free);
303 GOTO_IF_ERRPASS(!retval, SZIP_openRead_failed);
304
305 return retval;
306
307SZIP_openRead_failed:
308 if (io != NULL)
309 io->destroy(io);
310
311 if (buf)
312 allocator.Free(buf);
313
314 if (outBuffer)
315 alloc->Free(alloc, outBuffer);
316
317 return NULL;
318} /* SZIP_openRead */
319
320
321static PHYSFS_Io *SZIP_openWrite(void *opaque, const char *filename)
322{
323 BAIL(PHYSFS_ERR_READ_ONLY, NULL);
324} /* SZIP_openWrite */
325
326
327static PHYSFS_Io *SZIP_openAppend(void *opaque, const char *filename)
328{
329 BAIL(PHYSFS_ERR_READ_ONLY, NULL);
330} /* SZIP_openAppend */
331
332
333static int SZIP_remove(void *opaque, const char *name)
334{
335 BAIL(PHYSFS_ERR_READ_ONLY, 0);
336} /* SZIP_remove */
337
338
339static int SZIP_mkdir(void *opaque, const char *name)
340{
341 BAIL(PHYSFS_ERR_READ_ONLY, 0);
342} /* SZIP_mkdir */
343
344
345static inline PHYSFS_uint64 lzmasdkTimeToPhysfsTime(const CNtfsFileTime *t)
346{
347 const PHYSFS_uint64 winEpochToUnixEpoch = __PHYSFS_UI64(0x019DB1DED53E8000);
348 const PHYSFS_uint64 nanosecToMillisec = __PHYSFS_UI64(10000000);
349 const PHYSFS_uint64 quad = (((PHYSFS_uint64) t->High) << 32) | t->Low;
350 return (quad - winEpochToUnixEpoch) / nanosecToMillisec;
351} /* lzmasdkTimeToPhysfsTime */
352
353
354static int SZIP_stat(void *opaque, const char *path, PHYSFS_Stat *stat)
355{
356 SZIPinfo *info = (SZIPinfo *) opaque;
357 SZIPentry *entry;
358 PHYSFS_uint32 idx;
359
360 entry = (SZIPentry *) __PHYSFS_DirTreeFind(&info->tree, path);
361 BAIL_IF_ERRPASS(!entry, 0);
362 idx = entry->dbidx;
363
364 if (entry->tree.isdir)
365 {
366 stat->filesize = -1;
367 stat->filetype = PHYSFS_FILETYPE_DIRECTORY;
368 } /* if */
369 else
370 {
371 stat->filesize = (PHYSFS_sint64) SzArEx_GetFileSize(&info->db, idx);
372 stat->filetype = PHYSFS_FILETYPE_REGULAR;
373 } /* else */
374
375 if (info->db.MTime.Vals != NULL)
376 stat->modtime = lzmasdkTimeToPhysfsTime(&info->db.MTime.Vals[idx]);
377 else if (info->db.CTime.Vals != NULL)
378 stat->modtime = lzmasdkTimeToPhysfsTime(&info->db.CTime.Vals[idx]);
379 else
380 stat->modtime = -1;
381
382 if (info->db.CTime.Vals != NULL)
383 stat->createtime = lzmasdkTimeToPhysfsTime(&info->db.CTime.Vals[idx]);
384 else if (info->db.MTime.Vals != NULL)
385 stat->createtime = lzmasdkTimeToPhysfsTime(&info->db.MTime.Vals[idx]);
386 else
387 stat->createtime = -1;
388
389 stat->accesstime = -1;
390 stat->readonly = 1;
391
392 return 1;
393} /* SZIP_stat */
394
395
396void SZIP_global_init(void)
397{
398 /* this just needs to calculate some things, so it only ever
399 has to run once, even after a deinit. */
400 static int generatedTable = 0;
401 if (!generatedTable)
402 {
403 generatedTable = 1;
404 CrcGenerateTable();
405 } /* if */
406} /* SZIP_global_init */
407
408
409const PHYSFS_Archiver __PHYSFS_Archiver_7Z =
410{
411 CURRENT_PHYSFS_ARCHIVER_API_VERSION,
412 {
413 "7Z",
414 "7zip archives",
415 "Ryan C. Gordon <icculus@icculus.org>",
416 "https://icculus.org/physfs/",
417 0, /* supportsSymlinks */
418 },
419 SZIP_openArchive,
420 __PHYSFS_DirTreeEnumerate,
421 SZIP_openRead,
422 SZIP_openWrite,
423 SZIP_openAppend,
424 SZIP_remove,
425 SZIP_mkdir,
426 SZIP_stat,
427 SZIP_closeArchive
428};
429
430#endif /* defined PHYSFS_SUPPORTS_7Z */
431
432/* end of physfs_archiver_7z.c ... */
433
434