Browse Source

Initial commit

master
Christopher Snowhill 7 years ago
commit
8b2fea61d0
3 changed files with 562 additions and 0 deletions
  1. +467
    -0
      psflib.c
  2. +71
    -0
      psflib.h
  3. +24
    -0
      psflib.pro

+ 467
- 0
psflib.c View File

@@ -0,0 +1,467 @@
#include "psflib.h"

#include <string.h>
#include <stdlib.h>

#include <zlib.h>

const char * strrpbrk( const char * s, const char * accept)
{
const char * start;

if ( !s || !*s || !accept || !*accept ) return NULL;

start = s;
s += strlen( s ) - 1;

while (s >= start)
{
const char *a = accept;
while (*a != '\0')
if (*a++ == *s)
return s;
--s;
}

return NULL;
}

enum { max_recursion_depth = 30 };

typedef struct psf_load_state
{
int depth;

unsigned char allowed_version;

char * base_path;
const psf_file_callbacks * file_callbacks;

psf_load_callback load_target;
void * load_context;

psf_info_callback info_target;
void * info_context;

char lib_name_temp[32];
} psf_load_state;

static int psf_load_internal( psf_load_state * state, const char * file_name );

int psf_load( const char * uri, const psf_file_callbacks * file_callbacks, uint8_t allowed_version,
psf_load_callback load_target, void * load_context, psf_info_callback info_target, void * info_context )
{
int rval;

psf_load_state state;

const char * file_name;

if ( !uri || !*uri || !file_callbacks || !file_callbacks->path_separators || !*file_callbacks->path_separators || !file_callbacks->fopen ||
!file_callbacks->fread || !file_callbacks->fseek || !file_callbacks->fclose || !file_callbacks->ftell ) return -1;

state.depth = 0;
state.allowed_version = allowed_version;
state.file_callbacks = file_callbacks;
state.load_target = load_target;
state.load_context = load_context;
state.info_target = info_target;
state.info_context = info_context;

state.base_path = strdup( uri );
if ( !state.base_path ) return -1;

file_name = strrpbrk( uri, file_callbacks->path_separators );

if ( file_name )
{
++file_name;
state.base_path[ file_name - uri ] = '\0';
}
else
{
state.base_path[ 0 ] = '\0';
}

rval = psf_load_internal( &state, file_name );

free( state.base_path );

return rval;
}

typedef struct psf_tag psf_tag;

struct psf_tag {
char * name;
char * value;
psf_tag * next, *prev;
};

#define FIELDS_SPLIT 6
static const char * fields_to_split[FIELDS_SPLIT] = {"ARTIST", "ALBUM ARTIST", "PRODUCER", "COMPOSER", "PERFORMER", "GENRE"};

static int check_split_value( const char * name )
{
unsigned i;
for ( i = 0; i < FIELDS_SPLIT; i++ )
{
if ( !strcasecmp( name, fields_to_split[ i ] ) ) return 1;
}
return 0;
}

static psf_tag * find_tag( psf_tag * tags, const char * name )
{
if ( tags && name && *name )
{
while ( tags )
{
if ( !strcasecmp( tags->name, name ) ) return tags;
tags = tags->next;
}
}

return NULL;
}

static void free_tags( psf_tag * tags )
{
psf_tag * tag = tags, * next;
while ( tag )
{
next = tag->next;
if ( tag->name ) free( tag->name );
if ( tag->value ) free( tag->value );
free( tag );
tag = next;
}
}

static psf_tag * add_tag_multi( psf_tag * tags, const char * name, const char ** values, int values_count )
{
psf_tag * footer;
psf_tag * tag;

int i;

if ( !name || !*name || !values || !values_count || !*values ) return NULL;

footer = tags;

tag = find_tag( tags, name );
if ( !tag )
{
tag = calloc(1, sizeof(psf_tag));
if (!tag) return footer;
tag->name = strdup( name );
if ( !tag->name )
{
free( tag );
return footer;
}
tag->next = tags;
if ( tags ) tags->prev = tag;
footer = tag;
}
if ( tag->value )
{
size_t old_length = strlen(tag->value);
char * new_value = (char *) realloc( tag->value, old_length + strlen( values[ 0 ] ) + 2 );
if (!new_value) return footer;
tag->value = new_value;
new_value[ old_length ] = '\n';
strcpy( new_value + old_length + 1, values[ 0 ] );
}
else
{
tag->value = strdup( values[ 0 ] );
if ( !tag->value ) return footer;
}

for (i = 1; i < values_count; i++)
{
tag = calloc(1, sizeof(psf_tag));
if ( !tag ) return footer;
tag->name = strdup( name );
if ( !tag->name )
{
free( tag );
return footer;
}
tag->value = strdup( values[ i ] );
if ( !tag->value )
{
free( tag->name );
free( tag );
return footer;
}
tag->next = footer;
if ( footer ) footer->prev = tag;
footer = tag;
}

return footer;
}

static psf_tag * add_tag( psf_tag * tags, const char * name, const char * value )
{
int values_count;
const char ** values;
char * value_split;

if ( !name || !*name || !value || !*value ) return tags;

if ( check_split_value( name ) )
{
char * split_point, * remain;
const char ** new_values;
values_count = 0;
values = NULL;
value_split = strdup( value );
if ( !value_split ) return tags;
remain = value_split;
split_point = strstr( value_split, "; " );
while ( split_point )
{
values_count++;
new_values = realloc( values, sizeof(const char*) * ((values_count + 3) & ~3) );
if ( !new_values )
{
if ( values ) free( values );
free( value_split );
return tags;
}
values = new_values;
*split_point = '\0';
values[ values_count - 1 ] = remain;
remain = split_point + 2;
split_point = strstr( remain, "; " );
}
if ( *remain )
{
values_count++;
new_values = realloc( values, sizeof(char*) * ((values_count + 3) & ~3) );
if ( !new_values )
{
if ( values ) free( values );
free( value_split );
return tags;
}
values = new_values;
values[ values_count - 1 ] = remain;
}
}
else
{
values_count = 1;
value_split = NULL;
values = (const char **) malloc(sizeof(const char *));
if ( !values ) return tags;
values[ 0 ] = value;
}

tags = add_tag_multi( tags, name, values, values_count );

if ( value_split ) free( value_split );
free( values );

return tags;
}

/* Split line on first equals sign, and remove any whitespace surrounding the name and value fields */

static psf_tag * process_tag_line( psf_tag * tags, char * line )
{
char * name, * value, * end;
char * equals = strchr( line, '=' );
if ( !equals ) return tags;

name = line;
value = equals + 1;
end = line + strlen( line );

while ( name < equals && *name > 0 && *name <= ' ' ) name++;
if ( name == equals ) return tags;

--equals;
while ( equals > name && *equals > 0 && *equals <= ' ' ) --equals;
equals[1] = '\0';

while ( value < end && *value > 0 && *value <= ' ' ) value++;
if ( value == end ) return tags;

--end;
while ( end > value && *value > 0 && *value <= ' ' ) --end;
end[1] = '\0';

if ( *name == '_' )
{
psf_tag * tag = find_tag( tags, name );
if ( tag ) return tags;
}

return add_tag( tags, name, value );
}

static psf_tag * process_tags( char * buffer )
{
psf_tag * tags = NULL;
char * line_end;
if ( !buffer || !*buffer ) return NULL;

line_end = strpbrk( buffer, "\n\r" );
while ( line_end )
{
*line_end++ = '\0';
tags = process_tag_line( tags, buffer );
while ( *line_end && ( *line_end == '\n' || *line_end == '\r' ) ) line_end++;
buffer = line_end;
line_end = strpbrk( buffer, "\n\r" );
}
if ( *buffer ) tags = process_tag_line( tags, buffer );

return tags;
}

static int psf_load_internal( psf_load_state * state, const char * file_name )
{
psf_tag * tags = NULL;
psf_tag * tag;

char * full_path;

void * file;

long file_size, tag_size;

int n;

uint8_t header_buffer[16];

uint8_t * exe_compressed_buffer = NULL;
uint8_t * exe_decompressed_buffer = NULL;
uint8_t * reserved_buffer = NULL;
char * tag_buffer = NULL;

uint32_t exe_compressed_size, exe_crc32, reserved_size;
uLong exe_decompressed_size;

if ( ++state->depth > max_recursion_depth ) return -1;

full_path = (char *) malloc( strlen(state->base_path) + strlen(file_name) + 1 );
if ( !full_path ) return -1;

strcpy( full_path, state->base_path );
strcat( full_path, file_name );

file = state->file_callbacks->fopen( full_path );

free( full_path );

if ( !file ) return -1;

if ( state->file_callbacks->fread( header_buffer, 1, 16, file ) < 16 ) goto error_close_file;

if ( memcmp( header_buffer, "PSF", 3 ) ) goto error_close_file;

if ( state->allowed_version && ( header_buffer[ 3 ] != state->allowed_version ) ) goto error_close_file;

reserved_size = header_buffer[ 4 ] | ( header_buffer[ 5 ] << 8 ) | ( header_buffer[ 6 ] << 16 ) | ( header_buffer[ 7 ] << 24 );
exe_compressed_size = header_buffer[ 8 ] | ( header_buffer[ 9 ] << 8 ) | ( header_buffer[ 10 ] << 16 ) | ( header_buffer[ 11 ] << 24 );
exe_crc32 = header_buffer[ 12 ] | ( header_buffer[ 13 ] << 8 ) | ( header_buffer[ 14 ] << 16 ) | ( header_buffer[ 15 ] << 24 );

if ( state->file_callbacks->fseek( file, 0, SEEK_END ) ) goto error_close_file;

file_size = state->file_callbacks->ftell( file );

if ( file_size >= 16 + reserved_size + exe_compressed_size + 5 )
{
tag_size = file_size - ( 16 + reserved_size + exe_compressed_size );
if ( state->file_callbacks->fseek( file, -tag_size, SEEK_CUR ) ) goto error_close_file;
tag_buffer = (char *) malloc( tag_size + 1 );
if ( !tag_buffer ) goto error_close_file;
if ( state->file_callbacks->fread( tag_buffer, 1, tag_size, file ) < (size_t)tag_size ) goto error_free_buffers;
tag_buffer[ tag_size ] = 0;
if ( !memcmp( tag_buffer, "[TAG]", 5 ) ) tags = process_tags( tag_buffer + 5 );
free( tag_buffer );
tag_buffer = NULL;

if ( tags && state->info_target && state->depth == 1 )
{
tag = tags;
while ( tag->next ) tag = tag->next;
while ( tag )
{
state->info_target( state->info_context, tag->name, tag->value );
tag = tag->prev;
}
}
}

if ( !state->load_target ) goto done;

tag = find_tag( tags, "_lib" );
if ( tag )
{
if ( psf_load_internal( state, tag->value ) < 0 ) goto error_free_tags;
}

reserved_buffer = (uint8_t *) malloc( reserved_size );
if ( !reserved_buffer ) goto error_free_tags;
exe_compressed_buffer = (uint8_t *) malloc( exe_compressed_size );
if ( !exe_compressed_buffer ) goto error_free_tags;

if ( state->file_callbacks->fseek( file, 16, SEEK_SET ) ) goto error_free_tags;
if ( reserved_size && state->file_callbacks->fread( reserved_buffer, 1, reserved_size, file ) < reserved_size ) goto error_free_tags;
if ( exe_compressed_size && state->file_callbacks->fread( exe_compressed_buffer, 1, exe_compressed_size, file ) < exe_compressed_size ) goto error_free_tags;
if ( exe_crc32 != crc32(crc32(0L, Z_NULL, 0), exe_compressed_buffer, exe_compressed_size) ) goto error_free_tags;

exe_decompressed_size = 16 * 1024 * 1024;
exe_decompressed_buffer = (uint8_t *) malloc( exe_decompressed_size );
if ( !exe_decompressed_buffer ) goto error_free_tags;

uncompress( exe_decompressed_buffer, &exe_decompressed_size, exe_compressed_buffer, exe_compressed_size );

free( exe_compressed_buffer );
exe_compressed_buffer = NULL;

if ( state->load_target( state->load_context, exe_decompressed_buffer, exe_decompressed_size, reserved_buffer, reserved_size ) ) goto error_free_tags;

free( reserved_buffer );
reserved_buffer = NULL;

free( exe_decompressed_buffer );
exe_decompressed_buffer = NULL;

n = 2;
snprintf( state->lib_name_temp, 31, "_lib%u", n );
state->lib_name_temp[ 31 ] = '\0';
tag = find_tag( tags, state->lib_name_temp );
while ( tag )
{
if ( psf_load_internal( state, tag->value ) < 0 ) goto error_free_tags;
++n;
snprintf( state->lib_name_temp, 31, "_lib%u", n );
state->lib_name_temp[ 31 ] = '\0';
tag = find_tag( tags, state->lib_name_temp );
}

done:
free_tags( tags );

--state->depth;

return header_buffer[ 3 ];

error_free_tags:
free_tags( tags );
error_free_buffers:
if ( exe_compressed_buffer ) free( exe_compressed_buffer );
if ( exe_decompressed_buffer ) free( exe_decompressed_buffer );
if ( reserved_buffer ) free( reserved_buffer );
if ( tag_buffer ) free( tag_buffer );
error_close_file:
state->file_callbacks->fclose( file );
return -1;
}

+ 71
- 0
psflib.h View File

@@ -0,0 +1,71 @@
#ifndef PSFLIB_H
#define PSFLIB_H

#include <stdio.h>
#include <stddef.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef struct psf_file_callbacks
{
/* list of characters which act as path separators, null terminated */
const char * path_separators;

/* accepts UTF-8 encoding, returns file handle */
void * (* fopen )(const char *);

/* reads to specified buffer, returns count of size bytes read */
size_t (* fread )(void *, size_t size, size_t count, void * handle);

/* returns zero on success, -1 on error */
int (* fseek )(void * handle, int64_t, int);

/* returns zero on success, -1 on error */
int (* fclose)(void * handle);

/* returns current file offset */
long (* ftell )(void * handle);
} psf_file_callbacks;

/* Receives exe and reserved bodies, with deepest _lib->_lib->_lib etc head first, followed
* by the specified file itself, then followed by numbered library chains.
*
* Example:
*
* outermost file, a.psf, has _lib=b.psf and _lib2=c.psf tags; b.psf has _lib=d.psf:
*
* the callback will be passed the contents of d.psf, then b.psf, then a.psf, then c.psf
*
* Returning non-zero indicates an error.
*/
typedef int (* psf_load_callback)(void * context, const uint8_t * exe, size_t exe_size,
const uint8_t * reserved, size_t reserved_size);

/* Receives the name/value pairs from the outermost file, one at a time.
*
* Returning non-zero indicates an error.
*/
typedef int (* psf_info_callback)(void * context, const char * name, const char * value);

/* Loads the PSF chain starting with uri, opened using file_callbacks, passes the tags,
* if any, to the optional info_target callback, then passes all loaded data to load_target
* with the highest priority file first.
*
* allowed_version may be set to zero to probe the file version, but is not recommended when
* actually loading files into an emulator state.
*
* Both load_target and info_target are optional.
*
* Returns negative on error, PSF version on success.
*/
int psf_load( const char * uri, const psf_file_callbacks * file_callbacks, uint8_t allowed_version,
psf_load_callback load_target, void * load_context, psf_info_callback info_target, void * info_context );

#ifdef __cplusplus
}
#endif

#endif // PSFLIB_H

+ 24
- 0
psflib.pro View File

@@ -0,0 +1,24 @@
#-------------------------------------------------
#
# Project created by QtCreator 2012-12-26T16:54:29
#
#-------------------------------------------------

QT -= core gui

TARGET = psflib
TEMPLATE = lib
CONFIG += staticlib

SOURCES += \
psflib.c

HEADERS += psflib.h
unix:!symbian {
maemo5 {
target.path = /opt/usr/lib
} else {
target.path = /usr/lib
}
INSTALLS += target
}

Loading…
Cancel
Save