beetle-psx-libretro/libretro.cpp
Zachary Cook 798fab9d5b Fix eventcycles, add SPU Samples, allows much better performance
EventCycles should work up to 2048 now, now that it is used by MDEC and Timer

SPU samples option was added, audio glitches will occur in some games unless samples is 1
2022-11-20 21:59:38 +01:00

5202 lines
140 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>
#include <dlfcn.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_option_categories = false;
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;
uint8_t spu_samples = 1;
// 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;
static int override_bios;
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 (override_bios)
{
if (override_bios == 1)
{
bios_name_list[0] = "psxonpsp660.bin";
bios_name_list[1] = "PSXONPSP660.bin";
bios_name_list[2] = NULL;
bios_sha1 = "96880D1CA92A016FF054BE5159BB06FE03CB4E14";
}
else if (override_bios == 2)
{
bios_name_list[0] = "ps1_rom.bin";
bios_name_list[1] = "PS1_ROM.bin";
bios_name_list[2] = NULL;
bios_sha1 = "C40146361EB8CF670B19FDC9759190257803CAB7";
}
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)
{
bios_path[4095] = '\0';
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 obtained_sha1[41];
sha1_calculate(bios_path, obtained_sha1);
if (strcmp(obtained_sha1, bios_sha1))
{
log_cb(RETRO_LOG_WARN, "Override 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, "Override firmware found: %s\n", bios_path);
log_cb(RETRO_LOG_INFO, "Override firmware SHA1: %s\n", obtained_sha1);
return true;
}
log_cb(RETRO_LOG_WARN, "Override firmware is missing: %s\n", bios_name_list[0]);
log_cb(RETRO_LOG_WARN, "Fallback to region specific firmware.\n");
}
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_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_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)
{
bios_path[4095] = '\0';
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 &timestamp, 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 &timestamp, uint32_t A)
{
uint32_t V;
MemRW<uint8, false, false>(timestamp, A, V);
return(V);
}
uint16_t MDFN_FASTCALL PSX_MemRead16(int32_t &timestamp, uint32_t A)
{
uint32_t V;
MemRW<uint16, false, false>(timestamp, A, V);
return(V);
}
uint32_t MDFN_FASTCALL PSX_MemRead24(int32_t &timestamp, uint32_t A)
{
uint32_t V;
MemRW<uint32, false, true>(timestamp, A, V);
return(V);
}
uint32_t MDFN_FASTCALL PSX_MemRead32(int32_t &timestamp, 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);
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;
if(!strncmp(bootpos + 7, "SLUS_007.65", 11) || !strncmp(bootpos + 7, "SLES_009.79", 11))
{
is_monkey_hero = true;
log_cb(RETRO_LOG_INFO, "Monkey Hero FBWrite Tweak Activated\n");
}
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
#define SHM_SIZE RAM_SIZE+BIOS_SIZE+SCRATCH_SIZE
#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) {
/* Android 10+ / API 29+ gives EACCES (permission denied) opening /dev/ashmem
* fallback to ASharedMemory_create available since Android 8 / API 26 */
if(errno == EACCES) {
void *lib;
int (*create)(const char*, size_t);
int (*setProt)(int, int);
char *error1, *error2;
dlerror(); /* Clear any existing error */
lib = dlopen("libandroid.so", RTLD_NOW);
if (lib == NULL) {
log_cb(RETRO_LOG_ERROR, "Failed to dlopen: %s\n", dlerror());
return 0;
}
*(void **)(&create) = dlsym(lib, "ASharedMemory_create");
error1 = dlerror();
*(void **)(&setProt) = dlsym(lib, "ASharedMemory_setProt");
error2 = dlerror();
if (error1 == NULL)
memfd = (*create)("lightrec_memfd",SHM_SIZE);
if (memfd < 0) {
log_cb(RETRO_LOG_ERROR, "Failed to ASharedMemory_create: %s\n",
(error1 != NULL) ? error1 : strerror(errno));
dlclose(lib);
return 0;
}
if (error2 != NULL || (((*setProt)(memfd, PROT_READ|PROT_WRITE)) < 0))
log_cb(RETRO_LOG_ERROR, "Failed to ASharedMemory_setProt: %s\n",
(error2 != NULL) ? error2 : strerror(errno));
dlclose(lib);
} else {
log_cb(RETRO_LOG_ERROR, "Failed to create ASHMEM: %s\n", strerror(errno));
return 0;
}
} else {
ioctl(memfd, ASHMEM_SET_NAME, "lightrec_memfd");
ioctl(memfd, ASHMEM_SET_SIZE, SHM_SIZE);
}
#endif
#ifdef HAVE_SHM
int memfd;
const char *shm_name = "/lightrec_memfd_beetle";
memfd = shm_open(shm_name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
if (memfd < 0 && errno == EEXIST) {
shm_unlink(shm_name);
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, SHM_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, SHM_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
/* android shared memory is not pinned by mmap, it dies on close */
close(memfd);
#endif
}
#endif /* HAVE_LIGHTREC */
/* LED interface */
static retro_set_led_state_t led_state_cb = NULL;
static unsigned int retro_led_state[2] = {0};
static void retro_led_interface(void)
{
/* 0: Power
* 1: CD */
unsigned int led_state[2] = {0};
unsigned int l = 0;
led_state[0] = (!Running) ? 1 : 0;
led_state[1] = (PSX_CDC->DriveStatus > 0) ? 1 : 0;
for (l = 0; l < sizeof(led_state)/sizeof(led_state[0]); l++)
{
if (retro_led_state[l] != led_state[l])
{
retro_led_state[l] = led_state[l];
led_state_cb(l, led_state[l]);
}
}
}
/* 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 < 2; 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)
{
}
}
}
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 =
{
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
};
/* 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 cdimagecache = true;
#else
static bool 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;
extern void PSXDitherApply(bool);
static void check_variables(bool startup)
{
struct retro_variable var = {0};
#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)
{
cdimagecache = false;
cd_async = false;
}
else if (strcmp(var.value, "async") == 0)
{
cdimagecache = false;
cd_async = true;
}
else if (strcmp(var.value, "precache") == 0)
{
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;
var.key = BEETLE_OPT(dynarec_spu_samples);
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
spu_samples = atoi(var.value);
}
else
spu_samples = 1;
#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(override_bios);
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
{
override_bios = 0;
}
else if (!strcmp(var.value, "psxonpsp"))
{
override_bios = 1;
}
else if (!strcmp(var.value, "ps1_rom"))
{
override_bios = 2;
}
}
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(widescreen_hack_aspect_ratio);
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "16:10"))
{
if (!startup && widescreen_hack_aspect_ratio_setting != 0)
has_new_geometry = true;
widescreen_hack_aspect_ratio_setting = 0;
}
else if (!strcmp(var.value, "16:9"))
{
if (!startup && widescreen_hack_aspect_ratio_setting != 1)
has_new_geometry = true;
widescreen_hack_aspect_ratio_setting = 1;
}
else if (!strcmp(var.value, "18:9"))
{
if (!startup && widescreen_hack_aspect_ratio_setting != 2)
has_new_geometry = true;
widescreen_hack_aspect_ratio_setting = 2;
}
else if (!strcmp(var.value, "19:9"))
{
if (!startup && widescreen_hack_aspect_ratio_setting != 3)
has_new_geometry = true;
widescreen_hack_aspect_ratio_setting = 3;
}
else if (!strcmp(var.value, "20:9"))
{
if (!startup && widescreen_hack_aspect_ratio_setting != 4)
has_new_geometry = true;
widescreen_hack_aspect_ratio_setting = 4;
}
else if (!strcmp(var.value, "21:9")) // 64:27
{
if (!startup && widescreen_hack_aspect_ratio_setting != 5)
has_new_geometry = true;
widescreen_hack_aspect_ratio_setting = 5;
}
else if (!strcmp(var.value, "32:9"))
{
if (!startup && widescreen_hack_aspect_ratio_setting != 6)
has_new_geometry = true;
widescreen_hack_aspect_ratio_setting = 6;
}
}
else
{
if (!startup && widescreen_hack_aspect_ratio_setting != 1)
has_new_geometry = true;
widescreen_hack_aspect_ratio_setting = 1;
}
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(crosshair_color_p1);
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (strcmp(var.value, "red") == 0)
setting_crosshair_color_p1 = 0xFF0000;
else if (strcmp(var.value, "blue") == 0)
setting_crosshair_color_p1 = 0x0080FF;
else if (strcmp(var.value, "green") == 0)
setting_crosshair_color_p1 = 0x00FF00;
else if (strcmp(var.value, "orange") == 0)
setting_crosshair_color_p1 = 0xFF8000;
else if (strcmp(var.value, "yellow") == 0)
setting_crosshair_color_p1 = 0xFFFF00;
else if (strcmp(var.value, "cyan") == 0)
setting_crosshair_color_p1 = 0x00FFFF;
else if (strcmp(var.value, "pink") == 0)
setting_crosshair_color_p1 = 0xFF00FF;
else if (strcmp(var.value, "purple") == 0)
setting_crosshair_color_p1 = 0x8000FF;
else if (strcmp(var.value, "black") == 0)
setting_crosshair_color_p1 = 0x000000;
else if (strcmp(var.value, "white") == 0)
setting_crosshair_color_p1 = 0xFFFFFF;
}
var.key = BEETLE_OPT(crosshair_color_p2);
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (strcmp(var.value, "red") == 0)
setting_crosshair_color_p2 = 0xFF0000;
else if (strcmp(var.value, "blue") == 0)
setting_crosshair_color_p2 = 0x0080FF;
else if (strcmp(var.value, "green") == 0)
setting_crosshair_color_p2 = 0x00FF00;
else if (strcmp(var.value, "orange") == 0)
setting_crosshair_color_p2 = 0xFF8000;
else if (strcmp(var.value, "yellow") == 0)
setting_crosshair_color_p2 = 0xFFFF00;
else if (strcmp(var.value, "cyan") == 0)
setting_crosshair_color_p2 = 0x00FFFF;
else if (strcmp(var.value, "pink") == 0)
setting_crosshair_color_p2 = 0xFF00FF;
else if (strcmp(var.value, "purple") == 0)
setting_crosshair_color_p2 = 0x8000FF;
else if (strcmp(var.value, "black") == 0)
setting_crosshair_color_p2 = 0x000000;
else if (strcmp(var.value, "white") == 0)
setting_crosshair_color_p2 = 0xFFFFFF;
}
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)
{
int old_crop_overscan = crop_overscan;
if (strcmp(var.value, "disabled") == 0)
crop_overscan = 0;
else if (strcmp(var.value, "static") == 0)
crop_overscan = 1;
else if (strcmp(var.value, "smart") == 0)
crop_overscan = 2;
if(crop_overscan != old_crop_overscan)
{
has_new_geometry = true;
old_crop_overscan = crop_overscan;
}
}
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];
RFILE *fp = filestream_open(path.c_str(), RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (fp == NULL)
return;
MDFN_GetFilePathComponents(path, &dir_path);
while(filestream_gets(fp, linebuf, sizeof(linebuf)) != 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:
filestream_close(fp);
}
// TODO: LoadCommon()
static bool MDFNI_LoadCD(const char *devicename)
{
log_cb(RETRO_LOG_INFO, "Loading %s...\n", devicename);
try
{
size_t devicename_len = strlen(devicename);
if(devicename && devicename_len > 4 && !strcasecmp(devicename + devicename_len - 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, 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 && devicename_len > 4 && !strcasecmp(devicename + devicename_len - 4, ".pbp"))
{
bool success = true;
CDIF *image = CDIF_Open(&success, devicename, false, 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++)
{
/* image_name is at most 4096 - 4 (removing ".pbp")
* gives label room to add index and quiets gcc warnings */
char image_name[4092];
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';
bool cache = cdimagecache;
/* don't precache if physical cdrom, will take way too long and be unresponive */
if (cdimagecache && devicename && !strncasecmp(devicename, "cdrom:", 6)) {
cache = false;
log_cb(RETRO_LOG_INFO, "Skipping Pre-Cache due to using physical media: %s\n", devicename);
}
CDIF *image = CDIF_Open(&success, devicename, false, cache);
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;
}
#ifdef DEBUG
// 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);
}
#endif
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;
size_t name_len = strlen(name);
if(name_len > 4 && (
!strcasecmp(name + name_len - 4, ".cue") ||
!strcasecmp(name + name_len - 4, ".ccd") ||
!strcasecmp(name + name_len - 4, ".toc") ||
!strcasecmp(name + name_len - 4, ".m3u") ||
!strcasecmp(name + name_len - 4, ".chd") ||
!strcasecmp(name + name_len - 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(track_textures);
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
option_display.key = BEETLE_OPT(dump_textures);
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
option_display.key = BEETLE_OPT(replace_textures);
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(track_textures);
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
option_display.key = BEETLE_OPT(dump_textures);
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
option_display.key = BEETLE_OPT(replace_textures);
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(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);
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)
{
}
}
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)
{
}
}
// Update gun crosshair color
PSX_FIO->SetCrosshairsColor(0, setting_crosshair_color_p1);
PSX_FIO->SetCrosshairsColor(1, setting_crosshair_color_p2);
}
/* 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.LineWidths = rects;
spec.SoundBufSize = 0;
EmulateSpecStruct *espec = (EmulateSpecStruct*)&spec;
/* start of Emulate */
int32_t timestamp = 0;
espec->skip = false;
MDFNMP_ApplyPeriodicCheats();
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
espec->SoundBufSize = IntermediateBufferPos;
IntermediateBufferPos = 0;
PSX_CDC->ResetTS();
TIMER_ResetTS();
DMA_ResetTS();
GPU_ResetTS();
PSX_FIO->ResetTS();
RebaseTS(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));
}
/* LED interface */
if (led_state_cb)
retro_led_interface();
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_option_categories = false;
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;
struct retro_led_interface led_interface;
environ_cb = cb;
libretro_supports_option_categories = false;
libretro_set_core_options(environ_cb, &libretro_supports_option_categories);
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);
if (environ_cb(RETRO_ENVIRONMENT_GET_LED_INTERFACE, &led_interface))
if (led_interface.set_led_state && !led_state_cb)
led_state_cb = led_interface.set_led_state;
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)
{
fullpath[4095] = '\0';
log_cb(RETRO_LOG_ERROR,"MakeFName path longer than 4095: %s\n", fullpath);
}
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);
}