A native C# port of the RAHasher hashing engine (rcheevos
40d916d → 12.4.0) that produces 100% identical hashes to the original
for every supported console — the same hashes the
RetroAchievements website and its clients
use to identify ROMs and disc images.
- Library —
RetroAchievementsSharp(NuGet), targetsnet8.0,net9.0, andnet10.0; GPL-2.0-or-later. - CLI —
RetroAchievementsSharp.Cli.exe, byte-identical in behavior toRAHasher 1.8.3, plus convenience subcommands (scan,identify,consoles,checkkeys,fetch-db). - Verified — 415/415 fast tests + 172/172 slow (parity, real-ROM, RVZ, published-DB) green on each supported TFM, including a parity harness against source-built C oracles, RVZ-vs-ISO equality on real GameCube/Wii discs, and spot checks against the published RetroAchievements game database.
dotnet add package RetroAchievementsSharp
Works with the .NET 8, 9, and 10 SDKs/runtimes on Windows, Linux, macOS, x64 and arm64.
Requires .NET 8, 9, or 10 (older runtimes are not supported). Add the package:
dotnet add package RetroAchievementsSharp
All API entry points live in the RetroAchievementsSharp namespace; every
hash is a 32-character lowercase hex string that matches the hash published
on retroachievements.org for the same file.
using RetroAchievementsSharp;
if (RcHash.GenerateFromFile(out string hash, ConsoleIds.RcConsoleNintendo, "game.nes"))
Console.WriteLine(hash); // e.g. "a3f5c0f8e1b2d9a4c7d6e5f4a3b2c1d0"GenerateFromFile returns true when the file hashed successfully. The
ConsoleIds constants cover every supported console
(RcConsoleGameboy, RcConsoleMegaDrive, RcConsolePlaystation, …).
byte[] data = File.ReadAllBytes("rom.bin");
if (RcHash.GenerateFromBuffer(out string hash, ConsoleIds.RcConsoleGameboy, data, data.Length))
Console.WriteLine(hash);When you don't know the console, use the iterator API — it tries every console's handler in the engine's exact table order and returns the first match:
var iterator = new RcHashIterator();
HashIterator.InitializeIterator(iterator, "Super Mario (USA).sfc", null, 0);
while (HashIterator.Iterate(out string hash, iterator) != 0)
Console.WriteLine(hash); // the first hash that matched a console
HashIterator.DestroyIterator(iterator);A single file can produce several hashes (e.g. a multi-disc .m3u), which
is why Iterate is a loop.
Discs hash directly — no conversion needed. CHD is read via CHDSharp; GameCube/Wii RVZ/WIA images are decoded live via RVZSharp:
if (RcHash.GenerateFromFile(out string hash, ConsoleIds.RcConsolePlaystation, "disc.cue"))
Console.WriteLine(hash);
if (RcHash.GenerateFromFile(out hash, ConsoleIds.RcConsoleGamecube, "game.rvz"))
Console.WriteLine(hash);
if (RcHash.GenerateFromFile(out hash, ConsoleIds.RcConsoleWii, "game.chd"))
Console.WriteLine(hash);Call Hash3Ds.InitHash3Ds(systemDir) once with a directory containing
aes_keys.txt (and optionally seeddb.bin for seed-encrypted titles):
Hash3Ds.InitHash3Ds(@"C:\RetroArch\system");
if (RcHash.GenerateFromFile(out string hash, ConsoleIds.RcConsoleNintendo3Ds, "game.cia"))
Console.WriteLine(hash);GenerateFromFile/GenerateFromBufferreturnfalse(never throw) on unsupported files, missing 3DS keys, or hashing failures.- The engine reports errors through the message callbacks (see
RcHash.InitErrorMessageCallback) — the CLI wires these to Serilog. HashIterator.Iteratereturns0when no console accepted the file.
- Global configuration is process-wide —
Hash3Ds.InitHash3Dsand the custom filereader/cdreader registrations (RcHash.InitCustomFilereader,RcHash.InitCustomCdreader) are global state, matching the C engine. Initialize once at startup; concurrent hashing from multiple threads is not supported. - Full API reference: https://purelogiccode.github.io/RetroAchievementsSharp/reference/public-api/. For exact engine behavior (64 MiB cap, header-stripping rules, track selection, verbose messages), see the documentation and known quirks.
Raw ROMs (all cartridge consoles), .zip (pre-loaded ROM, Arduboy FX,
DOSZ/Zip64/DOSC), .m3u playlists, discs (.cue/.bin/.iso/.gdi/.chd,
GameCube/Wii .rvz/.wia),
3DS .cia/.3ds/.3dsx (keys via -s / Hash3Ds.InitHash3Ds), and
Neo Geo .neo carts. Console list: NES/Famicom, SNES/SFC, N64, GB/GBC/GBA,
Master System, Mega Drive/Genesis, Game Gear, 32X, SG-1000, PCE/TG-16,
PCE-CD, Saturn, Sega CD, Dreamcast, PS1, PS2, PSP, 3DO, PC-FX, Jaguar(+CD),
Neo Geo Pocket(+Color), Neo Geo CD, NDS/DSi/3DS — the full rcheevos console
table (~59 consoles), including classic microcomputers.
Identical to RAHasher 1.8.3:
RetroAchievementsSharp [-v] [-s systempath] system filepath...
-v— verbose messages for debugging-s systempath— directory withaes_keys.txt/seeddb.bin(3DS)system— console key (case-insensitive) or numeric id;?auto-detects by trying every consolefilepath— file(s) to hash; may contain wildcards in the filename; multiple files hash each in turn
Examples:
RetroAchievementsSharp NES game.nes
RetroAchievementsSharp PS1 disc.cue
RetroAchievementsSharp -s C:\RetroArch\system 3DS game.cia
Supported input formats: raw ROMs, .zip (pre-loaded ROM / Arduboy FX /
DOSZ), .m3u playlists, .cue/.bin/.iso/.gdi discs, .chd discs, 3DS
.cia/.3ds/.3dsx (keys required via -s), and .neo Neo Geo carts
(Geolith format, hashed by ROM content). Exit codes: 0 success,
1 any failure.
Quirk faithfully reproduced from the original: console keys are only accepted for consoles with a non-empty RA group; NULL-group consoles (
Oric,TI83,TIC-80,ESCV,DOS,3DS, …) must be addressed by numeric id — e.g.RetroAchievementsSharp 62 game.cia. (The C'sfind_console_idfalls back toatoi, so the key3DSwould silently resolve to console 3!)
(not present in RAHasher 1.8.3 — the legacy positional interface above is unchanged) hashes each file with per-file console auto-detection and emits one manifest row per file:
RetroAchievementsSharp scan [options] <path>...
-f, --format <text|csv|json> output format (default: text)
-s <systempath> supplementary files directory (3DS keys)
--match <db.json> RetroAchievements database snapshot
--move <dir> move matched files into <dir>/<console-key>/
--dry-run preview --move without moving anything
--no-recursive do not descend into subdirectories
-h, --help show help
Each file is auto-detected the same way the ? system key works for a
single file; matched rows append => <Title> (ID <id>). The manifest goes
to stdout, the summary (Scanned N file(s): X hashed, Y failed) to stderr;
exit code 0 when every file hashed, 1 when any failed.
RetroAchievementsSharp consoles [--format text|csv|json]— dump the console table.RetroAchievementsSharp checkkeys [-s <systempath>]— validate 3DS key files.RetroAchievementsSharp identify <system> <file> [--db <RetroAchievements.json> | --user <u> --api-key <k>]— hash one file and resolve it to a game with achievements (local snapshot or live API).RetroAchievementsSharp fetch-db <url-or-path> [--out <file>]— download a database snapshot, validate it, save atomically.
Full details for every subcommand: docs/getting-started/usage.md.
dotnet build RetroAchievementsSharp.sln -c ReleaseRequires any .NET 8+ SDK. All three projects multi-target net8.0;net9.0;net10.0
(CHDSharp, VideoGameFileSystemParser 1.2.0 and Serilog 4.4.0 all ship
portable libs for these TFMs). Build a single target with
-f net8.0 (faster).
Self-contained single-file executables (no runtime needed on the target):
dotnet publish RetroAchievementsSharp.Cli -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o artifacts/win-x64
dotnet publish RetroAchievementsSharp.Cli -c Release -r linux-x64 --self-contained true -p:PublishSingleFile=true -o artifacts/linux-x64Produces RetroAchievementsSharp.Cli.exe (Windows) / RetroAchievementsSharp (Linux) in artifacts\<rid>\.
(The parity suite's oracle is a Windows PE, so Tier-2 parity runs on Windows;
on Linux the parity cases skip and the ported vectors still run.)
dotnet pack RetroAchievementsSharp -c Release
# publishes RetroAchievementsSharp.1.0.0.nupkg + .snupkg (default: bin/Release); make artifacts/:
dotnet pack RetroAchievementsSharp -c Release -o artifactsThe package includes the net8/9/10 assemblies, XML docs, the GPL-2.0-or-later license and third-party notices, SourceLink/symbols, and runs NuGet package validation on every pack. Publishing to NuGet.org:
dotnet nuget push artifacts/RetroAchievementsSharp.1.0.0.nupkg --api-key <key> --source https://api.nuget.org/v3/index.jsonSee publishing.md for details.
Tests are split into two projects:
dotnet test RetroAchievementsSharp.sln -c Release # fast suite, all TFMs (net8, net9, net10)
dotnet test RetroAchievementsSharp.sln -f net10.0 # fast suite, one TFM
dotnet test RetroAchievementsSharp.Slow.Tests -c Release # slow suite (manual — not in the sln)The fast suite (RetroAchievementsSharp.Tests, in the solution — 415 tests, seconds)
runs every ported rcheevos test/rhash vector (cartridge, disc, cdreader,
zip, m3u, handler order), synthetic 3DS/CHD fixtures, and CLI/engine unit
tests. The slow suite (RetroAchievementsSharp.Slow.Tests, kept out of the solution —
172 tests, minutes) is run manually and covers:
- Tier-2 parity harness vs. a source-built C oracle (rcheevos 12.4.0, falling back to the pinned 1.8.3 build): 90 generated cases + all CLI arg modes; stdout/stderr + exit codes byte-identical.
- RVZ validation — real GameCube/Wii RVZ images hashed live through RVZSharp must equal the DolphinTool-converted ISO hash (6 files; skips when the libraries or DolphinTool are absent).
- Real-ROM parity — first files of 60 user library directories vs. the pinned 1.8.3 binary (skipped-with-note when the libraries or oracle are absent).
- Published-hash spot checks — real ROM samples hashed and looked up in a RetroAchievements game-database snapshot, asserting official matches per library (skips when the snapshot isn't present).
See parity-evidence.md for the coverage table and known-quirks.md for the unsupported-format behavior (ROM encoding not part of rcheevos).
Current: 415/415 fast + 172/172 slow green on net8.0, net9.0, net10.0 —
byte-identical CLI output between RetroAchievementsSharp.Cli.exe and the C oracles,
including verbose mode, error paths, and exit codes; RVZ-vs-ISO equality
6/6 (GameCube and Wii); real-ROM parity 61/61; published-hash spot-check
15/15 library/console pairs. The harness has caught and fixed real port bugs
(arg-count guard crash, wildcard path construction, usage banner blank line,
silent merge_callbacks bug) — parity is never "accepted" as a difference;
it's asserted.
Full docs — architecture, engine deep-dives, testing, known quirks, and the release-sync playbook for absorbing future rcheevos releases:
- Docs site (GitHub Pages) — https://purelogiccode.github.io/RetroAchievementsSharp/,
built from docs with MkDocs Material (left-nav sidebar);
every push to
docs/ormkdocs.ymlredeploys automatically. - Wiki — github.com/purelogiccode/RetroAchievementsSharp/wiki, hand-maintained quick-reference mirror of the site.
- LeXofLeviafan — author of the RALibretro RAHasher CLI this project is behaviorally compatible with. We use his RAHasher binaries as the reference oracle in our parity test suite (byte-identical output vs. the C original is asserted on every run). Thank you!
- RetroAchievements / rcheevos — the hashing engine, ported 1:1 (MIT).
GPL-2.0-or-later — see LICENSE and THIRD-PARTY-NOTICES.md. Copyright (c)
2026 Peterson Fernandes and Pure Logic Code. The rcheevos engine is ported
1:1 (MIT, credited in the notices); RVZ/WIA hashing links RVZSharp which is
GPL-2.0-or-later (Dolphin-derived). Program.cs, FileUtil.cs, Hash3DS.cs,
and ChdCdReader.cs are new implementations written for behavior parity with
the GPL-3.0 RALibretro RAHasher by LeXofLeviafan
(used as read-only reference, never copied; the GPL binary and sources live
in References\ only and are not shipped).
See CHANGELOG.md.