mirror of
https://github.com/glest/glest-source.git
synced 2025-02-24 19:52:25 +01:00
351 lines
9.7 KiB
C
351 lines
9.7 KiB
C
/**
|
|
* 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 ...
|
|
|