chd_capi/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg, doc_cfg_hide))]
2#![deny(unsafe_op_in_unsafe_fn)]
3//! A (mostly) [libchdr](https://github.com/rtissera/libchdr) compatible C-API for [chd-rs](https://crates.io/crates/chd).
4//!
5//! For Rust consumers, consider using [chd-rs](https://crates.io/crates/chd) instead.
6//!
7//! The best way to integrate chd-rs in your C or C++ project is to instead vendor the [sources](https://github.com/SnowflakePowered/chd-rs) directly
8//! into your project, with a compatible implementation of [libchdcorefile](https://github.com/SnowflakePowered/chd-rs/tree/master/chd-rs-capi/libchdcorefile)
9//! for your platform as required.
10//!
11//! ## ABI compatibility with libchdr
12//!
13//! chd-rs-capi makes the following ABI-compatibility guarantees compared to libchdr when compiled statically.
14//! * `chd_error` is ABI and API-compatible with [chd.h](https://github.com/rtissera/libchdr/blob/cdcb714235b9ff7d207b703260706a364282b063/include/libchdr/chd.h#L258)
15//! * `chd_header` is ABI and API-compatible [chd.h](https://github.com/rtissera/libchdr/blob/cdcb714235b9ff7d207b703260706a364282b063/include/libchdr/chd.h#L302)
16//! * `chd_file *` is an opaque pointer. It is **not layout compatible** with [chd.c](https://github.com/rtissera/libchdr/blob/cdcb714235b9ff7d207b703260706a364282b063/src/libchdr_chd.c#L265)
17//! * The layout of `core_file *` is user-defined when the `chd_core_file` feature is enabled.
18//! * Freeing any pointer returned by chd-rs with `free` is undefined behaviour. The exception are `chd_file *` pointers which can be safely freed with `chd_close`.
19
20extern crate core;
21
22mod header;
23
24#[cfg(feature = "chd_core_file")]
25mod chdcorefile;
26
27#[cfg(feature = "chd_core_file")]
28#[allow(non_camel_case_types)]
29#[allow(unused)]
30mod chdcorefile_sys;
31
32use crate::header::chd_header;
33use chd::header::Header;
34use chd::metadata::{KnownMetadata, Metadata, MetadataTag};
35pub use chd::Error as chd_error;
36use chd::{Chd, Error};
37use std::any::Any;
38use std::ffi::{CStr, CString};
39use std::fs::File;
40use std::io::{BufReader, Cursor, Read, Seek};
41use std::mem::MaybeUninit;
42use std::os::raw::{c_char, c_int, c_void};
43use std::path::Path;
44use std::slice;
45
46/// Open a CHD for reading.
47pub const CHD_OPEN_READ: i32 = 1;
48/// Open a CHD for reading and writing. This mode is not supported and will always return an error
49/// when passed into a constructor function such as [`chd_open`](crate::chd_open).
50pub const CHD_OPEN_READWRITE: i32 = 2;
51
52/// Trait alias for `Read + Seek + Any`.
53#[doc(hidden)]
54pub trait SeekRead: Any + Read + Seek {
55    fn as_any(&self) -> &dyn Any;
56}
57
58impl<R: Any + Read + Seek> SeekRead for BufReader<R> {
59    fn as_any(&self) -> &dyn Any {
60        self
61    }
62}
63
64impl SeekRead for Cursor<Vec<u8>> {
65    fn as_any(&self) -> &dyn Any {
66        self
67    }
68}
69
70#[allow(non_camel_case_types)]
71/// An opaque type for an opened CHD file.
72pub type chd_file = Chd<Box<dyn SeekRead>>;
73
74fn ffi_takeown_chd(chd: *mut chd_file) -> Box<Chd<Box<dyn SeekRead>>> {
75    unsafe { Box::from_raw(chd) }
76}
77
78fn ffi_expose_chd(chd: Box<Chd<Box<dyn SeekRead>>>) -> *mut chd_file {
79    Box::into_raw(chd)
80}
81
82fn ffi_open_chd(
83    filename: *const c_char,
84    parent: Option<Box<chd_file>>,
85) -> Result<chd_file, chd_error> {
86    let c_filename = unsafe { CStr::from_ptr(filename) };
87    let filename = std::str::from_utf8(c_filename.to_bytes())
88        .map(Path::new)
89        .map_err(|_| chd_error::InvalidParameter)?;
90
91    let file = File::open(filename).map_err(|_| chd_error::FileNotFound)?;
92
93    let bufread = Box::new(BufReader::new(file)) as Box<dyn SeekRead>;
94    Chd::open(bufread, parent)
95}
96
97/// Opens a CHD file by file name, with a layout-undefined backing file pointer owned by
98/// the library.
99///
100/// The result of passing an object created by this function into [`chd_core_file`](crate::chd_core_file)
101/// is strictly undefined. Instead, all `chd_file*` pointers with provenance from `chd_open` should be
102/// closed with [`chd_close`](crate::chd_close).
103///
104/// # Safety
105/// * `filename` is a valid, null-terminated **UTF-8** string.
106/// * `parent` is either `NULL` or a valid pointer to a `chd_file` obtained from [`chd_open`](crate::chd_open), [`chd_open_file`](crate::chd_open_file), or [`chd_open_core_file`](crate::chd_open_core_file).
107/// * `out` is aligned and can store a pointer to a `chd_file*`. On success, `out` will point to a valid `chd_file*`.
108/// * After this function returns, `parent` is invalid and must not be used, otherwise it will be undefined behaviour. There is no way to retake ownership of `parent`.
109#[no_mangle]
110pub unsafe extern "C" fn chd_open(
111    filename: *const c_char,
112    mode: c_int,
113    parent: *mut chd_file,
114    out: *mut *mut chd_file,
115) -> chd_error {
116    // we don't support READWRITE mode
117    if mode == CHD_OPEN_READWRITE {
118        return chd_error::FileNotWriteable;
119    }
120
121    let parent = if parent.is_null() {
122        None
123    } else {
124        Some(ffi_takeown_chd(parent))
125    };
126
127    let chd = match ffi_open_chd(filename, parent) {
128        Ok(chd) => chd,
129        Err(e) => return e,
130    };
131
132    unsafe { *out = ffi_expose_chd(Box::new(chd)) }
133    chd_error::None
134}
135
136#[no_mangle]
137/// Close a CHD file.
138///
139/// # Safety
140/// * `chd` is either `NULL` or a valid pointer to a `chd_file` obtained from [`chd_open`](crate::chd_open), [`chd_open_file`](crate::chd_open_file), or [`chd_open_core_file`](crate::chd_open_core_file).
141/// * If `chd` is `NULL`, this does nothing.
142pub unsafe extern "C" fn chd_close(chd: *mut chd_file) {
143    if !chd.is_null() {
144        unsafe { drop(Box::from_raw(chd)) }
145    }
146}
147
148#[no_mangle]
149/// Returns an error string for the corresponding CHD error.
150///
151/// # Safety
152/// The returned string is leaked and the memory **should not and can not ever** be validly freed.
153/// Attempting to free the returned pointer with `free` is **undefined behaviour**.
154pub unsafe extern "C" fn chd_error_string(err: chd_error) -> *const c_char {
155    // SAFETY: This will leak, but this is much safer than
156    // potentially allowing the C caller to corrupt internal state
157    // by returning an internal pointer to an interned string.
158    let err_string = unsafe { CString::new(err.to_string()).unwrap_unchecked() };
159    err_string.into_raw()
160}
161
162fn ffi_chd_get_header(chd: &chd_file) -> chd_header {
163    match chd.header() {
164        Header::V5Header(_) => header::get_v5_header(chd),
165        Header::V1Header(h) | Header::V2Header(h) => h.into(),
166        Header::V3Header(h) => h.into(),
167        Header::V4Header(h) => h.into(),
168    }
169}
170#[no_mangle]
171/// Returns a pointer to the extracted CHD header data.
172/// # Safety
173/// * `chd` is either `NULL` or a valid pointer to a `chd_file` obtained from [`chd_open`](crate::chd_open), [`chd_open_file`](crate::chd_open_file), or [`chd_open_core_file`](crate::chd_open_core_file).
174/// * If `chd` is `NULL`, returns `NULL`.
175/// * The returned pointer is leaked and the memory **should not and can not ever** be validly freed. Attempting to free the returned pointer with `free` is **undefined behaviour**. A non-leaking variant is provided in [`chd_read_header`](crate::chd_read_header).
176pub unsafe extern "C" fn chd_get_header(chd: *const chd_file) -> *const chd_header {
177    match unsafe { chd.as_ref() } {
178        Some(chd) => {
179            let header = ffi_chd_get_header(chd);
180            Box::into_raw(Box::new(header))
181        }
182        None => std::ptr::null(),
183    }
184}
185
186#[no_mangle]
187/// Read a single hunk from the CHD file.
188///
189/// # Safety
190/// * `chd` is either `NULL` or a valid pointer to a `chd_file` obtained from [`chd_open`](crate::chd_open), [`chd_open_file`](crate::chd_open_file), or [`chd_open_core_file`](crate::chd_open_core_file).
191/// * `buffer` must an aligned pointer to a block of initialized memory of exactly the hunk size for the input `chd_file*` that is valid for both reads and writes. This size can be found with [`chd_get_header`](crate::chd_get_header).
192/// * If `chd` is `NULL`, returns `CHDERR_INVALID_PARAMETER`.
193pub unsafe extern "C" fn chd_read(
194    chd: *mut chd_file,
195    hunknum: u32,
196    buffer: *mut c_void,
197) -> chd_error {
198    match unsafe { chd.as_mut() } {
199        None => chd_error::InvalidParameter,
200        Some(chd) => {
201            let hunk = chd.hunk(hunknum);
202            if let Ok(mut hunk) = hunk {
203                let size = hunk.len();
204                let mut comp_buf = Vec::new();
205                // SAFETY: The output buffer *must* be initialized and
206                // have a length of exactly the hunk size.
207                let output: &mut [u8] =
208                    unsafe { slice::from_raw_parts_mut(buffer as *mut u8, size) };
209                let result = hunk.read_hunk_in(&mut comp_buf, output);
210                match result {
211                    Ok(_) => chd_error::None,
212                    Err(e) => e,
213                }
214            } else {
215                chd_error::HunkOutOfRange
216            }
217        }
218    }
219}
220
221fn find_metadata(
222    chd: &mut chd_file,
223    search_tag: u32,
224    mut search_index: u32,
225) -> Result<Metadata, Error> {
226    for entry in chd.metadata_refs() {
227        if entry.metatag() == search_tag || entry.metatag() == KnownMetadata::Wildcard.metatag() {
228            if search_index == 0 {
229                return entry.read(chd.inner());
230            }
231            search_index -= 1;
232        }
233    }
234    Err(Error::MetadataNotFound)
235}
236#[no_mangle]
237/// Get indexed metadata of the given search tag and index.
238///
239/// # Safety
240/// * `chd` is either `NULL` or a valid pointer to a `chd_file` obtained from [`chd_open`](crate::chd_open), [`chd_open_file`](crate::chd_open_file), or [`chd_open_core_file`](crate::chd_open_core_file).
241/// * `output` must be an aligned pointer to a block of initialized memory of size exactly `output_len` that is valid for writes.
242/// * `result_len` must be either NULL or an aligned pointer to a `uint32_t` that is valid for writes.
243/// * `result_tag` must be either NULL or an aligned pointer to a `uint32_t` that is valid for writes.
244/// * `result_flags` must be either NULL or an aligned pointer to a `uint8_t` that is valid for writes.
245/// * If `chd` is `NULL`, returns `CHDERR_INVALID_PARAMETER`.
246pub unsafe extern "C" fn chd_get_metadata(
247    chd: *mut chd_file,
248    searchtag: u32,
249    searchindex: u32,
250    output: *mut c_void,
251    output_len: u32,
252    result_len: *mut u32,
253    result_tag: *mut u32,
254    result_flags: *mut u8,
255) -> chd_error {
256    match unsafe { chd.as_mut() } {
257        Some(chd) => {
258            let entry = find_metadata(chd, searchtag, searchindex);
259            match (entry, searchtag) {
260                (Ok(meta), _) => {
261                    unsafe {
262                        let output_len = std::cmp::min(output_len, meta.value.len() as u32);
263                        std::ptr::copy_nonoverlapping(
264                            meta.value.as_ptr() as *const c_void,
265                            output,
266                            output_len as usize,
267                        );
268
269                        if !result_tag.is_null() {
270                            result_tag.write(meta.metatag)
271                        }
272                        if !result_len.is_null() {
273                            result_len.write(meta.length)
274                        }
275                        if !result_flags.is_null() {
276                            result_flags.write(meta.flags)
277                        }
278                    }
279                    chd_error::None
280                }
281                (Err(_), tag) => unsafe {
282                    if (tag == KnownMetadata::HardDisk.metatag()
283                        || tag == KnownMetadata::Wildcard.metatag())
284                        && searchindex == 0
285                    {
286                        let header = chd.header();
287                        if let Header::V1Header(header) = header {
288                            let fake_meta = format!(
289                                "CYLS:{},HEADS:{},SECS:{},BPS:{}",
290                                header.cylinders,
291                                header.heads,
292                                header.sectors,
293                                header.hunk_bytes / header.hunk_size
294                            );
295                            let cstring = CString::from_vec_unchecked(fake_meta.into_bytes());
296                            let bytes = cstring.into_bytes_with_nul();
297                            let len = bytes.len();
298                            let output_len = std::cmp::min(output_len, len as u32);
299
300                            std::ptr::copy_nonoverlapping(
301                                bytes.as_ptr() as *const c_void,
302                                output,
303                                output_len as usize,
304                            );
305                            if !result_tag.is_null() {
306                                result_tag.write(KnownMetadata::HardDisk.metatag())
307                            }
308                            if !result_len.is_null() {
309                                result_len.write(len as u32)
310                            }
311                            return chd_error::None;
312                        }
313                    }
314                    chd_error::MetadataNotFound
315                },
316            }
317        }
318        None => chd_error::InvalidParameter,
319    }
320}
321
322#[no_mangle]
323/// Set codec internal parameters.
324///
325/// This function is not supported and always returns `CHDERR_INVALID_PARAMETER`.
326pub extern "C" fn chd_codec_config(
327    _chd: *const chd_file,
328    _param: i32,
329    _config: *mut c_void,
330) -> chd_error {
331    chd_error::InvalidParameter
332}
333
334#[no_mangle]
335/// Read CHD header data from the file into the pointed struct.
336///
337/// # Safety
338/// * `filename` is a valid, null-terminated **UTF-8** string.
339/// * `header` is either `NULL`, or an aligned pointer to a possibly uninitialized `chd_header` struct.
340/// * If `header` is `NULL`, returns `CHDERR_INVALID_PARAMETER`
341pub unsafe extern "C" fn chd_read_header(
342    filename: *const c_char,
343    header: *mut MaybeUninit<chd_header>,
344) -> chd_error {
345    let chd = ffi_open_chd(filename, None);
346    match chd {
347        Ok(chd) => {
348            let chd_header = ffi_chd_get_header(&chd);
349            match unsafe { header.as_mut() } {
350                None => Error::InvalidParameter,
351                Some(header) => {
352                    header.write(chd_header);
353                    Error::None
354                }
355            }
356        }
357        Err(e) => e,
358    }
359}
360
361#[no_mangle]
362#[cfg(feature = "chd_core_file")]
363#[cfg_attr(docsrs, doc(cfg(chd_core_file)))]
364/// Returns the associated `core_file*`.
365///
366/// This method has different semantics than `chd_core_file` in libchdr.
367///
368/// The input `chd_file*` will be dropped, and all prior references to
369/// to the input `chd_file*` are rendered invalid, with the same semantics as `chd_close`.
370///
371/// The provenance of the `chd_file*` is important to keep in mind.
372///
373/// If the input `chd_file*` was opened with [`chd_open`](crate::chd_open), the input `chd_file*` will be closed,
374/// and the return value should be considered undefined. For now it is `NULL`, but relying on this
375/// behaviour is unstable and may change in the future.
376///
377/// If the input `chd_file*` was opened with `chd_open_file` and the `chd_core_file` crate feature
378/// is enabled, this method will return the same pointer as passed to `chd_input_file`, which may
379/// be possible to cast to `FILE*` depending on the implementation of `libchdcorefile` that was
380/// linked.
381///
382/// # Safety
383/// * `chd` is either `NULL` or a valid pointer to a `chd_file` obtained from [`chd_open`](crate::chd_open), [`chd_open_file`](crate::chd_open_file), or [`chd_open_core_file`](crate::chd_open_core_file).
384/// * If `chd` is `NULL`, returns `NULL`.
385/// * If `chd` has provenance from [`chd_open`](crate::chd_open), the returned pointer is undefined and must not be used.
386/// * `chd` is **no longer valid** upon return of this function, and subsequent reuse of the `chd_file*` pointer is **undefined behaviour**.
387pub unsafe extern "C" fn chd_core_file(chd: *mut chd_file) -> *mut chdcorefile_sys::core_file {
388    if chd.is_null() {
389        return std::ptr::null_mut();
390    }
391
392    let (file, _) = ffi_takeown_chd(chd).into_inner();
393    let file_ref = file.as_any();
394
395    let pointer = match file_ref.downcast_ref::<crate::chdcorefile::CoreFile>() {
396        None => std::ptr::null_mut(),
397        Some(file) => file.file,
398    };
399    std::mem::forget(file);
400    pointer
401}
402
403#[no_mangle]
404#[cfg(feature = "chd_core_file")]
405#[cfg_attr(docsrs, doc(cfg(chd_core_file)))]
406/// Open an existing CHD file from an opened `core_file` object.
407///
408/// Ownership is taken of the `core_file*` object and should not be modified until
409/// `chd_core_file` is called to retake ownership of the `core_file*`.
410///
411/// # Safety
412/// * `file` is a valid pointer to a `core_file` with respect to the implementation of libchdcorefile that was linked.
413/// * `parent` is either `NULL` or a valid pointer to a `chd_file` obtained from [`chd_open`](crate::chd_open), [`chd_open_file`](crate::chd_open_file), or [`chd_open_core_file`](crate::chd_open_core_file).
414/// * `out` is aligned and can store a pointer to a `chd_file*`. On success, `out` will point to a valid `chd_file*`.
415/// * Until the returned `chd_file*` in `out` is closed with [`chd_close`](crate::chd_close) or [`chd_core_file`](crate::chd_core_file), external mutation of `file` will result in undefined behaviour.
416/// * After this function returns, `parent` is invalid and must not be used, otherwise it will be undefined behaviour. There is no way to retake ownership of `parent`.
417pub unsafe extern "C" fn chd_open_file(
418    file: *mut chdcorefile_sys::core_file,
419    mode: c_int,
420    parent: *mut chd_file,
421    out: *mut *mut chd_file,
422) -> chd_error {
423    // we don't support READWRITE mode
424    if mode == CHD_OPEN_READWRITE {
425        return chd_error::FileNotWriteable;
426    }
427
428    let parent = if parent.is_null() {
429        None
430    } else {
431        Some(ffi_takeown_chd(parent))
432    };
433
434    let core_file = Box::new(crate::chdcorefile::CoreFile { file: file }) as Box<dyn SeekRead>;
435    let chd = match Chd::open(core_file, parent) {
436        Ok(chd) => chd,
437        Err(e) => return e,
438    };
439
440    unsafe { *out = ffi_expose_chd(Box::new(chd)) }
441    chd_error::None
442}
443
444#[no_mangle]
445#[cfg(feature = "chd_virtio")]
446#[cfg_attr(docsrs, doc(cfg(chd_virtio)))]
447/// Open an existing CHD file from an opened `core_file` object.
448///
449/// Ownership is taken of the `core_file*` object and should not be modified until
450/// `chd_core_file` is called to retake ownership of the `core_file*`.
451///
452/// # Safety
453/// * `file` is a valid pointer to a `core_file` with respect to the implementation of libchdcorefile that was linked.
454/// * `parent` is either `NULL` or a valid pointer to a `chd_file` obtained from [`chd_open`](crate::chd_open), [`chd_open_file`](crate::chd_open_file), or [`chd_open_core_file`](crate::chd_open_core_file).
455/// * `out` is aligned and can store a pointer to a `chd_file*`. On success, `out` will point to a valid `chd_file*`.
456/// * Until the returned `chd_file*` in `out` is closed with [`chd_close`](crate::chd_close) or [`chd_core_file`](crate::chd_core_file), external mutation of `file` will result in undefined behaviour.
457/// * After this function returns, `parent` is invalid and must not be used, otherwise it will be undefined behaviour. There is no way to retake ownership of `parent`.
458pub unsafe extern "C" fn chd_open_core_file(
459    file: *mut chdcorefile_sys::core_file,
460    mode: c_int,
461    parent: *mut chd_file,
462    out: *mut *mut chd_file,
463) -> chd_error {
464    unsafe { chd_open_file(file, mode, parent, out) }
465}
466
467#[no_mangle]
468/// Get the name of a particular codec.
469///
470/// This method always returns the string "Unknown"
471pub extern "C" fn chd_get_codec_name(_codec: u32) -> *const c_char {
472    b"Unknown\0".as_ptr() as *const c_char
473}
474
475#[cfg(feature = "chd_precache")]
476use std::io::SeekFrom;
477
478#[cfg(feature = "chd_precache")]
479#[cfg_attr(docsrs, doc(cfg(chd_precache)))]
480/// The chunk size to read when pre-caching the underlying file stream into memory.
481pub const PRECACHE_CHUNK_SIZE: usize = 16 * 1024 * 1024;
482
483#[no_mangle]
484#[cfg(feature = "chd_precache")]
485#[cfg_attr(docsrs, doc(cfg(chd_precache)))]
486/// Precache the underlying file into memory with an optional callback to report progress.
487///
488/// The underlying stream of the input `chd_file` is swapped with a layout-undefined in-memory stream.
489///
490/// If the provenance of the original `chd_file` is from [`chd_open`](crate::chd_open), then the underlying
491/// stream is safely dropped.
492///
493/// If instead the underlying stream is a `core_file` opened from [`chd_open_file`](crate::chd_open_file),
494/// or [`chd_open_core_file`](crate::chd_open_core_file), then the same semantics of calling [`chd_core_file`](crate::chd_core_file)
495/// applies, and ownership of the underlying stream is released to the caller.
496///
497/// After precaching, the input `chd_file` no longer returns a valid inner stream when passed to [`chd_core_file`](crate::chd_core_file),
498/// and should be treated as having the same provenance as being from [`chd_open`](crate::chd_open).
499///
500/// # Safety
501/// * `chd` is either `NULL` or a valid pointer to a `chd_file` obtained from [`chd_open`](crate::chd_open), [`chd_open_file`](crate::chd_open_file), or [`chd_open_core_file`](crate::chd_open_core_file).
502pub unsafe extern "C" fn chd_precache_progress(
503    chd: *mut chd_file,
504    progress: Option<unsafe extern "C" fn(pos: usize, total: usize, param: *mut c_void)>,
505    param: *mut c_void,
506) -> chd_error {
507    let chd_file = if let Some(chd) = unsafe { chd.as_mut() } {
508        chd
509    } else {
510        return chd_error::InvalidParameter;
511    };
512
513    // if the inner is already a cursor over Vec<u8>, then it's already cached.
514    if chd_file.inner().as_any().is::<Cursor<Vec<u8>>>() {
515        return chd_error::None;
516    }
517
518    let file = chd_file.inner();
519    let length = if let Ok(length) = file.seek(SeekFrom::End(0)) {
520        length as usize
521    } else {
522        return chd_error::ReadError;
523    };
524
525    let mut buffer = Vec::new();
526    if let Err(_) = buffer.try_reserve_exact(length as usize) {
527        return chd_error::OutOfMemory;
528    }
529    let mut done: usize = 0;
530    let mut last_update_done: usize = 0;
531    let update_interval: usize = (length + 99) / 100;
532
533    if let Err(_) = file.seek(SeekFrom::Start(0)) {
534        return chd_error::ReadError;
535    }
536
537    while done < length {
538        let req_count = std::cmp::max(length - done, PRECACHE_CHUNK_SIZE);
539
540        // todo: this is kind of sus...
541        if let Err(_) = file.read_exact(&mut buffer[done..req_count]) {
542            return chd_error::ReadError;
543        }
544
545        done += req_count;
546        if let Some(progress) = progress {
547            if (done - last_update_done) >= update_interval && done != length {
548                last_update_done = done;
549                unsafe {
550                    progress(done, length, param);
551                }
552            }
553        }
554    }
555
556    // replace underlying stream of chd_file
557    let stream = Box::new(Cursor::new(buffer)) as Box<dyn SeekRead>;
558
559    // take ownership of the existing chd file
560    let chd_file = ffi_takeown_chd(chd);
561    let (_file, parent) = chd_file.into_inner();
562
563    let buffered_chd = match Chd::open(stream, parent) {
564        Err(e) => return e,
565        Ok(chd) => Box::new(chd),
566    };
567
568    let buffered_chd = ffi_expose_chd(buffered_chd);
569    unsafe { chd.swap(buffered_chd) };
570
571    chd_error::None
572}
573
574#[no_mangle]
575#[cfg(feature = "chd_precache")]
576#[cfg_attr(docsrs, doc(cfg(chd_precache)))]
577/// Precache the underlying file into memory.
578///
579/// The underlying stream of the input `chd_file` is swapped with a layout-undefined in-memory stream.
580///
581/// If the provenance of the original `chd_file` is from [`chd_open`](crate::chd_open), then the underlying
582/// stream is safely dropped.
583///
584/// If instead the underlying stream is a `core_file` opened from [`chd_open_file`](crate::chd_open_file),
585/// or [`chd_open_core_file`](crate::chd_open_core_file), then the same semantics of calling [`chd_core_file`](crate::chd_core_file)
586/// applies, and ownership of the underlying stream is released to the caller.
587///
588/// After precaching, the input `chd_file` no longer returns a valid inner stream when passed to [`chd_core_file`](crate::chd_core_file),
589/// and should be treated as having the same provenance as being from [`chd_open`](crate::chd_open).
590///
591/// # Safety
592/// * `chd` is either `NULL` or a valid pointer to a `chd_file` obtained from [`chd_open`](crate::chd_open), [`chd_open_file`](crate::chd_open_file), or [`chd_open_core_file`](crate::chd_open_core_file).
593pub unsafe extern "C" fn chd_precache(chd: *mut chd_file) -> chd_error {
594    unsafe { chd_precache_progress(chd, None, std::ptr::null_mut()) }
595}