-
Notifications
You must be signed in to change notification settings - Fork 0
verification
CHDSharp offers four levels of integrity checking: a fast header sniff, a full header DTO read, a header-only check, and a full deep verification that decompresses every hunk and validates every checksum the format defines.
| Level | API | What it does | Cost |
|---|---|---|---|
| Sniff |
Chd.IsChdFile(path) / Chd.CheckHeader(stream, ...)
|
Validates magic + version + header length. | Reads 16–20 bytes. |
| Header DTO | Chd.ReadHeader(path, out ChdHeaderInfo?) |
Parses the full header into a DTO (codec slots, sizes, hashes, parent linkage, unit info) without keeping the file open. | Reads the header (plus a small metadata peek for V1–V4 unit size). |
| Header-only | Chd.CheckFile(stream, name, deepCheck: false) |
Parses and validates the full header (codec slots, sizes, parent linkage). | Reads the header. |
| Deep | Chd.CheckFile(stream, name, deepCheck: true) |
Decompresses every hunk in parallel, verifies per-hunk CRCs, and recomputes MD5 / SHA1 / rawsha1 / combined metadata SHA1. | Reads the whole file; ~200–400 MB/s typical. |
| Chain | Chd.CheckFileWithParent(child, parent) |
Deep verification of a (possibly child) CHD, resolving parent hunks. Single-threaded. | Reads child + referenced parent hunks. |
For every hunk, CHDSharp verifies:
- V3/V4: CRC-32 of the decompressed hunk against the map entry (unless the NO_CRC flag is set).
- V5: CRC-16 of the decompressed hunk against the map entry.
Then, across the whole image:
- MD5 of the raw decompressed data — verified when the header stores one (V1–V3).
-
rawsha1— SHA1 of the raw decompressed data (V3–V5). -
sha1— the combined hash:SHA1(rawsha1 ‖ sorted entry hashes)over checksummed metadata entries (V4–V5). - Metadata chain integrity — cycle detection, entry-size caps, and readable offsets.
A mismatch at any level produces Chderrdecompressionerror (per-hunk CRC) or Chderrinvalidmetadata (combined hash), and the offending hunk index/bytes are not required to be exposed — the caller gets the error code.
| Version | MD5 | rawsha1 | combined sha1 | per-hunk |
|---|---|---|---|---|
| V1 | raw data | — | — | none |
| V2 | raw data | — | — | none |
| V3 | raw data | raw data | — | CRC32 |
| V4 | — | raw data | raw + metadata | CRC32 |
| V5 | — | raw data | raw + metadata | CRC16 |
CheckFile(deepCheck: true) runs a bounded pipeline (see Architecture):
- Producer reads compressed hunks sequentially from the stream.
-
Workers (default 8, configurable via
Chd.TaskCount, range 1–64) decompress hunks in parallel using pooled buffers. - Hasher consumes decompressed hunks in order, feeds the hash algorithms, and releases buffers.
Memory is bounded by:
- an
ArrayPoolper buffer class (input/output/cache), and - a semaphore limiting in-flight decompressed repeat-blocks to a 512 MiB budget (
blocksToKeep = 512 MiB / hunkbytes).
Chd.TaskCount is a process-global setting — set it before calling CheckFile:
Chd.TaskCount = 16; // tune for your CPU/memory
var result = Chd.CheckFile(stream, "game.chd", deepCheck: true);All long operations accept an optional IProgress<ChdProgress> and report after every decompressed hunk:
var progress = new Progress<ChdProgress>(p =>
Console.WriteLine($"{p.Percent:F0}% — {p.BytesProcessed:N0}/{p.TotalBytes:N0} bytes"));
var result = Chd.CheckFile(stream, "game.chd", deepCheck: true, progress);For CheckFile(deepCheck: true) the reports are emitted in hunk order from the hasher stage; new Progress<ChdProgress>(...) marshals them to the capturing thread/context. The same parameter exists on CheckFileWithParent, ReadAllBytes, EnumerateHunks, and ExtractToDirectory. Defaults to null (no reporting) everywhere.
All of these also accept an optional trailing CancellationToken and throw OperationCanceledException when cancelled. For deep verification the token is linked into the pipeline's internal CancellationTokenSource, so cancelling stops the workers immediately and the method throws OCE instead of reporting a bogus partial-hash mismatch.
var result = Chd.CheckFileWithParent("child.chd", "parent.chd");
// Or with an already-open parent (library does NOT take ownership):
var perr = ChdFile.Open("parent.chd", out var parent);
var r2 = Chd.CheckFileWithParent("child.chd", "parent.chd"); // path-based is simplest
// Out-parameter variant
var err = Chd.CheckFileWithParent("child.chd", null,
out var version, out var sha1, out var md5);Parent mismatches are caught at open time (Chderrinvalidparent), and missing parents produce Chderrrequiresparent. During deep verification, parent-referenced hunks are read through the parent instance and hashed as if they were local.
CheckFileWithParentis single-threaded by design (it walks parent chains); useCheckFilefor standalone parallel verification.
var result = Chd.CheckFile(File.OpenRead("game.chd"), "game.chd", deepCheck: true);
if (result.IsSuccess)
{
Console.WriteLine($"V{result.Version}");
Console.WriteLine($"SHA1: {result.Sha1Hex}");
Console.WriteLine($"MD5: {result.Md5Hex}");
}
else
{
Console.WriteLine($"Failed: {result.Error.GetMessage()}");
}
// Deconstruction
var (err, ver, sha1, md5) = result;The same verification logic is exposed by the CLI:
CHDSharpCli D:\CHD # deep-verify every .chd under D:\CHD
CHDSharpCli --list files.txt # deep-verify a list of paths
CHDSharpCli --parent child.chd parent.chdA successful run prints the A/V metadata and reports Valid; failures print the offending file and the ChdError.
- V1/V2 entries have no per-hunk CRC by design; only the whole-image MD5 is checked when present.
- V3/V4 CRC32 validation is stricter than libchdr, which stores but never checks V3/V4 CRCs — CHDSharp follows MAME's behavior and validates them (respecting the NO_CRC flag).
- If the header hashes are all-zero (a valid but unverified CHD), deep verification still decompresses everything and validates per-hunk CRCs, but cannot cross-check the whole image.
CHDSharp
Format & internals
API & usage
Operations
Writing CHDs
Reference