chd/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg, doc_cfg_hide))]
2#![warn(missing_docs)]
3//! An implementation of the MAME CHD (Compressed Hunks of Data) format in pure Safe Rust, with support
4//! for CHD V1-5.
5//!
6//! ## Supported Compression Codecs
7//! chd-rs supports the following compression codecs.
8//!
9//! * None
10//! * Zlib/Zlib+/Zlib V5
11//! * CDZL (CD Zlib)
12//! * CDLZ (CD LZMA)
13//! * CDFL (CD FLAC)
14//! * FLAC (Raw FLAC)
15//! * LZMA (Raw LZMA)
16//! * Huff (MAME Static Huffman)
17//! * AVHU (AV Huffman)
18//! * Zstandard
19//! * CDZS (CD Zstandard)
20//!
21//! ## Iterating over hunks
22//! Because [`Hunk`](crate::Hunk) keeps a mutable reference to its owning
23//! [`Chd`](crate::Chd), direct iteration of hunks is not possible without
24//! Generic Associated Types. Instead, the hunk indices should be iterated over.
25//!
26//!```rust
27//! use std::fs::File;
28//! use std::io::BufReader;
29//! use chd::Chd;
30//!
31//! let mut f = BufReader::new(File::open("file.chd")?);
32//! let mut chd = Chd::open(&mut f, None)?;
33//! let hunk_count = chd.header().hunk_count();
34//! let hunk_size = chd.header().hunk_size();
35//!
36//! // buffer to store uncompressed hunk data must be the same length as the hunk size.
37//! let mut hunk_buf = chd.get_hunksized_buffer();
38//! // buffer to store compressed data.
39//! let mut cmp_buf = Vec::new();
40//!
41//! for hunk_num in 0..hunk_count {
42//!     let mut hunk = chd.hunk(hunk_num)?;
43//!     hunk.read_hunk_in(&mut cmp_buf, &mut hunk_buf)?;
44//! }
45//! ```
46//!
47//! ## Iterating over metadata
48//! Metadata in a CHD file consists of a list of entries that contain offsets to the
49//! byte data of the metadata contents in the CHD file. The individual metadata entries
50//! can be iterated directly, but a reference to the source stream has to be provided to
51//! read the data.
52//! ```rust
53//! use std::fs::File;
54//! use std::io::BufReader;
55//! use chd::Chd;
56//!
57//! let mut f = BufReader::new(File::open("file.chd")?);
58//! let mut chd = Chd::open(&mut f, None)?;
59//! let entries = chd.metadata_refs()?;
60//! for entry in entries {
61//!     let metadata = entry.read(&mut f)?;
62//! }
63//!```
64//! `Vec<Metadata>` implements `TryFrom<MetadataRefs>` so all metadata entries
65//! can be collected at once without requiring a reference to the file.
66//! ```rust
67//! use std::fs::File;
68//! use std::io::BufReader;
69//! use chd::Chd;
70//! use chd::metadata::Metadata;
71//!
72//! let mut f = BufReader::new(File::open("file.chd")?);
73//! // chd takes ownership of f here
74//! let mut chd = Chd::open(f, None)?;
75//!
76//! let metadatas: Vec<Metadata> = chd.metadata_refs()?.try_into()?;
77//!```
78//!
79
80#[cfg(not(feature = "std"))]
81compile_error!(
82    "The feature `std` is required. \
83     no_std support is blocked on Rust issue \
84     #48331 <https://github.com/rust-lang/rust/issues/48331>.\
85    "
86);
87
88mod error;
89
90mod block_hash;
91mod cdrom;
92mod chdfile;
93mod compression;
94
95#[cfg(feature = "huffman_api")]
96pub mod huffman;
97
98#[cfg(not(feature = "huffman_api"))]
99mod huffman;
100
101#[cfg(feature = "codec_api")]
102/// Implementations of decompression codecs used in MAME CHD.
103///
104/// Each codec may have restrictions on the hunk size, lengths and contents
105/// of the buffer. If [`decompress`](crate::codecs::CodecImplementation::decompress) is called
106/// with buffers that do not satisfy the constraints, it may return [`CompressionError`](crate::Error),
107/// or panic, especially if the output buffer does not satisfy length requirements.
108///
109/// Because codecs are allowed to be used outside of a hunk-sized granularity, such as in
110/// CD-ROM wrapped codecs that use Deflate to decompress subcode data, the codec implementations
111/// do not check the length of the output buffer against the hunk size. It is up to the caller
112/// of [`decompress`](crate::codecs::CodecImplementation::decompress) to uphold length invariants.
113#[cfg_attr(docsrs, doc(cfg(codec_api)))]
114pub mod codecs {
115    pub use crate::compression::codecs::*;
116    pub use crate::compression::{
117        CodecImplementation, CompressionCodec, CompressionCodecType, DecompressResult,
118    };
119}
120
121const fn make_tag(a: &[u8; 4]) -> u32 {
122    ((a[0] as u32) << 24) | ((a[1] as u32) << 16) | ((a[2] as u32) << 8) | (a[3] as u32)
123}
124
125macro_rules! const_assert {
126    ($($list:ident : $ty:ty),* => $expr:expr) => {{
127        struct Assert<$(const $list: $ty,)*>;
128        impl<$(const $list: $ty,)*> Assert<$($list,)*> {
129            const OK: u8 = 0 - !($expr) as u8;
130        }
131        Assert::<$($list,)*>::OK
132    }};
133    ($expr:expr) => {
134        const OK: u8 = 0 - !($expr) as u8;
135    };
136}
137
138pub(crate) use const_assert;
139
140pub use chdfile::{Chd, Hunk};
141pub use error::{Error, Result};
142pub mod header;
143pub mod map;
144pub mod metadata;
145pub mod read;
146
147#[cfg(feature = "unstable_lending_iterators")]
148#[cfg_attr(docsrs, doc(cfg(unstable_lending_iterators)))]
149pub mod iter;
150
151#[cfg(test)]
152mod tests {
153    use crate::metadata::Metadata;
154    use crate::read::{ChdReader, HunkBufReader};
155    use crate::Chd;
156    use std::convert::TryInto;
157    use std::fs::File;
158    use std::io::{BufReader, Read, Write};
159
160    #[cfg(feature = "unstable_lending_iterators")]
161    use crate::iter::LendingIterator;
162
163    #[test]
164    fn read_metas_test() {
165        let mut f = File::open(".testimages/Test.chd").expect("");
166        let mut chd = Chd::open(&mut f, None).expect("file");
167
168        let metadatas: Vec<Metadata> = chd.metadata_refs().try_into().expect("");
169        let meta_datas: Vec<_> = metadatas
170            .into_iter()
171            .map(|s| String::from_utf8(s.value).unwrap())
172            .collect();
173        println!("{:?}", meta_datas);
174    }
175
176    #[test]
177    fn read_hunk_buffer_test() {
178        let mut f = BufReader::new(File::open(".testimages/cliffhgr.chd").expect(""));
179        let mut chd = Chd::open(&mut f, None).expect("file");
180        let hunk_count = chd.header().hunk_count();
181
182        let mut hunk_buf = Vec::new();
183        let mut cmp_buf = Vec::new();
184        for hunk_num in 0..hunk_count {
185            let mut hunk = chd.hunk(hunk_num).expect("could not acquire hunk");
186            let read = HunkBufReader::new_in(&mut hunk, &mut cmp_buf, hunk_buf)
187                .expect(format!("could not read_hunk {}", hunk_num).as_str());
188            hunk_buf = read.into_inner();
189        }
190    }
191
192    #[test]
193    fn read_hunk_test() {
194        let mut f = BufReader::new(File::open(".testimages/cliffhgr.chd").expect(""));
195        let mut chd = Chd::open(&mut f, None).expect("file");
196        let hunk_count = chd.header().hunk_count();
197
198        let mut hunk_buf = chd.get_hunksized_buffer();
199        let mut cmp_buf = Vec::new();
200        for hunk_num in 0..hunk_count {
201            let mut hunk = chd.hunk(hunk_num).expect("could not acquire hunk");
202            hunk.read_hunk_in(&mut cmp_buf, &mut hunk_buf)
203                .expect(format!("could not read_hunk {}", hunk_num).as_str());
204            println!("Read hunk {}", hunk_num);
205        }
206    }
207
208    #[test]
209    fn read_file_test() {
210        let mut f = BufReader::new(File::open(".testimages/Test.chd").expect(""));
211        let chd = Chd::open(&mut f, None).expect("file");
212        let mut read = ChdReader::new(chd);
213
214        let mut buf = Vec::new(); // this is really bad..
215        read.read_to_end(&mut buf).expect("can read to end");
216        let mut f_out = File::create(".testimages/out.bin").expect("");
217        f_out.write_all(&buf).expect("did not write")
218    }
219
220    #[test]
221    fn read_parent_test() {
222        let p = BufReader::new(File::open(".testimages/TombRaider.chd").expect(""));
223        let pchd = Chd::open(p, None).expect("parent");
224
225        let f = BufReader::new(File::open(".testimages/TombRaiderR1.chd").expect(""));
226
227        let _chd = Chd::open(f, Some(Box::new(pchd))).expect("child");
228    }
229
230    #[test]
231    #[cfg(feature = "unsound_owning_iterators")]
232    fn hunk_iter_test() {
233        let f_bytes = include_bytes!("../.testimages/mocapbj_a29a02.chd");
234        let mut f_cursor = Cursor::new(f_bytes);
235        // let mut f = BufReader::new(File::open(".testimages/mocapbj_a29a02.chd").expect(""));
236        let mut chd = Chd::open(&mut f_cursor, None).expect("file");
237        let mut hunk_buf = chd.get_hunksized_buffer();
238        let mut comp_buf = Vec::new();
239        for (_hunk_num, mut hunk) in chd.hunks().skip(7838).enumerate() {
240            hunk.read_hunk_in(&mut comp_buf, &mut hunk_buf)
241                .expect("hunk could not be read");
242        }
243    }
244
245    #[test]
246    #[cfg(feature = "unstable_lending_iterators")]
247    fn hunk_iter_lending_test() {
248        let mut f = BufReader::new(File::open(".testimages/cliffhgr.chd").expect(""));
249        // let mut f = BufReader::new(File::open(".testimages/cliffhgr.chd").expect(""));
250
251        let mut chd = Chd::open(&mut f, None).expect("file");
252        let mut hunk_buf = chd.get_hunksized_buffer();
253        let mut comp_buf = Vec::new();
254        let mut hunks = chd.hunks();
255        let mut hunk_num = 0;
256        while let Some(mut hunk) = hunks.next() {
257            hunk.read_hunk_in(&mut comp_buf, &mut hunk_buf)
258                .expect(&*format!("hunk {} could not be read", hunk_num));
259            hunk_num += 1;
260        }
261    }
262
263    #[test]
264    #[cfg(feature = "unstable_lending_iterators")]
265    fn metadata_iter_lending_test() {
266        let mut f = BufReader::new(File::open(".testimages/Test.chd").expect(""));
267        let mut chd = Chd::open(&mut f, None).expect("file");
268        let mut metas = chd.metadata();
269        while let Some(mut meta) = metas.next() {
270            let contents = meta.read().expect("metadata entry could not be read");
271            println!("{:?}", String::from_utf8(contents.value));
272        }
273    }
274
275    #[test]
276    #[cfg(feature = "unsound_owning_iterators")]
277    fn metadata_iter_test() {
278        let mut f = BufReader::new(File::open(".testimages/Test.chd").expect(""));
279        let mut chd = Chd::open(&mut f, None).expect("file");
280        for mut meta in chd.metadata().expect("metadata could not be read") {
281            let contents = meta.read().expect("metadata entry could not be read");
282            println!("{:?}", String::from_utf8(contents.value));
283        }
284    }
285}