mirror of
https://github.com/libretro/beetle-psx-libretro.git
synced 2024-11-27 02:40:31 +00:00
4951 lines
131 KiB
C++
4951 lines
131 KiB
C++
#include "mednafen/mednafen.h"
|
|
#include "mednafen/mempatcher.h"
|
|
#include "mednafen/git.h"
|
|
#include "mednafen/general.h"
|
|
#include "mednafen/settings.h"
|
|
#include <compat/msvc.h>
|
|
#include "mednafen/psx/gpu.h"
|
|
#ifdef NEED_DEINTERLACER
|
|
#include "mednafen/video/Deinterlacer.h"
|
|
#endif
|
|
#include <libretro.h>
|
|
#include <rthreads/rthreads.h>
|
|
#include <streams/file_stream.h>
|
|
#include <string/stdstring.h>
|
|
#include <rhash.h>
|
|
#include "ugui_tools.h"
|
|
#include "rsx/rsx_intf.h"
|
|
#include "libretro_cbs.h"
|
|
#include "beetle_psx_globals.h"
|
|
#include "libretro_options.h"
|
|
#include "input.h"
|
|
|
|
#include "parallel-psx/custom-textures/dbg_input_callback.h"
|
|
retro_input_state_t dbg_input_state_cb = 0;
|
|
|
|
#include "mednafen/mednafen-endian.h"
|
|
#include "mednafen/mednafen-types.h"
|
|
#include "mednafen/psx/psx.h"
|
|
#include "mednafen/error.h"
|
|
|
|
#include "pgxp/pgxp_main.h"
|
|
|
|
#include <vector>
|
|
#define ISHEXDEC ((codeLine[cursor]>='0') && (codeLine[cursor]<='9')) || ((codeLine[cursor]>='a') && (codeLine[cursor]<='f')) || ((codeLine[cursor]>='A') && (codeLine[cursor]<='F'))
|
|
|
|
#ifdef HAVE_LIGHTREC
|
|
#include <sys/mman.h>
|
|
|
|
#ifdef HAVE_ASHMEM
|
|
#include <sys/ioctl.h>
|
|
#include <linux/ashmem.h>
|
|
#endif
|
|
|
|
#if defined(HAVE_SHM) || defined(HAVE_ASHMEM)
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_WIN_SHM
|
|
#include <windows.h>
|
|
#endif
|
|
#endif /* HAVE_LIGHTREC */
|
|
|
|
//Fast Save States exclude string labels from variables in the savestate, and are at least 20% faster.
|
|
extern bool FastSaveStates;
|
|
const int DEFAULT_STATE_SIZE = 16 * 1024 * 1024;
|
|
|
|
static bool libretro_supports_bitmasks = false;
|
|
static unsigned libretro_msg_interface_version = 0;
|
|
|
|
struct retro_perf_callback perf_cb;
|
|
retro_get_cpu_features_t perf_get_cpu_features_cb = NULL;
|
|
retro_log_printf_t log_cb;
|
|
static retro_audio_sample_t audio_cb;
|
|
static retro_audio_sample_batch_t audio_batch_cb;
|
|
static retro_input_poll_t input_poll_cb;
|
|
static retro_input_state_t input_state_cb;
|
|
static unsigned frame_count = 0;
|
|
static unsigned internal_frame_count = 0;
|
|
static bool display_internal_framerate = false;
|
|
static bool allow_frame_duping = false;
|
|
static bool failed_init = false;
|
|
static unsigned image_offset = 0;
|
|
static unsigned image_crop = 0;
|
|
static bool enable_memcard1 = false;
|
|
static bool enable_variable_serialization_size = false;
|
|
static int frame_width = 0;
|
|
static int frame_height = 0;
|
|
static bool gui_inited = false;
|
|
static bool gui_show = false;
|
|
static char bios_path[4096];
|
|
static bool firmware_found = false;
|
|
|
|
// Switchable memory cards
|
|
static int memcard_left_index = 0;
|
|
static int memcard_left_index_old;
|
|
static int memcard_right_index = 1;
|
|
static int memcard_right_index_old;
|
|
|
|
unsigned cd_2x_speedup = 1;
|
|
bool cd_async = false;
|
|
bool cd_warned_slow = false;
|
|
int64 cd_slow_timeout = 8000; // microseconds
|
|
|
|
// If true, PAL games will run at 60fps
|
|
bool fast_pal = false;
|
|
|
|
#ifdef HAVE_LIGHTREC
|
|
enum DYNAREC psx_dynarec;
|
|
bool psx_dynarec_invalidate;
|
|
uint8 psx_mmap = 0;
|
|
uint8 *psx_mem = NULL;
|
|
uint8 *psx_bios = NULL;
|
|
uint8 *psx_scratch = NULL;
|
|
#if defined(HAVE_ASHMEM)
|
|
int memfd;
|
|
#endif
|
|
#endif
|
|
|
|
uint32 EventCycles = 128;
|
|
|
|
// CPU overclock factor (or 0 if disabled)
|
|
int32_t psx_overclock_factor = 0;
|
|
// GPU rasterizer overclock shift
|
|
unsigned psx_gpu_overclock_shift = 0;
|
|
|
|
// Sets how often (in number of output frames/retro_run invocations)
|
|
// the internal framerace counter should be updated if
|
|
// display_internal_framerate is true.
|
|
#define INTERNAL_FPS_SAMPLE_PERIOD 64
|
|
|
|
static int psx_skipbios;
|
|
|
|
bool psx_gte_overclock;
|
|
enum dither_mode psx_gpu_dither_mode;
|
|
|
|
//iCB: PGXP options
|
|
unsigned int psx_pgxp_mode;
|
|
int psx_pgxp_2d_tol;
|
|
unsigned int psx_pgxp_vertex_caching;
|
|
unsigned int psx_pgxp_texture_correction;
|
|
unsigned int psx_pgxp_nclip;
|
|
// \iCB
|
|
|
|
#define NEGCON_RANGE 0x7FFF
|
|
|
|
char retro_save_directory[4096];
|
|
char retro_base_directory[4096];
|
|
char retro_cd_base_directory[4096];
|
|
static char retro_cd_path[4096];
|
|
char retro_cd_base_name[4096];
|
|
#ifdef _WIN32
|
|
static char retro_slash = '\\';
|
|
#else
|
|
static char retro_slash = '/';
|
|
#endif
|
|
|
|
enum
|
|
{
|
|
REGION_JP = 0,
|
|
REGION_NA = 1,
|
|
REGION_EU = 2,
|
|
};
|
|
|
|
static bool firmware_is_present(unsigned region)
|
|
{
|
|
static const size_t list_size = 10;
|
|
const char *bios_name_list[list_size];
|
|
const char *bios_sha1 = NULL;
|
|
|
|
log_cb(RETRO_LOG_INFO, "Checking if required firmware is present...\n");
|
|
|
|
/* SHA1 and alternate BIOS names sourced from
|
|
https://github.com/mamedev/mame/blob/master/src/mame/drivers/psx.cpp */
|
|
if (region == REGION_JP)
|
|
{
|
|
bios_name_list[0] = "scph5500.bin";
|
|
bios_name_list[1] = "SCPH5500.bin";
|
|
bios_name_list[2] = "SCPH-5500.bin";
|
|
bios_name_list[3] = NULL;
|
|
bios_name_list[4] = NULL;
|
|
bios_name_list[5] = NULL;
|
|
bios_name_list[6] = NULL;
|
|
bios_name_list[7] = NULL;
|
|
bios_name_list[8] = NULL;
|
|
bios_name_list[9] = NULL;
|
|
bios_sha1 = "B05DEF971D8EC59F346F2D9AC21FB742E3EB6917";
|
|
}
|
|
else if (region == REGION_NA)
|
|
{
|
|
bios_name_list[0] = "scph5501.bin";
|
|
bios_name_list[1] = "SCPH5501.bin";
|
|
bios_name_list[2] = "SCPH-5501.bin";
|
|
bios_name_list[3] = "scph5503.bin";
|
|
bios_name_list[4] = "SCPH5503.bin";
|
|
bios_name_list[5] = "SCPH-5503.bin";
|
|
bios_name_list[6] = "scph7003.bin";
|
|
bios_name_list[7] = "SCPH7003.bin";
|
|
bios_name_list[8] = "SCPH-7003.bin";
|
|
bios_name_list[9] = NULL;
|
|
bios_sha1 = "0555C6FAE8906F3F09BAF5988F00E55F88E9F30B";
|
|
}
|
|
else if (region == REGION_EU)
|
|
{
|
|
bios_name_list[0] = "scph5502.bin";
|
|
bios_name_list[1] = "SCPH5502.bin";
|
|
bios_name_list[2] = "SCPH-5502.bin";
|
|
bios_name_list[3] = "scph5552.bin";
|
|
bios_name_list[4] = "SCPH5552.bin";
|
|
bios_name_list[5] = "SCPH-5552.bin";
|
|
bios_name_list[6] = NULL;
|
|
bios_name_list[7] = NULL;
|
|
bios_name_list[8] = NULL;
|
|
bios_name_list[9] = NULL;
|
|
bios_sha1 = "F6BC2D1F5EB6593DE7D089C425AC681D6FFFD3F0";
|
|
}
|
|
|
|
size_t i;
|
|
for (i = 0; i < list_size; ++i)
|
|
{
|
|
if (!bios_name_list[i])
|
|
break;
|
|
|
|
int r = snprintf(bios_path, sizeof(bios_path), "%s%c%s", retro_base_directory, retro_slash, bios_name_list[i]);
|
|
if (r >= 4096)
|
|
{
|
|
log_cb(RETRO_LOG_ERROR, "Firmware path longer than 4095: %s\n", bios_path);
|
|
break;
|
|
}
|
|
|
|
if (filestream_exists(bios_path))
|
|
{
|
|
firmware_found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!firmware_found)
|
|
{
|
|
char s[4096];
|
|
|
|
log_cb(RETRO_LOG_ERROR, "Firmware is missing: %s\n", bios_name_list[0]);
|
|
s[4095] = '\0';
|
|
|
|
snprintf(s, sizeof(s), "Firmware is missing:\n\n%s", bios_name_list[0]);
|
|
|
|
gui_set_message(s);
|
|
gui_show = true;
|
|
|
|
return false;
|
|
}
|
|
|
|
char obtained_sha1[41];
|
|
sha1_calculate(bios_path, obtained_sha1);
|
|
if (strcmp(obtained_sha1, bios_sha1))
|
|
{
|
|
log_cb(RETRO_LOG_WARN, "Firmware found but has invalid SHA1: %s\n", bios_path);
|
|
log_cb(RETRO_LOG_WARN, "Expected SHA1: %s\n", bios_sha1);
|
|
log_cb(RETRO_LOG_WARN, "Obtained SHA1: %s\n", obtained_sha1);
|
|
log_cb(RETRO_LOG_WARN, "Unsupported firmware may cause emulation glitches.\n");
|
|
return true;
|
|
}
|
|
|
|
log_cb(RETRO_LOG_INFO, "Firmware found: %s\n", bios_path);
|
|
log_cb(RETRO_LOG_INFO, "Firmware SHA1: %s\n", obtained_sha1);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void extract_basename(char *buf, const char *path, size_t size)
|
|
{
|
|
const char *base = strrchr(path, '/');
|
|
if (!base)
|
|
base = strrchr(path, '\\');
|
|
if (!base)
|
|
base = path;
|
|
|
|
if (*base == '\\' || *base == '/')
|
|
base++;
|
|
|
|
strncpy(buf, base, size - strlen(buf) - 1);
|
|
buf[size - 1] = '\0';
|
|
|
|
char *ext = strrchr(buf, '.');
|
|
if (ext)
|
|
*ext = '\0';
|
|
}
|
|
|
|
static void extract_directory(char *buf, const char *path, size_t size)
|
|
{
|
|
strncpy(buf, path, size - 1);
|
|
buf[size - 1] = '\0';
|
|
|
|
char *base = strrchr(buf, '/');
|
|
if (!base)
|
|
base = strrchr(buf, '\\');
|
|
|
|
if (base)
|
|
*base = '\0';
|
|
else
|
|
buf[0] = '\0';
|
|
}
|
|
|
|
/* start of Mednafen psx.cpp */
|
|
|
|
/* Mednafen - Multi-system Emulator
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "mednafen/psx/psx.h"
|
|
#include "mednafen/psx/mdec.h"
|
|
#include "mednafen/psx/frontio.h"
|
|
#include "mednafen/psx/timer.h"
|
|
#include "mednafen/psx/sio.h"
|
|
#include "mednafen/psx/cdc.h"
|
|
#include "mednafen/psx/spu.h"
|
|
#include "mednafen/mempatcher.h"
|
|
|
|
#include <stdarg.h>
|
|
#include <ctype.h>
|
|
|
|
bool setting_apply_analog_toggle = false;
|
|
bool use_mednafen_memcard0_method = false;
|
|
|
|
extern MDFNGI EmulatedPSX;
|
|
|
|
#if PSX_DBGPRINT_ENABLE
|
|
static unsigned psx_dbg_level = 0;
|
|
|
|
void PSX_DBG(unsigned level, const char *format, ...)
|
|
{
|
|
if(psx_dbg_level >= level)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, format);
|
|
vprintf(format, ap);
|
|
va_end(ap);
|
|
}
|
|
}
|
|
#else
|
|
static unsigned const psx_dbg_level = 0;
|
|
#endif
|
|
|
|
/* Based off(but not the same as) public-domain "JKISS" PRNG. */
|
|
struct MDFN_PseudoRNG
|
|
{
|
|
uint32_t x,y,z,c;
|
|
uint64_t lcgo;
|
|
};
|
|
|
|
static MDFN_PseudoRNG PSX_PRNG;
|
|
|
|
uint32_t PSX_GetRandU32(uint32_t mina, uint32_t maxa)
|
|
{
|
|
uint32_t tmp;
|
|
const uint32_t range_m1 = maxa - mina;
|
|
uint32_t range_mask = range_m1;
|
|
|
|
range_mask |= range_mask >> 1;
|
|
range_mask |= range_mask >> 2;
|
|
range_mask |= range_mask >> 4;
|
|
range_mask |= range_mask >> 8;
|
|
range_mask |= range_mask >> 16;
|
|
|
|
do
|
|
{
|
|
uint64_t t = 4294584393ULL * PSX_PRNG.z + PSX_PRNG.c;
|
|
|
|
PSX_PRNG.x = 314527869 * PSX_PRNG.x + 1234567;
|
|
PSX_PRNG.y ^= PSX_PRNG.y << 5;
|
|
PSX_PRNG.y ^= PSX_PRNG.y >> 7;
|
|
PSX_PRNG.y ^= PSX_PRNG.y << 22;
|
|
PSX_PRNG.c = t >> 32;
|
|
PSX_PRNG.z = t;
|
|
PSX_PRNG.lcgo = (19073486328125ULL * PSX_PRNG.lcgo) + 1;
|
|
tmp = ((PSX_PRNG.x + PSX_PRNG.y + PSX_PRNG.z) ^ (PSX_PRNG.lcgo >> 16)) & range_mask;
|
|
} while(tmp > range_m1);
|
|
|
|
return(mina + tmp);
|
|
}
|
|
|
|
static std::vector<CDIF *> CDInterfaces; // FIXME: Cleanup on error out.
|
|
static std::vector<CDIF*> *cdifs = NULL;
|
|
static std::vector<const char *> cdifs_scex_ids;
|
|
|
|
static bool eject_state;
|
|
|
|
static bool CD_TrayOpen;
|
|
int CD_SelectedDisc; // -1 for no disc
|
|
|
|
static bool CD_IsPBP = false;
|
|
extern int PBP_DiscCount;
|
|
/* The global value PBP_DiscCount is set to
|
|
* zero when loading single-disk PBP files.
|
|
* We therefore have to maintain a separate
|
|
* 'physical' disk count, otherwise the
|
|
* frontend disk control interface will fail */
|
|
static int PBP_PhysicalDiscCount;
|
|
|
|
typedef struct
|
|
{
|
|
unsigned initial_index;
|
|
std::string initial_path;
|
|
std::vector<std::string> image_paths;
|
|
std::vector<std::string> image_labels;
|
|
} disk_control_ext_info_t;
|
|
|
|
static disk_control_ext_info_t disk_control_ext_info;
|
|
|
|
static uint64_t Memcard_PrevDC[8];
|
|
static int64_t Memcard_SaveDelay[8];
|
|
|
|
PS_CPU *PSX_CPU = NULL;
|
|
PS_SPU *PSX_SPU = NULL;
|
|
PS_CDC *PSX_CDC = NULL;
|
|
FrontIO *PSX_FIO = NULL;
|
|
|
|
MultiAccessSizeMem<512 * 1024, uint32, false> *BIOSROM = NULL;
|
|
MultiAccessSizeMem<65536, uint32, false> *PIOMem = NULL;
|
|
MultiAccessSizeMem<2048 * 1024, uint32, false> *MainRAM = NULL;
|
|
MultiAccessSizeMem<1024, uint32, false> *ScratchRAM = NULL;
|
|
|
|
#ifdef HAVE_LIGHTREC
|
|
/* Size of Expansion 1 (8MB) */
|
|
#define PSX_EXPANSION1_SIZE 0x800000U
|
|
/* Base address of Expansion 1 */
|
|
#define PSX_EXPANSION1_BASE 0x1F000000U
|
|
|
|
/* Mednafen splits the expansion in two buffers (PIOMem and TextMem). That's not
|
|
* super convenient for us so I'm going to copy both of them in one contiguous
|
|
* buffer */
|
|
const uint8_t *PSX_LoadExpansion1(void) {
|
|
static uint8_t *expansion1 = NULL;
|
|
|
|
if (expansion1 == NULL) {
|
|
expansion1 = new uint8_t[PSX_EXPANSION1_SIZE];
|
|
}
|
|
|
|
/* Let's read 32bits at a time to speed things up a bit */
|
|
uint32_t *p = reinterpret_cast<uint32_t *>(expansion1);
|
|
|
|
for (unsigned i = 0; i < PSX_EXPANSION1_SIZE / 4; i++) {
|
|
p[i] = PSX_MemPeek32(PSX_EXPANSION1_BASE + i * 4);
|
|
}
|
|
|
|
return expansion1;
|
|
}
|
|
#endif
|
|
|
|
static uint32_t TextMem_Start;
|
|
static std::vector<uint8> TextMem;
|
|
|
|
static const uint32_t SysControl_Mask[9] = { 0x00ffffff, 0x00ffffff, 0xffffffff, 0x2f1fffff,
|
|
0xffffffff, 0x2f1fffff, 0x2f1fffff, 0xffffffff,
|
|
0x0003ffff };
|
|
|
|
static const uint32_t SysControl_OR[9] = { 0x1f000000, 0x1f000000, 0x00000000, 0x00000000,
|
|
0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
|
0x00000000 };
|
|
|
|
static struct
|
|
{
|
|
union
|
|
{
|
|
struct
|
|
{
|
|
uint32_t PIO_Base; // 0x1f801000 // BIOS Init: 0x1f000000, Writeable bits: 0x00ffffff(assumed, verify), FixedOR = 0x1f000000
|
|
uint32_t Unknown0; // 0x1f801004 // BIOS Init: 0x1f802000, Writeable bits: 0x00ffffff, FixedOR = 0x1f000000
|
|
uint32_t Unknown1; // 0x1f801008 // BIOS Init: 0x0013243f, ????
|
|
uint32_t Unknown2; // 0x1f80100c // BIOS Init: 0x00003022, Writeable bits: 0x2f1fffff, FixedOR = 0x00000000
|
|
|
|
uint32_t BIOS_Mapping; // 0x1f801010 // BIOS Init: 0x0013243f, ????
|
|
uint32_t SPU_Delay; // 0x1f801014 // BIOS Init: 0x200931e1, Writeable bits: 0x2f1fffff, FixedOR = 0x00000000 - Affects bus timing on access to SPU
|
|
uint32_t CDC_Delay; // 0x1f801018 // BIOS Init: 0x00020843, Writeable bits: 0x2f1fffff, FixedOR = 0x00000000
|
|
uint32_t Unknown4; // 0x1f80101c // BIOS Init: 0x00070777, ????
|
|
uint32_t Unknown5; // 0x1f801020 // BIOS Init: 0x00031125(but rewritten with other values often), Writeable bits: 0x0003ffff, FixedOR = 0x00000000 -- Possibly CDC related
|
|
};
|
|
uint32_t Regs[9];
|
|
};
|
|
} SysControl;
|
|
|
|
static unsigned DMACycleSteal = 0; // Doesn't need to be saved in save states, since it's calculated in the ForceEventUpdates() call chain.
|
|
|
|
void PSX_SetDMACycleSteal(unsigned stealage)
|
|
{
|
|
if (stealage > 200) // Due to 8-bit limitations in the CPU core.
|
|
stealage = 200;
|
|
|
|
DMACycleSteal = stealage;
|
|
}
|
|
|
|
//
|
|
// Event stuff
|
|
//
|
|
|
|
static int32_t Running; // Set to -1 when not desiring exit, and 0 when we are.
|
|
|
|
struct event_list_entry
|
|
{
|
|
uint32_t which;
|
|
int32_t event_time;
|
|
event_list_entry *prev;
|
|
event_list_entry *next;
|
|
};
|
|
|
|
static event_list_entry events[PSX_EVENT__COUNT];
|
|
|
|
static void EventReset(void)
|
|
{
|
|
unsigned i;
|
|
for(i = 0; i < PSX_EVENT__COUNT; i++)
|
|
{
|
|
events[i].which = i;
|
|
|
|
if(i == PSX_EVENT__SYNFIRST)
|
|
events[i].event_time = (int32_t)0x80000000;
|
|
else if(i == PSX_EVENT__SYNLAST)
|
|
events[i].event_time = 0x7FFFFFFF;
|
|
else
|
|
events[i].event_time = PSX_EVENT_MAXTS;
|
|
|
|
events[i].prev = (i > 0) ? &events[i - 1] : NULL;
|
|
events[i].next = (i < (PSX_EVENT__COUNT - 1)) ? &events[i + 1] : NULL;
|
|
}
|
|
}
|
|
|
|
//static void RemoveEvent(event_list_entry *e)
|
|
//{
|
|
// e->prev->next = e->next;
|
|
// e->next->prev = e->prev;
|
|
//}
|
|
|
|
static void RebaseTS(const int32_t timestamp)
|
|
{
|
|
unsigned i;
|
|
for(i = 0; i < PSX_EVENT__COUNT; i++)
|
|
{
|
|
if(i == PSX_EVENT__SYNFIRST || i == PSX_EVENT__SYNLAST)
|
|
continue;
|
|
|
|
assert(events[i].event_time > timestamp);
|
|
events[i].event_time -= timestamp;
|
|
}
|
|
|
|
PSX_CPU->SetEventNT(events[PSX_EVENT__SYNFIRST].next->event_time);
|
|
}
|
|
|
|
void PSX_SetEventNT(const int type, const int32_t next_timestamp)
|
|
{
|
|
event_list_entry *e = &events[type];
|
|
|
|
if(next_timestamp < e->event_time)
|
|
{
|
|
event_list_entry *fe = e;
|
|
|
|
do
|
|
{
|
|
fe = fe->prev;
|
|
}while(next_timestamp < fe->event_time);
|
|
|
|
// Remove this event from the list, temporarily of course.
|
|
e->prev->next = e->next;
|
|
e->next->prev = e->prev;
|
|
|
|
// Insert into the list, just after "fe".
|
|
e->prev = fe;
|
|
e->next = fe->next;
|
|
fe->next->prev = e;
|
|
fe->next = e;
|
|
|
|
e->event_time = next_timestamp;
|
|
}
|
|
else if(next_timestamp > e->event_time)
|
|
{
|
|
event_list_entry *fe = e;
|
|
|
|
do
|
|
{
|
|
fe = fe->next;
|
|
} while(next_timestamp > fe->event_time);
|
|
|
|
// Remove this event from the list, temporarily of course
|
|
e->prev->next = e->next;
|
|
e->next->prev = e->prev;
|
|
|
|
// Insert into the list, just BEFORE "fe".
|
|
e->prev = fe->prev;
|
|
e->next = fe;
|
|
fe->prev->next = e;
|
|
fe->prev = e;
|
|
|
|
e->event_time = next_timestamp;
|
|
}
|
|
|
|
PSX_CPU->SetEventNT(events[PSX_EVENT__SYNFIRST].next->event_time & Running);
|
|
}
|
|
|
|
// Called from debug.cpp too.
|
|
void ForceEventUpdates(const int32_t timestamp)
|
|
{
|
|
PSX_SetEventNT(PSX_EVENT_GPU, GPU_Update(timestamp));
|
|
PSX_SetEventNT(PSX_EVENT_CDC, PSX_CDC->Update(timestamp));
|
|
|
|
PSX_SetEventNT(PSX_EVENT_TIMER, TIMER_Update(timestamp));
|
|
|
|
PSX_SetEventNT(PSX_EVENT_DMA, DMA_Update(timestamp));
|
|
|
|
PSX_SetEventNT(PSX_EVENT_FIO, PSX_FIO->Update(timestamp));
|
|
|
|
PSX_CPU->SetEventNT(events[PSX_EVENT__SYNFIRST].next->event_time);
|
|
}
|
|
|
|
bool MDFN_FASTCALL PSX_EventHandler(const int32_t timestamp)
|
|
{
|
|
event_list_entry *e = events[PSX_EVENT__SYNFIRST].next;
|
|
|
|
while(timestamp >= e->event_time) // If Running = 0, PSX_EventHandler() may be called even if there isn't an event per-se, so while() instead of do { ... } while
|
|
{
|
|
int32_t nt;
|
|
event_list_entry *prev = e->prev;
|
|
|
|
switch(e->which)
|
|
{
|
|
default:
|
|
abort();
|
|
case PSX_EVENT_GPU:
|
|
nt = GPU_Update(e->event_time);
|
|
break;
|
|
case PSX_EVENT_CDC:
|
|
nt = PSX_CDC->Update(e->event_time);
|
|
break;
|
|
case PSX_EVENT_TIMER:
|
|
nt = TIMER_Update(e->event_time);
|
|
break;
|
|
case PSX_EVENT_DMA:
|
|
nt = DMA_Update(e->event_time);
|
|
break;
|
|
case PSX_EVENT_FIO:
|
|
nt = PSX_FIO->Update(e->event_time);
|
|
break;
|
|
}
|
|
|
|
PSX_SetEventNT(e->which, nt);
|
|
|
|
// Order of events can change due to calling PSX_SetEventNT(), this prev business ensures we don't miss an event due to reordering.
|
|
e = prev->next;
|
|
}
|
|
|
|
return(Running);
|
|
}
|
|
|
|
|
|
void PSX_RequestMLExit(void)
|
|
{
|
|
Running = 0;
|
|
PSX_CPU->SetEventNT(0);
|
|
}
|
|
|
|
|
|
//
|
|
// End event stuff
|
|
//
|
|
|
|
|
|
/* Remember to update MemPeek<>() and MemPoke<>() when we change address decoding in MemRW() */
|
|
template<typename T, bool IsWrite, bool Access24> static INLINE void MemRW(int32_t ×tamp, uint32_t A, uint32_t &V)
|
|
{
|
|
#if 0
|
|
if(IsWrite)
|
|
printf("Write%d: %08x(orig=%08x), %08x\n", (int)(sizeof(T) * 8), A & mask[A >> 29], A, V);
|
|
else
|
|
printf("Read%d: %08x(orig=%08x)\n", (int)(sizeof(T) * 8), A & mask[A >> 29], A);
|
|
#endif
|
|
|
|
if(!IsWrite)
|
|
timestamp += DMACycleSteal;
|
|
|
|
//if(A == 0xa0 && IsWrite)
|
|
// DBG_Break();
|
|
|
|
if(A < 0x00800000)
|
|
{
|
|
if(IsWrite)
|
|
{
|
|
//timestamp++; // Best-case timing.
|
|
}
|
|
else
|
|
{
|
|
// Overclock: get rid of memory access latency
|
|
if (!psx_gte_overclock)
|
|
timestamp += 3;
|
|
}
|
|
|
|
if(Access24)
|
|
{
|
|
if(IsWrite)
|
|
MainRAM->WriteU24(A & 0x1FFFFF, V);
|
|
else
|
|
V = MainRAM->ReadU24(A & 0x1FFFFF);
|
|
}
|
|
else
|
|
{
|
|
if(IsWrite)
|
|
MainRAM->Write<T>(A & 0x1FFFFF, V);
|
|
else
|
|
V = MainRAM->Read<T>(A & 0x1FFFFF);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if(A >= 0x1FC00000 && A <= 0x1FC7FFFF)
|
|
{
|
|
if(!IsWrite)
|
|
{
|
|
if(Access24)
|
|
V = BIOSROM->ReadU24(A & 0x7FFFF);
|
|
else
|
|
V = BIOSROM->Read<T>(A & 0x7FFFF);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if(timestamp >= events[PSX_EVENT__SYNFIRST].next->event_time)
|
|
PSX_EventHandler(timestamp);
|
|
|
|
if(A >= 0x1F801000 && A <= 0x1F802FFF)
|
|
{
|
|
|
|
//if(IsWrite)
|
|
// printf("HW Write%d: %08x %08x\n", (unsigned int)(sizeof(T)*8), (unsigned int)A, (unsigned int)V);
|
|
//else
|
|
// printf("HW Read%d: %08x\n", (unsigned int)(sizeof(T)*8), (unsigned int)A);
|
|
|
|
if(A >= 0x1F801C00 && A <= 0x1F801FFF) // SPU
|
|
{
|
|
if(sizeof(T) == 4 && !Access24)
|
|
{
|
|
if(IsWrite)
|
|
{
|
|
//timestamp += 15;
|
|
|
|
//if(timestamp >= events[PSX_EVENT__SYNFIRST].next->event_time)
|
|
// PSX_EventHandler(timestamp);
|
|
|
|
PSX_SPU->Write(timestamp, A | 0, V);
|
|
PSX_SPU->Write(timestamp, A | 2, V >> 16);
|
|
}
|
|
else
|
|
{
|
|
timestamp += 36;
|
|
|
|
if(timestamp >= events[PSX_EVENT__SYNFIRST].next->event_time)
|
|
PSX_EventHandler(timestamp);
|
|
|
|
V = PSX_SPU->Read(timestamp, A) | (PSX_SPU->Read(timestamp, A | 2) << 16);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(IsWrite)
|
|
{
|
|
//timestamp += 8;
|
|
|
|
//if(timestamp >= events[PSX_EVENT__SYNFIRST].next->event_time)
|
|
// PSX_EventHandler(timestamp);
|
|
|
|
PSX_SPU->Write(timestamp, A & ~1, V);
|
|
}
|
|
else
|
|
{
|
|
timestamp += 16; // Just a guess, need to test.
|
|
|
|
if(timestamp >= events[PSX_EVENT__SYNFIRST].next->event_time)
|
|
PSX_EventHandler(timestamp);
|
|
|
|
V = PSX_SPU->Read(timestamp, A & ~1);
|
|
}
|
|
}
|
|
return;
|
|
} // End SPU
|
|
|
|
|
|
// CDC: TODO - 8-bit access.
|
|
if(A >= 0x1f801800 && A <= 0x1f80180F)
|
|
{
|
|
if(!IsWrite)
|
|
{
|
|
timestamp += 6 * sizeof(T); //24;
|
|
}
|
|
|
|
if(IsWrite)
|
|
PSX_CDC->Write(timestamp, A & 0x3, V);
|
|
else
|
|
V = PSX_CDC->Read(timestamp, A & 0x3);
|
|
|
|
return;
|
|
}
|
|
|
|
if(A >= 0x1F801810 && A <= 0x1F801817)
|
|
{
|
|
if(!IsWrite)
|
|
timestamp++;
|
|
|
|
if(IsWrite)
|
|
GPU_Write(timestamp, A, V);
|
|
else
|
|
V = GPU_Read(timestamp, A);
|
|
|
|
return;
|
|
}
|
|
|
|
if(A >= 0x1F801820 && A <= 0x1F801827)
|
|
{
|
|
if(!IsWrite)
|
|
timestamp++;
|
|
|
|
if(IsWrite)
|
|
MDEC_Write(timestamp, A, V);
|
|
else
|
|
V = MDEC_Read(timestamp, A);
|
|
|
|
return;
|
|
}
|
|
|
|
if(A >= 0x1F801000 && A <= 0x1F801023)
|
|
{
|
|
unsigned index = (A & 0x1F) >> 2;
|
|
|
|
if(!IsWrite)
|
|
timestamp++;
|
|
|
|
//if(A == 0x1F801014 && IsWrite)
|
|
// fprintf(stderr, "%08x %08x\n",A,V);
|
|
|
|
if(IsWrite)
|
|
{
|
|
V <<= (A & 3) * 8;
|
|
SysControl.Regs[index] = V & SysControl_Mask[index];
|
|
}
|
|
else
|
|
{
|
|
V = SysControl.Regs[index] | SysControl_OR[index];
|
|
V >>= (A & 3) * 8;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if(A >= 0x1F801040 && A <= 0x1F80104F)
|
|
{
|
|
if(!IsWrite)
|
|
timestamp++;
|
|
|
|
if(IsWrite)
|
|
PSX_FIO->Write(timestamp, A, V);
|
|
else
|
|
V = PSX_FIO->Read(timestamp, A);
|
|
return;
|
|
}
|
|
|
|
if(A >= 0x1F801050 && A <= 0x1F80105F)
|
|
{
|
|
if(!IsWrite)
|
|
timestamp++;
|
|
|
|
#if 0
|
|
if(IsWrite)
|
|
{
|
|
PSX_WARNING("[SIO] Write: 0x%08x 0x%08x %u", A, V, (unsigned)sizeof(T));
|
|
}
|
|
else
|
|
{
|
|
PSX_WARNING("[SIO] Read: 0x%08x", A);
|
|
}
|
|
#endif
|
|
|
|
if(IsWrite)
|
|
SIO_Write(timestamp, A, V);
|
|
else
|
|
V = SIO_Read(timestamp, A);
|
|
return;
|
|
}
|
|
|
|
#if 0
|
|
if(A >= 0x1F801060 && A <= 0x1F801063)
|
|
{
|
|
if(IsWrite)
|
|
{
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
}
|
|
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if(A >= 0x1F801070 && A <= 0x1F801077) // IRQ
|
|
{
|
|
if(!IsWrite)
|
|
timestamp++;
|
|
|
|
if(IsWrite)
|
|
::IRQ_Write(A, V);
|
|
else
|
|
V = ::IRQ_Read(A);
|
|
return;
|
|
}
|
|
|
|
if(A >= 0x1F801080 && A <= 0x1F8010FF) // DMA
|
|
{
|
|
if(!IsWrite)
|
|
timestamp++;
|
|
|
|
if(IsWrite)
|
|
DMA_Write(timestamp, A, V);
|
|
else
|
|
V = DMA_Read(timestamp, A);
|
|
|
|
return;
|
|
}
|
|
|
|
if(A >= 0x1F801100 && A <= 0x1F80113F) // Root counters
|
|
{
|
|
if(!IsWrite)
|
|
timestamp++;
|
|
|
|
if(IsWrite)
|
|
TIMER_Write(timestamp, A, V);
|
|
else
|
|
V = TIMER_Read(timestamp, A);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
if(A >= 0x1F000000 && A <= 0x1F7FFFFF)
|
|
{
|
|
if(!IsWrite)
|
|
{
|
|
//if((A & 0x7FFFFF) <= 0x84)
|
|
//PSX_WARNING("[PIO] Read%d from 0x%08x at time %d", (int)(sizeof(T) * 8), A, timestamp);
|
|
|
|
V = ~0U; // A game this affects: Tetris with Cardcaptor Sakura
|
|
|
|
if(PIOMem)
|
|
{
|
|
if((A & 0x7FFFFF) < 65536)
|
|
{
|
|
if(Access24)
|
|
V = PIOMem->ReadU24(A & 0x7FFFFF);
|
|
else
|
|
V = PIOMem->Read<T>(A & 0x7FFFFF);
|
|
}
|
|
else if((A & 0x7FFFFF) < (65536 + TextMem.size()))
|
|
{
|
|
if(Access24)
|
|
V = MDFN_de24lsb(&TextMem[(A & 0x7FFFFF) - 65536]);
|
|
else switch(sizeof(T))
|
|
{
|
|
case 1: V = TextMem[(A & 0x7FFFFF) - 65536]; break;
|
|
case 2: V = MDFN_de16lsb<false>(&TextMem[(A & 0x7FFFFF) - 65536]); break;
|
|
case 4: V = MDFN_de32lsb<false>(&TextMem[(A & 0x7FFFFF) - 65536]); break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if(A == 0xFFFE0130) // Per tests on PS1, ignores the access(sort of, on reads the value is forced to 0 if not aligned) if not aligned to 4-bytes.
|
|
{
|
|
if(!IsWrite)
|
|
V = PSX_CPU->GetBIU();
|
|
else
|
|
PSX_CPU->SetBIU(V);
|
|
|
|
return;
|
|
}
|
|
|
|
if(IsWrite)
|
|
{
|
|
PSX_WARNING("[MEM] Unknown write%d to %08x at time %d, =%08x(%d)", (int)(sizeof(T) * 8), A, timestamp, V, V);
|
|
}
|
|
else
|
|
{
|
|
V = 0;
|
|
PSX_WARNING("[MEM] Unknown read%d from %08x at time %d", (int)(sizeof(T) * 8), A, timestamp);
|
|
}
|
|
}
|
|
|
|
void MDFN_FASTCALL PSX_MemWrite8(int32_t timestamp, uint32_t A, uint32_t V)
|
|
{
|
|
MemRW<uint8, true, false>(timestamp, A, V);
|
|
}
|
|
|
|
void MDFN_FASTCALL PSX_MemWrite16(int32_t timestamp, uint32_t A, uint32_t V)
|
|
{
|
|
MemRW<uint16, true, false>(timestamp, A, V);
|
|
}
|
|
|
|
void MDFN_FASTCALL PSX_MemWrite24(int32_t timestamp, uint32_t A, uint32_t V)
|
|
{
|
|
MemRW<uint32, true, true>(timestamp, A, V);
|
|
}
|
|
|
|
void MDFN_FASTCALL PSX_MemWrite32(int32_t timestamp, uint32_t A, uint32_t V)
|
|
{
|
|
MemRW<uint32, true, false>(timestamp, A, V);
|
|
}
|
|
|
|
uint8_t MDFN_FASTCALL PSX_MemRead8(int32_t ×tamp, uint32_t A)
|
|
{
|
|
uint32_t V;
|
|
|
|
MemRW<uint8, false, false>(timestamp, A, V);
|
|
|
|
return(V);
|
|
}
|
|
|
|
uint16_t MDFN_FASTCALL PSX_MemRead16(int32_t ×tamp, uint32_t A)
|
|
{
|
|
uint32_t V;
|
|
|
|
MemRW<uint16, false, false>(timestamp, A, V);
|
|
|
|
return(V);
|
|
}
|
|
|
|
uint32_t MDFN_FASTCALL PSX_MemRead24(int32_t ×tamp, uint32_t A)
|
|
{
|
|
uint32_t V;
|
|
|
|
MemRW<uint32, false, true>(timestamp, A, V);
|
|
|
|
return(V);
|
|
}
|
|
|
|
uint32_t MDFN_FASTCALL PSX_MemRead32(int32_t ×tamp, uint32_t A)
|
|
{
|
|
uint32_t V;
|
|
|
|
MemRW<uint32, false, false>(timestamp, A, V);
|
|
|
|
return(V);
|
|
}
|
|
|
|
template<typename T, bool Access24> static INLINE uint32_t MemPeek(int32_t timestamp, uint32_t A)
|
|
{
|
|
if(A < 0x00800000)
|
|
{
|
|
if(Access24)
|
|
return(MainRAM->ReadU24(A & 0x1FFFFF));
|
|
return(MainRAM->Read<T>(A & 0x1FFFFF));
|
|
}
|
|
|
|
if(A >= 0x1FC00000 && A <= 0x1FC7FFFF)
|
|
{
|
|
if(Access24)
|
|
return(BIOSROM->ReadU24(A & 0x7FFFF));
|
|
return(BIOSROM->Read<T>(A & 0x7FFFF));
|
|
}
|
|
|
|
if(A >= 0x1F801000 && A <= 0x1F802FFF)
|
|
{
|
|
if(A >= 0x1F801C00 && A <= 0x1F801FFF) // SPU
|
|
{
|
|
// TODO
|
|
|
|
} // End SPU
|
|
|
|
|
|
// CDC: TODO - 8-bit access.
|
|
if(A >= 0x1f801800 && A <= 0x1f80180F)
|
|
{
|
|
// TODO
|
|
|
|
}
|
|
|
|
if(A >= 0x1F801810 && A <= 0x1F801817)
|
|
{
|
|
// TODO
|
|
|
|
}
|
|
|
|
if(A >= 0x1F801820 && A <= 0x1F801827)
|
|
{
|
|
// TODO
|
|
|
|
}
|
|
|
|
if(A >= 0x1F801000 && A <= 0x1F801023)
|
|
{
|
|
unsigned index = (A & 0x1F) >> 2;
|
|
return((SysControl.Regs[index] | SysControl_OR[index]) >> ((A & 3) * 8));
|
|
}
|
|
|
|
if(A >= 0x1F801040 && A <= 0x1F80104F)
|
|
{
|
|
// TODO
|
|
|
|
}
|
|
|
|
if(A >= 0x1F801050 && A <= 0x1F80105F)
|
|
{
|
|
// TODO
|
|
|
|
}
|
|
|
|
|
|
if(A >= 0x1F801070 && A <= 0x1F801077) // IRQ
|
|
{
|
|
// TODO
|
|
|
|
}
|
|
|
|
if(A >= 0x1F801080 && A <= 0x1F8010FF) // DMA
|
|
{
|
|
// TODO
|
|
|
|
}
|
|
|
|
if(A >= 0x1F801100 && A <= 0x1F80113F) // Root counters
|
|
{
|
|
// TODO
|
|
|
|
}
|
|
}
|
|
|
|
|
|
if(A >= 0x1F000000 && A <= 0x1F7FFFFF)
|
|
{
|
|
if(PIOMem)
|
|
{
|
|
if((A & 0x7FFFFF) < 65536)
|
|
{
|
|
if(Access24)
|
|
return(PIOMem->ReadU24(A & 0x7FFFFF));
|
|
return(PIOMem->Read<T>(A & 0x7FFFFF));
|
|
}
|
|
else if((A & 0x7FFFFF) < (65536 + TextMem.size()))
|
|
{
|
|
if(Access24)
|
|
return(MDFN_de24lsb(&TextMem[(A & 0x7FFFFF) - 65536]));
|
|
else switch(sizeof(T))
|
|
{
|
|
case 1:
|
|
return(TextMem[(A & 0x7FFFFF) - 65536]);
|
|
case 2:
|
|
return(MDFN_de16lsb<false>(&TextMem[(A & 0x7FFFFF) - 65536]));
|
|
case 4:
|
|
return(MDFN_de32lsb<false>(&TextMem[(A & 0x7FFFFF) - 65536]));
|
|
}
|
|
}
|
|
}
|
|
return(~0U);
|
|
}
|
|
|
|
if(A == 0xFFFE0130)
|
|
return PSX_CPU->GetBIU();
|
|
|
|
return(0);
|
|
}
|
|
|
|
uint8_t PSX_MemPeek8(uint32_t A)
|
|
{
|
|
return MemPeek<uint8, false>(0, A);
|
|
}
|
|
|
|
uint16_t PSX_MemPeek16(uint32_t A)
|
|
{
|
|
return MemPeek<uint16, false>(0, A);
|
|
}
|
|
|
|
uint32_t PSX_MemPeek32(uint32_t A)
|
|
{
|
|
return MemPeek<uint32, false>(0, A);
|
|
}
|
|
|
|
// FIXME: Add PSX_Reset() and FrontIO::Reset() so that emulated input devices don't get power-reset on reset-button reset.
|
|
static void PSX_Power(void)
|
|
{
|
|
unsigned i;
|
|
|
|
PSX_PRNG.x = 123456789;
|
|
PSX_PRNG.y = 987654321;
|
|
PSX_PRNG.z = 43219876;
|
|
PSX_PRNG.c = 6543217;
|
|
PSX_PRNG.lcgo = 0xDEADBEEFCAFEBABEULL;
|
|
|
|
cd_warned_slow = false;
|
|
|
|
memset(MainRAM->data32, 0, 2048 * 1024);
|
|
|
|
for(i = 0; i < 9; i++)
|
|
SysControl.Regs[i] = 0;
|
|
|
|
PSX_CPU->Power();
|
|
|
|
EventReset();
|
|
|
|
TIMER_Power();
|
|
|
|
DMA_Power();
|
|
|
|
PSX_FIO->Power();
|
|
SIO_Power();
|
|
|
|
MDEC_Power();
|
|
PSX_CDC->Power();
|
|
GPU_Power();
|
|
//SPU->Power(); // Called from CDC->Power()
|
|
IRQ_Power();
|
|
|
|
ForceEventUpdates(0);
|
|
}
|
|
|
|
template<typename T, bool Access24> static INLINE void MemPoke(pscpu_timestamp_t timestamp, uint32 A, T V)
|
|
{
|
|
if(A < 0x00800000)
|
|
{
|
|
if(Access24)
|
|
MainRAM->WriteU24(A & 0x1FFFFF, V);
|
|
else
|
|
MainRAM->Write<T>(A & 0x1FFFFF, V);
|
|
|
|
return;
|
|
}
|
|
|
|
if(A >= 0x1FC00000 && A <= 0x1FC7FFFF)
|
|
{
|
|
if(Access24)
|
|
BIOSROM->WriteU24(A & 0x7FFFF, V);
|
|
else
|
|
BIOSROM->Write<T>(A & 0x7FFFF, V);
|
|
|
|
return;
|
|
}
|
|
|
|
if(A >= 0x1F801000 && A <= 0x1F802FFF)
|
|
{
|
|
if(A >= 0x1F801000 && A <= 0x1F801023)
|
|
{
|
|
unsigned index = (A & 0x1F) >> 2;
|
|
SysControl.Regs[index] = (V << ((A & 3) * 8)) & SysControl_Mask[index];
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(A == 0xFFFE0130)
|
|
{
|
|
PSX_CPU->SetBIU(V);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void PSX_MemPoke8(uint32 A, uint8 V)
|
|
{
|
|
MemPoke<uint8, false>(0, A, V);
|
|
}
|
|
|
|
void PSX_MemPoke16(uint32 A, uint16 V)
|
|
{
|
|
MemPoke<uint16, false>(0, A, V);
|
|
}
|
|
|
|
void PSX_MemPoke32(uint32 A, uint32 V)
|
|
{
|
|
MemPoke<uint32, false>(0, A, V);
|
|
}
|
|
|
|
void PSX_GPULineHook(const int32_t timestamp, const int32_t line_timestamp, bool vsync, uint32_t *pixels, const MDFN_PixelFormat* const format, const unsigned width, const unsigned pix_clock_offset, const unsigned pix_clock, const unsigned pix_clock_divider, const unsigned surf_pitchinpix, const unsigned upscale_factor)
|
|
{
|
|
PSX_FIO->GPULineHook(timestamp, line_timestamp, vsync, pixels, format, width, pix_clock_offset, pix_clock, pix_clock_divider, surf_pitchinpix, upscale_factor);
|
|
}
|
|
|
|
static bool TestMagic(const char *name, RFILE *fp, int64_t size)
|
|
{
|
|
uint8_t header[8];
|
|
|
|
if (size < 0x800)
|
|
return(false);
|
|
|
|
filestream_read(fp, header, 8);
|
|
|
|
if (
|
|
(header[0] == 'P') &&
|
|
(header[1] == 'S') &&
|
|
(header[2] == '-') &&
|
|
(header[3] == 'X') &&
|
|
(header[4] == ' ') &&
|
|
(header[5] == 'E') &&
|
|
(header[6] == 'X') &&
|
|
(header[7] == 'E')
|
|
)
|
|
return(true);
|
|
|
|
return(true);
|
|
}
|
|
|
|
static bool TestMagicCD(std::vector<CDIF *> *_CDInterfaces)
|
|
{
|
|
uint8_t buf[2048];
|
|
TOC toc;
|
|
int dt;
|
|
|
|
TOC_Clear(&toc);
|
|
|
|
(*_CDInterfaces)[0]->ReadTOC(&toc);
|
|
|
|
dt = TOC_FindTrackByLBA(&toc, 4);
|
|
|
|
if(dt > 0 && !(toc.tracks[dt].control & 0x4))
|
|
return(false);
|
|
|
|
if((*_CDInterfaces)[0]->ReadSector(buf, 4, 1) != 0x2)
|
|
return(false);
|
|
|
|
if(strncmp((char *)buf + 10, "Licensed by", strlen("Licensed by")))
|
|
return(false);
|
|
|
|
//if(strncmp((char *)buf + 32, "Sony", 4))
|
|
// return(false);
|
|
|
|
//for(int i = 0; i < 2048; i++)
|
|
// printf("%d, %02x %c\n", i, buf[i], buf[i]);
|
|
//exit(1);
|
|
|
|
#if 0
|
|
{
|
|
uint8_t buf[2048 * 7];
|
|
|
|
if((*cdifs)[0]->ReadSector(buf, 5, 7) == 0x2)
|
|
{
|
|
printf("CRC32: 0x%08x\n", (uint32)crc32(0, &buf[0], 0x3278));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return(true);
|
|
}
|
|
|
|
static const char *CalcDiscSCEx_BySYSTEMCNF(CDIF *c, unsigned *rr)
|
|
{
|
|
uint8_t pvd[2048];
|
|
uint32_t rdel, rdel_len;
|
|
const char *ret = NULL;
|
|
Stream *fp = NULL;
|
|
unsigned pvd_search_count = 0;
|
|
|
|
fp = c->MakeStream(0, ~0U);
|
|
fp->seek(0x8000, SEEK_SET);
|
|
|
|
try // Added this back to fix audio-cd loading issue
|
|
{
|
|
do
|
|
{
|
|
if((pvd_search_count++) == 32)
|
|
{
|
|
log_cb(RETRO_LOG_ERROR, "PVD search count limit met.\n");
|
|
ret = NULL;
|
|
goto Breakout;
|
|
}
|
|
|
|
fp->read(pvd, 2048);
|
|
|
|
if(memcmp(&pvd[1], "CD001", 5))
|
|
{
|
|
log_cb(RETRO_LOG_ERROR, "Not ISO-9660\n");
|
|
ret = NULL;
|
|
goto Breakout;
|
|
}
|
|
|
|
if(pvd[0] == 0xFF)
|
|
{
|
|
log_cb(RETRO_LOG_ERROR, "Missing Primary Volume Descriptor\n");
|
|
ret = NULL;
|
|
goto Breakout;
|
|
}
|
|
} while(pvd[0] != 0x01);
|
|
|
|
/*[156 ... 189], 34 bytes */
|
|
rdel = MDFN_de32lsb<false>(&pvd[0x9E]);
|
|
rdel_len = MDFN_de32lsb<false>(&pvd[0xA6]);
|
|
|
|
if(rdel_len >= (1024 * 1024 * 10)) /* Arbitrary sanity check. */
|
|
{
|
|
log_cb(RETRO_LOG_ERROR, "Root directory table too large\n");
|
|
ret = NULL;
|
|
goto Breakout;
|
|
}
|
|
|
|
fp->seek((int64)rdel * 2048, SEEK_SET);
|
|
//printf("%08x, %08x\n", rdel * 2048, rdel_len);
|
|
while(fp->tell() < (((int64)rdel * 2048) + rdel_len))
|
|
{
|
|
uint8_t len_dr = fp->get_u8();
|
|
uint8_t dr[256 + 1];
|
|
|
|
memset(dr, 0xFF, sizeof(dr));
|
|
|
|
if(!len_dr)
|
|
break;
|
|
|
|
memset(dr, 0, sizeof(dr));
|
|
dr[0] = len_dr;
|
|
fp->read(dr + 1, len_dr - 1);
|
|
|
|
uint8_t len_fi = dr[0x20];
|
|
|
|
if(len_fi == 12 && !memcmp(&dr[0x21], "SYSTEM.CNF;1", 12))
|
|
{
|
|
uint32_t file_lba = MDFN_de32lsb<false>(&dr[0x02]);
|
|
//uint32_t file_len = MDFN_de32lsb<false>(&dr[0x0A]);
|
|
uint8_t fb[2048 + 1];
|
|
char *bootpos;
|
|
|
|
memset(fb, 0, sizeof(fb));
|
|
fp->seek(file_lba * 2048, SEEK_SET);
|
|
fp->read(fb, 2048);
|
|
|
|
bootpos = strstr((char*)fb, "BOOT") + 4;
|
|
while(*bootpos == ' ' || *bootpos == '\t') bootpos++;
|
|
if(*bootpos == '=')
|
|
{
|
|
bootpos++;
|
|
while(*bootpos == ' ' || *bootpos == '\t') bootpos++;
|
|
if(!strncasecmp(bootpos, "cdrom:\\", 7))
|
|
{
|
|
bootpos += 7;
|
|
char *tmp;
|
|
|
|
if((tmp = strchr(bootpos, '_'))) *tmp = 0;
|
|
if((tmp = strchr(bootpos, '.'))) *tmp = 0;
|
|
if((tmp = strchr(bootpos, ';'))) *tmp = 0;
|
|
//puts(bootpos);
|
|
|
|
if(strlen(bootpos) == 4 && bootpos[0] == 'S' && (bootpos[1] == 'C' || bootpos[1] == 'L' || bootpos[1] == 'I'))
|
|
{
|
|
switch(bootpos[2])
|
|
{
|
|
case 'E': if(rr)
|
|
*rr = REGION_EU;
|
|
ret = "SCEE";
|
|
goto Breakout;
|
|
|
|
case 'U': if(rr)
|
|
*rr = REGION_NA;
|
|
ret = "SCEA";
|
|
goto Breakout;
|
|
|
|
case 'K': // Korea?
|
|
case 'B':
|
|
case 'P': if(rr)
|
|
*rr = REGION_JP;
|
|
ret = "SCEI";
|
|
goto Breakout;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//puts((char*)fb);
|
|
//puts("ASOFKOASDFKO");
|
|
}
|
|
}
|
|
} // try
|
|
catch(std::exception &e)
|
|
{
|
|
//
|
|
}
|
|
catch(...)
|
|
{
|
|
}
|
|
|
|
|
|
Breakout:
|
|
if(fp)
|
|
{
|
|
delete fp;
|
|
fp = NULL;
|
|
}
|
|
|
|
return(ret);
|
|
}
|
|
|
|
static unsigned CalcDiscSCEx(void)
|
|
{
|
|
const char *prev_valid_id = NULL;
|
|
unsigned ret_region = MDFN_GetSettingI("psx.region_default");
|
|
|
|
cdifs_scex_ids.clear();
|
|
|
|
if(cdifs)
|
|
for(unsigned i = 0; i < cdifs->size(); i++)
|
|
{
|
|
uint8_t buf[2048];
|
|
uint8_t fbuf[2048 + 1];
|
|
const char *id = CalcDiscSCEx_BySYSTEMCNF((*cdifs)[i], (i == 0) ? &ret_region : NULL);
|
|
|
|
memset(fbuf, 0, sizeof(fbuf));
|
|
|
|
if(id == NULL && (*cdifs)[i]->ReadSector(buf, 4, 1) == 0x2)
|
|
{
|
|
unsigned ipos, opos;
|
|
for(ipos = 0, opos = 0; ipos < 0x48; ipos++)
|
|
{
|
|
if(buf[ipos] > 0x20 && buf[ipos] < 0x80)
|
|
{
|
|
fbuf[opos++] = tolower(buf[ipos]);
|
|
}
|
|
}
|
|
|
|
fbuf[opos++] = 0;
|
|
|
|
PSX_DBG(PSX_DBG_SPARSE, "License string: %s", (char *)fbuf);
|
|
|
|
if(strstr((char *)fbuf, "licensedby") != NULL)
|
|
{
|
|
if(strstr((char *)fbuf, "america") != NULL)
|
|
{
|
|
id = "SCEA";
|
|
if(!i)
|
|
ret_region = REGION_NA;
|
|
}
|
|
else if(strstr((char *)fbuf, "europe") != NULL)
|
|
{
|
|
id = "SCEE";
|
|
if(!i)
|
|
ret_region = REGION_EU;
|
|
}
|
|
else if(strstr((char *)fbuf, "japan") != NULL)
|
|
{
|
|
id = "SCEI"; // ?
|
|
if(!i)
|
|
ret_region = REGION_JP;
|
|
}
|
|
else if(strstr((char *)fbuf, "sonycomputerentertainmentinc.") != NULL)
|
|
{
|
|
id = "SCEI";
|
|
if(!i)
|
|
ret_region = REGION_JP;
|
|
}
|
|
else // Failure case
|
|
{
|
|
if(prev_valid_id != NULL)
|
|
id = prev_valid_id;
|
|
else
|
|
{
|
|
switch(ret_region) // Less than correct, but meh, what can we do.
|
|
{
|
|
case REGION_JP:
|
|
id = "SCEI";
|
|
break;
|
|
|
|
case REGION_NA:
|
|
id = "SCEA";
|
|
break;
|
|
|
|
case REGION_EU:
|
|
id = "SCEE";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(id != NULL)
|
|
prev_valid_id = id;
|
|
|
|
cdifs_scex_ids.push_back(id);
|
|
}
|
|
|
|
return ret_region;
|
|
}
|
|
|
|
static void SetDiscWrapper(const bool CD_TrayOpen) {
|
|
CDIF *cdif = NULL;
|
|
const char *disc_id = NULL;
|
|
if (CD_SelectedDisc >= 0 && !CD_TrayOpen) {
|
|
// only allow one pbp file to be loaded (at index 0)
|
|
if (CD_IsPBP) {
|
|
cdif = (*cdifs)[0];
|
|
disc_id = cdifs_scex_ids[0];
|
|
} else {
|
|
cdif = (*cdifs)[CD_SelectedDisc];
|
|
disc_id = cdifs_scex_ids[CD_SelectedDisc];
|
|
}
|
|
}
|
|
|
|
PSX_CDC->SetDisc(CD_TrayOpen, cdif, disc_id);
|
|
}
|
|
|
|
#ifdef HAVE_LIGHTREC
|
|
/* MAP_FIXED_NOREPLACE allows base 0 to work if "sysctl vm.mmap_min_addr = 0"
|
|
was used. Base 0 will perform better by directly mapping emulated addresses
|
|
to host addresses. If MAP_FIXED_NOREPLACE is not available we should not use
|
|
MAP_FIXED, since it can cause strange crashes by unmapping memory mappings. */
|
|
#ifndef MAP_FIXED_NOREPLACE
|
|
#ifdef USE_FIXED
|
|
#define MAP_FIXED_NOREPLACE MAP_FIXED
|
|
#else
|
|
#define MAP_FIXED_NOREPLACE 0
|
|
#endif
|
|
#endif
|
|
|
|
static const uintptr_t supported_io_bases[] = {
|
|
static_cast<uintptr_t>(0x00000000),
|
|
static_cast<uintptr_t>(0x10000000),
|
|
static_cast<uintptr_t>(0x20000000),
|
|
static_cast<uintptr_t>(0x30000000),
|
|
static_cast<uintptr_t>(0x40000000),
|
|
static_cast<uintptr_t>(0x50000000),
|
|
static_cast<uintptr_t>(0x60000000),
|
|
static_cast<uintptr_t>(0x70000000),
|
|
static_cast<uintptr_t>(0x80000000),
|
|
static_cast<uintptr_t>(0x90000000),
|
|
/* Some platforms need higher address base for mmap to work */
|
|
#if UINTPTR_MAX == UINT64_MAX
|
|
static_cast<uintptr_t>(0x100000000),
|
|
static_cast<uintptr_t>(0x200000000),
|
|
static_cast<uintptr_t>(0x300000000),
|
|
static_cast<uintptr_t>(0x400000000),
|
|
static_cast<uintptr_t>(0x500000000),
|
|
static_cast<uintptr_t>(0x600000000),
|
|
static_cast<uintptr_t>(0x700000000),
|
|
static_cast<uintptr_t>(0x800000000),
|
|
static_cast<uintptr_t>(0x900000000),
|
|
#endif
|
|
};
|
|
|
|
#define RAM_SIZE 0x200000
|
|
#define BIOS_SIZE 0x80000
|
|
#define SCRATCH_SIZE 0x400
|
|
|
|
#ifdef HAVE_WIN_SHM
|
|
#define MAP(addr, size, fd, offset) \
|
|
MapViewOfFileEx(fd, FILE_MAP_ALL_ACCESS, 0, offset, size, addr)
|
|
#define UNMAP(addr, size) UnmapViewOfFile(addr)
|
|
#define MFAILED NULL
|
|
#define NUM_MEM 4
|
|
#elif defined(HAVE_SHM) || defined(HAVE_ASHMEM)
|
|
#define MAP(addr, size, fd, offset) \
|
|
mmap(addr,size, PROT_READ | PROT_WRITE, \
|
|
MAP_SHARED | MAP_FIXED_NOREPLACE, fd, offset)
|
|
#define UNMAP(addr, size) munmap(addr, size)
|
|
#define MFAILED MAP_FAILED
|
|
#define NUM_MEM 4
|
|
#else
|
|
#define MAP(addr, size, fd, offset) \
|
|
mmap(addr,size, PROT_READ | PROT_WRITE, \
|
|
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)
|
|
#define UNMAP(addr, size) munmap(addr, size)
|
|
#define MFAILED MAP_FAILED
|
|
#define NUM_MEM 1
|
|
#endif
|
|
|
|
int lightrec_init_mmap()
|
|
{
|
|
int r = 0, i, j;
|
|
uintptr_t base;
|
|
void *bios, *scratch, *map;
|
|
|
|
/* open memfd and set size */
|
|
#ifdef HAVE_ASHMEM
|
|
memfd = open("/dev/ashmem", O_RDWR);
|
|
|
|
if (memfd < 0) {
|
|
log_cb(RETRO_LOG_ERROR, "Failed to create ASHMEM: %s\n", strerror(errno));
|
|
return 0;
|
|
}
|
|
|
|
ioctl(memfd, ASHMEM_SET_NAME, "lightrec_memfd");
|
|
ioctl(memfd, ASHMEM_SET_SIZE, RAM_SIZE+BIOS_SIZE+SCRATCH_SIZE);
|
|
#endif
|
|
#ifdef HAVE_SHM
|
|
int memfd;
|
|
const char *shm_name = "/lightrec_memfd";
|
|
|
|
memfd = shm_open(shm_name, O_RDWR | O_CREAT | O_EXCL,
|
|
S_IRUSR | S_IWUSR);
|
|
|
|
if (memfd < 0) {
|
|
log_cb(RETRO_LOG_ERROR, "Failed to create SHM: %s\n", strerror(errno));
|
|
return 0;
|
|
}
|
|
|
|
/* unlink ASAP to prevent leaving a file in shared memory if we crash */
|
|
shm_unlink(shm_name);
|
|
|
|
if (ftruncate(memfd, RAM_SIZE+BIOS_SIZE+SCRATCH_SIZE) < 0) {
|
|
log_cb(RETRO_LOG_ERROR, "Could not truncate SHM size: %s\n", strerror(errno));
|
|
goto close_return;
|
|
}
|
|
#endif
|
|
#ifdef HAVE_WIN_SHM
|
|
HANDLE memfd;
|
|
|
|
memfd = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,
|
|
RAM_SIZE+BIOS_SIZE+SCRATCH_SIZE, NULL);
|
|
|
|
if (memfd == NULL) {
|
|
log_cb(RETRO_LOG_ERROR, "Failed to create WIN_SHM: %s (%d)\n", strerror(errno), GetLastError());
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/* Try to map at various base addresses*/
|
|
for (i = 0; i < ARRAY_SIZE(supported_io_bases); i++) {
|
|
base = supported_io_bases[i];
|
|
bios = (void *)(base + 0x1fc00000);
|
|
scratch = (void *)(base + 0x1f800000);
|
|
|
|
for (j = 0; j < NUM_MEM; j++) {
|
|
map = MAP((void *)(base + j * RAM_SIZE), RAM_SIZE, memfd, 0);
|
|
if (map == MFAILED)
|
|
break;
|
|
else if (map != (void *)(base + j * RAM_SIZE))
|
|
{
|
|
//not at expected address, reject it
|
|
UNMAP(map, RAM_SIZE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Impossible to map using this base */
|
|
if (j == 0)
|
|
continue;
|
|
|
|
/* All mirrors mapped - we got a match! */
|
|
if (j == NUM_MEM)
|
|
{
|
|
psx_mem = (uint8 *)base;
|
|
|
|
map = MAP(bios, BIOS_SIZE, memfd, RAM_SIZE);
|
|
if (map == MFAILED)
|
|
goto err_unmap;
|
|
|
|
psx_bios = (uint8 *)map;
|
|
|
|
if (map != bios)
|
|
goto err_unmap_bios;
|
|
|
|
map = MAP(scratch, SCRATCH_SIZE, memfd, RAM_SIZE+BIOS_SIZE);
|
|
if (map == MFAILED)
|
|
goto err_unmap_bios;
|
|
|
|
psx_scratch = (uint8 *)map;
|
|
|
|
if (map != scratch)
|
|
goto err_unmap_scratch;
|
|
|
|
r = NUM_MEM;
|
|
|
|
goto close_return;
|
|
}
|
|
|
|
err_unmap_scratch:
|
|
if(psx_scratch){
|
|
UNMAP(psx_scratch, SCRATCH_SIZE);
|
|
psx_scratch = NULL;
|
|
}
|
|
err_unmap_bios:
|
|
if(psx_bios){
|
|
UNMAP(psx_bios, BIOS_SIZE);
|
|
psx_bios = NULL;
|
|
}
|
|
err_unmap:
|
|
/* Clean up any mapped ram or mirrors and try again */
|
|
for (; j > 0; j--)
|
|
UNMAP((void *)(base + (j - 1) * RAM_SIZE), RAM_SIZE);
|
|
|
|
psx_mem = NULL;
|
|
}
|
|
|
|
if (i == ARRAY_SIZE(supported_io_bases)) {
|
|
log_cb(RETRO_LOG_WARN, "Unable to mmap on any base address, dynarec will be slower\n");
|
|
}
|
|
|
|
close_return:
|
|
#ifdef HAVE_SHM
|
|
close(memfd);
|
|
#endif
|
|
#ifdef HAVE_WIN_SHM
|
|
CloseHandle(memfd);
|
|
#endif
|
|
return r;
|
|
}
|
|
|
|
void lightrec_free_mmap()
|
|
{
|
|
for (int i = 0; i < NUM_MEM; i++)
|
|
UNMAP((void *)((uintptr_t)psx_mem + i * RAM_SIZE), RAM_SIZE);
|
|
|
|
UNMAP(psx_bios, BIOS_SIZE);
|
|
UNMAP(psx_scratch, SCRATCH_SIZE);
|
|
|
|
#ifdef HAVE_ASHMEM
|
|
/* ashmem shared memory is not pinned by mmap, it dies on close */
|
|
close(memfd);
|
|
#endif
|
|
}
|
|
#endif /* HAVE_LIGHTREC */
|
|
|
|
/* Forward declarations, required for disk control
|
|
* 'set initial disk' functionality */
|
|
static unsigned disk_get_num_images(void);
|
|
static void CDInsertEject(void);
|
|
static void CDEject(void);
|
|
|
|
static void InitCommon(std::vector<CDIF *> *_CDInterfaces, const bool EmulateMemcards = true, const bool WantPIOMem = false)
|
|
{
|
|
unsigned region, i;
|
|
bool emulate_memcard[8];
|
|
bool emulate_multitap[2];
|
|
int sls, sle;
|
|
|
|
#if PSX_DBGPRINT_ENABLE
|
|
psx_dbg_level = MDFN_GetSettingUI("psx.dbg_level");
|
|
#endif
|
|
|
|
for(i = 0; i < 8; i++)
|
|
{
|
|
char buf[64];
|
|
snprintf(buf, sizeof(buf), "psx.input.port%u.memcard", i + 1);
|
|
emulate_memcard[i] = EmulateMemcards && MDFN_GetSettingB(buf);
|
|
}
|
|
|
|
if (!enable_memcard1) {
|
|
emulate_memcard[1] = false;
|
|
}
|
|
|
|
emulate_multitap[0] = setting_psx_multitap_port_1;
|
|
emulate_multitap[1] = setting_psx_multitap_port_2;
|
|
|
|
cdifs = _CDInterfaces;
|
|
region = CalcDiscSCEx();
|
|
|
|
if(!MDFN_GetSettingB("psx.region_autodetect"))
|
|
region = MDFN_GetSettingI("psx.region_default");
|
|
|
|
sls = MDFN_GetSettingI((region == REGION_EU) ? "psx.slstartp" : "psx.slstart");
|
|
sle = MDFN_GetSettingI((region == REGION_EU) ? "psx.slendp" : "psx.slend");
|
|
|
|
if(sls > sle)
|
|
{
|
|
int tmp = sls;
|
|
sls = sle;
|
|
sle = tmp;
|
|
}
|
|
|
|
PSX_CPU = new PS_CPU();
|
|
PSX_SPU = new PS_SPU();
|
|
|
|
GPU_Init(region == REGION_EU, sls, sle, psx_gpu_upscale_shift);
|
|
|
|
PSX_CDC = new PS_CDC();
|
|
PSX_FIO = new FrontIO(emulate_memcard, emulate_multitap);
|
|
PSX_FIO->SetAMCT(MDFN_GetSettingB("psx.input.analog_mode_ct"));
|
|
for(unsigned i = 0; i < 8; i++)
|
|
{
|
|
char buf[64];
|
|
snprintf(buf, sizeof(buf), "psx.input.port%u.gun_chairs", i + 1);
|
|
PSX_FIO->SetCrosshairsColor(i, MDFN_GetSettingUI(buf));
|
|
}
|
|
|
|
input_set_fio( PSX_FIO );
|
|
|
|
DMA_Init();
|
|
|
|
GPU_FillVideoParams(&EmulatedPSX);
|
|
|
|
switch (psx_gpu_dither_mode)
|
|
{
|
|
case DITHER_NATIVE:
|
|
GPU_set_dither_upscale_shift(psx_gpu_upscale_shift);
|
|
break;
|
|
case DITHER_UPSCALED:
|
|
GPU_set_dither_upscale_shift(0);
|
|
break;
|
|
case DITHER_OFF:
|
|
break;
|
|
}
|
|
|
|
PGXP_SetModes(psx_pgxp_mode | psx_pgxp_vertex_caching | psx_pgxp_texture_correction | psx_pgxp_nclip);
|
|
|
|
CD_TrayOpen = true;
|
|
CD_SelectedDisc = -1;
|
|
|
|
if(cdifs)
|
|
{
|
|
CD_TrayOpen = false;
|
|
CD_SelectedDisc = 0;
|
|
|
|
/* Attempt to set initial disk index */
|
|
if ((disk_control_ext_info.initial_index > 0) &&
|
|
(disk_control_ext_info.initial_index < disk_get_num_images()))
|
|
if (disk_control_ext_info.initial_index <
|
|
disk_control_ext_info.image_paths.size())
|
|
if (string_is_equal(
|
|
disk_control_ext_info.image_paths[disk_control_ext_info.initial_index].c_str(),
|
|
disk_control_ext_info.initial_path.c_str()))
|
|
CD_SelectedDisc = (int)disk_control_ext_info.initial_index;
|
|
}
|
|
|
|
PSX_CDC->SetDisc(true, NULL, NULL);
|
|
|
|
/* Multi-disk PBP files cause additional complication
|
|
* here, since the first disk is always loaded by default */
|
|
if(CD_IsPBP && (CD_SelectedDisc > 0))
|
|
{
|
|
CDEject();
|
|
CDInsertEject();
|
|
}
|
|
else
|
|
SetDiscWrapper(CD_TrayOpen);
|
|
|
|
#ifdef HAVE_LIGHTREC
|
|
psx_mmap = lightrec_init_mmap();
|
|
|
|
if(psx_mmap > 0)
|
|
{
|
|
MainRAM = new(psx_mem) MultiAccessSizeMem<RAM_SIZE, uint32, false>();
|
|
ScratchRAM = new(psx_scratch) MultiAccessSizeMem<SCRATCH_SIZE, uint32, false>();
|
|
BIOSROM = new(psx_bios) MultiAccessSizeMem<BIOS_SIZE, uint32, false>();
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
MainRAM = new MultiAccessSizeMem<2048 * 1024, uint32, false>();
|
|
ScratchRAM = new MultiAccessSizeMem<1024, uint32, false>();
|
|
BIOSROM = new MultiAccessSizeMem<512 * 1024, uint32, false>();
|
|
}
|
|
|
|
PIOMem = NULL;
|
|
|
|
if(WantPIOMem)
|
|
PIOMem = new MultiAccessSizeMem<65536, uint32, false>();
|
|
|
|
for(uint32_t ma = 0x00000000; ma < 0x00800000; ma += 2048 * 1024)
|
|
{
|
|
PSX_CPU->SetFastMap(MainRAM->data32, 0x00000000 + ma, 2048 * 1024);
|
|
PSX_CPU->SetFastMap(MainRAM->data32, 0x80000000 + ma, 2048 * 1024);
|
|
PSX_CPU->SetFastMap(MainRAM->data32, 0xA0000000 + ma, 2048 * 1024);
|
|
}
|
|
|
|
PSX_CPU->SetFastMap(BIOSROM->data32, 0x1FC00000, 512 * 1024);
|
|
PSX_CPU->SetFastMap(BIOSROM->data32, 0x9FC00000, 512 * 1024);
|
|
PSX_CPU->SetFastMap(BIOSROM->data32, 0xBFC00000, 512 * 1024);
|
|
|
|
if(PIOMem)
|
|
{
|
|
PSX_CPU->SetFastMap(PIOMem->data32, 0x1F000000, 65536);
|
|
PSX_CPU->SetFastMap(PIOMem->data32, 0x9F000000, 65536);
|
|
PSX_CPU->SetFastMap(PIOMem->data32, 0xBF000000, 65536);
|
|
}
|
|
|
|
|
|
MDFNMP_Init(1024, ((uint64)1 << 29) / 1024);
|
|
MDFNMP_AddRAM(2048 * 1024, 0x00000000, MainRAM->data8);
|
|
#if 0
|
|
MDFNMP_AddRAM(1024, 0x1F800000, ScratchRAM.data8);
|
|
#endif
|
|
|
|
RFILE *BIOSFile;
|
|
|
|
if(firmware_is_present(region))
|
|
{
|
|
BIOSFile = filestream_open(bios_path,
|
|
RETRO_VFS_FILE_ACCESS_READ,
|
|
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
|
}
|
|
else
|
|
{
|
|
const char *biospath_sname;
|
|
|
|
if(region == REGION_JP)
|
|
biospath_sname = "psx.bios_jp";
|
|
else if(region == REGION_EU)
|
|
biospath_sname = "psx.bios_eu";
|
|
else if(region == REGION_NA)
|
|
biospath_sname = "psx.bios_na";
|
|
else
|
|
abort();
|
|
|
|
const char *biospath = MDFN_MakeFName(MDFNMKF_FIRMWARE,
|
|
0, MDFN_GetSettingS(biospath_sname));
|
|
|
|
BIOSFile = filestream_open(biospath,
|
|
RETRO_VFS_FILE_ACCESS_READ,
|
|
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
|
}
|
|
|
|
if (BIOSFile)
|
|
{
|
|
filestream_read(BIOSFile, BIOSROM->data8, 512 * 1024);
|
|
filestream_close(BIOSFile);
|
|
}
|
|
|
|
i = 0;
|
|
|
|
if (!use_mednafen_memcard0_method)
|
|
{
|
|
PSX_FIO->LoadMemcard(0);
|
|
i = 1;
|
|
}
|
|
|
|
for(; i < 8; i++)
|
|
{
|
|
char ext[64];
|
|
const char *memcard = NULL;
|
|
if (i == 0)
|
|
snprintf(ext, sizeof(ext), "%d.mcr", memcard_left_index);
|
|
else if (i == 1)
|
|
snprintf(ext, sizeof(ext), "%d.mcr", memcard_right_index);
|
|
else
|
|
snprintf(ext, sizeof(ext), "%d.mcr", i);
|
|
memcard = MDFN_MakeFName(MDFNMKF_SAV, 0, ext);
|
|
PSX_FIO->LoadMemcard(i, memcard);
|
|
}
|
|
|
|
for(i = 0; i < 8; i++)
|
|
{
|
|
Memcard_PrevDC[i] = PSX_FIO->GetMemcardDirtyCount(i);
|
|
Memcard_SaveDelay[i] = -1;
|
|
}
|
|
|
|
input_init_calibration();
|
|
|
|
#ifdef WANT_DEBUGGER
|
|
DBG_Init();
|
|
#endif
|
|
PSX_Power();
|
|
}
|
|
|
|
static bool LoadEXE(const uint8_t *data, const uint32_t size, bool ignore_pcsp = false)
|
|
{
|
|
uint32 PC = MDFN_de32lsb<false>(&data[0x10]);
|
|
uint32 SP = MDFN_de32lsb<false>(&data[0x30]);
|
|
uint32 TextStart = MDFN_de32lsb<false>(&data[0x18]);
|
|
uint32 TextSize = MDFN_de32lsb<false>(&data[0x1C]);
|
|
|
|
if(ignore_pcsp)
|
|
log_cb(RETRO_LOG_DEBUG, "TextStart=0x%08x\nTextSize=0x%08x\n", TextStart, TextSize);
|
|
else
|
|
log_cb(RETRO_LOG_DEBUG, "PC=0x%08x\nSP=0x%08x\nTextStart=0x%08x\nTextSize=0x%08x\n", PC, SP, TextStart, TextSize);
|
|
|
|
TextStart &= 0x1FFFFF;
|
|
|
|
if(TextSize > 2048 * 1024)
|
|
{
|
|
MDFN_Error(0, "Text section too large");
|
|
return false;
|
|
}
|
|
|
|
if(TextSize > (size - 0x800))
|
|
{
|
|
MDFN_Error(0, "Text section recorded size is larger than data available in file. Header=0x%08x, Available=0x%08x", TextSize, size - 0x800);
|
|
return false;
|
|
}
|
|
|
|
if(TextSize < (size - 0x800))
|
|
{
|
|
MDFN_Error(0, "Text section recorded size is smaller than data available in file. Header=0x%08x, Available=0x%08x", TextSize, size - 0x800);
|
|
return false;
|
|
}
|
|
|
|
if(!TextMem.size())
|
|
{
|
|
TextMem_Start = TextStart;
|
|
TextMem.resize(TextSize);
|
|
}
|
|
|
|
if(TextStart < TextMem_Start)
|
|
{
|
|
uint32 old_size = TextMem.size();
|
|
|
|
//printf("RESIZE: 0x%08x\n", TextMem_Start - TextStart);
|
|
|
|
TextMem.resize(old_size + TextMem_Start - TextStart);
|
|
memmove(&TextMem[TextMem_Start - TextStart], &TextMem[0], old_size);
|
|
|
|
TextMem_Start = TextStart;
|
|
}
|
|
|
|
if(TextMem.size() < (TextStart - TextMem_Start + TextSize))
|
|
TextMem.resize(TextStart - TextMem_Start + TextSize);
|
|
|
|
memcpy(&TextMem[TextStart - TextMem_Start], data + 0x800, TextSize);
|
|
|
|
// BIOS patch
|
|
BIOSROM->WriteU32(0x6990, (3 << 26) | ((0xBF001000 >> 2) & ((1 << 26) - 1)));
|
|
#if 0
|
|
BIOSROM->WriteU32(0x691C, (3 << 26) | ((0xBF001000 >> 2) & ((1 << 26) - 1)));
|
|
#endif
|
|
|
|
uint8 *po;
|
|
|
|
po = &PIOMem->data8[0x0800];
|
|
|
|
MDFN_en32lsb<false>(po, (0x0 << 26) | (31 << 21) | (0x8 << 0)); // JR
|
|
po += 4;
|
|
MDFN_en32lsb<false>(po, 0); // NOP(kinda)
|
|
po += 4;
|
|
|
|
po = &PIOMem->data8[0x1000];
|
|
|
|
// Load cacheable-region target PC into r2
|
|
MDFN_en32lsb<false>(po, (0xF << 26) | (0 << 21) | (1 << 16) | (0x9F001010 >> 16)); // LUI
|
|
po += 4;
|
|
MDFN_en32lsb<false>(po, (0xD << 26) | (1 << 21) | (2 << 16) | (0x9F001010 & 0xFFFF)); // ORI
|
|
po += 4;
|
|
|
|
// Jump to r2
|
|
MDFN_en32lsb<false>(po, (0x0 << 26) | (2 << 21) | (0x8 << 0)); // JR
|
|
po += 4;
|
|
MDFN_en32lsb<false>(po, 0); // NOP(kinda)
|
|
po += 4;
|
|
|
|
//
|
|
// 0x9F001010:
|
|
//
|
|
|
|
// Load source address into r8
|
|
uint32 sa = 0x9F000000 + 65536;
|
|
MDFN_en32lsb<false>(po, (0xF << 26) | (0 << 21) | (1 << 16) | (sa >> 16)); // LUI
|
|
po += 4;
|
|
MDFN_en32lsb<false>(po, (0xD << 26) | (1 << 21) | (8 << 16) | (sa & 0xFFFF)); // ORI
|
|
po += 4;
|
|
|
|
// Load dest address into r9
|
|
MDFN_en32lsb<false>(po, (0xF << 26) | (0 << 21) | (1 << 16) | (TextMem_Start >> 16)); // LUI
|
|
po += 4;
|
|
MDFN_en32lsb<false>(po, (0xD << 26) | (1 << 21) | (9 << 16) | (TextMem_Start & 0xFFFF)); // ORI
|
|
po += 4;
|
|
|
|
// Load size into r10
|
|
MDFN_en32lsb<false>(po, (0xF << 26) | (0 << 21) | (1 << 16) | (TextMem.size() >> 16)); // LUI
|
|
po += 4;
|
|
MDFN_en32lsb<false>(po, (0xD << 26) | (1 << 21) | (10 << 16) | (TextMem.size() & 0xFFFF)); // ORI
|
|
po += 4;
|
|
|
|
//
|
|
// Loop begin
|
|
//
|
|
|
|
MDFN_en32lsb<false>(po, (0x24 << 26) | (8 << 21) | (1 << 16)); // LBU to r1
|
|
po += 4;
|
|
|
|
MDFN_en32lsb<false>(po, (0x08 << 26) | (10 << 21) | (10 << 16) | 0xFFFF); // Decrement size
|
|
po += 4;
|
|
|
|
MDFN_en32lsb<false>(po, (0x28 << 26) | (9 << 21) | (1 << 16)); // SB from r1
|
|
po += 4;
|
|
|
|
MDFN_en32lsb<false>(po, (0x08 << 26) | (8 << 21) | (8 << 16) | 0x0001); // Increment source addr
|
|
po += 4;
|
|
|
|
MDFN_en32lsb<false>(po, (0x05 << 26) | (0 << 21) | (10 << 16) | (-5 & 0xFFFF));
|
|
po += 4;
|
|
MDFN_en32lsb<false>(po, (0x08 << 26) | (9 << 21) | (9 << 16) | 0x0001); // Increment dest addr
|
|
po += 4;
|
|
|
|
//
|
|
// Loop end
|
|
//
|
|
|
|
// Load SP into r29
|
|
if(ignore_pcsp)
|
|
{
|
|
po += 16;
|
|
}
|
|
else
|
|
{
|
|
MDFN_en32lsb<false>(po, (0xF << 26) | (0 << 21) | (1 << 16) | (SP >> 16)); // LUI
|
|
po += 4;
|
|
MDFN_en32lsb<false>(po, (0xD << 26) | (1 << 21) | (29 << 16) | (SP & 0xFFFF)); // ORI
|
|
po += 4;
|
|
|
|
// Load PC into r2
|
|
MDFN_en32lsb<false>(po, (0xF << 26) | (0 << 21) | (1 << 16) | ((PC >> 16) | 0x8000)); // LUI
|
|
po += 4;
|
|
MDFN_en32lsb<false>(po, (0xD << 26) | (1 << 21) | (2 << 16) | (PC & 0xFFFF)); // ORI
|
|
po += 4;
|
|
}
|
|
|
|
// Half-assed instruction cache flush. ;)
|
|
for(unsigned i = 0; i < 1024; i++)
|
|
{
|
|
MDFN_en32lsb<false>(po, 0);
|
|
po += 4;
|
|
}
|
|
|
|
|
|
|
|
// Jump to r2
|
|
MDFN_en32lsb<false>(po, (0x0 << 26) | (2 << 21) | (0x8 << 0)); // JR
|
|
po += 4;
|
|
MDFN_en32lsb<false>(po, 0); // NOP(kinda)
|
|
po += 4;
|
|
|
|
#ifdef HAVE_LIGHTREC
|
|
/* Reload Expansion1 copy */
|
|
PSX_LoadExpansion1();
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
static int Load(const char *name, RFILE *fp)
|
|
{
|
|
int64_t size = filestream_get_size(fp);
|
|
const bool IsPSF = false;
|
|
char image_label[4096];
|
|
|
|
image_label[0] = '\0';
|
|
|
|
if(!TestMagic(name, fp, size))
|
|
{
|
|
MDFN_Error(0, "File format is unknown to module psx..");
|
|
return -1;
|
|
}
|
|
|
|
InitCommon(NULL, !IsPSF, true);
|
|
|
|
TextMem.resize(0);
|
|
|
|
if(size >= 0x800)
|
|
{
|
|
int64_t len = size;
|
|
uint8_t *header = (uint8_t*)malloc(len * sizeof(uint8_t));
|
|
|
|
filestream_read_file(name, (void**)&header, &len);
|
|
|
|
if (!LoadEXE(header, len))
|
|
return -1;
|
|
|
|
free(header);
|
|
}
|
|
|
|
disk_control_ext_info.image_paths.push_back(name);
|
|
extract_basename(image_label, name, sizeof(image_label));
|
|
disk_control_ext_info.image_labels.push_back(image_label);
|
|
|
|
return(1);
|
|
}
|
|
|
|
static int LoadCD(std::vector<CDIF *> *_CDInterfaces)
|
|
{
|
|
InitCommon(_CDInterfaces);
|
|
|
|
if (psx_skipbios == 1)
|
|
BIOSROM->WriteU32(0x6990, 0);
|
|
|
|
EmulatedPSX.GameType = GMT_CDROM;
|
|
|
|
return(1);
|
|
}
|
|
|
|
static void Cleanup(void)
|
|
{
|
|
TextMem.resize(0);
|
|
|
|
if(PSX_CDC)
|
|
delete PSX_CDC;
|
|
PSX_CDC = NULL;
|
|
|
|
if(PSX_SPU)
|
|
delete PSX_SPU;
|
|
PSX_SPU = NULL;
|
|
|
|
GPU_Destroy();
|
|
|
|
if(PSX_CPU)
|
|
delete PSX_CPU;
|
|
PSX_CPU = NULL;
|
|
|
|
if(PSX_FIO)
|
|
delete PSX_FIO;
|
|
PSX_FIO = NULL;
|
|
input_set_fio(NULL);
|
|
|
|
DMA_Kill();
|
|
|
|
#ifdef HAVE_LIGHTREC
|
|
MainRAM = NULL;
|
|
ScratchRAM = NULL;
|
|
BIOSROM = NULL;
|
|
if(psx_mmap > 0)
|
|
lightrec_free_mmap();
|
|
#else
|
|
if(MainRAM)
|
|
delete MainRAM;
|
|
MainRAM = NULL;
|
|
|
|
if(ScratchRAM)
|
|
delete ScratchRAM;
|
|
ScratchRAM = NULL;
|
|
|
|
if(BIOSROM)
|
|
delete BIOSROM;
|
|
BIOSROM = NULL;
|
|
#endif
|
|
|
|
if(PIOMem)
|
|
delete PIOMem;
|
|
PIOMem = NULL;
|
|
|
|
cdifs = NULL;
|
|
}
|
|
|
|
static void CloseGame(void)
|
|
{
|
|
int i;
|
|
|
|
if (!failed_init)
|
|
{
|
|
for(i = 0; i < 8; i++)
|
|
{
|
|
if (i == 0 && !use_mednafen_memcard0_method)
|
|
{
|
|
PSX_FIO->SaveMemcard(i);
|
|
continue;
|
|
}
|
|
|
|
// If there's an error saving one memcard, don't skip trying to save the other, since it might succeed and
|
|
// we can reduce potential data loss!
|
|
try
|
|
{
|
|
char ext[64];
|
|
const char *memcard = NULL;
|
|
if (i == 0)
|
|
snprintf(ext, sizeof(ext), "%d.mcr", memcard_left_index);
|
|
else if (i == 1)
|
|
snprintf(ext, sizeof(ext), "%d.mcr", memcard_right_index);
|
|
else
|
|
snprintf(ext, sizeof(ext), "%d.mcr", i);
|
|
memcard = MDFN_MakeFName(MDFNMKF_SAV, 0, ext);
|
|
PSX_FIO->SaveMemcard(i, memcard);
|
|
}
|
|
catch(std::exception &e)
|
|
{
|
|
log_cb(RETRO_LOG_ERROR, "%s\n", e.what());
|
|
}
|
|
}
|
|
}
|
|
|
|
Cleanup();
|
|
}
|
|
|
|
static void CDInsertEject(void)
|
|
{
|
|
CD_TrayOpen = !CD_TrayOpen;
|
|
|
|
for(unsigned disc = 0; disc < cdifs->size(); disc++)
|
|
{
|
|
if(!(*cdifs)[disc]->Eject(CD_TrayOpen))
|
|
{
|
|
MDFND_DispMessage(3, RETRO_LOG_ERROR,
|
|
RETRO_MESSAGE_TARGET_ALL, RETRO_MESSAGE_TYPE_NOTIFICATION_ALT,
|
|
"Eject error.");
|
|
|
|
CD_TrayOpen = !CD_TrayOpen;
|
|
}
|
|
}
|
|
|
|
if(CD_TrayOpen)
|
|
MDFND_DispMessage(0, RETRO_LOG_INFO,
|
|
RETRO_MESSAGE_TARGET_OSD, RETRO_MESSAGE_TYPE_NOTIFICATION_ALT,
|
|
"Virtual CD Drive Tray Open");
|
|
else
|
|
MDFND_DispMessage(0, RETRO_LOG_INFO,
|
|
RETRO_MESSAGE_TARGET_OSD, RETRO_MESSAGE_TYPE_NOTIFICATION_ALT,
|
|
"Virtual CD Drive Tray Closed");
|
|
|
|
SetDiscWrapper(CD_TrayOpen);
|
|
}
|
|
|
|
static void CDEject(void)
|
|
{
|
|
if(!CD_TrayOpen)
|
|
CDInsertEject();
|
|
}
|
|
|
|
static void CDSelect(void)
|
|
{
|
|
if(cdifs && CD_TrayOpen)
|
|
{
|
|
int disc_count = (CD_IsPBP ? PBP_PhysicalDiscCount : (int)cdifs->size());
|
|
|
|
CD_SelectedDisc = (CD_SelectedDisc + 1) % (disc_count + 1);
|
|
|
|
if(CD_SelectedDisc == disc_count)
|
|
CD_SelectedDisc = -1;
|
|
|
|
if(CD_SelectedDisc == -1)
|
|
MDFND_DispMessage(0, RETRO_LOG_INFO,
|
|
RETRO_MESSAGE_TARGET_OSD, RETRO_MESSAGE_TYPE_NOTIFICATION_ALT,
|
|
"Disc absence selected.");
|
|
else
|
|
MDFN_DispMessage(0, RETRO_LOG_INFO,
|
|
RETRO_MESSAGE_TARGET_OSD, RETRO_MESSAGE_TYPE_NOTIFICATION_ALT,
|
|
"Disc %d of %d selected.", CD_SelectedDisc + 1, disc_count);
|
|
}
|
|
}
|
|
|
|
extern "C" int StateAction(StateMem *sm, int load, int data_only)
|
|
{
|
|
SFORMAT StateRegs[] =
|
|
{
|
|
SFVAR(CD_TrayOpen),
|
|
SFVAR(CD_SelectedDisc),
|
|
SFARRAYN(MainRAM->data8, 1024 * 2048, "MainRAM.data8"),
|
|
SFARRAY32(SysControl.Regs, 9),
|
|
SFVAR(PSX_PRNG.lcgo),
|
|
SFVAR(PSX_PRNG.x),
|
|
SFVAR(PSX_PRNG.y),
|
|
SFVAR(PSX_PRNG.z),
|
|
SFVAR(PSX_PRNG.c),
|
|
SFEND
|
|
};
|
|
|
|
|
|
int ret = MDFNSS_StateAction(sm, load, data_only, StateRegs, "MAIN");
|
|
|
|
// Call SetDisc() BEFORE we load CDC state, since SetDisc() has emulation side effects. We might want to clean this up in the future.
|
|
if(load)
|
|
{
|
|
if(CD_IsPBP)
|
|
{
|
|
if(!cdifs || CD_SelectedDisc >= PBP_PhysicalDiscCount)
|
|
CD_SelectedDisc = -1;
|
|
|
|
CDEject();
|
|
CDInsertEject();
|
|
} else {
|
|
if(!cdifs || CD_SelectedDisc >= (int)cdifs->size())
|
|
CD_SelectedDisc = -1;
|
|
|
|
SetDiscWrapper(CD_TrayOpen);
|
|
}
|
|
}
|
|
|
|
// TODO: Remember to increment dirty count in memory card state loading routine.
|
|
|
|
ret &= PSX_CPU->StateAction(sm, load, data_only);
|
|
ret &= DMA_StateAction(sm, load, data_only);
|
|
ret &= TIMER_StateAction(sm, load, data_only);
|
|
ret &= SIO_StateAction(sm, load, data_only);
|
|
|
|
ret &= PSX_CDC->StateAction(sm, load, data_only);
|
|
ret &= MDEC_StateAction(sm, load, data_only);
|
|
ret &= GPU_StateAction(sm, load, data_only);
|
|
ret &= PSX_SPU->StateAction(sm, load, data_only);
|
|
|
|
ret &= PSX_FIO->StateAction(sm, load, data_only);
|
|
|
|
ret &= IRQ_StateAction(sm, load, data_only); // Do it last.
|
|
|
|
if(load)
|
|
{
|
|
ForceEventUpdates(0); // FIXME to work with debugger step mode.
|
|
}
|
|
|
|
return(ret);
|
|
}
|
|
|
|
static void DoSimpleCommand(int cmd)
|
|
{
|
|
switch(cmd)
|
|
{
|
|
case MDFN_MSC_RESET:
|
|
PSX_Power();
|
|
break;
|
|
case MDFN_MSC_POWER:
|
|
PSX_Power();
|
|
break;
|
|
case MDFN_MSC_INSERT_DISK:
|
|
CDInsertEject();
|
|
break;
|
|
case MDFN_MSC_SELECT_DISK:
|
|
CDSelect();
|
|
break;
|
|
case MDFN_MSC_EJECT_DISK:
|
|
CDEject();
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void GSCondCode(MemoryPatch* patch, const char* cc, const unsigned len, const uint32 addr, const uint16 val)
|
|
{
|
|
char tmp[256];
|
|
|
|
if(patch->conditions.size() > 0)
|
|
patch->conditions.append(", ");
|
|
|
|
if(len == 2)
|
|
snprintf(tmp, 256, "%u L 0x%08x %s 0x%04x", len, addr, cc, val & 0xFFFFU);
|
|
else
|
|
snprintf(tmp, 256, "%u L 0x%08x %s 0x%02x", len, addr, cc, val & 0xFFU);
|
|
|
|
patch->conditions.append(tmp);
|
|
}
|
|
|
|
static bool DecodeGS(const std::string& cheat_string, MemoryPatch* patch)
|
|
{
|
|
uint64 code = 0;
|
|
unsigned nybble_count = 0;
|
|
|
|
for(unsigned i = 0; i < cheat_string.size(); i++)
|
|
{
|
|
if(cheat_string[i] == ' ' || cheat_string[i] == '-' || cheat_string[i] == ':' || cheat_string[i] == '+')
|
|
continue;
|
|
|
|
nybble_count++;
|
|
code <<= 4;
|
|
|
|
if(cheat_string[i] >= '0' && cheat_string[i] <= '9')
|
|
code |= cheat_string[i] - '0';
|
|
else if(cheat_string[i] >= 'a' && cheat_string[i] <= 'f')
|
|
code |= cheat_string[i] - 'a' + 0xA;
|
|
else if(cheat_string[i] >= 'A' && cheat_string[i] <= 'F')
|
|
code |= cheat_string[i] - 'A' + 0xA;
|
|
else
|
|
{
|
|
if(cheat_string[i] & 0x80)
|
|
log_cb(RETRO_LOG_ERROR, "[Mednafen]: Invalid character in GameShark code..\n");
|
|
else
|
|
log_cb(RETRO_LOG_ERROR, "[Mednafen]: Invalid character in GameShark code: %c.\n", cheat_string[i]);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(nybble_count != 12)
|
|
{
|
|
log_cb(RETRO_LOG_ERROR, "GameShark code is of an incorrect length.\n");
|
|
return false;
|
|
}
|
|
|
|
const uint8 code_type = code >> 40;
|
|
const uint64 cl = code & 0xFFFFFFFFFFULL;
|
|
|
|
patch->bigendian = false;
|
|
patch->compare = 0;
|
|
|
|
if(patch->type == 'T')
|
|
{
|
|
if(code_type != 0x80)
|
|
log_cb(RETRO_LOG_ERROR, "Unrecognized GameShark code type for second part to copy bytes code.\n");
|
|
|
|
patch->addr = cl >> 16;
|
|
return(false);
|
|
}
|
|
|
|
switch(code_type)
|
|
{
|
|
default:
|
|
log_cb(RETRO_LOG_ERROR, "GameShark code type 0x%02X is currently not supported.\n", code_type);
|
|
return(false);
|
|
|
|
// TODO:
|
|
case 0x10: // 16-bit increment
|
|
patch->length = 2;
|
|
patch->type = 'A';
|
|
patch->addr = cl >> 16;
|
|
patch->val = cl & 0xFFFF;
|
|
return(false);
|
|
|
|
case 0x11: // 16-bit decrement
|
|
patch->length = 2;
|
|
patch->type = 'A';
|
|
patch->addr = cl >> 16;
|
|
patch->val = (0 - cl) & 0xFFFF;
|
|
return(false);
|
|
|
|
case 0x20: // 8-bit increment
|
|
patch->length = 1;
|
|
patch->type = 'A';
|
|
patch->addr = cl >> 16;
|
|
patch->val = cl & 0xFF;
|
|
return(false);
|
|
|
|
case 0x21: // 8-bit decrement
|
|
patch->length = 1;
|
|
patch->type = 'A';
|
|
patch->addr = cl >> 16;
|
|
patch->val = (0 - cl) & 0xFF;
|
|
return(false);
|
|
//
|
|
//
|
|
//
|
|
|
|
case 0x30: // 8-bit constant
|
|
patch->length = 1;
|
|
patch->type = 'R';
|
|
patch->addr = cl >> 16;
|
|
patch->val = cl & 0xFF;
|
|
return(false);
|
|
|
|
case 0x80: // 16-bit constant
|
|
patch->length = 2;
|
|
patch->type = 'R';
|
|
patch->addr = cl >> 16;
|
|
patch->val = cl & 0xFFFF;
|
|
return(false);
|
|
|
|
case 0x50: // Repeat thingy
|
|
{
|
|
const uint8 wcount = (cl >> 24) & 0xFF;
|
|
const uint8 addr_inc = (cl >> 16) & 0xFF;
|
|
const uint8 val_inc = (cl >> 0) & 0xFF;
|
|
|
|
patch->mltpl_count = wcount;
|
|
patch->mltpl_addr_inc = addr_inc;
|
|
patch->mltpl_val_inc = val_inc;
|
|
}
|
|
return(true);
|
|
|
|
case 0xC2: // Copy
|
|
{
|
|
const uint16 ccount = cl & 0xFFFF;
|
|
|
|
patch->type = 'T';
|
|
patch->val = 0;
|
|
patch->length = 1;
|
|
|
|
patch->copy_src_addr = cl >> 16;
|
|
patch->copy_src_addr_inc = 1;
|
|
|
|
patch->mltpl_count = ccount;
|
|
patch->mltpl_addr_inc = 1;
|
|
patch->mltpl_val_inc = 0;
|
|
}
|
|
return(true);
|
|
|
|
case 0xD0: // 16-bit == condition
|
|
GSCondCode(patch, "==", 2, cl >> 16, cl);
|
|
return(true);
|
|
|
|
case 0xD1: // 16-bit != condition
|
|
GSCondCode(patch, "!=", 2, cl >> 16, cl);
|
|
return(true);
|
|
|
|
case 0xD2: // 16-bit < condition
|
|
GSCondCode(patch, "<", 2, cl >> 16, cl);
|
|
return(true);
|
|
|
|
case 0xD3: // 16-bit > condition
|
|
GSCondCode(patch, ">", 2, cl >> 16, cl);
|
|
return(true);
|
|
|
|
|
|
|
|
case 0xE0: // 8-bit == condition
|
|
GSCondCode(patch, "==", 1, cl >> 16, cl);
|
|
return(true);
|
|
|
|
case 0xE1: // 8-bit != condition
|
|
GSCondCode(patch, "!=", 1, cl >> 16, cl);
|
|
return(true);
|
|
|
|
case 0xE2: // 8-bit < condition
|
|
GSCondCode(patch, "<", 1, cl >> 16, cl);
|
|
return(true);
|
|
|
|
case 0xE3: // 8-bit > condition
|
|
GSCondCode(patch, ">", 1, cl >> 16, cl);
|
|
return(true);
|
|
|
|
}
|
|
}
|
|
|
|
static CheatFormatStruct CheatFormats[] =
|
|
{
|
|
{ "GameShark", "Sharks with lamprey eels for eyes.", DecodeGS },
|
|
};
|
|
|
|
static CheatFormatInfoStruct CheatFormatInfo =
|
|
{
|
|
1,
|
|
CheatFormats
|
|
};
|
|
|
|
// Note for the future: If we ever support PSX emulation with non-8-bit RGB color components, or add a new linear RGB colorspace to MDFN_PixelFormat, we'll need
|
|
// to buffer the intermediate 24-bit non-linear RGB calculation into an array and pass that into the GPULineHook stuff, otherwise netplay could break when
|
|
// an emulated GunCon is used. This IS assuming, of course, that we ever implement save state support so that netplay actually works at all...
|
|
MDFNGI EmulatedPSX =
|
|
{
|
|
MDFN_MASTERCLOCK_FIXED(33868800),
|
|
0,
|
|
|
|
true, // Multires possible?
|
|
|
|
//
|
|
// Note: Following video settings will be overwritten during game load.
|
|
//
|
|
0, // lcm_width
|
|
0, // lcm_height
|
|
NULL, // Dummy
|
|
|
|
320, // Nominal width
|
|
240, // Nominal height
|
|
|
|
0, // Framebuffer width
|
|
0, // Framebuffer height
|
|
//
|
|
//
|
|
//
|
|
|
|
2, // Number of output sound channels
|
|
|
|
};
|
|
|
|
/* end of Mednafen psx.cpp */
|
|
|
|
//forward decls
|
|
extern void Emulate(EmulateSpecStruct *espec);
|
|
|
|
static bool overscan;
|
|
static double last_sound_rate;
|
|
|
|
#ifdef NEED_DEINTERLACER
|
|
static bool PrevInterlaced;
|
|
static Deinterlacer deint;
|
|
#endif
|
|
|
|
static MDFN_Surface *surf = NULL;
|
|
|
|
static void alloc_surface(void)
|
|
{
|
|
MDFN_PixelFormat pix_fmt(MDFN_COLORSPACE_RGB, 16, 8, 0, 24);
|
|
uint32_t width = MEDNAFEN_CORE_GEOMETRY_MAX_W;
|
|
uint32_t height = content_is_pal ? MEDNAFEN_CORE_GEOMETRY_MAX_H : 480;
|
|
|
|
width <<= GPU_get_upscale_shift();
|
|
height <<= GPU_get_upscale_shift();
|
|
|
|
if (surf != NULL)
|
|
delete surf;
|
|
|
|
surf = new MDFN_Surface(NULL, width, height, width, pix_fmt);
|
|
}
|
|
|
|
static void check_system_specs(void)
|
|
{
|
|
// Hints that we need a fairly powerful system to run this.
|
|
unsigned level = 15;
|
|
environ_cb(RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL, &level);
|
|
}
|
|
|
|
static unsigned disk_get_num_images(void)
|
|
{
|
|
if(cdifs)
|
|
return CD_IsPBP ? PBP_PhysicalDiscCount : cdifs->size();
|
|
return 0;
|
|
}
|
|
|
|
static bool disk_set_eject_state(bool ejected)
|
|
{
|
|
log_cb(RETRO_LOG_INFO, "[Mednafen]: Ejected: %u.\n", ejected);
|
|
if (ejected == eject_state)
|
|
return false;
|
|
|
|
DoSimpleCommand(ejected ? MDFN_MSC_EJECT_DISK : MDFN_MSC_INSERT_DISK);
|
|
eject_state = ejected;
|
|
return true;
|
|
}
|
|
|
|
static bool disk_get_eject_state(void)
|
|
{
|
|
return eject_state;
|
|
}
|
|
|
|
static unsigned disk_get_image_index(void)
|
|
{
|
|
// PSX global. Hacky.
|
|
return CD_SelectedDisc;
|
|
}
|
|
|
|
static bool disk_set_image_index(unsigned index)
|
|
{
|
|
CD_SelectedDisc = index;
|
|
if (CD_SelectedDisc > disk_get_num_images())
|
|
CD_SelectedDisc = disk_get_num_images();
|
|
|
|
// Very hacky. CDSelect command will increment first.
|
|
CD_SelectedDisc--;
|
|
|
|
DoSimpleCommand(MDFN_MSC_SELECT_DISK);
|
|
return true;
|
|
}
|
|
|
|
// Mednafen PSX really doesn't support adding disk images on the fly ...
|
|
// Hack around this.
|
|
|
|
// Untested ...
|
|
static bool disk_replace_image_index(unsigned index, const struct retro_game_info *info)
|
|
{
|
|
if (index >= disk_get_num_images() || !eject_state || CD_IsPBP)
|
|
return false;
|
|
|
|
if (!info)
|
|
{
|
|
delete cdifs->at(index);
|
|
cdifs->erase(cdifs->begin() + index);
|
|
if (index < CD_SelectedDisc)
|
|
CD_SelectedDisc--;
|
|
|
|
disk_control_ext_info.image_paths.erase(
|
|
disk_control_ext_info.image_paths.begin() + index);
|
|
disk_control_ext_info.image_labels.erase(
|
|
disk_control_ext_info.image_labels.begin() + index);
|
|
|
|
// Poke into psx.cpp
|
|
CalcDiscSCEx();
|
|
return true;
|
|
}
|
|
|
|
bool success = true;
|
|
|
|
CDIF *iface = CDIF_Open(&success, info->path, false, false);
|
|
|
|
if (!success)
|
|
return false;
|
|
|
|
delete cdifs->at(index);
|
|
cdifs->at(index) = iface;
|
|
CalcDiscSCEx();
|
|
|
|
/* If we replace, we want the "swap disk manually effect". */
|
|
extract_basename(retro_cd_base_name, info->path, sizeof(retro_cd_base_name));
|
|
|
|
/* Update disk path/label vectors */
|
|
disk_control_ext_info.image_paths[index] = info->path;
|
|
disk_control_ext_info.image_labels[index] = retro_cd_base_name;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool disk_add_image_index(void)
|
|
{
|
|
if(CD_IsPBP)
|
|
return false;
|
|
|
|
cdifs->push_back(NULL);
|
|
disk_control_ext_info.image_paths.push_back("");
|
|
disk_control_ext_info.image_labels.push_back("");
|
|
return true;
|
|
}
|
|
|
|
static bool disk_set_initial_image(unsigned index, const char *path)
|
|
{
|
|
if (string_is_empty(path))
|
|
return false;
|
|
|
|
disk_control_ext_info.initial_index = index;
|
|
disk_control_ext_info.initial_path = path;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool disk_get_image_path(unsigned index, char *path, size_t len)
|
|
{
|
|
if (len < 1)
|
|
return false;
|
|
|
|
if ((index < disk_get_num_images()) &&
|
|
(index < disk_control_ext_info.image_paths.size()))
|
|
{
|
|
if (!string_is_empty(disk_control_ext_info.image_paths[index].c_str()))
|
|
{
|
|
strlcpy(path, disk_control_ext_info.image_paths[index].c_str(), len);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool disk_get_image_label(unsigned index, char *label, size_t len)
|
|
{
|
|
if (len < 1)
|
|
return false;
|
|
|
|
if ((index < disk_get_num_images()) &&
|
|
(index < disk_control_ext_info.image_labels.size()))
|
|
{
|
|
if (!string_is_empty(disk_control_ext_info.image_labels[index].c_str()))
|
|
{
|
|
strlcpy(label, disk_control_ext_info.image_labels[index].c_str(), len);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static struct retro_disk_control_callback disk_interface = {
|
|
disk_set_eject_state,
|
|
disk_get_eject_state,
|
|
disk_get_image_index,
|
|
disk_set_image_index,
|
|
disk_get_num_images,
|
|
disk_replace_image_index,
|
|
disk_add_image_index,
|
|
};
|
|
|
|
static struct retro_disk_control_ext_callback disk_interface_ext =
|
|
{
|
|
disk_set_eject_state,
|
|
disk_get_eject_state,
|
|
disk_get_image_index,
|
|
disk_set_image_index,
|
|
disk_get_num_images,
|
|
disk_replace_image_index,
|
|
disk_add_image_index,
|
|
disk_set_initial_image,
|
|
disk_get_image_path,
|
|
disk_get_image_label,
|
|
};
|
|
|
|
static void fallback_log(enum retro_log_level level, const char *fmt, ...)
|
|
{
|
|
}
|
|
|
|
|
|
void retro_init(void)
|
|
{
|
|
struct retro_log_callback log;
|
|
uint64_t serialization_quirks = RETRO_SERIALIZATION_QUIRK_CORE_VARIABLE_SIZE;
|
|
unsigned dci_version = 0;
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log))
|
|
log_cb = log.log;
|
|
else
|
|
log_cb = fallback_log;
|
|
|
|
libretro_msg_interface_version = 0;
|
|
environ_cb(RETRO_ENVIRONMENT_GET_MESSAGE_INTERFACE_VERSION, &libretro_msg_interface_version);
|
|
|
|
CDUtility_Init();
|
|
|
|
eject_state = false;
|
|
|
|
const char *dir = NULL;
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &dir) && dir)
|
|
{
|
|
snprintf(retro_base_directory, sizeof(retro_base_directory), "%s", dir);
|
|
}
|
|
else
|
|
{
|
|
/* TODO: Add proper fallback */
|
|
log_cb(RETRO_LOG_WARN, "System directory is not defined. Fallback on using same dir as ROM for system directory later ...\n");
|
|
failed_init = true;
|
|
}
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &dir) && dir)
|
|
{
|
|
// If save directory is defined use it, otherwise use system directory
|
|
if (dir)
|
|
snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", dir);
|
|
else
|
|
snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", retro_base_directory);
|
|
}
|
|
else
|
|
{
|
|
/* TODO: Add proper fallback */
|
|
log_cb(RETRO_LOG_WARN, "Save directory is not defined. Fallback on using SYSTEM directory ...\n");
|
|
snprintf(retro_save_directory, sizeof(retro_save_directory), "%s", retro_base_directory);
|
|
}
|
|
|
|
/* Initialise disk control interface */
|
|
disk_control_ext_info.initial_index = 0;
|
|
disk_control_ext_info.initial_path.clear();
|
|
disk_control_ext_info.image_paths.clear();
|
|
disk_control_ext_info.image_labels.clear();
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION, &dci_version) && (dci_version >= 1))
|
|
environ_cb(RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE, &disk_interface_ext);
|
|
else
|
|
environ_cb(RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE, &disk_interface);
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_PERF_INTERFACE, &perf_cb))
|
|
perf_get_cpu_features_cb = perf_cb.get_cpu_features;
|
|
else
|
|
perf_get_cpu_features_cb = NULL;
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS, &serialization_quirks) &&
|
|
(serialization_quirks & RETRO_SERIALIZATION_QUIRK_FRONT_VARIABLE_SIZE))
|
|
enable_variable_serialization_size = true;
|
|
|
|
setting_initial_scanline = 0;
|
|
setting_last_scanline = 239;
|
|
setting_initial_scanline_pal = 0;
|
|
setting_last_scanline_pal = 287;
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, NULL))
|
|
libretro_supports_bitmasks = true;
|
|
|
|
check_system_specs();
|
|
}
|
|
|
|
void retro_reset(void)
|
|
{
|
|
DoSimpleCommand(MDFN_MSC_RESET);
|
|
}
|
|
|
|
bool retro_load_game_special(unsigned, const struct retro_game_info *, size_t)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#ifdef EMSCRIPTEN
|
|
static bool old_cdimagecache = true;
|
|
#else
|
|
static bool old_cdimagecache = false;
|
|
#endif
|
|
|
|
static bool boot = true;
|
|
|
|
// shared memory cards support
|
|
static bool shared_memorycards = false;
|
|
|
|
static bool has_new_geometry = false;
|
|
static bool has_new_timing = false;
|
|
|
|
static void check_variables(bool startup)
|
|
{
|
|
struct retro_variable var = {0};
|
|
|
|
extern void PSXDitherApply(bool);
|
|
|
|
#ifndef EMSCRIPTEN
|
|
var.key = BEETLE_OPT(cd_access_method);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "sync") == 0)
|
|
{
|
|
old_cdimagecache = false;
|
|
cd_async = false;
|
|
}
|
|
else if (strcmp(var.value, "async") == 0)
|
|
{
|
|
old_cdimagecache = false;
|
|
cd_async = true;
|
|
}
|
|
else if (strcmp(var.value, "precache") == 0)
|
|
{
|
|
old_cdimagecache = true;
|
|
cd_async = false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_LIGHTREC
|
|
var.key = BEETLE_OPT(cpu_dynarec);
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "execute") == 0)
|
|
psx_dynarec = DYNAREC_EXECUTE;
|
|
else if (strcmp(var.value, "execute_one") == 0)
|
|
psx_dynarec = DYNAREC_EXECUTE_ONE;
|
|
else if (strcmp(var.value, "run_interpreter") == 0)
|
|
psx_dynarec = DYNAREC_RUN_INTERPRETER;
|
|
else
|
|
psx_dynarec = DYNAREC_DISABLED;
|
|
}
|
|
else
|
|
psx_dynarec = DYNAREC_DISABLED;
|
|
|
|
var.key = BEETLE_OPT(dynarec_invalidate);
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "full") == 0)
|
|
psx_dynarec_invalidate = false;
|
|
else if (strcmp(var.value, "dma") == 0)
|
|
psx_dynarec_invalidate = true;
|
|
}
|
|
else
|
|
psx_dynarec_invalidate = false;
|
|
|
|
var.key = BEETLE_OPT(dynarec_eventcycles);
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
EventCycles = atoi(var.value);
|
|
}
|
|
else
|
|
EventCycles = 128;
|
|
#endif
|
|
|
|
var.key = BEETLE_OPT(cpu_freq_scale);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
int scale_percent = atoi(var.value);
|
|
|
|
if (scale_percent == 100)
|
|
psx_overclock_factor = 0;
|
|
else
|
|
psx_overclock_factor = ((scale_percent << OVERCLOCK_SHIFT) + 50) / 100;
|
|
}
|
|
else
|
|
psx_overclock_factor = 0;
|
|
|
|
// Need to adjust the CPU<->GPU frequency ratio if the overclocking changes
|
|
GPU_RecalcClockRatio();
|
|
|
|
var.key = BEETLE_OPT(gte_overclock);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "enabled") == 0)
|
|
psx_gte_overclock = true;
|
|
else if (strcmp(var.value, "disabled") == 0)
|
|
psx_gte_overclock = false;
|
|
}
|
|
else
|
|
psx_gte_overclock = false;
|
|
|
|
var.key = BEETLE_OPT(gpu_overclock);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
unsigned val = atoi(var.value);
|
|
|
|
// Upscale must be a power of two
|
|
assert((val & (val - 1)) == 0);
|
|
|
|
// Crappy "ffs" implementation since the standard function is not
|
|
// widely supported by libc in the wild
|
|
uint8_t n;
|
|
for (n = 0; (val & 1) == 0; ++n)
|
|
{
|
|
val >>= 1;
|
|
}
|
|
psx_gpu_overclock_shift = n;
|
|
}
|
|
else
|
|
psx_gpu_overclock_shift = 0;
|
|
|
|
var.key = BEETLE_OPT(skip_bios);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "enabled") == 0)
|
|
psx_skipbios = 1;
|
|
else
|
|
psx_skipbios = 0;
|
|
}
|
|
|
|
var.key = BEETLE_OPT(widescreen_hack);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "enabled") == 0)
|
|
{
|
|
if (widescreen_hack == false)
|
|
has_new_geometry = true;
|
|
widescreen_hack = true;
|
|
}
|
|
else if (strcmp(var.value, "disabled") == 0)
|
|
{
|
|
if (widescreen_hack == true)
|
|
has_new_geometry = true;
|
|
widescreen_hack = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (widescreen_hack == true)
|
|
has_new_geometry = true;
|
|
widescreen_hack = false;
|
|
}
|
|
|
|
var.key = BEETLE_OPT(pal_video_timing_override);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
bool want_fast_pal = (strcmp(var.value, "enabled") == 0);
|
|
|
|
if (want_fast_pal != fast_pal) {
|
|
fast_pal = want_fast_pal;
|
|
has_new_timing = true;
|
|
}
|
|
}
|
|
|
|
var.key = BEETLE_OPT(analog_calibration);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "enabled") == 0)
|
|
input_enable_calibration(true);
|
|
else if (strcmp(var.value, "disabled") == 0)
|
|
input_enable_calibration(false);
|
|
}
|
|
else
|
|
input_enable_calibration(false);
|
|
|
|
var.key = BEETLE_OPT(core_timing_fps);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "force_progressive") == 0)
|
|
{
|
|
if (!startup && core_timing_fps_mode != FORCE_PROGRESSIVE_TIMING)
|
|
has_new_timing = true;
|
|
|
|
core_timing_fps_mode = FORCE_PROGRESSIVE_TIMING;
|
|
}
|
|
else if (strcmp(var.value, "force_interlaced") == 0)
|
|
{
|
|
if (!startup && core_timing_fps_mode != FORCE_INTERLACED_TIMING)
|
|
has_new_timing = true;
|
|
|
|
core_timing_fps_mode = FORCE_INTERLACED_TIMING;
|
|
}
|
|
else // auto toggle setting, timing changes are allowed
|
|
{
|
|
if (!startup && core_timing_fps_mode != AUTO_TOGGLE_TIMING)
|
|
has_new_timing = true;
|
|
|
|
core_timing_fps_mode = AUTO_TOGGLE_TIMING;
|
|
}
|
|
}
|
|
|
|
var.key = BEETLE_OPT(aspect_ratio);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (!strcmp(var.value, "corrected"))
|
|
{
|
|
if (!startup && aspect_ratio_setting != 0)
|
|
has_new_geometry = true;
|
|
aspect_ratio_setting = 0;
|
|
}
|
|
else if (!strcmp(var.value, "uncorrected"))
|
|
{
|
|
if (!startup && aspect_ratio_setting != 1)
|
|
has_new_geometry = true;
|
|
aspect_ratio_setting = 1;
|
|
}
|
|
else if (!strcmp(var.value, "4:3"))
|
|
{
|
|
if (!startup && aspect_ratio_setting != 2)
|
|
has_new_geometry = true;
|
|
aspect_ratio_setting = 2;
|
|
}
|
|
else if (!strcmp(var.value, "ntsc"))
|
|
{
|
|
if (!startup && aspect_ratio_setting != 3)
|
|
has_new_geometry = true;
|
|
aspect_ratio_setting = 3;
|
|
}
|
|
}
|
|
|
|
if (startup)
|
|
{
|
|
var.key = BEETLE_OPT(renderer);
|
|
bool hw_renderer = false;
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (!strcmp(var.value, "hardware") || !strcmp(var.value, "hardware_gl") || !strcmp(var.value, "hardware_vk"))
|
|
{
|
|
hw_renderer = true;
|
|
}
|
|
}
|
|
|
|
var.key = BEETLE_OPT(internal_resolution);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
uint8_t new_upscale_shift;
|
|
uint8_t val = atoi(var.value);
|
|
|
|
// Upscale must be a power of two
|
|
assert((val & (val - 1)) == 0);
|
|
|
|
// Crappy "ffs" implementation since the standard function is not
|
|
// widely supported by libc in the wild
|
|
for (new_upscale_shift = 0; (val & 1) == 0; ++new_upscale_shift)
|
|
val >>= 1;
|
|
psx_gpu_upscale_shift_hw = new_upscale_shift;
|
|
}
|
|
else
|
|
psx_gpu_upscale_shift_hw = 0;
|
|
|
|
if (hw_renderer)
|
|
psx_gpu_upscale_shift = 0;
|
|
else
|
|
psx_gpu_upscale_shift = psx_gpu_upscale_shift_hw;
|
|
}
|
|
else
|
|
{
|
|
rsx_intf_refresh_variables();
|
|
|
|
var.key = BEETLE_OPT(internal_resolution);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
uint8_t new_upscale_shift;
|
|
uint8_t val = atoi(var.value);
|
|
|
|
// Upscale must be a power of two
|
|
assert((val & (val - 1)) == 0);
|
|
|
|
// Crappy "ffs" implementation since the standard function is not
|
|
// widely supported by libc in the wild
|
|
for (new_upscale_shift = 0; (val & 1) == 0; ++new_upscale_shift)
|
|
val >>= 1;
|
|
psx_gpu_upscale_shift_hw = new_upscale_shift;
|
|
}
|
|
else
|
|
psx_gpu_upscale_shift_hw = 0;
|
|
|
|
switch (rsx_intf_is_type())
|
|
{
|
|
case RSX_SOFTWARE:
|
|
psx_gpu_upscale_shift = psx_gpu_upscale_shift_hw;
|
|
break;
|
|
case RSX_OPENGL:
|
|
case RSX_VULKAN:
|
|
psx_gpu_upscale_shift = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
var.key = BEETLE_OPT(dither_mode);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "1x(native)") == 0)
|
|
psx_gpu_dither_mode = DITHER_NATIVE;
|
|
else if (strcmp(var.value, "internal resolution") == 0)
|
|
psx_gpu_dither_mode = DITHER_UPSCALED;
|
|
else if (strcmp(var.value, "disabled") == 0)
|
|
psx_gpu_dither_mode = DITHER_OFF;
|
|
}
|
|
else
|
|
psx_gpu_dither_mode = DITHER_NATIVE;
|
|
|
|
// iCB: PGXP settings
|
|
var.key = BEETLE_OPT(pgxp_mode);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "disabled") == 0)
|
|
psx_pgxp_mode = PGXP_MODE_NONE;
|
|
else if (strcmp(var.value, "memory only") == 0)
|
|
psx_pgxp_mode = PGXP_MODE_MEMORY | PGXP_MODE_GTE;
|
|
else if (strcmp(var.value, "memory + CPU") == 0)
|
|
psx_pgxp_mode = PGXP_MODE_MEMORY | PGXP_MODE_GTE | PGXP_MODE_CPU;
|
|
}
|
|
else
|
|
psx_pgxp_mode = PGXP_MODE_NONE;
|
|
|
|
var.key = BEETLE_OPT(pgxp_2d_tol);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "disabled") == 0)
|
|
psx_pgxp_2d_tol = -1;
|
|
else
|
|
psx_pgxp_2d_tol = atoi(var.value);
|
|
}
|
|
else
|
|
psx_pgxp_2d_tol = -1;
|
|
|
|
var.key = BEETLE_OPT(pgxp_vertex);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "disabled") == 0)
|
|
psx_pgxp_vertex_caching = PGXP_MODE_NONE;
|
|
else if (strcmp(var.value, "enabled") == 0)
|
|
psx_pgxp_vertex_caching = PGXP_VERTEX_CACHE;
|
|
}
|
|
else
|
|
psx_pgxp_vertex_caching = PGXP_MODE_NONE;
|
|
|
|
var.key = BEETLE_OPT(pgxp_texture);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "disabled") == 0)
|
|
psx_pgxp_texture_correction = PGXP_MODE_NONE;
|
|
else if (strcmp(var.value, "enabled") == 0)
|
|
psx_pgxp_texture_correction = PGXP_TEXTURE_CORRECTION;
|
|
}
|
|
else
|
|
psx_pgxp_texture_correction = PGXP_MODE_NONE;
|
|
// \iCB
|
|
|
|
var.key = BEETLE_OPT(pgxp_nclip);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "disabled") == 0)
|
|
psx_pgxp_nclip = PGXP_MODE_NONE;
|
|
else if (strcmp(var.value, "enabled") == 0)
|
|
psx_pgxp_nclip = PGXP_NCLIP_IMPL;
|
|
}
|
|
else
|
|
psx_pgxp_nclip = PGXP_MODE_NONE;
|
|
|
|
var.key = BEETLE_OPT(line_render);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "disabled") == 0)
|
|
line_render_mode = 0;
|
|
else if (strcmp(var.value, "default") == 0)
|
|
line_render_mode = 1;
|
|
else if (strcmp(var.value, "aggressive") == 0)
|
|
line_render_mode = 2;
|
|
}
|
|
|
|
var.key = BEETLE_OPT(filter);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
int old_filter_mode = filter_mode;
|
|
if (strcmp(var.value, "nearest") == 0)
|
|
filter_mode = 0;
|
|
else if (strcmp(var.value, "xBR") == 0)
|
|
filter_mode = 1;
|
|
else if (strcmp(var.value, "SABR") == 0)
|
|
filter_mode = 2;
|
|
else if (strcmp(var.value, "bilinear") == 0)
|
|
filter_mode = 3;
|
|
else if (strcmp(var.value, "3-point") == 0)
|
|
filter_mode = 4;
|
|
else if (strcmp(var.value, "JINC2") == 0)
|
|
filter_mode = 5;
|
|
|
|
if(filter_mode != old_filter_mode)
|
|
{
|
|
opaque_check = true;
|
|
semitrans_check = true;
|
|
old_filter_mode = filter_mode;
|
|
}
|
|
}
|
|
|
|
var.key = BEETLE_OPT(analog_toggle);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if ((strcmp(var.value, "enabled") == 0)
|
|
&& setting_psx_analog_toggle != 1)
|
|
{
|
|
setting_psx_analog_toggle = 1;
|
|
setting_apply_analog_toggle = true;
|
|
}
|
|
else if ((strcmp(var.value, "disabled") == 0)
|
|
&& setting_psx_analog_toggle != 0)
|
|
{
|
|
setting_psx_analog_toggle = 0;
|
|
setting_apply_analog_toggle = true;
|
|
}
|
|
}
|
|
|
|
var.key = BEETLE_OPT(enable_multitap_port1);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "enabled") == 0)
|
|
setting_psx_multitap_port_1 = true;
|
|
else if (strcmp(var.value, "disabled") == 0)
|
|
setting_psx_multitap_port_1 = false;
|
|
}
|
|
|
|
var.key = BEETLE_OPT(enable_multitap_port2);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "enabled") == 0)
|
|
setting_psx_multitap_port_2 = true;
|
|
else if (strcmp(var.value, "disabled") == 0)
|
|
setting_psx_multitap_port_2 = false;
|
|
}
|
|
|
|
var.key = BEETLE_OPT(mouse_sensitivity);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
input_set_mouse_sensitivity(atoi(var.value));
|
|
|
|
var.key = BEETLE_OPT(gun_cursor);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "off") == 0)
|
|
input_set_gun_cursor(FrontIO::SETTING_GUN_CROSSHAIR_OFF);
|
|
else if (strcmp(var.value, "cross") == 0)
|
|
input_set_gun_cursor(FrontIO::SETTING_GUN_CROSSHAIR_CROSS);
|
|
else if (strcmp(var.value, "dot") == 0)
|
|
input_set_gun_cursor(FrontIO::SETTING_GUN_CROSSHAIR_DOT);
|
|
}
|
|
|
|
var.key = BEETLE_OPT(gun_input_mode);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "lightgun") == 0)
|
|
gun_input_mode = SETTING_GUN_INPUT_LIGHTGUN;
|
|
else if (strcmp(var.value, "touchscreen") == 0)
|
|
gun_input_mode = SETTING_GUN_INPUT_POINTER;
|
|
}
|
|
else
|
|
gun_input_mode = SETTING_GUN_INPUT_LIGHTGUN;
|
|
|
|
var.key = BEETLE_OPT(negcon_deadzone);
|
|
input_set_negcon_deadzone(0);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
input_set_negcon_deadzone((int)(atoi(var.value) * 0.01f * NEGCON_RANGE));
|
|
}
|
|
|
|
var.key = BEETLE_OPT(negcon_response);
|
|
input_set_negcon_linearity(1);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "quadratic") == 0)
|
|
input_set_negcon_linearity(2);
|
|
else if (strcmp(var.value, "cubic") == 0)
|
|
input_set_negcon_linearity(3);
|
|
}
|
|
|
|
// Initial scanline NTSC
|
|
var.key = BEETLE_OPT(initial_scanline);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
int new_scanline_value = atoi(var.value);
|
|
if (setting_initial_scanline != new_scanline_value)
|
|
{
|
|
has_new_geometry = true;
|
|
setting_initial_scanline = new_scanline_value;
|
|
}
|
|
}
|
|
|
|
// Last scanline NTSC
|
|
var.key = BEETLE_OPT(last_scanline);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
int new_scanline_value = atoi(var.value);
|
|
if (setting_last_scanline != new_scanline_value)
|
|
{
|
|
has_new_geometry = true;
|
|
setting_last_scanline = new_scanline_value;
|
|
}
|
|
}
|
|
|
|
// Initial scanline PAL
|
|
var.key = BEETLE_OPT(initial_scanline_pal);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
int new_scanline_value = atoi(var.value);
|
|
if (setting_initial_scanline_pal != new_scanline_value)
|
|
{
|
|
has_new_geometry = true;
|
|
setting_initial_scanline_pal = new_scanline_value;
|
|
}
|
|
}
|
|
|
|
// Last scanline PAL
|
|
var.key = BEETLE_OPT(last_scanline_pal);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
int new_scanline_value = atoi(var.value);
|
|
if (setting_last_scanline_pal != new_scanline_value)
|
|
{
|
|
has_new_geometry = true;
|
|
setting_last_scanline_pal = new_scanline_value;
|
|
}
|
|
}
|
|
|
|
if(setting_psx_multitap_port_1 && setting_psx_multitap_port_2)
|
|
input_set_player_count(8);
|
|
else if (setting_psx_multitap_port_1 || setting_psx_multitap_port_2)
|
|
input_set_player_count(5);
|
|
else
|
|
input_set_player_count(2);
|
|
|
|
/* Memcards (startup only) */
|
|
if (startup)
|
|
{
|
|
var.key = BEETLE_OPT(use_mednafen_memcard0_method);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (!strcmp(var.value, "libretro"))
|
|
use_mednafen_memcard0_method = false;
|
|
else if (!strcmp(var.value, "mednafen"))
|
|
use_mednafen_memcard0_method = true;
|
|
}
|
|
|
|
var.key = BEETLE_OPT(enable_memcard1);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (!strcmp(var.value, "enabled"))
|
|
enable_memcard1 = true;
|
|
else if (!strcmp(var.value, "disabled"))
|
|
enable_memcard1 = false;
|
|
}
|
|
|
|
var.key = BEETLE_OPT(shared_memory_cards);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (!strcmp(var.value, "enabled"))
|
|
{
|
|
if(use_mednafen_memcard0_method)
|
|
shared_memorycards = true;
|
|
else
|
|
MDFND_DispMessage(3, RETRO_LOG_WARN,
|
|
RETRO_MESSAGE_TARGET_ALL, RETRO_MESSAGE_TYPE_NOTIFICATION,
|
|
"Memory Card 0 Method not set to Mednafen; shared memory cards could not be enabled.");
|
|
}
|
|
else if (!strcmp(var.value, "disabled"))
|
|
{
|
|
shared_memorycards = false;
|
|
}
|
|
}
|
|
}
|
|
/* End Memcards */
|
|
|
|
var.key = BEETLE_OPT(frame_duping);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "enabled") == 0)
|
|
{
|
|
bool can_dupe = false;
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_CAN_DUPE, &can_dupe))
|
|
allow_frame_duping = can_dupe;
|
|
}
|
|
else if (strcmp(var.value, "disabled") == 0)
|
|
allow_frame_duping = false;
|
|
}
|
|
else
|
|
allow_frame_duping = false;
|
|
|
|
var.key = BEETLE_OPT(display_internal_fps);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "enabled") == 0)
|
|
display_internal_framerate = true;
|
|
else if (strcmp(var.value, "disabled") == 0)
|
|
display_internal_framerate = false;
|
|
}
|
|
else
|
|
display_internal_framerate = false;
|
|
|
|
var.key = BEETLE_OPT(crop_overscan);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "enabled") == 0)
|
|
{
|
|
if (crop_overscan == false)
|
|
has_new_geometry = true;
|
|
crop_overscan = true;
|
|
}
|
|
else if (strcmp(var.value, "disabled") == 0)
|
|
{
|
|
if (crop_overscan == true)
|
|
has_new_geometry = true;
|
|
crop_overscan = false;
|
|
}
|
|
}
|
|
|
|
var.key = BEETLE_OPT(image_offset);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "disabled") == 0)
|
|
image_offset = 0;
|
|
else
|
|
image_offset = atoi(var.value);
|
|
}
|
|
|
|
var.key = BEETLE_OPT(image_crop);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
if (strcmp(var.value, "disabled") == 0)
|
|
image_crop = 0;
|
|
else
|
|
image_crop = atoi(var.value);
|
|
}
|
|
|
|
var.key = BEETLE_OPT(cd_fastload);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
uint8_t val = var.value[0] - '0';
|
|
if (var.value[1] != 'x')
|
|
{
|
|
val = (var.value[0] - '0') * 10;
|
|
val += var.value[1] - '0';
|
|
}
|
|
// Value is a multiplier from the native 2x, so we divide by two
|
|
cd_2x_speedup = val / 2;
|
|
}
|
|
else
|
|
cd_2x_speedup = 1;
|
|
|
|
var.key = BEETLE_OPT(memcard_left_index);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
memcard_left_index_old = memcard_left_index;
|
|
memcard_left_index = atoi(var.value);
|
|
}
|
|
|
|
var.key = BEETLE_OPT(memcard_right_index);
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
|
{
|
|
memcard_right_index_old = memcard_right_index;
|
|
memcard_right_index = atoi(var.value);
|
|
}
|
|
}
|
|
|
|
#ifdef NEED_CD
|
|
static void ReadM3U(std::vector<std::string> &file_list, std::string path, unsigned depth = 0)
|
|
{
|
|
std::string dir_path;
|
|
char linebuf[2048];
|
|
FILE *fp = fopen(path.c_str(), "rb");
|
|
|
|
if (fp == NULL)
|
|
return;
|
|
|
|
MDFN_GetFilePathComponents(path, &dir_path);
|
|
|
|
while(fgets(linebuf, sizeof(linebuf), fp) != NULL)
|
|
{
|
|
std::string efp;
|
|
|
|
if(linebuf[0] == '#')
|
|
continue;
|
|
string_trim_whitespace_right(linebuf);
|
|
if(linebuf[0] == 0)
|
|
continue;
|
|
|
|
efp = MDFN_EvalFIP(dir_path, std::string(linebuf), false);
|
|
|
|
if(efp.size() >= 4 && efp.substr(efp.size() - 4) == ".m3u")
|
|
{
|
|
if(efp == path)
|
|
{
|
|
log_cb(RETRO_LOG_ERROR, "M3U at \"%s\" references self.\n", efp.c_str());
|
|
goto end;
|
|
}
|
|
|
|
if(depth == 99)
|
|
{
|
|
log_cb(RETRO_LOG_ERROR, "M3U load recursion too deep!\n");
|
|
goto end;
|
|
}
|
|
|
|
ReadM3U(file_list, efp, depth++);
|
|
}
|
|
else
|
|
file_list.push_back(efp);
|
|
}
|
|
|
|
end:
|
|
fclose(fp);
|
|
}
|
|
|
|
// TODO: LoadCommon()
|
|
|
|
static bool MDFNI_LoadCD(const char *devicename)
|
|
{
|
|
log_cb(RETRO_LOG_INFO, "Loading %s...\n", devicename);
|
|
|
|
try
|
|
{
|
|
if(devicename && strlen(devicename) > 4 && !strcasecmp(devicename + strlen(devicename) - 4, ".m3u"))
|
|
{
|
|
ReadM3U(disk_control_ext_info.image_paths, devicename);
|
|
|
|
for(unsigned i = 0; i < disk_control_ext_info.image_paths.size(); i++)
|
|
{
|
|
char image_label[4096];
|
|
bool success = true;
|
|
|
|
image_label[0] = '\0';
|
|
|
|
CDIF *image = CDIF_Open(
|
|
&success, disk_control_ext_info.image_paths[i].c_str(), false, old_cdimagecache);
|
|
CDInterfaces.push_back(image);
|
|
|
|
extract_basename(
|
|
image_label, disk_control_ext_info.image_paths[i].c_str(),
|
|
sizeof(image_label));
|
|
disk_control_ext_info.image_labels.push_back(image_label);
|
|
}
|
|
}
|
|
else if(devicename && strlen(devicename) > 4 && !strcasecmp(devicename + strlen(devicename) - 4, ".pbp"))
|
|
{
|
|
bool success = true;
|
|
CDIF *image = CDIF_Open(&success, devicename, false, old_cdimagecache);
|
|
CD_IsPBP = true;
|
|
CDInterfaces.push_back(image);
|
|
|
|
/* CDIF_Open() sets PBP_DiscCount, so we can populate
|
|
* image_paths/image_labels here */
|
|
PBP_PhysicalDiscCount = (PBP_DiscCount == 0) ?
|
|
1 : PBP_DiscCount;
|
|
|
|
for(unsigned i = 0; i < PBP_PhysicalDiscCount; i++)
|
|
{
|
|
char image_name[4096];
|
|
char image_label[4096];
|
|
|
|
image_name[0] = '\0';
|
|
image_label[0] = '\0';
|
|
|
|
/* All 'disks' have the same path when using
|
|
* multi-disk PBP files */
|
|
disk_control_ext_info.image_paths.push_back(devicename);
|
|
|
|
/* Label is name+index */
|
|
extract_basename(image_name, devicename, sizeof(image_name));
|
|
snprintf(image_label, sizeof(image_label), "%s #%u", image_name, i + 1);
|
|
disk_control_ext_info.image_labels.push_back(image_label);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
char image_label[4096];
|
|
bool success = true;
|
|
|
|
image_label[0] = '\0';
|
|
|
|
CDIF *image = CDIF_Open(&success, devicename, false, old_cdimagecache);
|
|
if (!success)
|
|
return false;
|
|
|
|
CDInterfaces.push_back(image);
|
|
|
|
disk_control_ext_info.image_paths.push_back(devicename);
|
|
extract_basename(image_label, devicename, sizeof(image_label));
|
|
disk_control_ext_info.image_labels.push_back(image_label);
|
|
}
|
|
}
|
|
catch(std::exception &e)
|
|
{
|
|
log_cb(RETRO_LOG_ERROR, "Error opening CD.\n");
|
|
return false;
|
|
}
|
|
|
|
// Print out a track list for all discs.
|
|
for(unsigned i = 0; i < CDInterfaces.size(); i++)
|
|
{
|
|
TOC toc;
|
|
TOC_Clear(&toc);
|
|
|
|
CDInterfaces[i]->ReadTOC(&toc);
|
|
|
|
log_cb(RETRO_LOG_DEBUG, "CD %d Layout:\n", i + 1);
|
|
|
|
for(int32 track = toc.first_track; track <= toc.last_track; track++)
|
|
{
|
|
log_cb(RETRO_LOG_DEBUG, "Track %2d, LBA: %6d %s\n", track, toc.tracks[track].lba, (toc.tracks[track].control & 0x4) ? "DATA" : "AUDIO");
|
|
}
|
|
|
|
log_cb(RETRO_LOG_DEBUG, "Leadout: %6d\n", toc.tracks[100].lba);
|
|
}
|
|
|
|
if(!(LoadCD(&CDInterfaces)))
|
|
{
|
|
for(unsigned i = 0; i < CDInterfaces.size(); i++)
|
|
delete CDInterfaces[i];
|
|
CDInterfaces.clear();
|
|
|
|
disk_control_ext_info.initial_index = 0;
|
|
disk_control_ext_info.initial_path.clear();
|
|
disk_control_ext_info.image_paths.clear();
|
|
disk_control_ext_info.image_labels.clear();
|
|
|
|
return false;
|
|
}
|
|
|
|
//MDFNI_SetLayerEnableMask(~0ULL);
|
|
|
|
MDFN_LoadGameCheats(NULL);
|
|
MDFNMP_InstallReadPatches();
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
static bool MDFNI_LoadGame(const char *name)
|
|
{
|
|
RFILE *GameFile = NULL;
|
|
|
|
if(strlen(name) > 4 && (
|
|
!strcasecmp(name + strlen(name) - 4, ".cue") ||
|
|
!strcasecmp(name + strlen(name) - 4, ".ccd") ||
|
|
!strcasecmp(name + strlen(name) - 4, ".toc") ||
|
|
!strcasecmp(name + strlen(name) - 4, ".m3u") ||
|
|
!strcasecmp(name + strlen(name) - 4, ".chd") ||
|
|
!strcasecmp(name + strlen(name) - 4, ".pbp")
|
|
))
|
|
return MDFNI_LoadCD(name);
|
|
|
|
GameFile = filestream_open(name,
|
|
RETRO_VFS_FILE_ACCESS_READ,
|
|
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
|
|
|
if(!GameFile)
|
|
goto error;
|
|
|
|
if(Load(name, GameFile) <= 0)
|
|
goto error;
|
|
|
|
filestream_close(GameFile);
|
|
GameFile = NULL;
|
|
|
|
return true;
|
|
|
|
error:
|
|
if (GameFile)
|
|
filestream_close(GameFile);
|
|
GameFile = NULL;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool retro_load_game(const struct retro_game_info *info)
|
|
{
|
|
char tocbasepath[4096];
|
|
|
|
if (failed_init)
|
|
return false;
|
|
|
|
input_init_env(environ_cb);
|
|
|
|
enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888;
|
|
if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt))
|
|
return false;
|
|
|
|
extract_basename(retro_cd_base_name, info->path, sizeof(retro_cd_base_name));
|
|
extract_directory(retro_cd_base_directory, info->path, sizeof(retro_cd_base_directory));
|
|
|
|
int r = snprintf(tocbasepath, sizeof(tocbasepath), "%s%c%s.toc", retro_cd_base_directory, retro_slash, retro_cd_base_name);
|
|
|
|
if (r >= 0 && r < 4096 && filestream_exists(tocbasepath))
|
|
snprintf(retro_cd_path, sizeof(retro_cd_path), "%s", tocbasepath);
|
|
else
|
|
snprintf(retro_cd_path, sizeof(retro_cd_path), "%s", info->path);
|
|
|
|
check_variables(true);
|
|
|
|
if (!MDFNI_LoadGame(retro_cd_path))
|
|
{
|
|
failed_init = true;
|
|
return false;
|
|
}
|
|
|
|
MDFN_LoadGameCheats(NULL);
|
|
MDFNMP_InstallReadPatches();
|
|
|
|
// Determine content_is_pal before calling alloc_surface()
|
|
unsigned disc_region = CalcDiscSCEx();
|
|
content_is_pal = (disc_region == REGION_EU);
|
|
|
|
alloc_surface();
|
|
|
|
#ifdef NEED_DEINTERLACER
|
|
PrevInterlaced = false;
|
|
deint.ClearState();
|
|
#endif
|
|
|
|
input_init();
|
|
|
|
boot = false;
|
|
|
|
frame_count = 0;
|
|
internal_frame_count = 0;
|
|
|
|
// MDFNI_LoadGame() has been called and surface has been allocated,
|
|
// we can now perform firmware check
|
|
bool force_software_renderer = false;
|
|
if (!firmware_found)
|
|
{
|
|
log_cb(RETRO_LOG_ERROR, "Content cannot be loaded\n");
|
|
|
|
/* TODO - We're forcing the sw renderer to show the ugui error message. Figure out
|
|
how to copy the ugui framebuffer to the hardware renderer side with rsx_intf calls,
|
|
so we don't have to force this anymore. */
|
|
force_software_renderer = true;
|
|
|
|
#ifdef HAVE_LIGHTREC
|
|
/* Do not run lightrec if firmware is not found, recompiling garbage is bad*/
|
|
psx_dynarec = DYNAREC_DISABLED;
|
|
#endif
|
|
}
|
|
|
|
bool ret = rsx_intf_open(content_is_pal, force_software_renderer);
|
|
|
|
/* Hide irrelevant core options */
|
|
switch (rsx_intf_is_type())
|
|
{
|
|
case RSX_SOFTWARE:
|
|
{
|
|
struct retro_core_option_display option_display;
|
|
option_display.visible = false;
|
|
|
|
option_display.key = BEETLE_OPT(renderer_software_fb);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(scaled_uv_offset);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(filter_exclude_sprite);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(filter_exclude_2d_polygon);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(adaptive_smoothing);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(super_sampling);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(msaa);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(mdec_yuv);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(depth);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(wireframe);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(display_vram);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(filter);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(pgxp_vertex);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(pgxp_texture);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
|
|
option_display.key = BEETLE_OPT(image_offset_cycles);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
|
|
break;
|
|
}
|
|
case RSX_OPENGL:
|
|
{
|
|
struct retro_core_option_display option_display;
|
|
option_display.visible = false;
|
|
|
|
option_display.key = BEETLE_OPT(scaled_uv_offset);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(filter_exclude_sprite);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(filter_exclude_2d_polygon);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(adaptive_smoothing);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(super_sampling);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(msaa);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(mdec_yuv);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
|
|
option_display.key = BEETLE_OPT(image_offset);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(image_crop);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
|
|
option_display.key = BEETLE_OPT(frame_duping);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
|
|
break;
|
|
}
|
|
case RSX_VULKAN:
|
|
{
|
|
struct retro_core_option_display option_display;
|
|
option_display.visible = false;
|
|
|
|
option_display.key = BEETLE_OPT(depth);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(wireframe);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
|
|
option_display.key = BEETLE_OPT(image_offset);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(image_crop);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Hide irrelevant scanline core options for current content */
|
|
struct retro_core_option_display option_display;
|
|
option_display.visible = false;
|
|
if (content_is_pal)
|
|
{
|
|
option_display.key = BEETLE_OPT(initial_scanline);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(last_scanline);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
}
|
|
else
|
|
{
|
|
option_display.key = BEETLE_OPT(initial_scanline_pal);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
option_display.key = BEETLE_OPT(last_scanline_pal);
|
|
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void retro_unload_game(void)
|
|
{
|
|
rsx_intf_close();
|
|
|
|
MDFN_FlushGameCheats(0);
|
|
|
|
CloseGame();
|
|
|
|
MDFNMP_Kill();
|
|
|
|
for(unsigned i = 0; i < CDInterfaces.size(); i++)
|
|
delete CDInterfaces[i];
|
|
CDInterfaces.clear();
|
|
|
|
disk_control_ext_info.initial_index = 0;
|
|
disk_control_ext_info.initial_path.clear();
|
|
disk_control_ext_info.image_paths.clear();
|
|
disk_control_ext_info.image_labels.clear();
|
|
|
|
retro_cd_base_directory[0] = '\0';
|
|
retro_cd_path[0] = '\0';
|
|
retro_cd_base_name[0] = '\0';
|
|
}
|
|
|
|
static uint64_t video_frames, audio_frames;
|
|
#define SOUND_CHANNELS 2
|
|
|
|
void retro_run(void)
|
|
{
|
|
bool updated = false;
|
|
//code to implement audio and video disable is not yet implemented
|
|
//bool disableVideo = false;
|
|
//bool disableAudio = false;
|
|
//bool hardDisableAudio = false;
|
|
//int flags = 3;
|
|
//if (environ_cb(RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE, &flags))
|
|
//{
|
|
// disableVideo = !(flags & 1);
|
|
// disableAudio = !(flags & 2);
|
|
// hardDisableAudio = !!(flags & 8);
|
|
//}
|
|
|
|
if (gui_show && gui_inited && frame_width > 0 && frame_height > 0)
|
|
{
|
|
gui_draw();
|
|
video_cb(gui_get_framebuffer(), frame_width, frame_height, frame_width * sizeof(unsigned));
|
|
}
|
|
|
|
rsx_intf_prepare_frame();
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated)
|
|
{
|
|
check_variables(false);
|
|
struct retro_system_av_info new_av_info;
|
|
|
|
/* Max width/height changed, need to call SET_SYSTEM_AV_INFO */
|
|
if (GPU_get_upscale_shift() != psx_gpu_upscale_shift)
|
|
{
|
|
retro_get_system_av_info(&new_av_info);
|
|
if (environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &new_av_info))
|
|
{
|
|
// We successfully changed the frontend's resolution, we can
|
|
// apply the change immediately
|
|
GPU_Rescale(psx_gpu_upscale_shift);
|
|
alloc_surface();
|
|
has_new_geometry = false;
|
|
}
|
|
else
|
|
{
|
|
// Failed, we have to postpone the upscaling change
|
|
psx_gpu_upscale_shift = GPU_get_upscale_shift();
|
|
}
|
|
}
|
|
|
|
/* Core timing option changed, need to call SET_SYSTEM_AV_INFO
|
|
*
|
|
* Note: May be possible to bundle this dirty flag with the other
|
|
* dirty flags such as the one for widescreen hack and do a full
|
|
* RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO callback; should be acceptable
|
|
* to have video/audio reinits after changing core options
|
|
*/
|
|
if (has_new_timing)
|
|
{
|
|
retro_get_system_av_info(&new_av_info);
|
|
if (environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &new_av_info))
|
|
has_new_timing = false;
|
|
}
|
|
|
|
/* Widescreen hack, scanlines, overscan cropping, or aspect ratio setting
|
|
changed, need to call SET_GEOMETRY to change aspect ratio */
|
|
if (has_new_geometry)
|
|
{
|
|
retro_get_system_av_info(&new_av_info);
|
|
if (environ_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, &new_av_info))
|
|
{
|
|
has_new_geometry = false;
|
|
}
|
|
}
|
|
|
|
switch (psx_gpu_dither_mode)
|
|
{
|
|
case DITHER_NATIVE:
|
|
GPU_set_dither_upscale_shift(psx_gpu_upscale_shift);
|
|
break;
|
|
case DITHER_UPSCALED:
|
|
GPU_set_dither_upscale_shift(0);
|
|
break;
|
|
case DITHER_OFF:
|
|
break;
|
|
}
|
|
|
|
GPU_set_visible_scanlines(MDFN_GetSettingI(content_is_pal ? "psx.slstartp" : "psx.slstart"),
|
|
MDFN_GetSettingI(content_is_pal ? "psx.slendp" : "psx.slend"));
|
|
|
|
PGXP_SetModes(psx_pgxp_mode | psx_pgxp_vertex_caching | psx_pgxp_texture_correction | psx_pgxp_nclip);
|
|
|
|
// Reload memory cards if they were changed
|
|
if (use_mednafen_memcard0_method &&
|
|
memcard_left_index_old != memcard_left_index)
|
|
{
|
|
MDFN_DispMessage(0, RETRO_LOG_INFO,
|
|
RETRO_MESSAGE_TARGET_OSD, RETRO_MESSAGE_TYPE_NOTIFICATION_ALT,
|
|
"changing from memory card %d to memory card %d in left slot",
|
|
memcard_left_index_old, memcard_left_index);
|
|
|
|
try
|
|
{
|
|
char ext[64];
|
|
const char *memcard = NULL;
|
|
|
|
// Save contents of left memory card to previously selected index
|
|
snprintf(ext, sizeof(ext), "%d.mcr", memcard_left_index_old);
|
|
memcard = MDFN_MakeFName(MDFNMKF_SAV, 0, ext);
|
|
PSX_FIO->SaveMemcard(0, memcard, true);
|
|
|
|
// Load contents of currently selected index to left memory card
|
|
snprintf(ext, sizeof(ext), "%d.mcr", memcard_left_index);
|
|
memcard = MDFN_MakeFName(MDFNMKF_SAV, 0, ext);
|
|
PSX_FIO->LoadMemcard(0, memcard, true);
|
|
}
|
|
catch (std::exception &e)
|
|
{
|
|
log_cb(RETRO_LOG_ERROR, "%s\n", e.what());
|
|
}
|
|
}
|
|
|
|
if (memcard_right_index_old != memcard_right_index)
|
|
{
|
|
MDFN_DispMessage(0, RETRO_LOG_INFO,
|
|
RETRO_MESSAGE_TARGET_OSD, RETRO_MESSAGE_TYPE_NOTIFICATION_ALT,
|
|
"changing from memory card %d to memory card %d in right slot",
|
|
memcard_right_index_old, memcard_right_index);
|
|
|
|
try
|
|
{
|
|
char ext[64];
|
|
const char *memcard = NULL;
|
|
|
|
// Save contents of right memory card to previously selected index
|
|
snprintf(ext, sizeof(ext), "%d.mcr", memcard_right_index_old);
|
|
memcard = MDFN_MakeFName(MDFNMKF_SAV, 0, ext);
|
|
PSX_FIO->SaveMemcard(1, memcard, true);
|
|
|
|
// Load contents of currently selected index to right memory card
|
|
snprintf(ext, sizeof(ext), "%d.mcr", memcard_right_index);
|
|
memcard = MDFN_MakeFName(MDFNMKF_SAV, 0, ext);
|
|
PSX_FIO->LoadMemcard(1, memcard, true);
|
|
}
|
|
catch (std::exception &e)
|
|
{
|
|
log_cb(RETRO_LOG_ERROR, "%s\n", e.what());
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We only start counting after the first frame we encounter. This
|
|
way the value we display remains consistent if the real
|
|
framerate is not a multiple of INTERNAL_FPS_SAMPLE_PERIOD
|
|
*/
|
|
if (display_internal_framerate && internal_frame_count)
|
|
{
|
|
frame_count++;
|
|
|
|
if (frame_count % INTERNAL_FPS_SAMPLE_PERIOD == 0)
|
|
{
|
|
char msg_buffer[64];
|
|
|
|
msg_buffer[0] = '\0';
|
|
|
|
// Just report the "real-world" refresh rate here regardless of system av info reported to the frontend
|
|
float fps = (content_is_pal && !fast_pal) ?
|
|
(currently_interlaced ? FPS_PAL_INTERLACED : FPS_PAL_NONINTERLACED) :
|
|
(currently_interlaced ? FPS_NTSC_INTERLACED : FPS_NTSC_NONINTERLACED);
|
|
float internal_fps = (internal_frame_count * fps) / INTERNAL_FPS_SAMPLE_PERIOD;
|
|
|
|
snprintf(msg_buffer, sizeof(msg_buffer),
|
|
"Internal FPS: %.2f", internal_fps);
|
|
|
|
MDFND_DispMessage(1, RETRO_LOG_INFO,
|
|
RETRO_MESSAGE_TARGET_OSD, RETRO_MESSAGE_TYPE_STATUS,
|
|
msg_buffer);
|
|
|
|
internal_frame_count = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Keep the counters at 0 so that they don't display a bogus
|
|
// value if this option is enabled later on
|
|
frame_count = 0;
|
|
internal_frame_count = 0;
|
|
}
|
|
|
|
if (setting_apply_analog_toggle)
|
|
{
|
|
PSX_FIO->SetAMCT(setting_psx_analog_toggle);
|
|
setting_apply_analog_toggle = false;
|
|
}
|
|
|
|
input_poll_cb();
|
|
|
|
input_update(libretro_supports_bitmasks, input_state_cb );
|
|
|
|
static int32 rects[MEDNAFEN_CORE_GEOMETRY_MAX_H];
|
|
rects[0] = ~0;
|
|
|
|
EmulateSpecStruct spec = {0};
|
|
spec.surface = surf;
|
|
spec.SoundRate = 44100;
|
|
spec.SoundBuf = NULL;
|
|
spec.LineWidths = rects;
|
|
spec.SoundBufMaxSize = 0;
|
|
spec.SoundVolume = 1.0;
|
|
spec.soundmultiplier = 1.0;
|
|
spec.SoundBufSize = 0;
|
|
spec.VideoFormatChanged = false;
|
|
spec.SoundFormatChanged = false;
|
|
|
|
//if (disableVideo)
|
|
//{
|
|
// spec.skip = true;
|
|
//}
|
|
|
|
EmulateSpecStruct *espec = (EmulateSpecStruct*)&spec;
|
|
/* start of Emulate */
|
|
int32_t timestamp = 0;
|
|
|
|
espec->skip = false;
|
|
|
|
MDFNMP_ApplyPeriodicCheats();
|
|
|
|
|
|
espec->MasterCycles = 0;
|
|
espec->SoundBufSize = 0;
|
|
|
|
PSX_FIO->UpdateInput();
|
|
GPU_StartFrame(espec);
|
|
|
|
Running = -1;
|
|
timestamp = PSX_CPU->Run(timestamp, false, false);
|
|
|
|
assert(timestamp);
|
|
|
|
ForceEventUpdates(timestamp);
|
|
#if 0
|
|
if(GPU_GetScanlineNum() < 100)
|
|
PSX_DBG(PSX_DBG_ERROR, "[BUUUUUUUG] Frame timing end glitch; scanline=%u, st=%u\n", GPU_GetScanlineNum(), timestamp);
|
|
#endif
|
|
|
|
//printf("scanline=%u, st=%u\n", GPU_GetScanlineNum(), timestamp);
|
|
|
|
espec->SoundBufSize = IntermediateBufferPos;
|
|
IntermediateBufferPos = 0;
|
|
|
|
PSX_CDC->ResetTS();
|
|
TIMER_ResetTS();
|
|
DMA_ResetTS();
|
|
GPU_ResetTS();
|
|
PSX_FIO->ResetTS();
|
|
|
|
RebaseTS(timestamp);
|
|
|
|
espec->MasterCycles = timestamp;
|
|
|
|
// Save memcards if dirty.
|
|
unsigned players = input_get_player_count();
|
|
for(int i = 0; i < players; i++)
|
|
{
|
|
uint64_t new_dc = PSX_FIO->GetMemcardDirtyCount(i);
|
|
|
|
if(new_dc > Memcard_PrevDC[i])
|
|
{
|
|
Memcard_PrevDC[i] = new_dc;
|
|
Memcard_SaveDelay[i] = 0;
|
|
}
|
|
|
|
if(Memcard_SaveDelay[i] >= 0)
|
|
{
|
|
Memcard_SaveDelay[i] += timestamp;
|
|
if(Memcard_SaveDelay[i] >= (33868800 * 2)) // Wait until about 2 seconds of no new writes.
|
|
{
|
|
char ext[64];
|
|
const char *memcard = NULL;
|
|
|
|
#ifndef NDEBUG
|
|
log_cb(RETRO_LOG_INFO, "Saving memcard %d...\n", i);
|
|
#endif
|
|
|
|
if (i == 0 && !use_mednafen_memcard0_method)
|
|
{
|
|
PSX_FIO->SaveMemcard(i);
|
|
Memcard_SaveDelay[i] = -1;
|
|
Memcard_PrevDC[i] = 0;
|
|
continue;
|
|
}
|
|
|
|
int index = i;
|
|
if (i == 0) index = memcard_left_index;
|
|
else if (i == 1) index = memcard_right_index;
|
|
|
|
snprintf(ext, sizeof(ext), "%d.mcr", index);
|
|
memcard = MDFN_MakeFName(MDFNMKF_SAV, 0, ext);
|
|
PSX_FIO->SaveMemcard(i, memcard);
|
|
Memcard_SaveDelay[i] = -1;
|
|
Memcard_PrevDC[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* end of Emulate */
|
|
|
|
// Check if aspect ratio needs to be changed due to display mode change on this frame
|
|
if (MDFN_UNLIKELY((aspect_ratio_setting == 1) && aspect_ratio_dirty))
|
|
{
|
|
struct retro_system_av_info new_av_info;
|
|
retro_get_system_av_info(&new_av_info);
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, &new_av_info.geometry))
|
|
aspect_ratio_dirty = false;
|
|
|
|
// If unable to change geometry here, defer to next frame and leave aspect_ratio_dirty flagged
|
|
}
|
|
|
|
// Check if timing needs to be changed due to interlacing change on this frame
|
|
// May be possible to track interlacing via espec instead of via RSX?
|
|
if (MDFN_UNLIKELY((core_timing_fps_mode == AUTO_TOGGLE_TIMING) && interlace_setting_dirty))
|
|
{
|
|
// This may cause video and audio reinit on the frontend, so it may be preferable to
|
|
// set the core option to force progressive or interlaced timings
|
|
struct retro_system_av_info new_av_info;
|
|
retro_get_system_av_info(&new_av_info);
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &new_av_info))
|
|
interlace_setting_dirty = false;
|
|
|
|
// If unable to change AV info here, defer to next frame and leave interlace_setting_dirty flagged
|
|
}
|
|
|
|
const void *fb = NULL;
|
|
unsigned width = rects[0];
|
|
unsigned height = spec.DisplayRect.h;
|
|
uint8_t upscale_shift = GPU_get_upscale_shift();
|
|
|
|
if (rsx_intf_is_type() == RSX_SOFTWARE)
|
|
{
|
|
#ifdef NEED_DEINTERLACER
|
|
if (spec.InterlaceOn)
|
|
{
|
|
if (!PrevInterlaced)
|
|
deint.ClearState();
|
|
|
|
deint.Process(surf, spec.DisplayRect, rects, spec.InterlaceField);
|
|
|
|
PrevInterlaced = true;
|
|
|
|
spec.InterlaceOn = false;
|
|
spec.InterlaceField = 0;
|
|
}
|
|
else
|
|
PrevInterlaced = false;
|
|
#endif
|
|
// PSX is rather special, and needs specific handling ...
|
|
|
|
width = rects[0]; // spec.DisplayRect.w is 0. Only rects[0].w seems to return something sane.
|
|
height = spec.DisplayRect.h;
|
|
//fprintf(stderr, "(%u x %u)\n", width, height);
|
|
|
|
// PSX core inserts padding on left and right (overscan). Optionally crop this.
|
|
const uint32_t *pix = surf->pixels;
|
|
unsigned pix_offset = 0;
|
|
|
|
if (crop_overscan)
|
|
{
|
|
// Crop total # of pixels output by PSX in active scanline region down to # of pixels in corresponding horizontal display mode
|
|
// 280 width -> 256 width.
|
|
// 350 width -> 320 width.
|
|
// 400 width -> 366 width.
|
|
// 560 width -> 512 width.
|
|
// 700 width -> 640 width.
|
|
switch (width)
|
|
{
|
|
case 280:
|
|
pix_offset += 12 - image_offset + floor(0.5 * image_crop);
|
|
width = 256 - image_crop;
|
|
break;
|
|
|
|
case 350:
|
|
pix_offset += 15 - image_offset + floor(0.5 * image_crop);
|
|
width = 320 - image_crop;
|
|
break;
|
|
|
|
/* 368px mode. Some games are overcropped at 364 width or undercropped at 368 width, so crop to 366.
|
|
Adjust in future if there are issues. */
|
|
case 400:
|
|
pix_offset += 17 - image_offset + floor(0.5 * image_crop);
|
|
width = 366 - image_crop;
|
|
break;
|
|
|
|
case 560:
|
|
pix_offset += 24 - image_offset + floor(0.5 * image_crop);
|
|
width = 512 - image_crop;
|
|
break;
|
|
|
|
case 700:
|
|
pix_offset += 30 - image_offset + floor(0.5 * image_crop);
|
|
width = 640 - image_crop;
|
|
break;
|
|
|
|
default:
|
|
// This shouldn't happen.
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
width <<= upscale_shift;
|
|
height <<= upscale_shift;
|
|
pix += pix_offset << upscale_shift;
|
|
|
|
if (GPU_get_display_possibly_dirty() || (GPU_get_display_change_count() != 0) || !allow_frame_duping)
|
|
fb = pix;
|
|
}
|
|
|
|
int16_t *interbuf = (int16_t*)&IntermediateBuffer;
|
|
|
|
if (gui_show)
|
|
{
|
|
if (!gui_inited)
|
|
{
|
|
frame_width = width;
|
|
frame_height = height;
|
|
|
|
gui_init(frame_width, frame_height, sizeof(unsigned));
|
|
gui_set_window_title("Error");
|
|
gui_inited = true;
|
|
}
|
|
|
|
if (width != frame_width || height != frame_height)
|
|
{
|
|
frame_width = width;
|
|
frame_height = height;
|
|
gui_window_resize(0, 0, frame_width, frame_height);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rsx_intf_finalize_frame(fb, width, height,
|
|
MEDNAFEN_CORE_GEOMETRY_MAX_W << (2 + upscale_shift));
|
|
}
|
|
|
|
video_frames++;
|
|
audio_frames += spec.SoundBufSize;
|
|
|
|
audio_batch_cb(interbuf, spec.SoundBufSize);
|
|
|
|
if (GPU_get_display_possibly_dirty() || (GPU_get_display_change_count() != 0))
|
|
{
|
|
internal_frame_count++;
|
|
GPU_set_display_change_count(0);
|
|
GPU_set_display_possibly_dirty(false);
|
|
}
|
|
}
|
|
|
|
void retro_get_system_info(struct retro_system_info *info)
|
|
{
|
|
memset(info, 0, sizeof(*info));
|
|
info->library_name = MEDNAFEN_CORE_NAME;
|
|
#ifdef GIT_VERSION
|
|
info->library_version = MEDNAFEN_CORE_VERSION GIT_VERSION;
|
|
#else
|
|
info->library_version = MEDNAFEN_CORE_VERSION;
|
|
#endif
|
|
info->need_fullpath = true;
|
|
info->valid_extensions = MEDNAFEN_CORE_EXTENSIONS;
|
|
info->block_extract = false;
|
|
}
|
|
|
|
void retro_get_system_av_info(struct retro_system_av_info *info)
|
|
{
|
|
rsx_intf_get_system_av_info(info);
|
|
}
|
|
|
|
void retro_deinit(void)
|
|
{
|
|
delete surf;
|
|
surf = NULL;
|
|
|
|
log_cb(RETRO_LOG_DEBUG, "[%s]: Samples / Frame: %.5f\n",
|
|
MEDNAFEN_CORE_NAME, (double)audio_frames / video_frames);
|
|
log_cb(RETRO_LOG_DEBUG, "[%s]: Estimated FPS: %.5f\n",
|
|
MEDNAFEN_CORE_NAME, (double)video_frames * 44100 / audio_frames);
|
|
|
|
libretro_supports_bitmasks = false;
|
|
}
|
|
|
|
unsigned retro_get_region(void)
|
|
{
|
|
// simias: should I override this when fast_pal is set?
|
|
//
|
|
// I'm not entirely sure what's that used for.
|
|
return content_is_pal ? RETRO_REGION_PAL : RETRO_REGION_NTSC;
|
|
}
|
|
|
|
unsigned retro_api_version(void)
|
|
{
|
|
return RETRO_API_VERSION;
|
|
}
|
|
|
|
#include "libretro_core_options.h"
|
|
|
|
void retro_set_environment(retro_environment_t cb)
|
|
{
|
|
struct retro_vfs_interface_info vfs_iface_info;
|
|
environ_cb = cb;
|
|
|
|
libretro_set_core_options(environ_cb);
|
|
|
|
vfs_iface_info.required_interface_version = 1;
|
|
vfs_iface_info.iface = NULL;
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &vfs_iface_info))
|
|
filestream_vfs_init(&vfs_iface_info);
|
|
|
|
input_set_env( cb );
|
|
|
|
rsx_intf_set_environment(cb);
|
|
}
|
|
|
|
void retro_set_audio_sample(retro_audio_sample_t cb)
|
|
{
|
|
audio_cb = cb;
|
|
}
|
|
|
|
void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb)
|
|
{
|
|
audio_batch_cb = cb;
|
|
}
|
|
|
|
void retro_set_input_poll(retro_input_poll_t cb)
|
|
{
|
|
input_poll_cb = cb;
|
|
}
|
|
|
|
void retro_set_input_state(retro_input_state_t cb)
|
|
{
|
|
input_state_cb = cb;
|
|
dbg_input_state_cb = cb;
|
|
}
|
|
|
|
void retro_set_video_refresh(retro_video_refresh_t cb)
|
|
{
|
|
video_cb = cb;
|
|
|
|
rsx_intf_set_video_refresh(cb);
|
|
}
|
|
|
|
size_t retro_serialize_size(void)
|
|
{
|
|
if (enable_variable_serialization_size)
|
|
{
|
|
StateMem st;
|
|
|
|
st.data = NULL;
|
|
st.loc = 0;
|
|
st.len = 0;
|
|
st.malloced = 0;
|
|
st.initial_malloc = 0;
|
|
|
|
if (!MDFNSS_SaveSM(&st, 0, 0, NULL, NULL, NULL))
|
|
return 0;
|
|
|
|
free(st.data);
|
|
return st.len;
|
|
}
|
|
|
|
return DEFAULT_STATE_SIZE; // 16MB
|
|
}
|
|
|
|
bool UsingFastSavestates(void)
|
|
{
|
|
int flags;
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE, &flags))
|
|
return flags & 4;
|
|
return false;
|
|
}
|
|
|
|
bool retro_serialize(void *data, size_t size)
|
|
{
|
|
StateMem st;
|
|
bool ret = false;
|
|
|
|
st.len = 0;
|
|
st.loc = 0;
|
|
st.malloced = size;
|
|
st.initial_malloc = 0;
|
|
|
|
if (size == DEFAULT_STATE_SIZE) //16MB buffer reserved
|
|
{
|
|
//actual size is around 3.75MB (3.67MB for fast savestates) rather than 16MB, so 16MB will hold a savestate without worrying about realloc
|
|
|
|
//save state in place
|
|
st.data = (uint8_t*)data;
|
|
|
|
//fast save states are at least 20% faster
|
|
FastSaveStates = UsingFastSavestates();
|
|
ret = MDFNSS_SaveSM(&st, 0, 0, NULL, NULL, NULL);
|
|
}
|
|
else
|
|
{
|
|
/* it seems that mednafen can realloc pointers sent to it?
|
|
since we don't know the disposition of void* data (is it safe to realloc?) we have to manage a new buffer here */
|
|
static bool logged;
|
|
uint8_t *_dat = (uint8_t*)malloc(size);
|
|
|
|
if (!_dat)
|
|
return false;
|
|
|
|
st.data = _dat;
|
|
|
|
/* there are still some errors with the save states,
|
|
* the size seems to change on some games for now
|
|
* just log when this happens */
|
|
if (!logged && st.len != size)
|
|
{
|
|
log_cb(RETRO_LOG_WARN, "warning, save state size has changed\n");
|
|
logged = true;
|
|
}
|
|
|
|
FastSaveStates = UsingFastSavestates();
|
|
ret = MDFNSS_SaveSM(&st, 0, 0, NULL, NULL, NULL);
|
|
|
|
memcpy(data, st.data, size);
|
|
free(st.data);
|
|
}
|
|
|
|
FastSaveStates = false;
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool retro_unserialize(const void *data, size_t size)
|
|
{
|
|
StateMem st;
|
|
|
|
st.data = (uint8_t*)data;
|
|
st.loc = 0;
|
|
st.len = size;
|
|
st.malloced = 0;
|
|
st.initial_malloc = 0;
|
|
|
|
//fast save states are at least 20% faster
|
|
FastSaveStates = UsingFastSavestates();
|
|
bool okay = MDFNSS_LoadSM(&st, 0, 0);
|
|
FastSaveStates = false;
|
|
return okay;
|
|
}
|
|
|
|
void *retro_get_memory_data(unsigned type)
|
|
{
|
|
uint8_t *data;
|
|
|
|
switch (type)
|
|
{
|
|
case RETRO_MEMORY_SYSTEM_RAM:
|
|
return MainRAM->data8;
|
|
case RETRO_MEMORY_SAVE_RAM:
|
|
if (!use_mednafen_memcard0_method)
|
|
return PSX_FIO->GetMemcardDevice(0)->GetNVData();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
size_t retro_get_memory_size(unsigned type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case RETRO_MEMORY_SYSTEM_RAM:
|
|
return 0x200000;
|
|
case RETRO_MEMORY_SAVE_RAM:
|
|
if (!use_mednafen_memcard0_method)
|
|
return (1 << 17);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void retro_cheat_reset(void)
|
|
{
|
|
MDFN_FlushGameCheats(1);
|
|
}
|
|
|
|
void retro_cheat_set(unsigned index, bool enabled, const char * codeLine)
|
|
{
|
|
const CheatFormatStruct* cf = CheatFormats;
|
|
char name[256];
|
|
std::vector<std::string> codeParts;
|
|
int matchLength=0;
|
|
int cursor;
|
|
std::string part;
|
|
|
|
if (codeLine==NULL)
|
|
return;
|
|
|
|
//Break the code into Parts
|
|
for (cursor=0;;cursor++)
|
|
{
|
|
if (ISHEXDEC)
|
|
matchLength++;
|
|
else
|
|
{
|
|
if (matchLength)
|
|
{
|
|
part=codeLine+cursor-matchLength;
|
|
part.erase(matchLength,std::string::npos);
|
|
codeParts.push_back(part);
|
|
matchLength=0;
|
|
}
|
|
}
|
|
if (!codeLine[cursor])
|
|
break;
|
|
}
|
|
|
|
MemoryPatch patch;
|
|
bool trueML=0;
|
|
for (cursor=0;cursor<codeParts.size();cursor++)
|
|
{
|
|
part=codeParts[cursor];
|
|
if (part.length()==8)
|
|
part+=codeParts[++cursor];
|
|
if (part.length()==12)
|
|
{
|
|
//Decode the cheat
|
|
try
|
|
{
|
|
if(!cf->DecodeCheat(std::string(part), &patch))
|
|
{
|
|
//Generate a name
|
|
sprintf(name,"cheat_%i_%i",index,cursor);
|
|
|
|
//Set parameters
|
|
patch.name=(std::string)name;
|
|
patch.status=enabled;
|
|
|
|
MDFNI_AddCheat(patch);
|
|
patch=MemoryPatch();
|
|
}
|
|
}
|
|
catch(std::exception &e)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Use a simpler approach to make sure that things go right for libretro.
|
|
const char *MDFN_MakeFName(MakeFName_Type type, int id1, const char *cd1)
|
|
{
|
|
static char fullpath[4096];
|
|
int r = 0;
|
|
|
|
fullpath[0] = '\0';
|
|
|
|
switch (type)
|
|
{
|
|
case MDFNMKF_SAV:
|
|
r = snprintf(fullpath, sizeof(fullpath), "%s%c%s.%s",
|
|
retro_save_directory,
|
|
retro_slash,
|
|
shared_memorycards ? "mednafen_psx_libretro_shared" : retro_cd_base_name,
|
|
cd1);
|
|
break;
|
|
case MDFNMKF_FIRMWARE:
|
|
r = snprintf(fullpath, sizeof(fullpath), "%s%c%s", retro_base_directory, retro_slash, cd1);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (r > 4095)
|
|
{
|
|
log_cb(RETRO_LOG_ERROR,"MakeFName path longer than 4095\n");
|
|
fullpath[4095] = '\0';
|
|
}
|
|
|
|
return fullpath;
|
|
}
|
|
|
|
void MDFND_DispMessage(
|
|
unsigned priority, enum retro_log_level level,
|
|
enum retro_message_target target, enum retro_message_type type,
|
|
const char *str)
|
|
{
|
|
if (libretro_msg_interface_version >= 1)
|
|
{
|
|
struct retro_message_ext msg = {
|
|
str,
|
|
3000,
|
|
priority,
|
|
level,
|
|
target,
|
|
type,
|
|
-1
|
|
};
|
|
environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE_EXT, &msg);
|
|
}
|
|
else
|
|
{
|
|
struct retro_message msg =
|
|
{
|
|
str,
|
|
180
|
|
};
|
|
environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE, &msg);
|
|
}
|
|
}
|
|
|
|
void MDFN_DispMessage(
|
|
unsigned priority, enum retro_log_level level,
|
|
enum retro_message_target target, enum retro_message_type type,
|
|
const char *format, ...)
|
|
{
|
|
va_list ap;
|
|
char *str = (char*)malloc(4096 * sizeof(char));
|
|
|
|
va_start(ap, format);
|
|
vsnprintf(str, 4096, format, ap);
|
|
va_end(ap);
|
|
|
|
MDFND_DispMessage(priority, level, target, type, str);
|
|
free(str);
|
|
}
|