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
24static const char* VDF_SIGNATURE_G1 = "PSVDSC_V2.00\r\n\r\n";
25static const char* VDF_SIGNATURE_G2 = "PSVDSC_V2.00\n\r\n\r";
26
27
28static 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
37static 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
53static 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
96static 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 /* !!! FIXME: check case_sensitive and only_usascii params for this archive. */
133 unpkarc = UNPK_openArchive(io, 1, 0);
134 BAIL_IF_ERRPASS(!unpkarc, NULL);
135
136 if (!vdfLoadEntries(io, count, vdfDosTimeToEpoch(timestamp), unpkarc))
137 {
138 UNPK_abandonArchive(unpkarc);
139 return NULL;
140 } /* if */
141
142 return unpkarc;
143} /* VDF_openArchive */
144
145
146const PHYSFS_Archiver __PHYSFS_Archiver_VDF =
147{
148 CURRENT_PHYSFS_ARCHIVER_API_VERSION,
149 {
150 "VDF",
151 "Gothic I/II engine format",
152 "Francesco Bertolaccini <bertolaccinifrancesco@gmail.com>",
153 "https://github.com/frabert",
154 0, /* supportsSymlinks */
155 },
156 VDF_openArchive,
157 UNPK_enumerate,
158 UNPK_openRead,
159 UNPK_openWrite,
160 UNPK_openAppend,
161 UNPK_remove,
162 UNPK_mkdir,
163 UNPK_stat,
164 UNPK_closeArchive
165};
166
167#endif /* defined PHYSFS_SUPPORTS_VDF */
168
169/* end of physfs_archiver_vdf.c ... */
170