Skip to content

parent child chds

Peterson Fernandes edited this page Aug 19, 2026 · 1 revision

Parent/Child CHDs

CHD supports delta (incremental) images: a child CHD stores only the hunks that differ from its parent; identical hunks become parent references. This is how MAME ships multi-disc or regional variants without duplicating identical data.


How it works

  • The child header stores the parent's md5 (V1–V3) and/or sha1 (V3–V5) hashes.
  • Map entries of type PARENT point into the parent's data instead of local storage.
  • In V1–V4, a parent reference is a direct hunk index into the parent.
  • In V5, a parent reference is a unit index (unit = hunkbytes / unitbytes subdivision, e.g. 512-byte sectors inside 4096-byte hunks). References can be unaligned — a hunk may need the tail of parent hunk N and the head of parent hunk N+1; CHDSharp stitches the two halves.
child.chd                       parent.chd
┌───────────────────┐           ┌───────────────────┐
│ hunk 0: compressed│           │ hunk 0: data      │
│ hunk 1: PARENT→u4 │──────────▶│ hunk 1: data      │
│ hunk 2: compressed│           │ hunk 2: data      │
│ hunk 3: PARENT→u7 │──────┐    │ hunk 3: data      │
└───────────────────┘      └───▶│ hunk 4: data      │
                                └───────────────────┘

Opening child CHDs

Three ways, matching the three Open overloads:

// 1. Path-based: the library opens the parent and owns it.
var err = ChdFile.Open("child.chd", "parent.chd", out var child);
using (child) { ... }

// 2. External parent instance: caller keeps ownership, may share it.
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; parent hunks resolve through `parent` */ }
    }
}

// 3. From streams.
using var childStream = File.OpenRead("child.chd");
ChdFile.Open(childStream, leaveOpen: false, parent, out var child2);

Async twins exist for all three (OpenAsync).


Error semantics

Situation Result
Child opened without a parent Chderrrequiresparent
Supplied parent's md5/sha1 does not match the child's stored parent hashes Chderrinvalidparent
Parent-referenced hunk read when no parent is attached Chderrrequiresparent (from ReadHunk)

Parent validation happens at open time: the child's stored parentmd5/parentsha1 is compared against the actual parent's Md5/Sha1 (when both are non-empty).


Verification

var result = Chd.CheckFileWithParent("child.chd", "parent.chd");
if (result.IsSuccess)
    Console.WriteLine($"child V{result.Version} verified against parent");

CheckFileWithParent decompresses the child and the referenced parent hunks and validates every hash — this is the single-threaded counterpart of CheckFile (which is standalone-only).


Reading through a child

From the consumer's point of view, reading is transparent:

var err = ChdFile.Open("child.chd", "parent.chd", out var child);
using (child)
{
    var hunk = new byte[child.HunkBytes];
    for (uint i = 0; i < child.HunkCount; i++)
    {
        var herr = child.ReadHunk(i, hunk);   // local or parent data, same call
        if (herr != ChdError.Chderrnone) break;
    }
}

Read, ReadAllBytes, EnumerateHunks, and extraction all work identically on child CHDs.


Hunk resolution details

ReadHunk resolves a map entry as follows:

  1. PARENTReadParentHunk:
    • V1–V4 (and uncompressed V5 maps): direct parent hunk index.
    • V5 compressed maps: convert the unit index to parent hunk(s):
      • aligned → one parent hunk;
      • unaligned → two adjacent parent hunks stitched at the unit boundary.
  2. SELF → follow the self-reference to the entry that holds real data (and use the decompressed cache when the block is repeated).
  3. Otherwise → read local compressed bytes (stream or Precache buffer) and decompress.

The child keeps a reference to the parent (_parent); when the child was opened with a parent path, it owns the parent and disposes it together with itself.

Clone this wiki locally