-
Notifications
You must be signed in to change notification settings - Fork 4
09 retroachievements
The RetroAchievements integration: API client, system matching, hashing, credential injection, UI. Related: 07 — Core Services · 12 — Data Formats
flowchart LR
U[RA Windows] --> S[RetroAchievementsService<br/>REST client]
S --> RA[(retroachievements.org API)]
H[HasherTool] --> F[RetroAchievementsFileHasher<br/>RetroAchievementsSharp CLI tool]
H --> M[SystemMatcher]
M --> S
C[EmulatorConfigurator] --> E[RetroArch / PCSX2 / DuckStation /<br/>PPSSPP / Dolphin / Flycast / BizHawk configs]
Mgr[RetroAchievementsManager<br/>local .dat store] --> S
Scn[RetroAchievementsHashScanner<br/>Core] --> F
Scn --> Str[RetroAchievementsHashStore<br/>JSON per system]
Btn[RA filter button] --> Scn
Btn --> Mgr
HTTP client: named "RetroAchievementsClient" via IHttpClientFactory (RetroAchievementsService.cs:37; registered App.xaml.cs:145-149 — 30 s timeout, User-Agent: SimpleLauncher/1.0). Base URLs from config Urls:RetroAchievementsApi/Request/Site (defaults https://retroachievements.org/API/…). Auth: API key as y=, username as u= query params; every call throws RaUnauthorizedException on HTTP 401.
| Method | Endpoint | Returns |
|---|---|---|
GetSessionTokenAsync(user, pass) |
POST dorequest.php (r=login) |
session token |
GetGameInfoAndUserProgressAsync(gameId, user, key) |
API_GetGameInfoAndUserProgress.php |
RaUserGameProgress + RaAchievement[] (incl. hardcore points) |
GetGameExtendedAsync(gameId, user, key) |
API_GetGameExtended.php |
RaGameExtendedDetails |
GetUserGameRankAndScoreAsync(...) |
API_GetUserGameRankAndScore.php |
List<RaUserGameRank> |
GetGameRankAndScoreAsync(..., latestMasters) |
API_GetGameRankAndScore.php (`t=1 |
0`) |
GetUserProfileAsync(user, key) |
API_GetUserProfile.php |
RaProfile |
GetUserRecentlyPlayedGamesAsync(..., count, offset) |
API_GetUserRecentlyPlayedGames.php |
recently played |
GetAchievementsEarnedBetweenAsync(..., from, to) |
API_GetAchievementsEarnedBetween.php (epoch f/t) |
List<RaEarnedAchievement> |
GetUserCompletionProgressAsync(..., count=100, offset=0) |
API_GetUserCompletionProgress.php |
paginated completion list (site URL prefixed onto ImageIcon) |
-
SystemMappings: static dictionary official RA system name →RaSystemInfo { Id, Aliases[] }— ~80 systems, plus"unsupported"(ID 102) with a huge alias list (PS3/PS4/Xbox/Switch/Windows…) (:27-186). -
GetBestMatchSystemName(:193-217): lowercase/trim, exact scan over all aliases; unmatched logged once per name. -
GetExactAliasMatch(:256-271),IsSystemInMappings(:279-305, contains/substring),GetSupportedSystemNames(:233-236),GetSystemId(:243-249, −1 if unknown).
All hash computation is delegated to the bundled RetroAchievementsSharp CLI tool
(tools\RetroAchievementsSharp\RetroAchievementsSharp.exe, _arm64.exe for arm64 — a
1:1 C# port of the rcheevos hashing engine that produces the exact same hashes as RAHasher).
The in-process NuGet library, the old custom MD5 logic, and the previous RAHasher binary are
all gone: the file hasher spawns the CLI (single-file mode for one game, scan --console <id> --format json batch mode for whole systems) and reads the hash/manifest back. The system
matcher resolves the system name to its official RA console ID (passed numerically —
NULL-group consoles like 3DS can only be addressed by numeric id), and the tool handles every
console's algorithm internally (whole-file MD5, header stripping, N64 byte-swapping, arcade
filename hashing, Arduboy line-ending normalization, disc hashing, RVZ/WIA via RVZSharp, …).
| Concern | Implementation |
|---|---|
| Console dispatch |
RetroAchievementsSystemMatcher.GetSystemId → CLI positional mode RetroAchievementsSharp <consoleId> <file>
|
| Complex discs (PS1/Saturn/Dreamcast/…) | hashed directly by the CLI — no DiscConverter conversion, no 60 s process timeout |
GameCube/Wii .rvz/.wia
|
hashed live by the CLI via RVZSharp (decode-on-read, no temp ISO) |
.zip/.7z/.rar
|
.zip handled by the CLI itself (first entry pre-load); .7z/.rar extracted to temp first (except arcade, which hashes the file name) |
| 3DS | requires decryption keys (-s/Hash3Ds); without them the CLI produces no hash → hash null |
| Unsupported input | the CLI prints no hash and exits non-zero → hash null, logged at Information |
Flow (GetGameHashForRetroAchievementsAsync): exact alias match or system-picker prompt
(SystemSelectionWindow with fuzzy pre-selected guess; cancel → RaHashResult(null,null,false,…));
systems without a usable console ID (e.g. the unsupported pseudo-system, ID > 90) are rejected
up front; .zip archives are passed to the CLI directly (it pre-loads the first entry —
single entry from memory, multi-entry hashes the whole archive, oversized entries fall back to
a temp file) — no extraction unless really needed; only .7z/.rar archives are extracted to
temp (except arcade); single hash call; temp cleaned.
The RetroAchievements filter button (trophy icon on the main window) no longer matches games by filename. It matches by hash:
- On click, the filter checks whether a hash scan result exists for the selected system.
- If none exists, the user is prompted: "We need to scan your game path to see what game is
compatible with RetroAchievements."
- Cancel → nothing happens (filter stays disabled).
- OK → the entire game path is scanned in the background; the user is told they can click the button again later to see if the hashing is complete. A notification toast is shown when the calculation finishes.
- Once a scan result exists, the filter keeps only game files whose hash resolves to a known
game in
RetroAchievementsManager.GetGameInfoByHash— i.e. the hash is compared against the compatible-hash set loaded fromRetroAchievements.dat(see Local data store), and only the matched games are displayed.
Parallel hash calculations are prevented (single global scan flag) — clicking the button or running the menu command while a scan is running shows an in-progress toast instead of starting a second scan.
Before hashing a system, the scanner enumerates the ROM path and compares the number of game
files against the FileCount stored in the system's JSON file. If the count is unchanged, the
scan is skipped entirely — hashes are only recalculated when a game was added to or removed from
the path, since there is no need to hash again when nothing changed.
Each scanned system produces a JSON file in %LocalAppData%\SimpleLauncher\RetroAchievementsHashes\:
| File | Content |
|---|---|
{SystemName}.json |
RaSystemHashes — SystemName, ScannedAtUtc, FileCount (number of games at scan time), Hashes (dictionary of full file path → hash) |
Files are written via RetroAchievementsHashStore (System.Text.Json, indented) and read back
for filtering. The scan enumerates the exact same file set as the game-list cache
(IGetListOfFilesService.GetFilesAsync), hashes the directly-supported files (including
.zip) in one batch CLI invocation per system (scan --console <id> --format json),
hashes .7z/.rar individually after extraction, and persists one result file per system.
Only one scan runs at a time (avoids spawning many CLI processes at once).
Zips are handled by the CLI tool itself — its scan/positional zip pre-load mirrors the
old library behavior (FileUtil.LoadZippedFile + GenerateFromBuffer): single-entry .zip
files are loaded into memory and hashed from a buffer (no disk extraction); multi-entry zips
hash the whole archive; entries too large for a byte[] fall back to a temporary file that
is deleted afterwards. Only .7z/.rar archives are extracted to a temporary folder before
hashing (unless the system is arcade, which hashes by file name). The hash is always
persisted under the original archive path so the game list can match it.
RaSystemHashes.HashVersion records the hash-logic version: when it changes (e.g. extraction
rules), previously scanned systems are re-hashed automatically even if the game count is
unchanged.
Options → RetroAchievements → Calculate hash for all Game Paths runs the same background scan for every configured system (systems without a usable RA console ID are skipped), with a completion toast per system and a single in-progress toast on repeat clicks.
| Emulator | File / section | Notes |
|---|---|---|
| RetroArch | retroarch.cfg |
keys cheevos_enable/username/password/hardcore_mode_enable, " = " format |
| PCSX2 |
PCSX2.ini → [Achievements]
|
inis\ beside exe or %MyDocuments%\PCSX2\inis
|
| DuckStation |
settings.ini → [Cheevos]
|
token encrypted (EncryptDuckStationToken); portable if portable.txt
|
| PPSSPP |
memstick\PSP\SYSTEM\ppsspp.ini + ppsspp_retroachievements.dat
|
TitleCase keys; token in separate .dat
|
| Dolphin |
RetroAchievements.ini → [Achievements]
|
portable User\Config\ or %MyDocuments%\Dolphin Emulator\Config
|
| Flycast |
emu.cfg → [achievements]
|
yes/no values; exe dir or %APPDATA%\flycast\
|
| BizHawk |
config.ini (JSON) |
flat root keys RAUsername/RAToken/RACheevosActive/RAHardcoreMode/…
|
Missing/0-byte configs are restored from samples\{emulatorFolderName}\{filename}. INI helpers: UpdateSimpleIniFile (:358-415), UpdateIniFile with sections (:418-493). Wired from RetroAchievementsSettingsViewModel (:125-131).
RetroAchievementsManager persists API data to RetroAchievements.dat (MessagePack): RaGameInfo rows (id, title, console, hashes), achievements, recently played, completion progress. RaGameInfo carries the hashes used for local matching.
RaApiAchievement, RaEarnedAchievement, RaGameExtendedDetails, RaGameInfo, RaGameProgressResponse, RaGameRankAndScore, RaHashResult (struct), RaProfile, RaRecentlyPlayedGame, RaUnauthorizedException, RaUserCompletionGame, RaUserCompletionProgressResponse, RaUserGameProgress, RaUserGameRank — see 07 — Core Services for locations.
-
RetroAchievementsWindow— browse profile, unlocks, completion progress. -
RetroAchievementsForAGameWindow— per-game achievements/rankings/progress (badges, hardcore 🏆, rarity). -
RetroAchievementsSettingsWindow— credentials + "Configure Emulator" for the 7 supported emulators. -
SystemSelectionWindow— system picker when auto-matching is unsure.
Credentials (username/API key/password/token) are stored DPAPI-encrypted in settings.xml (see 05 — Configuration).
- Home
- 01 Overview
- 02 Projects And Solution
- 03 Quickstart
- 04 Architecture
- 05 Configuration
- 06 Systems And Launch
- 07 Core Services
- 08 Ui Layer
- 09 Retroachievements
- 10 Game Scanning
- 11 Bundled Tools
- 12 Data Formats
- 13 Logging And Debug
- 14 Testing
- 15 Development
- 16 Updater
- 17 Release Notes
- 18 Emulator Parameters
- Manual Tests
- Parameters