ppsspp/libretro/libretro.cpp
Henrik Rydgård 19e4de5088 Change global UI messages to use an enum instead of strings.
Makes it easier to add new ones and delete outdated ones without missing
any uses.
2023-09-30 11:37:02 +02:00

1760 lines
54 KiB
C++

#include "ppsspp_config.h"
#include <cstring>
#include <cassert>
#include <thread>
#include <atomic>
#include <vector>
#include <cstdlib>
#include <mutex>
#include "Common/CPUDetect.h"
#include "Common/Log.h"
#include "Common/LogManager.h"
#include "Common/System/Display.h"
#include "Common/System/NativeApp.h"
#include "Common/System/System.h"
#include "Common/TimeUtil.h"
#include "Common/File/FileUtil.h"
#include "Common/Serialize/Serializer.h"
#include "Common/ConsoleListener.h"
#include "Common/Input/InputState.h"
#include "Common/Thread/ThreadUtil.h"
#include "Common/Thread/ThreadManager.h"
#include "Common/File/VFS/VFS.h"
#include "Common/File/VFS/DirectoryReader.h"
#include "Common/Data/Text/I18n.h"
#include "Common/StringUtils.h"
#include "Core/Config.h"
#include "Core/ConfigValues.h"
#include "Core/Core.h"
#include "Core/HLE/sceCtrl.h"
#include "Core/HLE/sceUtility.h"
#include "Core/HLE/__sceAudio.h"
#include "Core/HW/MemoryStick.h"
#include "Core/HW/StereoResampler.h"
#include "Core/MemMap.h"
#include "Core/System.h"
#include "Core/CoreTiming.h"
#include "Core/HW/Display.h"
#include "Core/CwCheat.h"
#include "Core/ELF/ParamSFO.h"
#include "GPU/GPUState.h"
#include "GPU/GPUInterface.h"
#include "GPU/Common/FramebufferManagerCommon.h"
#include "GPU/Common/TextureScalerCommon.h"
#include "GPU/Common/PresentationCommon.h"
#include "UI/AudioCommon.h"
#include "libretro/libretro.h"
#include "libretro/LibretroGraphicsContext.h"
#include "libretro/libretro_core_options.h"
#if PPSSPP_PLATFORM(ANDROID)
#include <sys/system_properties.h>
#endif
#define DIR_SEP "/"
#ifdef _WIN32
#define DIR_SEP_CHRS "/\\"
#else
#define DIR_SEP_CHRS "/"
#endif
#ifdef HAVE_LIBRETRO_VFS
#include "streams/file_stream.h"
#endif
#define SAMPLERATE 44100
// Calculated swap interval is 'stable' if the same
// value is recorded for a number of retro_run()
// calls equal to VSYNC_SWAP_INTERVAL_FRAMES
#define VSYNC_SWAP_INTERVAL_FRAMES 6
// Calculated swap interval is 'valid' if it is
// within VSYNC_SWAP_INTERVAL_THRESHOLD of an integer
// value
#define VSYNC_SWAP_INTERVAL_THRESHOLD 0.05f
// Swap interval detection is only enabled if the
// core is running at 'normal' speed - i.e. if
// run speed is within VSYNC_SWAP_INTERVAL_RUN_SPEED_THRESHOLD
// percent of 100
#define VSYNC_SWAP_INTERVAL_RUN_SPEED_THRESHOLD 5.0f
static bool libretro_supports_bitmasks = false;
static bool libretro_supports_option_categories = false;
static bool show_ip_address_options = true;
static bool show_upnp_port_option = true;
static bool show_detect_frame_rate_option = true;
static std::string changeProAdhocServer;
namespace Libretro
{
LibretroGraphicsContext *ctx;
retro_environment_t environ_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 bool detectVsyncSwapInterval = false;
static bool detectVsyncSwapIntervalOptShown = true;
static s64 expectedTimeUsPerRun = 0;
static uint32_t vsyncSwapInterval = 1;
static uint32_t vsyncSwapIntervalLast = 1;
static uint32_t vsyncSwapIntervalCounter = 0;
static int numVBlanksLast = 0;
static double fpsTimeLast = 0.0;
static float runSpeed = 0.0f;
static s64 runTicksLast = 0;
static void VsyncSwapIntervalReset()
{
expectedTimeUsPerRun = (s64)(1000000.0f / (60.0f / 1.001f));
vsyncSwapInterval = 1;
vsyncSwapIntervalLast = 1;
vsyncSwapIntervalCounter = 0;
numVBlanksLast = 0;
fpsTimeLast = 0.0;
runSpeed = 0.0f;
runTicksLast = 0;
detectVsyncSwapIntervalOptShown = true;
}
static void VsyncSwapIntervalDetect()
{
if (!detectVsyncSwapInterval)
return;
// All bets are off if core is running at
// the 'wrong' speed (i.e. cycle count for
// this run will be meaningless if internal
// frame rate is dropping below expected
// value, or fast forward is enabled)
double fpsTime = time_now_d();
int numVBlanks = __DisplayGetNumVblanks();
int frames = numVBlanks - numVBlanksLast;
if (frames >= VSYNC_SWAP_INTERVAL_FRAMES << 1)
{
double fps = (double)frames / (fpsTime - fpsTimeLast);
runSpeed = fps / ((60.0f / 1.001f) / 100.0f);
fpsTimeLast = fpsTime;
numVBlanksLast = numVBlanks;
}
float speedDelta = 100.0f - runSpeed;
speedDelta = (speedDelta < 0.0f) ? -speedDelta : speedDelta;
// Speed is measured relative to a 60 Hz refresh
// rate. If we are transitioning from a low internal
// frame rate to a higher internal frame rate, then
// 'full speed' may actually equate to
// (100 / current_swap_interval)...
if ((vsyncSwapInterval > 1) &&
(speedDelta >= VSYNC_SWAP_INTERVAL_RUN_SPEED_THRESHOLD))
{
speedDelta = 100.0f - (runSpeed * (float)vsyncSwapInterval);
speedDelta = (speedDelta < 0.0f) ? -speedDelta : speedDelta;
}
if (speedDelta >= VSYNC_SWAP_INTERVAL_RUN_SPEED_THRESHOLD)
{
// Swap interval detection is invalid - bail out
vsyncSwapIntervalCounter = 0;
return;
}
// Get elapsed time (us) for this run
s64 runTicks = CoreTiming::GetTicks();
s64 runTimeUs = cyclesToUs(runTicks - runTicksLast);
// Check if current internal frame rate is a
// factor of the default ~60 Hz
float swapRatio = (float)runTimeUs / (float)expectedTimeUsPerRun;
uint32_t swapInteger;
float swapRemainder;
// If internal frame rate is equal to (within threshold)
// or higher than the default ~60 Hz, fall back to a
// swap interval of 1
if (swapRatio < (1.0f + VSYNC_SWAP_INTERVAL_THRESHOLD))
{
swapInteger = 1;
swapRemainder = 0.0f;
}
else
{
swapInteger = (uint32_t)(swapRatio + 0.5f);
swapRemainder = swapRatio - (float)swapInteger;
swapRemainder = (swapRemainder < 0.0f) ?
-swapRemainder : swapRemainder;
}
// > Swap interval is considered 'valid' if it is
// within VSYNC_SWAP_INTERVAL_THRESHOLD of an integer
// value
// > If valid, check if new swap interval differs from
// previously logged value
if ((swapRemainder <= VSYNC_SWAP_INTERVAL_THRESHOLD) &&
(swapInteger != vsyncSwapInterval))
{
vsyncSwapIntervalCounter =
(swapInteger == vsyncSwapIntervalLast) ?
(vsyncSwapIntervalCounter + 1) : 0;
// Check whether swap interval is 'stable'
if (vsyncSwapIntervalCounter >= VSYNC_SWAP_INTERVAL_FRAMES)
{
vsyncSwapInterval = swapInteger;
vsyncSwapIntervalCounter = 0;
// Notify frontend
retro_system_av_info avInfo;
retro_get_system_av_info(&avInfo);
environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &avInfo);
}
vsyncSwapIntervalLast = swapInteger;
}
else
vsyncSwapIntervalCounter = 0;
runTicksLast = runTicks;
}
} // namespace Libretro
using namespace Libretro;
class PrintfLogger : public LogListener
{
public:
PrintfLogger(retro_log_callback log) : log_(log.log) {}
void Log(const LogMessage &message)
{
switch (message.level)
{
case LogLevel::LVERBOSE:
case LogLevel::LDEBUG:
log_(RETRO_LOG_DEBUG, "[%s] %s",
message.log, message.msg.c_str());
break;
case LogLevel::LERROR:
log_(RETRO_LOG_ERROR, "[%s] %s",
message.log, message.msg.c_str());
break;
case LogLevel::LNOTICE:
case LogLevel::LWARNING:
log_(RETRO_LOG_WARN, "[%s] %s",
message.log, message.msg.c_str());
break;
case LogLevel::LINFO:
default:
log_(RETRO_LOG_INFO, "[%s] %s",
message.log, message.msg.c_str());
break;
}
}
private:
retro_log_printf_t log_;
};
static PrintfLogger *printfLogger;
static bool set_variable_visibility(void)
{
struct retro_core_option_display option_display;
struct retro_variable var;
bool updated = false;
// Show/hide IP address options
bool show_ip_address_options_prev = show_ip_address_options;
show_ip_address_options = true;
var.key = "ppsspp_change_pro_ad_hoc_server_address";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && strcmp(var.value, "IP address"))
show_ip_address_options = false;
if (show_ip_address_options != show_ip_address_options_prev)
{
option_display.visible = show_ip_address_options;
for (int i = 0; i < 12; i++)
{
char key[64] = {0};
option_display.key = key;
snprintf(key, sizeof(key), "ppsspp_pro_ad_hoc_server_address%02d", i + 1);
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
}
updated = true;
}
// Show/hide 'UPnP Use Original Port' option
bool show_upnp_port_option_prev = show_upnp_port_option;
show_upnp_port_option = true;
var.key = "ppsspp_enable_upnp";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && !strcmp(var.value, "disabled"))
show_upnp_port_option = false;
if (show_upnp_port_option != show_upnp_port_option_prev)
{
option_display.visible = show_upnp_port_option;
option_display.key = "ppsspp_upnp_use_original_port";
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
updated = true;
}
// Show/hide 'Detect Frame Rate Changes' option
bool show_detect_frame_rate_option_prev = show_detect_frame_rate_option;
int frameskip = 0;
bool auto_frameskip = false;
bool dupe_frames = false;
show_detect_frame_rate_option = true;
var.key = "ppsspp_frameskip";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && strcmp(var.value, "disabled"))
frameskip = atoi(var.value);
var.key = "ppsspp_auto_frameskip";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && !strcmp(var.value, "enabled"))
auto_frameskip = true;
var.key = "ppsspp_frame_duplication";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value && !strcmp(var.value, "enabled"))
dupe_frames = true;
show_detect_frame_rate_option = (frameskip == 0) && !auto_frameskip && !dupe_frames;
if (show_detect_frame_rate_option != show_detect_frame_rate_option_prev)
{
option_display.visible = show_detect_frame_rate_option;
option_display.key = "ppsspp_detect_vsync_swap_interval";
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
updated = true;
}
return updated;
}
void retro_set_environment(retro_environment_t cb)
{
environ_cb = cb;
libretro_set_core_options(environ_cb, &libretro_supports_option_categories);
struct retro_core_options_update_display_callback update_display_cb;
update_display_cb.callback = set_variable_visibility;
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK, &update_display_cb);
#ifdef HAVE_LIBRETRO_VFS
struct retro_vfs_interface_info vfs_iface_info { 1, nullptr };
if (cb(RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &vfs_iface_info))
filestream_vfs_init(&vfs_iface_info);
#endif
}
static int get_language_auto(void)
{
retro_language val = RETRO_LANGUAGE_ENGLISH;
environ_cb(RETRO_ENVIRONMENT_GET_LANGUAGE, &val);
switch (val)
{
default:
case RETRO_LANGUAGE_ENGLISH:
return PSP_SYSTEMPARAM_LANGUAGE_ENGLISH;
case RETRO_LANGUAGE_JAPANESE:
return PSP_SYSTEMPARAM_LANGUAGE_JAPANESE;
case RETRO_LANGUAGE_FRENCH:
return PSP_SYSTEMPARAM_LANGUAGE_FRENCH;
case RETRO_LANGUAGE_GERMAN:
return PSP_SYSTEMPARAM_LANGUAGE_GERMAN;
case RETRO_LANGUAGE_SPANISH:
return PSP_SYSTEMPARAM_LANGUAGE_SPANISH;
case RETRO_LANGUAGE_ITALIAN:
return PSP_SYSTEMPARAM_LANGUAGE_ITALIAN;
case RETRO_LANGUAGE_PORTUGUESE_BRAZIL:
case RETRO_LANGUAGE_PORTUGUESE_PORTUGAL:
return PSP_SYSTEMPARAM_LANGUAGE_PORTUGUESE;
case RETRO_LANGUAGE_RUSSIAN:
return PSP_SYSTEMPARAM_LANGUAGE_RUSSIAN;
case RETRO_LANGUAGE_DUTCH:
return PSP_SYSTEMPARAM_LANGUAGE_DUTCH;
case RETRO_LANGUAGE_KOREAN:
return PSP_SYSTEMPARAM_LANGUAGE_KOREAN;
case RETRO_LANGUAGE_CHINESE_TRADITIONAL:
return PSP_SYSTEMPARAM_LANGUAGE_CHINESE_TRADITIONAL;
case RETRO_LANGUAGE_CHINESE_SIMPLIFIED:
return PSP_SYSTEMPARAM_LANGUAGE_CHINESE_SIMPLIFIED;
}
}
static std::string map_psp_language_to_i18n_locale(int val)
{
switch (val)
{
default:
case PSP_SYSTEMPARAM_LANGUAGE_ENGLISH:
return "en_US";
case PSP_SYSTEMPARAM_LANGUAGE_JAPANESE:
return "ja_JP";
case PSP_SYSTEMPARAM_LANGUAGE_FRENCH:
return "fr_FR";
case PSP_SYSTEMPARAM_LANGUAGE_GERMAN:
return "de_DE";
case PSP_SYSTEMPARAM_LANGUAGE_SPANISH:
return "es_ES";
case PSP_SYSTEMPARAM_LANGUAGE_ITALIAN:
return "it_IT";
case PSP_SYSTEMPARAM_LANGUAGE_PORTUGUESE:
return "pt_PT";
case PSP_SYSTEMPARAM_LANGUAGE_RUSSIAN:
return "ru_RU";
case PSP_SYSTEMPARAM_LANGUAGE_DUTCH:
return "nl_NL";
case PSP_SYSTEMPARAM_LANGUAGE_KOREAN:
return "ko_KR";
case PSP_SYSTEMPARAM_LANGUAGE_CHINESE_TRADITIONAL:
return "zh_TW";
case PSP_SYSTEMPARAM_LANGUAGE_CHINESE_SIMPLIFIED:
return "zh_CN";
}
}
static void check_variables(CoreParameter &coreParam)
{
bool isFastForwarding;
if (environ_cb(RETRO_ENVIRONMENT_GET_FASTFORWARDING, &isFastForwarding))
coreParam.fastForward = isFastForwarding;
bool updated = false;
if ( coreState != CoreState::CORE_POWERUP
&& environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated)
&& !updated)
return;
struct retro_variable var = {0};
std::string sTextureShaderName_prev;
int iInternalResolution_prev;
int iTexScalingType_prev;
int iTexScalingLevel_prev;
int iMultiSampleLevel_prev;
var.key = "ppsspp_language";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "Automatic"))
g_Config.iLanguage = -1;
else if (!strcmp(var.value, "English"))
g_Config.iLanguage = PSP_SYSTEMPARAM_LANGUAGE_ENGLISH;
else if (!strcmp(var.value, "Japanese"))
g_Config.iLanguage = PSP_SYSTEMPARAM_LANGUAGE_JAPANESE;
else if (!strcmp(var.value, "French"))
g_Config.iLanguage = PSP_SYSTEMPARAM_LANGUAGE_FRENCH;
else if (!strcmp(var.value, "Spanish"))
g_Config.iLanguage = PSP_SYSTEMPARAM_LANGUAGE_SPANISH;
else if (!strcmp(var.value, "German"))
g_Config.iLanguage = PSP_SYSTEMPARAM_LANGUAGE_GERMAN;
else if (!strcmp(var.value, "Italian"))
g_Config.iLanguage = PSP_SYSTEMPARAM_LANGUAGE_ITALIAN;
else if (!strcmp(var.value, "Dutch"))
g_Config.iLanguage = PSP_SYSTEMPARAM_LANGUAGE_DUTCH;
else if (!strcmp(var.value, "Portuguese"))
g_Config.iLanguage = PSP_SYSTEMPARAM_LANGUAGE_PORTUGUESE;
else if (!strcmp(var.value, "Russian"))
g_Config.iLanguage = PSP_SYSTEMPARAM_LANGUAGE_RUSSIAN;
else if (!strcmp(var.value, "Korean"))
g_Config.iLanguage = PSP_SYSTEMPARAM_LANGUAGE_KOREAN;
else if (!strcmp(var.value, "Chinese Traditional"))
g_Config.iLanguage = PSP_SYSTEMPARAM_LANGUAGE_CHINESE_TRADITIONAL;
else if (!strcmp(var.value, "Chinese Simplified"))
g_Config.iLanguage = PSP_SYSTEMPARAM_LANGUAGE_CHINESE_SIMPLIFIED;
}
#ifndef __EMSCRIPTEN__
var.key = "ppsspp_cpu_core";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "JIT"))
g_Config.iCpuCore = (int)CPUCore::JIT;
else if (!strcmp(var.value, "IR JIT"))
g_Config.iCpuCore = (int)CPUCore::IR_INTERPRETER;
else if (!strcmp(var.value, "Interpreter"))
g_Config.iCpuCore = (int)CPUCore::INTERPRETER;
}
if (System_GetPropertyBool(SYSPROP_CAN_JIT) == false && g_Config.iCpuCore == (int)CPUCore::JIT) {
// Just gonna force it to the IR interpreter on startup.
// We don't hide the option, but we make sure it's off on bootup. In case someone wants
// to experiment in future iOS versions or something...
g_Config.iCpuCore = (int)CPUCore::IR_INTERPRETER;
}
#else
g_Config.iCpuCore = (int)CPUCore::INTERPRETER;
#endif
var.key = "ppsspp_fast_memory";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bFastMemory = false;
else
g_Config.bFastMemory = true;
}
var.key = "ppsspp_ignore_bad_memory_access";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bIgnoreBadMemAccess = false;
else
g_Config.bIgnoreBadMemAccess = true;
}
var.key = "ppsspp_io_timing_method";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "Fast"))
g_Config.iIOTimingMethod = IOTIMING_FAST;
else if (!strcmp(var.value, "Host"))
g_Config.iIOTimingMethod = IOTIMING_HOST;
else if (!strcmp(var.value, "Simulate UMD delays"))
g_Config.iIOTimingMethod = IOTIMING_REALISTIC;
}
var.key = "ppsspp_force_lag_sync";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bForceLagSync = false;
else
g_Config.bForceLagSync = true;
}
var.key = "ppsspp_locked_cpu_speed";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
g_Config.iLockedCPUSpeed = atoi(var.value);
var.key = "ppsspp_cache_iso";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bCacheFullIsoInRam = false;
else
g_Config.bCacheFullIsoInRam = true;
}
var.key = "ppsspp_cheats";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bEnableCheats = false;
else
g_Config.bEnableCheats = true;
}
var.key = "ppsspp_psp_model";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "psp_1000"))
g_Config.iPSPModel = PSP_MODEL_FAT;
else if (!strcmp(var.value, "psp_2000_3000"))
g_Config.iPSPModel = PSP_MODEL_SLIM;
}
var.key = "ppsspp_button_preference";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "Cross"))
g_Config.iButtonPreference = PSP_SYSTEMPARAM_BUTTON_CROSS;
else if (!strcmp(var.value, "Circle"))
g_Config.iButtonPreference = PSP_SYSTEMPARAM_BUTTON_CIRCLE;
}
var.key = "ppsspp_analog_is_circular";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bAnalogIsCircular = false;
else
g_Config.bAnalogIsCircular = true;
}
var.key = "ppsspp_internal_resolution";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
iInternalResolution_prev = g_Config.iInternalResolution;
if (!strcmp(var.value, "480x272"))
g_Config.iInternalResolution = 1;
else if (!strcmp(var.value, "960x544"))
g_Config.iInternalResolution = 2;
else if (!strcmp(var.value, "1440x816"))
g_Config.iInternalResolution = 3;
else if (!strcmp(var.value, "1920x1088"))
g_Config.iInternalResolution = 4;
else if (!strcmp(var.value, "2400x1360"))
g_Config.iInternalResolution = 5;
else if (!strcmp(var.value, "2880x1632"))
g_Config.iInternalResolution = 6;
else if (!strcmp(var.value, "3360x1904"))
g_Config.iInternalResolution = 7;
else if (!strcmp(var.value, "3840x2176"))
g_Config.iInternalResolution = 8;
else if (!strcmp(var.value, "4320x2448"))
g_Config.iInternalResolution = 9;
else if (!strcmp(var.value, "4800x2720"))
g_Config.iInternalResolution = 10;
}
#if 0 // see issue #16786
var.key = "ppsspp_mulitsample_level";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
iMultiSampleLevel_prev = g_Config.iMultiSampleLevel;
if (!strcmp(var.value, "Disabled"))
g_Config.iMultiSampleLevel = 0;
else if (!strcmp(var.value, "x2"))
g_Config.iMultiSampleLevel = 1;
else if (!strcmp(var.value, "x4"))
g_Config.iMultiSampleLevel = 2;
else if (!strcmp(var.value, "x8"))
g_Config.iMultiSampleLevel = 3;
}
#endif
var.key = "ppsspp_skip_gpu_readbacks";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bSkipGPUReadbacks = false;
else
g_Config.bSkipGPUReadbacks = true;
}
var.key = "ppsspp_frameskip";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
g_Config.iFrameSkip = atoi(var.value);
var.key = "ppsspp_frameskiptype";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "Number of frames"))
g_Config.iFrameSkipType = 0;
else if (!strcmp(var.value, "Percent of FPS"))
g_Config.iFrameSkipType = 1;
}
var.key = "ppsspp_auto_frameskip";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bAutoFrameSkip = false;
else
g_Config.bAutoFrameSkip = true;
}
var.key = "ppsspp_frame_duplication";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bRenderDuplicateFrames = false;
else
g_Config.bRenderDuplicateFrames = true;
}
var.key = "ppsspp_detect_vsync_swap_interval";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
detectVsyncSwapInterval = false;
else
detectVsyncSwapInterval = true;
}
var.key = "ppsspp_inflight_frames";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "No buffer"))
g_Config.iInflightFrames = 0;
else if (!strcmp(var.value, "Up to 1"))
g_Config.iInflightFrames = 1;
else if (!strcmp(var.value, "Up to 2"))
g_Config.iInflightFrames = 2;
}
var.key = "ppsspp_gpu_hardware_transform";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bHardwareTransform = false;
else
g_Config.bHardwareTransform = true;
}
var.key = "ppsspp_software_skinning";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bSoftwareSkinning = false;
else
g_Config.bSoftwareSkinning = true;
}
var.key = "ppsspp_vertex_cache";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bVertexCache = false;
else
g_Config.bVertexCache = true;
}
var.key = "ppsspp_lazy_texture_caching";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bTextureBackoffCache = false;
else
g_Config.bTextureBackoffCache = true;
}
var.key = "ppsspp_spline_quality";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "Low"))
g_Config.iSplineBezierQuality = 0;
else if (!strcmp(var.value, "Medium"))
g_Config.iSplineBezierQuality = 1;
else if (!strcmp(var.value, "High"))
g_Config.iSplineBezierQuality = 2;
}
var.key = "ppsspp_hardware_tesselation";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bHardwareTessellation = false;
else
g_Config.bHardwareTessellation = true;
}
var.key = "ppsspp_lower_resolution_for_effects";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.iBloomHack = 0;
else if (!strcmp(var.value, "Safe"))
g_Config.iBloomHack = 1;
else if (!strcmp(var.value, "Balanced"))
g_Config.iBloomHack = 2;
else if (!strcmp(var.value, "Aggressive"))
g_Config.iBloomHack = 3;
}
var.key = "ppsspp_texture_scaling_type";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
iTexScalingType_prev = g_Config.iTexScalingType;
if (!strcmp(var.value, "xbrz"))
g_Config.iTexScalingType = TextureScalerCommon::XBRZ;
else if (!strcmp(var.value, "hybrid"))
g_Config.iTexScalingType = TextureScalerCommon::HYBRID;
else if (!strcmp(var.value, "bicubic"))
g_Config.iTexScalingType = TextureScalerCommon::BICUBIC;
else if (!strcmp(var.value, "hybrid_bicubic"))
g_Config.iTexScalingType = TextureScalerCommon::HYBRID_BICUBIC;
}
var.key = "ppsspp_texture_scaling_level";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
iTexScalingLevel_prev = g_Config.iTexScalingLevel;
if (!strcmp(var.value, "disabled"))
g_Config.iTexScalingLevel = 1;
else if (!strcmp(var.value, "2x"))
g_Config.iTexScalingLevel = 2;
else if (!strcmp(var.value, "3x"))
g_Config.iTexScalingLevel = 3;
else if (!strcmp(var.value, "4x"))
g_Config.iTexScalingLevel = 4;
else if (!strcmp(var.value, "5x"))
g_Config.iTexScalingLevel = 5;
}
var.key = "ppsspp_texture_deposterize";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bTexDeposterize = false;
else
g_Config.bTexDeposterize = true;
}
var.key = "ppsspp_texture_shader";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
sTextureShaderName_prev = g_Config.sTextureShaderName;
if (!strcmp(var.value, "disabled"))
g_Config.sTextureShaderName = "Off";
else if (!strcmp(var.value, "2xBRZ"))
g_Config.sTextureShaderName = "Tex2xBRZ";
else if (!strcmp(var.value, "4xBRZ"))
g_Config.sTextureShaderName = "Tex4xBRZ";
else if (!strcmp(var.value, "MMPX"))
g_Config.sTextureShaderName = "TexMMPX";
}
var.key = "ppsspp_texture_anisotropic_filtering";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.iAnisotropyLevel = 0;
else if (!strcmp(var.value, "2x"))
g_Config.iAnisotropyLevel = 1;
else if (!strcmp(var.value, "4x"))
g_Config.iAnisotropyLevel = 2;
else if (!strcmp(var.value, "8x"))
g_Config.iAnisotropyLevel = 3;
else if (!strcmp(var.value, "16x"))
g_Config.iAnisotropyLevel = 4;
}
var.key = "ppsspp_texture_filtering";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "Auto"))
g_Config.iTexFiltering = 1;
else if (!strcmp(var.value, "Nearest"))
g_Config.iTexFiltering = 2;
else if (!strcmp(var.value, "Linear"))
g_Config.iTexFiltering = 3;
else if (!strcmp(var.value, "Auto max quality"))
g_Config.iTexFiltering = 4;
}
var.key = "ppsspp_texture_replacement";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bReplaceTextures = false;
else
g_Config.bReplaceTextures = true;
}
var.key = "ppsspp_enable_wlan";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bEnableWlan = false;
else
g_Config.bEnableWlan = true;
}
var.key = "ppsspp_wlan_channel";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
g_Config.iWlanAdhocChannel = atoi(var.value);
var.key = "ppsspp_enable_builtin_pro_ad_hoc_server";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bEnableAdhocServer = false;
else
g_Config.bEnableAdhocServer = true;
}
var.key = "ppsspp_change_pro_ad_hoc_server_address";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
changeProAdhocServer = var.value;
var.key = "ppsspp_enable_upnp";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bEnableUPnP = false;
else
g_Config.bEnableUPnP = true;
}
var.key = "ppsspp_upnp_use_original_port";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bUPnPUseOriginalPort = false;
else
g_Config.bUPnPUseOriginalPort = true;
}
var.key = "ppsspp_port_offset";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
g_Config.iPortOffset = atoi(var.value);
var.key = "ppsspp_minimum_timeout";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
g_Config.iMinTimeout = atoi(var.value);
var.key = "ppsspp_forced_first_connect";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bForcedFirstConnect = false;
else
g_Config.bForcedFirstConnect = true;
}
std::string ppsspp_change_mac_address[12];
int ppsspp_pro_ad_hoc_ipv4[12];
char key[64] = {0};
var.key = key;
g_Config.sMACAddress = "";
g_Config.proAdhocServer = "";
for (int i = 0; i < 12; i++)
{
snprintf(key, sizeof(key), "ppsspp_change_mac_address%02d", i + 1);
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
ppsspp_change_mac_address[i] = var.value;
if (i && i % 2 == 0)
g_Config.sMACAddress += ":";
g_Config.sMACAddress += ppsspp_change_mac_address[i];
}
snprintf(key, sizeof(key), "ppsspp_pro_ad_hoc_server_address%02d", i + 1);
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
ppsspp_pro_ad_hoc_ipv4[i] = atoi(var.value);
}
if (g_Config.sMACAddress == "00:00:00:00:00:00")
{
g_Config.sMACAddress = CreateRandMAC();
for (int i = 0; i < 12; i++)
{
snprintf(key, sizeof(key), "ppsspp_change_mac_address%02d", i + 1);
std::string digit = {g_Config.sMACAddress[i + i / 2]};
var.value = digit.c_str();
environ_cb(RETRO_ENVIRONMENT_SET_VARIABLE, &var);
}
}
if (changeProAdhocServer == "IP address")
{
g_Config.proAdhocServer = "";
bool leadingZero = true;
for (int i = 0; i < 12; i++)
{
if (i && i % 3 == 0)
{
g_Config.proAdhocServer += '.';
leadingZero = true;
}
int addressPt = ppsspp_pro_ad_hoc_ipv4[i];
if (addressPt || i % 3 == 2)
leadingZero = false; // We are either non-zero or the last digit of a byte
if (! leadingZero)
g_Config.proAdhocServer += static_cast<char>('0' + addressPt);
}
}
else
g_Config.proAdhocServer = changeProAdhocServer;
g_Config.bTexHardwareScaling = g_Config.sTextureShaderName != "Off";
if (gpu && (g_Config.iTexScalingType != iTexScalingType_prev
|| g_Config.iTexScalingLevel != iTexScalingLevel_prev
|| g_Config.sTextureShaderName != sTextureShaderName_prev))
{
gpu->NotifyConfigChanged();
}
if (g_Config.iLanguage < 0)
g_Config.iLanguage = get_language_auto();
g_Config.sLanguageIni = map_psp_language_to_i18n_locale(g_Config.iLanguage);
g_i18nrepo.LoadIni(g_Config.sLanguageIni);
// Cannot detect refresh rate changes if:
// > Frame skipping is enabled
// > Frame duplication is enabled
detectVsyncSwapInterval &=
!g_Config.bAutoFrameSkip &&
(g_Config.iFrameSkip == 0) &&
!g_Config.bRenderDuplicateFrames;
bool updateAvInfo = false;
if (!detectVsyncSwapInterval && (vsyncSwapInterval != 1))
{
vsyncSwapInterval = 1;
updateAvInfo = true;
}
if (g_Config.iInternalResolution != iInternalResolution_prev && !PSP_IsInited())
{
coreParam.pixelWidth = coreParam.renderWidth = g_Config.iInternalResolution * 480;
coreParam.pixelHeight = coreParam.renderHeight = g_Config.iInternalResolution * 272;
if (gpu)
{
retro_system_av_info avInfo;
retro_get_system_av_info(&avInfo);
environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &avInfo);
updateAvInfo = false;
gpu->NotifyDisplayResized();
}
}
#if 0 // see issue #16786
if (g_Config.iMultiSampleLevel != iMultiSampleLevel_prev && PSP_IsInited())
{
if (gpu)
{
gpu->NotifyRenderResized();
}
}
#endif
if (updateAvInfo)
{
retro_system_av_info avInfo;
retro_get_system_av_info(&avInfo);
environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &avInfo);
}
set_variable_visibility();
}
void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) { audio_batch_cb = cb; }
void retro_set_audio_sample(retro_audio_sample_t cb) { (void)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; }
void retro_init(void)
{
VsyncSwapIntervalReset();
g_threadManager.Init(cpu_info.num_cores, cpu_info.logical_cpu_count);
struct retro_input_descriptor desc[] = {
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "D-Pad Left" },
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "D-Pad Up" },
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "D-Pad Down" },
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "D-Pad Right" },
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "Cross" },
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "Circle" },
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Triangle" },
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Square" },
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L" },
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "R" },
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" },
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" },
{ 0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X, "Right Analog X" },
{ 0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y, "Right Analog Y" },
{ 0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X, "Left Analog X" },
{ 0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y, "Left Analog Y" },
{ 0 },
};
environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc);
if (environ_cb(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, NULL))
libretro_supports_bitmasks = true;
struct retro_log_callback log;
if (environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log))
{
LogManager::Init(&g_Config.bEnableLogging);
printfLogger = new PrintfLogger(log);
LogManager* logman = LogManager::GetInstance();
logman->RemoveListener(logman->GetConsoleListener());
logman->RemoveListener(logman->GetDebuggerListener());
logman->ChangeFileLog(nullptr);
logman->AddListener(printfLogger);
logman->SetAllLogLevels(LogLevel::LINFO);
}
g_Config.Load("", "");
g_Config.iInternalResolution = 0;
const char* nickname = NULL;
if (environ_cb(RETRO_ENVIRONMENT_GET_USERNAME, &nickname) && nickname)
g_Config.sNickName = std::string(nickname);
Path retro_base_dir;
Path retro_save_dir;
const char* dir_ptr = NULL;
if (environ_cb(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &dir_ptr) && dir_ptr)
retro_base_dir = Path(dir_ptr);
if (environ_cb(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY, &dir_ptr) && dir_ptr)
retro_save_dir = Path(dir_ptr);
retro_base_dir /= "PPSSPP";
g_Config.currentDirectory = retro_base_dir;
g_Config.defaultCurrentDirectory = retro_base_dir;
g_Config.memStickDirectory = retro_save_dir;
g_Config.flash0Directory = retro_base_dir / "flash0";
g_Config.internalDataDirectory = retro_base_dir;
g_Config.bEnableNetworkChat = false;
g_Config.bDiscordPresence = false;
g_VFS.Register("", new DirectoryReader(retro_base_dir));
}
void retro_deinit(void)
{
g_threadManager.Teardown();
LogManager::Shutdown();
delete printfLogger;
printfLogger = nullptr;
libretro_supports_bitmasks = false;
libretro_supports_option_categories = false;
VsyncSwapIntervalReset();
}
void retro_set_controller_port_device(unsigned port, unsigned device)
{
(void)port;
(void)device;
}
void retro_get_system_info(struct retro_system_info *info)
{
*info = {};
info->library_name = "PPSSPP";
info->library_version = PPSSPP_GIT_VERSION;
info->need_fullpath = true;
info->valid_extensions = "elf|iso|cso|prx|pbp";
}
void retro_get_system_av_info(struct retro_system_av_info *info)
{
*info = {};
info->timing.fps = (60.0 / 1.001) / (double)vsyncSwapInterval;
info->timing.sample_rate = SAMPLERATE;
info->geometry.base_width = g_Config.iInternalResolution * 480;
info->geometry.base_height = g_Config.iInternalResolution * 272;
info->geometry.max_width = g_Config.iInternalResolution * 480;
info->geometry.max_height = g_Config.iInternalResolution * 272;
info->geometry.aspect_ratio = 480.0 / 272.0; // Not 16:9! But very, very close.
}
unsigned retro_api_version(void) { return RETRO_API_VERSION; }
namespace Libretro
{
bool useEmuThread = false;
std::atomic<EmuThreadState> emuThreadState(EmuThreadState::DISABLED);
static std::thread emuThread;
static void EmuFrame()
{
ctx->SetRenderTarget();
if (ctx->GetDrawContext())
ctx->GetDrawContext()->BeginFrame(Draw::DebugFlags::NONE);
gpu->BeginHostFrame();
coreState = CORE_RUNNING;
PSP_RunLoopUntil(UINT64_MAX);
gpu->EndHostFrame();
if (ctx->GetDrawContext()) {
ctx->GetDrawContext()->EndFrame();
ctx->GetDrawContext()->Present(Draw::PresentMode::FIFO, 1);
}
}
static void EmuThreadFunc()
{
SetCurrentThreadName("Emu");
for (;;)
{
switch ((EmuThreadState)emuThreadState)
{
case EmuThreadState::START_REQUESTED:
emuThreadState = EmuThreadState::RUNNING;
/* fallthrough */
case EmuThreadState::RUNNING:
EmuFrame();
break;
case EmuThreadState::PAUSE_REQUESTED:
emuThreadState = EmuThreadState::PAUSED;
/* fallthrough */
case EmuThreadState::PAUSED:
sleep_ms(1);
break;
default:
case EmuThreadState::QUIT_REQUESTED:
emuThreadState = EmuThreadState::STOPPED;
ctx->StopThread();
return;
}
}
}
void EmuThreadStart()
{
bool wasPaused = emuThreadState == EmuThreadState::PAUSED;
emuThreadState = EmuThreadState::START_REQUESTED;
if (!wasPaused)
{
ctx->ThreadStart();
emuThread = std::thread(&EmuThreadFunc);
}
}
void EmuThreadStop()
{
if (emuThreadState != EmuThreadState::RUNNING)
return;
emuThreadState = EmuThreadState::QUIT_REQUESTED;
// Need to keep eating frames to allow the EmuThread to exit correctly.
while (ctx->ThreadFrame())
;
emuThread.join();
emuThread = std::thread();
ctx->ThreadEnd();
}
void EmuThreadPause()
{
if (emuThreadState != EmuThreadState::RUNNING)
return;
emuThreadState = EmuThreadState::PAUSE_REQUESTED;
ctx->ThreadFrame(); // Eat 1 frame
while (emuThreadState != EmuThreadState::PAUSED)
sleep_ms(1);
}
} // namespace Libretro
bool retro_load_game(const struct retro_game_info *game)
{
retro_pixel_format fmt = retro_pixel_format::RETRO_PIXEL_FORMAT_XRGB8888;
if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt))
{
ERROR_LOG(SYSTEM, "XRGB8888 is not supported.\n");
return false;
}
coreState = CORE_POWERUP;
ctx = LibretroGraphicsContext::CreateGraphicsContext();
INFO_LOG(SYSTEM, "Using %s backend", ctx->Ident());
Core_SetGraphicsContext(ctx);
SetGPUBackend((GPUBackend)g_Config.iGPUBackend);
useEmuThread = ctx->GetGPUCore() == GPUCORE_GLES;
// default to interpreter to allow startup in platforms w/o JIT capability
g_Config.iCpuCore = (int)CPUCore::INTERPRETER;
CoreParameter coreParam = {};
coreParam.enableSound = true;
coreParam.fileToStart = Path(std::string(game->path));
coreParam.mountIso.clear();
coreParam.startBreak = false;
coreParam.headLess = true;
coreParam.graphicsContext = ctx;
coreParam.gpuCore = ctx->GetGPUCore();
check_variables(coreParam);
// set cpuCore from libretro setting variable
coreParam.cpuCore = (CPUCore)g_Config.iCpuCore;
std::string error_string;
if (!PSP_InitStart(coreParam, &error_string))
{
ERROR_LOG(BOOT, "%s", error_string.c_str());
return false;
}
struct retro_core_option_display option_display;
// Show/hide 'MSAA' and 'Texture Shader' options, Vulkan only
option_display.visible = (g_Config.iGPUBackend == (int)GPUBackend::VULKAN) ? true : false;
#if 0 // see issue #16786
option_display.key = "ppsspp_mulitsample_level";
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
#endif
option_display.key = "ppsspp_texture_shader";
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
// Show/hide 'Buffered Frames' option, Vulkan/GL only
option_display.visible = (g_Config.iGPUBackend == (int)GPUBackend::VULKAN ||
g_Config.iGPUBackend == (int)GPUBackend::OPENGL) ? true : false;
option_display.key = "ppsspp_inflight_frames";
environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &option_display);
set_variable_visibility();
return true;
}
void retro_unload_game(void)
{
if (Libretro::useEmuThread)
Libretro::EmuThreadStop();
PSP_Shutdown();
g_VFS.Clear();
delete ctx;
ctx = nullptr;
PSP_CoreParameter().graphicsContext = nullptr;
}
void retro_reset(void)
{
std::string error_string;
PSP_Shutdown();
if (!PSP_Init(PSP_CoreParameter(), &error_string))
{
ERROR_LOG(BOOT, "%s", error_string.c_str());
environ_cb(RETRO_ENVIRONMENT_SHUTDOWN, nullptr);
}
}
static void retro_input(void)
{
unsigned i;
int16_t ret = 0;
// clang-format off
static struct
{
u32 retro;
u32 sceCtrl;
} map[] = {
{ RETRO_DEVICE_ID_JOYPAD_UP, CTRL_UP },
{ RETRO_DEVICE_ID_JOYPAD_DOWN, CTRL_DOWN },
{ RETRO_DEVICE_ID_JOYPAD_LEFT, CTRL_LEFT },
{ RETRO_DEVICE_ID_JOYPAD_RIGHT, CTRL_RIGHT },
{ RETRO_DEVICE_ID_JOYPAD_X, CTRL_TRIANGLE },
{ RETRO_DEVICE_ID_JOYPAD_A, CTRL_CIRCLE },
{ RETRO_DEVICE_ID_JOYPAD_B, CTRL_CROSS },
{ RETRO_DEVICE_ID_JOYPAD_Y, CTRL_SQUARE },
{ RETRO_DEVICE_ID_JOYPAD_L, CTRL_LTRIGGER },
{ RETRO_DEVICE_ID_JOYPAD_R, CTRL_RTRIGGER },
{ RETRO_DEVICE_ID_JOYPAD_START, CTRL_START },
{ RETRO_DEVICE_ID_JOYPAD_SELECT, CTRL_SELECT },
};
// clang-format on
input_poll_cb();
if (libretro_supports_bitmasks)
ret = input_state_cb(0, RETRO_DEVICE_JOYPAD,
0, RETRO_DEVICE_ID_JOYPAD_MASK);
else
{
for (i = RETRO_DEVICE_ID_JOYPAD_B; i <= RETRO_DEVICE_ID_JOYPAD_R; i++)
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, i))
ret |= (1 << i);
}
for (i = 0; i < sizeof(map) / sizeof(*map); i++)
{
bool pressed = ret & (1 << map[i].retro);
if (pressed)
{
__CtrlUpdateButtons(map[i].sceCtrl, 0);
}
else
{
__CtrlUpdateButtons(0, map[i].sceCtrl);
}
}
float x_left = input_state_cb(0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X) / 32767.0f;
float y_left = input_state_cb(0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y) / -32767.0f;
float x_right = input_state_cb(0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X) / 32767.0f;
float y_right = input_state_cb(0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y) / -32767.0f;
__CtrlSetAnalogXY(CTRL_STICK_LEFT, x_left, y_left);
__CtrlSetAnalogXY(CTRL_STICK_RIGHT, x_right, y_right);
// Analog circle vs square gate compensation
// copied from ControlMapper.cpp's ConvertAnalogStick function
const bool isCircular = g_Config.bAnalogIsCircular;
float norm = std::max(fabsf(x_left), fabsf(y_left));
if (norm == 0.0f)
return;
if (isCircular) {
float newNorm = sqrtf(x_left * x_left + y_left * y_left);
float factor = newNorm / norm;
x_left *= factor;
y_left *= factor;
norm = newNorm;
}
float mappedNorm = norm;
x_left = std::clamp(x_left / norm * mappedNorm, -1.0f, 1.0f);
y_left = std::clamp(y_left / norm * mappedNorm, -1.0f, 1.0f);
__CtrlSetAnalogXY(CTRL_STICK_LEFT, x_left, y_left);
__CtrlSetAnalogXY(CTRL_STICK_RIGHT, x_right, y_right);
}
void retro_run(void)
{
if (PSP_IsIniting())
{
std::string error_string;
while (!PSP_InitUpdate(&error_string))
sleep_ms(4);
if (!PSP_IsInited())
{
ERROR_LOG(BOOT, "%s", error_string.c_str());
environ_cb(RETRO_ENVIRONMENT_SHUTDOWN, nullptr);
return;
}
}
check_variables(PSP_CoreParameter());
retro_input();
if (useEmuThread)
{
if( emuThreadState == EmuThreadState::PAUSED ||
emuThreadState == EmuThreadState::PAUSE_REQUESTED)
{
VsyncSwapIntervalDetect();
ctx->SwapBuffers();
return;
}
if (emuThreadState != EmuThreadState::RUNNING)
EmuThreadStart();
if (!ctx->ThreadFrame())
{
VsyncSwapIntervalDetect();
return;
}
}
else
EmuFrame();
VsyncSwapIntervalDetect();
ctx->SwapBuffers();
}
unsigned retro_get_region(void) { return RETRO_REGION_NTSC; }
bool retro_load_game_special(unsigned type, const struct retro_game_info *info, size_t num) { return false; }
namespace SaveState
{
struct SaveStart
{
void DoState(PointerWrap &p);
};
} // namespace SaveState
size_t retro_serialize_size(void)
{
if(!gpu) { // The HW renderer isn't ready on first pass.
return 134217728; // 128MB ought to be enough for anybody.
}
SaveState::SaveStart state;
// TODO: Libretro API extension to use the savestate queue
if (useEmuThread)
EmuThreadPause();
return (CChunkFileReader::MeasurePtr(state) + 0x800000) & ~0x7FFFFF;
// We don't unpause intentionally
}
bool retro_serialize(void *data, size_t size)
{
if(!gpu) { // The HW renderer isn't ready on first pass.
return false;
}
// TODO: Libretro API extension to use the savestate queue
if (useEmuThread)
EmuThreadPause(); // Does nothing if already paused
size_t measuredSize;
SaveState::SaveStart state;
auto err = CChunkFileReader::MeasureAndSavePtr(state, (u8 **)&data, &measuredSize);
bool retVal = err == CChunkFileReader::ERROR_NONE;
if (useEmuThread)
{
EmuThreadStart();
sleep_ms(4);
}
return retVal;
}
bool retro_unserialize(const void *data, size_t size)
{
// TODO: Libretro API extension to use the savestate queue
if (useEmuThread)
EmuThreadPause(); // Does nothing if already paused
std::string errorString;
SaveState::SaveStart state;
bool retVal = CChunkFileReader::LoadPtr((u8 *)data, state, &errorString)
== CChunkFileReader::ERROR_NONE;
if (useEmuThread)
{
EmuThreadStart();
sleep_ms(4);
}
return retVal;
}
void *retro_get_memory_data(unsigned id)
{
if ( id == RETRO_MEMORY_SYSTEM_RAM )
return Memory::GetPointerWriteUnchecked(PSP_GetKernelMemoryBase()) ;
return NULL;
}
size_t retro_get_memory_size(unsigned id)
{
if ( id == RETRO_MEMORY_SYSTEM_RAM )
return Memory::g_MemorySize ;
return 0;
}
void retro_cheat_reset(void) {
// Init Cheat Engine
CWCheatEngine *cheatEngine = new CWCheatEngine(g_paramSFO.GetDiscID());
Path file=cheatEngine->CheatFilename();
// Output cheats to cheat file
std::ofstream outFile;
outFile.open(file.c_str());
outFile << "_S " << g_paramSFO.GetDiscID() << std::endl;
outFile.close();
g_Config.bReloadCheats = true;
// Parse and Run the Cheats
cheatEngine->ParseCheats();
if (cheatEngine->HasCheats()) {
cheatEngine->Run();
}
}
void retro_cheat_set(unsigned index, bool enabled, const char *code) {
// Initialize Cheat Engine
CWCheatEngine *cheatEngine = new CWCheatEngine(g_paramSFO.GetDiscID());
cheatEngine->CreateCheatFile();
Path file=cheatEngine->CheatFilename();
// Read cheats file
std::vector<std::string> cheats;
std::ifstream cheat_content(file.c_str());
std::stringstream buffer;
buffer << cheat_content.rdbuf();
std::string existing_cheats=ReplaceAll(buffer.str(), std::string("\n_C"), std::string("|"));
SplitString(existing_cheats, '|', cheats);
// Generate Cheat String
std::stringstream cheat("");
cheat << (enabled ? "1 " : "0 ") << index << std::endl;
std::string code_str(code);
std::vector<std::string> codes;
code_str=ReplaceAll(code_str, std::string(" "), std::string("+"));
SplitString(code_str, '+', codes);
int part=0;
for (int i=0; i < codes.size(); i++) {
if (codes[i].size() <= 2) {
// _L _M ..etc
// Assume _L
} else if (part == 0) {
cheat << "_L " << codes[i] << " ";
part++;
} else {
cheat << codes[i] << std::endl;
part=0;
}
}
// Add or Replace the Cheat
if (index + 1 < cheats.size()) {
cheats[index + 1]=cheat.str();
} else {
cheats.push_back(cheat.str());
}
// Output cheats to cheat file
std::ofstream outFile;
outFile.open(file.c_str());
outFile << "_S " << g_paramSFO.GetDiscID() << std::endl;
for (int i=1; i < cheats.size(); i++) {
outFile << "_C" << cheats[i] << std::endl;
}
outFile.close();
g_Config.bReloadCheats = true;
// Parse and Run the Cheats
cheatEngine->ParseCheats();
if (cheatEngine->HasCheats()) {
cheatEngine->Run();
}
}
int System_GetPropertyInt(SystemProperty prop)
{
switch (prop)
{
case SYSPROP_AUDIO_SAMPLE_RATE:
return SAMPLERATE;
#if PPSSPP_PLATFORM(ANDROID)
case SYSPROP_SYSTEMVERSION: {
char sdk[PROP_VALUE_MAX] = {0};
if (__system_property_get("ro.build.version.sdk", sdk) != 0) {
return atoi(sdk);
}
return -1;
}
#endif
default:
break;
}
return -1;
}
float System_GetPropertyFloat(SystemProperty prop)
{
switch (prop)
{
case SYSPROP_DISPLAY_REFRESH_RATE:
// Have to lie here and report 60 Hz instead
// of (60.0 / 1.001), otherwise the internal
// stereo resampler will output at the wrong
// frequency...
return 60.0f;
case SYSPROP_DISPLAY_SAFE_INSET_LEFT:
case SYSPROP_DISPLAY_SAFE_INSET_RIGHT:
case SYSPROP_DISPLAY_SAFE_INSET_TOP:
case SYSPROP_DISPLAY_SAFE_INSET_BOTTOM:
return 0.0f;
default:
break;
}
return -1;
}
bool System_GetPropertyBool(SystemProperty prop)
{
switch (prop)
{
case SYSPROP_CAN_JIT:
#if PPSSPP_PLATFORM(IOS)
bool can_jit;
return (environ_cb(RETRO_ENVIRONMENT_GET_JIT_CAPABLE, &can_jit) && can_jit);
#else
return true;
#endif
default:
return false;
}
}
std::string System_GetProperty(SystemProperty prop) { return ""; }
std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) { return std::vector<std::string>(); }
void System_Notify(SystemNotification notification) {
switch (notification) {
default:
break;
}
}
bool System_MakeRequest(SystemRequestType type, int requestId, const std::string &param1, const std::string &param2, int param3) { return false; }
void System_PostUIMessage(UIMessage message, const std::string &param) {}
void NativeFrame(GraphicsContext *graphicsContext) {}
void NativeResized() {}
void System_Toast(const char *str) {}
inline int16_t Clamp16(int32_t sample) {
if (sample < -32767) return -32767;
if (sample > 32767) return 32767;
return sample;
}
void System_AudioPushSamples(const int32_t *audio, int numSamples) {
// Convert to 16-bit audio for further processing.
int16_t buffer[1024 * 2];
while (numSamples > 0) {
int blockSize = std::min(1024, numSamples);
for (int i = 0; i < blockSize; i++) {
buffer[i * 2] = Clamp16(audio[i * 2]);
buffer[i * 2 + 1] = Clamp16(audio[i * 2 + 1]);
}
int framesToWrite = blockSize;
uint32_t framesWritten = audio_batch_cb(buffer, framesToWrite);
if (framesWritten != framesToWrite) {
// Let's just stop, and eat any left-over samples.
break;
}
numSamples -= blockSize;
}
}
void System_AudioGetDebugStats(char *buf, size_t bufSize) { if (buf) buf[0] = '\0'; }
void System_AudioClear() {}
#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS)
std::vector<std::string> System_GetCameraDeviceList() { return std::vector<std::string>(); }
bool System_AudioRecordingIsAvailable() { return false; }
bool System_AudioRecordingState() { return false; }
void System_InputBoxGetString(const std::string &title, const std::string &defaultValue, std::function<void(bool, const std::string &)> cb) { cb(false, ""); }
#endif
// TODO: To avoid having to define these here, these should probably be turned into system "requests".
bool NativeSaveSecret(const char *nameOfSecret, const std::string &data) { return false; }
std::string NativeLoadSecret(const char *nameOfSecret) {
return "";
}