Skip to content

Commit c9515ce

Browse files
authored
Merge pull request #19002 from hrydgard/initial-ra-integration
Add initial RAIntegration support through rc_client
2 parents 32620ff + 6f825e4 commit c9515ce

25 files changed

+198
-53
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ build
136136
libretro/obj/local
137137

138138
ppsspp_retroachievements.dat
139+
RACache
140+
RAPrefs_PPSSPP.cfg
139141

140142
# For CLion
141143
cmake-build-*/

Common/System/System.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ enum SystemProperty {
192192
SYSPROP_USER_DOCUMENTS_DIR,
193193

194194
SYSPROP_OK_BUTTON_LEFT,
195+
196+
SYSPROP_MAIN_WINDOW_HANDLE,
195197
};
196198

197199
enum class SystemNotification {
@@ -252,7 +254,7 @@ enum class UIMessage {
252254

253255
std::string System_GetProperty(SystemProperty prop);
254256
std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop);
255-
int System_GetPropertyInt(SystemProperty prop);
257+
int64_t System_GetPropertyInt(SystemProperty prop);
256258
float System_GetPropertyFloat(SystemProperty prop);
257259
bool System_GetPropertyBool(SystemProperty prop);
258260

Core/Config.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ static bool DefaultSasThread() {
314314
static const ConfigSetting achievementSettings[] = {
315315
// Core settings
316316
ConfigSetting("AchievementsEnable", &g_Config.bAchievementsEnable, true, CfgFlag::DEFAULT),
317+
ConfigSetting("AchievementsEnableRAIntegration", &g_Config.bAchievementsEnableRAIntegration, false, CfgFlag::DEFAULT),
317318
ConfigSetting("AchievementsChallengeMode", &g_Config.bAchievementsChallengeMode, true, CfgFlag::PER_GAME | CfgFlag::DEFAULT),
318319
ConfigSetting("AchievementsEncoreMode", &g_Config.bAchievementsEncoreMode, false, CfgFlag::PER_GAME | CfgFlag::DEFAULT),
319320
ConfigSetting("AchievementsUnofficial", &g_Config.bAchievementsUnofficial, false, CfgFlag::PER_GAME | CfgFlag::DEFAULT),

Core/Config.h

+1
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,7 @@ struct Config {
528528
bool bAchievementsSoundEffects;
529529
bool bAchievementsLogBadMemReads;
530530
bool bAchievementsSaveStateInHardcoreMode;
531+
bool bAchievementsEnableRAIntegration;
531532

532533
// Positioning of the various notifications
533534
int iAchievementsLeaderboardTrackerPos;

Core/Core.vcxproj

+4-4
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@
139139
<ClCompile>
140140
<WarningLevel>Level3</WarningLevel>
141141
<AdditionalIncludeDirectories>..\ffmpeg\WindowsInclude;..\ext\libchdr\include;..\ffmpeg\Windows\x86\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib</AdditionalIncludeDirectories>
142-
<PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_M_IX86=1;_DEBUG;_LIB;_UNICODE;UNICODE;MINIUPNP_STATICLIB;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions)</PreprocessorDefinitions>
142+
<PreprocessorDefinitions>RC_CLIENT_SUPPORTS_RAINTEGRATION;_CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_M_IX86=1;_DEBUG;_LIB;_UNICODE;UNICODE;MINIUPNP_STATICLIB;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions)</PreprocessorDefinitions>
143143
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
144144
<FloatingPointModel>Precise</FloatingPointModel>
145145
<MultiProcessorCompilation>true</MultiProcessorCompilation>
@@ -166,7 +166,7 @@
166166
<ClCompile>
167167
<WarningLevel>Level3</WarningLevel>
168168
<AdditionalIncludeDirectories>..\ffmpeg\WindowsInclude;..\ext\libchdr\include;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/glew;../ext/snappy;../ext/libpng17;../ext/zlib;../ext;../ext/zstd/lib;../ext/zstd/lib</AdditionalIncludeDirectories>
169-
<PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_M_X64=1;_DEBUG;_LIB;_UNICODE;UNICODE;MINIUPNP_STATICLIB;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions)</PreprocessorDefinitions>
169+
<PreprocessorDefinitions>RC_CLIENT_SUPPORTS_RAINTEGRATION;_CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_M_X64=1;_DEBUG;_LIB;_UNICODE;UNICODE;MINIUPNP_STATICLIB;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions)</PreprocessorDefinitions>
170170
<EnableEnhancedInstructionSet>NotSet</EnableEnhancedInstructionSet>
171171
<FloatingPointModel>Precise</FloatingPointModel>
172172
<OmitFramePointers>false</OmitFramePointers>
@@ -257,7 +257,7 @@
257257
<BufferSecurityCheck>false</BufferSecurityCheck>
258258
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
259259
<FloatingPointModel>Precise</FloatingPointModel>
260-
<PreprocessorDefinitions>USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_M_IX86=1;_LIB;NDEBUG;_UNICODE;UNICODE;MINIUPNP_STATICLIB;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions)</PreprocessorDefinitions>
260+
<PreprocessorDefinitions>RC_CLIENT_SUPPORTS_RAINTEGRATION;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_32=1;_M_IX86=1;_LIB;NDEBUG;_UNICODE;UNICODE;MINIUPNP_STATICLIB;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions)</PreprocessorDefinitions>
261261
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
262262
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
263263
<MultiProcessorCompilation>true</MultiProcessorCompilation>
@@ -294,7 +294,7 @@
294294
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
295295
<WholeProgramOptimization>false</WholeProgramOptimization>
296296
<MultiProcessorCompilation>true</MultiProcessorCompilation>
297-
<PreprocessorDefinitions>USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_M_X64=1;_LIB;NDEBUG;_UNICODE;UNICODE;MINIUPNP_STATICLIB;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions)</PreprocessorDefinitions>
297+
<PreprocessorDefinitions>RC_CLIENT_SUPPORTS_RAINTEGRATION;USING_WIN_UI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;USE_FFMPEG;WITH_UPNP;WIN32;_ARCH_64=1;_M_X64=1;_LIB;NDEBUG;_UNICODE;UNICODE;MINIUPNP_STATICLIB;ARMIPS_USE_STD_FILESYSTEM;%(PreprocessorDefinitions)</PreprocessorDefinitions>
298298
<RuntimeTypeInfo>false</RuntimeTypeInfo>
299299
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
300300
<StringPooling>true</StringPooling>

Core/RetroAchievements.cpp

+119-7
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
#include "ext/rcheevos/include/rcheevos.h"
2727
#include "ext/rcheevos/include/rc_client.h"
28+
#include "ext/rcheevos/include/rc_client_raintegration.h"
2829
#include "ext/rcheevos/include/rc_api_user.h"
2930
#include "ext/rcheevos/include/rc_api_info.h"
3031
#include "ext/rcheevos/include/rc_api_request.h"
@@ -38,8 +39,6 @@
3839
#include "Common/Log.h"
3940
#include "Common/File/Path.h"
4041
#include "Common/File/FileUtil.h"
41-
#include "Core/FileLoaders/LocalFileLoader.h"
42-
#include "Core/FileSystems/BlockDevices.h"
4342
#include "Common/Net/HTTPClient.h"
4443
#include "Common/System/OSD.h"
4544
#include "Common/System/System.h"
@@ -55,19 +54,28 @@
5554
#include "Core/MemMap.h"
5655
#include "Core/Config.h"
5756
#include "Core/CoreParameter.h"
58-
#include "Core/ELF/ParamSFO.h"
57+
#include "Core/Core.h"
5958
#include "Core/System.h"
59+
#include "Core/FileLoaders/LocalFileLoader.h"
60+
#include "Core/FileSystems/BlockDevices.h"
61+
#include "Core/ELF/ParamSFO.h"
6062
#include "Core/FileSystems/MetaFileSystem.h"
6163
#include "Core/FileSystems/ISOFileSystem.h"
6264
#include "Core/RetroAchievements.h"
6365

66+
#if RC_CLIENT_SUPPORTS_RAINTEGRATION
67+
68+
#include "Windows/MainWindow.h"
69+
70+
#endif
71+
6472
static bool HashISOFile(ISOFileSystem *fs, const std::string filename, md5_context *md5) {
6573
int handle = fs->OpenFile(filename, FILEACCESS_READ);
6674
if (handle < 0) {
6775
return false;
6876
}
6977

70-
uint32_t sz = fs->SeekFile(handle, 0, FILEMOVE_END);
78+
uint32_t sz = (uint32_t)fs->SeekFile(handle, 0, FILEMOVE_END);
7179
fs->SeekFile(handle, 0, FILEMOVE_BEGIN);
7280
if (!sz) {
7381
return false;
@@ -131,7 +139,7 @@ static Achievements::Statistics g_stats;
131139
const std::string g_gameIconCachePrefix = "game:";
132140
const std::string g_iconCachePrefix = "badge:";
133141

134-
Path s_game_path;
142+
Path g_gamePath;
135143
std::string s_game_hash;
136144

137145
std::set<uint32_t> g_activeChallenges;
@@ -461,6 +469,90 @@ static void login_token_callback(int result, const char *error_message, rc_clien
461469
g_isLoggingIn = false;
462470
}
463471

472+
bool RAIntegrationDirty() {
473+
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
474+
return rc_client_raintegration_has_modifications(g_rcClient);
475+
#else
476+
return false;
477+
#endif
478+
}
479+
480+
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
481+
482+
static void raintegration_get_game_name_handler(char *buffer, uint32_t buffer_size, rc_client_t *client) {
483+
snprintf(buffer, buffer_size, "%s", g_gamePath.GetFilename().c_str());
484+
}
485+
486+
static void raintegration_write_memory_handler(uint32_t address, uint8_t *buffer, uint32_t num_bytes, rc_client_t *client) {
487+
// convert_retroachievements_address_to_real_address
488+
uint32_t realAddress = address + PSP_MEMORY_OFFSET;
489+
uint8_t *writePtr = Memory::GetPointerWriteRange(address, num_bytes);
490+
if (writePtr) {
491+
memcpy(writePtr, buffer, num_bytes);
492+
}
493+
}
494+
495+
static void raintegration_event_handler(const rc_client_raintegration_event_t *event, rc_client_t *client) {
496+
switch (event->type) {
497+
case RC_CLIENT_RAINTEGRATION_EVENT_MENUITEM_CHECKED_CHANGED:
498+
// The checked state of one of the menu items has changed and should be reflected in the UI.
499+
// Call the handy helper function if the menu was created by rc_client_raintegration_rebuild_submenu.
500+
rc_client_raintegration_update_menu_item(client, event->menu_item);
501+
break;
502+
case RC_CLIENT_RAINTEGRATION_EVENT_PAUSE:
503+
// The toolkit has hit a breakpoint and wants to pause the emulator. Do so.
504+
Core_EnableStepping(true, "ra_breakpoint");
505+
break;
506+
case RC_CLIENT_RAINTEGRATION_EVENT_HARDCORE_CHANGED:
507+
// Hardcore mode has been changed (either directly by the user, or disabled through the use of the tools).
508+
// The frontend doesn't necessarily need to know that this value changed, they can still query it whenever
509+
// it's appropriate, but the event lets the frontend do things like enable/disable rewind or cheats.
510+
// handle_hardcore_changed();
511+
break;
512+
default:
513+
ERROR_LOG(ACHIEVEMENTS, "Unsupported raintegration event %u\n", event->type);
514+
break;
515+
}
516+
}
517+
518+
static void load_integration_callback(int result, const char *error_message, rc_client_t *client, void *userdata) {
519+
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
520+
521+
// If DLL not present, do nothing. User can still play without the toolkit.
522+
switch (result) {
523+
case RC_OK:
524+
{
525+
// DLL was loaded correctly.
526+
g_OSD.Show(OSDType::MESSAGE_SUCCESS, ac->T("RAIntegration DLL loaded."));
527+
528+
rc_client_raintegration_set_event_handler(g_rcClient, &raintegration_event_handler);
529+
rc_client_raintegration_set_write_memory_function(g_rcClient, &raintegration_write_memory_handler);
530+
rc_client_raintegration_set_get_game_name_function(g_rcClient, &raintegration_get_game_name_handler);
531+
HWND hWnd = (HWND)userdata;
532+
rc_client_raintegration_rebuild_submenu(g_rcClient, GetMenu(hWnd));
533+
break;
534+
}
535+
case RC_MISSING_VALUE:
536+
// This is fine, proceeding to login.
537+
g_OSD.Show(OSDType::MESSAGE_WARNING, ac->T("RAIntegration is enabled, but RAIntegration-x64.dll was not found."));
538+
break;
539+
case RC_ABORTED:
540+
// This is fine, proceeding to login.
541+
g_OSD.Show(OSDType::MESSAGE_WARNING, ac->T("Wrong version of RAIntegration-x64.dll?"));
542+
break;
543+
default:
544+
g_OSD.Show(OSDType::MESSAGE_ERROR, StringFromFormat("RAIntegration init failed: %s", error_message));
545+
// Bailing.
546+
return;
547+
}
548+
549+
// Things are ready to load a game. If the DLL was initialized, calling rc_client_begin_load_game will be redirected
550+
// through the DLL so the toolkit has access to the game data. Similarly, things like rc_create_leaderboard_list will
551+
// be redirected through the DLL to reflect any local changes made by the user.
552+
TryLoginByToken(true);
553+
}
554+
#endif
555+
464556
void Initialize() {
465557
if (!g_Config.bAchievementsEnable) {
466558
_dbg_assert_(!g_rcClient);
@@ -477,14 +569,28 @@ void Initialize() {
477569

478570
// Provide a logging function to simplify debugging
479571
rc_client_enable_logging(g_rcClient, RC_CLIENT_LOG_LEVEL_VERBOSE, log_message_callback);
480-
481572
if (!System_GetPropertyBool(SYSPROP_SUPPORTS_HTTPS)) {
482573
// Disable SSL if not supported by our platform implementation.
483574
rc_client_set_host(g_rcClient, "http://retroachievements.org");
484575
}
485576

486577
rc_client_set_event_handler(g_rcClient, event_handler_callback);
487578

579+
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
580+
if (g_Config.bAchievementsEnableRAIntegration) {
581+
wchar_t szFilePath[MAX_PATH];
582+
GetModuleFileNameW(NULL, szFilePath, MAX_PATH);
583+
for (int64_t i = wcslen(szFilePath) - 1; i > 0; i--) {
584+
if (szFilePath[i] == '\\') {
585+
szFilePath[i] = '\0';
586+
break;
587+
}
588+
}
589+
HWND hWnd = (HWND)System_GetPropertyInt(SYSPROP_MAIN_WINDOW_HANDLE);
590+
rc_client_begin_load_raintegration(g_rcClient, szFilePath, hWnd, "PPSSPP", PPSSPP_GIT_VERSION, &load_integration_callback, hWnd);
591+
return;
592+
}
593+
#endif
488594
TryLoginByToken(true);
489595
}
490596

@@ -551,7 +657,7 @@ static void login_password_callback(int result, const char *error_message, rc_cl
551657

552658
bool LoginAsync(const char *username, const char *password) {
553659
auto di = GetI18NCategory(I18NCat::DIALOG);
554-
if (IsLoggedIn() || std::strlen(username) == 0 || std::strlen(password) == 0 || IsUsingRAIntegration())
660+
if (IsLoggedIn() || std::strlen(username) == 0 || std::strlen(password) == 0)
555661
return false;
556662

557663
g_OSD.SetProgressBar("cheevos_async_login", di->T("Logging in..."), 0, 0, 0, 0.0f);
@@ -587,6 +693,9 @@ void UpdateSettings() {
587693

588694
bool Shutdown() {
589695
g_activeChallenges.clear();
696+
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
697+
rc_client_unload_raintegration(g_rcClient);
698+
#endif
590699
rc_client_destroy(g_rcClient);
591700
g_rcClient = nullptr;
592701
INFO_LOG(ACHIEVEMENTS, "Achievements shut down.");
@@ -832,6 +941,7 @@ void SetGame(const Path &path, IdentifiedFileType fileType, FileLoader *fileLoad
832941
}
833942

834943
// The caller should hold off on executing game code until this turns false, checking with IsBlockingExecution()
944+
g_gamePath = path;
835945
g_isIdentifying = true;
836946

837947
// TODO: Fish the block device out of the loading process somewhere else. Though, probably easier to just do it here.
@@ -861,6 +971,8 @@ void SetGame(const Path &path, IdentifiedFileType fileType, FileLoader *fileLoad
861971
void UnloadGame() {
862972
if (g_rcClient) {
863973
rc_client_unload_game(g_rcClient);
974+
g_gamePath.clear();
975+
s_game_hash.clear();
864976
}
865977
}
866978

Core/RetroAchievements.h

+3-14
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,6 @@ struct Statistics {
3434
int badMemoryAccessCount;
3535
};
3636

37-
// RAIntegration only exists for Windows, so no point checking it on other platforms.
38-
#ifdef WITH_RAINTEGRATION
39-
40-
bool IsUsingRAIntegration();
41-
42-
#else
43-
44-
static inline bool IsUsingRAIntegration()
45-
{
46-
return false;
47-
}
48-
49-
#endif
50-
5137
// Returns true if the user is logged in properly, and everything is set up for playing games with achievements.
5238
bool IsLoggedIn();
5339

@@ -80,6 +66,9 @@ bool WarnUserIfHardcoreModeActive(bool isSaveStateAction, std::string_view messa
8066
// Returns the length of the string. If (size_t)-1, there's no message.
8167
size_t GetRichPresenceMessage(char *buffer, size_t bufSize);
8268

69+
// Returns true if the user has unsaved RAIntegration changes. Should prompt the user to be sure they want to exit.
70+
bool RAIntegrationDirty();
71+
8372
// The new API is so much nicer that we can use it directly instead of wrapping it. So let's expose the client.
8473
// Will of course return nullptr if not active.
8574
rc_client_t *GetClient();

Qt/QtMain.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {
185185
}
186186
}
187187

188-
int System_GetPropertyInt(SystemProperty prop) {
188+
int64_t System_GetPropertyInt(SystemProperty prop) {
189189
switch (prop) {
190190
#if defined(SDL)
191191
case SYSPROP_AUDIO_SAMPLE_RATE:

SDL/SDLMain.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {
492492
}
493493
}
494494

495-
int System_GetPropertyInt(SystemProperty prop) {
495+
int64_t System_GetPropertyInt(SystemProperty prop) {
496496
switch (prop) {
497497
case SYSPROP_AUDIO_SAMPLE_RATE:
498498
return g_retFmt.freq;

UI/MiscScreens.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,9 @@ UI::EventReturn PromptScreen::OnNo(UI::EventParams &e) {
553553
}
554554

555555
void PromptScreen::TriggerFinish(DialogResult result) {
556-
callback_(result == DR_OK || result == DR_YES);
556+
if (callback_) {
557+
callback_(result == DR_OK || result == DR_YES);
558+
}
557559
UIDialogScreenWithBackground::TriggerFinish(result);
558560
}
559561

UI/PauseScreen.cpp

+20-4
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ void GamePauseScreen::update() {
258258
UIScreen::update();
259259

260260
if (finishNextFrame_) {
261-
TriggerFinish(DR_CANCEL);
261+
TriggerFinish(finishNextFrameResult_);
262262
finishNextFrame_ = false;
263263
}
264264

@@ -494,10 +494,26 @@ UI::EventReturn GamePauseScreen::OnScreenshotClicked(UI::EventParams &e) {
494494
}
495495

496496
UI::EventReturn GamePauseScreen::OnExitToMenu(UI::EventParams &e) {
497-
if (g_Config.bPauseMenuExitsEmulator) {
498-
System_ExitApp();
497+
// If RAIntegration has dirty info, ask for confirmation.
498+
if (Achievements::RAIntegrationDirty()) {
499+
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
500+
auto di = GetI18NCategory(I18NCat::DIALOG);
501+
screenManager()->push(new PromptScreen(gamePath_, ac->T("You have unsaved RAIntegration changes. Exit?"), di->T("Yes"), di->T("No"), [=](bool result) {
502+
if (result) {
503+
if (g_Config.bPauseMenuExitsEmulator) {
504+
System_ExitApp();
505+
} else {
506+
finishNextFrameResult_ = DR_OK; // exit game
507+
finishNextFrame_ = true;
508+
}
509+
}
510+
}));
499511
} else {
500-
TriggerFinish(DR_OK);
512+
if (g_Config.bPauseMenuExitsEmulator) {
513+
System_ExitApp();
514+
} else {
515+
TriggerFinish(DR_OK);
516+
}
501517
}
502518
return UI::EVENT_DONE;
503519
}

0 commit comments

Comments
 (0)