Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | pmbaty | 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))) |
||
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 | |||
289 | io->destroy(io); |
||
290 | io = NULL; |
||
291 | |||
292 | buf = allocator.Malloc(outSizeProcessed); |
||
293 | GOTO_IF(rc != SZ_OK, PHYSFS_ERR_OUT_OF_MEMORY, SZIP_openRead_failed); |
||
294 | memcpy(buf, outBuffer + offset, outSizeProcessed); |
||
295 | |||
296 | alloc->Free(alloc, outBuffer); |
||
297 | outBuffer = NULL; |
||
298 | |||
299 | retval = __PHYSFS_createMemoryIo(buf, outSizeProcessed, allocator.Free); |
||
300 | GOTO_IF_ERRPASS(!retval, SZIP_openRead_failed); |
||
301 | |||
302 | return retval; |
||
303 | |||
304 | SZIP_openRead_failed: |
||
305 | if (io != NULL) |
||
306 | io->destroy(io); |
||
307 | |||
308 | if (buf) |
||
309 | allocator.Free(buf); |
||
310 | |||
311 | if (outBuffer) |
||
312 | alloc->Free(alloc, outBuffer); |
||
313 | |||
314 | return NULL; |
||
315 | } /* SZIP_openRead */ |
||
316 | |||
317 | |||
318 | static PHYSFS_Io *SZIP_openWrite(void *opaque, const char *filename) |
||
319 | { |
||
320 | BAIL(PHYSFS_ERR_READ_ONLY, NULL); |
||
321 | } /* SZIP_openWrite */ |
||
322 | |||
323 | |||
324 | static PHYSFS_Io *SZIP_openAppend(void *opaque, const char *filename) |
||
325 | { |
||
326 | BAIL(PHYSFS_ERR_READ_ONLY, NULL); |
||
327 | } /* SZIP_openAppend */ |
||
328 | |||
329 | |||
330 | static int SZIP_remove(void *opaque, const char *name) |
||
331 | { |
||
332 | BAIL(PHYSFS_ERR_READ_ONLY, 0); |
||
333 | } /* SZIP_remove */ |
||
334 | |||
335 | |||
336 | static int SZIP_mkdir(void *opaque, const char *name) |
||
337 | { |
||
338 | BAIL(PHYSFS_ERR_READ_ONLY, 0); |
||
339 | } /* SZIP_mkdir */ |
||
340 | |||
341 | |||
342 | static inline PHYSFS_uint64 lzmasdkTimeToPhysfsTime(const CNtfsFileTime *t) |
||
343 | { |
||
344 | const PHYSFS_uint64 winEpochToUnixEpoch = __PHYSFS_UI64(0x019DB1DED53E8000); |
||
345 | const PHYSFS_uint64 nanosecToMillisec = __PHYSFS_UI64(10000000); |
||
346 | const PHYSFS_uint64 quad = (((PHYSFS_uint64) t->High) << 32) | t->Low; |
||
347 | return (quad - winEpochToUnixEpoch) / nanosecToMillisec; |
||
348 | } /* lzmasdkTimeToPhysfsTime */ |
||
349 | |||
350 | |||
351 | static int SZIP_stat(void *opaque, const char *path, PHYSFS_Stat *stat) |
||
352 | { |
||
353 | SZIPinfo *info = (SZIPinfo *) opaque; |
||
354 | SZIPentry *entry; |
||
355 | PHYSFS_uint32 idx; |
||
356 | |||
357 | entry = (SZIPentry *) __PHYSFS_DirTreeFind(&info->tree, path); |
||
358 | BAIL_IF_ERRPASS(!entry, 0); |
||
359 | idx = entry->dbidx; |
||
360 | |||
361 | if (entry->tree.isdir) |
||
362 | { |
||
363 | stat->filesize = -1; |
||
364 | stat->filetype = PHYSFS_FILETYPE_DIRECTORY; |
||
365 | } /* if */ |
||
366 | else |
||
367 | { |
||
368 | stat->filesize = (PHYSFS_sint64) SzArEx_GetFileSize(&info->db, idx); |
||
369 | stat->filetype = PHYSFS_FILETYPE_REGULAR; |
||
370 | } /* else */ |
||
371 | |||
372 | if (info->db.MTime.Vals != NULL) |
||
373 | stat->modtime = lzmasdkTimeToPhysfsTime(&info->db.MTime.Vals[idx]); |
||
374 | else if (info->db.CTime.Vals != NULL) |
||
375 | stat->modtime = lzmasdkTimeToPhysfsTime(&info->db.CTime.Vals[idx]); |
||
376 | else |
||
377 | stat->modtime = -1; |
||
378 | |||
379 | if (info->db.CTime.Vals != NULL) |
||
380 | stat->createtime = lzmasdkTimeToPhysfsTime(&info->db.CTime.Vals[idx]); |
||
381 | else if (info->db.MTime.Vals != NULL) |
||
382 | stat->createtime = lzmasdkTimeToPhysfsTime(&info->db.MTime.Vals[idx]); |
||
383 | else |
||
384 | stat->createtime = -1; |
||
385 | |||
386 | stat->accesstime = -1; |
||
387 | stat->readonly = 1; |
||
388 | |||
389 | return 1; |
||
390 | } /* SZIP_stat */ |
||
391 | |||
392 | |||
393 | void SZIP_global_init(void) |
||
394 | { |
||
395 | /* this just needs to calculate some things, so it only ever |
||
396 | has to run once, even after a deinit. */ |
||
397 | static int generatedTable = 0; |
||
398 | if (!generatedTable) |
||
399 | { |
||
400 | generatedTable = 1; |
||
401 | CrcGenerateTable(); |
||
402 | } /* if */ |
||
403 | } /* SZIP_global_init */ |
||
404 | |||
405 | |||
406 | const PHYSFS_Archiver __PHYSFS_Archiver_7Z = |
||
407 | { |
||
408 | CURRENT_PHYSFS_ARCHIVER_API_VERSION, |
||
409 | { |
||
410 | "7Z", |
||
411 | "7zip archives", |
||
412 | "Ryan C. Gordon <icculus@icculus.org>", |
||
413 | "https://icculus.org/physfs/", |
||
414 | 0, /* supportsSymlinks */ |
||
415 | }, |
||
416 | SZIP_openArchive, |
||
417 | __PHYSFS_DirTreeEnumerate, |
||
418 | SZIP_openRead, |
||
419 | SZIP_openWrite, |
||
420 | SZIP_openAppend, |
||
421 | SZIP_remove, |
||
422 | SZIP_mkdir, |
||
423 | SZIP_stat, |
||
424 | SZIP_closeArchive |
||
425 | }; |
||
426 | |||
427 | #endif /* defined PHYSFS_SUPPORTS_7Z */ |
||
428 | |||
429 | /* end of physfs_archiver_7z.c ... */ |
||
430 |