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())