ppsspp/libretro/libretro.cpp
Henrik Rydgård 349b73acec
Move the resampler usage to a common file, AudioCommon. (#17176)
* Move the resampler usage to a common file, AudioCommon.

Ports that don't want to use the resampler can now simply exclude that
file and provide their own implementation. Next up, libretro will be
converted to do it that way.

* Android.mk typo

* libretro makefile fix

* libretro buildfix

* libretro: try a different approach for the temporary solution

* duh

* double duh
2023-03-24 17:19:57 +01:00

1922 lines
59 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/Host.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
#define AUDIO_RING_BUFFER_SIZE (1 << 16)
#define AUDIO_RING_BUFFER_SIZE_MASK (AUDIO_RING_BUFFER_SIZE - 1)
// An alpha factor of 1/180 is *somewhat* equivalent
// to calculating the average for the last 180
// frames, or 3 seconds of runtime...
#define AUDIO_FRAMES_MOVING_AVG_ALPHA (1.0f / 180.0f)
// 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;
} // namespace Libretro
namespace Libretro
{
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
namespace Libretro
{
static std::mutex audioSampleLock_;
static int16_t audioRingBuffer[AUDIO_RING_BUFFER_SIZE] = {0};
static uint32_t audioRingBufferBase = 0;
static uint32_t audioRingBufferIndex = 0;
static int16_t *audioOutBuffer = NULL;
static uint32_t audioOutBufferSize = 0;
static float audioOutFramesAvg = 0.0f;
// Set this to an arbitrarily large value,
// it will be fine tuned in AudioUploadSamples()
static uint32_t audioBatchFramesMax = AUDIO_RING_BUFFER_SIZE >> 1;
static void AudioBufferFlush()
{
const std::lock_guard<std::mutex> lock(audioSampleLock_);
audioRingBufferBase = 0;
audioRingBufferIndex = 0;
audioOutFramesAvg = (float)SAMPLERATE / (60.0f / 1.001f);
}
static void AudioBufferInit()
{
audioOutFramesAvg = (float)SAMPLERATE / (60.0f / 1.001f);
audioOutBufferSize = ((uint32_t)audioOutFramesAvg + 1) * 2;
audioOutBuffer = (int16_t *)malloc(audioOutBufferSize * sizeof(int16_t));
audioBatchFramesMax = AUDIO_RING_BUFFER_SIZE >> 1;
AudioBufferFlush();
}
static void AudioBufferDeinit()
{
if (audioOutBuffer)
free(audioOutBuffer);
audioOutBuffer = NULL;
audioOutBufferSize = 0;
audioOutFramesAvg = 0.0f;
audioBatchFramesMax = AUDIO_RING_BUFFER_SIZE >> 1;
AudioBufferFlush();
}
static uint32_t AudioBufferOccupancy()
{
const std::lock_guard<std::mutex> lock(audioSampleLock_);
uint32_t occupancy = (audioRingBufferIndex - audioRingBufferBase) &
AUDIO_RING_BUFFER_SIZE_MASK;
return occupancy >> 1;
}
static void AudioBufferWrite(int16_t *audio, uint32_t frames)
{
const std::lock_guard<std::mutex> lock(audioSampleLock_);
uint32_t frameIndex;
uint32_t bufferIndex = audioRingBufferIndex;
for (frameIndex = 0; frameIndex < frames; frameIndex++)
{
audioRingBuffer[audioRingBufferIndex] = *(audio++);
audioRingBuffer[audioRingBufferIndex + 1] = *(audio++);
audioRingBufferIndex = (audioRingBufferIndex + 2) % AUDIO_RING_BUFFER_SIZE;
}
}
static uint32_t AudioBufferRead(int16_t *audio, uint32_t frames)
{
const std::lock_guard<std::mutex> lock(audioSampleLock_);
uint32_t framesAvailable = ((audioRingBufferIndex - audioRingBufferBase) &
AUDIO_RING_BUFFER_SIZE_MASK) >> 1;
uint32_t frameIndex;
if (frames > framesAvailable)
frames = framesAvailable;
for(frameIndex = 0; frameIndex < frames; frameIndex++)
{
uint32_t bufferIndex = (audioRingBufferBase + (frameIndex << 1)) &
AUDIO_RING_BUFFER_SIZE_MASK;
*(audio++) = audioRingBuffer[bufferIndex];
*(audio++) = audioRingBuffer[bufferIndex + 1];
}
audioRingBufferBase += frames << 1;
audioRingBufferBase &= AUDIO_RING_BUFFER_SIZE_MASK;
return frames;
}
static void AudioUploadSamples()
{
// - If 'Detect Frame Rate Changes' is disabled, then
// the core specifies a fixed frame rate of (60.0f / 1.001f)
// - At the audio sample rate of 44100, this means the
// frontend expects exactly 735.735 sample frames per call of
// retro_run()
// - If g_Config.bRenderDuplicateFrames is enabled and
// frameskip is disabled, the mean of the buffer occupancy
// willapproximate to this value in most cases
uint32_t framesAvailable = AudioBufferOccupancy();
if (framesAvailable > 0)
{
// Update 'running average' of buffer occupancy.
// Note that this is not a true running
// average, but just a leaky-integrator/
// exponential moving average, used because
// it is simple and fast (i.e. requires no
// window of samples).
audioOutFramesAvg = (AUDIO_FRAMES_MOVING_AVG_ALPHA * (float)framesAvailable) +
((1.0f - AUDIO_FRAMES_MOVING_AVG_ALPHA) * audioOutFramesAvg);
uint32_t frames = (uint32_t)audioOutFramesAvg;
if (audioOutBufferSize < (frames << 1))
{
audioOutBufferSize = (frames << 1);
audioOutBuffer = (int16_t *)realloc(audioOutBuffer,
audioOutBufferSize * sizeof(int16_t));
}
frames = AudioBufferRead(audioOutBuffer, frames);
int16_t *audioOutBufferPtr = audioOutBuffer;
while (frames > 0)
{
uint32_t framesToWrite = (frames > audioBatchFramesMax) ?
audioBatchFramesMax : frames;
uint32_t framesWritten = audio_batch_cb(audioOutBufferPtr,
framesToWrite);
if ((framesWritten < framesToWrite) &&
(framesWritten > 0))
audioBatchFramesMax = framesWritten;
frames -= framesToWrite;
audioOutBufferPtr += framesToWrite << 1;
}
}
}
} // namespace Libretro
using namespace Libretro;
class LibretroHost : public Host
{
public:
LibretroHost() {}
void UpdateSound() override
{
const int hostAttemptBlockSize = 512;
static int16_t audio[hostAttemptBlockSize * 2];
int samples = __AudioMix(audio, hostAttemptBlockSize, SAMPLERATE);
AudioBufferWrite(audio, samples);
}
bool AttemptLoadSymbolMap() override { return false; }
};
class PrintfLogger : public LogListener
{
public:
PrintfLogger(retro_log_callback log) : log_(log.log) {}
void Log(const LogMessage &message)
{
switch (message.level)
{
case LogTypes::LVERBOSE:
case LogTypes::LDEBUG:
log_(RETRO_LOG_DEBUG, "[%s] %s",
message.log, message.msg.c_str());
break;
case LogTypes::LERROR:
log_(RETRO_LOG_ERROR, "[%s] %s",
message.log, message.msg.c_str());
break;
case LogTypes::LNOTICE:
case LogTypes::LWARNING:
log_(RETRO_LOG_WARN, "[%s] %s",
message.log, message.msg.c_str());
break;
case LogTypes::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 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;
}
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_JIT;
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_JIT;
}
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;
}
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;
}
var.key = "ppsspp_skip_buffer_effects";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
g_Config.bSkipBufferEffects = false;
else
g_Config.bSkipBufferEffects = true;
}
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);
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 (g_Config.iMultiSampleLevel != iMultiSampleLevel_prev && PSP_IsInited())
{
if (gpu)
{
gpu->NotifyRenderResized();
}
}
if (updateAvInfo)
{
retro_system_av_info avInfo;
retro_get_system_av_info(&avInfo);
environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &avInfo);
}
bool isFastForwarding = environ_cb(RETRO_ENVIRONMENT_GET_FASTFORWARDING, &isFastForwarding);
coreParam.fastForward = isFastForwarding;
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();
AudioBufferInit();
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(LogTypes::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));
host = new LibretroHost();
}
void retro_deinit(void)
{
g_threadManager.Teardown();
LogManager::Shutdown();
delete printfLogger;
printfLogger = nullptr;
delete host;
host = nullptr;
libretro_supports_bitmasks = false;
libretro_supports_option_categories = false;
VsyncSwapIntervalReset();
AudioBufferDeinit();
}
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();
gpu->BeginHostFrame();
coreState = CORE_RUNNING;
PSP_RunLoopUntil(UINT64_MAX);
gpu->EndHostFrame();
if (ctx->GetDrawContext())
ctx->GetDrawContext()->EndFrame();
}
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())
AudioBufferFlush();
emuThread.join();
emuThread = std::thread();
ctx->ThreadEnd();
}
void EmuThreadPause()
{
if (emuThreadState != EmuThreadState::RUNNING)
return;
emuThreadState = EmuThreadState::PAUSE_REQUESTED;
ctx->ThreadFrame(); // Eat 1 frame
AudioBufferFlush();
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.printfEmuLog = true;
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;
}
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)
{
__CtrlButtonDown(map[i].sceCtrl);
}
else
{
__CtrlButtonUp(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();
AudioUploadSamples();
ctx->SwapBuffers();
return;
}
if (emuThreadState != EmuThreadState::RUNNING)
EmuThreadStart();
if (!ctx->ThreadFrame())
{
VsyncSwapIntervalDetect();
AudioUploadSamples();
return;
}
}
else
EmuFrame();
VsyncSwapIntervalDetect();
AudioUploadSamples();
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;
}
bool retVal;
SaveState::SaveStart state;
// TODO: Libretro API extension to use the savestate queue
if (useEmuThread)
EmuThreadPause(); // Does nothing if already paused
size_t measuredSize;
auto err = CChunkFileReader::MeasureAndSavePtr(state, (u8 **)&data, &measuredSize);
retVal = err == CChunkFileReader::ERROR_NONE;
if (useEmuThread)
{
EmuThreadStart();
sleep_ms(4);
}
AudioBufferFlush();
return retVal;
}
bool retro_unserialize(const void *data, size_t size)
{
bool retVal;
SaveState::SaveStart state;
// TODO: Libretro API extension to use the savestate queue
if (useEmuThread)
EmuThreadPause(); // Does nothing if already paused
std::string errorString;
retVal = CChunkFileReader::LoadPtr((u8 *)data, state, &errorString)
== CChunkFileReader::ERROR_NONE;
if (useEmuThread)
{
EmuThreadStart();
sleep_ms(4);
}
AudioBufferFlush();
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)
return false;
#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 NativeUpdate() {}
void NativeRender(GraphicsContext *graphicsContext) {}
void NativeResized() {}
void System_Toast(const char *str) {}
// Temporary, to keep the old behavior before changing it.
StereoResampler g_resampler;
// numFrames is number of stereo frames.
// This is called from *outside* the emulator thread.
int __AudioMix(int16_t *outstereo, int numFrames, int sampleRate) {
return g_resampler.Mix(outstereo, numFrames, false, sampleRate);
}
void System_AudioGetDebugStats(char *buf, size_t bufSize) {
if (buf) {
g_resampler.GetAudioDebugStats(buf, bufSize);
} else {
g_resampler.ResetStatCounters();
}
}
void System_AudioClear() {
g_resampler.Clear();
}
void System_AudioPushSamples(const int32_t *audio, int numSamples) {
if (audio) {
g_resampler.PushSamples(audio, numSamples);
} else {
g_resampler.Clear();
}
}
#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