Pure C# CHD (Compressed Hunks of Data) reader and writer — the disk-image format used by MAME for arcade hard disks, CD/GD-ROMs, DVDs, and laserdisc A/V content.
Supports every CHD format version (V1–V5), all 10 compression codecs, parent/child differential chains, parallel verification, metadata, extraction, and CHD creation — with zero native dependencies and a 100% byte-for-byte match with MAME chdman.
Fork of RomVault/CHDSharp by Gordon Jefferyes — extended with Zstd, AVHuff, V5 compressed maps, random access, async APIs, parent/child chaining, parallel verification, seekable stream, span reads, read-ahead decompression, lazy parent resolution, encoding capabilities, and a comprehensive test suite.
- Zero code-inspection warnings -
jb inspectcodenow reports no warnings or errors for the whole solution (308 before). The vendored codec sources are excluded from style inspections in.editorconfigand remain untouched forchdmanparity; the first-party fixes removed redundant null checks and dead tester state. - CI deploys the docs - after the three-OS build/test matrix passes on
main, CI regenerates the GitHub wiki fromdocs/and requests a Pages rebuild; the tag pipeline does the same on every release. - Dark docs theme and badge rows - the documentation site is dark by default with a persisted light/dark toggle, and every README starts with its badge row.
- Verification - full
dotnet testacrossnet8.0/net9.0/net10.0: 14,004/14,004 passed; full battle harness: 3,174/3,174 checks, byte-identicalchdmanoutput.
- Single-bin multisession cues with an unusable lead-out now encode exactly like chdman - a
REM LEAD-OUTat or before the session's last track (REM LEAD-OUT 00:00:00is common in real IsoBuster/ImgBurn rips) used to trim the track to a negative length and abort (the session lead-out values leave a negative length (-11400 frames)). MAME compares auint32_tframe count against a signedint32_tend frame, so the negative value wraps and the trim is skipped; the parser now performs the same unsigned comparison. Byte-identical tochdman createcdon the three reported failure shapes. - A pad larger than its track no longer zeroes the track - the CD reader now mirrors chdman's unsigned pad offset (
chdman.cpp:471-472): when the pad is larger than the track the offset wraps below the track start, the pad region is unreachable, and every frame is read from the source file. The GD-ROM data/pad/split ordering now matches chdman as well. - Verification - 3 new
chdman createcdbyte-parity fixtures plus parser regression tests;CHDSharpEncoderTest454/454,CHDSharpTest4214/4214, battle harness--quick905/905 checks.
- First fully automated release - one tag builds the six platform bundles, the GitHub release, the NuGet package, the wiki and the docs site (see
.github/workflows/release.ymlandscripts/package-release.ps1). - Fixed built-in update check - the CLI now links directly to the bundle for your platform (
CHDSharp_<os>-<arch>_v<version>.zip); it previously looked up an asset name that could never match and always fell back to the releases page. - Download the CLI - bundles for Windows/Linux/macOS on x64 and ARM64 are attached to every release; each contains a framework-dependent single-file binary plus
LICENSE.txt,README.mdandWhatsNew.md(requires the .NET 10 runtime). - Documentation - the
docs/folder is now published twice: as the documentation site and as the GitHub wiki, both with the same side menu; dark theme by default. - Housekeeping - test-spawned CLI runs no longer file bug reports; badges on every README.
- Multisession cue parity - ports MAME's
REM SESSION/REM LEAD-IN/REM LEAD-OUT/ session-pregap layout rules (IsoBuster/ImgBurn single-bin trim + zero-filled inter-session gaps, Redump/DiscImageCreator synthesized lead-in/out and.imgheuristic). Multisession cues now produce byte-identical CHDs tochdman createcd; malformed lead-out values are rejected with a clear error instead of being encoded as a wrong image. Verification: 3 chdman byte-parity fixtures + unit tests, suite 451/451.
- Redump multi-cue Dreamcast parity - a
.cuewithREM SINGLE-DENSITY AREA/REM HIGH-DENSITY AREAmarkers (MAME'sis_gdicue) now gets the full GD-ROM layout: pregaps move onto the previous track as split regions, the last single-density track pads up to LBA 45000, and the CHD is written withCHGDmetadata. Verified byte-identical tochdman createcdon a real 1.19 GB Redump multi-cue, with a byte-perfectchdman extractcdround-trip of all three tracks. - cdrdao TOC
STARTparity - a bareSTARTline (cdrdao's documented form) is now accepted as "no pregap", matching MAME 0.289'sparse_tocexactly (msf_to_frames("")is 0). Previously it threwMalformed START command, failing cdrdao.tocfiles thatchdmanconverts fine. Locked down with a parse regression test and byte-parity tests againstchdman createcdfor bothSTARTandSTART 00:00:00.
Consolidates all changes since v1.4.2 - the last chdman byte-parity gaps are closed:
- Partial-tail raw-encode fix -
EncodeRaw/createrawon inputs of ≥ 513 hunks whose length is not hunk-aligned could produce a self-consistent-but-wrong CHD (stale compression ring-buffer slots). Fixed and locked down with guardrail tests: a synthetic probe, partial-last-hunk unit tests, and a resized long-tail battle case. - FLAC encoder byte parity - fixed-predictor analysis window + native
cosftukey-window rounding. The vendored FLAC encode is now byte-identical tochdmanon the verification corpora: a 7.8 GB XGD2 image (1,912,816 hunks, default codec list), a 100 MB partial-tail slice, and three single-hunk repros. - Earlier byte-parity fixes in the cycle - stale work-buffer tail + LZMA match-finder insert parity.
- Diagnostics - detailed decompression-failure messages (hunk index, codec, CRC/truncation reasons) and chdman-style progress + speed-meter output.
- Tooling -
CHDBattleTestmerged into a singleCHDSharpBattleTestharness (3174/3174 vs MAME 0.289); analyzer/style hardening (regex timeouts,CultureInfofixes); new benchmark suite covering all 11 codecs (decode + encode) and a chdman comparison harness. - Status -
CHDSharpTest12,648/12,648 (1,308/1,308 per-TFM aggregate),CHDSharpBattleTest3174/3174.
- Final
chdmanparity — closes the last byte-parity gaps against MAME 0.289 (battle harness still green, 2907/2907 synthetic + 3003/3003 real-world):- LZMA raw-encode now replicates
chd.cpp's 1 MiB compression work buffer exactly — partial final hunks keep the stale slot bytes from the previous cycle and fold into the raw SHA-1 (hash-update-order parity). createhdsize parsing matcheschdman(sscanf %I64uleading digits only —"512K"= 512 bytes) and small sizes round up to the guessed-CHS product.- Laserdisc
createld/extractldAVI output is now byte-identical. - Strict
info/verifyoption validation (duplicate-i, per-command sets) withchdman-consistent exit codes; DVD metadata corrected to the single-NUL byte payload (std::stringlength()+1).
- LZMA raw-encode now replicates
- CLI fixes —
createrawaccepts--hunk-size/--unit-sizealiases,extractcd%ttemplate regex fixed, correctedhashhelp texts, and a new full CLI reference (docs/cli-commands.md). - Docs — CHD format history added to
docs/chd-format.md, wiki home updated, docs URL fixed.
- Complete
chdmanparity — all 16 audited discrepancies (D1–D16) plus EdgeGaps §1–§3 fixed and verified against MAME 0.289. Battle-test still green (2907/2907 synthetic + 3003/3003 real-world). Highlights:createhd -inow writes correctGDDDgeometry,createhd --identCHS extraction, blanknoneenforcement, andguess_chsparity.extractcdnow defaults to cooked sectors and matcheschdmanfor all 43 CDs + 3 GD-ROMs; GD-ROM Redumphas_physical_pregap/SINGLE-DENSITY/HIGH-DENSITYhandling is now exact.copyper-type default codecs (lzma,zlib,huff,flacvscdlz,cdzl,cdflvsavhu) and parent hunk-size inheritance/factor checks.DVDmetadata now empty (length 0), 32 MiB aligned extraction with correct audio byte-swap, andaddmeta --valuetextno trailing NUL — all matchingchdman.- CLI option parsing is now
chdman-strict (unknown/duplicate/missing, per-command valid sets,isb/ish/ib/ihexclusion, trailingBhandling).
- New CHD Deep Reference —
docs/chd-deep-reference.mdaudited against MAME 0.289 and the library, with 9⚠ Corrections vs the old reference. - Tooling —
Meziantou.Analyzer 3.0.190, zero warnings, targetsnet8.0/net9.0/net10.0.
- Full
chdmanCLI argument parity — the CLI (CHDSharp) accepts everychdmansubcommand:info,verify,createraw,createhd,createcd,createdvd,createld,extractraw,extracthd,extractcd,extractdvd,extractld,copy,addmeta,delmeta,dumpmeta,listtemplates. - Full
chdmanbattle-test parity (2611/2611 + 3003/3003) — the newCHDSharpBattleTestharness cross-checks the decoder and encoder againstchdman.exeon deterministic and real-world CHD corpora. - Byte-for-byte parity for every codec — including
createld(AVHuff),cdzs, andzstd. - Zero native dependencies —
VendoredZSTD(a pure C# port of MAME's zstd 1.5.5) replaces theZstdSharp.Portpackage, and every compression codec is vendored in pure C#. The only runtime NuGet dependency is the optionalMicrosoft.Extensions.Logging.Abstractionslogging abstraction. - 38 bugs fixed from a deep code review.
See docs/ReleaseNotes.md for the full changelog.
dotnet add package CHDSharpTargets net8.0, net9.0, and net10.0. Zero native dependencies — every codec (zlib, lzma, huffman, flac, zstd, AVHuff) is implemented in pure C#.
Prebuilt CLI bundles for Windows, Linux and macOS (x64 + ARM64) are attached to each release — framework-dependent single-file binaries that require the .NET 10 runtime. See WhatsNew.md for what changed in the latest release.
using CHDSharp;
using CHDSharp.Models;
// Quick check: is this a valid CHD?
if (Chd.IsChdFile("game.chd", out uint version))
Console.WriteLine($"Detected CHD version {version}");
// Read the header without opening the full file
var headerResult = Chd.ReadHeader("game.chd");
if (headerResult.IsSuccess)
{
Console.WriteLine($"Version: {headerResult.Header.Version}");
Console.WriteLine($"Hunk size: {headerResult.Header.HunkBytes} bytes");
Console.WriteLine($"Total size: {headerResult.Header.TotalBytes} bytes");
Console.WriteLine($"SHA-1: {headerResult.Header.Sha1Hex}");
}
// Full verification (parallel, deep decompress every hunk)
using var stream = File.OpenRead("game.chd");
var result = Chd.CheckFile(stream, "game.chd", deepCheck: true);
Console.WriteLine(result.IsSuccess
? $"V{result.Version} SHA1: {result.Sha1Hex}"
: $"Error: {result.Error.GetMessage()}");var err = ChdFile.Open("game.chd", out var chd);
using (chd)
{
// Inspect metadata (game name, disc label, etc.)
foreach (var meta in chd.Metadata)
Console.WriteLine(meta); // e.g. "GAME: gauntlet"
// Parse CD/GD-ROM track layout (TOC)
if (chd.Tracks is { } tracks)
foreach (var track in tracks)
Console.WriteLine($"Track {track.TrackNumber}: {track.GetTypeString()}");
// Read hunk #42
var hunk = new byte[chd.HunkBytes];
chd.ReadHunk(42, hunk);
// Read arbitrary byte range (crosses hunk boundaries automatically)
var buf = new byte[1024];
chd.Read(offset: 0x10000, buf, 0, buf.Length);
// Decompress the entire image at once
chd.ReadAllBytes(out var image);
// Zero-copy Span<byte> reads
Span<byte> span = stackalloc byte[512];
chd.ReadHunk(0, span);
chd.Read(0x1000, span, span.Length);
}var err = ChdFile.Open("game.chd", out var chd);
using (chd)
{
using var stream = chd.OpenAsStream();
stream.Seek(0x10000, SeekOrigin.Current);
var buffer = new byte[4096];
stream.Read(buffer, 0, buffer.Length);
}var (_, chd) = await ChdFile.OpenAsync("game.chd");
await using (chd)
{
var hunk = new byte[chd.HunkBytes];
await chd.ReadHunkAsync(0, hunk);
var buf = new byte[1024];
await chd.ReadAsync(0x10000, buf, 0, buf.Length);
}// Verify a child CHD against its parent
var result = Chd.CheckFileWithParent("child.chd", "parent.chd");
// Open with parent
var err = ChdFile.Open("child.chd", out var child, parentPath: "parent.chd");
using (child)
{
child.ReadAllBytes(out var image);
}
// Lazy parent resolution (resolve by hash on first read)
var (_, lazyChd) = ChdFile.Open("child.chd", out var chd2,
parentResolver: hash => FindParentByHash(hash));var err = ChdFile.Open("game.chd", out var chd);
using (chd)
{
// Read by LBA (Logical Block Address)
var sector = new byte[2352]; // raw CD sector
chd.ReadSector(0, sector);
// Read by MSF (Minute:Second:Frame, BCD format)
chd.ReadSectorMsf(0x000200, sector); // 00:02:00
// Read full 2448-byte frame (2352 data + 96 subchannel)
var frame = new byte[2448];
chd.ReadFrame(0, frame);
// Convert between MSF and LBA
int lba = CdRomAddress.MsfToLba(0x000200);
uint msf = CdRomAddress.LbaToMsf(lba);
}using var cts = new CancellationTokenSource();
var progress = new Progress<ChdProgress>(p =>
Console.WriteLine($"Verified {p.HunksProcessed}/{p.TotalHunks} hunks"));
var result = Chd.CheckFile("game.chd", deepCheck: true,
progress: progress, cancellationToken: cts.Token);The encoder is part of the same library — no separate package needed.
using CHDSharp.Encoder;
// Simplest form — default codec (zlib), auto hunk/unit sizes
ChdEncoder.EncodeRaw("game.bin", "game.chd");
// Custom codecs (tried per hunk; smallest output wins)
ChdEncoder.EncodeRaw("game.bin", "game.chd",
codecTags: ChdCodecs.ParseCodecTags("zlib,zstd,lzma"));
// Custom hunk/unit sizes
ChdEncoder.EncodeRaw("game.bin", "game.chd",
hunkBytes: 65536, unitBytes: 4096);
// Uncompressed CHD (-c none)
ChdEncoder.EncodeRaw("game.bin", "game.chd",
codecTags: [CodecTags.None]);// From CUE sheet
ChdEncoder.EncodeCd("game.cue", "game.chd");
// From GDI, ISO, TOC, or NRG
ChdEncoder.EncodeCd("game.gdi", "game.chd");
ChdEncoder.EncodeCd("game.iso", "game.chd");// Zero-filled CHD with auto-derived CHS geometry
ChdEncoder.CreateBlank("blank.chd", 100 * 1024 * 1024UL); // 100 MB
// Explicit CHS geometry
ChdEncoder.CreateBlankWithChs("blank.chd",
cylinders: 1024, heads: 16, sectors: 63, sectorSize: 512);// Re-compress with Zstd
ChdEncoder.Copy("old.chd", "new.chd",
codecTags: [CodecTags.Zstd]);
// Preserve legacy metadata (no upgrade)
ChdEncoder.Copy("old.chd", "new.chd",
codecTags: [CodecTags.Zstd],
options: new ChdEncodeOptions { NoMetadataUpgrade = true });// Create a differential child against a parent
ChdEncoder.EncodeRaw("game.bin", "game.chd",
options: new ChdEncodeOptions { ParentPath = "base.chd" });var options = new ChdEncodeOptions
{
TaskCount = 8,
HunkCompleted = p => Console.WriteLine(
$"hunk {p.HunkIndex,6}/{p.HunkCount} {p.CodecName,-5} " +
$"{p.RawBytes,8} -> {p.StoredBytes,8} B ({p.Ratio:P1})")
};
ChdEncoder.EncodeRaw("game.bin", "game.chd", options: options);# Raw binary -> CHD
CHDSharp createraw -o out.chd -i in.bin [-c zlib,zstd,lzma,none] [-hs 65536] [-us 4096] [-np 8] [-op parent.chd] [-v]
# CD image -> CHD (CUE/GDI/ISO/TOC/NRG)
CHDSharp createcd -o out.chd -i in.cue [-c zlib,zstd,lzma,none] [-np 8] [-op parent.chd] [-v]
# Blank HD CHD
CHDSharp createhd -o out.chd --size 104857600 [-chs 1024,16,63] [-ss 512] [-c zlib] [-v]
# DVD CHD
CHDSharp createdvd -o out.chd -i in.iso [-c lzma,zlib,huff,flac] [-v]
# Re-compress an existing CHD
CHDSharp copy -o out.chd -i in.chd [-c zlib,zstd,lzma,none] [-np 8] [-ip parent.chd] [-op parent.chd] [-v]-np N sets the parallel worker count (1-64, default 8): it tunes encode/verify speed
only and never changes the output bytes — any value keeps the byte-for-byte chdman
parity. (chdman's own compression queue caps at 16 workers and its verify is
single-threaded.)
- Any CHD, any version — V1–V5 headers, every internal map format (self-hunk dedup, CRC32 maps, CRC16/compressed/RLE maps, uncompressed V5 maps, unit-based parent references)
- All 10 codecs — zlib, lzma, huffman, flac, zstd, AVHuff, plus the four CD-aware variants (
cdzl,cdlz,cdfl,cdzs) with ECC/sync regeneration - Random access —
ReadHunk(),Read()(byte ranges across hunk boundaries),EnumerateHunks(),ReadAllBytes() - LBA/MSF sector reads —
ReadSector(),ReadSectorMsf(), andReadFrame()address CD/GD-ROM sectors or full 2448-byte frames by logical block address - Async API —
OpenAsync,ReadHunkAsync,ReadAsync,IAsyncDisposable - Seekable stream —
OpenAsStream()returns a read-only, seekableStreamover the decompressed image - Span<byte> reads —
ReadHunk(uint, Span<byte>)andRead(ulong, Span<byte>, int)for zero-copy paths - Read-ahead decompression —
ConfigureReadAhead(int)enables background pre-decompression of upcoming hunks - Lazy parent resolution —
ParentResolvercallback resolves parents by SHA1/MD5 hash on first read - Header DTO without opening —
Chd.ReadHeader()returns the full parsed header without keeping the file open - Parallel verification — multi-threaded
CheckFile()with bounded memory and configurable worker count - Parent/child chains — transparent differential CHD support with wrong-parent detection
- Metadata — tag/index query API (
GetMetadata) plus the full entry list; IDNT (ATA IDENTIFY), KEY (encryption), CIS (PCMCIA) metadata support - Track info & extraction — CD/GD-ROM TOC parsing (
ChdTrackInfo), CUE/GDI descriptor generation, whole-image extraction (.bin/.cue,.iso,.img,.raw,.gdi) - Platform/game detection — 11 systems (PS1, Saturn, Dreamcast, etc.)
- Pluggable logging —
Microsoft.Extensions.Loggingintegration, silent by default
- Raw binary → CHD —
ChdEncoder.EncodeRaw()with auto or custom hunk/unit sizes - CD image → CHD —
ChdEncoder.EncodeCd()from CUE, GDI, ISO, TOC, or NRG - Blank HD CHD —
ChdEncoder.CreateBlank()/CreateBlankWithChs()for zero-filled images - Re-compression —
ChdEncoder.Copy()re-compresses existing CHDs with new codecs - Delta children — create differential CHDs against a parent (
-ip) - All 10 codecs — zlib, zstd, lzma, huff, flac, cdzl, cdlz, cdzs, cdfl, none; best-per-hunk selection
- SELF deduplication — COMPRESSION_SELF with SELF_0/SELF_1 map promotion
- Metadata cloning — all source entries preserved during copy
- Metadata upgrade — legacy CHCD/CHTR/CHGT → modern CHT2/CHGD during copy (matching chdman)
- Parallel encoding — 1–64 workers, deterministic output regardless of worker count
- 100% chdman match — byte-identical output vs
chdmanfor all codecs
| Version | Header | Map | Status |
|---|---|---|---|
| V1 | 76 bytes | Self-hunk dedup | ✅ |
| V2 | 80 bytes | Self-hunk dedup | ✅ |
| V3 | 120 bytes | CRC32 map, self-hunk | ✅ |
| V4 | 108 bytes | CRC32 map, parent chain | ✅ |
| V5 | 124 bytes | CRC16 / compressed / RLE, parent/unit chain | ✅ |
| Codec | FourCC | CD Variant | Read | Write |
|---|---|---|---|---|
| Zlib (Deflate) | zlib |
cdzl |
✅ | ✅ |
| LZMA | lzma |
cdlz |
✅ | ✅ |
| Huffman | huff |
— | ✅ | ✅ |
| FLAC | flac |
cdfl |
✅ | ✅ |
| Zstd | zstd |
cdzs |
✅ | ✅ |
| AVHuff | avhu |
— | ✅ | ✅ |
| Feature | libchdr 0.3.0 (C) | CHDSharp (C#) |
|---|---|---|
| V1–V5 headers | ✅ | ✅ |
| 9 of 10 codecs | ✅ | ✅ |
AVHuff (avhu) |
❌ | ✅ |
| Parent/child chains | ✅ | ✅ |
| Random access | ✅ | ✅ |
| Byte-range reads | ❌ (hunk-only) | ✅ |
| LBA/MSF sector reads | ❌ | ✅ |
| Full-image verification | ❌ | ✅ parallel |
| Metadata read/write | ✅ / ❌ | ✅ / ✅ |
| Extraction | ❌ | ✅ |
| Async API | ❌ | ✅ |
| CHD creation | ❌ | ✅ |
| Native dependencies | zlib, LZMA SDK, zstd, dr_flac | none |
See docs/libchdr-comparison.md for the full parity analysis.
CHDSharp vs the two other independent CHD implementations, MAME's reference chdman (0.288), and libchdr 0.3.0.
| Capability | CHDSharp | chd-rs 0.3.4 (Rust) | CHDlite 0.2.1 (C++) | chdman (MAME) |
libchdr 0.3.0 (C) |
|---|---|---|---|---|---|
| Reading | |||||
| Read V1–V5 | ✅ | ✅ | 🟡 V3–V5 | ✅ | ✅ |
| All 10 codecs (decode) | ✅ | ✅ | ✅ | ✅ | 🟡 9 of 10 |
| Parent/child chains | ✅ | ✅ | ✅ | ✅ | ✅ |
| Per-hunk CRC verification | ✅ | 🟡 opt-in | ✅ | ✅ | 🟡 |
| Full-image verify | ✅ parallel | 🟡 raw SHA1 | ✅ | ✅ | ❌ |
| Track/TOC parsing | ✅ | 🟡 | ✅ | ✅ | ❌ |
| Writing | |||||
| Write V5 | ✅ | ❌ | ✅ | ✅ | ❌ |
| All 10 codecs (encode) | ✅ | ❌ | ✅ | ✅ | ❌ |
| Delta/parent CHD | ✅ | ❌ | ✅ | ✅ | ❌ |
| CHD→CHD copy | ✅ | ❌ | ✅ | ✅ | ❌ |
| API | |||||
| Byte-range reads | ✅ | ✅ | ✅ | — | ❌ |
| LBA/MSF sector reads | ✅ | ❌ | ✅ | — | ❌ |
| Thread-safe random access | ✅ | ❌ | ❌ | — | ❌ |
| Async I/O | ✅ | ❌ | 🟡 | — | ❌ |
| Parallel verification | ✅ | ❌ | ❌ | ❌ | ❌ |
| Parallel encoding | ✅ | ❌ | ✅ | ✅ | ❌ |
| Extras | |||||
| Extraction | ✅ | 🟡 | ✅ | ✅ | ❌ |
| Platform detection | ✅ | ❌ | ✅ | ❌ | ❌ |
| Native dependencies | none | none | zlib-ng/zstd/lzma/flac | zlib/lzma/flac | zlib/LZMA/zstd/flac |
By default the library is silent. Set Chd.LoggerFactory before any other call to enable logging:
using Serilog;
using Serilog.Extensions.Logging;
Chd.LoggerFactory = new SerilogLoggerFactory(
new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.CreateLogger());The CLI (CHDSharpCli) and WPF tester (CHDSharpTester) always configure Serilog (console/debug + rolling-day file under %LocalAppData% + automatic Warning+ bug reports) — see Logging.
The CLI tool is named CHDSharp (e.g. CHDSharp.exe on Windows) and accepts the same subcommands as MAME's chdman:
# Display CHD information
CHDSharp info -i game.chd
# Verify a CHD
CHDSharp verify -i game.chd
# Create CHDs
CHDSharp createraw -o out.chd -i in.bin [-c zlib,zstd,lzma,none] [-hs 65536] [-us 4096] [-np 8] [-op parent.chd] [-v]
CHDSharp createcd -o out.chd -i in.cue [-c zlib,zstd,lzma,none] [-np 8] [-op parent.chd] [-v]
CHDSharp createhd -o out.chd --size N [-c zlib,zstd,lzma,none] [-chs C,H,S] [-ss N] [-np 8] [-v]
CHDSharp createdvd -o out.chd -i in.iso [-c lzma,zlib,huff,flac] [-v]
CHDSharp createld -o out.chd -i in.avi [-c avhu] [-v]
# Extract CHDs
CHDSharp extractraw -o out.bin -i in.chd
CHDSharp extractcd -o out.cue -i in.chd
CHDSharp extractdvd -o out.iso -i in.chd
CHDSharp extractld -o out.avi -i in.chd
# Re-compress
CHDSharp copy -o out.chd -i in.chd [-c zstd] [-ip parent.chd] [-op parent.chd]
# Metadata
CHDSharp addmeta -i game.chd -t GAME -vt "gauntlet"
CHDSharp delmeta -i game.chd -t GAME
CHDSharp dumpmeta -i game.chd -t GAME
# List hard disk templates
CHDSharp listtemplates
# Help
CHDSharp help
CHDSharp help createcdAdditional convenience commands (CHDSharp extensions):
# Verify all .chd files in directories (recursive)
CHDSharp D:\CHD
# Verify paths from a text file
CHDSharp --list chd_paths.txt
# Random-access self-test on a single CHD
CHDSharp --random game.chd
# Verify a child CHD against its parent
CHDSharp --parent child.chd parent.chd
# Print CD/GD-ROM table of contents
CHDSharp --toc game.chd
# Generate CUE sheet for CD CHDs
CHDSharp --cue game.chd
# Classify CHD media type
CHDSharp --classify game.chd
# Detect game platform
CHDSharp --detect game.chd
# Compute content hashes
CHDSharp --hash game.chd --hashes sha1,sha256,crc32 --result json
# Batch extract/create
CHDSharp --batch input-dir output-dir --action extractThe full wiki lives in docs/:
| Page | Description |
|---|---|
| Getting Started | Install, first program, CLI tour |
| CHD Format Reference | On-disk format: headers, maps, metadata, hashing |
| Codecs | All 10 decompression codecs |
| Architecture | Solution layout, library design, data flow |
| API Reference | Complete Chd, ChdFile, and all public models |
| Encoder | CHD creation, codecs, dedup, chdman validation |
| Verification | Full/parallel and header-only verification |
| Extraction | TOC parsing, CUE/GDI/ISO extraction |
| Metadata | Reading and querying CHD metadata tags |
| Parent/Child CHDs | Differential CHDs, unit-based references |
| Performance | Throughput, parallelism tuning, caching |
| Testing | The xUnit suite, corpus, generators |
| Troubleshooting | Common errors, known limitations |
| Project | Type | Description |
|---|---|---|
CHDSharpTest |
xUnit | Unit + corpus tests (4,214 tests per TFM, 30 CHD fixtures) |
CHDSharpEncoderTest |
xUnit | Encoder tests (454 tests, chdman cross-validation) |
CHDSharpBattleTest |
Console | Battle harness: 2907/2907 (synthetic) + 3003/3003 (real-world) checks vs chdman |
CHDSharpTester |
WPF | Interactive batch verification against chdman |
CHDSharpTestDataGeneration |
Console | Deterministic corpus generator |
# Run all tests
dotnet test
# Run only encoder tests
dotnet test CHDSharpEncoderTest/
# Run the chdman battle harness (requires chdman.exe)
dotnet run --project CHDSharpBattleTest
# Run large file integration tests
dotnet test CHDSharpEncoderTest/ --filter "FullyQualifiedName~LargeFileValidationTests"
# Regenerate corpus (requires chdman binaries)
dotnet run --project CHDSharpTestDataGenerationgit clone https://github.com/purelogiccode/CHDSharp.git
cd CHDSharp
dotnet build -c Release
# NuGet package
dotnet pack -c Release CHDSharpLib/
# ReSharper inspections (requires the JetBrains `jb` CLI); must report zero findings
jb inspectcode CSharp_CHDSharp.sln --severity=WARNINGRequires .NET 8.0 SDK or later. Works on Windows, Linux, and macOS.
CHDSharp/
├── CHDSharpLib/ The library (NuGet: CHDSharp)
│ ├── CHD.cs Main entry point (Chd class)
│ ├── CHDFile.cs Random-access reader (ChdFile)
│ ├── CHDHeaders.cs V1–V5 header parsing
│ ├── CHDReaders.cs Codec decompression
│ ├── CHDMetaData.cs Metadata read/write
│ ├── ChdTocParser.cs CD/GD-ROM TOC parsing
│ ├── CueConverter.cs CUE sheet generation
│ ├── DiscDetector.cs Platform/game detection
│ ├── Encoder/ CHD creation (ChdEncoder)
│ │ ├── ChdEncoder.cs Main encoder API
│ │ ├── ChdCodec.cs Codec implementations
│ │ ├── HunkProcessor.cs Parallel compression pipeline
│ │ ├── MapCompressor.cs V5 compressed map writer
│ │ ├── ParentMap.cs Delta/parent CHD support
│ │ ├── Models/ Encoder models (ChdEncodeOptions, etc.)
│ │ └── Interfaces/ IChdCodec interface
│ ├── Models/ Public models (ChdHeaderInfo, ChdError, etc.)
│ └── Utils/ Internal utilities (CRC, Huffman, BitStream)
├── CHDSharpCli/ CLI tool (binary: CHDSharp)
├── CHDSharpTest/ Unit + corpus tests
├── CHDSharpEncoderTest/ Encoder tests
├── CHDSharpTester/ WPF interactive tester
├── CHDSharpTestDataGeneration/ Corpus generator
├── CHDSharpBench/ Benchmarks
├── CHDSharpBattleTest/ Battle test harness
├── VendoredZLib/ Pure C# zlib port
├── VendoredLZMA/ LZMA SDK C# port
├── VendoredFlac/ Pure C# FLAC encoder/decoder
└── VendoredZSTD/ Pure C# zstd 1.5.5 encoder/decoder (MAME parity)
This is a combined work: the project code is MIT; VendoredFlac is LGPL-2.1; VendoredZLib is zlib-licensed; VendoredLZMA is public domain; VendoredZSTD is MIT (based on Facebook zstd, BSD-3-Clause). See LICENSE.txt for the full third-party notice and obligations.
The VendoredFlac component (FLAC encoder/decoder) is based on CUETools.Flake and is licensed under the GNU Lesser General Public License v2.1. See LICENSE.txt for full details.
Gordon Jefferyes (@gjefferyes) — the original author of RomVault/CHDSharp, which this project is forked from. Gordon built the foundational C# CHD reader (V1–V5 headers, zlib/lzma/huffman/flac codecs, and a custom LZMA/FLAC stack).
Peterson Fernandes (@purelogiccode) — extended Gordon's reader into a full-featured library and toolchain. Peterson added the encoder subsystem, CLI (CHDSharp), NuGet package, VendoredZSTD (pure C# zstd 1.5.5), async/stream APIs, parent/child chaining, parallel verification and encoding, V5 compressed maps, DVD/GD-ROM/laserdisc support, platform detection, 100% chdman byte-parity (MAME 0.289), and a comprehensive test suite (4200+ unit tests, 2900+ battle checks).
- MAME — CHD format specification and
chdmanreference implementation - libchdr — C reference library by Romain Tisseraud
- ZstdSharp.Port — pure C# Zstd port by Oleg Stepanischev, vendored in-repo as
VendoredZSTD(ZstdSharp 0.7.6 = libzstd 1.5.5 source); the port matches MAME's bundled zstd 1.5.5 frames byte-for-byte
- Donate: support the developer
- Star this repo on GitHub