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}