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 |