#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 void makelibpath(const char *path, const char *libpath, char *finalpath, int finalpath_length) {
int l;
int p_l = 0;
for(l = 0; path[l]; l++) { if(isdirsep(path[l])) { p_l = l + 1; } }
while(isdirsep(*libpath)) libpath++;
if(!finalpath_length) return;
*finalpath = 0;
if(p_l > (finalpath_length - 1)) p_l = (finalpath_length - 1);
if(p_l) {
memcpy(finalpath, path, p_l);
finalpath[p_l] = 0;
finalpath += p_l;
finalpath_length -= p_l;
}
if(!finalpath_length) return;
strncpy(finalpath, libpath, finalpath_length);
finalpath[finalpath_length - 1] = 0;
}
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;
}
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) {
goto corrupt;
}
if(u == 0 && b == 0 && o != 0) {
dir->subdir = makearchivedir(fs, o, source);
if(fs->adderror) goto error;
} else if(u == 0 || b == 0 || o == 0) {
} else {
int i;
int blocks = (u + (b-1)) / b;
int dataofs = o + 4 * blocks;
if(dataofs >= source->reserved_size) { goto corrupt; }
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;
}
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;
}
static struct DIR_ENTRY *mergedir(
struct DIR_ENTRY *to,
struct DIR_ENTRY *from
) {
while(from) {
struct DIR_ENTRY *entry_to;
struct DIR_ENTRY *entry_from;
entry_from = from;
from = from->next;
entry_from->next = NULL;
entry_to = finddirentry(to, entry_from->name, strlen(entry_from->name));
if(entry_to) {
if((entry_to->subdir) && (entry_from->subdir)) {
entry_to->subdir = mergedir(entry_to->subdir, entry_from->subdir);
entry_from->subdir = NULL;
} 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;
}
dir_cleanup_free(entry_from);
entry_from = NULL;
} else {
entry_from->next = to;
to = entry_from;
}
}
return to;
}
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;
struct SOURCE_FILE *this_source = NULL;
struct DIR_ENTRY *this_dir = NULL;
fs->adderror = 0;
this_source = ( struct SOURCE_FILE * ) malloc( sizeof( struct SOURCE_FILE ) );
if(!this_source) goto outofmemory;
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_source->next = NULL;
this_dir = makearchivedir(fs, 0, this_source);
if(fs->adderror) goto error;
*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, 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) {
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;
block_usize = entry->length - (blocknum * entry->block_size);
if(block_usize > entry->block_size) block_usize = entry->block_size;
if(
(fs->cacheblock.from_offset != block_zofs) ||
(fs->cacheblock.from_source != entry->source)
) {
fs->cacheblock.from_source = NULL;
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;
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) {
goto error;
}
}
canread = fs->cacheblock.uncompressed_size - ofs_within_block;
if(canread > (length - length_read)) canread = length - length_read;
memcpy(buffer, fs->cacheblock.uncompressed_data + ofs_within_block, canread);
offset += canread;
length_read += canread;
buffer += canread;
}
success:
return length_read;
bounds:
goto error;
outofmemory:
goto error;
error:
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(entry->subdir) goto pathnotfound;
if(!length) return entry->length;
return virtual_read(fs, entry, offset, buffer, length);
pathnotfound:
goto error;
invalidarg:
goto error;
error:
return -1;
}