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 | |
16 | typedef 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. */ |
24 | typedef 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. */ |
31 | typedef 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 | |
39 | static 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 | |
63 | static void *SZIP_ISzAlloc_Alloc(void *p, size_t size) |
64 | { |
65 | return allocator.Malloc(size ? size : 1); |
66 | } /* SZIP_ISzAlloc_Alloc */ |
67 | |
68 | static void SZIP_ISzAlloc_Free(void *p, void *address) |
69 | { |
70 | if (address) |
71 | allocator.Free(address); |
72 | } /* SZIP_ISzAlloc_Free */ |
73 | |
74 | static 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 | |
85 | static 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 | |
102 | static 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 | |
141 | static 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. */ |
156 | static 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 | |
184 | static 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 | |
201 | static 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 | |
215 | static 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 | |
249 | failed: |
250 | info->io = NULL; /* don't let cleanup destroy the PHYSFS_Io. */ |
251 | SZIP_closeArchive(info); |
252 | return NULL; |
253 | } /* SZIP_openArchive */ |
254 | |
255 | |
256 | static 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 | |
307 | SZIP_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 | |
321 | static PHYSFS_Io *SZIP_openWrite(void *opaque, const char *filename) |
322 | { |
323 | BAIL(PHYSFS_ERR_READ_ONLY, NULL); |
324 | } /* SZIP_openWrite */ |
325 | |
326 | |
327 | static PHYSFS_Io *SZIP_openAppend(void *opaque, const char *filename) |
328 | { |
329 | BAIL(PHYSFS_ERR_READ_ONLY, NULL); |
330 | } /* SZIP_openAppend */ |
331 | |
332 | |
333 | static int SZIP_remove(void *opaque, const char *name) |
334 | { |
335 | BAIL(PHYSFS_ERR_READ_ONLY, 0); |
336 | } /* SZIP_remove */ |
337 | |
338 | |
339 | static int SZIP_mkdir(void *opaque, const char *name) |
340 | { |
341 | BAIL(PHYSFS_ERR_READ_ONLY, 0); |
342 | } /* SZIP_mkdir */ |
343 | |
344 | |
345 | static 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 | |
354 | static 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 | |
396 | void 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 | |
409 | const 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 | |