Browse Source

Implemented C version of psf2fs

master
Christopher Snowhill 7 years ago
parent
commit
a0de859e99
3 changed files with 525 additions and 2 deletions
  1. +498
    -0
      psf2fs.c
  2. +23
    -0
      psf2fs.h
  3. +4
    -2
      psflib.pro

+ 498
- 0
psf2fs.c View File

@@ -0,0 +1,498 @@
#include "psf2fs.h"

#include <ctype.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;
}

/////////////////////////////////////////////////////////////////////////////
//
// 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, 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->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;

// 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) {
(void)exe;
(void)exe_size;
struct PSF2FS *fs = (struct PSF2FS*)psf2fs;
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) {
// 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;
}

/////////////////////////////////////////////////////////////////////////////

+ 23
- 0
psf2fs.h View File

@@ -0,0 +1,23 @@
#ifndef PSF2FS_H
#define PSF2FS_H

#include "psflib.h"

#ifdef __cplusplus
extern "C" {
#endif

void * psf2fs_create();

void psf2fs_delete( void * );

int psf2fs_load_callback(void * psf2vfs, const uint8_t * exe, size_t exe_size,
const uint8_t * reserved, size_t reserved_size);

int psf2fs_virtual_readfile(void *psf2vfs, const char *path, int offset, char *buffer, int length);

#ifdef __cplusplus
}
#endif

#endif

+ 4
- 2
psflib.pro View File

@@ -11,9 +11,11 @@ TEMPLATE = lib
CONFIG += staticlib

SOURCES += \
psflib.c
psflib.c \
psf2fs.c

HEADERS += psflib.h
HEADERS += psflib.h \
psf2fs.h
unix:!symbian {
maemo5 {
target.path = /opt/usr/lib


Loading…
Cancel
Save