Subversion Repositories Games.Descent

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 pmbaty 1
/*
2
 * VDF support routines for PhysicsFS.
3
 *
4
 * This driver handles Gothic I/II VDF archives.
5
 * This format (but not this driver) was designed by Piranha Bytes for
6
 *  use wih the ZenGin engine.
7
 *
8
 * This file was written by Francesco Bertolaccini, based on the UNPK archiver
9
 *  by Ryan C. Gordon and the works of degenerated1123 and Nico Bendlin.
10
 */
11
 
12
#define __PHYSICSFS_INTERNAL__
13
#include "physfs_internal.h"
14
 
15
#if PHYSFS_SUPPORTS_VDF
16
 
17
#include <time.h>
18
 
19
#define VDF_COMMENT_LENGTH 256
20
#define VDF_SIGNATURE_LENGTH 16
21
#define VDF_ENTRY_NAME_LENGTH 64
22
#define VDF_ENTRY_DIR 0x80000000
23
 
24
static const char* VDF_SIGNATURE_G1 = "PSVDSC_V2.00\r\n\r\n";
25
static const char* VDF_SIGNATURE_G2 = "PSVDSC_V2.00\n\r\n\r";
26
 
27
 
28
static inline int readui32(PHYSFS_Io *io, PHYSFS_uint32 *val)
29
{
30
    PHYSFS_uint32 v;
31
    BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &v, sizeof (v)), 0);
32
    *val = PHYSFS_swapULE32(v);
33
    return 1;
34
} /* readui32 */
35
 
36
 
37
static PHYSFS_sint64 vdfDosTimeToEpoch(const PHYSFS_uint32 dostime)
38
{
39
    /* VDF stores timestamps as 32bit DOS dates: the seconds are counted in
40
       2-seconds intervals and the years are counted since 1 Jan. 1980 */
41
    struct tm t;
42
    memset(&t, '\0', sizeof (t));
43
    t.tm_year = ((int) ((dostime >> 25) & 0x7F)) + 80; /* 1980 to 1900 */
44
    t.tm_mon = ((int) ((dostime >> 21) & 0xF)) - 1;  /* 1-12 to 0-11 */
45
    t.tm_mday = (int) ((dostime >> 16) & 0x1F);
46
    t.tm_hour = (int) ((dostime >> 11) & 0x1F);
47
    t.tm_min = (int) ((dostime >> 5) & 0x3F);
48
    t.tm_sec = ((int) ((dostime >> 0) & 0x1F)) * 2;  /* 2 seconds to 1. */
49
    return (PHYSFS_sint64) mktime(&t);
50
} /* vdfDosTimeToEpoch */
51
 
52
 
53
static int vdfLoadEntries(PHYSFS_Io *io, const PHYSFS_uint32 count,
54
                          const PHYSFS_sint64 ts, void *arc)
55
{
56
    PHYSFS_uint32 i;
57
 
58
    for (i = 0; i < count; i++)
59
    {
60
        char name[VDF_ENTRY_NAME_LENGTH + 1];
61
        int namei;
62
        PHYSFS_uint32 jump, size, type, attr;
63
 
64
        BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, name, sizeof (name) - 1), 0);
65
        BAIL_IF_ERRPASS(!readui32(io, &jump), 0);
66
        BAIL_IF_ERRPASS(!readui32(io, &size), 0);
67
        BAIL_IF_ERRPASS(!readui32(io, &type), 0);
68
        BAIL_IF_ERRPASS(!readui32(io, &attr), 0);
69
 
70
        /* Trim whitespace off the end of the filename */
71
        name[VDF_ENTRY_NAME_LENGTH] = '\0';  /* always null-terminated. */
72
        for (namei = VDF_ENTRY_NAME_LENGTH - 1; namei >= 0; namei--)
73
        {
74
            /* We assume the filenames are low-ASCII; consider the archive
75
               corrupt if we see something above 127, since we don't know the
76
               encoding. (We can change this later if we find out these exist
77
               and are intended to be, say, latin-1 or UTF-8 encoding). */
78
            BAIL_IF(((PHYSFS_uint8) name[namei]) > 127, PHYSFS_ERR_CORRUPT, 0);
79
 
80
            if (name[namei] == ' ')
81
                name[namei] = '\0';
82
            else
83
                break;
84
        } /* for */
85
 
86
        BAIL_IF(!name[0], PHYSFS_ERR_CORRUPT, 0);
87
        if (!(type & VDF_ENTRY_DIR)) {
88
            BAIL_IF_ERRPASS(!UNPK_addEntry(arc, name, 0, ts, ts, jump, size), 0);
89
        }
90
    } /* for */
91
 
92
    return 1;
93
} /* vdfLoadEntries */
94
 
95
 
96
static void *VDF_openArchive(PHYSFS_Io *io, const char *name,
97
                             int forWriting, int *claimed)
98
{
99
    PHYSFS_uint8 ignore[16];
100
    PHYSFS_uint8 sig[VDF_SIGNATURE_LENGTH];
101
    PHYSFS_uint32 count, timestamp, version, dataSize, rootCatOffset;
102
    void *unpkarc;
103
 
104
    assert(io != NULL); /* shouldn't ever happen. */
105
 
106
    BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL);
107
 
108
    /* skip the 256-byte comment field. */
109
    BAIL_IF_ERRPASS(!io->seek(io, VDF_COMMENT_LENGTH), NULL);
110
 
111
    BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, sig, sizeof (sig)), NULL);
112
 
113
    if ((memcmp(sig, VDF_SIGNATURE_G1, VDF_SIGNATURE_LENGTH) != 0) &&
114
        (memcmp(sig, VDF_SIGNATURE_G2, VDF_SIGNATURE_LENGTH) != 0))
115
    {
116
        BAIL(PHYSFS_ERR_UNSUPPORTED, NULL);
117
    } /* if */
118
 
119
    *claimed = 1;
120
 
121
    BAIL_IF_ERRPASS(!readui32(io, &count), NULL);
122
    BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, ignore, 4), NULL);  /* numFiles */
123
    BAIL_IF_ERRPASS(!readui32(io, &timestamp), NULL);
124
    BAIL_IF_ERRPASS(!readui32(io, &dataSize), NULL);  /* dataSize */
125
    BAIL_IF_ERRPASS(!readui32(io, &rootCatOffset), NULL);  /* rootCatOff */
126
    BAIL_IF_ERRPASS(!readui32(io, &version), NULL);
127
 
128
    BAIL_IF(version != 0x50, PHYSFS_ERR_UNSUPPORTED, NULL);
129
 
130
    BAIL_IF_ERRPASS(!io->seek(io, rootCatOffset), NULL);
131
 
132
    unpkarc = UNPK_openArchive(io);
133
    BAIL_IF_ERRPASS(!unpkarc, NULL);
134
 
135
    if (!vdfLoadEntries(io, count, vdfDosTimeToEpoch(timestamp), unpkarc))
136
    {
137
        UNPK_abandonArchive(unpkarc);
138
        return NULL;
139
    } /* if */
140
 
141
    return unpkarc;
142
} /* VDF_openArchive */
143
 
144
 
145
const PHYSFS_Archiver __PHYSFS_Archiver_VDF =
146
{
147
    CURRENT_PHYSFS_ARCHIVER_API_VERSION,
148
    {
149
        "VDF",
150
        "Gothic I/II engine format",
151
        "Francesco Bertolaccini <bertolaccinifrancesco@gmail.com>",
152
        "https://github.com/frabert",
153
        0,  /* supportsSymlinks */
154
    },
155
    VDF_openArchive,
156
    UNPK_enumerate,
157
    UNPK_openRead,
158
    UNPK_openWrite,
159
    UNPK_openAppend,
160
    UNPK_remove,
161
    UNPK_mkdir,
162
    UNPK_stat,
163
    UNPK_closeArchive
164
};
165
 
166
#endif /* defined PHYSFS_SUPPORTS_VDF */
167
 
168
/* end of physfs_archiver_vdf.c ... */