diff --git a/dep/rcheevos/CMakeLists.txt b/dep/rcheevos/CMakeLists.txt
index f9aa474fb..a081e533a 100644
--- a/dep/rcheevos/CMakeLists.txt
+++ b/dep/rcheevos/CMakeLists.txt
@@ -48,3 +48,14 @@ target_include_directories(rcheevos PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include
target_include_directories(rcheevos INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_compile_definitions(rcheevos PRIVATE "RCHEEVOS_URL_SSL=1" "RC_NO_THREADS=1")
+# RAIntegration is not supported outside of Win32 and only on x64.
+if(WIN32 AND CPU_ARCH_X64)
+ target_sources(rcheevos PRIVATE
+ src/rc_client_external.c
+ src/rc_client_external.h
+ src/rc_client_external_versions.h
+ src/rc_client_raintegration.c
+ src/rc_client_raintegration_internal.h
+ )
+ target_compile_definitions(rcheevos PUBLIC "RC_CLIENT_SUPPORTS_RAINTEGRATION=1")
+endif()
diff --git a/dep/rcheevos/rcheevos.vcxproj b/dep/rcheevos/rcheevos.vcxproj
index bf6057f18..a4dc695f9 100644
--- a/dep/rcheevos/rcheevos.vcxproj
+++ b/dep/rcheevos/rcheevos.vcxproj
@@ -21,6 +21,12 @@
+
+ true
+
+
+ true
+
@@ -43,7 +49,16 @@
+
+ true
+
+
+ true
+
+
+ true
+
@@ -59,7 +74,8 @@
TurnOffAllWarnings
- RC_DISABLE_LUA=1;RCHEEVOS_URL_SSL=1;RC_NO_THREADS=1;%(PreprocessorDefinitions)
+ RCHEEVOS_URL_SSL=1;RC_NO_THREADS=1;%(PreprocessorDefinitions)
+ RC_CLIENT_SUPPORTS_RAINTEGRATION=1;%(PreprocessorDefinitions)
$(ProjectDir)include;%(AdditionalIncludeDirectories)
diff --git a/dep/rcheevos/rcheevos.vcxproj.filters b/dep/rcheevos/rcheevos.vcxproj.filters
index e955f0fd1..5856cfa37 100644
--- a/dep/rcheevos/rcheevos.vcxproj.filters
+++ b/dep/rcheevos/rcheevos.vcxproj.filters
@@ -81,6 +81,8 @@
+
+
@@ -137,6 +139,9 @@
include
+
+
+
diff --git a/src/core/achievements.cpp b/src/core/achievements.cpp
index 685a4ad2a..012845521 100644
--- a/src/core/achievements.cpp
+++ b/src/core/achievements.cpp
@@ -62,13 +62,6 @@
LOG_CHANNEL(Achievements);
-#ifdef ENABLE_RAINTEGRATION
-// RA_Interface ends up including windows.h, with its silly macros.
-#ifdef _WIN32
-#include "common/windows_headers.h"
-#endif
-#include "RA_Interface.h"
-#endif
namespace Achievements {
static constexpr const char* INFO_SOUND_NAME = "sounds/achievements/message.wav";
@@ -233,6 +226,13 @@ static void BuildProgressDatabase(const rc_client_all_progress_list_t* allprog);
static void UpdateProgressDatabase(bool force);
static void ClearProgressDatabase();
+#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
+
+static void BeginLoadRAIntegration();
+static void UnloadRAIntegration();
+
+#endif
+
namespace {
struct PauseMenuAchievementInfo
@@ -249,10 +249,6 @@ struct State
bool has_leaderboards = false;
bool has_rich_presence = false;
-#ifdef ENABLE_RAINTEGRATION
- bool using_raintegration = false;
-#endif
-
std::recursive_mutex mutex; // large
std::string rich_presence_string;
@@ -297,6 +293,11 @@ struct State
rc_client_hash_library_t* fetch_hash_library_result = nullptr;
rc_client_async_handle_t* fetch_all_progress_request = nullptr;
rc_client_all_progress_list_t* fetch_all_progress_result = nullptr;
+
+#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
+ rc_client_async_handle_t* load_raintegration_request = nullptr;
+ bool using_raintegration = false;
+#endif
};
} // namespace
@@ -566,20 +567,11 @@ void Achievements::UpdateGlyphRanges()
bool Achievements::IsActive()
{
-#ifdef ENABLE_RAINTEGRATION
- return (s_state.client != nullptr) || s_state.using_raintegration;
-#else
return (s_state.client != nullptr);
-#endif
}
bool Achievements::IsHardcoreModeActive()
{
-#ifdef ENABLE_RAINTEGRATION
- if (IsUsingRAIntegration())
- return RA_HardcoreModeIsActive() != 0;
-#endif
-
if (!s_state.client)
return false;
@@ -656,6 +648,11 @@ bool Achievements::Initialize()
rc_client_set_event_handler(s_state.client, ClientEventHandler);
+#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
+ if (g_settings.achievements_use_raintegration)
+ BeginLoadRAIntegration();
+#endif
+
// Hardcore starts off. We enable it on first boot.
rc_client_set_hardcore_enabled(s_state.client, false);
rc_client_set_encore_mode_enabled(s_state.client, g_settings.achievements_encore_mode);
@@ -752,9 +749,6 @@ bool Achievements::TryLoggingInWithToken()
void Achievements::UpdateSettings(const Settings& old_config)
{
- if (IsUsingRAIntegration())
- return;
-
if (!g_settings.achievements_enabled)
{
// we're done here
@@ -769,6 +763,16 @@ void Achievements::UpdateSettings(const Settings& old_config)
return;
}
+#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
+ if (g_settings.achievements_use_raintegration != old_config.achievements_use_raintegration)
+ {
+ // RAIntegration requires a full client reload?
+ Shutdown();
+ Initialize();
+ return;
+ }
+#endif
+
if (g_settings.achievements_hardcore_mode != old_config.achievements_hardcore_mode)
{
// Enables have to wait for reset, disables can go through immediately.
@@ -823,6 +827,11 @@ void Achievements::Shutdown()
s_state.login_request = nullptr;
}
+#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
+ if (s_state.using_raintegration)
+ UnloadRAIntegration();
+#endif
+
DestroyClient(&s_state.client, &s_state.http_downloader);
}
@@ -898,11 +907,6 @@ void Achievements::IdleUpdate()
if (!IsActive())
return;
-#ifdef ENABLE_RAINTEGRATION
- if (IsUsingRAIntegration())
- return;
-#endif
-
const auto lock = GetLock();
s_state.http_downloader->PollRequests();
@@ -923,14 +927,6 @@ void Achievements::FrameUpdate()
if (!IsActive())
return;
-#ifdef ENABLE_RAINTEGRATION
- if (IsUsingRAIntegration())
- {
- RA_DoAchievementsFrame();
- return;
- }
-#endif
-
auto lock = GetLock();
s_state.http_downloader->PollRequests();
@@ -1162,28 +1158,12 @@ void Achievements::OnSystemDestroyed()
UpdateGlyphRanges();
}
-void Achievements::OnSystemPaused(bool paused)
-{
-#ifdef ENABLE_RAINTEGRATION
- if (IsUsingRAIntegration())
- RA_SetPaused(paused);
-#endif
-}
-
void Achievements::OnSystemReset()
{
const auto lock = GetLock();
if (!IsActive())
return;
-#ifdef ENABLE_RAINTEGRATION
- if (IsUsingRAIntegration())
- {
- RA_OnReset();
- return;
- }
-#endif
-
// Do we need to enable hardcore mode?
if (System::IsValid() && g_settings.achievements_hardcore_mode && !rc_client_get_hardcore_enabled(s_state.client))
{
@@ -1263,12 +1243,6 @@ bool Achievements::IdentifyGame(CDImage* image)
}
s_state.game_hash = game_hash;
-
-#ifdef ENABLE_RAINTEGRATION
- if (IsUsingRAIntegration())
- RAIntegration::GameChanged();
-#endif
-
return true;
}
@@ -1834,16 +1808,6 @@ void Achievements::DisableHardcoreMode(bool show_message, bool display_game_summ
if (!IsActive())
return;
-#ifdef ENABLE_RAINTEGRATION
- if (IsUsingRAIntegration())
- {
- if (RA_HardcoreModeIsActive())
- RA_DisableHardcore();
-
- return;
- }
-#endif
-
const auto lock = GetLock();
if (!rc_client_get_hardcore_enabled(s_state.client))
return;
@@ -1919,7 +1883,7 @@ bool Achievements::DoState(StateWrapper& sw)
{
// if we're active, make sure we've downloaded and activated all the achievements
// before deserializing, otherwise that state's going to get lost.
- if (!IsUsingRAIntegration() && s_state.load_game_request)
+ if (s_state.load_game_request)
{
// Messy because GPU-thread, but at least it looks pretty.
GPUThread::RunOnThread([]() {
@@ -1938,14 +1902,7 @@ bool Achievements::DoState(StateWrapper& sw)
{
// reset runtime, no data (state might've been created without cheevos)
WARNING_LOG("State is missing cheevos data, resetting runtime");
-#ifdef ENABLE_RAINTEGRATION
- if (IsUsingRAIntegration())
- RA_OnReset();
- else
- rc_client_reset(s_state.client);
-#else
rc_client_reset(s_state.client);
-#endif
return !sw.HasError();
}
@@ -1954,20 +1911,11 @@ bool Achievements::DoState(StateWrapper& sw)
if (sw.HasError())
return false;
-#ifdef ENABLE_RAINTEGRATION
- if (IsUsingRAIntegration())
+ const int result = rc_client_deserialize_progress_sized(s_state.client, data.data(), data_size);
+ if (result != RC_OK)
{
- RA_RestoreState(reinterpret_cast(data.data()));
- }
- else
-#endif
- {
- const int result = rc_client_deserialize_progress_sized(s_state.client, data.data(), data_size);
- if (result != RC_OK)
- {
- WARNING_LOG("Failed to deserialize cheevos state ({}), resetting", result);
- rc_client_reset(s_state.client);
- }
+ WARNING_LOG("Failed to deserialize cheevos state ({}), resetting", result);
+ rc_client_reset(s_state.client);
}
return true;
@@ -1976,46 +1924,22 @@ bool Achievements::DoState(StateWrapper& sw)
{
const size_t size_pos = sw.GetPosition();
-#ifdef ENABLE_RAINTEGRATION
- if (IsUsingRAIntegration())
- {
- const int size = RA_CaptureState(nullptr, 0);
- u32 write_size = static_cast(std::max(size, 0));
- sw.Do(&write_size);
+ u32 data_size = static_cast(rc_client_progress_size(s_state.client));
+ sw.Do(&data_size);
- const std::span data = sw.GetDeferredBytes(write_size);
- if (!data.empty())
+ if (data_size > 0)
+ {
+ const std::span data = sw.GetDeferredBytes(data_size);
+ if (!sw.HasError()) [[likely]]
{
- const int result = RA_CaptureState(reinterpret_cast(data.data()), size);
- if (result != static_cast(size))
+ const int result = rc_client_serialize_progress_sized(s_state.client, data.data(), data_size);
+ if (result != RC_OK)
{
- WARNING_LOG("Failed to serialize cheevos state from RAIntegration.");
- write_size = 0;
+ // set data to zero, effectively serializing nothing
+ WARNING_LOG("Failed to serialize cheevos state ({})", result);
+ data_size = 0;
sw.SetPosition(size_pos);
- sw.Do(&write_size);
- }
- }
- }
- else
-#endif
- {
- u32 data_size = static_cast(rc_client_progress_size(s_state.client));
- sw.Do(&data_size);
-
- if (data_size > 0)
- {
- const std::span data = sw.GetDeferredBytes(data_size);
- if (!sw.HasError()) [[likely]]
- {
- const int result = rc_client_serialize_progress_sized(s_state.client, data.data(), data_size);
- if (result != RC_OK)
- {
- // set data to zero, effectively serializing nothing
- WARNING_LOG("Failed to serialize cheevos state ({})", result);
- data_size = 0;
- sw.SetPosition(size_pos);
- sw.Do(&data_size);
- }
+ sw.Do(&data_size);
}
}
}
@@ -2281,7 +2205,7 @@ std::string Achievements::GetLoggedInUserBadgePath()
u32 Achievements::GetPauseThrottleFrames()
{
- if (!IsActive() || !IsHardcoreModeActive() || IsUsingRAIntegration())
+ if (!IsActive() || !IsHardcoreModeActive())
return 0;
u32 frames_remaining = 0;
@@ -2314,23 +2238,8 @@ void Achievements::Logout()
ClearProgressDatabase();
}
-bool Achievements::ConfirmGameChange()
-{
-#ifdef ENABLE_RAINTEGRATION
- if (IsUsingRAIntegration())
- return RA_ConfirmLoadNewRom(false);
-#endif
-
- return true;
-}
-
bool Achievements::ConfirmHardcoreModeDisable(const char* trigger)
{
-#ifdef ENABLE_RAINTEGRATION
- if (IsUsingRAIntegration())
- return (RA_WarnDisableHardcore(trigger) != 0);
-#endif
-
// I really hope this doesn't deadlock :/
const bool confirmed = Host::ConfirmMessage(
TRANSLATE("Achievements", "Confirm Hardcore Mode Disable"),
@@ -2346,30 +2255,19 @@ bool Achievements::ConfirmHardcoreModeDisable(const char* trigger)
void Achievements::ConfirmHardcoreModeDisableAsync(const char* trigger, std::function callback)
{
- auto real_callback = [callback = std::move(callback)](bool res) mutable {
- // don't run the callback in the middle of rendering the UI
- Host::RunOnCPUThread([callback = std::move(callback), res]() {
- if (res)
- DisableHardcoreMode(true, true);
- callback(res);
- });
- };
-
-#ifdef ENABLE_RAINTEGRATION
- if (IsUsingRAIntegration())
- {
- const bool result = (RA_WarnDisableHardcore(trigger) != 0);
- real_callback(result);
- return;
- }
-#endif
-
Host::ConfirmMessageAsync(
TRANSLATE_STR("Achievements", "Confirm Hardcore Mode Disable"),
fmt::format(TRANSLATE_FS("Achievements", "{0} cannot be performed while hardcore mode is active. Do you want to "
"disable hardcore mode? {0} will be cancelled if you select No."),
trigger),
- std::move(real_callback));
+ [callback = std::move(callback)](bool res) mutable {
+ // don't run the callback in the middle of rendering the UI
+ Host::RunOnCPUThread([callback = std::move(callback), res]() {
+ if (res)
+ DisableHardcoreMode(true, true);
+ callback(res);
+ });
+ });
}
void Achievements::ClearUIState()
@@ -4665,195 +4563,122 @@ const Achievements::ProgressDatabase::Entry* Achievements::ProgressDatabase::Loo
return (iter != m_entries.end() && iter->game_id == game_id) ? &(*iter) : nullptr;
}
-#ifdef ENABLE_RAINTEGRATION
+#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
-#include "RA_Consoles.h"
+#include "common/windows_headers.h"
+
+#include "rc_client_raintegration.h"
+
+namespace Achievements {
+
+static void RAIntegrationBeginLoadCallback(int result, const char* error_message, rc_client_t* client, void* userdata);
+static void RAIntegrationEventHandler(const rc_client_raintegration_event_t* event, rc_client_t* client);
+static void RAIntegrationWriteMemoryCallback(uint32_t address, uint8_t* buffer, uint32_t num_bytes,
+ rc_client_t* client);
+static void RAIntegrationGetGameNameCallback(char* buffer, uint32_t buffer_size, rc_client_t* client);
+
+} // namespace Achievements
bool Achievements::IsUsingRAIntegration()
{
return s_state.using_raintegration;
}
-namespace Achievements::RAIntegration {
-static void InitializeRAIntegration(void* main_window_handle);
-
-static int RACallbackIsActive();
-static void RACallbackCauseUnpause();
-static void RACallbackCausePause();
-static void RACallbackRebuildMenu();
-static void RACallbackEstimateTitle(char* buf);
-static void RACallbackResetEmulator();
-static void RACallbackLoadROM(const char* unused);
-static unsigned char RACallbackReadRAM(unsigned int address);
-static unsigned int RACallbackReadRAMBlock(unsigned int nAddress, unsigned char* pBuffer, unsigned int nBytes);
-static void RACallbackWriteRAM(unsigned int address, unsigned char value);
-static unsigned char RACallbackReadScratchpad(unsigned int address);
-static unsigned int RACallbackReadScratchpadBlock(unsigned int nAddress, unsigned char* pBuffer, unsigned int nBytes);
-static void RACallbackWriteScratchpad(unsigned int address, unsigned char value);
-
-static bool s_raintegration_initialized = false;
-} // namespace Achievements::RAIntegration
-
-void Achievements::SwitchToRAIntegration()
+bool Achievements::IsRAIntegrationAvailable()
{
- s_state.using_raintegration = true;
+ return (FileSystem::FileExists(Path::Combine(EmuFolders::AppRoot, "RA_Integration-x64.dll").c_str()) ||
+ FileSystem::FileExists(Path::Combine(EmuFolders::AppRoot, "RA_Integration.dll").c_str()));
}
-void Achievements::RAIntegration::InitializeRAIntegration(void* main_window_handle)
+void Achievements::BeginLoadRAIntegration()
{
- RA_InitClient((HWND)main_window_handle, "DuckStation", g_scm_tag_str);
- RA_SetUserAgentDetail(Host::GetHTTPUserAgent().c_str());
-
- RA_InstallSharedFunctions(RACallbackIsActive, RACallbackCauseUnpause, RACallbackCausePause, RACallbackRebuildMenu,
- RACallbackEstimateTitle, RACallbackResetEmulator, RACallbackLoadROM);
- RA_SetConsoleID(PlayStation);
-
- // Apparently this has to be done early, or the memory inspector doesn't work.
- // That's a bit unfortunate, because the RAM size can vary between games, and depending on the option.
- RA_InstallMemoryBank(0, RACallbackReadRAM, RACallbackWriteRAM, Bus::RAM_2MB_SIZE);
- RA_InstallMemoryBankBlockReader(0, RACallbackReadRAMBlock);
- RA_InstallMemoryBank(1, RACallbackReadScratchpad, RACallbackWriteScratchpad, CPU::SCRATCHPAD_SIZE);
- RA_InstallMemoryBankBlockReader(1, RACallbackReadScratchpadBlock);
-
- // Fire off a login anyway. Saves going into the menu and doing it.
- RA_AttemptLogin(0);
-
- s_raintegration_initialized = true;
-
- // this is pretty lame, but we may as well persist until we exit anyway
- std::atexit(RA_Shutdown);
+ const std::optional wi = Host::GetTopLevelWindowInfo();
+ const std::wstring wapproot = StringUtil::UTF8StringToWideString(EmuFolders::AppRoot);
+ s_state.load_raintegration_request = rc_client_begin_load_raintegration(
+ s_state.client, wapproot.c_str(),
+ (wi.has_value() && wi->type == WindowInfo::Type::Win32) ? static_cast(wi->window_handle) : NULL,
+ "DuckStation", g_scm_tag_str, RAIntegrationBeginLoadCallback, nullptr);
}
-void Achievements::RAIntegration::MainWindowChanged(void* new_handle)
+void Achievements::RAIntegrationBeginLoadCallback(int result, const char* error_message, rc_client_t* client,
+ void* userdata)
{
- if (s_raintegration_initialized)
+ if (result != RC_OK)
{
- RA_UpdateHWnd((HWND)new_handle);
+ std::string message = fmt::format("Failed to load RAIntegration:\n{}", error_message ? error_message : "");
+ Host::ReportErrorAsync("RAIntegration Error", message);
return;
}
- InitializeRAIntegration(new_handle);
-}
-
-void Achievements::RAIntegration::GameChanged()
-{
- s_state.game_id = s_state.game_hash.has_value() ? RA_IdentifyHash(GameHashToString(s_state.game_hash.value())) : 0;
- RA_ActivateGame(s_state.game_id);
-}
-
-std::vector> Achievements::RAIntegration::GetMenuItems()
-{
- std::array items;
- const int num_items = RA_GetPopupMenuItems(items.data());
-
- std::vector> ret;
- ret.reserve(static_cast(num_items));
-
- for (int i = 0; i < num_items; i++)
{
- const RA_MenuItem& it = items[i];
- if (!it.sLabel)
- ret.emplace_back(0, std::string(), false);
- else
- ret.emplace_back(static_cast(it.nID), StringUtil::WideStringToUTF8String(it.sLabel), it.bChecked);
+ const auto lock = GetLock();
+
+ rc_client_raintegration_set_write_memory_function(client, RAIntegrationWriteMemoryCallback);
+ rc_client_raintegration_set_console_id(client, RC_CONSOLE_PLAYSTATION);
+ rc_client_raintegration_set_get_game_name_function(client, RAIntegrationGetGameNameCallback);
+ rc_client_raintegration_set_event_handler(client, RAIntegrationEventHandler);
+
+ s_state.using_raintegration = true;
}
- return ret;
+ INFO_COLOR_LOG(StrongGreen, "RAIntegration loaded.");
+
+ Host::OnRAIntegrationMenuChanged();
}
-void Achievements::RAIntegration::ActivateMenuItem(int item)
+void Achievements::UnloadRAIntegration()
{
- RA_InvokeDialog(item);
-}
-
-int Achievements::RAIntegration::RACallbackIsActive()
-{
- return static_cast(HasActiveGame());
-}
-
-void Achievements::RAIntegration::RACallbackCauseUnpause()
-{
- Host::RunOnCPUThread([]() { System::PauseSystem(false); });
-}
-
-void Achievements::RAIntegration::RACallbackCausePause()
-{
- Host::RunOnCPUThread([]() { System::PauseSystem(true); });
-}
-
-void Achievements::RAIntegration::RACallbackRebuildMenu()
-{
- // unused, we build the menu on demand
-}
-
-void Achievements::RAIntegration::RACallbackEstimateTitle(char* buf)
-{
- StringUtil::Strlcpy(buf, System::GetGameTitle(), 256);
-}
-
-void Achievements::RAIntegration::RACallbackResetEmulator()
-{
- if (System::IsValid())
- System::ResetSystem();
-}
-
-void Achievements::RAIntegration::RACallbackLoadROM(const char* unused)
-{
- // unused
- UNREFERENCED_PARAMETER(unused);
-}
-
-unsigned char Achievements::RAIntegration::RACallbackReadRAM(unsigned int address)
-{
- if (!System::IsValid())
- return 0;
-
- u8 value = 0;
- CPU::SafeReadMemoryByte(address, &value);
- return value;
-}
-
-void Achievements::RAIntegration::RACallbackWriteRAM(unsigned int address, unsigned char value)
-{
- CPU::SafeWriteMemoryByte(address, value);
-}
-
-unsigned int Achievements::RAIntegration::RACallbackReadRAMBlock(unsigned int nAddress, unsigned char* pBuffer,
- unsigned int nBytes)
-{
- if (nAddress >= Bus::g_ram_size)
- return 0;
-
- const u32 copy_size = std::min(Bus::g_ram_size - nAddress, nBytes);
- std::memcpy(pBuffer, Bus::g_unprotected_ram + nAddress, copy_size);
- return copy_size;
-}
-
-unsigned char Achievements::RAIntegration::RACallbackReadScratchpad(unsigned int address)
-{
- if (!System::IsValid() || address >= CPU::SCRATCHPAD_SIZE)
- return 0;
-
- return CPU::g_state.scratchpad[address];
-}
-
-void Achievements::RAIntegration::RACallbackWriteScratchpad(unsigned int address, unsigned char value)
-{
- if (address >= CPU::SCRATCHPAD_SIZE)
+ if (!s_state.using_raintegration)
return;
- CPU::g_state.scratchpad[address] = value;
+ rc_client_unload_raintegration(s_state.client);
+ s_state.using_raintegration = false;
+ Host::OnRAIntegrationMenuChanged();
}
-unsigned int Achievements::RAIntegration::RACallbackReadScratchpadBlock(unsigned int nAddress, unsigned char* pBuffer,
- unsigned int nBytes)
+void Achievements::RAIntegrationEventHandler(const rc_client_raintegration_event_t* event, rc_client_t* client)
{
- if (nAddress >= CPU::SCRATCHPAD_SIZE)
- return 0;
+ switch (event->type)
+ {
+ case RC_CLIENT_RAINTEGRATION_EVENT_MENUITEM_CHECKED_CHANGED:
+ case RC_CLIENT_RAINTEGRATION_EVENT_MENU_CHANGED:
+ {
+ Host::OnRAIntegrationMenuChanged();
+ }
+ break;
- const u32 copy_size = std::min(CPU::SCRATCHPAD_SIZE - nAddress, nBytes);
- std::memcpy(pBuffer, &CPU::g_state.scratchpad[nAddress], copy_size);
- return copy_size;
+ case RC_CLIENT_RAINTEGRATION_EVENT_HARDCORE_CHANGED:
+ {
+ // Could get called from a different thread...
+ Host::RunOnCPUThread([]() {
+ const auto lock = GetLock();
+ OnHardcoreModeChanged(rc_client_get_hardcore_enabled(s_state.client) != 0, false, false);
+ });
+ }
+ break;
+
+ case RC_CLIENT_RAINTEGRATION_EVENT_PAUSE:
+ {
+ Host::RunOnCPUThread([]() { System::PauseSystem(true); });
+ }
+ break;
+
+ default:
+ ERROR_LOG("Unhandled RAIntegration event {}", static_cast(event->type));
+ break;
+ }
+}
+
+void Achievements::RAIntegrationWriteMemoryCallback(uint32_t address, uint8_t* buffer, uint32_t num_bytes,
+ rc_client_t* client)
+{
+ // I hope this is called on the CPU thread...
+ CPU::SafeWriteMemoryBytes(address, buffer, num_bytes);
+}
+
+void Achievements::RAIntegrationGetGameNameCallback(char* buffer, uint32_t buffer_size, rc_client_t* client)
+{
+ StringUtil::Strlcpy(buffer, System::GetGameTitle(), buffer_size);
}
#else
@@ -4863,4 +4688,9 @@ bool Achievements::IsUsingRAIntegration()
return false;
}
+bool Achievements::IsRAIntegrationAvailable()
+{
+ return false;
+}
+
#endif
diff --git a/src/core/achievements.h b/src/core/achievements.h
index ca6a7c14d..0437c46b5 100644
--- a/src/core/achievements.h
+++ b/src/core/achievements.h
@@ -89,9 +89,6 @@ void OnSystemDestroyed();
/// Called when the system is being reset. Resets the internal state of all achievement tracking.
void OnSystemReset();
-/// Called when the system is being paused and resumed.
-void OnSystemPaused(bool paused);
-
/// Called when the system changes game.
void GameChanged(CDImage* image);
@@ -126,9 +123,7 @@ bool IsHardcoreModeActive();
/// RAIntegration only exists for Windows, so no point checking it on other platforms.
bool IsUsingRAIntegration();
-
-/// Hook for RAIntegration to confirm reset/shutdown.
-bool ConfirmGameChange();
+bool IsRAIntegrationAvailable();
/// Returns true if the achievement system is active. Achievements can be active without a valid client.
bool IsActive();
@@ -207,22 +202,11 @@ void DrawLeaderboardsWindow();
#endif // __ANDROID__
-#ifdef ENABLE_RAINTEGRATION
-/// Prevents the internal implementation from being used. Instead, RAIntegration will be
-/// called into when achievement-related events occur.
-void SwitchToRAIntegration();
-
-namespace RAIntegration {
-void MainWindowChanged(void* new_handle);
-void GameChanged();
-std::vector> GetMenuItems();
-void ActivateMenuItem(int item);
-} // namespace RAIntegration
-#endif
} // namespace Achievements
/// Functions implemented in the frontend.
namespace Host {
+
/// Called if the big picture UI requests achievements login, or token login fails.
void OnAchievementsLoginRequested(Achievements::LoginRequestReason reason);
@@ -235,4 +219,12 @@ void OnAchievementsRefreshed();
/// Called whenever hardcore mode is toggled.
void OnAchievementsHardcoreModeChanged(bool enabled);
+
+#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
+
+/// Called when the RAIntegration menu changes.
+void OnRAIntegrationMenuChanged();
+
+#endif
+
} // namespace Host
diff --git a/src/core/core.props b/src/core/core.props
index 68bafc777..f0329b52f 100644
--- a/src/core/core.props
+++ b/src/core/core.props
@@ -4,7 +4,6 @@
- ENABLE_RAINTEGRATION=1;%(PreprocessorDefinitions)
ENABLE_RECOMPILER=1;%(PreprocessorDefinitions)
ENABLE_MMAP_FASTMEM=1;%(PreprocessorDefinitions)
@@ -17,6 +16,7 @@
%(AdditionalIncludeDirectories);$(SolutionDir)dep\xbyak\xbyak
XBYAK_NO_EXCEPTION=1;%(PreprocessorDefinitions)
+ RC_CLIENT_SUPPORTS_RAINTEGRATION=1;%(PreprocessorDefinitions)
%(AdditionalIncludeDirectories);$(SolutionDir)dep\vixl\include
diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp
index 705e0082e..8a314e6ea 100644
--- a/src/core/fullscreen_ui.cpp
+++ b/src/core/fullscreen_ui.cpp
@@ -6223,19 +6223,6 @@ void FullscreenUI::DrawAudioSettingsPage()
void FullscreenUI::DrawAchievementsSettingsPage()
{
-#ifdef ENABLE_RAINTEGRATION
- if (Achievements::IsUsingRAIntegration())
- {
- BeginMenuButtons();
- MenuButtonWithoutSummary(
- FSUI_ICONSTR(ICON_FA_BAN,
- FSUI_CSTR("RAIntegration is being used instead of the built-in achievements implementation.")),
- false);
- EndMenuButtons();
- return;
- }
-#endif
-
SettingsInterface* bsi = GetEditingSettingsInterface();
BeginMenuButtons();
diff --git a/src/core/system.cpp b/src/core/system.cpp
index 30ae7f61a..98758f5c3 100644
--- a/src/core/system.cpp
+++ b/src/core/system.cpp
@@ -1551,7 +1551,7 @@ void System::UpdateInputSettingsLayer(std::string input_profile_name, std::uniqu
void System::ResetSystem()
{
- if (!IsValid() || !Achievements::ConfirmGameChange())
+ if (!IsValid())
return;
InternalReset();
@@ -1591,8 +1591,6 @@ void System::PauseSystem(bool paused)
InputManager::PauseVibration();
InputManager::UpdateHostMouseMode();
- Achievements::OnSystemPaused(true);
-
if (g_settings.inhibit_screensaver)
PlatformMisc::ResumeScreensaver();
@@ -1610,8 +1608,6 @@ void System::PauseSystem(bool paused)
InputManager::UpdateHostMouseMode();
- Achievements::OnSystemPaused(false);
-
if (g_settings.inhibit_screensaver)
PlatformMisc::SuspendScreensaver();
diff --git a/src/duckstation-qt/achievementsettingswidget.cpp b/src/duckstation-qt/achievementsettingswidget.cpp
index c0897d135..a7c57a8f4 100644
--- a/src/duckstation-qt/achievementsettingswidget.cpp
+++ b/src/duckstation-qt/achievementsettingswidget.cpp
@@ -101,6 +101,23 @@ AchievementSettingsWidget::AchievementSettingsWidget(SettingsWindow* dialog, QWi
m_ui.loginBox = nullptr;
}
+ // RAIntegration is not available on non-win32/x64.
+#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
+ if (Achievements::IsRAIntegrationAvailable())
+ SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.useRAIntegration, "Cheevos", "UseRAIntegration", false);
+ else
+ m_ui.useRAIntegration->setEnabled(false);
+
+ dialog->registerWidgetHelp(
+ m_ui.useRAIntegration, tr("Enable RAIntegration (Development Only)"), tr("Unchecked"),
+ tr("When enabled, DuckStation will load the RAIntegration DLL which allows for achievement development.
The "
+ "RA_Integration.dll file must be placed in the same directory as the DuckStation executable."));
+#else
+ m_ui.settingsLayout->removeWidget(m_ui.useRAIntegration);
+ delete m_ui.useRAIntegration;
+ m_ui.useRAIntegration = nullptr;
+#endif
+
updateEnableState();
onAchievementsNotificationDurationSliderChanged();
onLeaderboardsNotificationDurationSliderChanged();
diff --git a/src/duckstation-qt/achievementsettingswidget.ui b/src/duckstation-qt/achievementsettingswidget.ui
index ec19e3334..d413b1d2e 100644
--- a/src/duckstation-qt/achievementsettingswidget.ui
+++ b/src/duckstation-qt/achievementsettingswidget.ui
@@ -24,11 +24,11 @@
0
-
-
+
Settings
-
+
-
@@ -64,6 +64,13 @@
+ -
+
+
+ Enable RAIntegration (Development Only)
+
+
+
diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp
index 6120feb39..2c5c22771 100644
--- a/src/duckstation-qt/mainwindow.cpp
+++ b/src/duckstation-qt/mainwindow.cpp
@@ -21,7 +21,6 @@
#include "settingswindow.h"
#include "settingwidgetbinder.h"
-#include "core/achievements.h"
#include "core/cheats.h"
#include "core/game_list.h"
#include "core/host.h"
@@ -179,11 +178,6 @@ void MainWindow::initialize()
switchToGameListView();
updateWindowTitle();
-#ifdef ENABLE_RAINTEGRATION
- if (Achievements::IsUsingRAIntegration())
- Achievements::RAIntegration::MainWindowChanged((void*)winId());
-#endif
-
#ifdef _WIN32
registerForDeviceNotifications();
#endif
@@ -794,6 +788,8 @@ void MainWindow::recreate()
dlg->setCategoryRow(settings_window_row);
QtUtils::ShowOrRaiseWindow(dlg);
}
+
+ notifyRAIntegrationOfWindowChange();
}
void MainWindow::destroySubWindows()
@@ -1697,37 +1693,6 @@ void MainWindow::setupAdditionalUi()
s_disable_window_rounded_corners = Host::GetBaseBoolSettingValue("Main", "DisableWindowRoundedCorners", false);
if (s_disable_window_rounded_corners)
PlatformMisc::SetWindowRoundedCornerState(reinterpret_cast(winId()), false);
-
-#ifdef ENABLE_RAINTEGRATION
- if (Achievements::IsUsingRAIntegration())
- {
- QMenu* raMenu = new QMenu(QStringLiteral("&RAIntegration"));
- m_ui.menuBar->insertMenu(m_ui.menuDebug->menuAction(), raMenu);
- connect(raMenu, &QMenu::aboutToShow, this, [this, raMenu]() {
- raMenu->clear();
-
- const auto items = Achievements::RAIntegration::GetMenuItems();
- for (const auto& [id, title, checked] : items)
- {
- if (id == 0)
- {
- raMenu->addSeparator();
- continue;
- }
-
- QAction* raAction = raMenu->addAction(QString::fromUtf8(title));
- if (checked)
- {
- raAction->setCheckable(true);
- raAction->setChecked(checked);
- }
-
- connect(raAction, &QAction::triggered, this,
- [id = id]() { Host::RunOnCPUThread([id]() { Achievements::RAIntegration::ActivateMenuItem(id); }); });
- }
- });
- }
-#endif
}
void MainWindow::updateToolbarActions()
@@ -3089,3 +3054,100 @@ bool QtHost::IsSystemLocked()
{
return (s_system_locked.load(std::memory_order_acquire) > 0);
}
+
+#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
+
+#include "core/achievements.h"
+#include "core/achievements_private.h"
+
+#include "rc_client_raintegration.h"
+
+void MainWindow::onRAIntegrationMenuChanged()
+{
+ const auto lock = Achievements::GetLock();
+
+ if (!Achievements::IsUsingRAIntegration())
+ {
+ if (m_raintegration_menu)
+ {
+ m_ui.menuBar->removeAction(m_raintegration_menu->menuAction());
+ m_raintegration_menu->deleteLater();
+ m_raintegration_menu = nullptr;
+ }
+
+ return;
+ }
+
+ if (!m_raintegration_menu)
+ {
+ m_raintegration_menu = new QMenu(QStringLiteral("&RAIntegration"));
+ m_ui.menuBar->insertMenu(m_ui.menuDebug->menuAction(), m_raintegration_menu);
+ }
+
+ m_raintegration_menu->clear();
+
+ const rc_client_raintegration_menu_t* menu = rc_client_raintegration_get_menu(Achievements::GetClient());
+ if (!menu)
+ return;
+
+ for (const rc_client_raintegration_menu_item_t& item :
+ std::span(menu->items, menu->num_items))
+ {
+ if (item.id == 0)
+ {
+ m_raintegration_menu->addSeparator();
+ continue;
+ }
+
+ QAction* action = m_raintegration_menu->addAction(QString::fromUtf8(item.label));
+ action->setEnabled(item.enabled != 0);
+ action->setCheckable(item.checked != 0);
+ action->setChecked(item.checked != 0);
+ connect(action, &QAction::triggered, this, [id = item.id]() {
+ Host::RunOnCPUThread([id]() {
+ // not locked in case a callback fires immediately and tries to lock
+ // client will be safe since this is running on the main thread
+ if (!Achievements::IsUsingRAIntegration())
+ return;
+
+ rc_client_raintegration_activate_menu_item(Achievements::GetClient(), id);
+ });
+ });
+ }
+}
+
+void MainWindow::notifyRAIntegrationOfWindowChange()
+{
+ const auto lock = Achievements::GetLock();
+ if (!Achievements::IsUsingRAIntegration())
+ return;
+
+ HWND hwnd = static_cast((void*)winId());
+ Host::RunOnCPUThread([hwnd]() {
+ const auto lock = Achievements::GetLock();
+ if (!Achievements::IsUsingRAIntegration())
+ return;
+
+ rc_client_raintegration_update_main_window_handle(Achievements::GetClient(), hwnd);
+ });
+
+ onRAIntegrationMenuChanged();
+}
+
+void Host::OnRAIntegrationMenuChanged()
+{
+ QMetaObject::invokeMethod(g_main_window, "onRAIntegrationMenuChanged", Qt::QueuedConnection);
+}
+
+#else // RC_CLIENT_SUPPORTS_RAINTEGRATION
+
+void MainWindow::onRAIntegrationMenuChanged()
+{
+ // has to be stubbed out because otherwise moc won't find it
+}
+
+void MainWindow::notifyRAIntegrationOfWindowChange()
+{
+}
+
+#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h
index c4c1048fe..11e857e95 100644
--- a/src/duckstation-qt/mainwindow.h
+++ b/src/duckstation-qt/mainwindow.h
@@ -209,10 +209,12 @@ private Q_SLOTS:
void onGameListEntryContextMenuRequested(const QPoint& point);
void onUpdateCheckComplete();
+ void onRAIntegrationMenuChanged();
void onDebugLogChannelsMenuAboutToShow();
void openCPUDebugger();
+
protected:
void showEvent(QShowEvent* event) override;
void closeEvent(QCloseEvent* event) override;
@@ -277,6 +279,7 @@ private:
void registerForDeviceNotifications();
void unregisterForDeviceNotifications();
+ void notifyRAIntegrationOfWindowChange();
/// Fills menu with save state info and handlers.
void populateGameListContextMenu(const GameList::Entry* entry, QWidget* parent_window, QMenu* menu);
@@ -345,6 +348,10 @@ private:
#ifdef _WIN32
void* m_device_notification_handle = nullptr;
#endif
+
+#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
+ QMenu* m_raintegration_menu = nullptr;
+#endif
};
extern MainWindow* g_main_window;
diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp
index d314615b5..f664004cd 100644
--- a/src/duckstation-qt/qthost.cpp
+++ b/src/duckstation-qt/qthost.cpp
@@ -201,12 +201,6 @@ bool QtHost::EarlyProcessStartup()
}
#endif
- // Config-based RAIntegration switch must happen before the main window is displayed.
-#ifdef ENABLE_RAINTEGRATION
- if (!Achievements::IsUsingRAIntegration() && Host::GetBaseBoolSettingValue("Cheevos", "UseRAIntegration", false))
- Achievements::SwitchToRAIntegration();
-#endif
-
Error error;
if (System::ProcessStartup(&error)) [[likely]]
return true;
@@ -2725,9 +2719,6 @@ void QtHost::PrintCommandLineHelp(const char* progname)
std::fprintf(stderr, " -nogui: Disables main window from being shown, exits on shutdown.\n");
std::fprintf(stderr, " -bigpicture: Automatically starts big picture UI.\n");
std::fprintf(stderr, " -earlyconsole: Creates console as early as possible, for logging.\n");
-#ifdef ENABLE_RAINTEGRATION
- std::fprintf(stderr, " -raintegration: Use RAIntegration instead of built-in achievement support.\n");
-#endif
std::fprintf(stderr, " --: Signals that no more arguments will follow and the remaining\n"
" parameters make up the filename. Use when the filename contains\n"
" spaces or starts with a dash.\n");
@@ -2858,13 +2849,6 @@ bool QtHost::ParseCommandLineParametersAndInitializeConfig(QApplication& app,
s_cleanup_after_update = AutoUpdaterDialog::isSupported();
continue;
}
-#ifdef ENABLE_RAINTEGRATION
- else if (CHECK_ARG("-raintegration"))
- {
- Achievements::SwitchToRAIntegration();
- continue;
- }
-#endif
else if (CHECK_ARG("--"))
{
no_more_args = true;
diff --git a/src/duckstation-qt/settingswindow.cpp b/src/duckstation-qt/settingswindow.cpp
index 477bf98ea..64b95e7c2 100644
--- a/src/duckstation-qt/settingswindow.cpp
+++ b/src/duckstation-qt/settingswindow.cpp
@@ -161,20 +161,8 @@ void SettingsWindow::addPages()
"Achievements from the menu. Mouse over an option for additional information, and "
"Shift+Wheel to scroll this panel."));
- if (!Achievements::IsUsingRAIntegration())
- {
- addWidget(m_achievement_settings = new AchievementSettingsWidget(this, m_ui.settingsContainer), std::move(title),
- std::move(icon_text), std::move(help_text));
- }
- else
- {
- QLabel* placeholder_label =
- new QLabel(QStringLiteral("RAIntegration is being used, built-in RetroAchievements support is disabled."),
- m_ui.settingsContainer);
- placeholder_label->setAlignment(Qt::AlignLeft | Qt::AlignTop);
-
- addWidget(placeholder_label, std::move(title), std::move(icon_text), std::move(help_text));
- }
+ addWidget(m_achievement_settings = new AchievementSettingsWidget(this, m_ui.settingsContainer), std::move(title),
+ std::move(icon_text), std::move(help_text));
}
if (!isPerGameSettings())