Repository navigation
Hardcore Compliance
This page tracks how Emutastic for Linux implements every item in RetroAchievements' hardcore-mode compliance requirements. Sections below mirror RA's own structure (A–H) and address each bullet on the official list. It is the Linux counterpart of the Windows app's Hardcore Compliance page — same author, same product family, independently audited against this codebase.
Status: Re-audited October 10, 2026 against the current text of RA's page (unchanged since June 30, 2026, when RA renamed "Softcore" to "Casual"; last substantive change April 9, 2026; this page and the app follow the Casual wording). The formal application for the emulator as a whole was submitted on October 9, 2026 and is awaiting RA's review; this port's own dates are stated in Section D. The re-audit found one gap in this port, fixed in v1.9.3 (October 10, 2026): core options that switch on a core's own cheat engine (Dolphin, PPSSPP, MAME, FBNeo) were reachable in a hardcore session and are now locked for its length. The September 23 items (the achievement runtime resets with the game; every disc swap is verified by rcheevos) shipped in v1.8.10. Optional items are tracked at the bottom.
Architecture note (Linux-specific): games run in a separate --game-host process (the library window and the game window are different OS processes). All hardcore enforcement lives in the game-host process (Emulator/EmulatorSession.cs), which reads configuration once at session start and never writes it — referenced throughout below.
User must be able to conveniently access the list of achievements within the emulator.
✅ The achievements list for the active game appears on the game detail card, accessible from the library by clicking any title. A dedicated Achievements tab in the main window shows account-wide stats, recent unlocks, friends, leaderboards, and activity.
Code reference: Detail-card RA section — GameDetailWindow.axaml.cs. Achievements tab — MainWindow.RaTab.cs.
Triggers must evaluate correctly. Measured and Trigger flags must work properly.
✅ Trigger evaluation is fully delegated to the rcheevos rc_client C library (built from the vendored 12.5.0 source as librcheevos.so; a build-time ABI harness and a runtime VerifyAbi check keep the marshaled layouts honest), the upstream reference implementation. The frontend exposes the standard libretro RETRO_MEMORY_* regions and replays SET_MEMORY_MAPS descriptors that arrive during retro_load_game to the client once it exists. A game reset resets the runtime too (rc_client_reset runs alongside retro_reset in the emulation loop, for the cog's Reset and for resets rcheevos requests), so hit counts and active leaderboard attempts never carry across a reset.
Code reference: P/Invoke layer — RcheevosInterop.cs. rc_client wrapper — RetroAchievementsClient.cs. Descriptor replay — EmulatorSession.cs (InitRetroAchievements).
Both flags must be visible in the achievement list as well as displayed during gameplay when appropriate conditions are met.
✅ During gameplay, the GL overlay renders RetroArch-style indicators directly over the game: primed challenge achievements show their badges bottom-right for as long as the condition is armed (RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW/HIDE), and measured achievements show a transient progress tracker ("47/100") top-right on PROGRESS_INDICATOR_SHOW/UPDATE/HIDE. Live measured progress is also captured per session and persisted for the detail card's next library visit.
Code reference: Event surfacing — RetroAchievementsClient.cs (ChallengeIndicatorChanged, ProgressIndicatorChanged, _liveProgress). In-game rendering — GlOsd.cs (DrawRaIndicators); state plumbing — EmulatorSession.cs (SetRaChallenge, SetRaProgress).
Rich Presence and Leaderboards: Must function correctly.
✅ Both flow through rcheevos. Rich Presence pings run inside rc_client's DoFrame/Idle cycle on the emulation thread. Leaderboard scoreboard events are shipped from the game host to the library process (which owns the friends/rank cache), where triumph/proximity toasts are decided and sent back for in-game display; submissions themselves are entirely rc_client's.
Code reference: Frame/idle pump — EmulatorSession.cs (RaDoFrame, RaIdle). Scoreboard cross-process path — same file (LeaderboardScoreboardReceived → lb-scoreboard command) and MainWindow.RaTab.cs (OnHostCommandForGame).
Unlocks created while offline must be securely cached and sync to RetroAchievements when connectivity returns.
✅ rcheevos manages the unlock queue and resync internally; the frontend surfaces the DISCONNECTED/RECONNECTED events as status text and does not intervene.
Code reference: Upstream rcheevos source at github.com/RetroAchievements/rcheevos. Event handling — RetroAchievementsClient.cs.
Hit counts should be stored in save states (highly recommended, not strictly required).
✅ Met — same side-car design as the Windows app. Every .state is paired with a .cheevos file containing the rc_client_serialize_progress_sized blob; on load, the side-car is applied via rc_client_deserialize_progress_sized only after a successful retro_unserialize (restoring hits onto a failed load would mis-credit). Stale side-cars are deleted when a state is overwritten without RA active; renaming or deleting a state moves/removes its side-car. Older states without a side-car load with current rcheevos state untouched.
Code reference: Serialize/deserialize wrappers — RetroAchievementsClient.cs (SerializeProgress, DeserializeProgress). Side-car write — EmulatorSession.cs (FinalizeSave). Read + apply — same file (RequestLoadState, ExecuteLoadOnEmuThread). Rename/delete handling — MainWindow.axaml.cs (save-state card context menu).
If you ship a Windows version of the emulator, please investigate adding RAIntegration DLL support.
➖ Not applicable to this build — RAIntegration is Windows-only tooling for set authors. (The Windows Emutastic addresses this on its own compliance page.)
Save files should use standard formats compatible with other emulators of the same system.
✅ SRAM is read from the libretro RETRO_MEMORY_SAVE_RAM region and written as a plain .srm (auto-saved periodically and flushed on exit). Save states are the per-core libretro serialized format; the .cheevos side-car keeps the .state itself byte-compatible with any other libretro frontend.
Code reference: EmulatorSession.cs (SaveSram, the _srmAutoSaveTick cadence, FinalizeSave).
Cheats are disabled in hardcore, including: Built-in cheat engines, Mountable cheat devices, External cheat files…
✅ All three categories are blocked.
-
Built-in cheat engine (the frontend's): every apply path funnels through a single chokepoint that short-circuits when hardcore is active — session-start apply, in-game toggles, database import, the live re-apply queued by the library's cheat editor (
reload-cheatsover the process boundary), and post-state-load re-apply. The in-game Cheats menu row is additionally hidden/disabled in hardcore. -
Built-in cheat engines (the cores' own): several cores run a cheat engine of their own, switched on through a core option that the libretro cheat API never sees — Dolphin's
dolphin_cheats_enabled(Gecko / Action Replay codes from the game's INI), PPSSPP'sppsspp_cheats(CWCheat), MAME'smame_cheats_enable(the cheat.7z collection), MAME 2003-Plus'smame2003-plus_cheat_input_ports(the IPF_CHEAT "fake dip switch" ports) and FBNeo's one-option-per-cheatfbneo-cheat-…entries. Since v1.9.3 a hardcore session serves each of these its disabled value (the core's default when it lists no "disabled"), whatever Preferences → Core Options holds. The decision is taken before the core loads, so it already applies when the core reads its options inretro_load_game; the player sees "Cheat options are disabled in hardcore mode" once, and the saved choice comes back in casual. RetroArch does not lock these; RA's rule covers built-in cheat engines, so Emutastic does. -
Mountable cheat devices: none are emulated. Cheat codes apply via the libretro
retro_cheat_setinterface and frontend Action Replay RAM writes — both behind the same gate. -
External cheat files: per-game JSON, blocked by the gate above. The one path the frontend cannot gate directly is PPSSPP's own
cheats/<DiscID>.ini, which the core reads itself with no libretro signal for hardcore. As on Windows, Emutastic therefore refuses hardcore on PSP entirely: launching a PSP game with hardcore enabled drops that session to casual with a visible message ("Hardcore Mode is disabled for PSP titles — achievements still track"). Theppsspp_cheatslock above is a second line of defence. -
Enhancement packs that patch the ROM: a Mesen HD pack whose
hires.txtcarries a<patch>tag makes the core patch the ROM at load, so rcheevos would have hashed the clean file while modified code runs. Such a launch is demoted to casual for the session; setting the pack to None restores hardcore on the next launch. ROM hacks proper are identified by their patched bytes (see ROM Hacks), so they earn on their own sets or show as unsupported, never against the base game.
Code reference: Chokepoint — EmulatorSession.cs (ExecuteCheatsApplyOnEmuThread, with the explicit RA-compliance comment). Cog-row gating — same file (buildCogMain, the cheatable predicate). Launch decision (PSP and ROM-patch demotions) — HardcoreLaunchPolicy.cs (Evaluate, DemotionMessage), taken in EmulatorSession.cs (DecideHardcoreForLaunch, before _core.LoadGame; consumed by InitRetroAchievements). Core-option lock — same two files (IsCheatCoreOption, LockedValue; HandleGetVariable). ROM-patch detection — HdPackService.cs (ActiveModHasRomPatch).
Rewind is disabled in hardcore.
✅ Emutastic for Linux does not implement rewind. A RewindEnabled config field exists as a placeholder (default false) with no consumers — no rewind buffer, hotkey, or UI exists.
Code reference: Dead field — ConfigurationModels.cs (RewindEnabled). Search the repo to confirm no consumers.
Slowdown and frame advance are disabled in hardcore.
✅ Neither slow-motion nor frame-stepping exists. The emulation loop is audio-clocked at the core's native rate; the only pacing controls are internal (DRC/backpressure) and cannot slow gameplay below real time for the player's benefit.
Code reference: Emulation loop — EmulatorSession.cs (the decoupled run loop).
Loading save states is ALWAYS blocked in hardcore.
✅ Gated at every entry point, two layers deep:
-
Chokepoint:
RequestLoadState— covers the F7 quick-load hotkey, the in-game status-bar Load State picker, and library-initiated loads. The Load State button is also hidden from the in-game UI in hardcore (and its hit-test region is gated, so a stale click cannot land). -
Belt:
ExecuteLoadOnEmuThreadre-checks at execution time. This closes a Linux-specific race: a boot-time--load-state(launching a game directly from a save-state card) queues its pending load before the asynchronous RA login resolves hardcore — so the gate is re-evaluated on the emulation thread the moment the load would actually run.
State creation stays fully functional in hardcore — only loading is blocked, per the requirement.
Code reference: EmulatorSession.cs (RequestLoadState, ExecuteLoadOnEmuThread, both with RA-compliance comments; status-bar hit-test gate in the present loop). HUD hiding — GlOsd.cs (status-bar buttons render conditionally on hardcore).
Rich Presence and Leaderboards cannot be disabled in hardcore. Disabling leaderboard popups is okay…
✅ No setting disables RP or leaderboard submission. The only related settings are popup/toast presentation (visibility, sound, cooldown), which the requirement explicitly permits.
Code reference: Settings surface — ConfigurationModels.cs (only ToastOnUnlock, HardcoreOnlyToast, LbToastSoundEnabled-class fields exist).
If the emulator supports a resume/quick resume feature, the resumed session must drop to Casual.
➖ Not applicable. There is no resume-on-launch; every launch is a fresh session. (Launching from a save-state card routes through the blocked load path above when hardcore is on.)
Switching from casual to hardcore is not allowed mid-session… must result in a full reset…
✅ Doubly enforced by the process architecture: the hardcore decision is taken once in the game-host process (DecideHardcoreForLaunch, before the core loads) and snapshotted at InitRetroAchievements, and the host never re-reads configuration — a Preferences toggle flipped mid-session lives in the library process and physically cannot reach the running game. It takes effect on the next launch, which is a fresh process and a fresh session. Additionally, if rcheevos itself demands a reset (RC_CLIENT_EVENT_RESET, e.g. hardcore being enabled at game load), the handler resets both the achievement runtime and the game (retro_reset) — stricter than required.
Code reference: Decision and snapshot — EmulatorSession.cs (DecideHardcoreForLaunch → _raHardcoreIntent; _raHardcoreActive, assigned once in InitRetroAchievements; RaHardcoreActive property). The one mid-session change is downward: when rcheevos rejects a swapped disc the session drops to casual (RaChangeMedia), which RA's rule permits. Reset handler — same file (ResetRequested subscription). Host-never-writes-config convention — GameHost.cs.
Users should be allowed to create save states while in hardcore for debugging…
✅ Explicit design: F5 and the in-game Save State button work in hardcore; only the load paths are blocked.
Code reference: EmulatorSession.cs (RequestSaveState — no hardcore guard, by design).
Memory editors, debuggers, and/or scripting/TAS/recorded input playback are strictly prohibited.
✅ None exist: no memory editor, RAM watch, debugger, scripting host, TAS recorder, or input replay anywhere in the codebase.
Code reference: Verification by negative — search the repo for RamWatch, MemoryEditor, Debugger, TAS, InputReplay, MovieRecord. No matches.
Disc swaps (not a listed bullet, but the same integrity concern): the in-game swap chord (L3 + Start by default) cycles the disc through the core's disk-control interface. Since v1.8.10 every swap hands the newly inserted disc file to rcheevos (rc_client_begin_identify_and_change_media), which verifies it belongs to the loaded game and drops a hardcore session to casual itself when it is not recognised; the session mirrors that drop in its snapshot and says so on screen. The disc file is resolved from the launch .m3u; when a hardcore session has no playlist to resolve from, the swap is refused rather than continuing on unverified media.
Code reference: EmulatorSession.cs (SwapToNextDisk, ResolveDiscPath, RaChangeMedia). Playlist entry → file — DiscPlaylist.cs (ResolveDiscPath). Client wrapper — RetroAchievementsClient.cs (ChangeMedia; every pending check is kept until rcheevos answers it).
Format:
EmulatorName/v1.0.0 (OSName 10.0) core_name/v0.5.0
Emutastic for Linux sends, on both the rcheevos HTTP client and the public Web API client:
Emutastic/<version> (<os-release name + version>) <core_name>/<core_version>
The OS segment comes from /etc/os-release (e.g. Debian GNU/Linux 13). The core segment is stamped per session from retro_get_system_info() before the login/identify HTTP fires, so even the first request carries it.
Code reference: UA builder — EmutasticUserAgent.cs. Core stamping — EmulatorSession.cs (InitRetroAchievements → RetroAchievementsClient.SetCoreContext).
Segment A (Required): numeric, incrementing version…
✅ Semantic versioning (1.9.3 as of this page), numeric and incrementing across every release tag. The Windows, Linux and macOS builds are one emulator to RA: all three identify as Emutastic/ and, since September 23, 2026, share one version line (this port moved from its own 0.9.x numbering to 1.8.10 so the "numeric and incrementing" rule holds for the name as a whole). The three builds release independently, so their patch numbers can differ for a while; each platform's own sequence only ever goes up. The OS segment tells the platforms apart.
Segment B (Optional): OS name and version. — ✅ From
/etc/os-release.
Segment C (Optional but strongly advised): core name and version. — ✅ Per-session, stamped before first request.
Emulators discovered to have previously identified as another client must disclose…
✅ Stated in the application (October 9, 2026): this build identifies as Emutastic — the same product identity as the Windows Emutastic by the same author, never as any third-party client. The shared identity across the platforms was disclosed explicitly.
The emulator, or the parent emulator it is forked from, must have been publicly available for at least 6 months…
⏳ Submitted, awaiting RA's review. The repository has been public since May 31, 2026, the first tag (v0.5.0) is from June 5, 2026, and the first GitHub release with binaries (v0.8.3) was published June 9, 2026 → the conservative standalone date is December 9, 2026. The Windows Emutastic — same author, same product, the codebase this port mirrors — has been public since April 14, 2026 (six months: October 14, 2026); the application was submitted once for the emulator as a whole on October 9, 2026, with this port's dates stated, and RA decides whether the ports count under the parent.
Enabling hardcore by default is recommended, but not required…
✅ Fresh installs default to hardcore (HardcoreMode = true). The toggle is one click away in Preferences → Achievements, clearly labelled; existing users who chose casual keep their preference.
Code reference: ConfigurationModels.cs (HardcoreMode).
Hardcore state must be visibly indicated in the UI during play.
✅ A red HARDCORE chip renders in the game window's persistent status bar — drawn by the GL overlay itself, outside the auto-hiding HUD, so it is on screen for the entire hardcore session.
Code reference: GlOsd.cs (DrawStatus, the hardcore parameter).
Monetization must include a features matrix…
✅ Fully free, open-source software — no paid tiers, purchases, or quotas. The features matrix is trivially "everything, for everyone." Source: github.com/codingncaffeine/Emutastic-For-Linux.
Publish a listing of every shipped FOSS core, its license, and upstream links.
✅ Met by the product-family Cores page — the Linux build surfaces the same core catalog with the same distribution model: Emutastic ships no core binaries; the in-app Core Manager downloads directly from libretro's buildbot (Linux .so builds).
Non-commercial licenses may not be shipped if there is any commercialization…
✅ Not applicable — non-commercial product, and core binaries are not redistributed by Emutastic (users fetch them from libretro's buildbot directly).
GPL/LGPL/MPL obligations…
✅ Met. Emutastic for Linux is GPL-3.0 (same license as the Windows app) with full source public and LICENSE at the repository root; the OpenEmu controller-art BSD-3 notice ships alongside. LGPL dependencies (libVLC) are dynamically-linked system packages.
Privacy policy with data retention periods, server locations, GDPR compliance details.
✅ Met by this wiki's Privacy Policy (the Linux counterpart of the product-family policy): the author operates no servers and collects no data; no telemetry, analytics, or crash reporting exist. The policy itemises every outbound connection this build makes — RetroAchievements, ScreenScraper, ArcadeDatabase, libretro buildbot + thumbnails, the ES-DE theme catalogue on gitlab.com, SteamGridDB (only with a key the user supplies), the arcade bezel project, GitHub Releases for updates, and the user's own GitHub repository when the optional cloud-save sync is signed in (saves and library data go to an account the user controls; nothing flows to the author) — including which of them run on a timer once the user has opted in.
-
Loading save states in hardcore mode — ✅ Blocked at two layers. See Section B (
RequestLoadState+ExecuteLoadOnEmuThread). - Allowing rewind / slo-mo / frame advance in hardcore — ✅ N/A; the features do not exist.
-
Allowing gameplay-altering cheats in hardcore — ✅ Blocked at the
ExecuteCheatsApplyOnEmuThreadchokepoint; from v1.9.3 the cores' own cheat options are locked too (HardcoreLaunchPolicy); PSP carved out entirely. - Switching to hardcore without a reset — ✅ Impossible by process isolation; rcheevos-demanded resets also reset the game.
-
Non-unique user agent — ✅ Unique
Emutastic/<version>with OS + core segments. - Undisclosed prior identification as another emulator — ✅ None; shared Windows/Linux product identity to be disclosed proactively.
- Shipping non-commercial cores with commercialization — ✅ N/A; non-commercial, cores not redistributed.
- Privacy policy with placeholders/contradictions — ✅ See Section F.
➖ Not applicable. Emutastic is not on the banned list and has no history of shipping cheat tools to hardcore players or evading RA enforcement.
Quick lookup for the hardcore-specific code. All links are to the live main branch.
| Concern | Location | Symbol |
|---|---|---|
| Hardcore session snapshot | EmulatorSession.cs |
_raHardcoreActive (set in InitRetroAchievements), RaHardcoreActive
|
| Save-state load gate (chokepoint) | EmulatorSession.cs |
RequestLoadState |
| Save-state load gate (boot-race belt) | EmulatorSession.cs |
ExecuteLoadOnEmuThread |
| Load-button hide + hit-test gate |
EmulatorSession.cs / GlOsd.cs
|
present-loop status hit-test / DrawStatus
|
| Cheat apply gate (chokepoint) | EmulatorSession.cs |
ExecuteCheatsApplyOnEmuThread |
| Cheat menu gating | EmulatorSession.cs |
buildCogMain cheatable predicate |
| Launch decision (setting, PSP and ROM-patch carve-outs) |
HardcoreLaunchPolicy.cs / EmulatorSession.cs
|
Evaluate, DemotionMessage / DecideHardcoreForLaunch, _raHardcoreIntent
|
| Cheat core-option lock |
HardcoreLaunchPolicy.cs / EmulatorSession.cs
|
IsCheatCoreOption, LockedValue / HandleGetVariable
|
| Self-test of the pure rules | HardcoreSelfTest.cs |
--selftest-hardcore |
| rcheevos reset → game reset | EmulatorSession.cs |
ResetRequested subscription |
| Game reset → runtime reset | EmulatorSession.cs |
RaReset (emu loop, next to retro_reset) |
| Disc swap → media verification |
EmulatorSession.cs / DiscPlaylist.cs / RetroAchievementsClient.cs
|
SwapToNextDisk, RaChangeMedia / ResolveDiscPath / ChangeMedia
|
| Marshaled-layout guard |
RcheevosInterop.cs / native/rcheevos/checkabi.c
|
VerifyAbi |
.cheevos progress side-car |
EmulatorSession.cs |
FinalizeSave / RequestLoadState / ExecuteLoadOnEmuThread
|
| Side-car rename/delete | MainWindow.axaml.cs |
save-state card context menu |
| HARDCORE status chip | GlOsd.cs |
DrawStatus (hardcore parameter) |
| User-Agent builder | EmutasticUserAgent.cs |
Build() |
| Core segment stamping | RetroAchievementsClient.cs |
SetCoreContext |
| Hardcore default | ConfigurationModels.cs |
HardcoreMode = true |
| In-game challenge/progress indicators |
GlOsd.cs / EmulatorSession.cs
|
DrawRaIndicators / SetRaChallenge, SetRaProgress
|
Console Notes
- Nintendo 64
- Nintendo 3DS
- GameCube
- Sega Saturn
- Dreamcast
- PlayStation
- PlayStation Portable
- TurboGrafx-CD
- Neo Geo
- Arcade
- Vectrex
- Philips CD-i
- Atari Jaguar
Features
- Artwork & Metadata
- Cheats
- Cloud Sync
- Controllers
- Disc-Based Systems
- Disk Swapping
- Portable Mode
- RetroAchievements
- ROM Hacks
- Hardcore Compliance
Technical
Platforms
- Other Distributions
- Differences from Windows
- macOS (Apple Silicon)
- Emutastic on Windows
- Emutastic for macOS
Legal