///////////////////////////////////////////////////////////////////////////// // // 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; } } /////////////////////////////////////////////////////////////////////////////