This commit is contained in:
LillyJadeKatrin 2025-04-19 14:27:04 -04:00 committed by GitHub
commit cd8a19c94a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 359 additions and 51 deletions

View File

@ -124,6 +124,11 @@ option(OPROFILING "Enable profiling" OFF)
# TODO: Add DSPSpy
option(DSPTOOL "Build dsptool" OFF)
# RetroAchievements developer tools require Windows hooks
if(WIN32)
option(RC_CLIENT_SUPPORTS_RAINTEGRATION "Enables RetroAchievements developer tools" ON)
endif()
# Enable SDL by default on operating systems that aren't Android.
if(NOT ANDROID)
option(ENABLE_SDL "Enables SDL as a generic controller backend" ON)

View File

@ -43,9 +43,12 @@ add_library(rcheevos
rcheevos/src/rhash/hash.c
rcheevos/src/rhash/md5.c
rcheevos/src/rhash/md5.h
rcheevos/src/rhash/rc_hash_internal.h
rcheevos/src/rurl/url.c
rcheevos/src/rc_client.c
rcheevos/src/rc_client_external.c
rcheevos/src/rc_client_external.h
rcheevos/src/rc_client_external_versions.h
rcheevos/src/rc_client_internal.h
rcheevos/src/rc_client_raintegration.c
rcheevos/src/rc_client_raintegration_internal.h
@ -61,6 +64,11 @@ target_include_directories(rcheevos PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/rcheevo
target_include_directories(rcheevos INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}")
target_compile_definitions(rcheevos PRIVATE "RC_DISABLE_LUA=1" "RCHEEVOS_URL_SSL")
target_compile_definitions(rcheevos PRIVATE "RC_CLIENT_SUPPORTS_HASH")
target_compile_definitions(rcheevos PRIVATE "RC_CLIENT_SUPPORTS_EXTERNAL")
target_compile_definitions(rcheevos PRIVATE "RC_HASH_NO_ENCRYPTED")
target_compile_definitions(rcheevos PRIVATE "RC_HASH_NO_ROM")
target_compile_definitions(rcheevos PRIVATE "RC_HASH_NO_ZIP")
if(CMAKE_SYSTEM_NAME MATCHES "Windows")
target_compile_definitions(rcheevos PRIVATE "RC_CLIENT_SUPPORTS_RAINTEGRATION")
target_compile_definitions(rcheevos PRIVATE "_CRT_SECURE_NO_WARNINGS")
endif()

@ -1 +1 @@
Subproject commit d54cf8f1059cebc90a6f5ecdf03df69259f22054
Subproject commit 022ac70cff6cf60c8957de63d6297998904a6f05

View File

@ -41,6 +41,7 @@
<ClCompile Include="rcheevos\src\rhash\md5.c" />
<ClCompile Include="rcheevos\src\rurl\url.c" />
<ClCompile Include="rcheevos\src\rc_client.c" />
<ClCompile Include="rcheevos\src\rc_client_external.c" />
<ClCompile Include="rcheevos\src\rc_client_raintegration.c" />
<ClCompile Include="rcheevos\src\rc_compat.c" />
<ClCompile Include="rcheevos\src\rc_util.c" />
@ -68,7 +69,9 @@
<ClInclude Include="rcheevos\src\rcheevos\rc_validate.h" />
<ClInclude Include="rcheevos\src\rhash\aes.h" />
<ClInclude Include="rcheevos\src\rhash\md5.h" />
<ClInclude Include="rcheevos\src\rhash\rc_hash_internal.h" />
<ClInclude Include="rcheevos\src\rc_client_external.h" />
<ClInclude Include="rcheevos\src\rc_client_external_versions.h" />
<ClInclude Include="rcheevos\src\rc_client_internal.h" />
<ClInclude Include="rcheevos\src\rc_client_raintegration_internal.h" />
<ClInclude Include="rcheevos\src\rc_compat.h" />
@ -76,7 +79,7 @@
</ItemGroup>
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>RC_DISABLE_LUA;RCHEEVOS_URL_SSL;RC_CLIENT_SUPPORTS_HASH;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>RC_DISABLE_LUA;RCHEEVOS_URL_SSL;RC_CLIENT_SUPPORTS_HASH;RC_CLIENT_SUPPORTS_EXTERNAL;RC_CLIENT_SUPPORTS_RAINTEGRATION;RC_HASH_NO_ENCRYPTED;RC_HASH_NO_ROM;RC_HASH_NO_ZIP;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(ProjectDir)rcheevos\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>

View File

@ -22,6 +22,7 @@
#include "Common/Image.h"
#include "Common/Logging/Log.h"
#include "Common/ScopeGuard.h"
#include "Common/StringUtil.h"
#include "Common/Version.h"
#include "Common/WorkQueueThread.h"
#include "Core/ActionReplay.h"
@ -33,6 +34,7 @@
#include "Core/GeckoCode.h"
#include "Core/HW/Memmap.h"
#include "Core/HW/VideoInterface.h"
#include "Core/Host.h"
#include "Core/PatchEngine.h"
#include "Core/PowerPC/MMU.h"
#include "Core/System.h"
@ -42,6 +44,12 @@
#include "VideoCommon/OnScreenDisplay.h"
#include "VideoCommon/VideoEvents.h"
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
#include <libloaderapi.h>
#include <rcheevos/include/rc_client_raintegration.h>
#include <shlwapi.h>
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
static const Common::HttpRequest::Headers USER_AGENT_HEADER = {
{"User-Agent", Common::GetUserAgentStr()}};
@ -51,7 +59,7 @@ AchievementManager& AchievementManager::GetInstance()
return s_instance;
}
void AchievementManager::Init()
void AchievementManager::Init(void* hwnd)
{
LoadDefaultBadges();
if (!m_client && Config::Get(Config::RA_ENABLED))
@ -73,9 +81,19 @@ void AchievementManager::Init()
m_queue.Reset("AchievementManagerQueue", [](const std::function<void()>& func) { func(); });
m_image_queue.Reset("AchievementManagerImageQueue",
[](const std::function<void()>& func) { func(); });
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
// Attempt to load the integration DLL from the directory containing the main client executable.
// In x64 build, will look for RA_Integration-x64.dll, then RA_Integration.dll.
// In non-x64 build, will only look for RA_Integration.dll.
rc_client_begin_load_raintegration(
m_client, UTF8ToWString(File::GetExeDirectory()).c_str(), reinterpret_cast<HWND>(hwnd),
"Dolphin", Common::GetScmDescStr().c_str(), LoadIntegrationCallback, NULL);
#else // RC_CLIENT_SUPPORTS_RAINTEGRATION
if (HasAPIToken())
Login("");
INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager Initialized");
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
}
}
@ -163,12 +181,17 @@ void AchievementManager::LoadGame(const std::string& file_path, const DiscIO::Vo
rc_client_set_unofficial_enabled(m_client, Config::Get(Config::RA_UNOFFICIAL_ENABLED));
rc_client_set_encore_mode_enabled(m_client, Config::Get(Config::RA_ENCORE_ENABLED));
rc_client_set_spectator_mode_enabled(m_client, Config::Get(Config::RA_SPECTATOR_ENABLED));
if (volume)
{
std::lock_guard lg{m_lock};
if (!m_loading_volume)
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
SplitPath(file_path, nullptr, &m_title_estimate, nullptr);
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
if (volume)
{
m_loading_volume = DiscIO::CreateVolume(volume->GetBlobReader().CopyReader());
if (!m_loading_volume)
{
m_loading_volume = DiscIO::CreateVolume(volume->GetBlobReader().CopyReader());
}
}
}
std::lock_guard lg{m_filereader_lock};
@ -292,15 +315,26 @@ void AchievementManager::FetchGameBadges()
void AchievementManager::DoFrame()
{
if (!IsGameLoaded() || !Core::IsCPUThread())
if (!(IsGameLoaded() || m_dll_found) || !Core::IsCPUThread())
return;
{
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
if (m_dll_found)
{
std::lock_guard lg{m_memory_lock};
Core::System* system = m_system.load(std::memory_order_acquire);
if (!system)
return;
Core::CPUThreadGuard thread_guard(*system);
u32 ram_size = system->GetMemory().GetRamSizeReal();
if (m_cloned_memory.size() != ram_size)
m_cloned_memory.resize(ram_size);
system->GetMemory().CopyFromEmu(m_cloned_memory.data(), 0, m_cloned_memory.size());
}
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
std::lock_guard lg{m_lock};
rc_client_do_frame(m_client);
}
Core::System* system = m_system.load(std::memory_order_acquire);
if (!system)
return;
auto current_time = std::chrono::steady_clock::now();
if (current_time - m_last_rp_time > std::chrono::seconds{10})
{
@ -621,6 +655,22 @@ std::vector<std::string> AchievementManager::GetActiveLeaderboards() const
return display_values;
}
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
const rc_client_raintegration_menu_t* AchievementManager::GetDevelopmentMenu()
{
if (!m_dll_found)
return nullptr;
return rc_client_raintegration_get_menu(m_client);
}
u32 AchievementManager::ActivateDevMenuItem(u32 menu_item_id)
{
if (!m_dll_found)
return 0;
return rc_client_raintegration_activate_menu_item(m_client, menu_item_id);
}
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
void AchievementManager::DoState(PointerWrap& p)
{
if (!m_client || !Config::Get(Config::RA_ENABLED))
@ -716,6 +766,7 @@ void AchievementManager::Shutdown()
// DON'T log out - keep those credentials for next run.
rc_client_destroy(m_client);
m_client = nullptr;
m_dll_found = false;
INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager shut down.");
}
}
@ -921,7 +972,8 @@ void AchievementManager::LeaderboardEntriesCallback(int result, const char* erro
void AchievementManager::LoadGameCallback(int result, const char* error_message,
rc_client_t* client, void* userdata)
{
AchievementManager::GetInstance().m_loading_volume.reset(nullptr);
auto& instance = AchievementManager::GetInstance();
instance.m_loading_volume.reset(nullptr);
if (result == RC_API_FAILURE)
{
WARN_LOG_FMT(ACHIEVEMENTS, "Load data request rejected for old Dolphin version.");
@ -936,6 +988,12 @@ void AchievementManager::LoadGameCallback(int result, const char* error_message,
WARN_LOG_FMT(ACHIEVEMENTS, "Failed to load data for current game.");
OSD::AddMessage("Achievements are not supported for this title.", OSD::Duration::VERY_LONG,
OSD::Color::RED);
if (instance.m_dll_found && result == RC_NO_GAME_LOADED)
{
// Allow developer tools for unidentified games
rc_client_set_read_memory_function(instance.m_client, MemoryPeeker);
instance.m_system.store(&Core::System::GetInstance(), std::memory_order_release);
}
return;
}
@ -949,7 +1007,6 @@ void AchievementManager::LoadGameCallback(int result, const char* error_message,
}
INFO_LOG_FMT(ACHIEVEMENTS, "Loaded data for game ID {}.", game->id);
auto& instance = AchievementManager::GetInstance();
rc_client_set_read_memory_function(instance.m_client, MemoryPeeker);
instance.m_display_welcome_message = true;
instance.FetchGameBadges();
@ -1035,6 +1092,7 @@ void AchievementManager::DisplayWelcomeMessage()
void AchievementManager::HandleAchievementTriggeredEvent(const rc_client_event_t* client_event)
{
const auto& instance = AchievementManager::GetInstance();
OSD::AddMessage(fmt::format("Unlocked: {} ({})", client_event->achievement->title,
client_event->achievement->points),
OSD::Duration::VERY_LONG,
@ -1043,6 +1101,30 @@ void AchievementManager::HandleAchievementTriggeredEvent(const rc_client_event_t
&instance.GetAchievementBadge(client_event->achievement->id, false));
AchievementManager::GetInstance().m_update_callback(
UpdatedItems{.achievements = {client_event->achievement->id}});
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
switch (rc_client_raintegration_get_achievement_state(instance.m_client,
client_event->achievement->id))
{
case RC_CLIENT_RAINTEGRATION_ACHIEVEMENT_STATE_LOCAL:
// Achievement only exists locally and has not been uploaded.
OSD::AddMessage("Local achievement; not submitted to site.", OSD::Duration::VERY_LONG,
OSD::Color::GREEN);
break;
case RC_CLIENT_RAINTEGRATION_ACHIEVEMENT_STATE_MODIFIED:
// Achievement has been modified locally and differs from the one on the site.
OSD::AddMessage("Modified achievement; not submitted to site.", OSD::Duration::VERY_LONG,
OSD::Color::GREEN);
break;
case RC_CLIENT_RAINTEGRATION_ACHIEVEMENT_STATE_INSECURE:
// The player has done something that we consider cheating like modifying the RAM while playing.
// Just indicate that the achievement was only unlocked locally, but don't clarify why.
OSD::AddMessage("Achievement not submitted to site.", OSD::Duration::VERY_LONG,
OSD::Color::GREEN);
break;
default:
break;
}
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
}
void AchievementManager::HandleLeaderboardStartedEvent(const rc_client_event_t* client_event)
@ -1229,16 +1311,33 @@ u32 AchievementManager::MemoryPeeker(u32 address, u8* buffer, u32 num_bytes, rc_
{
if (buffer == nullptr)
return 0u;
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
auto& instance = AchievementManager::GetInstance();
if (instance.m_dll_found)
{
std::lock_guard lg{instance.m_memory_lock};
if (u64(address) + num_bytes >= instance.m_cloned_memory.size())
{
ERROR_LOG_FMT(ACHIEVEMENTS,
"Attempt to read past memory size: size {} address {} write length {}",
instance.m_cloned_memory.size(), address, num_bytes);
return 0;
}
std::copy(instance.m_cloned_memory.begin() + address,
instance.m_cloned_memory.begin() + address + num_bytes, buffer);
return num_bytes;
}
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
auto& system = Core::System::GetInstance();
if (!(Core::IsHostThread() || Core::IsCPUThread()))
{
ASSERT_MSG(ACHIEVEMENTS, false, "MemoryPeeker called from wrong thread");
return 0;
}
Core::CPUThreadGuard threadguard(system);
Core::CPUThreadGuard thread_guard(system);
for (u32 num_read = 0; num_read < num_bytes; num_read++)
{
auto value = system.GetMMU().HostTryReadU8(threadguard, address + num_read,
auto value = system.GetMMU().HostTryReadU8(thread_guard, address + num_read,
PowerPC::RequestedAddressSpace::Physical);
if (!value.has_value())
return num_read;
@ -1395,4 +1494,97 @@ void AchievementManager::EventHandler(const rc_client_event_t* event, rc_client_
}
}
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
void AchievementManager::LoadIntegrationCallback(int result, const char* error_message,
rc_client_t* client, void* userdata)
{
auto& instance = AchievementManager::GetInstance();
switch (result)
{
case RC_OK:
INFO_LOG_FMT(ACHIEVEMENTS, "RAIntegration.dll found.");
instance.m_dll_found = true;
rc_client_raintegration_set_event_handler(instance.m_client, RAIntegrationEventHandler);
rc_client_raintegration_set_write_memory_function(instance.m_client, MemoryPoker);
rc_client_raintegration_set_get_game_name_function(instance.m_client, GameTitleEstimateHandler);
instance.m_dev_menu_callback();
// TODO: hook up menu and dll event handlers
break;
case RC_MISSING_VALUE:
INFO_LOG_FMT(ACHIEVEMENTS, "RAIntegration.dll not found.");
// DLL is not present; do nothing.
break;
default:
WARN_LOG_FMT(ACHIEVEMENTS, "Failed to load RAIntegration.dll. {}", error_message);
break;
}
if (instance.HasAPIToken())
instance.Login("");
INFO_LOG_FMT(ACHIEVEMENTS, "Achievement Manager Initialized");
}
void AchievementManager::RAIntegrationEventHandler(const rc_client_raintegration_event_t* event,
rc_client_t* client)
{
auto& instance = AchievementManager::GetInstance();
switch (event->type)
{
case RC_CLIENT_RAINTEGRATION_EVENT_MENU_CHANGED:
case RC_CLIENT_RAINTEGRATION_EVENT_MENUITEM_CHECKED_CHANGED:
instance.m_dev_menu_callback();
break;
case RC_CLIENT_RAINTEGRATION_EVENT_PAUSE:
{
Core::QueueHostJob([](Core::System& system) { Core::SetState(system, Core::State::Paused); });
break;
}
case RC_CLIENT_RAINTEGRATION_EVENT_HARDCORE_CHANGED:
Config::SetBaseOrCurrent(Config::RA_HARDCORE_ENABLED,
!Config::Get(Config::RA_HARDCORE_ENABLED));
break;
default:
WARN_LOG_FMT(ACHIEVEMENTS, "Unsupported raintegration event. {}", event->type);
break;
}
}
void AchievementManager::MemoryPoker(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client)
{
if (buffer == nullptr)
return;
if (!(Core::IsHostThread() || Core::IsCPUThread()))
{
Core::QueueHostJob([address, buffer, num_bytes, client](Core::System& system) {
MemoryPoker(address, buffer, num_bytes, client);
});
return;
}
auto& instance = AchievementManager::GetInstance();
if (u64(address) + num_bytes >= instance.m_cloned_memory.size())
{
ERROR_LOG_FMT(ACHIEVEMENTS,
"Attempt to write past memory size: size {} address {} write length {}",
instance.m_cloned_memory.size(), address, num_bytes);
return;
}
Core::System* system = instance.m_system.load(std::memory_order_acquire);
if (!system)
return;
Core::CPUThreadGuard thread_guard(*system);
std::lock_guard lg{instance.m_memory_lock};
system->GetMemory().CopyToEmu(address, buffer, num_bytes);
std::copy(buffer, buffer + num_bytes, instance.m_cloned_memory.begin() + address);
}
void AchievementManager::GameTitleEstimateHandler(char* buffer, u32 buffer_size,
rc_client_t* client)
{
auto& instance = AchievementManager::GetInstance();
std::lock_guard lg{instance.m_lock};
strncpy(buffer, instance.m_title_estimate.c_str(), static_cast<size_t>(buffer_size));
}
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
#endif // USE_RETRO_ACHIEVEMENTS

View File

@ -34,6 +34,10 @@
#include "DiscIO/Volume.h"
#include "VideoCommon/Assets/CustomTextureData.h"
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
#include <rcheevos/include/rc_client_raintegration.h>
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
namespace Core
{
class CPUThreadGuard;
@ -113,7 +117,7 @@ public:
using UpdateCallback = std::function<void(const UpdatedItems&)>;
static AchievementManager& GetInstance();
void Init();
void Init(void* hwnd);
void SetUpdateCallback(UpdateCallback callback);
void Login(const std::string& password);
bool HasAPIToken() const;
@ -161,6 +165,16 @@ public:
const std::unordered_set<AchievementId>& GetActiveChallenges() const;
std::vector<std::string> GetActiveLeaderboards() const;
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
const rc_client_raintegration_menu_t* GetDevelopmentMenu();
u32 ActivateDevMenuItem(u32 menu_item_id);
void SetDevMenuUpdateCallback(std::function<void(void)> callback)
{
m_dev_menu_callback = callback;
};
bool CheckForModifications() { return rc_client_raintegration_has_modifications(m_client); };
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
void DoState(PointerWrap& p);
void CloseGame();
@ -235,6 +249,15 @@ private:
const UpdatedItems callback_data);
static void EventHandler(const rc_client_event_t* event, rc_client_t* client);
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
static void LoadIntegrationCallback(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 MemoryPoker(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client);
static void GameTitleEstimateHandler(char* buffer, u32 buffer_size, rc_client_t* client);
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
rc_runtime_t m_runtime{};
rc_client_t* m_client{};
std::atomic<Core::System*> m_system{};
@ -266,6 +289,14 @@ private:
std::unordered_set<AchievementId> m_active_challenges;
std::vector<rc_client_leaderboard_tracker_t> m_active_leaderboards;
bool m_dll_found = false;
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
std::function<void(void)> m_dev_menu_callback;
std::vector<u8> m_cloned_memory;
std::recursive_mutex m_memory_lock;
std::string m_title_estimate;
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
Common::WorkQueueThread<std::function<void()>> m_queue;
Common::WorkQueueThread<std::function<void()>> m_image_queue;
mutable std::recursive_mutex m_lock;

View File

@ -787,4 +787,7 @@ if(USE_RETRO_ACHIEVEMENTS)
target_link_libraries(core PUBLIC rcheevos)
target_compile_definitions(core PUBLIC -DUSE_RETRO_ACHIEVEMENTS)
target_compile_definitions(core PUBLIC -DRC_CLIENT_SUPPORTS_HASH)
if(RC_CLIENT_SUPPORTS_RAINTEGRATION)
target_compile_definitions(core PUBLIC -DRC_CLIENT_SUPPORTS_RAINTEGRATION)
endif()
endif()

View File

@ -22,7 +22,7 @@
static constexpr size_t PROGRESS_LENGTH = 24;
AchievementBox::AchievementBox(QWidget* parent, rc_client_achievement_t* achievement)
AchievementBox::AchievementBox(QWidget* parent, const rc_client_achievement_t* achievement)
: QGroupBox(parent), m_achievement(achievement)
{
const auto& instance = AchievementManager::GetInstance();

View File

@ -18,7 +18,7 @@ class AchievementBox final : public QGroupBox
{
Q_OBJECT
public:
explicit AchievementBox(QWidget* parent, rc_client_achievement_t* achievement);
explicit AchievementBox(QWidget* parent, const rc_client_achievement_t* achievement);
void UpdateData();
void UpdateProgress();
@ -28,7 +28,7 @@ private:
QProgressBar* m_progress_bar;
QLabel* m_progress_label;
rc_client_achievement_t* m_achievement;
const rc_client_achievement_t* m_achievement;
};
#endif // USE_RETRO_ACHIEVEMENTS

View File

@ -34,10 +34,6 @@ AchievementSettingsWidget::AchievementSettingsWidget(QWidget* parent) : QWidget(
connect(&Settings::Instance(), &Settings::ConfigChanged, this,
&AchievementSettingsWidget::LoadSettings);
// If hardcore is enabled when the emulator starts, make sure it turns off what it needs to
if (Config::Get(Config::RA_HARDCORE_ENABLED))
UpdateHardcoreMode();
}
void AchievementSettingsWidget::UpdateData(int login_failed_code)
@ -256,10 +252,9 @@ void AchievementSettingsWidget::ToggleRAIntegration()
auto& instance = AchievementManager::GetInstance();
if (Config::Get(Config::RA_ENABLED))
instance.Init();
instance.Init(reinterpret_cast<void*>(winId()));
else
instance.Shutdown();
UpdateHardcoreMode();
}
void AchievementSettingsWidget::Login()
@ -297,7 +292,6 @@ void AchievementSettingsWidget::ToggleHardcore()
}
}
SaveSettings();
UpdateHardcoreMode();
}
void AchievementSettingsWidget::ToggleUnofficial()
@ -327,14 +321,4 @@ void AchievementSettingsWidget::ToggleProgress()
SaveSettings();
}
void AchievementSettingsWidget::UpdateHardcoreMode()
{
if (Config::Get(Config::RA_HARDCORE_ENABLED))
{
Settings::Instance().SetDebugModeEnabled(false);
}
emit Settings::Instance().EmulationStateChanged(Core::GetState(Core::System::GetInstance()));
emit Settings::Instance().HardcoreStateChanged();
}
#endif // USE_RETRO_ACHIEVEMENTS

View File

@ -39,8 +39,6 @@ private:
void ToggleDiscordPresence();
void ToggleProgress();
void UpdateHardcoreMode();
QGroupBox* m_common_box;
QVBoxLayout* m_common_layout;
ToolTipCheckBox* m_common_integration_enabled_input;

View File

@ -39,8 +39,6 @@ AchievementsWindow::AchievementsWindow(QWidget* parent) : QDialog(parent)
});
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
[this] { m_settings_widget->UpdateData(RC_OK); });
connect(&Settings::Instance(), &Settings::HardcoreStateChanged, this,
[this] { AchievementsWindow::UpdateData({.all = true}); });
}
void AchievementsWindow::showEvent(QShowEvent* event)

View File

@ -677,4 +677,7 @@ endif()
if(USE_RETRO_ACHIEVEMENTS)
target_link_libraries(dolphin-emu PRIVATE rcheevos)
target_compile_definitions(dolphin-emu PRIVATE -DUSE_RETRO_ACHIEVEMENTS)
if(RC_CLIENT_SUPPORTS_RAINTEGRATION)
target_compile_definitions(dolphin-emu PRIVATE -DRC_CLIENT_SUPPORTS_RAINTEGRATION)
endif()
endif()

View File

@ -43,6 +43,7 @@
#include "Core/BootManager.h"
#include "Core/CommonTitles.h"
#include "Core/Config/AchievementSettings.h"
#include "Core/Config/FreeLookSettings.h"
#include "Core/Config/MainSettings.h"
#include "Core/Config/NetplaySettings.h"
#include "Core/Config/UISettings.h"
@ -272,9 +273,15 @@ MainWindow::MainWindow(Core::System& system, std::unique_ptr<BootParameters> boo
NetPlayInit();
#ifdef USE_RETRO_ACHIEVEMENTS
AchievementManager::GetInstance().Init();
AchievementManager::GetInstance().Init(reinterpret_cast<void*>(winId()));
if (AchievementManager::GetInstance().IsHardcoreModeActive())
Settings::Instance().SetDebugModeEnabled(false);
// This needs to trigger on both RA_HARDCORE_ENABLED and RA_ENABLED
Config::AddConfigChangedCallback(
[this]() { QueueOnObject(this, [this] { this->OnHardcoreChanged(); }); });
// If hardcore is enabled when the emulator starts, make sure it turns off what it needs to
if (Config::Get(Config::RA_HARDCORE_ENABLED))
OnHardcoreChanged();
#endif // USE_RETRO_ACHIEVEMENTS
#if defined(__unix__) || defined(__unix) || defined(__APPLE__)
@ -935,7 +942,11 @@ bool MainWindow::RequestStop()
else
FullScreen();
if (Config::Get(Config::MAIN_CONFIRM_ON_STOP))
bool confirm_on_stop = Config::Get(Config::MAIN_CONFIRM_ON_STOP);
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
confirm_on_stop = confirm_on_stop || AchievementManager::GetInstance().CheckForModifications();
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
if (confirm_on_stop)
{
if (std::exchange(m_stop_confirm_showing, true))
return true;
@ -960,13 +971,27 @@ bool MainWindow::RequestStop()
// This is to avoid any "race conditions" between the "Window Activate" message and the
// message box returning, which could break cursor locking depending on the order
m_render_widget->SetWaitingForMessageBox(true);
auto confirm = ModalMessageBox::question(
confirm_parent, tr("Confirm"),
m_stop_requested ? tr("A shutdown is already in progress. Unsaved data "
"may be lost if you stop the current emulation "
"before it completes. Force stop?") :
tr("Do you want to stop the current emulation?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::NoButton, Qt::ApplicationModal);
QString message;
if (m_stop_requested)
{
message = tr("A shutdown is already in progress. Unsaved data "
"may be lost if you stop the current emulation "
"before it completes. Force stop?");
}
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
else if (AchievementManager::GetInstance().CheckForModifications())
{
message = tr(
"Do you want to stop the current emulation? Unsaved achievement modifications detected.");
}
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
else
{
message = tr("Do you want to stop the current emulation?");
}
auto confirm = ModalMessageBox::question(confirm_parent, tr("Confirm"), message,
QMessageBox::Yes | QMessageBox::No,
QMessageBox::NoButton, Qt::ApplicationModal);
// If a user confirmed stopping the emulation, we do not capture the cursor again,
// even if the render widget will stay alive for a while.
@ -1992,6 +2017,13 @@ void MainWindow::ShowAchievementSettings()
ShowAchievementsWindow();
m_achievements_window->ForceSettingsTab();
}
void MainWindow::OnHardcoreChanged()
{
if (Config::Get(Config::RA_HARDCORE_ENABLED))
Settings::Instance().SetDebugModeEnabled(false);
emit Settings::Instance().EmulationStateChanged(Core::GetState(Core::System::GetInstance()));
}
#endif // USE_RETRO_ACHIEVEMENTS
void MainWindow::ShowMemcardManager()

View File

@ -181,6 +181,7 @@ private:
#ifdef USE_RETRO_ACHIEVEMENTS
void ShowAchievementsWindow();
void ShowAchievementSettings();
void OnHardcoreChanged();
#endif // USE_RETRO_ACHIEVEMENTS
void NetPlayInit();

View File

@ -64,6 +64,7 @@
#include "DolphinQt/QtUtils/DolphinFileDialog.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h"
#include "DolphinQt/QtUtils/ParallelProgressDialog.h"
#include "DolphinQt/QtUtils/QueueOnObject.h"
#include "DolphinQt/QtUtils/SetWindowDecorations.h"
#include "DolphinQt/Settings.h"
#include "DolphinQt/Updater.h"
@ -71,6 +72,10 @@
#include "UICommon/AutoUpdate.h"
#include "UICommon/GameFile.h"
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
#include <rcheevos/include/rc_client_raintegration.h>
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
QPointer<MenuBar> MenuBar::s_menu_bar;
QString MenuBar::GetSignatureSelector() const
@ -284,8 +289,14 @@ void MenuBar::AddToolsMenu()
tools_menu->addSeparator();
#ifdef USE_RETRO_ACHIEVEMENTS
tools_menu->addAction(tr("Achievements"), this, [this] { emit ShowAchievementsWindow(); });
m_achievements_action =
tools_menu->addAction(tr("Achievements"), this, [this] { emit ShowAchievementsWindow(); });
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
m_achievements_dev_menu = tools_menu->addMenu(tr("RetroAchievements Development"));
AchievementManager::GetInstance().SetDevMenuUpdateCallback(
[this]() { QueueOnObject(this, [this] { this->UpdateAchievementDevelopmentMenu(); }); });
m_achievements_dev_menu->menuAction()->setVisible(false);
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
tools_menu->addSeparator();
#endif // USE_RETRO_ACHIEVEMENTS
@ -1123,6 +1134,38 @@ void MenuBar::UpdateToolsMenu(const Core::State state)
}
}
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
void MenuBar::UpdateAchievementDevelopmentMenu()
{
auto* dev_menu = AchievementManager::GetInstance().GetDevelopmentMenu();
if (dev_menu)
{
m_achievements_dev_menu->menuAction()->setVisible(true);
m_achievements_dev_menu->clear();
for (u32 i = 0; i < dev_menu->num_items; i++)
{
const auto& menu_item = dev_menu->items[i];
if (menu_item.label == nullptr)
{
m_achievements_dev_menu->addSeparator();
continue;
}
auto* ra_dev_menu_item = m_achievements_dev_menu->addAction(
QString::fromStdString(menu_item.label), this,
[menu_item]() { AchievementManager::GetInstance().ActivateDevMenuItem(menu_item.id); });
ra_dev_menu_item->setEnabled(menu_item.enabled);
// Recommended hardcode by RAIntegration.dll developer Jamiras
ra_dev_menu_item->setCheckable(i < 2);
ra_dev_menu_item->setChecked(menu_item.checked);
}
}
else
{
m_achievements_dev_menu->menuAction()->setVisible(false);
}
}
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
void MenuBar::InstallWAD()
{
QString wad_file = DolphinFileDialog::getOpenFileName(this, tr("Select Title to Install to NAND"),

View File

@ -44,6 +44,9 @@ public:
explicit MenuBar(QWidget* parent = nullptr);
void UpdateToolsMenu(Core::State state);
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
void UpdateAchievementDevelopmentMenu();
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
QMenu* GetListColumnsMenu() const { return m_cols_menu; }
@ -205,6 +208,10 @@ private:
QAction* m_wad_install_action;
QMenu* m_perform_online_update_menu;
QAction* m_perform_online_update_for_current_region;
QAction* m_achievements_action;
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
QMenu* m_achievements_dev_menu;
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
QAction* m_ntscj_ipl;
QAction* m_ntscu_ipl;
QAction* m_pal_ipl;

View File

@ -222,7 +222,6 @@ signals:
void SDCardInsertionChanged(bool inserted);
void USBKeyboardConnectionChanged(bool connected);
void EnableGfxModsChanged(bool enabled);
void HardcoreStateChanged();
private:
Settings();

View File

@ -46,6 +46,7 @@
<PreprocessorDefinitions>HAVE_SDL2;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>USE_RETRO_ACHIEVEMENTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>RC_CLIENT_SUPPORTS_HASH;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>RC_CLIENT_SUPPORTS_RAINTEGRATION;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>HAVE_CUBEB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<!-- Warnings one may want to ignore when using Level4.