cog/Frameworks/HighlyExperimental/HighlyExperimental/Core/psx.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;
}
}
/////////////////////////////////////////////////////////////////////////////