| 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 | |