/*
 * Resident Evil 3 rofs<n>.dat support routines for PhysicsFS.
 *
 * This driver handles Resident Evil 3 archives.
 *
 * Please see the file LICENSE in the source's root directory.
 *
 * This file written by Patrice Mandin, based on the QPAK archiver by
 *  Ryan C. Gordon.
 */

#define __PHYSICSFS_INTERNAL__
#include "physfs_internal.h"

#if PHYSFS_SUPPORTS_ROFS

typedef struct
{
	__PHYSFS_DirTreeEntry tree;	/* manages directory tree */

	PHYSFS_uint32 startPos;
	PHYSFS_uint32 size;
	char name[48];	/* room to store /dir1/dir2/filename */

	int compression;

	/* Block infos */
	int num_blocks;
	PHYSFS_uint32 *block_alloc;
	PHYSFS_uint32 *block_startkey;
	PHYSFS_uint32 *block_length;
} ROFSentry;

/* rofsinfo -> rofsentry level1 -> rofsentry level2 -> rofsentry files */

typedef struct
{
	__PHYSFS_DirTree tree;	/* manages directory tree. */
	PHYSFS_Io *io;			/* the i/o interface for this archive. */

	PHYSFS_uint32 entryCount;
	ROFSentry dirs[2];
	ROFSentry *entries;
} ROFSinfo;

typedef struct {
	/* Current decryption infos */
	PHYSFS_uint32 curblock, curoffset;
	PHYSFS_uint32 curkey, blockindex;
	PHYSFS_uint8 xorkey, baseindex;
} rofs_decrypt_t;

enum {
	ROFS_STATE_UNPACK_BYTE,
	ROFS_STATE_UNPACK_BLOCK,
	ROFS_STATE_COPY_BLOCK,
	ROFS_STATE_ERROR
};

typedef struct {
	int state;	/* above enum */

	int src_num_bit, src_byte_num;
	PHYSFS_uint8 src_bytes[2];
	int lzss_pos, lzss_start, lzss_length, copy_length;
	PHYSFS_uint8 *lzss_dict;
} rofs_depack_t;

typedef struct
{
	ROFSentry *entry;
	PHYSFS_Io *io;

	PHYSFS_uint32 curPos;

	rofs_decrypt_t decrypt;
	rofs_depack_t depack;
} ROFSfileinfo;

typedef struct {
	PHYSFS_uint16 offset;
	PHYSFS_uint16 num_keys;
	PHYSFS_uint32 length;
	char ident[8];
} rofs_dat_file_t;

static PHYSFS_sint64 ROFS_read(PHYSFS_Io *io, void *buf, PHYSFS_uint64 len);

/*
 * Read an unsigned 32-bit int and swap to native byte order.
 */
static int readui32(PHYSFS_Io *io, PHYSFS_uint32 *val)
{
	PHYSFS_uint32 v;
	BAIL_IF_ERRPASS(!__PHYSFS_readAll(io, &v, sizeof (v)), 0);
	*val = PHYSFS_swapULE32(v);
	return 1;
} /* readui32 */


/*--- Decryption ---*/

static const unsigned short base_array[64]={
	0x00e6, 0x01a4, 0x00e6, 0x01c5,
	0x0130, 0x00e8, 0x03db, 0x008b,
	0x0141, 0x018e, 0x03ae, 0x0139,
	0x00f0, 0x027a, 0x02c9, 0x01b0,
	0x01f7, 0x0081, 0x0138, 0x0285,
	0x025a, 0x015b, 0x030f, 0x0335,
	0x02e4, 0x01f6, 0x0143, 0x00d1,
	0x0337, 0x0385, 0x007b, 0x00c6,
	0x0335, 0x0141, 0x0186, 0x02a1,
	0x024d, 0x0342, 0x01fb, 0x03e5,
	0x01b0, 0x006d, 0x0140, 0x00c0,
	0x0386, 0x016b, 0x020b, 0x009a,
	0x0241, 0x00de, 0x015e, 0x035a,
	0x025b, 0x0154, 0x0068, 0x02e8,
	0x0321, 0x0071, 0x01b0, 0x0232,
	0x02d9, 0x0263, 0x0164, 0x0290
};

static PHYSFS_uint8 rofs_next_key(PHYSFS_uint32 *key)
{
	*key *= 0x5d588b65;
	*key += 0x8000000b;

	return (*key >> 24);
}

static PHYSFS_uint8 rofs_decrypt_byte(ROFSfileinfo *finfo, PHYSFS_uint8 value)
{
	if (finfo->decrypt.blockindex>base_array[finfo->decrypt.baseindex]) {
		finfo->decrypt.baseindex = rofs_next_key(&finfo->decrypt.curkey) % 0x3f;
		finfo->decrypt.xorkey = rofs_next_key(&finfo->decrypt.curkey);
		finfo->decrypt.blockindex = 0;
	}
	finfo->decrypt.blockindex++;

	return (value ^ finfo->decrypt.xorkey);
}

static void rofs_decrypt_block_init(ROFSfileinfo *finfo)
{
	ROFSentry *entry = finfo->entry;

	finfo->decrypt.curoffset = 0;
	finfo->decrypt.curkey = entry->block_startkey[finfo->decrypt.curblock];
	finfo->decrypt.blockindex = 0;
	finfo->decrypt.xorkey = rofs_next_key(&finfo->decrypt.curkey);
	finfo->decrypt.baseindex = rofs_next_key(&finfo->decrypt.curkey) % 0x3f;
}


/*--- Decompression ---*/

static void rofs_depack_block_init(ROFSfileinfo *finfo)
{
	int i;

	if (!finfo->depack.lzss_dict) {
		finfo->depack.lzss_dict = (PHYSFS_uint8 *) allocator.Malloc(4096+256);
	}

	if (finfo->depack.lzss_dict) {
		for (i=0; i<256; i++) {
			memset(&(finfo->depack.lzss_dict)[i*16], i, 16);
		}
		memset(&(finfo->depack.lzss_dict)[4096], 0, 256);
	}

	finfo->depack.state = ROFS_STATE_UNPACK_BYTE;
	finfo->depack.src_num_bit = 8;
	finfo->depack.src_byte_num = 1;
	finfo->depack.src_bytes[0] = 0;
	finfo->depack.src_bytes[1] = 0;
	finfo->depack.lzss_pos = 0;
	finfo->depack.lzss_start = 0;
	finfo->depack.lzss_length = 0;
}

/*	if (__PHYSFS_platformRead(finfo->handle, &dummy, 1) != 1) {	\*/

#define ROFS_READ_BYTE() \
{	\
	PHYSFS_uint8 dummy;	\
	\
	if (finfo->decrypt.curoffset >= entry->block_length[finfo->decrypt.curblock]) {	\
		finfo->depack.state = ROFS_STATE_ERROR;	\
		break;	\
	}	\
	\
	if (finfo->io->read(finfo->io, &dummy, 1) != 1) {	\
		finfo->depack.state = ROFS_STATE_ERROR;	\
		break;	\
	}	\
	\
	dummy = rofs_decrypt_byte(finfo, dummy);	\
	finfo->decrypt.curoffset++;	\
	\
	finfo->depack.src_byte_num ^= 1;	\
	finfo->depack.src_bytes[finfo->depack.src_byte_num] = dummy;	\
}

static int rofs_depack_block(ROFSfileinfo *finfo, void *buffer,
							 PHYSFS_uint32 srcLength, PHYSFS_uint32 dstLength)
{
	ROFSentry *entry = finfo->entry;
	PHYSFS_uint8 *dstBuffer = (PHYSFS_uint8 *) buffer;
	PHYSFS_uint32 dstIndex = 0;

	if (!finfo->depack.lzss_dict) {
		return(0);
	}

	while ((dstIndex < dstLength) && (finfo->depack.state!=ROFS_STATE_ERROR)) {
		switch(finfo->depack.state) {
			case ROFS_STATE_UNPACK_BYTE:
			{
				PHYSFS_uint16 src_bitfield;

				if (finfo->depack.lzss_pos > 4095) {
					finfo->depack.lzss_pos = 0;
				}

				if (finfo->depack.src_num_bit==8) {
					finfo->depack.src_num_bit = 0;
					ROFS_READ_BYTE();
				}

				finfo->depack.src_num_bit++;

				src_bitfield =
				finfo->depack.src_bytes[finfo->depack.src_byte_num] << finfo->depack.src_num_bit;

				ROFS_READ_BYTE();

				src_bitfield |=
				finfo->depack.src_bytes[finfo->depack.src_byte_num] >> (8-finfo->depack.src_num_bit);

				if (src_bitfield & (1<<8)) {
					/* Init copy from lzss dict */
					int value2;

					value2 =
					finfo->depack.src_bytes[finfo->depack.src_byte_num] << finfo->depack.src_num_bit;

					ROFS_READ_BYTE();

					value2 |=
					finfo->depack.src_bytes[finfo->depack.src_byte_num] >> (8-finfo->depack.src_num_bit);

					finfo->depack.lzss_start = (value2 >> 4) & 0xfff;
					finfo->depack.lzss_start |= (src_bitfield & 0xff) << 4;
					finfo->depack.lzss_length = (value2 & 0x0f)+2;
					finfo->depack.copy_length = finfo->depack.lzss_length;
					finfo->depack.state = ROFS_STATE_UNPACK_BLOCK;
				} else {
					/* Copy byte */
					dstBuffer[dstIndex++] =
					finfo->depack.lzss_dict[finfo->depack.lzss_pos++] =
					src_bitfield;
				}
			}
			break;
			case ROFS_STATE_UNPACK_BLOCK:
			{
				/* Copy from dictionnary */
				dstBuffer[dstIndex++] =
				finfo->depack.lzss_dict[finfo->depack.lzss_start++];
				if (--finfo->depack.lzss_length <= 0) {
					finfo->depack.state = ROFS_STATE_COPY_BLOCK;
				}
			}
			break;
			case ROFS_STATE_COPY_BLOCK:
			{
				int i;
				PHYSFS_uint8 *src =
				&(dstBuffer[dstIndex - finfo->depack.copy_length]);

				for (i=0; i<finfo->depack.copy_length; i++) {
					finfo->depack.lzss_dict[finfo->depack.lzss_pos++] = *src++;
				}
				finfo->depack.state = ROFS_STATE_UNPACK_BYTE;
			}
			break;
		}
	}

	return(dstIndex);
}


/*--- Physfs driver ---*/

static PHYSFS_sint64 ROFS_read(PHYSFS_Io *io, void *buf, PHYSFS_uint64 len)
{
	ROFSfileinfo *finfo = (ROFSfileinfo *) io->opaque;
	ROFSentry *entry = finfo->entry;
	PHYSFS_uint32 curPos = finfo->curPos;
	PHYSFS_uint8 *dstBuffer = (PHYSFS_uint8 *) buf;
	int srcLength, dstLength;
	PHYSFS_sint64 retval = 0;
	PHYSFS_sint64 maxread = (PHYSFS_sint64) len;
	PHYSFS_sint64 avail = entry->size - finfo->curPos;

	if (avail < maxread)
		maxread = avail;

	BAIL_IF_ERRPASS(maxread == 0, 0);    /* quick rejection. */

	dstLength = maxread;
	while (dstLength>0) {
		/* Limit to number of bytes to read */
		srcLength = entry->block_length[finfo->decrypt.curblock] - finfo->decrypt.curoffset;

		if (entry->compression) {
			/* read max srcLength, or depack max 32K */
			int dstDepacked;

			/* Read till end of current block */
			dstDepacked = rofs_depack_block(finfo, dstBuffer, srcLength, dstLength);

			dstBuffer += dstDepacked;
			finfo->curPos += dstDepacked;
			dstLength -= dstDepacked;
		} else {
			int i;

			if (srcLength > dstLength) {
				srcLength = dstLength;
			}

			retval = finfo->io->read(finfo->io, dstBuffer, srcLength);

			for (i=0; i<retval; i++) {
				PHYSFS_uint8 c = rofs_decrypt_byte(finfo, *dstBuffer);
				*dstBuffer++ = c;
			}

			finfo->decrypt.curoffset += (PHYSFS_uint32) retval;
			finfo->curPos += (PHYSFS_uint32) retval;
			dstLength -= (PHYSFS_uint32) retval;
		}

		/* Next block ? */
		if (finfo->decrypt.curoffset >= entry->block_length[finfo->decrypt.curblock]) {
			finfo->decrypt.curblock++;
			rofs_decrypt_block_init(finfo);
			if (entry->compression) {
				rofs_depack_block_init(finfo);
			}
		}
	}

	return finfo->curPos - curPos;
} /* ROFS_read */


static PHYSFS_sint64 ROFS_write(PHYSFS_Io *io, const void *b, PHYSFS_uint64 len)
{
	BAIL(PHYSFS_ERR_READ_ONLY, -1);
} /* ROFS_write */


static PHYSFS_sint64 ROFS_tell(PHYSFS_Io *io)
{
	return ((ROFSfileinfo *) io->opaque)->curPos;
} /* ROFS_tell */


static int ROFS_seek(PHYSFS_Io *io, PHYSFS_uint64 offset)
{
	ROFSfileinfo *finfo = (ROFSfileinfo *) io->opaque;
	ROFSentry *entry = finfo->entry;
	int rc = 1, i, srcBlock, dstBlock, remaining;

	BAIL_IF(offset >= entry->size, PHYSFS_ERR_PAST_EOF, 0);

	/* Skip to a given 32KB block */
	srcBlock = finfo->curPos >> 15;
	dstBlock = offset >> 15;
	if ((srcBlock != dstBlock) || (offset < finfo->curPos)) {
		PHYSFS_uint64 startOffset;

		finfo->decrypt.curblock = dstBlock;

		rofs_decrypt_block_init(finfo);
		if (entry->compression) {
			rofs_depack_block_init(finfo);
		}

		/* Seek to start of block */
		startOffset = 0;
		for (i=0; i<dstBlock; i++) {
			startOffset += entry->block_length[i];
		}

		finfo->curPos = startOffset;

		finfo->io->seek(finfo->io, entry->startPos + startOffset);
	}

	if (!rc) {
		return(0);
	}

	/* Then advance in the block */
	remaining = offset - finfo->curPos;
	if (entry->compression) {
		for (i=0; i<remaining; i++) {
			PHYSFS_uint8 dummy;
			if (!rofs_depack_block(finfo, &dummy, 32768, 1)) {
				return(0);
			}
		}
	} else {
		/* Run decryption steps */
		for (i=0; i<remaining; i++) {
			PHYSFS_uint8 dummy;

			finfo->io->read(finfo->io, &dummy, 1);
			dummy = rofs_decrypt_byte(finfo, dummy);
		}
		finfo->curPos += i;
		finfo->decrypt.curoffset += i;
	}

	return(rc);
} /* ROFS_seek */


static PHYSFS_sint64 ROFS_length(PHYSFS_Io *io)
{
	ROFSfileinfo *finfo = (ROFSfileinfo *) io->opaque;
	return((PHYSFS_sint64) finfo->entry->size);
} /* ROFS_length */


static PHYSFS_Io *ROFS_duplicate(PHYSFS_Io *_io)
{
    ROFSfileinfo *origfinfo = (ROFSfileinfo *) _io->opaque;
    PHYSFS_Io *io = NULL;
    PHYSFS_Io *retval = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io));
    ROFSfileinfo *finfo = (ROFSfileinfo *) allocator.Malloc(sizeof (ROFSfileinfo));
    GOTO_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, ROFS_duplicate_failed);
    GOTO_IF(!finfo, PHYSFS_ERR_OUT_OF_MEMORY, ROFS_duplicate_failed);

    io = origfinfo->io->duplicate(origfinfo->io);
    if (!io) goto ROFS_duplicate_failed;
    finfo->io = io;
    finfo->entry = origfinfo->entry;
    finfo->curPos = 0;

		/* Init decryption */
	finfo->decrypt.curblock = 0;
	rofs_decrypt_block_init(finfo);

	/* Init decompression */
	if (finfo->entry->compression) {
		rofs_depack_block_init(finfo);
	}

	memcpy(retval, _io, sizeof (PHYSFS_Io));
    retval->opaque = finfo;
    return retval;

ROFS_duplicate_failed:
    if (finfo != NULL) allocator.Free(finfo);
    if (retval != NULL) allocator.Free(retval);
    if (io != NULL) io->destroy(io);
    return NULL;
} /* ROFS_duplicate */


static int ROFS_flush(PHYSFS_Io *io) { return 1;  /* no write support. */ }


static void ROFS_destroy(PHYSFS_Io *io)
{
	ROFSfileinfo *finfo = (ROFSfileinfo *) io->opaque;
	finfo->io->destroy(finfo->io);

	if (finfo->depack.lzss_dict) {
		allocator.Free(finfo->depack.lzss_dict);
		finfo->depack.lzss_dict = NULL;
	}

	allocator.Free(finfo);
	allocator.Free(io);
} /* ROFS_destroy */


static const PHYSFS_Io ROFS_Io =
{
	0x12345678, NULL,
	ROFS_read,
	ROFS_write,
	ROFS_seek,
	ROFS_tell,
	ROFS_length,
	ROFS_duplicate,
	ROFS_flush,
	ROFS_destroy
};


/* All rofs<n>.dat files start with this */
static const char rofs_id[21]={
	3, 0, 0, 0,
	1, 0, 0, 0,
	4, 0, 0, 0,
	0, 1, 1, 0,
	0, 4, 0, 0,
	0
};

static int isRofs(PHYSFS_Io *io)
{
	PHYSFS_uint8 buf[21];

	/* Check if known header */
	BAIL_IF_ERRPASS(!io->seek(io, 0), 0);

	if (io->read(io, buf, sizeof(rofs_id)) != sizeof(rofs_id))
		return 0;

	if (memcmp(buf, rofs_id, sizeof(rofs_id)) != 0)
		return 0;

	return(1);
} /* isRofs */


static int rofs_read_filename(PHYSFS_Io *io, char *dst, PHYSFS_uint32 dst_len)
{
	int i = 0;

	memset(dst, '\0', dst_len);

	do {
		char c;

		if (!__PHYSFS_readAll(io, &c, 1))
			return(0);

		dst[i] = c;
	} while (dst[i++] != '\0');

	return(1);
}


static ROFSentry *rofs_load_entry(ROFSinfo *info)
{
    PHYSFS_Io *io = info->io;
	rofs_dat_file_t fileHeader;
	char shortname[16];
	PHYSFS_sint64 location;
	int i;
	ROFSentry entry, *retval;

    memset(&entry, '\0', sizeof (entry));

	BAIL_IF_ERRPASS(!readui32(io, &entry.startPos), 0);
	entry.startPos <<= 3;

	BAIL_IF_ERRPASS(!readui32(io, &entry.size), 0);
	entry.size = 0; /* This was compressed size */

	if (!rofs_read_filename(io, shortname, sizeof(shortname))) {
		return 0;
	}

	/* Generate long filename */
	i = snprintf(entry.name, sizeof(entry.name), "%s/%s/%s", info->dirs[0].name,
			info->dirs[1].name, shortname);

	/* Bail if long filename is too long */
	BAIL_IF(i >= (int)sizeof(entry.name), PHYSFS_ERR_OUT_OF_MEMORY, 0);

	/* Now go read real file size */
	location = io->tell(io);

	BAIL_IF_ERRPASS(!io->seek(io, entry.startPos), 0);

	io->read(io, &fileHeader, sizeof(fileHeader));

	fileHeader.offset = PHYSFS_swapULE16(fileHeader.offset);
	fileHeader.num_keys = PHYSFS_swapULE16(fileHeader.num_keys);
	fileHeader.length = PHYSFS_swapULE32(fileHeader.length);
	for (i=0; i<8; i++) {
		fileHeader.ident[i] ^= fileHeader.ident[7];
	}

	entry.size = fileHeader.length;

	/*--- Decryption info ---*/
	entry.num_blocks = fileHeader.num_keys;
	entry.block_alloc = (PHYSFS_uint32 *) allocator.Malloc(fileHeader.num_keys*2*sizeof(PHYSFS_uint32));
    BAIL_IF(!entry.block_alloc, PHYSFS_ERR_OUT_OF_MEMORY, 0);

	io->read(io, entry.block_alloc, fileHeader.num_keys*2*sizeof(PHYSFS_uint32));

	entry.block_startkey = entry.block_alloc;
	entry.block_length = &entry.block_alloc[fileHeader.num_keys];
	for (i=0; i<entry.num_blocks; i++) {
		entry.block_startkey[i] = PHYSFS_swapULE32(entry.block_startkey[i]);
		entry.block_length[i] = PHYSFS_swapULE32(entry.block_length[i]);
	}

	/*--- Decompression info ---*/
	entry.compression = (strcmp("Hi_Comp", fileHeader.ident)==0);

	/* Fix offset to start of encrypted/packed data */
	entry.startPos += fileHeader.offset;

	if (!io->seek(io, location))
		goto failed;

    retval = (ROFSentry *) __PHYSFS_DirTreeAdd(&info->tree, entry.name, 0);
	GOTO_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, failed);

    /* Move the data we already read into place in the official object. */
    memcpy(((PHYSFS_uint8 *) retval) + sizeof (__PHYSFS_DirTreeEntry),
           ((PHYSFS_uint8 *) &entry) + sizeof (__PHYSFS_DirTreeEntry),
           sizeof (*retval) - sizeof (__PHYSFS_DirTreeEntry));

	return retval;  /* success. */

failed:
	allocator.Free(entry.block_alloc);
	return NULL;  /* failure. */
} /* rofs_load_entry */


static int rofs_load_header(ROFSinfo *info)
{
    PHYSFS_Io *io = info->io;
	PHYSFS_uint32 dir_location, dir_length;

	/* Till now all tested rofs.dat file archives have 2 levels */
	if (!rofs_read_filename(io, info->dirs[0].name, sizeof(info->dirs[0].name))) {
		return 0;
	}

	BAIL_IF_ERRPASS(!readui32(io, &dir_location), -1);
	dir_location <<= 3;

	BAIL_IF_ERRPASS(!readui32(io, &dir_length), -1);

	if (!rofs_read_filename(io, info->dirs[1].name, sizeof(info->dirs[1].name))) {
		return 0;
	}

	info->dirs[0].startPos = info->dirs[1].startPos = dir_location;
	info->dirs[0].size = info->dirs[1].size = dir_length;

	return(1);
}


static int rofs_load_entries(ROFSinfo *info)
{
    PHYSFS_Io *io = info->io;
	PHYSFS_uint32 i;

	BAIL_IF_ERRPASS(!io->seek(io, sizeof(rofs_id)), 0);
	BAIL_IF_ERRPASS(!rofs_load_header(info), 0);

	BAIL_IF_ERRPASS(!io->seek(io, info->dirs[0].startPos), 0);
	BAIL_IF_ERRPASS(!readui32(io, &info->entryCount), -1);

	for (i = 0; i < info->entryCount; i++)
	{
		ROFSentry *entry = rofs_load_entry(info);
        BAIL_IF_ERRPASS(!entry, 0);
	} /* for */

	return 1;
} /* rofs_load_entries */


static void *ROFS_openArchive(PHYSFS_Io *io, const char *name, int forWriting, int *claimed)
{
	ROFSinfo *info = NULL;

	assert(io != NULL);  /* shouldn't ever happen. */

	BAIL_IF(forWriting, PHYSFS_ERR_READ_ONLY, NULL);
	BAIL_IF_ERRPASS(!isRofs(io), NULL);

	*claimed = 1;

	info = (ROFSinfo *) allocator.Malloc(sizeof (ROFSinfo));
	BAIL_IF(!info, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
	memset(info, '\0', sizeof (ROFSinfo));

	info->io = io;

	if (!__PHYSFS_DirTreeInit(&info->tree, sizeof (ROFSentry), 1, 1))
        goto ROFS_openarchive_failed;

	if (!rofs_load_entries(info))
		goto ROFS_openarchive_failed;

	assert(info->tree.root->sibling == NULL);

	return info;

ROFS_openarchive_failed:
	if (info != NULL)
		allocator.Free(info);

	return NULL;
} /* ROFS_openArchive */


static inline ROFSentry *rofs_find_entry(ROFSinfo *info, const char *path)
{
    return (ROFSentry *) __PHYSFS_DirTreeFind(&info->tree, path);
} /* rofs_find_entry */


static PHYSFS_Io *ROFS_openRead(void *opaque, const char *filename)
{
	PHYSFS_Io *retval = NULL;
	ROFSinfo *info = (ROFSinfo *) opaque;
	ROFSentry *entry = rofs_find_entry(info, filename);
	ROFSfileinfo *finfo = NULL;

	BAIL_IF_ERRPASS(!entry, NULL);

	retval = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io));
	GOTO_IF(!retval, PHYSFS_ERR_OUT_OF_MEMORY, ROFS_openRead_failed);

	finfo = (ROFSfileinfo *) allocator.Malloc(sizeof (ROFSfileinfo));
	GOTO_IF(!finfo, PHYSFS_ERR_OUT_OF_MEMORY, ROFS_openRead_failed);
	memset(finfo, '\0', sizeof (ROFSfileinfo));

	finfo->io = info->io->duplicate(info->io);
	GOTO_IF_ERRPASS(!finfo->io, ROFS_openRead_failed);

	if (!finfo->io->seek(finfo->io, entry->startPos))
		goto ROFS_openRead_failed;

	finfo->entry = entry;
	finfo->curPos = 0;

	/* Init decryption */
	finfo->decrypt.curblock = 0;
	rofs_decrypt_block_init(finfo);

	/* Init decompression */
	if (entry->compression) {
		rofs_depack_block_init(finfo);
	}

	memcpy(retval, &ROFS_Io, sizeof (PHYSFS_Io));
	retval->opaque = finfo;

	return retval;

ROFS_openRead_failed:
	if (finfo != NULL)
	{
		if (finfo->io != NULL)
			finfo->io->destroy(finfo->io);

		allocator.Free(finfo);
	} /* if */

	if (retval != NULL)
		allocator.Free(retval);

	return NULL;
} /* ROFS_openRead */


static void ROFS_closeArchive(void *opaque)
{
	ROFSinfo *info = (ROFSinfo *) opaque;

	if (!info)
		return;

	if (info->io)
		info->io->destroy(info->io);

    __PHYSFS_DirTreeDeinit(&info->tree);

	allocator.Free(info);
} /* ROFS_closeArchive */


const PHYSFS_Archiver __PHYSFS_Archiver_ROFS =
{
	CURRENT_PHYSFS_ARCHIVER_API_VERSION,
	{
		"ROFS",
		"Resident Evil 3 ROFS format",
		"Patrice Mandin <patmandin@gmail.com>",
		"http://pmandin.atari.org/",
		0	/* supportsSymlinks */
	},
	ROFS_openArchive,
	UNPK_enumerate,
	ROFS_openRead,
    UNPK_openWrite,
    UNPK_openAppend,
    UNPK_remove,
    UNPK_mkdir,
	UNPK_stat,
	ROFS_closeArchive
};

#endif  /* defined PHYSFS_SUPPORTS_ROFS */

/* end of physfs_archiver_rofs.c ... */
