CHDSharp 1.2.0

dotnet add package CHDSharp --version 1.2.0
                    
NuGet\Install-Package CHDSharp -Version 1.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CHDSharp" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CHDSharp" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="CHDSharp" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CHDSharp --version 1.2.0
                    
#r "nuget: CHDSharp, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CHDSharp@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CHDSharp&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=CHDSharp&version=1.2.0
                    
Install as a Cake Tool

.NET NuGet License

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.


What's New in v1.2.0

  • CD/GD-ROM track (TOC) parsing — Full track layout via Tracks property backed by ChdTocParser, exposing ChdTrackInfo with track type, sector sizes, pregap/postgap, and GD-ROM support. Includes GenerateCueSheet(), GenerateGdiDescriptor(), ExportToc(), ExtractToDirectory().
  • UnitBytes property — Derives sector size from metadata for all CHD versions: V5 reads from header, V1-V4 detects HDD (512B) or CD (2448B) from metadata tags
  • New enumsChdTrackType (matches MAME cdrom.h: Mode1, Mode2, Audio, etc.) and ChdSubType (None, Normal, Raw)
  • Deterministic reproducible builds — Byte-for-byte reproducible via <Deterministic>true</Deterministic> with embedded SourceLink and debug symbols

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
}

Get CD/GD-ROM track layout (TOC)

ChdFile.Open("game.chd", out var chd);
using (chd)
{
    if (chd.Tracks is not { } tracks) return;
    foreach (var track in tracks)
    {
        Console.WriteLine($"Track {track.TrackNumber}: {track.GetTypeString()} " +
                          $"{track.Frames} frames, pregap={track.PreGap}");
    }
}

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.
Open(Stream s, bool leaveOpen, ChdFile? parent, out ChdFile? chd) From stream with external parent.
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.
GenerateCueSheet string GenerateCueSheet(string) Generate CUE sheet for CD CHDs.
GenerateGdiDescriptor string GenerateGdiDescriptor(string[]) Generate GDI descriptor for GD-ROM CHDs.
ExportToc string ExportToc() Export TOC as human-readable text.
ExtractToDirectory List<string> ExtractToDirectory(string, string) Extract CHD tracks to directory. Returns file paths.
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.
UnitBytes uint Unit size for parent block address translation. V5 reads from header; V1-V4 derives from metadata (HDD BPS, CD 2448, or HunkBytes).
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.
Tracks IReadOnlyList<ChdTrackInfo>? CD/GD-ROM track layout. null if not a CD/GD-ROM image.
IsCd bool True if CD-ROM track metadata present.
IsGdRom bool True if GD-ROM (Sega Dreamcast) image.
IsDvd bool True if DVD metadata present.
IsHdd bool True if hard disk geometry metadata present.
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.

ChdTrackInfo — Track record

Property Type Description
TrackNumber int 1-based track number.
TrackType ChdTrackType CD track data type (Mode1, Audio, etc.).
SubType ChdSubType Subcode type for this track.
DataSize int Bytes per sector (2048, 2352, etc.).
SubSize int Subcode bytes per sector (0 or 96).
Frames int Number of frames in this track.
ExtraFrames int Padding frames for 4-frame alignment.
PreGap int Pregap frames (index 00 to index 01).
PostGap int Postgap frames.
PreGapType ChdTrackType Track type of pregap sectors.
PreGapSubType ChdSubType Subcode type of pregap sectors.
PreGapDataSize int Bytes per sector for pregap data.
PreGapSubSize int Subcode bytes per sector for pregap.
PadFrames int GD-ROM pad frames (GD-ROM only).
StartFrame ulong CHD frame offset where this track starts.
GetTypeString() string e.g. "MODE1/2048", "AUDIO".
GetSubTypeString() string e.g. "RW", "RW_RAW", "NONE".

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-safeChdFile instances must be used from a single thread
  • No lossy video — Lossy AVHuff video variants are not supported
  • Stream must be seekable — for ChdFile.Open stream 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 chdman reference
  • libchdr — C reference library by Romain Tisseraud
  • ZstdSharp — pure C# Zstd decompressor
Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.

Version Downloads Last Updated
1.2.0 190 7/23/2026
1.1.0 100 7/18/2026
1.0.0 104 7/17/2026

v1.2.0: CD/GD-ROM track (TOC) parsing with full track layout and sector type information via ChdTrackInfo and ChdTocParser. New ChdTrackType and ChdSubType enums. Improved UnitBytes property detects sector sizes from metadata for all CHD versions. Centralized versioning via Directory.Build.props. Deterministic and reproducible builds with embedded SourceLink.