#include "psf2fs.h" #include #include #include #include ///////////////////////////////////////////////////////////////////////////// #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; } /////////////////////////////////////////////////////////////////////////////