CHDSharp 1.0.0
See the version list below for details.
dotnet add package CHDSharp --version 1.0.0
NuGet\Install-Package CHDSharp -Version 1.0.0
<PackageReference Include="CHDSharp" Version="1.0.0" />
<PackageVersion Include="CHDSharp" Version="1.0.0" />
<PackageReference Include="CHDSharp" />
paket add CHDSharp --version 1.0.0
#r "nuget: CHDSharp, 1.0.0"
#:package CHDSharp@1.0.0
#addin nuget:?package=CHDSharp&version=1.0.0
#tool nuget:?package=CHDSharp&version=1.0.0
CHDSharpLib
Pure C# read-only CHD (Compressed Hunks of Data) library — V1–V5, all 10 codecs, parent/child chaining, parallel verification, 100% match with MAME chdman.
Fork of RomVault/CHDSharp by Gordon Jefferyes, extended with Zstd, AVHuff, V5 compressed map, random-access API, parent/child chaining, and parallel verification.
Installation
dotnet add package CHDSharp
Targets net8.0, net9.0, and net10.0. No native dependencies — all codecs (except Zstd via the pure-C# ZstdSharp.Port) are implemented from scratch in C#.
Quick Start
Verify a standalone CHD (parallel, fast)
using CHDSharp;
using CHDSharp.Models;
using Stream s = File.OpenRead("game.chd");
var result = Chd.CheckFile(s, "game.chd", deepCheck: true);
if (result.IsSuccess)
Console.WriteLine($"V{result.Version} — SHA1: {result.Sha1Hex}");
else
Console.WriteLine($"Error: {result.Error.GetMessage()}");
Verify a child (differential) CHD against its parent
var result = Chd.CheckFileWithParent("child.chd", "parent.chd");
Random-access reading
var err = ChdFile.Open("game.chd", out var chd);
if (err != ChdError.Chderrnone) return;
using (chd)
{
// Inspect metadata (game name, disc label, etc.)
foreach (var meta in chd.Metadata)
Console.WriteLine(meta.ToString());
// Read a single decompressed hunk
byte[] hunk = new byte[chd.HunkBytes];
chd.ReadHunk(42, hunk);
// Read arbitrary byte range (handles hunk boundaries)
byte[] buf = new byte[1024];
chd.Read(offset: 0x10000, buf, 0, buf.Length);
}
Async random-access reading
var (err, chd) = await ChdFile.OpenAsync("game.chd");
if (err != ChdError.Chderrnone) return;
await using (chd)
{
byte[] hunk = new byte[chd.HunkBytes];
await chd.ReadHunkAsync(42, hunk);
}
Quick file checking
bool isChd = Chd.IsChdFile("game.chd", out uint version);
// isChd=true, version=5 for a V5 CHD
// Or just yes/no:
bool yesNo = Chd.IsChdFile("game.chd");
Decompress entire image to a byte array
ChdFile.Open("game.chd", out var chd);
using (chd)
{
chd.ReadAllBytes(out byte[] image);
// image now contains the full decompressed image
}
Iterate hunks one at a time
ChdFile.Open("game.chd", out var chd);
using (chd)
{
foreach (byte[] hunk in chd.EnumerateHunks())
{
// Process each decompressed hunk; buffer is reused — copy if needed
}
}
Logging
The library uses Microsoft.Extensions.Logging.Abstractions. By default, logging is discarded. To enable logging (e.g., with Serilog):
using Serilog;
using Serilog.Extensions.Logging;
var serilogLogger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.CreateLogger();
Chd.LoggerFactory = new SerilogLoggerFactory(serilogLogger);
// All subsequent Chd/ChdFile operations will log through Serilog
You can use any ILoggerFactory-compatible provider (NLog, Microsoft.Extensions.Logging.Console, etc.).
API Reference
Chd — Static class
| Member | Signature | Description |
|---|---|---|
| LoggerFactory | ILoggerFactory? (static property) |
Set to enable internal logging. |
| TaskCount | int (static property, default 8) |
Number of parallel workers for CheckFile (1-64). Change before calling. |
| CheckFile | ChdResult CheckFile(Stream, string, bool) |
Full parallel verification. Returns error, version, SHA1, MD5. |
| CheckFileWithParent | ChdResult CheckFileWithParent(string, string) |
Verify child CHD against parent. Pass null for second arg for standalone. |
| CheckHeader | bool CheckHeader(Stream, out uint length, out uint version) |
Sniff magic + version. Stream must be at position 0. |
| IsChdFile | bool IsChdFile(string) / bool IsChdFile(string, out uint) |
Quick check if a file is a valid CHD. |
ChdResult — Verification result
| Property | Type | Description |
|---|---|---|
Error |
ChdError |
Error code (ChderrNone on success). |
Version |
uint? |
CHD version (1-5). |
Sha1 |
byte[]? |
SHA1 hash from header. |
Md5 |
byte[]? |
MD5 hash from header. |
IsSuccess |
bool |
True if Error == ChderrNone. |
Sha1Hex |
string |
SHA1 as lowercase hex, or "(none)". |
Md5Hex |
string |
MD5 as lowercase hex, or "(none)". |
Supports deconstruction: var (err, ver, sha1, md5) = result;
ChdFile — Random-access reader
All Open overloads seek from the start. The reader is not thread-safe — serialize all calls.
Static factory methods
| Overload | Description |
|---|---|
Open(string path, out ChdFile? chd) |
Standalone CHD from disk. |
Open(string path, string parentPath, out ChdFile? chd) |
Child CHD; parent opened and owned internally. |
Open(string path, ChdFile? parent, out ChdFile? chd) |
Child with external parent. Pass null for standalone. |
Open(Stream s, bool leaveOpen, out ChdFile? chd) |
From seekable stream. |
OpenAsync(...) |
Async overloads for all Open variants. |
Instance methods
| Method | Signature | Description |
|---|---|---|
| ReadHunk | ChdError ReadHunk(uint, byte[]) |
Decompress a single hunk. |
| Read | ChdError Read(ulong, byte[], int, int) |
Read byte range. Caches last hunk. |
| ReadAllBytes | ChdError ReadAllBytes(out byte[]) |
Decompress entire image to a byte[]. |
| EnumerateHunks | IEnumerable<byte[]> EnumerateHunks() |
Yield each decompressed hunk. Buffer reused — copy if needed. |
| ReadHunkAsync | Task<ChdError> ReadHunkAsync(uint, byte[]) |
Async hunk read. |
| ReadAsync | Task<ChdError> ReadAsync(ulong, byte[], int, int) |
Async byte range read. |
| Dispose / DisposeAsync | void Dispose() / ValueTask DisposeAsync() |
Release stream and parent. |
Properties
| Property | Type | Description |
|---|---|---|
Version |
uint |
CHD format version (1–5). |
TotalBytes |
ulong |
Decompressed image size. |
HunkBytes |
uint |
Size of one hunk. |
HunkCount |
uint |
Total number of hunks. |
Sha1 |
byte[]? |
Combined SHA1 (image + metadata). |
RawSha1 |
byte[]? |
Raw image data SHA1. |
Md5 |
byte[]? |
Raw image MD5. |
RequiresParent |
bool |
True if differential child. |
IsChild |
bool |
Alias for RequiresParent. |
Metadata |
IReadOnlyList<ChdMetadataEntry> |
CHD metadata entries (game name, disc type, etc.). Lazy-loaded. |
ChdMetadataEntry — Metadata record
| Property | Type | Description |
|---|---|---|
Tag |
string |
4-char tag (e.g. "GAME", "DISC", "HARD"). |
Data |
byte[] |
Raw metadata bytes. |
IsText |
bool |
True if data is printable ASCII. |
GetText() |
string |
ASCII text representation. |
ToString() |
string |
Human-readable: GAME: gauntlet. |
ChdError.GetMessage() — Extension method
ChdError err = ChdFile.Open("bad.chd", out _);
Console.WriteLine(err.GetMessage());
// "File not found"
Supported Formats
CHD Versions
| Version | Header | Map Type | Status |
|---|---|---|---|
| V1 | 76 bytes | Self-hunk dedup via offset | ✅ |
| V2 | 80 bytes | Self-hunk dedup via offset | ✅ |
| V3 | 120 bytes | CRC32 map, self-hunk | ✅ |
| V4 | 108 bytes | CRC32 map, parent chain | ✅ |
| V5 | 124 bytes | CRC16 map, compressed/uncompressed map, RLE, parent/unit chain | ✅ |
Compression Codecs
| Codec | FourCC | CD Variant | Implementation |
|---|---|---|---|
| Zlib (Deflate) | zlib |
cdzl |
System.IO.Compression (managed) |
| LZMA | lzma |
cdlz |
Custom pure C# LZMA decoder |
| Huffman | huff |
— | Custom pure C# Huffman decoder |
| FLAC | flac |
cdfl |
Custom pure C# FLAC decoder (16-bit stereo/mono) |
| Zstd | zstd |
cdzs |
ZstdSharp.Port (pure C#) |
| AVHuff | avhu |
— | Custom pure C# AV Huffman decoder |
Common Usage Patterns
Pattern 1: Fast batch verification
var files = Directory.GetFiles(@"D:\CHD", "*.chd");
foreach (var path in files)
{
using var s = File.OpenRead(path);
var result = Chd.CheckFile(s, Path.GetFileName(path), deepCheck: true);
Console.WriteLine($"{Path.GetFileName(path)}: {result.Error.GetMessage()}");
}
Pattern 2: Universal verification (standalone or child)
static ChdError UniversalVerify(string path, string? parentPath = null)
{
if (parentPath != null)
{
var r = Chd.CheckFileWithParent(path, parentPath);
return r.Error;
}
using var s = File.OpenRead(path);
var result = Chd.CheckFile(s, Path.GetFileName(path), deepCheck: true);
if (result.Error == ChdError.Chderrrequiresparent)
Console.WriteLine(" -> requires parent CHD");
return result.Error;
}
Pattern 3: Working with child (differential) CHDs
// Option A: Let the library manage parent lifetime
ChdFile.Open("child.chd", "parent.chd", out var child);
child?.Dispose();
// Option B: Share parent across multiple children
ChdFile.Open("parent.chd", out var parent);
using (parent)
{
foreach (var childPath in new[] { "child1.chd", "child2.chd" })
{
ChdFile.Open(childPath, parent, out var c);
using (c) { /* read hunks */ }
}
}
Pattern 4: Computing SHA1 while streaming
using var sha1 = System.Security.Cryptography.SHA1.Create();
ChdFile.Open("game.chd", out var chd);
using (chd)
{
var buf = new byte[chd.HunkBytes];
var remaining = chd.TotalBytes;
ulong offset = 0;
while (remaining > 0)
{
var chunk = (int)Math.Min((ulong)buf.Length, remaining);
chd.Read(offset, buf, 0, chunk);
sha1.TransformBlock(buf, 0, chunk, null, 0);
offset += (ulong)chunk;
remaining -= (ulong)chunk;
}
sha1.TransformFinalBlock([], 0, 0);
Console.WriteLine($"SHA1: {Convert.ToHexString(sha1.Hash!).ToLower()}");
}
Performance
| Scenario | Throughput | Notes |
|---|---|---|
CheckFile(deepCheck: true) |
~200–400 MB/s | 8 parallel threads, bounded memory |
CheckFile(deepCheck: false) |
> 1 GB/s | Header-only |
ChdFile.Read() sequential |
~150–300 MB/s | Single-threaded, hunk-cached |
ChdFile.ReadHunk() random |
~50–150 MB/s | Per-hunk re-decompression |
Tuning parallelism
Chd.TaskCount = 16; // set before calling CheckFile
var result = Chd.CheckFile(s, name, deepCheck: true);
Architecture
┌────────────────────────────────────────────────────┐
│ Public API │
│ Chd.CheckFile() ChdFile.Open() ChdFile.Read() │
├────────────────────────────────────────────────────┤
│ CHDHeaders → Parse V1–V5 headers + maps │
│ CHDBlockRead → Dispatch hunk → codec delegate │
│ CHDReaders → Decompression delegates (10) │
│ CHDCodec → Per-codec reusable state │
│ CHDMetaData → Metadata traversal + SHA1 check │
├────────────────────────────────────────────────────┤
│ Utils/ │
│ CRC · CRC16 · BitStream · HuffmanDecoder · │
│ HuffmanDecoderRLE · BigEndian · ArrayPool · cdRom │
├────────────────────────────────────────────────────┤
│ LZMA/ │
│ LzmaStream · LzmaDecoder · RangeCoder · │
│ LzBinTree · LzInWindow · LzOutWindow │
├────────────────────────────────────────────────────┤
│ Flac/ │
│ AudioDecoder · FlacFrame · FlacSubframe · │
│ BitReader · LPC · RiceContext · WindowFunction │
├────────────────────────────────────────────────────┤
│ ZstdSharp.Port (NuGet) │
└────────────────────────────────────────────────────┘
Building
dotnet build CHDSharpLib/CHDSharpLib.csproj -c Release
dotnet pack CHDSharpLib/CHDSharpLib.csproj -c Release
Dependencies
| Package | Version | Purpose |
|---|---|---|
| ZstdSharp.Port | 0.8.8 | Pure C# Zstd decompression |
| Microsoft.Extensions.Logging.Abstractions | 8.0.3 (net8.0) / 9.0.9 (net9.0) / 10.0.10 (net10.0) | Pluggable logging (optional) |
Limits
- Read-only — Cannot create, modify, or repack CHD files
- Not thread-safe —
ChdFileinstances must be used from a single thread - No lossy video — Lossy AVHuff video variants are not supported
- Stream must be seekable — for
ChdFile.Openstream overloads - V6+ not supported — MAME has not released a V6 format
License
MIT License — see LICENSE.
Acknowledgments
- Gordon Jefferyes — original C# CHDSharp implementation
- MAME — CHD format specification and
chdmanreference - libchdr — C reference library by Romain Tisseraud
- ZstdSharp — pure C# Zstd decompressor
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.10)
- ZstdSharp.Port (>= 0.8.8)
-
net8.0
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.3)
- ZstdSharp.Port (>= 0.8.8)
-
net9.0
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.9)
- ZstdSharp.Port (>= 0.8.8)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on CHDSharp:
| Package | Downloads |
|---|---|
|
VideoGameFileSystemParser
Video game file system parsing library for console disc images. Supports ISO 9660, UDF, XDVDFS, OperaFS, CD-i, and other formats through CHD, ISO and raw sector readers. |
|
|
RetroAchievementsSharp
A native C# port of the RetroAchievements hashing engine (rcheevos/RAHasher, MIT) that produces 100% identical hashes to the original for every supported console. Supports cartridge, disc (.cue/.bin/.iso/.gdi/.chd/.rvz/.wia), zip, m3u, Arcade, DOS and 3DS inputs. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Initial release: CHD V1-V5 reading, all 10 codecs, parent/child chaining, parallel verification, random-access API, async API, and metadata support.