1543 lines
41 KiB
Text
1543 lines
41 KiB
Text
//
|
|
// HCDecoder.m
|
|
// HighlyComplete
|
|
//
|
|
// Created by Christopher Snowhill on 9/30/13.
|
|
// Copyright 2013 __NoWork, Inc__. All rights reserved.
|
|
//
|
|
|
|
#import "HCDecoder.h"
|
|
|
|
#import "hebios.h"
|
|
|
|
#import <psflib/psflib.h>
|
|
#import <psflib/psf2fs.h>
|
|
|
|
#import <HighlyExperimental/psx.h>
|
|
#import <HighlyExperimental/bios.h>
|
|
#import <HighlyExperimental/iop.h>
|
|
#import <HighlyExperimental/r3000.h>
|
|
|
|
#import <HighlyTheoretical/sega.h>
|
|
|
|
#import <HighlyQuixotic/qsound.h>
|
|
|
|
#import <HighlyAdvanced/GBA.h>
|
|
|
|
#include <vector>
|
|
#import <SSEQPlayer/SDAT.h>
|
|
#import <SSEQPlayer/Player.h>
|
|
|
|
#import <vio2sf/state.h>
|
|
|
|
#include <zlib.h>
|
|
|
|
@interface psf_file_container : NSObject {
|
|
NSLock * lock;
|
|
NSMutableDictionary * list;
|
|
}
|
|
+ (psf_file_container *)instance;
|
|
- (void)add_hint:(NSString *)path source:(id)source;
|
|
- (void)remove_hint:(NSString *)path;
|
|
- (BOOL)try_hint:(NSString *)path source:(id*)source;
|
|
@end
|
|
|
|
@implementation psf_file_container
|
|
+ (psf_file_container *)instance {
|
|
static psf_file_container * instance;
|
|
|
|
@synchronized(self) {
|
|
if (!instance) {
|
|
instance = [[self alloc] init];
|
|
}
|
|
}
|
|
|
|
return instance;
|
|
}
|
|
- (psf_file_container *)init
|
|
{
|
|
if ((self = [super init]))
|
|
{
|
|
lock = [[NSLock alloc] init];
|
|
list = [[NSMutableDictionary alloc] initWithCapacity:0];
|
|
}
|
|
return self;
|
|
}
|
|
- (void)dealloc
|
|
{
|
|
[list release];
|
|
[lock release];
|
|
[super dealloc];
|
|
}
|
|
- (void)add_hint:(NSString *)path source:(id)source
|
|
{
|
|
[lock lock];
|
|
[list setObject:source forKey:path];
|
|
[lock unlock];
|
|
}
|
|
- (void)remove_hint:(NSString *)path
|
|
{
|
|
[lock lock];
|
|
[list removeObjectForKey:path];
|
|
[lock unlock];
|
|
}
|
|
- (BOOL)try_hint:(NSString *)path source:(id *)source
|
|
{
|
|
[lock lock];
|
|
*source = [list objectForKey:path];
|
|
[lock unlock];
|
|
if ( *source )
|
|
{
|
|
[ *source seek:0 whence:0 ];
|
|
return YES;
|
|
}
|
|
else
|
|
{
|
|
return NO;
|
|
}
|
|
}
|
|
@end
|
|
|
|
|
|
void * source_fopen(const char * path)
|
|
{
|
|
id<CogSource> source;
|
|
if ( ![[psf_file_container instance] try_hint:[NSString stringWithUTF8String:path] source:&source] )
|
|
{
|
|
NSString * urlString = [NSString stringWithUTF8String:path];
|
|
NSURL * url = [NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
|
|
|
|
id audioSourceClass = NSClassFromString(@"AudioSource");
|
|
source = [audioSourceClass audioSourceForURL:url];
|
|
|
|
if (![source open:url])
|
|
return 0;
|
|
|
|
if (![source seekable])
|
|
return 0;
|
|
}
|
|
|
|
return [source retain];
|
|
}
|
|
|
|
size_t source_fread(void * buffer, size_t size, size_t count, void * handle)
|
|
{
|
|
id source = (id)handle;
|
|
|
|
return [source read:buffer amount:(size * count)] / size;
|
|
}
|
|
|
|
int source_fseek(void * handle, int64_t offset, int whence)
|
|
{
|
|
id source = (id)handle;
|
|
|
|
return [source seek:(long)offset whence:whence] ? 0 : -1;
|
|
}
|
|
|
|
int source_fclose(void * handle)
|
|
{
|
|
id source = (id)handle;
|
|
|
|
[source release];
|
|
|
|
return 0;
|
|
}
|
|
|
|
long source_ftell(void * handle)
|
|
{
|
|
id source = (id)handle;
|
|
|
|
return [source tell];
|
|
}
|
|
|
|
static psf_file_callbacks source_callbacks =
|
|
{
|
|
"/|\\",
|
|
source_fopen,
|
|
source_fread,
|
|
source_fseek,
|
|
source_fclose,
|
|
source_ftell
|
|
};
|
|
|
|
|
|
@implementation HCDecoder
|
|
|
|
+ (void)initialize
|
|
{
|
|
bios_set_image(hebios, HEBIOS_SIZE);
|
|
psx_init();
|
|
sega_init();
|
|
qsound_init();
|
|
}
|
|
|
|
- (NSDictionary *)metadata
|
|
{
|
|
return metadataList;
|
|
}
|
|
|
|
- (long)retrieveFrameCount:(long)ms
|
|
{
|
|
return ms * (sampleRate / 100) / 10;
|
|
}
|
|
|
|
struct psf_info_meta_state
|
|
{
|
|
NSMutableDictionary * info;
|
|
|
|
bool utf8;
|
|
|
|
int tag_length_ms;
|
|
int tag_fade_ms;
|
|
|
|
float albumGain;
|
|
float albumPeak;
|
|
float trackGain;
|
|
float trackPeak;
|
|
float volume;
|
|
};
|
|
|
|
static int parse_time_crap(NSString * value)
|
|
{
|
|
NSArray *crapFix = [value componentsSeparatedByString:@"\n"];
|
|
NSArray *components = [[crapFix objectAtIndex:0] componentsSeparatedByString:@":"];
|
|
|
|
float totalSeconds = 0;
|
|
float multiplier = 1000;
|
|
bool first = YES;
|
|
for (id component in [components reverseObjectEnumerator]) {
|
|
if (first) {
|
|
first = NO;
|
|
totalSeconds += [component floatValue] * multiplier;
|
|
} else {
|
|
totalSeconds += [component integerValue] * multiplier;
|
|
}
|
|
multiplier *= 60;
|
|
}
|
|
|
|
return totalSeconds;
|
|
}
|
|
|
|
static int psf_info_meta(void * context, const char * name, const char * value)
|
|
{
|
|
struct psf_info_meta_state * state = ( struct psf_info_meta_state * ) context;
|
|
|
|
NSString * tag = [NSString stringWithUTF8String:name];
|
|
NSString * taglc = [tag lowercaseString];
|
|
NSString * svalue = [NSString stringWithUTF8String:value];
|
|
|
|
if ( svalue == nil )
|
|
return 0;
|
|
|
|
if ([taglc isEqualToString:@"game"])
|
|
{
|
|
taglc = @"album";
|
|
}
|
|
else if ([taglc isEqualToString:@"date"])
|
|
{
|
|
taglc = @"year";
|
|
}
|
|
|
|
if ([taglc hasPrefix:@"replaygain_"])
|
|
{
|
|
if ([taglc hasPrefix:@"replaygain_album_"])
|
|
{
|
|
if ([taglc hasSuffix:@"gain"])
|
|
state->albumGain = [svalue floatValue];
|
|
else if ([taglc hasSuffix:@"peak"])
|
|
state->albumPeak = [svalue floatValue];
|
|
}
|
|
else if ([taglc hasPrefix:@"replaygain_track_"])
|
|
{
|
|
if ([taglc hasSuffix:@"gain"])
|
|
state->trackGain = [svalue floatValue];
|
|
else if ([taglc hasSuffix:@"peak"])
|
|
state->trackPeak = [svalue floatValue];
|
|
}
|
|
}
|
|
else if ([taglc isEqualToString:@"volume"])
|
|
{
|
|
state->volume = [svalue floatValue];
|
|
}
|
|
else if ([taglc isEqualToString:@"length"])
|
|
{
|
|
state->tag_length_ms = parse_time_crap(svalue);
|
|
}
|
|
else if ([taglc isEqualToString:@"fade"])
|
|
{
|
|
state->tag_fade_ms = parse_time_crap(svalue);
|
|
}
|
|
else if ([taglc isEqualToString:@"utf8"])
|
|
{
|
|
state->utf8 = true;
|
|
}
|
|
else if ([taglc isEqualToString:@"title"] ||
|
|
[taglc isEqualToString:@"artist"] ||
|
|
[taglc isEqualToString:@"album"] ||
|
|
[taglc isEqualToString:@"year"] ||
|
|
[taglc isEqualToString:@"genre"] ||
|
|
[taglc isEqualToString:@"track"])
|
|
{
|
|
[state->info setObject:svalue forKey:taglc];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct psf1_load_state
|
|
{
|
|
void * emu;
|
|
bool first;
|
|
unsigned refresh;
|
|
};
|
|
|
|
typedef struct {
|
|
uint32_t pc0;
|
|
uint32_t gp0;
|
|
uint32_t t_addr;
|
|
uint32_t t_size;
|
|
uint32_t d_addr;
|
|
uint32_t d_size;
|
|
uint32_t b_addr;
|
|
uint32_t b_size;
|
|
uint32_t s_ptr;
|
|
uint32_t s_size;
|
|
uint32_t sp,fp,gp,ret,base;
|
|
} exec_header_t;
|
|
|
|
typedef struct {
|
|
char key[8];
|
|
uint32_t text;
|
|
uint32_t data;
|
|
exec_header_t exec;
|
|
char title[60];
|
|
} psxexe_hdr_t;
|
|
|
|
static int psf1_info(void * context, const char * name, const char * value)
|
|
{
|
|
struct psf1_load_state * state = ( struct psf1_load_state * ) context;
|
|
|
|
NSString * sname = [[NSString stringWithUTF8String:name] lowercaseString];
|
|
NSString * svalue = [NSString stringWithUTF8String:value];
|
|
|
|
if ( !state->refresh && [sname isEqualToString:@"_refresh"] )
|
|
{
|
|
state->refresh = [svalue intValue];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
unsigned get_be16( void const* p )
|
|
{
|
|
return (unsigned) ((unsigned char const*) p) [0] << 8 |
|
|
(unsigned) ((unsigned char const*) p) [1];
|
|
}
|
|
|
|
unsigned get_le32( void const* p )
|
|
{
|
|
return (unsigned) ((unsigned char const*) p) [3] << 24 |
|
|
(unsigned) ((unsigned char const*) p) [2] << 16 |
|
|
(unsigned) ((unsigned char const*) p) [1] << 8 |
|
|
(unsigned) ((unsigned char const*) p) [0];
|
|
}
|
|
|
|
unsigned get_be32( void const* p )
|
|
{
|
|
return (unsigned) ((unsigned char const*) p) [0] << 24 |
|
|
(unsigned) ((unsigned char const*) p) [1] << 16 |
|
|
(unsigned) ((unsigned char const*) p) [2] << 8 |
|
|
(unsigned) ((unsigned char const*) p) [3];
|
|
}
|
|
|
|
void set_le32( void* p, unsigned n )
|
|
{
|
|
((unsigned char*) p) [0] = (unsigned char) n;
|
|
((unsigned char*) p) [1] = (unsigned char) (n >> 8);
|
|
((unsigned char*) p) [2] = (unsigned char) (n >> 16);
|
|
((unsigned char*) p) [3] = (unsigned char) (n >> 24);
|
|
}
|
|
|
|
int psf1_loader(void * context, const uint8_t * exe, size_t exe_size,
|
|
const uint8_t * reserved, size_t reserved_size)
|
|
{
|
|
struct psf1_load_state * state = ( struct psf1_load_state * ) context;
|
|
|
|
psxexe_hdr_t *psx = (psxexe_hdr_t *) exe;
|
|
|
|
if ( exe_size < 0x800 ) return -1;
|
|
if ( exe_size > UINT_MAX ) return -1;
|
|
|
|
uint32_t addr = get_le32( &psx->exec.t_addr );
|
|
uint32_t size = (uint32_t)exe_size - 0x800;
|
|
|
|
addr &= 0x1fffff;
|
|
if ( ( addr < 0x10000 ) || ( size > 0x1f0000 ) || ( addr + size > 0x200000 ) ) return -1;
|
|
|
|
void * pIOP = psx_get_iop_state( state->emu );
|
|
iop_upload_to_ram( pIOP, addr, exe + 0x800, size );
|
|
|
|
if ( !state->refresh )
|
|
{
|
|
if (!strncasecmp((const char *) exe + 113, "Japan", 5)) state->refresh = 60;
|
|
else if (!strncasecmp((const char *) exe + 113, "Europe", 6)) state->refresh = 50;
|
|
else if (!strncasecmp((const char *) exe + 113, "North America", 13)) state->refresh = 60;
|
|
}
|
|
|
|
if ( state->first )
|
|
{
|
|
void * pR3000 = iop_get_r3000_state( pIOP );
|
|
r3000_setreg(pR3000, R3000_REG_PC, get_le32( &psx->exec.pc0 ) );
|
|
r3000_setreg(pR3000, R3000_REG_GEN+29, get_le32( &psx->exec.s_ptr ) );
|
|
state->first = false;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int EMU_CALL virtual_readfile(void *context, const char *path, int offset, char *buffer, int length)
|
|
{
|
|
return psf2fs_virtual_readfile(context, path, offset, buffer, length);
|
|
}
|
|
|
|
struct sdsf_loader_state
|
|
{
|
|
uint8_t * data;
|
|
size_t data_size;
|
|
};
|
|
|
|
int sdsf_loader(void * context, const uint8_t * exe, size_t exe_size,
|
|
const uint8_t * reserved, size_t reserved_size)
|
|
{
|
|
if ( exe_size < 4 ) return -1;
|
|
|
|
struct sdsf_loader_state * state = ( struct sdsf_loader_state * ) context;
|
|
|
|
uint8_t * dst = state->data;
|
|
|
|
if ( state->data_size < 4 ) {
|
|
state->data = dst = ( uint8_t * ) malloc( exe_size );
|
|
state->data_size = exe_size;
|
|
memcpy( dst, exe, exe_size );
|
|
return 0;
|
|
}
|
|
|
|
uint32_t dst_start = get_le32( dst );
|
|
uint32_t src_start = get_le32( exe );
|
|
dst_start &= 0x7fffff;
|
|
src_start &= 0x7fffff;
|
|
size_t dst_len = state->data_size - 4;
|
|
size_t src_len = exe_size - 4;
|
|
if ( dst_len > 0x800000 ) dst_len = 0x800000;
|
|
if ( src_len > 0x800000 ) src_len = 0x800000;
|
|
|
|
if ( src_start < dst_start )
|
|
{
|
|
uint32_t diff = dst_start - src_start;
|
|
state->data_size = dst_len + 4 + diff;
|
|
state->data = dst = ( uint8_t * ) realloc( dst, state->data_size );
|
|
memmove( dst + 4 + diff, dst + 4, dst_len );
|
|
memset( dst + 4, 0, diff );
|
|
dst_len += diff;
|
|
dst_start = src_start;
|
|
set_le32( dst, dst_start );
|
|
}
|
|
if ( ( src_start + src_len ) > ( dst_start + dst_len ) )
|
|
{
|
|
size_t diff = ( src_start + src_len ) - ( dst_start + dst_len );
|
|
state->data_size = dst_len + 4 + diff;
|
|
state->data = dst = ( uint8_t * ) realloc( dst, state->data_size );
|
|
memset( dst + 4 + dst_len, 0, diff );
|
|
}
|
|
|
|
memcpy( dst + 4 + ( src_start - dst_start ), exe + 4, src_len );
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct qsf_loader_state
|
|
{
|
|
uint8_t * key;
|
|
uint32_t key_size;
|
|
|
|
uint8_t * z80_rom;
|
|
uint32_t z80_size;
|
|
|
|
uint8_t * sample_rom;
|
|
uint32_t sample_size;
|
|
};
|
|
|
|
static int upload_gsf_section( struct qsf_loader_state * state, const char * section, uint32_t start,
|
|
const uint8_t * data, uint32_t size )
|
|
{
|
|
uint8_t ** array = NULL;
|
|
uint32_t * array_size = NULL;
|
|
uint32_t max_size = 0x7fffffff;
|
|
|
|
if ( !strcmp( section, "KEY" ) ) { array = &state->key; array_size = &state->key_size; max_size = 11; }
|
|
else if ( !strcmp( section, "Z80" ) ) { array = &state->z80_rom; array_size = &state->z80_size; }
|
|
else if ( !strcmp( section, "SMP" ) ) { array = &state->sample_rom; array_size = &state->sample_size; }
|
|
else return -1;
|
|
|
|
if ( ( start + size ) < start ) return -1;
|
|
|
|
uint32_t new_size = start + size;
|
|
uint32_t old_size = *array_size;
|
|
if ( new_size > max_size ) return -1;
|
|
|
|
if ( new_size > old_size ) {
|
|
*array = ( uint8_t * ) realloc( *array, new_size );
|
|
*array_size = new_size;
|
|
memset( (*array) + old_size, 0, new_size - old_size );
|
|
}
|
|
|
|
memcpy( (*array) + start, data, size );
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qsf_loader(void * context, const uint8_t * exe, size_t exe_size,
|
|
const uint8_t * reserved, size_t reserved_size)
|
|
{
|
|
struct qsf_loader_state * state = ( struct qsf_loader_state * ) context;
|
|
|
|
for (;;) {
|
|
char s[4];
|
|
if ( exe_size < 11 ) break;
|
|
memcpy( s, exe, 3 ); exe += 3; exe_size -= 3;
|
|
s [3] = 0;
|
|
uint32_t dataofs = get_le32( exe ); exe += 4; exe_size -= 4;
|
|
uint32_t datasize = get_le32( exe ); exe += 4; exe_size -= 4;
|
|
if ( datasize > exe_size )
|
|
return -1;
|
|
|
|
if ( upload_gsf_section( state, s, dataofs, exe, datasize ) < 0 )
|
|
return -1;
|
|
|
|
exe += datasize;
|
|
exe_size -= datasize;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct gsf_loader_state
|
|
{
|
|
int entry_set;
|
|
uint32_t entry;
|
|
uint8_t * data;
|
|
size_t data_size;
|
|
};
|
|
|
|
static int gsf_loader(void * context, const uint8_t * exe, size_t exe_size,
|
|
const uint8_t * reserved, size_t reserved_size)
|
|
{
|
|
if ( exe_size < 12 ) return -1;
|
|
|
|
struct gsf_loader_state * state = ( struct gsf_loader_state * ) context;
|
|
|
|
unsigned char *iptr;
|
|
size_t isize;
|
|
unsigned char *xptr;
|
|
unsigned xentry = get_le32(exe + 0);
|
|
unsigned xsize = get_le32(exe + 8);
|
|
unsigned xofs = get_le32(exe + 4) & 0x1ffffff;
|
|
if ( xsize < exe_size - 12 ) return -1;
|
|
if (!state->entry_set)
|
|
{
|
|
state->entry = xentry;
|
|
state->entry_set = 1;
|
|
}
|
|
{
|
|
iptr = state->data;
|
|
isize = state->data_size;
|
|
state->data = 0;
|
|
state->data_size = 0;
|
|
}
|
|
if (!iptr)
|
|
{
|
|
size_t rsize = xofs + xsize;
|
|
{
|
|
rsize -= 1;
|
|
rsize |= rsize >> 1;
|
|
rsize |= rsize >> 2;
|
|
rsize |= rsize >> 4;
|
|
rsize |= rsize >> 8;
|
|
rsize |= rsize >> 16;
|
|
rsize += 1;
|
|
}
|
|
iptr = (unsigned char *) malloc(rsize + 10);
|
|
if (!iptr)
|
|
return -1;
|
|
memset(iptr, 0, rsize + 10);
|
|
isize = rsize;
|
|
}
|
|
else if (isize < xofs + xsize)
|
|
{
|
|
size_t rsize = xofs + xsize;
|
|
{
|
|
rsize -= 1;
|
|
rsize |= rsize >> 1;
|
|
rsize |= rsize >> 2;
|
|
rsize |= rsize >> 4;
|
|
rsize |= rsize >> 8;
|
|
rsize |= rsize >> 16;
|
|
rsize += 1;
|
|
}
|
|
xptr = (unsigned char *) realloc(iptr, xofs + rsize + 10);
|
|
if (!xptr)
|
|
{
|
|
free(iptr);
|
|
return -1;
|
|
}
|
|
iptr = xptr;
|
|
isize = rsize;
|
|
}
|
|
memcpy(iptr + xofs, exe + 12, xsize);
|
|
{
|
|
state->data = iptr;
|
|
state->data_size = isize;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
struct gsf_sound_out : public GBASoundOut
|
|
{
|
|
uint8_t * buffer;
|
|
size_t samples_written;
|
|
size_t buffer_size;
|
|
gsf_sound_out() : buffer( nil ), samples_written( 0 ), buffer_size( 0 ) { }
|
|
virtual ~gsf_sound_out() { if ( buffer ) free( buffer ); }
|
|
// Receives signed 16-bit stereo audio and a byte count
|
|
virtual void write(const void * samples, unsigned long bytes)
|
|
{
|
|
if ( bytes + samples_written > buffer_size )
|
|
{
|
|
size_t new_buffer_size = ( buffer_size + bytes + 2047 ) & ~2047;
|
|
buffer = ( uint8_t * ) realloc( buffer, new_buffer_size );
|
|
buffer_size = new_buffer_size;
|
|
}
|
|
memcpy( buffer + samples_written, samples, bytes );
|
|
samples_written += bytes;
|
|
}
|
|
};
|
|
|
|
struct ncsf_loader_state
|
|
{
|
|
uint32_t sseq;
|
|
std::vector<uint8_t> sdatData;
|
|
std::unique_ptr<SDAT> sdat;
|
|
|
|
std::vector<uint8_t> outputBuffer;
|
|
|
|
ncsf_loader_state() : sseq( 0 ) { }
|
|
};
|
|
|
|
static int ncsf_loader(void * context, const uint8_t * exe, size_t exe_size,
|
|
const uint8_t * reserved, size_t reserved_size)
|
|
{
|
|
struct ncsf_loader_state * state = ( struct ncsf_loader_state * ) context;
|
|
|
|
if ( reserved_size >= 4 )
|
|
{
|
|
state->sseq = get_le32( reserved );
|
|
}
|
|
|
|
if ( exe_size >= 12 )
|
|
{
|
|
uint32_t sdat_size = get_le32( exe + 8 );
|
|
if ( sdat_size > exe_size ) return -1;
|
|
|
|
if ( state->sdatData.empty() )
|
|
state->sdatData.resize( sdat_size, 0 );
|
|
else if ( state->sdatData.size() < sdat_size )
|
|
state->sdatData.resize( sdat_size );
|
|
memcpy( &state->sdatData[0], exe, sdat_size );
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct twosf_loader_state
|
|
{
|
|
uint8_t * rom;
|
|
uint8_t * state;
|
|
size_t rom_size;
|
|
size_t state_size;
|
|
|
|
int initial_frames;
|
|
int sync_type;
|
|
int clockdown;
|
|
int arm9_clockdown_level;
|
|
int arm7_clockdown_level;
|
|
};
|
|
|
|
static int load_twosf_map(struct twosf_loader_state *state, int issave, const unsigned char *udata, unsigned usize)
|
|
{
|
|
if (usize < 8) return -1;
|
|
|
|
unsigned char *iptr;
|
|
size_t isize;
|
|
unsigned char *xptr;
|
|
unsigned xsize = get_le32(udata + 4);
|
|
unsigned xofs = get_le32(udata + 0);
|
|
if (issave)
|
|
{
|
|
iptr = state->state;
|
|
isize = state->state_size;
|
|
state->state = 0;
|
|
state->state_size = 0;
|
|
}
|
|
else
|
|
{
|
|
iptr = state->rom;
|
|
isize = state->rom_size;
|
|
state->rom = 0;
|
|
state->rom_size = 0;
|
|
}
|
|
if (!iptr)
|
|
{
|
|
size_t rsize = xofs + xsize;
|
|
if (!issave)
|
|
{
|
|
rsize -= 1;
|
|
rsize |= rsize >> 1;
|
|
rsize |= rsize >> 2;
|
|
rsize |= rsize >> 4;
|
|
rsize |= rsize >> 8;
|
|
rsize |= rsize >> 16;
|
|
rsize += 1;
|
|
}
|
|
iptr = (unsigned char *) malloc(rsize + 10);
|
|
if (!iptr)
|
|
return -1;
|
|
memset(iptr, 0, rsize + 10);
|
|
isize = rsize;
|
|
}
|
|
else if (isize < xofs + xsize)
|
|
{
|
|
size_t rsize = xofs + xsize;
|
|
if (!issave)
|
|
{
|
|
rsize -= 1;
|
|
rsize |= rsize >> 1;
|
|
rsize |= rsize >> 2;
|
|
rsize |= rsize >> 4;
|
|
rsize |= rsize >> 8;
|
|
rsize |= rsize >> 16;
|
|
rsize += 1;
|
|
}
|
|
xptr = (unsigned char *) realloc(iptr, xofs + rsize + 10);
|
|
if (!xptr)
|
|
{
|
|
free(iptr);
|
|
return -1;
|
|
}
|
|
iptr = xptr;
|
|
isize = rsize;
|
|
}
|
|
memcpy(iptr + xofs, udata + 8, xsize);
|
|
if (issave)
|
|
{
|
|
state->state = iptr;
|
|
state->state_size = isize;
|
|
}
|
|
else
|
|
{
|
|
state->rom = iptr;
|
|
state->rom_size = isize;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int load_twosf_mapz(struct twosf_loader_state *state, int issave, const unsigned char *zdata, unsigned zsize, unsigned zcrc)
|
|
{
|
|
int ret;
|
|
int zerr;
|
|
uLongf usize = 8;
|
|
uLongf rsize = usize;
|
|
unsigned char *udata;
|
|
unsigned char *rdata;
|
|
|
|
udata = (unsigned char *) malloc(usize);
|
|
if (!udata)
|
|
return -1;
|
|
|
|
while (Z_OK != (zerr = uncompress(udata, &usize, zdata, zsize)))
|
|
{
|
|
if (Z_MEM_ERROR != zerr && Z_BUF_ERROR != zerr)
|
|
{
|
|
free(udata);
|
|
return -1;
|
|
}
|
|
if (usize >= 8)
|
|
{
|
|
usize = get_le32(udata + 4) + 8;
|
|
if (usize < rsize)
|
|
{
|
|
rsize += rsize;
|
|
usize = rsize;
|
|
}
|
|
else
|
|
rsize = usize;
|
|
}
|
|
else
|
|
{
|
|
rsize += rsize;
|
|
usize = rsize;
|
|
}
|
|
rdata = (unsigned char *) realloc(udata, usize);
|
|
if (!rdata)
|
|
{
|
|
free(udata);
|
|
return -1;
|
|
}
|
|
udata = rdata;
|
|
}
|
|
|
|
rdata = (unsigned char *) realloc(udata, usize);
|
|
if (!rdata)
|
|
{
|
|
free(udata);
|
|
return -1;
|
|
}
|
|
|
|
if (0)
|
|
{
|
|
uLong ccrc = crc32(crc32(0L, Z_NULL, 0), rdata, (uInt) usize);
|
|
if (ccrc != zcrc)
|
|
return -1;
|
|
}
|
|
|
|
ret = load_twosf_map(state, issave, rdata, (unsigned) usize);
|
|
free(rdata);
|
|
return ret;
|
|
}
|
|
|
|
static int twosf_loader(void * context, const uint8_t * exe, size_t exe_size,
|
|
const uint8_t * reserved, size_t reserved_size)
|
|
{
|
|
struct twosf_loader_state * state = ( struct twosf_loader_state * ) context;
|
|
|
|
if ( exe_size >= 8 )
|
|
{
|
|
if ( load_twosf_map(state, 0, exe, (unsigned) exe_size) )
|
|
return -1;
|
|
}
|
|
|
|
if ( reserved_size )
|
|
{
|
|
size_t resv_pos = 0;
|
|
if ( reserved_size < 16 )
|
|
return -1;
|
|
while ( resv_pos + 12 < reserved_size )
|
|
{
|
|
unsigned save_size = get_le32(reserved + resv_pos + 4);
|
|
unsigned save_crc = get_le32(reserved + resv_pos + 8);
|
|
if (get_le32(reserved + resv_pos + 0) == 0x45564153)
|
|
{
|
|
if (resv_pos + 12 + save_size > reserved_size)
|
|
return -1;
|
|
if (load_twosf_mapz(state, 1, reserved + resv_pos + 12, save_size, save_crc))
|
|
return -1;
|
|
}
|
|
resv_pos += 12 + save_size;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int twosf_info(void * context, const char * name, const char * value)
|
|
{
|
|
struct twosf_loader_state * state = ( struct twosf_loader_state * ) context;
|
|
|
|
NSString * sname = [[NSString stringWithUTF8String:name] lowercaseString];
|
|
NSString * svalue = [NSString stringWithUTF8String:value];
|
|
|
|
if ( [sname isEqualToString:@"_frames"] )
|
|
{
|
|
state->initial_frames = [svalue intValue];
|
|
}
|
|
else if ( [sname isEqualToString:@"_clockdown"] )
|
|
{
|
|
state->clockdown = [svalue intValue];
|
|
}
|
|
else if ( [sname isEqualToString:@"_vio2sf_sync_type"] )
|
|
{
|
|
state->sync_type = [svalue intValue];
|
|
}
|
|
else if ( [sname isEqualToString:@"_vio2sf_arm9_clockdown_level"] )
|
|
{
|
|
state->arm9_clockdown_level = [svalue intValue];
|
|
}
|
|
else if ( [sname isEqualToString:@"_vio2sf_arm7_clockdown_level"] )
|
|
{
|
|
state->arm7_clockdown_level = [svalue intValue];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
- (BOOL)initializeDecoder
|
|
{
|
|
if ( type == 1 )
|
|
{
|
|
emulatorCore = ( uint8_t * ) malloc( psx_get_state_size( 1 ) );
|
|
|
|
psx_clear_state( emulatorCore, 1 );
|
|
|
|
struct psf1_load_state state;
|
|
|
|
state.emu = emulatorCore;
|
|
state.first = true;
|
|
state.refresh = 0;
|
|
|
|
if ( psf_load( [currentUrl UTF8String], &source_callbacks, 1, psf1_loader, &state, psf1_info, &state ) <= 0 )
|
|
return NO;
|
|
|
|
if ( state.refresh )
|
|
psx_set_refresh( emulatorCore, state.refresh );
|
|
}
|
|
else if ( type == 2 )
|
|
{
|
|
emulatorExtra = psf2fs_create();
|
|
|
|
struct psf1_load_state state;
|
|
|
|
state.refresh = 0;
|
|
|
|
if ( psf_load( [currentUrl UTF8String], &source_callbacks, 2, psf2fs_load_callback, emulatorExtra, psf1_info, &state ) <= 0 )
|
|
return NO;
|
|
|
|
emulatorCore = ( uint8_t * ) malloc( psx_get_state_size( 2 ) );
|
|
|
|
psx_clear_state( emulatorCore, 2 );
|
|
|
|
if ( state.refresh )
|
|
psx_set_refresh( emulatorCore, state.refresh );
|
|
|
|
psx_set_readfile( emulatorCore, virtual_readfile, emulatorExtra );
|
|
}
|
|
else if ( type == 0x11 || type == 0x12 )
|
|
{
|
|
struct sdsf_loader_state state;
|
|
memset( &state, 0, sizeof(state) );
|
|
|
|
if ( psf_load( [currentUrl UTF8String], &source_callbacks, type, sdsf_loader, &state, 0, 0) <= 0 )
|
|
return NO;
|
|
|
|
emulatorCore = ( uint8_t * ) malloc( sega_get_state_size( type - 0x10 ) );
|
|
|
|
sega_clear_state( emulatorCore, type - 0x10 );
|
|
|
|
sega_enable_dry( emulatorCore, 1 );
|
|
sega_enable_dsp( emulatorCore, 1 );
|
|
|
|
sega_enable_dsp_dynarec( emulatorCore, 0 );
|
|
|
|
uint32_t start = *(uint32_t*) state.data;
|
|
size_t length = state.data_size;
|
|
const size_t max_length = ( type == 0x12 ) ? 0x800000 : 0x80000;
|
|
if ( ( start + ( length - 4 ) ) > max_length ) {
|
|
length = max_length - start + 4;
|
|
}
|
|
sega_upload_program( emulatorCore, state.data, (uint32_t)length );
|
|
|
|
free( state.data );
|
|
}
|
|
else if ( type == 0x22 )
|
|
{
|
|
struct gsf_loader_state state;
|
|
memset( &state, 0, sizeof(state) );
|
|
|
|
if ( psf_load( [currentUrl UTF8String], &source_callbacks, 0x22, gsf_loader, &state, 0, 0 ) <= 0 )
|
|
return NO;
|
|
|
|
if ( state.data_size > UINT_MAX )
|
|
return NO;
|
|
|
|
GBASystem * system = new GBASystem;
|
|
|
|
emulatorCore = ( uint8_t * ) system;
|
|
|
|
system->cpuIsMultiBoot = ((state.entry >> 24) == 2);
|
|
|
|
CPULoadRom( system, state.data, (uint32_t)state.data_size );
|
|
|
|
free( state.data );
|
|
|
|
struct gsf_sound_out * sound_out = new gsf_sound_out;
|
|
|
|
emulatorExtra = sound_out;
|
|
|
|
soundInit( system, sound_out );
|
|
soundReset( system );
|
|
|
|
CPUInit( system );
|
|
CPUReset( system );
|
|
}
|
|
else if ( type == 0x24 )
|
|
{
|
|
struct twosf_loader_state state;
|
|
memset( &state, 0, sizeof(state) );
|
|
state.initial_frames = -1;
|
|
|
|
if ( psf_load( [currentUrl UTF8String], &source_callbacks, 0x24, twosf_loader, &state, twosf_info, &state) <= 0 )
|
|
{
|
|
if (state.rom) free(state.rom);
|
|
if (state.state) free(state.state);
|
|
return NO;
|
|
}
|
|
|
|
if ( state.rom_size > UINT_MAX || state.state_size > UINT_MAX )
|
|
{
|
|
if (state.rom) free(state.rom);
|
|
if (state.state) free(state.state);
|
|
return NO;
|
|
}
|
|
|
|
NDS_state * core = ( NDS_state * ) calloc(1, sizeof(NDS_state));
|
|
if (!core)
|
|
{
|
|
if (state.rom) free(state.rom);
|
|
if (state.state) free(state.state);
|
|
return NO;
|
|
}
|
|
|
|
if ( state_init(core) )
|
|
{
|
|
state_deinit(core);
|
|
if (state.rom) free(state.rom);
|
|
if (state.state) free(state.state);
|
|
return NO;
|
|
}
|
|
|
|
core->dwInterpolation = 2;
|
|
core->dwChannelMute = 0;
|
|
|
|
if (!state.arm7_clockdown_level)
|
|
state.arm7_clockdown_level = state.clockdown;
|
|
if (!state.arm9_clockdown_level)
|
|
state.arm9_clockdown_level = state.clockdown;
|
|
|
|
core->initial_frames = state.initial_frames;
|
|
core->sync_type = state.sync_type;
|
|
core->arm7_clockdown_level = state.arm7_clockdown_level;
|
|
core->arm9_clockdown_level = state.arm9_clockdown_level;
|
|
|
|
emulatorCore = ( uint8_t * ) core;
|
|
emulatorExtra = state.rom;
|
|
|
|
if ( state.rom )
|
|
state_setrom(core, state.rom, (u32) state.rom_size );
|
|
|
|
state_loadstate(core, state.state, (u32) state.state_size);
|
|
|
|
if (state.state) free(state.state);
|
|
}
|
|
else if ( type == 0x25 )
|
|
{
|
|
struct ncsf_loader_state * state = new struct ncsf_loader_state;
|
|
|
|
if ( psf_load( [currentUrl UTF8String], &source_callbacks, 0x25, ncsf_loader, state, 0, 0) <= 0 )
|
|
{
|
|
delete state;
|
|
return NO;
|
|
}
|
|
|
|
Player * player = new Player;
|
|
|
|
player->interpolation = INTERPOLATION_LANCZOS;
|
|
|
|
PseudoFile file;
|
|
file.data = &state->sdatData;
|
|
|
|
state->sdat.reset(new SDAT(file, state->sseq));
|
|
|
|
auto * sseqToPlay = state->sdat->sseq.get();
|
|
|
|
player->sampleRate = 44100;
|
|
player->Setup( sseqToPlay );
|
|
player->Timer();
|
|
|
|
state->outputBuffer.resize( 1024 * sizeof(int16_t) * 2 );
|
|
|
|
emulatorCore = ( uint8_t * ) player;
|
|
emulatorExtra = state;
|
|
}
|
|
else if ( type == 0x41 )
|
|
{
|
|
struct qsf_loader_state * state = ( struct qsf_loader_state * ) calloc( 1, sizeof( *state ) );
|
|
|
|
emulatorExtra = state;
|
|
|
|
if ( psf_load( [currentUrl UTF8String], &source_callbacks, 0x41, qsf_loader, state, 0, 0) <= 0 )
|
|
return NO;
|
|
|
|
emulatorCore = ( uint8_t * ) malloc( qsound_get_state_size() );
|
|
|
|
qsound_clear_state( emulatorCore );
|
|
|
|
if(state->key_size == 11) {
|
|
uint8_t * ptr = state->key;
|
|
uint32_t swap_key1 = get_be32( ptr + 0 );
|
|
uint32_t swap_key2 = get_be32( ptr + 4 );
|
|
uint32_t addr_key = get_be16( ptr + 8 );
|
|
uint8_t xor_key = *( ptr + 10 );
|
|
qsound_set_kabuki_key( emulatorCore, swap_key1, swap_key2, addr_key, xor_key );
|
|
} else {
|
|
qsound_set_kabuki_key( emulatorCore, 0, 0, 0, 0 );
|
|
}
|
|
qsound_set_z80_rom( emulatorCore, state->z80_rom, state->z80_size );
|
|
qsound_set_sample_rom( emulatorCore, state->sample_rom, state->sample_size );
|
|
}
|
|
else return NO;
|
|
|
|
framesRead = 0;
|
|
|
|
silence_test_buffer.resize( sampleRate * 30 * 2 );
|
|
|
|
if (![self fillBuffer])
|
|
return NO;
|
|
|
|
silence_test_buffer.remove_leading_silence();
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)open:(id<CogSource>)source
|
|
{
|
|
if (![source seekable]) {
|
|
return NO;
|
|
}
|
|
|
|
currentSource = [source retain];
|
|
|
|
struct psf_info_meta_state info;
|
|
|
|
info.info = [NSMutableDictionary dictionary];
|
|
info.utf8 = false;
|
|
info.tag_length_ms = 0;
|
|
info.tag_fade_ms = 0;
|
|
|
|
info.albumGain = 0;
|
|
info.albumPeak = 0;
|
|
info.trackGain = 0;
|
|
info.trackPeak = 0;
|
|
info.volume = 1;
|
|
|
|
currentUrl = [[[[source url] absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] retain];
|
|
|
|
[[psf_file_container instance] add_hint:currentUrl source:currentSource];
|
|
|
|
type = psf_load( [currentUrl UTF8String], &source_callbacks, 0, 0, 0, psf_info_meta, &info );
|
|
|
|
if (type <= 0)
|
|
return NO;
|
|
|
|
emulatorCore = nil;
|
|
emulatorExtra = nil;
|
|
|
|
sampleRate = 44100;
|
|
|
|
if ( type == 2 )
|
|
sampleRate = 48000;
|
|
|
|
tagLengthMs = info.tag_length_ms;
|
|
tagFadeMs = info.tag_fade_ms;
|
|
|
|
if (!tagLengthMs) {
|
|
tagLengthMs = ( 2 * 60 + 30 ) * 1000;
|
|
tagFadeMs = 8000;
|
|
}
|
|
|
|
replayGainAlbumGain = info.albumGain;
|
|
replayGainAlbumPeak = info.albumPeak;
|
|
replayGainTrackGain = info.trackGain;
|
|
replayGainTrackPeak = info.trackPeak;
|
|
volume = info.volume;
|
|
|
|
metadataList = info.info;
|
|
|
|
framesLength = [self retrieveFrameCount:tagLengthMs];
|
|
totalFrames = [self retrieveFrameCount:tagLengthMs + tagFadeMs];
|
|
|
|
[self willChangeValueForKey:@"properties"];
|
|
[self didChangeValueForKey:@"properties"];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)fillBuffer
|
|
{
|
|
long frames_left = totalFrames - framesRead - silence_test_buffer.data_available() / 2;
|
|
long free_space = silence_test_buffer.free_space() / 2;
|
|
if ( free_space > frames_left )
|
|
free_space = frames_left;
|
|
while ( free_space > 0 )
|
|
{
|
|
unsigned long samples_to_write = 0;
|
|
int16_t * buf = silence_test_buffer.get_write_ptr( samples_to_write );
|
|
int samples_read = [self readAudioInternal:buf frames:(UInt32)samples_to_write / 2];
|
|
if ( !samples_read ) break;
|
|
silence_test_buffer.samples_written( samples_read * 2 );
|
|
free_space -= samples_read;
|
|
}
|
|
return !silence_test_buffer.test_silence();
|
|
}
|
|
|
|
- (int)readAudioInternal:(void *)buf frames:(UInt32)frames
|
|
{
|
|
if ( type == 1 || type == 2 )
|
|
{
|
|
uint32_t howmany = frames;
|
|
psx_execute( emulatorCore, 0x7fffffff, ( int16_t * ) buf, &howmany, 0 );
|
|
frames = howmany;
|
|
}
|
|
else if ( type == 0x11 || type == 0x12 )
|
|
{
|
|
uint32_t howmany = frames;
|
|
sega_execute( emulatorCore, 0x7fffffff, ( int16_t * ) buf, &howmany );
|
|
frames = howmany;
|
|
}
|
|
else if ( type == 0x22 )
|
|
{
|
|
GBASystem * system = ( GBASystem * ) emulatorCore;
|
|
struct gsf_sound_out * sound_out = ( struct gsf_sound_out * ) emulatorExtra;
|
|
|
|
unsigned long frames_to_render = frames;
|
|
|
|
do
|
|
{
|
|
unsigned long frames_rendered = sound_out->samples_written / 4;
|
|
|
|
if ( frames_rendered >= frames_to_render )
|
|
{
|
|
memcpy( buf, sound_out->buffer, frames_to_render * 4 );
|
|
frames_rendered -= frames_to_render;
|
|
memcpy( sound_out->buffer, sound_out->buffer + frames_to_render * 4, frames_rendered * 4 );
|
|
frames_to_render = 0;
|
|
}
|
|
else
|
|
{
|
|
memcpy( buf, sound_out->buffer, frames_rendered * 4 );
|
|
buf = ((uint8_t *) buf) + frames_rendered * 4;
|
|
frames_to_render -= frames_rendered;
|
|
frames_rendered = 0;
|
|
}
|
|
sound_out->samples_written = frames_rendered * 4;
|
|
|
|
if ( frames_to_render )
|
|
{
|
|
CPULoop( system, 250000 );
|
|
if ( !sound_out->samples_written )
|
|
break;
|
|
}
|
|
}
|
|
while ( frames_to_render );
|
|
frames -= (UInt32) frames_to_render;
|
|
}
|
|
else if ( type == 0x24 )
|
|
{
|
|
NDS_state * state = ( NDS_state * ) emulatorCore;
|
|
state_render(state, (s16*) buf, frames);
|
|
}
|
|
else if ( type == 0x25 )
|
|
{
|
|
Player * player = ( Player * ) emulatorCore;
|
|
ncsf_loader_state * state = ( ncsf_loader_state * ) emulatorExtra;
|
|
std::vector<uint8_t> & buffer = state->outputBuffer;
|
|
unsigned long frames_to_do = frames;
|
|
while ( frames_to_do )
|
|
{
|
|
unsigned frames_this_run = 1024;
|
|
if ( frames_this_run > frames_to_do )
|
|
frames_this_run = (unsigned int) frames_to_do;
|
|
player->GenerateSamples(buffer, 0, frames_this_run);
|
|
memcpy( buf, &buffer[0], frames_this_run * sizeof(int16_t) * 2 );
|
|
buf = ((uint8_t *)buf) + frames_this_run * sizeof(int16_t) * 2;
|
|
frames_to_do -= frames_this_run;
|
|
}
|
|
}
|
|
else if ( type == 0x41 )
|
|
{
|
|
uint32_t howmany = frames;
|
|
qsound_execute( emulatorCore, 0x7fffffff, ( int16_t * ) buf, &howmany);
|
|
frames = howmany;
|
|
}
|
|
|
|
return frames;
|
|
}
|
|
|
|
- (int)readAudio:(void *)buf frames:(UInt32)frames
|
|
{
|
|
if ( !emulatorCore )
|
|
{
|
|
if (![self initializeDecoder])
|
|
return 0;
|
|
}
|
|
else if ( ![self fillBuffer] )
|
|
return 0;
|
|
|
|
unsigned long written = silence_test_buffer.data_available() / 2;
|
|
if ( written > frames )
|
|
written = frames;
|
|
if ( written > totalFrames - framesRead )
|
|
written = totalFrames - framesRead;
|
|
if ( written == 0 )
|
|
return 0;
|
|
|
|
silence_test_buffer.read( (int16_t *) buf, written * 2 );
|
|
|
|
if ( framesRead + written > framesLength ) {
|
|
long fadeStart = (framesLength > framesRead) ? framesLength : framesRead;
|
|
long fadeEnd = framesRead + written;
|
|
long fadeTotal = totalFrames - framesLength;
|
|
long fadePos;
|
|
|
|
int16_t * buf16 = ( int16_t * ) buf;
|
|
|
|
for (fadePos = fadeStart; fadePos < fadeEnd; ++fadePos) {
|
|
long scale = totalFrames - fadePos;
|
|
buf16[ 0 ] = buf16[ 0 ] * scale / fadeTotal;
|
|
buf16[ 1 ] = buf16[ 1 ] * scale / fadeTotal;
|
|
buf16 += 2;
|
|
}
|
|
}
|
|
|
|
framesRead += written;
|
|
|
|
return (int) written;
|
|
}
|
|
|
|
- (void)closeDecoder
|
|
{
|
|
if ( emulatorCore ) {
|
|
if ( type == 0x22 ) {
|
|
GBASystem * system = ( GBASystem * ) emulatorCore;
|
|
CPUCleanUp( system );
|
|
soundShutdown( system );
|
|
delete system;
|
|
} else if ( type == 0x24 ) {
|
|
NDS_state * state = ( NDS_state * ) emulatorCore;
|
|
state_deinit(state);
|
|
free(state);
|
|
}else if ( type == 0x25 ) {
|
|
Player * player = ( Player * ) emulatorCore;
|
|
delete player;
|
|
} else {
|
|
free( emulatorCore );
|
|
}
|
|
emulatorCore = nil;
|
|
}
|
|
|
|
if ( type == 2 && emulatorExtra ) {
|
|
psf2fs_delete( emulatorExtra );
|
|
emulatorExtra = nil;
|
|
} else if ( type == 0x22 && emulatorExtra ) {
|
|
delete ( gsf_sound_out * ) emulatorExtra;
|
|
emulatorExtra = nil;
|
|
} else if ( type == 0x24 && emulatorExtra ) {
|
|
free( emulatorExtra );
|
|
emulatorExtra = nil;
|
|
} else if ( type == 0x25 && emulatorExtra ) {
|
|
struct ncsf_loader_state * state = ( struct ncsf_loader_state * ) emulatorExtra;
|
|
delete state;
|
|
emulatorExtra = nil;
|
|
} else if ( type == 0x41 && emulatorExtra ) {
|
|
struct qsf_loader_state * state = ( struct qsf_loader_state * ) emulatorExtra;
|
|
free( state->key );
|
|
free( state->z80_rom );
|
|
free( state->sample_rom );
|
|
free( state );
|
|
emulatorExtra = nil;
|
|
}
|
|
}
|
|
|
|
- (void)close
|
|
{
|
|
[self closeDecoder];
|
|
[currentSource release];
|
|
[[psf_file_container instance] remove_hint:currentUrl];
|
|
[currentUrl release];
|
|
}
|
|
|
|
- (long)seek:(long)frame
|
|
{
|
|
if (frame < framesRead || emulatorCore == NULL) {
|
|
[self closeDecoder];
|
|
if (![self initializeDecoder])
|
|
return -1;
|
|
}
|
|
|
|
unsigned long buffered_samples = silence_test_buffer.data_available() / 2;
|
|
if ( buffered_samples >= ( frame - framesRead ) )
|
|
{
|
|
frame -= framesRead;
|
|
silence_test_buffer.read( NULL, frame * 2 );
|
|
framesRead += frame;
|
|
return framesRead;
|
|
}
|
|
else if ( buffered_samples )
|
|
{
|
|
silence_test_buffer.read( NULL, buffered_samples * 2 );
|
|
framesRead += buffered_samples;
|
|
}
|
|
|
|
if ( type == 1 || type == 2 )
|
|
{
|
|
do
|
|
{
|
|
uint32_t howmany = (uint32_t)(frame - framesRead);
|
|
if ( psx_execute( emulatorCore, 0x7fffffff, 0, &howmany, 0 ) < 0 ) break;
|
|
framesRead += howmany;
|
|
}
|
|
while (framesRead < frame);
|
|
}
|
|
else if ( type == 0x11 || type == 0x12 )
|
|
{
|
|
do
|
|
{
|
|
uint32_t howmany = (uint32_t)(frame - framesRead);
|
|
if ( sega_execute( emulatorCore, 0x7fffffff, 0, &howmany ) < 0 ) break;
|
|
framesRead += howmany;
|
|
}
|
|
while (framesRead < frame);
|
|
}
|
|
else if ( type == 0x22 )
|
|
{
|
|
GBASystem * system = ( GBASystem * ) emulatorCore;
|
|
struct gsf_sound_out * sound_out = ( struct gsf_sound_out * ) emulatorExtra;
|
|
|
|
long frames_to_run = frame - framesRead;
|
|
|
|
do
|
|
{
|
|
if ( frames_to_run * 4 >= sound_out->samples_written )
|
|
{
|
|
frames_to_run -= sound_out->samples_written / 4;
|
|
sound_out->samples_written = 0;
|
|
}
|
|
else
|
|
{
|
|
sound_out->samples_written -= frames_to_run * 4;
|
|
memcpy( sound_out->buffer, sound_out->buffer + frames_to_run * 4, sound_out->samples_written );
|
|
frames_to_run = 0;
|
|
}
|
|
|
|
if ( frames_to_run )
|
|
{
|
|
CPULoop( system, 250000 );
|
|
if ( !sound_out->samples_written ) break;
|
|
}
|
|
} while ( frames_to_run );
|
|
|
|
framesRead = frame;
|
|
}
|
|
else if ( type == 0x24 )
|
|
{
|
|
NDS_state * state = ( NDS_state * ) emulatorCore;
|
|
s16 temp[2048];
|
|
|
|
long frames_to_run = frame - framesRead;
|
|
|
|
while ( frames_to_run )
|
|
{
|
|
unsigned frames_this_run = 1024;
|
|
if ( frames_this_run > frames_to_run )
|
|
frames_this_run = (unsigned) frames_to_run;
|
|
|
|
state_render(state, temp, frames_this_run);
|
|
|
|
frames_to_run -= frames_this_run;
|
|
}
|
|
|
|
framesRead = frame;
|
|
}
|
|
else if ( type == 0x25 )
|
|
{
|
|
Player * player = ( Player * ) emulatorCore;
|
|
ncsf_loader_state * state = ( ncsf_loader_state * ) emulatorExtra;
|
|
std::vector<uint8_t> & buffer = state->outputBuffer;
|
|
|
|
long frames_to_run = frame - framesRead;
|
|
|
|
while ( frames_to_run )
|
|
{
|
|
int frames_to_render = 1024;
|
|
if ( frames_to_render > frames_to_run ) frames_to_render = (int)frames_to_run;
|
|
|
|
player->GenerateSamples(buffer, 0, frames_to_render);
|
|
|
|
frames_to_run -= frames_to_render;
|
|
}
|
|
|
|
framesRead = frame;
|
|
}
|
|
else if ( type == 0x41 )
|
|
{
|
|
do
|
|
{
|
|
uint32_t howmany = (uint32_t)(frame - framesRead);
|
|
if ( qsound_execute( emulatorCore, 0x7fffffff, 0, &howmany ) < 0 ) break;
|
|
framesRead += howmany;
|
|
}
|
|
while ( framesRead < frame );
|
|
}
|
|
|
|
return framesRead;
|
|
}
|
|
|
|
- (NSDictionary *)properties
|
|
{
|
|
return [NSDictionary dictionaryWithObjectsAndKeys:
|
|
[NSNumber numberWithInt:2], @"channels",
|
|
[NSNumber numberWithInt:16], @"bitsPerSample",
|
|
[NSNumber numberWithFloat:sampleRate], @"sampleRate",
|
|
[NSNumber numberWithInteger:totalFrames], @"totalFrames",
|
|
[NSNumber numberWithInt:0], @"bitrate",
|
|
[NSNumber numberWithBool:YES], @"seekable",
|
|
[NSNumber numberWithFloat:replayGainAlbumGain], @"replayGainAlbumGain",
|
|
[NSNumber numberWithFloat:replayGainAlbumPeak], @"replayGainAlbumPeak",
|
|
[NSNumber numberWithFloat:replayGainTrackGain], @"replayGainTrackGain",
|
|
[NSNumber numberWithFloat:replayGainTrackPeak], @"replayGainTrackPeak",
|
|
[NSNumber numberWithFloat:volume], @"volume",
|
|
@"host", @"endian",
|
|
nil];
|
|
}
|
|
|
|
+ (NSDictionary *)metadataForURL:(NSURL *)url
|
|
{
|
|
struct psf_info_meta_state info;
|
|
|
|
info.info = [NSMutableDictionary dictionary];
|
|
info.utf8 = false;
|
|
info.tag_length_ms = 0;
|
|
info.tag_fade_ms = 0;
|
|
|
|
NSString * decodedUrl = [[url absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
|
|
|
if ( psf_load( [decodedUrl UTF8String], &source_callbacks, 0, 0, 0, psf_info_meta, &info ) <= 0)
|
|
return NO;
|
|
|
|
return info.info;
|
|
}
|
|
|
|
+ (NSArray *)fileTypes
|
|
{
|
|
return [NSArray arrayWithObjects:@"psf",@"minipsf",@"psf2", @"minipsf2", @"ssf", @"minissf", @"dsf", @"minidsf", @"qsf", @"miniqsf", @"gsf", @"minigsf", @"ncsf", @"minincsf", @"2sf", @"mini2sf", nil];
|
|
}
|
|
|
|
+ (NSArray *)mimeTypes
|
|
{
|
|
return [NSArray arrayWithObjects:@"audio/x-psf", nil];
|
|
}
|
|
|
|
+ (float)priority
|
|
{
|
|
return 1.0;
|
|
}
|
|
|
|
|
|
@end
|