Skip to content

RA_Integration Support & QoL Improvements#2258

Open
CySlaytor wants to merge 4 commits intoflyinghead:devfrom
CySlaytor:dev
Open

RA_Integration Support & QoL Improvements#2258
CySlaytor wants to merge 4 commits intoflyinghead:devfrom
CySlaytor:dev

Conversation

@CySlaytor
Copy link
Copy Markdown

Hey there! This ended up being a pretty massive changeset, so I wanted to break down exactly what I did and why.

I'm a RetroAchievements developer, and while Flycast already supports rcheevos (which is great for playing), I noticed it was missing RA_Integration support. By adding this, the Windows build can now load the RA_Integration.dll. This is a huge deal for us achievement devs because it exposes the Memory Inspector, Achievement Editor, and Asset Editor directly inside Flycast. Previously, we had to rely on RALibretro for this, but honestly, RALibretro struggles with the performance overhead of Dreamcast titles. This change makes developing sets for these systems much smoother.

To get this working, I had to implement RA_Defs.h and some window hooking logic to handle the external DLL menu. I also had to call _RA_OnLoadNewRom in Exports.cpp in RA_Integration specifically because without that change, the integration refused to accept game hashes and wouldn't load the achievement data (@Jamiras suggested to call RA_IdentifyHash instead but unfortunately the implementation results in a lot of race conditions that I couldn't figure out, so calling _RA_OnLoadNewRom is the best approach here). I also pulled in logic to fetch game rarity data, which is a nice touch that was previously mostly unique to RALibretro only.

image

Audio Feedback & Rarity

I've fully implemented audio feedback for achievements. You now get sound cues for logging in, loading a game, and unlocking achievements. I added a rarity check using RarityHardcore. if an achievement has an unlock rate below 5%, it plays a special rareunlock.wav instead of the standard sound.

For Android, since it doesn't handle SDL audio quite like the desktop builds, I wrote a JNI bridge (playRASound) to trigger these effects using Android's native SoundPool. I've verified this works reliably on my device.

All the sounds are public domain, sourced from http://onj3.andrelouis.com/phonetones/. I picked them specifically to fit the Dreamcast atmosphere.
The set includes:

  • info.wav, login.wav (System/Auth)
  • unlock.wav, rareunlock.wav (Achievements)
  • lb.wav, lbcancel.wav, lbsubmit.wav (Leaderboards)

Input System & QoL

With the dev tools now available, I overhauled the input system to support workflows that developers expect. I added support for button combos and set up default hotkeys for Save/Load States (Shift+F1 / F1), Pause/Play, and Frame Advance. Frame Advance is particularly critical when you are inspecting memory addresses frame-by-frame.

I also cleaned up the input mapping UI. I moved System Buttons and Emulator Hotkeys out of the "Dreamcast Controls" view and gave them their own filters to keep things organized.

image

OSD & Hardcore Logic Improvements

The OSD needed a bit of a refresh to properly support the newer features, so I reworked how everything is laid out and behaves. Status icons like Fast Forward now stack in the top-right with a small sliding animation, while text notifications stay centered at the bottom so things remain readable and don't fight for space.

At the same time, I tightened up Hardcore Mode to better match RA rules. When Hardcore is enabled, Pause and Frame Advance hotkeys are fully blocked, and I added a short cooldown to the Menu button so the emulation can't be manipulated after pausing or resuming. This mainly exists to prevent accidental state abuse and mirrors the behavior seen in emulators like PCSX2.

Most of the work here was structural; the goal was to make the overlay system more stable and predictable rather than just visual polish. The layout logic was reworked to prevent indicators from colliding with each other, and the Trigger indicator was moved to the bottom-right to avoid overlapping with the Measured indicator. Trigger notifications now use symmetrical slide-in/out animations, and achievement unlocks were updated to slide out instead of fading so animations feel consistent across the board.

I also fixed a few race conditions between Trigger/Measured indicators and achievement notifications, and decoupled overlay lifecycles so achievement cards no longer disappear prematurely when other UI elements update.

Leaderboard behavior received similar cleanup. Active leaderboard counters no longer block the Measured indicator, and overlays now dynamically stack with automatic repositioning as items appear or disappear. Multiple leaderboard counters are supported simultaneously, and submission/cancel cards were reworked to sit in the top-left with a simpler fade in/out instead of sliding. Multiple submission cards can now appear at once if they're triggered on the same frame, which avoids UI conflicts.

Hardcore Mode behavior was refined a bit further. Loading savestates is now prevented while still allowing savestate creation for debugging purposes, and the Pause and Frame Advance restrictions remain enforced to avoid unintended exploits.

Building & Cross-Platform

I spent the last few days ensuring this plays nice with other platforms. All the RA_Integration logic is strictly guarded by RC_CLIENT_SUPPORTS_RAINTEGRATION and _WIN32 defines, so it won't bloat the binary or affect runtime on Linux, macOS, or Switch.

I also added #if defined(USE_SDL) guards in achievements.cpp to prevent compilation errors on Android/iOS where those headers might be missing. Then, I updated CMakeLists.txt to make sure the sound assets are copied correctly to the build directory (or bundled into Contents/Resources on macOS to avoid CodeSign errors).

I admit I hesitated a bit before opening this PR since the changes are pretty substantial and touch some sensitive parts of the codebase (like emulator.cpp). Because of that, I triple-checked everything more times than I'd like to admit before wrapping it up. I've verified it locally on Windows and Android, and everything seems stable.

Let me know if you spot any conflicts or weirdness!

Pinging @wescopeland here for discussion, just in case.

CySlaytor added 4 commits February 22, 2026 14:25
…ions

- Added mappable hotkeys for Pause, Toggle Fast-Forward, and Frame Advance.
- Added direct input mappings for Save/Load State Slots 1 through 10.
- Implemented `OSDOverlay` in GUI to support sliding notifications and persistent status icons.
- Added anti-abuse cooldowns in Hardcore mode to prevent pause-spamming.
- Added a "Reset" button to the main pause menu.
- Added `rend_present()` to force OSD frame updates even when emulation thread is paused.
- Split RA notifications into visual quadrants for better readability.
- Top-Left: Leaderboard Events (Start, Submit, Cancel).
- Bottom-Left: Main Overlays (Unlocks, Login, Progress, Mastery) and stacked Leaderboard Trackers.
- Bottom-Right: Independent Challenge Indicators.
- Updated event handlers in achievements.cpp to map leaderboard events to the new Top-Left queue (`Notification::Leaderboard`).
- Uses `nlohmann::json` to fetch and parse achievement rarity data from the RA servers (or local RACache).
- Added `playSound` helper to dispatch `.wav` playback using SDL (Desktop) or Android `SoundPool`.
- Tied specific sounds to events: login, leaderboard start/submit/fail, and game load.
- Distinguishes standard unlock sounds from rare unlock sounds (< 5% global unlock rate).
- Updated CMake configuration to bundle `resources/sounds/` automatically into the build output/assets.
- Added support for the official Windows RAIntegration DLL.
- Created `RA_Defs.h` and bound the required function pointers.
- Implemented a dummy HWND message pump to handle background HTTP callbacks and menu invocations on Windows.
- Added `EnableRAIntegration` toggle to the general settings UI.
- Hooked `RA_UpdateFrame()` into the main UI loop to maintain the RA overlay.
- Added dynamic syncing of Hardcore Mode state if toggled from the RA DLL.
- Added safe deferred emulator resets when triggered by the RA DLL.
Copy link
Copy Markdown

@Jamiras Jamiras left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted a few quick things. Did not do a full review.

}

INFO_LOG(COMMON, "RA: Fetching rarity data from server for game %u...", gameId);
std::string url = "https://retroachievements.org/dorequest.php?r=getgameextended&i="
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm very confused what this is trying to do. That API doesn't exist.

Rarity data (for the current game) should already be available in the rc_client_achievement_info_t structure returned by https://github.com/RetroAchievements/rcheevos/wiki/rc_client_get_achievement_info

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had no idea that rarity is already exposed via the rc_client_achievement_info_t struct. I was trying to get the rarity data to determine which audio cue to play (standard vs. rare unlock) and went down a rabbit hole of manually fetching and parsing the JSON. A workaround to temporarily store it in memory for that purpose. I'm glad there's a function for it 👍

typedef int (*_RA_OnLoadNewRom_Fn)(const unsigned char *pData, unsigned int nDataSize);
typedef void (*_RA_SetConsoleID_Fn)(unsigned int nConsoleID);
typedef int (*_RA_OnReset_Fn)();
typedef void (*_RA_SetPaused_Fn)(int bIsPaused);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused here too. If the code already supports rc_client, you should be using the rc_client_raintegration functions and callbacks instead of hooking directly into the DLL. Direct integration with the DLL functions should be done using the RAInterface module.

I'm not finding any references to these (_RA_ only appears in this file), so I presume they're leftover from an earlier attempt? If so, they should be cleaned up. RA_Defs.h appears to only be referenced by the CMakeLists.txt and never actually #included.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RA_Defs.h now is definitely a leftover dead code from an earlier, messier attempt at manually loading the DLL before I discovered that rc_client_begin_load_raintegration already wraps everything nicely.

if (result == RC_OK) {
rc_client_raintegration_set_write_memory_function(client, RA_WriteMemory);
rc_client_raintegration_set_console_id(client, RA_DREAMCAST_ID);
rc_client_raintegration_set_get_game_name_function(client, RA_GetGameTitle);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't seem to be providing an rc_client_raintegration_set_event_handler function, which is mostly used when the toolkit wants to pause the emulator.

@wescopeland
Copy link
Copy Markdown

Thank you for the ping and this work! This one unfortunately is out of my wheelhouse and I defer fully to @Jamiras.

@CySlaytor
Copy link
Copy Markdown
Author

Thanks for taking the time to review this, @Jamiras ! Your feedback is incredibly helpful. You are completely right. I missed some of the built-in rc_client and rc_client_raintegration functionality and ended up overcomplicating a few things.

@nexus382
Copy link
Copy Markdown
Contributor

this is great! a year and a half ago I was also developing and making chevo sets over at RA... and I had brought up to flying head's attention about bringing in the development tools.So I didn't have to use RAs retroarch like tool set to do dreamcast games.... And it seemed that the thought behind it was since only the windows version of flycast could run it without substantial work to get it to run on the other platforms, flycast runs on, and that every release of flycast must have the same features.... So that is why we never got the memory editor on FC stand alone.... so I was always hoping someone would come along to do a full implementation, and bring the cheevo system more up to par, but I was not willing to port the tool set to all the platforms that flyvast runs on, so great job working on this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants