Skip to content

Commit

Permalink
Added UnrealEngine2 UZ2 archive support.
Browse files Browse the repository at this point in the history
  • Loading branch information
icculus committed May 10, 2009
1 parent 2fa5af3 commit 00fb227
Show file tree
Hide file tree
Showing 3 changed files with 370 additions and 0 deletions.
7 changes: 7 additions & 0 deletions CMakeLists.txt
Expand Up @@ -225,6 +225,7 @@ SET(MOJOSETUP_SRCS
fileio.c
archive_zip.c
archive_tar.c
archive_uz2.c
checksum_crc32.c
checksum_md5.c
checksum_sha1.c
Expand Down Expand Up @@ -494,6 +495,12 @@ IF(MOJOSETUP_ARCHIVE_TAR)
ENDIF(MOJOSETUP_ARCHIVE_TAR_BZ2)
ENDIF(MOJOSETUP_ARCHIVE_TAR)

OPTION(MOJOSETUP_ARCHIVE_UZ2 "Enable UZ2 support" FALSE)
IF(MOJOSETUP_ARCHIVE_UZ2)
ADD_DEFINITIONS(-DSUPPORT_UZ2=1)
SET(MOJOSETUP_NEED_ZLIB TRUE)
ENDIF(MOJOSETUP_ARCHIVE_UZ2)


# Image decoders for GUIs...

Expand Down
361 changes: 361 additions & 0 deletions archive_uz2.c
@@ -0,0 +1,361 @@
/**
* 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 "zlib-1.2.3/zlib.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;
int64 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 readui32(MojoInput *io, uint32 *ui32)
{
uint8 buf[sizeof (uint32)];
if (io->read(io, buf, sizeof (buf)) != sizeof (buf))
return false;

*ui32 = (buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24));
return true;
} // readui32

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;
if (available == 0)
{
if (input->position == input->fsize)
return 0;
else if (!readui32(input->io, &input->compsize))
return (retval == 0) ? -1 : retval;
else if (!readui32(input->io, &input->uncompsize))
return (retval == 0) ? -1 : retval;
else if (!unpack(input))
return (retval == 0) ? -1 : retval;
continue; // try again.
} // if

const uint32 cpy = (available < bufsize) ? available : bufsize;
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 (!readui32(io, &input->compsize))
return false;
else if (!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 = pos - input->position;
input->position += input->uncompindex;

return true;
} // MojoInput_uz2_seek

static int64 MojoInput_uz2_tell(MojoInput *io)
{
return ((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 (readui32(io, &compsize))
{
if (!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 ...

2 changes: 2 additions & 0 deletions fileio.c
Expand Up @@ -13,6 +13,7 @@ typedef MojoArchive* (*MojoArchiveCreateEntryPoint)(MojoInput *io);

MojoArchive *MojoArchive_createZIP(MojoInput *io);
MojoArchive *MojoArchive_createTAR(MojoInput *io);
MojoArchive *MojoArchive_createUZ2(MojoInput *io);

typedef struct
{
Expand All @@ -31,6 +32,7 @@ static const MojoArchiveType archives[] =
{ "tbz2", MojoArchive_createTAR, true },
{ "tb2", MojoArchive_createTAR, true },
{ "tbz", MojoArchive_createTAR, true },
{ "uz2", MojoArchive_createUZ2, false },
};

MojoArchive *MojoArchive_newFromInput(MojoInput *io, const char *origfname)
Expand Down

0 comments on commit 00fb227

Please sign in to comment.