forked from icculus/mojosetup
-
Notifications
You must be signed in to change notification settings - Fork 0
/
archive_uz2.c
350 lines (285 loc) · 9.7 KB
/
archive_uz2.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
/**
* MojoSetup; a portable, flexible installation application.
*
* Please see the file LICENSE.txt in the source's root directory.
*
* This file written by Ryan C. Gordon.
*/
#include "fileio.h"
#include "platform.h"
#if !SUPPORT_UZ2
MojoArchive *MojoArchive_createUZ2(MojoInput *io) { return NULL; }
#else
// UZ2 format is a simple compressed file format used by UnrealEngine2.
// it's just a stream of blocks like this:
// uint32 compressed size
// uint32 uncompressed size
// uint8 data[compressed size] <-- unpacks to (uncompressed size) bytes.
// Decompression is handled by zlib's "uncompress" function.
#include "miniz.h"
#define MAXCOMPSIZE 32768
#define MAXUNCOMPSIZE 33096 // MAXCOMPSIZE + 1%
// MojoInput implementation...
// Decompression is handled in the parent MojoInput, so this just needs to
// make sure we stay within the bounds of the tarfile entry.
typedef struct UZ2input
{
MojoInput *io;
int64 fsize;
uint64 position;
uint32 compsize;
uint8 compbuf[MAXCOMPSIZE];
uint32 uncompsize;
uint8 uncompbuf[MAXUNCOMPSIZE];
uint32 uncompindex;
} UZ2input;
typedef struct UZ2info
{
char *outname;
int64 outsize;
boolean enumerated;
} UZ2info;
static boolean unpack(UZ2input *inp)
{
MojoInput *io = inp->io;
uLongf ul = (uLongf) inp->uncompsize;
// we checked these formally elsewhere.
assert(inp->compsize > 0);
assert(inp->uncompsize > 0);
assert(inp->compsize <= MAXCOMPSIZE);
assert(inp->uncompsize <= MAXUNCOMPSIZE);
if (io->read(io, inp->compbuf, inp->compsize) != inp->compsize)
return false;
if (uncompress(inp->uncompbuf, &ul, inp->compbuf, inp->compsize) != Z_OK)
return false;
if (ul != ((uLongf) inp->uncompsize)) // corrupt data.
return false;
inp->uncompindex = 0;
return true;
} // unpack
static boolean MojoInput_uz2_ready(MojoInput *io)
{
UZ2input *input = (UZ2input *) io->opaque;
if (input->uncompsize > 0)
return true;
return true; // !!! FIXME: need to know we have a full compressed block.
} // MojoInput_uz2_ready
static int64 MojoInput_uz2_read(MojoInput *io, void *_buf, uint32 bufsize)
{
uint8 *buf = (uint8 *) _buf;
UZ2input *input = (UZ2input *) io->opaque;
int64 retval = 0;
while (bufsize > 0)
{
const uint32 available = input->uncompsize - input->uncompindex;
const uint32 cpy = (available < bufsize) ? available : bufsize;
if (available == 0)
{
if (input->position == input->fsize)
return 0;
else if (!MojoInput_readui32(input->io, &input->compsize))
return (retval == 0) ? -1 : retval;
else if (!MojoInput_readui32(input->io, &input->uncompsize))
return (retval == 0) ? -1 : retval;
else if (!unpack(input))
return (retval == 0) ? -1 : retval;
continue; // try again.
} // if
memcpy(buf, input->uncompbuf + input->uncompindex, cpy);
buf += cpy;
bufsize -= cpy;
retval += cpy;
input->uncompindex += cpy;
input->position += cpy;
} // while
return retval;
} // MojoInput_uz2_read
static boolean MojoInput_uz2_seek(MojoInput *io, uint64 pos)
{
UZ2input *input = (UZ2input *) io->opaque;
int64 seekpos = 0;
// in a perfect world, this wouldn't seek from the start if moving
// forward. But oh well.
input->position = 0;
while (input->position < pos)
{
if (!input->io->seek(input->io, seekpos))
return false;
else if (!MojoInput_readui32(io, &input->compsize))
return false;
else if (!MojoInput_readui32(io, &input->uncompsize))
return false;
// we checked these formally elsewhere.
assert(input->compsize > 0);
assert(input->uncompsize > 0);
assert(input->compsize <= MAXCOMPSIZE);
assert(input->uncompsize <= MAXUNCOMPSIZE);
input->position += input->uncompsize;
seekpos += (sizeof (uint32) * 2) + input->compsize;
} // while
// we are positioned on the compressed block that contains the seek target.
if (!unpack(input))
return false;
input->position -= input->uncompsize;
input->uncompindex = (uint32) (pos - input->position);
input->position += input->uncompindex;
return true;
} // MojoInput_uz2_seek
static int64 MojoInput_uz2_tell(MojoInput *io)
{
return (int64) (((UZ2input *) io->opaque)->position);
} // MojoInput_uz2_tell
static int64 MojoInput_uz2_length(MojoInput *io)
{
return ((UZ2input *) io->opaque)->fsize;
} // MojoInput_uz2_length
static MojoInput *MojoInput_uz2_duplicate(MojoInput *io)
{
MojoInput *retval = NULL;
UZ2input *input = (UZ2input *) io->opaque;
MojoInput *newio = input->io->duplicate(input->io);
if (newio != NULL)
{
UZ2input *newopaque = (UZ2input *) xmalloc(sizeof (UZ2input));
newopaque->io = newio;
newopaque->fsize = input->fsize;
// everything else is properly zero'd by xmalloc().
retval = (MojoInput *) xmalloc(sizeof (MojoInput));
memcpy(retval, io, sizeof (MojoInput));
retval->opaque = newopaque;
} // if
return retval;
} // MojoInput_uz2_duplicate
static void MojoInput_uz2_close(MojoInput *io)
{
UZ2input *input = (UZ2input *) io->opaque;
input->io->close(input->io);
free(input);
free(io);
} // MojoInput_uz2_close
// MojoArchive implementation...
static boolean MojoArchive_uz2_enumerate(MojoArchive *ar)
{
UZ2info *info = (UZ2info *) ar->opaque;
MojoArchive_resetEntry(&ar->prevEnum);
info->enumerated = false;
return true;
} // MojoArchive_uz2_enumerate
static const MojoArchiveEntry *MojoArchive_uz2_enumNext(MojoArchive *ar)
{
UZ2info *info = (UZ2info *) ar->opaque;
MojoArchive_resetEntry(&ar->prevEnum);
if (info->enumerated)
return NULL; // only one file in this "archive".
ar->prevEnum.perms = MojoPlatform_defaultFilePerms();
ar->prevEnum.filesize = info->outsize;
ar->prevEnum.filename = xstrdup(info->outname);
ar->prevEnum.type = MOJOARCHIVE_ENTRY_FILE;
info->enumerated = true;
return &ar->prevEnum;
} // MojoArchive_uz2_enumNext
static MojoInput *MojoArchive_uz2_openCurrentEntry(MojoArchive *ar)
{
UZ2info *info = (UZ2info *) ar->opaque;
MojoInput *io = NULL;
UZ2input *opaque = NULL;
MojoInput *dupio = NULL;
if (!info->enumerated)
return NULL;
dupio = ar->io->duplicate(ar->io);
if (dupio == NULL)
return NULL;
opaque = (UZ2input *) xmalloc(sizeof (UZ2input));
opaque->io = dupio;
opaque->fsize = info->outsize;
// rest is zero'd by xmalloc().
io = (MojoInput *) xmalloc(sizeof (MojoInput));
io->ready = MojoInput_uz2_ready;
io->read = MojoInput_uz2_read;
io->seek = MojoInput_uz2_seek;
io->tell = MojoInput_uz2_tell;
io->length = MojoInput_uz2_length;
io->duplicate = MojoInput_uz2_duplicate;
io->close = MojoInput_uz2_close;
io->opaque = opaque;
return io;
} // MojoArchive_uz2_openCurrentEntry
static void MojoArchive_uz2_close(MojoArchive *ar)
{
UZ2info *info = (UZ2info *) ar->opaque;
MojoArchive_resetEntry(&ar->prevEnum);
ar->io->close(ar->io);
free(info->outname);
free(info);
free(ar);
} // MojoArchive_uz2_close
// Unfortunately, we have to walk the whole file, but we don't have to actually
// do any decompression work here. Just seek, read 8 bytes, repeat until EOF.
static int64 calculate_uz2_outsize(MojoInput *io)
{
int64 retval = 0;
uint32 compsize = 0;
uint32 uncompsize = 0;
int64 pos = 0;
if (!io->seek(io, 0))
return -1;
while (MojoInput_readui32(io, &compsize))
{
if (!MojoInput_readui32(io, &uncompsize))
return -1;
else if ((compsize > MAXCOMPSIZE) || (uncompsize > MAXUNCOMPSIZE))
return -1;
else if ((compsize == 0) || (uncompsize == 0))
return -1;
retval += uncompsize;
pos += (sizeof (uint32) * 2) + compsize;
if (!io->seek(io, pos))
return -1;
} // while
if (!io->seek(io, 0)) // make sure we're back to the start.
return -1;
return retval;
} // calculate_uz2_outsize
MojoArchive *MojoArchive_createUZ2(MojoInput *io, const char *origfname)
{
MojoArchive *ar = NULL;
const char *fname = NULL;
char *outname = NULL;
size_t len = 0;
int64 outsize = 0;
UZ2info *uz2info = NULL;
// There's no magic in a UZ2 that allows us to identify the format.
// The higher-level won't call this unless the file extension in
// (origfname) is ".uz2"
// Figure out the output name ("x.uz2" would produce "x").
if (origfname == NULL)
return NULL; // just in case.
fname = strrchr(origfname, '/');
if (fname == NULL)
fname = origfname;
else
fname++;
len = strlen(fname) - 4; // -4 == ".uz2"
if (strcasecmp(fname + len, ".uz2") != 0)
return NULL; // just in case.
outsize = calculate_uz2_outsize(io);
if (outsize < 0)
return NULL; // wasn't really a uz2? Corrupt/truncated file?
outname = (char *) xmalloc(len+1);
memcpy(outname, fname, len);
outname[len] = '\0';
uz2info = (UZ2info *) xmalloc(sizeof (UZ2info));
uz2info->enumerated = false;
uz2info->outname = outname;
uz2info->outsize = outsize;
ar = (MojoArchive *) xmalloc(sizeof (MojoArchive));
ar->opaque = uz2info;
ar->enumerate = MojoArchive_uz2_enumerate;
ar->enumNext = MojoArchive_uz2_enumNext;
ar->openCurrentEntry = MojoArchive_uz2_openCurrentEntry;
ar->close = MojoArchive_uz2_close;
ar->io = io;
return ar;
} // MojoArchive_createUZ2
#endif // SUPPORT_UZ2
// end of archive_uz2.c ...