/*
* UPSE: the unix playstation sound emulator.
*
* Filename: upse_loader_psf1.c
* Purpose: libupse: PSF/MiniPSF loader.
*
* Copyright (c) 2008 William Pitcock
*
* UPSE is free software, released under the GNU General Public License,
* version 2.
*
* A copy of the GNU General Public License, version 2, is included in
* the UPSE source kit as COPYING.
*
* UPSE is offered without any warranty of any kind, explicit or implicit.
*/
#include
#include
#include
#include
#include "upse-internal.h"
#include "upse.h"
#include "upse-string.h"
// LOAD STUFF
#ifdef WIN32_MSC
#pragma pack(4)
#endif
typedef struct
{
unsigned char id[8];
u32 text;
u32 data;
u32 pc0;
u32 gp0;
u32 t_addr;
u32 t_size;
u32 d_addr;
u32 d_size;
u32 b_addr;
u32 b_size;
u32 s_addr;
u32 s_size;
u32 SavedSP;
u32 SavedFP;
u32 SavedGP;
u32 SavedRA;
u32 SavedS0;
} upse_packed_t upse_exe_header_t;
static char *_upse_resolve_path(const char *f, const char *newfile)
{
static char *ret;
char *tp1;
#if PSS_STYLE==1
tp1 = ((char *) strrchr(f, '/'));
#else
tp1 = ((char *) strrchr(f, '\\'));
#if PSS_STYLE!=3
{
char *tp3;
tp3 = ((char *) strrchr(f, '/'));
if (tp1 < tp3)
tp1 = tp3;
tp3 = ((char *) strrchr(f, '|'));
if (tp1 < tp3)
tp1 = tp3;
}
#endif
#endif
if (!tp1)
{
ret = malloc(strlen(newfile) + 1);
strcpy(ret, newfile);
}
else
{
ret = malloc(tp1 - f + 2 + strlen(newfile)); // 1(NULL), 1(/).
memcpy(ret, f, tp1 - f + 1);
ret[tp1 - f + 1] = 0;
strcat(ret, newfile);
}
return (ret);
}
// Type==1 for just info load.
static upse_psf_t *_upse_load_psf(upse_module_instance_t *ins, void *fp, const char *path, int level, int type, const upse_iofuncs_t *funcs);
static upse_psf_t *
_upse_load_psf_from_file(upse_module_instance_t *ins, const char *path, int level, int type, const upse_iofuncs_t *funcs)
{
void *fp;
upse_psf_t *ret;
if (!(fp = funcs->open_impl(path, "rb")))
{
_ERROR("path %s failed to load\n", path);
return NULL;
}
ret = _upse_load_psf(ins, fp, path, level, type, funcs);
funcs->close_impl(fp);
return ret;
}
static upse_psf_t *
_upse_load_psf(upse_module_instance_t *ins, void *fp, const char *path, int level, int type, const upse_iofuncs_t *funcs)
{
upse_exe_header_t tmpHead;
unsigned char *in, *out = 0;
u64 outlen;
u32 t_size;
upse_psf_t *psfi;
upse_xsf_t *xsf;
u32 inlen;
u32 refresh;
_ENTER;
in = upse_get_buffer(fp, funcs, &inlen);
xsf = upse_xsf_decode(in, inlen, &out, &outlen);
memcpy(&tmpHead, out, min(outlen, sizeof(upse_exe_header_t)));
if (outlen < sizeof(upse_exe_header_t)) memset(((u8*)&tmpHead)+outlen, 0, sizeof(upse_exe_header_t)-outlen);
if (outlen < 0x800) t_size = 0; else t_size = outlen - 0x800;
psfi = calloc(sizeof(upse_psf_t), 1);
psfi->xsf = xsf;
psfi->volume = upse_strtof(xsf->inf_volume) * 32;
psfi->fade = upse_time_to_ms(xsf->inf_fade);
psfi->stop = upse_time_to_ms(xsf->inf_length);
psfi->title = xsf->inf_title;
psfi->artist = xsf->inf_artist;
psfi->copyright = xsf->inf_copy;
psfi->game = xsf->inf_game;
psfi->year = xsf->inf_year;
if ( *xsf->inf_refresh != '\0' )
{
refresh = atoi( xsf->inf_refresh );
if ( refresh ) upse_ps1_set_vsync( ins, refresh );
}
/* if querying information, we do not want to upset the emulator. */
if (!type)
{
ins->cpustate.pc = BFLIP32(tmpHead.pc0);
ins->cpustate.GPR.n.gp = BFLIP32(tmpHead.gp0);
ins->cpustate.GPR.n.sp = BFLIP32(tmpHead.s_addr);
if (ins->cpustate.GPR.n.sp == 0)
ins->cpustate.GPR.n.sp = 0x801fff00; /* first executable block in memory */
}
/* we are loading a psflib */
if (level && !type)
{
upse_ps1_memory_load(ins, BFLIP32(tmpHead.t_addr), t_size, out + 0x800);
free(in);
free(out);
return psfi;
}
if (!type && *xsf->lib != '\0')
{
upse_psf_t *tmpi;
if (*xsf->lib != '\0')
{
char *tmpfn;
tmpfn = _upse_resolve_path(path, xsf->lib);
tmpi = _upse_load_psf_from_file(ins, tmpfn, level + 1, 0, funcs);
free(tmpfn);
upse_free_psf_metadata(tmpi);
}
} // if(!type)
if (!level && !type)
{
upse_psf_t *tmpi;
char *lib;
int i;
u32 ba[3]; /* table holding base addresses for restore after loading aux libs */
upse_ps1_memory_load(ins, BFLIP32(tmpHead.t_addr), t_size, out + 0x800);
free(in);
free(out);
for (lib = xsf->libaux[0], i = 1; *lib != '\0'; lib = xsf->libaux[i], i++)
{
char *tmpfn;
ba[0] = ins->cpustate.pc;
ba[1] = ins->cpustate.GPR.n.gp;
ba[2] = ins->cpustate.GPR.n.sp;
tmpfn = _upse_resolve_path(path, lib);
tmpi = _upse_load_psf_from_file(ins, tmpfn, level + 1, 0, funcs);
if (tmpi == NULL)
continue;
free(tmpfn);
upse_free_psf_metadata(tmpi);
ins->cpustate.pc = ba[0];
ins->cpustate.GPR.n.gp = ba[1];
ins->cpustate.GPR.n.sp = ba[2];
}
}
_LEAVE;
return psfi;
}
void upse_free_psf_metadata(upse_psf_t * info)
{
if (info == NULL)
return;
if (info->xsf != NULL)
free(info->xsf);
free(info);
}
upse_psf_t *
upse_get_psf_metadata(const char *path, const upse_iofuncs_t * iofuncs)
{
void *fp;
upse_psf_t *ret;
u8 *in;
u8 *out;
u64 outlen;
upse_xsf_t *xsf;
u32 inlen;
_ENTER;
if (!(fp = iofuncs->open_impl(path, "rb")))
{
_ERROR("path %s failed to load\n", path);
return NULL;
}
in = upse_get_buffer(fp, iofuncs, &inlen);
xsf = upse_xsf_decode(in, inlen, &out, &outlen);
iofuncs->close_impl(fp);
ret = calloc(sizeof(upse_psf_t), 1);
ret->xsf = xsf;
ret->volume = upse_strtof(xsf->inf_volume) * 32;
ret->fade = upse_time_to_ms(xsf->inf_fade);
ret->stop = upse_time_to_ms(xsf->inf_length);
ret->title = xsf->inf_title;
ret->artist = xsf->inf_artist;
ret->copyright = xsf->inf_copy;
ret->game = xsf->inf_game;
ret->year = xsf->inf_year;
if (ret->stop == (u32) ~ 0)
ret->fade = 0;
ret->length = ret->stop + ret->fade;
free(in);
free(out);
_LEAVE;
return ret;
}
upse_module_t *
upse_load_psf(void *fp, const char *path, const upse_iofuncs_t * iofuncs)
{
upse_psf_t *psf;
upse_module_t *ret;
upse_module_instance_t *ins;
_ENTER;
ret = (upse_module_t *) calloc(sizeof(upse_module_t), 1);
ins = &ret->instance;
upse_ps1_init(ins);
upse_ps1_reset(ins, UPSE_PSX_REV_PS1);
if (!(psf = _upse_load_psf(ins, fp, path, 0, 0, iofuncs)))
{
upse_ps1_shutdown(ins);
free(ret);
return NULL;
}
if (psf->stop == (u32) ~ 0)
psf->fade = 0;
upse_ps1_spu_setvolume(ret->instance.spu, psf->volume);
upse_ps1_spu_setlength(ret->instance.spu, psf->stop, psf->fade);
psf->length = psf->stop + psf->fade;
psf->rate = 44100;
/*
* Several rips by CaitSith2 have an invalid jump to the branch delay slot
* before the BNE instruction, which causes the branching unit to deadlock
* when we're cycle-accurate or running on the real deal. This patch has
* existed for some time in PlayPSF, actually, but since PlayPSF has been
* separated from UPSE in UPSE2, we have to patch it here in the loader.
*
* Before, we would just stuff the PSF into memory after chainloading
* PlayPSF.exe.
*
* -- nenolod.
*/
_DEBUG("checking for CaitSith2's JNE-delay-slot bug in this rip");
if (PSXMu32(ins, 0xbc090) == BFLIP32(0x0802f040)) {
_DEBUG("...fixing. :(");
PSXMu32(ins, 0xbc090) = BFLIP32(0);
PSXMu32(ins, 0xbc094) = BFLIP32(0x0802f040);
PSXMu32(ins, 0xbc098) = BFLIP32(0);
}
_DEBUG("applying bugfixes for naughtydog replayers...");
if (PSXMu32(ins, 0x118b8) == BFLIP32(0x1060fffd)) {
_DEBUG("naughtydog patch applied.");
PSXMu32(ins, 0x118b8) = BFLIP32(0);
}
ret->metadata = psf;
ret->evloop_run = upse_r3000_cpu_execute;
ret->evloop_stop = upse_ps1_spu_stop;
ret->evloop_render = upse_r3000_cpu_execute_render;
ret->evloop_setcb = upse_ps1_spu_set_audio_callback;
ret->evloop_seek = upse_ps1_spu_seek;
_LEAVE;
return ret;
}