481 lines
14 KiB
C
481 lines
14 KiB
C
#include "psf2fs.h"
|
|
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <zlib.h>
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
#define MYMAXPATH (1024)
|
|
|
|
struct SOURCE_FILE {
|
|
uint8_t * reserved_data;
|
|
int reserved_size;
|
|
struct SOURCE_FILE *next;
|
|
};
|
|
|
|
struct DIR_ENTRY {
|
|
char name[37];
|
|
struct DIR_ENTRY *subdir;
|
|
int length;
|
|
int block_size;
|
|
struct SOURCE_FILE *source;
|
|
int *offset_table;
|
|
struct DIR_ENTRY *next;
|
|
};
|
|
|
|
struct CACHEBLOCK {
|
|
struct SOURCE_FILE *from_source;
|
|
int from_offset;
|
|
char *uncompressed_data;
|
|
int uncompressed_size;
|
|
};
|
|
|
|
struct PSF2FS {
|
|
struct SOURCE_FILE *sources;
|
|
struct DIR_ENTRY *dir;
|
|
struct CACHEBLOCK cacheblock;
|
|
|
|
int adderror;
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
static void source_cleanup_free(struct SOURCE_FILE *source) {
|
|
while(source) {
|
|
struct SOURCE_FILE *next = source->next;
|
|
if(source->reserved_data) free( source->reserved_data );
|
|
free( source );
|
|
source = next;
|
|
}
|
|
}
|
|
|
|
static void dir_cleanup_free(struct DIR_ENTRY *dir) {
|
|
while(dir) {
|
|
struct DIR_ENTRY *next = dir->next;
|
|
if(dir->subdir) dir_cleanup_free(dir->subdir);
|
|
free( dir );
|
|
dir = next;
|
|
}
|
|
}
|
|
|
|
static void cache_cleanup(struct CACHEBLOCK *cacheblock) {
|
|
if(!cacheblock) return;
|
|
if(cacheblock->uncompressed_data) free( cacheblock->uncompressed_data );
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
void *psf2fs_create(void) {
|
|
struct PSF2FS *fs;
|
|
fs = ( struct PSF2FS * ) malloc( sizeof( struct PSF2FS ) );
|
|
if(!fs) return NULL;
|
|
memset(fs, 0, sizeof(struct PSF2FS));
|
|
return fs;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
void psf2fs_delete(void *psf2fs) {
|
|
struct PSF2FS *fs = (struct PSF2FS*)psf2fs;
|
|
if(fs->sources) source_cleanup_free(fs->sources);
|
|
if(fs->dir) dir_cleanup_free(fs->dir);
|
|
cache_cleanup(&(fs->cacheblock));
|
|
free( fs );
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
static int isdirsep(char c) { return (c == '/' || c == '\\' || c == '|' || c == ':'); }
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
static unsigned read32lsb(const uint8_t * foo) {
|
|
return (
|
|
((foo[0] & 0xFF) << 0) |
|
|
((foo[1] & 0xFF) << 8) |
|
|
((foo[2] & 0xFF) << 16) |
|
|
((foo[3] & 0xFF) << 24)
|
|
);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
static int __memicmp(const char * a, const char * b, int length)
|
|
{
|
|
int o, p;
|
|
for (o = 0; o < length; o++) {
|
|
p = tolower(a[o]) - tolower(b[o]);
|
|
if (p) return p;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
static struct DIR_ENTRY *finddirentry(
|
|
struct DIR_ENTRY *dir,
|
|
const char *name,
|
|
int name_l
|
|
) {
|
|
if(name_l > 36) return NULL;
|
|
while(dir) {
|
|
if(!__memicmp(dir->name, name, name_l) && dir->name[name_l] == 0) return dir;
|
|
dir = dir->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Make a DIR_ENTRY list for a given file and Reserved offset.
|
|
// Recurses subdirectories also.
|
|
// All entries are set to point to the given SOURCE_FILE.
|
|
//
|
|
static struct DIR_ENTRY *makearchivedir(
|
|
struct PSF2FS *fs,
|
|
int offset,
|
|
struct SOURCE_FILE *source
|
|
) {
|
|
struct DIR_ENTRY *dir = NULL;
|
|
const uint8_t *file = source->reserved_data;
|
|
int n, num;
|
|
if(offset < 0) goto corrupt;
|
|
if(offset >= source->reserved_size) { goto corrupt; }
|
|
if((offset + 4) > source->reserved_size) { goto corrupt; }
|
|
num = read32lsb(file + offset);
|
|
offset += 4;
|
|
if(num < 0) goto corrupt;
|
|
for(n = 0; n < num; n++) {
|
|
int o, u, b;
|
|
if((offset + 48) > source->reserved_size) { goto corrupt; }
|
|
{ struct DIR_ENTRY *entry = ( struct DIR_ENTRY * ) malloc( sizeof( struct DIR_ENTRY ) );
|
|
if(!entry) goto outofmemory;
|
|
memset(entry, 0, sizeof(struct DIR_ENTRY));
|
|
entry->next = dir;
|
|
dir = entry;
|
|
}
|
|
memcpy(dir->name, file + offset, 36);
|
|
o = read32lsb(file + offset + 36);
|
|
u = read32lsb(file + offset + 40);
|
|
b = read32lsb(file + offset + 44);
|
|
offset += 48;
|
|
if(o < 0) goto corrupt;
|
|
if(u < 0) goto corrupt;
|
|
if(b < 0) goto corrupt;
|
|
if(o && o < offset) {
|
|
// char s[100];
|
|
// sprintf(s,"q[o=%08X offset=%08X]",o,offset);
|
|
// errormessageadd(fs, s);
|
|
goto corrupt;
|
|
}
|
|
// if this new entry describes a subdirectory:
|
|
if(u == 0 && b == 0 && o != 0) {
|
|
dir->subdir = makearchivedir(fs, o, source);
|
|
if(fs->adderror) goto error;
|
|
// if(!dir->subdir) goto error;
|
|
// if this new entry describes a zero-length file:
|
|
} else if(u == 0 || b == 0 || o == 0) {
|
|
// fields were zero anyway
|
|
// if this new entry describes a real source file:
|
|
} else {
|
|
int i;
|
|
int blocks = (u + (b-1)) / b;
|
|
int dataofs = o + 4 * blocks;
|
|
if(dataofs >= source->reserved_size) { goto corrupt; }
|
|
// record the info
|
|
dir->length = u;
|
|
dir->block_size = b;
|
|
dir->source = source;
|
|
dir->offset_table = (int *) malloc( ( blocks + 1 ) * sizeof( int ) );
|
|
if(!dir->offset_table) goto outofmemory;
|
|
for(i = 0; i < blocks; i++) {
|
|
int cbs;
|
|
if((o + 4) > source->reserved_size) { goto corrupt; }
|
|
cbs = read32lsb(file + o);
|
|
o += 4;
|
|
dir->offset_table[i] = dataofs;
|
|
dataofs += cbs;
|
|
}
|
|
dir->offset_table[i] = dataofs;
|
|
}
|
|
}
|
|
//success:
|
|
return dir;
|
|
|
|
corrupt:
|
|
goto error;
|
|
outofmemory:
|
|
goto error;
|
|
error:
|
|
dir_cleanup_free(dir);
|
|
fs->adderror = 1;
|
|
return NULL;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Merge two SOURCE_FILE lists.
|
|
// Guaranteed to succeed and not to free anything.
|
|
//
|
|
static struct SOURCE_FILE *mergesource(
|
|
struct SOURCE_FILE *to,
|
|
struct SOURCE_FILE *from
|
|
) {
|
|
struct SOURCE_FILE *to_tail;
|
|
if(!to && !from) return NULL;
|
|
if(!to) {
|
|
struct SOURCE_FILE *t;
|
|
t = to; to = from; from = t;
|
|
}
|
|
to_tail = to;
|
|
while(to_tail->next) { to_tail = to_tail->next; }
|
|
to_tail->next = from;
|
|
return to;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Merge two DIR_ENTRY lists.
|
|
// Guaranteed to succeed. May free some structures.
|
|
//
|
|
static struct DIR_ENTRY *mergedir(
|
|
struct DIR_ENTRY *to,
|
|
struct DIR_ENTRY *from
|
|
) {
|
|
// will traverse "from", and add to "to".
|
|
while(from) {
|
|
struct DIR_ENTRY *entry_to;
|
|
struct DIR_ENTRY *entry_from;
|
|
entry_from = from;
|
|
from = from->next;
|
|
// delink entry_from
|
|
entry_from->next = NULL;
|
|
// look for a duplicate entry in "to"
|
|
entry_to = finddirentry(to, entry_from->name, (int)strlen(entry_from->name));
|
|
// if there is one, do something fancy and then free entry_from.
|
|
if(entry_to) {
|
|
// if both are subdirs, merge the subdirs
|
|
if((entry_to->subdir) && (entry_from->subdir)) {
|
|
entry_to->subdir = mergedir(entry_to->subdir, entry_from->subdir);
|
|
entry_from->subdir = NULL;
|
|
// if both are files, copy over the info
|
|
} else if((!(entry_to->subdir)) && (!(entry_from->subdir))) {
|
|
entry_to->length = entry_from->length;
|
|
entry_to->block_size = entry_from->block_size;
|
|
entry_to->source = entry_from->source;
|
|
if(entry_to->offset_table) free( entry_to->offset_table );
|
|
entry_to->offset_table = entry_from->offset_table;
|
|
entry_from->offset_table = NULL;
|
|
// if one's a subdir but the other's not, we lose "from". this is fine.
|
|
}
|
|
dir_cleanup_free(entry_from);
|
|
entry_from = NULL;
|
|
// otherwise, just relink to the top of "to"
|
|
} else {
|
|
entry_from->next = to;
|
|
to = entry_from;
|
|
}
|
|
}
|
|
return to;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// only modifies *psource and *pdir on success
|
|
//
|
|
static int addarchive(
|
|
struct PSF2FS *fs,
|
|
const uint8_t *reserved_data,
|
|
int reserved_size,
|
|
struct SOURCE_FILE **psource,
|
|
struct DIR_ENTRY **pdir
|
|
) {
|
|
struct SOURCE_FILE *source = *psource;
|
|
struct DIR_ENTRY *dir = *pdir;
|
|
// these relate to the current file
|
|
struct SOURCE_FILE *this_source = NULL;
|
|
struct DIR_ENTRY *this_dir = NULL;
|
|
|
|
// default to no error
|
|
fs->adderror = 0;
|
|
|
|
// create a source entry for this psf2
|
|
this_source = ( struct SOURCE_FILE * ) malloc( sizeof( struct SOURCE_FILE ) );
|
|
if(!this_source) goto outofmemory;
|
|
this_source->next = NULL;
|
|
this_source->reserved_data = ( uint8_t * ) malloc( reserved_size );
|
|
if(!this_source->reserved_data) goto outofmemory;
|
|
memcpy(this_source->reserved_data, reserved_data, reserved_size);
|
|
this_source->reserved_size = reserved_size;
|
|
this_dir = makearchivedir(fs, 0, this_source);
|
|
if(fs->adderror) goto error;
|
|
|
|
// success
|
|
// now merge everything
|
|
*psource = mergesource(source, this_source);
|
|
*pdir = mergedir(dir, this_dir);
|
|
//success:
|
|
return 0;
|
|
|
|
outofmemory:
|
|
goto error;
|
|
error:
|
|
if(dir) dir_cleanup_free(dir);
|
|
if(source) source_cleanup_free(source);
|
|
if(this_dir) dir_cleanup_free(this_dir);
|
|
if(this_source) source_cleanup_free(this_source);
|
|
return -1;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
//
|
|
//
|
|
int psf2fs_load_callback(void * psf2fs, const uint8_t * exe, size_t exe_size,
|
|
const uint8_t * reserved, size_t reserved_size) {
|
|
struct PSF2FS *fs = (struct PSF2FS*)psf2fs;
|
|
(void)exe;
|
|
(void)exe_size;
|
|
return addarchive(fs, reserved, (int)reserved_size, &(fs->sources), &(fs->dir));
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
//
|
|
//
|
|
static int virtual_read(struct PSF2FS *fs, struct DIR_ENTRY *entry, int offset, char *buffer, int length) {
|
|
int length_read = 0;
|
|
int r;
|
|
unsigned long destlen;
|
|
if(offset >= entry->length) return 0;
|
|
if((offset + length) > entry->length) length = entry->length - offset;
|
|
while(length_read < length) {
|
|
// get info on the current block
|
|
int blocknum = offset / entry->block_size;
|
|
int ofs_within_block = offset % entry->block_size;
|
|
int canread;
|
|
int block_zofs = entry->offset_table[blocknum];
|
|
int block_zsize = entry->offset_table[blocknum+1] - block_zofs;
|
|
int block_usize;
|
|
if(block_zofs <= 0 || block_zofs >= entry->source->reserved_size) goto bounds;
|
|
if((block_zofs+block_zsize) > entry->source->reserved_size) goto bounds;
|
|
|
|
// get the actual uncompressed size of this block
|
|
block_usize = entry->length - (blocknum * entry->block_size);
|
|
if(block_usize > entry->block_size) block_usize = entry->block_size;
|
|
|
|
// if it's not already in the cache block, read it
|
|
if(
|
|
(fs->cacheblock.from_offset != block_zofs) ||
|
|
(fs->cacheblock.from_source != entry->source)
|
|
) {
|
|
// invalidate cache without freeing buffer
|
|
fs->cacheblock.from_source = NULL;
|
|
|
|
// make sure there's a buffer allocated
|
|
// but only reallocate if the size is different
|
|
if(fs->cacheblock.uncompressed_size != block_usize) {
|
|
fs->cacheblock.uncompressed_size = 0;
|
|
if(fs->cacheblock.uncompressed_data) {
|
|
free( fs->cacheblock.uncompressed_data );
|
|
fs->cacheblock.uncompressed_data = NULL;
|
|
}
|
|
fs->cacheblock.uncompressed_data = ( char * ) malloc( block_usize );
|
|
if(!fs->cacheblock.uncompressed_data) goto outofmemory;
|
|
fs->cacheblock.uncompressed_size = block_usize;
|
|
}
|
|
destlen = block_usize;
|
|
// attempt decompress
|
|
r = uncompress((unsigned char *) fs->cacheblock.uncompressed_data, &destlen, (const unsigned char *) entry->source->reserved_data + block_zofs, block_zsize);
|
|
if(r != Z_OK || destlen != block_usize) {
|
|
// char s[999];
|
|
// sprintf(s,"zdata=%02X %02X %02X blockz=%d blocku=%d destlenout=%d", zdata[0], zdata[1], zdata[2], block_zsize, block_usize, destlen);
|
|
// errormessageadd(fs, s);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
// at this point, we can read whatever we want out of the cacheblock
|
|
canread = fs->cacheblock.uncompressed_size - ofs_within_block;
|
|
if(canread > (length - length_read)) canread = length - length_read;
|
|
|
|
// copy
|
|
memcpy(buffer, fs->cacheblock.uncompressed_data + ofs_within_block, canread);
|
|
|
|
// advance pointers/counters
|
|
offset += canread;
|
|
length_read += canread;
|
|
buffer += canread;
|
|
}
|
|
|
|
//success:
|
|
return length_read;
|
|
|
|
bounds:
|
|
goto error;
|
|
outofmemory:
|
|
goto error;
|
|
error:
|
|
// if cacheblock was invalidated, we can free it
|
|
if(!fs->cacheblock.from_source) {
|
|
fs->cacheblock.uncompressed_size = 0;
|
|
if(fs->cacheblock.uncompressed_data) {
|
|
free( fs->cacheblock.uncompressed_data );
|
|
fs->cacheblock.uncompressed_data = NULL;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
//
|
|
//
|
|
int psf2fs_virtual_readfile(void *psf2fs, const char *path, int offset, char *buffer, int length) {
|
|
struct PSF2FS *fs = (struct PSF2FS*)psf2fs;
|
|
struct DIR_ENTRY *entry = fs->dir;
|
|
|
|
|
|
if(!path) goto invalidarg;
|
|
if(offset < 0) goto invalidarg;
|
|
if(!buffer) goto invalidarg;
|
|
if(length < 0) goto invalidarg;
|
|
|
|
for(;;) {
|
|
int l;
|
|
int need_dir;
|
|
if(!entry) goto pathnotfound;
|
|
while(isdirsep(*path)) path++;
|
|
for(l = 0;; l++) {
|
|
if(!path[l]) { need_dir = 0; break; }
|
|
if(isdirsep(path[l])) { need_dir = 1; break; }
|
|
}
|
|
entry = finddirentry(entry, path, l);
|
|
if(!entry) goto pathnotfound;
|
|
if(!need_dir) break;
|
|
entry = entry->subdir;
|
|
path += l;
|
|
}
|
|
|
|
// if we "found" a file but it's a directory, then we didn't find it
|
|
if(entry->subdir) goto pathnotfound;
|
|
|
|
// special case: if requested length is 0, return the total file length
|
|
if(!length) return entry->length;
|
|
|
|
// otherwise, read from source
|
|
return virtual_read(fs, entry, offset, buffer, length);
|
|
|
|
pathnotfound:
|
|
goto error;
|
|
invalidarg:
|
|
goto error;
|
|
error:
|
|
return -1;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|