Subversion Repositories Games.Descent

Rev

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