Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
libchdman-rs
A Rust wrapper for the official MAME CHD core (chd_file).
This crate provides native support for reading, writing, and managing CHD (Compressed Hunks of Data) containers by wrapping the original MAME C++ source code. It ensures 100% feature parity with chdman by using the same core logic.
Features
- Native CHD Support: Seek, read, and write at both hunk and byte levels.
- Metadata Management: Add, read, and delete CHD metadata.
- Custom I/O: Implement the
ChdIotrait to provide your own I/O backends (e.g., in-memory, network, or custom encrypted filesystems). - Asynchronous Compression: High-level
ChdCompressorAPI for creating compressed CHDs from custom data sources. - chdman parity:
hd,dvd,cd, andcopymodules covercreatehd/extracthd,createdvd/extractdvd,createcd/extractcd, andcopybyte-for-byte. - Zero Re-implementation: Directly wraps MAME's
libutil/chd.cpp.
For the full API surface and a chdman-flag-by-flag mapping see
docs/format-modules.md and
docs/chdman-mapping.md.
Choosing between crates.io and git
libchdman-rs is published to crates.io in a slim form that contains only the Rust wrapper code. The ~900 MB MAME C++ source is not shipped in the crates.io tarball — published crates can't be that large, and most consumers don't need to compile MAME from source.
Use crates.io (recommended for most consumers)
If you don't need to compile MAME from source — and you almost never do
— depend on the crates.io version and enable the prebuilt feature:
[]
= { = "0.288", = ["prebuilt"] }
The prebuilt feature downloads a pre-built static archive matching
your target triple from this crate's GitHub Releases. See the
"Pre-built static archives"
section below for supported triples, glibc floors, and escape hatches.
Without features = ["prebuilt"], the build script aborts with an
actionable message — the crates.io tarball has no C++ source for a
local build to consume.
Use the git dependency (for source builds)
If you need to compile MAME's C++ from source — debugging the wrapped C++, custom MAME patches, or a target triple not covered by the prebuilt matrix — depend on the git repo:
[]
= { = "https://github.com/danifunker/libchdman-rs", = "v0.288.0" }
The git dependency includes the full vendored MAME source tree (~1 GB), so cold builds are slow.
Summary
| Need | Use this |
|---|---|
| Fast CI, prebuilt target | crates.io + prebuilt |
| Custom MAME modifications | git + source build |
| Unusual target not in prebuilt matrix | git + source build |
| Reproducible historic builds | git, pinned by tag |
Usage
Opening a CHD
use Chd;
let chd = open.expect;
let info = chd.info.expect;
println!;
println!;
Creating a hard-disk CHD
use ;
use Path;
create_from_path?;
Creating a CD CHD from a CUE
use ;
use parse_codec_spec;
use Path;
create_from_cue?;
Reading a CD CHD as a cooked ISO stream
For MODE1 / MODE1_RAW CD tracks, CdCookedReader exposes the 2048-byte/sector
user data as a Read + Seek stream — useful for browsing the ISO9660 / UDF
filesystem inside a CD CHD without extracting to a temp file first. Length is
track.frames * 2048; sync, header, and ECC bytes are stripped by MAME
regardless of whether the CHD stored the track raw or cooked.
use CdCookedReader;
use Chd;
use ;
let chd = open?;
let mut reader = open?;
// Jump to the ISO9660 Primary Volume Descriptor (LBA 16, byte 16 * 2048).
reader.seek?;
let mut pvd = ;
reader.read_exact?;
assert_eq!;
// Hand `reader` to any ISO9660 / UDF parser that wants Read + Seek.
let total_bytes = reader.len;
For multi-track CHDs (PSX / Saturn / mixed-mode PC CDs: one data track plus one
or more audio tracks), use CdCookedReader::open_track(chd, track_index) to
pick which track to expose. Position 0 corresponds to the start of the selected
track's user data, not the start of the CHD. The track must be MODE1 / MODE1_RAW;
audio and Mode 2 variants return Err(ChdError::UnsupportedFormat).
// PSX game: track 0 is data, tracks 1..N are audio.
let chd = open?;
let data_reader = open_track?;
CdCookedReader::open (no _track) returns Err(ChdError::UnsupportedFormat)
for multi-track CHDs as a back-compat guard; pick a track explicitly with
open_track instead. Out-of-range track_index returns Err(ChdError::InvalidData).
Use Chd::info().is_cd and cd::list_tracks to inspect the TOC before opening.
into_inner() recovers the owned Chd if you need it back.
Runtime hard-disk writes (MAME-style)
HdImage is a block-device view over a hard-disk CHD — the same surface
MAME's harddisk_image_device exposes to a running emulated machine.
Writes to a HdImage go straight back into the CHD via write_bytes,
which is exactly what MAME does when an emulated guest (e.g. a Macintosh
LC) writes to its CHD hard disk.
For an uncompressed CHD, open it writeable in place:
use HdImage;
use Path;
let mut img = open?;
let ss = img.sector_size as usize;
let mut buf = vec!;
img.read_sector?; // read MBR / boot sector
buf = 0x55;
buf = 0xAA;
img.write_sector?; // persisted on drop
For a compressed CHD, MAME's runtime strategy is to keep the parent
CHD untouched and route every write into a fresh uncompressed diff
child. HdImage::open_with_diff does the same:
use HdImage;
use Path;
// Parent stays read-only and untouched. Diff is uncompressed and
// linked to the parent by SHA-1.
let mut img = open_with_diff?;
img.write_sector?;
// Later, re-attach to the existing diff:
let img = reopen_diff?;
Chd::create_with_parent is the lower-level primitive if you want to
build the diff yourself (it wraps MAME's
chd_file::create(filename, logicalbytes, hunkbytes, compression, parent)
overload). Pass compression = [0; 4] for an uncompressed diff, which
is the only configuration that supports per-hunk runtime writes.
Caveats
- Writes only work on uncompressed CHDs (or uncompressed diffs against a
compressed parent). MAME's
write_hunk/write_bytesreject compressed targets — this is a CHD-format constraint, not a wrapper limitation. HdImage::open_with_difffails if the diff path already exists. Usereopen_diffto re-attach to an existing diff.HdImagevalidatesbuf.len() == sector_size()andlba < sector_count(); both fail withChdError::InvalidDatasince the underlying error enum has noInvalidParametervariant.- The diff inherits logical size, hunk size, and unit size from the parent — you can't resize on attach.
- For diff-mode images the
HdImageowns the parentChdhandle internally and drops the child first, because MAME'schd_filestoresm_parentas a non-owning aliasingshared_ptr(chd.cppL681,L841). Don't extract the innerChdand outlive the wrapper — useas_chd()/as_chd_mut()to reach lower-level APIs while theHdImageis in scope.
Re-compressing an existing CHD
use ;
use ;
use Path;
copy?;
Custom I/O
use ;
use ;
// ChdIo is automatically implemented for Read + Write + Seek
let io = new;
let chd = open_custom.expect;
Pre-built static archives (faster CI builds)
libchdman-rs wraps MAME's C++ core via the cc crate, which can take
several minutes on cold builds. For CI and other build-time-sensitive
consumers, every tagged release ships pre-built static archives that
skip the C++ compile entirely.
Enable the prebuilt feature in your Cargo.toml:
= { = "0.288", = ["prebuilt"] }
On cargo build, the build script downloads the archive matching your
target triple from this repo's GitHub Releases, verifies its sha256, and
links it statically.
Supported targets
| Triple | Notes |
|---|---|
x86_64-unknown-linux-gnu |
Three glibc floors (see below) |
aarch64-unknown-linux-gnu |
Three glibc floors |
x86_64-apple-darwin |
MACOSX_DEPLOYMENT_TARGET=10.13 |
aarch64-apple-darwin |
MACOSX_DEPLOYMENT_TARGET=10.13 |
x86_64-pc-windows-msvc |
|
i686-pc-windows-msvc |
Targets not in the list (musl, BSD, anything exotic) fall back to source
build automatically when LIBCHDMAN_PREBUILT_FALLBACK=1 is set; otherwise
the build will fail with a clear message.
Linux: picking a glibc floor
Linux archives are built against two glibc versions, and you choose which floor your binary should require:
LIBCHDMAN_GLIBC |
glibc floor | Built on Ubuntu | Works on (examples) |
|---|---|---|---|
2.35 (default) |
2.35 | 22.04 | Debian 12, RHEL 9, modern distros |
2.39 |
2.39 | 24.04 | Newest distros only |
If unset (or auto), the build script picks 2.35 — the modern sweet
spot. macOS and Windows builds ignore this variable.
The previous
2.31floor (Debian 11 / RHEL 8 era) was dropped when GitHub retired theubuntu-20.04runner image. Consumers on older distros should fall back to source build withLIBCHDMAN_PREBUILT_FALLBACK=1.
Escape hatches
| Env var | Effect |
|---|---|
LIBCHDMAN_FORCE_SOURCE=1 |
Always compile from source, even with prebuilt on |
LIBCHDMAN_PREBUILT_FALLBACK=1 |
Silently fall back to source if the download fails |
Caching
The downloaded archive is cached under:
- Linux:
~/.cache/libchdman-rs/<version>/(or$XDG_CACHE_HOME/libchdman-rs/<version>/) - macOS:
~/Library/Caches/libchdman-rs/<version>/ - Windows:
%LOCALAPPDATA%\libchdman-rs\<version>\
cargo clean doesn't invalidate the cache; bumping the crate version does.
Versioning
The crate version matches the MAME release it embeds — for example,
0.288.0 embeds MAME 0.288. If a wrapper-only fix is needed against the
same MAME version, the patch component is bumped (0.288.1, 0.288.2,
...).
Testing
cargo test runs the lib-only suite (round-trip parity, metadata,
custom I/O). It never invokes the chdman binary and has no system
dependencies beyond a C++20 toolchain.
For byte-for-byte parity verification against the upstream chdman
tool, enable the chdman_compat_tests feature locally:
This is dev-local only. The feature gates tests that shell out to
chdman on $PATH; CI does not install chdman, so these tests never
run there. Use them on demand during development cycles when you want
to confirm that the output of this crate matches what chdman produces
for the same input.
Build Requirements
- Rust 1.75+
- C++20 compliant compiler (GCC 10+, Clang 10+, MSVC 2019+)
License
This crate is licensed under the same terms as MAME (BSD-3-Clause).