physfs_archiver_7z.c 12 KB

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