Achievements: Swap RAInterface for RAIntegration via rc_client

This commit is contained in:
Stenzek 2025-04-09 19:50:59 +10:00
parent 1bb1354d4e
commit d286b96c2d
No known key found for this signature in database
14 changed files with 328 additions and 426 deletions

View File

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

View File

@ -21,6 +21,12 @@
<ClCompile Include="src\rcheevos\trigger.c" />
<ClCompile Include="src\rcheevos\value.c" />
<ClCompile Include="src\rc_client.c" />
<ClCompile Include="src\rc_client_external.c">
<ExcludedFromBuild Condition="'$(Platform)'!='x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="src\rc_client_raintegration.c">
<ExcludedFromBuild Condition="'$(Platform)'!='x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="src\rc_compat.c" />
<ClCompile Include="src\rc_util.c" />
<ClCompile Include="src\rhash\md5.c" />
@ -43,7 +49,16 @@
<ClInclude Include="include\rc_util.h" />
<ClInclude Include="src\rapi\rc_api_common.h" />
<ClInclude Include="src\rcheevos\rc_internal.h" />
<ClInclude Include="src\rc_client_external.h">
<ExcludedFromBuild Condition="'$(Platform)'!='x64'">true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="src\rc_client_external_versions.h">
<ExcludedFromBuild Condition="'$(Platform)'!='x64'">true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="src\rc_client_internal.h" />
<ClInclude Include="src\rc_client_raintegration_internal.h">
<ExcludedFromBuild Condition="'$(Platform)'!='x64'">true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="src\rc_compat.h" />
<ClInclude Include="src\rc_version.h" />
<ClInclude Include="src\rhash\md5.h" />
@ -59,7 +74,8 @@
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
<PreprocessorDefinitions>RC_DISABLE_LUA=1;RCHEEVOS_URL_SSL=1;RC_NO_THREADS=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>RCHEEVOS_URL_SSL=1;RC_NO_THREADS=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(Platform)'=='x64'">RC_CLIENT_SUPPORTS_RAINTEGRATION=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(ProjectDir)include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>

View File

@ -81,6 +81,8 @@
<ClCompile Include="src\rc_compat.c" />
<ClCompile Include="src\rc_util.c" />
<ClCompile Include="src\rc_client.c" />
<ClCompile Include="src\rc_client_external.c" />
<ClCompile Include="src\rc_client_raintegration.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="include\rc_consoles.h">
@ -137,6 +139,9 @@
<ClInclude Include="include\rc_util.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="src\rc_client_external.h" />
<ClInclude Include="src\rc_client_external_versions.h" />
<ClInclude Include="src\rc_client_raintegration_internal.h" />
</ItemGroup>
<ItemGroup>
<Natvis Include="src\rc_client_types.natvis" />

View File

@ -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<const char*>(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<u32>(std::max(size, 0));
sw.Do(&write_size);
u32 data_size = static_cast<u32>(rc_client_progress_size(s_state.client));
sw.Do(&data_size);
const std::span<u8> data = sw.GetDeferredBytes(write_size);
if (!data.empty())
if (data_size > 0)
{
const std::span<u8> data = sw.GetDeferredBytes(data_size);
if (!sw.HasError()) [[likely]]
{
const int result = RA_CaptureState(reinterpret_cast<char*>(data.data()), size);
if (result != static_cast<int>(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<u32>(rc_client_progress_size(s_state.client));
sw.Do(&data_size);
if (data_size > 0)
{
const std::span<u8> 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<void(bool)> 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<WindowInfo> 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<HWND>(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<std::tuple<int, std::string, bool>> Achievements::RAIntegration::GetMenuItems()
{
std::array<RA_MenuItem, 64> items;
const int num_items = RA_GetPopupMenuItems(items.data());
std::vector<std::tuple<int, std::string, bool>> ret;
ret.reserve(static_cast<u32>(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<int>(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<int>(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<u32>(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<u32>(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<u32>(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

View File

@ -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<std::tuple<int, std::string, bool>> 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

View File

@ -4,7 +4,6 @@
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions Condition="('$(Platform)'!='ARM64')">ENABLE_RAINTEGRATION=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="('$(Platform)'=='x64' Or '$(Platform)'=='ARM' Or '$(Platform)'=='ARM64')">ENABLE_RECOMPILER=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="('$(Platform)'=='x64' Or '$(Platform)'=='ARM64')">ENABLE_MMAP_FASTMEM=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
@ -17,6 +16,7 @@
<AdditionalIncludeDirectories Condition="'$(Platform)'=='x64'">%(AdditionalIncludeDirectories);$(SolutionDir)dep\xbyak\xbyak</AdditionalIncludeDirectories>
<PreprocessorDefinitions Condition="'$(Platform)'=='x64'">XBYAK_NO_EXCEPTION=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(Platform)'=='x64'">RC_CLIENT_SUPPORTS_RAINTEGRATION=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories Condition="'$(Platform)'=='ARM' Or '$(Platform)'=='ARM64'">%(AdditionalIncludeDirectories);$(SolutionDir)dep\vixl\include</AdditionalIncludeDirectories>
</ClCompile>

View File

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

View File

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

View File

@ -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.<br>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();

View File

@ -24,11 +24,11 @@
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox">
<widget class="QGroupBox" name="settingsGroupBox">
<property name="title">
<string>Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<layout class="QGridLayout" name="settingsLayout">
<item row="1" column="1">
<widget class="QCheckBox" name="spectatorMode">
<property name="text">
@ -64,6 +64,13 @@
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="useRAIntegration">
<property name="text">
<string>Enable RAIntegration (Development Only)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -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<void*>(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<const rc_client_raintegration_menu_item_t>(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<HWND>((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

View File

@ -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;

View File

@ -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;

View File

@ -161,20 +161,8 @@ void SettingsWindow::addPages()
"<strong>Achievements</strong> 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())