464 lines
13 KiB
C
464 lines
13 KiB
C
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// psx - Top-level emulation
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifndef EMU_COMPILE
|
|
#error "Hi I forgot to set EMU_COMPILE"
|
|
#endif
|
|
|
|
#include "psx.h"
|
|
|
|
#include "r3000.h"
|
|
#include "iop.h"
|
|
#include "spu.h"
|
|
#include "bios.h"
|
|
#include "ioptimer.h"
|
|
#include "spucore.h"
|
|
#include "vfs.h"
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Static init for the whole library
|
|
//
|
|
static uint8 library_was_initialized = 0;
|
|
|
|
static uint32 ps1preboot;
|
|
static uint32 ps2preboot;
|
|
|
|
//
|
|
// Deliberately create a NULL dereference
|
|
// Useful for calling attention to show-stopper problems like forgetting to
|
|
// call psx_init() or compiling with the wrong byte order
|
|
//
|
|
static void psx_hang(const char *message) {
|
|
for(;;) { *((volatile char*)0) = *message; }
|
|
}
|
|
|
|
//
|
|
// Endian check
|
|
//
|
|
static void psx_endian_check(void) {
|
|
uint32 num = 0x61626364;
|
|
// Big
|
|
if(!memcmp(&num, "abcd", 4)) {
|
|
#ifdef EMU_BIG_ENDIAN
|
|
return;
|
|
#endif
|
|
psx_hang("endian check");
|
|
}
|
|
// Little
|
|
if(!memcmp(&num, "dcba", 4)) {
|
|
#ifndef EMU_BIG_ENDIAN
|
|
return;
|
|
#endif
|
|
psx_hang("endian check");
|
|
}
|
|
// Don't know what!
|
|
psx_hang("endian check");
|
|
}
|
|
|
|
//
|
|
// Data type size check
|
|
//
|
|
static void psx_size_check(void) {
|
|
if(sizeof(uint8 ) != 1) psx_hang("size check");
|
|
if(sizeof(uint16) != 2) psx_hang("size check");
|
|
if(sizeof(uint32) != 4) psx_hang("size check");
|
|
if(sizeof(uint64) != 8) psx_hang("size check");
|
|
if(sizeof(sint8 ) != 1) psx_hang("size check");
|
|
if(sizeof(sint16) != 2) psx_hang("size check");
|
|
if(sizeof(sint32) != 4) psx_hang("size check");
|
|
if(sizeof(sint64) != 8) psx_hang("size check");
|
|
}
|
|
|
|
static uint32 EMU_CALL getenvhex(const char *name) {
|
|
uint32 value;
|
|
char s[100];
|
|
char *t = s;
|
|
if(bios_getenv(name, s, sizeof(s))) psx_hang("getenv failed");
|
|
s[99] = 0;
|
|
value = 0;
|
|
for(;;) {
|
|
int c = *t++;
|
|
if(!c) break;
|
|
if(c >= 'a' && c <= 'f') { c -= 'a'; c += 10; }
|
|
else if(c >= 'A' && c <= 'F') { c -= 'A'; c += 10; }
|
|
else if(c >= '0' && c <= '9') { c -= '0'; c += 0; }
|
|
else psx_hang("invalid hex string");
|
|
value <<= 4;
|
|
value += c;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
sint32 EMU_CALL psx_init(void) {
|
|
sint32 r;
|
|
psx_endian_check();
|
|
psx_size_check();
|
|
|
|
// BIOS must be loaded first
|
|
if ( !bios_get_image_native() || !bios_get_imagesize() ) return 0;
|
|
//
|
|
// BIOS must be a power of 2, or all hell breaks loose
|
|
//
|
|
{ uint32 s = bios_get_imagesize();
|
|
if(s & (s - 1)) psx_hang("imagesize error");
|
|
}
|
|
//
|
|
// Environment inits
|
|
//
|
|
ps1preboot = getenvhex("ps1preboot");
|
|
ps2preboot = getenvhex("ps2preboot");
|
|
|
|
r = iop_init(); if(r) return r;
|
|
r = ioptimer_init(); if(r) return r;
|
|
r = r3000_init(); if(r) return r;
|
|
r = spu_init(); if(r) return r;
|
|
r = spucore_init(); if(r) return r;
|
|
r = vfs_init(); if(r) return r;
|
|
library_was_initialized = 1;
|
|
return 0;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Version information
|
|
//
|
|
const char* EMU_CALL psx_getversion(void) {
|
|
static const char s[] = "PSXCore0008 (built " __DATE__ ")";
|
|
return s;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// State information
|
|
//
|
|
struct PSX_STATE {
|
|
uint8 version;
|
|
uint32 offset_to_vfs;
|
|
uint32 offset_to_iop;
|
|
psx_console_out_t console_callback;
|
|
void *console_context;
|
|
uint8 console_enable;
|
|
};
|
|
|
|
#define PSXSTATE ((struct PSX_STATE*)(state))
|
|
#define VFSSTATE ((void*)(((char*)(state))+(PSXSTATE->offset_to_vfs)))
|
|
#define IOPSTATE ((void*)(((char*)(state))+(PSXSTATE->offset_to_iop)))
|
|
|
|
#define HAVE_VFS (PSXSTATE->offset_to_vfs!=0)
|
|
#define HAVE_IOP (PSXSTATE->offset_to_iop!=0)
|
|
|
|
uint32 EMU_CALL psx_get_state_size(uint8 version) {
|
|
uint32 size = 0;
|
|
if(version != 2) version = 1;
|
|
size += sizeof(struct PSX_STATE);
|
|
size += iop_get_state_size(version);
|
|
if(version == 2) size += vfs_get_state_size();
|
|
return size;
|
|
}
|
|
|
|
static EMU_INLINE void EMU_CALL preboot(struct PSX_STATE *state, uint32 target_address) {
|
|
sint32 r;
|
|
for(;;) {
|
|
uint32 s = 10000;
|
|
r = iop_execute(IOPSTATE, state, 10000, NULL, &s, 0);
|
|
if(r < 0) break;
|
|
if(r3000_getreg(iop_get_r3000_state(IOPSTATE), R3000_REG_PC) == target_address) break;
|
|
}
|
|
}
|
|
|
|
void EMU_CALL psx_clear_state(void *state, uint8 version) {
|
|
uint32 offset;
|
|
//
|
|
// If we haven't initialized, we really SHOULD have.
|
|
//
|
|
if(!library_was_initialized) psx_hang("library not initialized");
|
|
|
|
if(version != 2) version = 1;
|
|
// Clear local struct
|
|
memset(state, 0, sizeof(struct PSX_STATE));
|
|
// Set local version
|
|
PSXSTATE->version = version;
|
|
// Set up offsets
|
|
offset = sizeof(struct PSX_STATE);
|
|
if(version == 2) {
|
|
PSXSTATE->offset_to_vfs = offset; offset += vfs_get_state_size();
|
|
}
|
|
PSXSTATE->offset_to_iop = offset; offset += iop_get_state_size(version);
|
|
// Take care of VFS state
|
|
if(HAVE_VFS) vfs_clear_state(VFSSTATE);
|
|
// Take care of IOP state
|
|
if(HAVE_IOP) iop_clear_state(IOPSTATE, version);
|
|
//
|
|
// Do some final inits
|
|
//
|
|
switch(version) {
|
|
case 1:
|
|
//
|
|
// preboot for PS1
|
|
//
|
|
preboot(PSXSTATE, 0xBFC00000 | (ps1preboot & 0x003FFFFF));
|
|
r3000_setreg(iop_get_r3000_state(IOPSTATE), R3000_REG_PC , 0x80010000);
|
|
r3000_setreg(iop_get_r3000_state(IOPSTATE), R3000_REG_GEN+29, 0x801FFFF0);
|
|
break;
|
|
case 2:
|
|
//
|
|
// preboot for PS2
|
|
//
|
|
preboot(PSXSTATE, 0xBFC00000 | (ps2preboot & 0x003FFFFF));
|
|
//
|
|
// simulate jr $v0 to transfer execution to loadcore
|
|
//
|
|
r3000_setreg(iop_get_r3000_state(IOPSTATE), R3000_REG_PC,
|
|
r3000_getreg(iop_get_r3000_state(IOPSTATE), R3000_REG_GEN+2)
|
|
);
|
|
break;
|
|
}
|
|
// Done
|
|
}
|
|
|
|
//
|
|
// Obtain substates
|
|
//
|
|
void* EMU_CALL psx_get_iop_state(void *state) { return IOPSTATE; }
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Set readfile
|
|
//
|
|
void EMU_CALL psx_set_readfile(
|
|
void *state,
|
|
psx_readfile_t callback,
|
|
void *context
|
|
) {
|
|
if(HAVE_VFS) vfs_set_readfile(VFSSTATE, callback, context);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Console
|
|
//
|
|
void EMU_CALL psx_set_console_out(
|
|
void *state,
|
|
psx_console_out_t callback,
|
|
void *context
|
|
) {
|
|
PSXSTATE->console_callback = callback;
|
|
PSXSTATE->console_context = context;
|
|
}
|
|
|
|
void EMU_CALL psx_console_in(void *state, char c) {
|
|
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
static EMU_INLINE uint32 EMU_CALL get32lsb(uint8 *src) {
|
|
return
|
|
((((uint32)(src[0])) & 0xFF) << 0) |
|
|
((((uint32)(src[1])) & 0xFF) << 8) |
|
|
((((uint32)(src[2])) & 0xFF) << 16) |
|
|
((((uint32)(src[3])) & 0xFF) << 24);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Determine if an ASCII string exists in a byte block
|
|
//
|
|
static int string_exists(const char *block, uint32 len, const char *string) {
|
|
uint32 sl = (uint32)strlen(string);
|
|
uint32 i;
|
|
if(sl > len) return 0;
|
|
len = (len - sl) + 1;
|
|
for(i = 0; i < len; i++) {
|
|
if(memcmp(block + i, string, sl) == 0) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Upload PS-X EXE - must INCLUDE the header.
|
|
// If the header includes the strings "North America", "Japan", or "Europe",
|
|
// the appropriate refresh rate is set.
|
|
// Returns nonzero on error.
|
|
// Will return error for PS2.
|
|
//
|
|
sint32 EMU_CALL psx_upload_psxexe(void *state, void *program, uint32 size) {
|
|
uint32 init_pc;
|
|
uint32 init_sp;
|
|
uint32 text_start;
|
|
uint32 text_size;
|
|
|
|
if(PSXSTATE->version != 1) return -1;
|
|
|
|
if(size < 0x801) return -1;
|
|
if(memcmp(program, "PS-X EXE", 8)) return -1;
|
|
|
|
text_start = get32lsb(((uint8*)program) + 0x18);
|
|
text_size = get32lsb(((uint8*)program) + 0x1C);
|
|
init_pc = get32lsb(((uint8*)program) + 0x10);
|
|
init_sp = get32lsb(((uint8*)program) + 0x30);
|
|
|
|
// Try to determine the region, or leave it at the default if it's not found
|
|
if(string_exists(program, 0x800, "North America")) { psx_set_refresh(state, 60); }
|
|
else if(string_exists(program, 0x800, "Japan" )) { psx_set_refresh(state, 60); }
|
|
else if(string_exists(program, 0x800, "Europe" )) { psx_set_refresh(state, 50); }
|
|
|
|
if(text_size > (size - 0x800)) { text_size = (size - 0x800); }
|
|
|
|
iop_upload_to_ram(IOPSTATE, text_start, ((uint8*)program) + 0x800, text_size);
|
|
|
|
r3000_setreg(iop_get_r3000_state(IOPSTATE), R3000_REG_PC , init_pc);
|
|
r3000_setreg(iop_get_r3000_state(IOPSTATE), R3000_REG_GEN+29, init_sp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Set the screen refresh rate in Hz (50 or 60 for PAL or NTSC)
|
|
// Only 50 or 60 are valid; other values will be ignored
|
|
//
|
|
void EMU_CALL psx_set_refresh(void *state, uint32 refresh) {
|
|
iop_set_refresh(IOPSTATE, refresh);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Executes the given number of cycles or the given number of samples
|
|
// (whichever is less)
|
|
//
|
|
// Sets *sound_samples to the number of samples actually generated,
|
|
// which may be ZERO or LESS than the number requested, but never more.
|
|
//
|
|
// Return value:
|
|
// >= 0 The number of cycles actually executed, which may be ZERO, MORE,
|
|
// or LESS than the number requested
|
|
// -1 Halted successfully (only applicable to PS2 environment)
|
|
// <= -2 Unrecoverable error
|
|
//
|
|
sint32 EMU_CALL psx_execute(
|
|
void *state,
|
|
sint32 cycles,
|
|
sint16 *sound_buf,
|
|
uint32 *sound_samples,
|
|
uint32 event_mask
|
|
) {
|
|
return iop_execute(IOPSTATE, state, cycles, sound_buf, sound_samples, event_mask);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// emu virtual I/O
|
|
//
|
|
static sint32 EMU_CALL vopen(void *vfsstate, uint8 *ram_native, uint32 ram_size, sint32 ofs) {
|
|
char path[256];
|
|
sint32 i;
|
|
for(i = 0; i < 255; i++) {
|
|
char c = ram_native[(ofs & (ram_size-1)) ^ (EMU_ENDIAN_XOR(3))]; ofs++;
|
|
if(!c) break;
|
|
path[i] = c;
|
|
}
|
|
if(!i) return -2; // ENOENT if path is empty
|
|
path[i] = 0;
|
|
i = vfs_open(vfsstate, path);
|
|
return i;
|
|
}
|
|
|
|
static sint32 EMU_CALL vclose(void *vfsstate, sint32 emufd) {
|
|
if(emufd < 0) return -9;
|
|
return vfs_close(vfsstate, emufd);
|
|
}
|
|
|
|
#ifdef PSX_BIG_ENDIAN
|
|
//
|
|
// This byte swapper performs NO bounds checking. Prepare accordingly.
|
|
// Also, it assumes you're only calling it if PSX_BIG_ENDIAN is defined.
|
|
//
|
|
static void EMU_CALL vreadswap(uint8 *base, uint32 ofs, uint32 len) {
|
|
uint32 slop_0 = ofs & 3;
|
|
uint32 slop_1 = (0-(ofs+len)) & 3;
|
|
uint32 start = ofs - slop_0;
|
|
uint32 end = ofs + len + slop_1;
|
|
for(; start < end; start += 4) {
|
|
uint8 a, b, c, d;
|
|
a = base[start+0];
|
|
b = base[start+1];
|
|
c = base[start+2];
|
|
d = base[start+3];
|
|
base[start+0] = d;
|
|
base[start+1] = c;
|
|
base[start+2] = b;
|
|
base[start+3] = a;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static sint32 EMU_CALL vread(void *vfsstate, uint8 *ram_native, uint32 ram_size, sint32 emufd, sint32 ofs, sint32 len) {
|
|
sint32 r;
|
|
if(emufd < 0) return -9;
|
|
if(len < 0) return -22;
|
|
if(len >= (sint32)ram_size) return -22;
|
|
ofs &= (ram_size-1);
|
|
if((len + ofs) > (sint32)ram_size) return -22;
|
|
#ifdef PSX_BIG_ENDIAN
|
|
vreadswap(ram_native, ofs, len);
|
|
#endif
|
|
r = vfs_read(vfsstate, emufd, (char *)ram_native + ofs, len);
|
|
#ifdef PSX_BIG_ENDIAN
|
|
vreadswap(ram_native, ofs, len);
|
|
#endif
|
|
return r;
|
|
}
|
|
|
|
static sint32 EMU_CALL vlseek(void *vfsstate, sint32 emufd, sint32 offset, sint32 whence) {
|
|
if(emufd < 0) return -9;
|
|
if(whence < 0 || whence > 2) return -22;
|
|
if(whence == 0 && offset < 0) return -22;
|
|
return vfs_lseek(vfsstate, emufd, offset, whence);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// hefile emucall handler
|
|
//
|
|
sint32 EMU_CALL psx_emucall(
|
|
void *state,
|
|
uint8 *ram_native,
|
|
uint32 ram_size,
|
|
sint32 type,
|
|
sint32 emufd,
|
|
sint32 ofs,
|
|
sint32 arg1,
|
|
sint32 arg2
|
|
) {
|
|
if(type == 0) {
|
|
if(PSXSTATE->console_callback) {
|
|
sint32 i;
|
|
for(i = 0; i < arg1; i++) {
|
|
char c = ram_native[ofs & (ram_size-1)]; ofs++;
|
|
if(c == 'H') { PSXSTATE->console_enable = 1; }
|
|
if(PSXSTATE->console_enable) {
|
|
(PSXSTATE->console_callback)(PSXSTATE->console_context, c);
|
|
}
|
|
}
|
|
}
|
|
return arg1;
|
|
}
|
|
if(!(HAVE_VFS)) return -5;
|
|
switch(type) {
|
|
case 3: return vopen (VFSSTATE, ram_native, ram_size, ofs);
|
|
case 4: return vclose(VFSSTATE, emufd);
|
|
case 5: return vread (VFSSTATE, ram_native, ram_size, emufd, ofs, arg1);
|
|
case 6: return -13; // EACCES permission denied
|
|
case 7: return vlseek(VFSSTATE, emufd, arg1, arg2);
|
|
default: return -5;
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|