#![cfg_attr(docsrs, feature(doc_cfg, doc_cfg_hide))]
#![deny(unsafe_op_in_unsafe_fn)]
extern crate core;
mod header;
#[cfg(feature = "chd_core_file")]
mod chdcorefile;
#[cfg(feature = "chd_core_file")]
#[allow(non_camel_case_types)]
#[allow(unused)]
mod chdcorefile_sys;
use crate::header::chd_header;
use chd::header::Header;
use chd::metadata::{KnownMetadata, Metadata, MetadataTag};
pub use chd::Error as chd_error;
use chd::{Chd, Error};
use std::any::Any;
use std::ffi::{CStr, CString};
use std::fs::File;
use std::io::{BufReader, Cursor, Read, Seek};
use std::mem::MaybeUninit;
use std::os::raw::{c_char, c_int, c_void};
use std::path::Path;
use std::slice;
pub const CHD_OPEN_READ: i32 = 1;
pub const CHD_OPEN_READWRITE: i32 = 2;
#[doc(hidden)]
pub trait SeekRead: Any + Read + Seek {
fn as_any(&self) -> &dyn Any;
}
impl<R: Any + Read + Seek> SeekRead for BufReader<R> {
fn as_any(&self) -> &dyn Any {
self
}
}
impl SeekRead for Cursor<Vec<u8>> {
fn as_any(&self) -> &dyn Any {
self
}
}
#[allow(non_camel_case_types)]
pub type chd_file = Chd<Box<dyn SeekRead>>;
fn ffi_takeown_chd(chd: *mut chd_file) -> Box<Chd<Box<dyn SeekRead>>> {
unsafe { Box::from_raw(chd) }
}
fn ffi_expose_chd(chd: Box<Chd<Box<dyn SeekRead>>>) -> *mut chd_file {
Box::into_raw(chd)
}
fn ffi_open_chd(
filename: *const c_char,
parent: Option<Box<chd_file>>,
) -> Result<chd_file, chd_error> {
let c_filename = unsafe { CStr::from_ptr(filename) };
let filename = std::str::from_utf8(c_filename.to_bytes())
.map(Path::new)
.map_err(|_| chd_error::InvalidParameter)?;
let file = File::open(filename).map_err(|_| chd_error::FileNotFound)?;
let bufread = Box::new(BufReader::new(file)) as Box<dyn SeekRead>;
Chd::open(bufread, parent)
}
#[no_mangle]
pub unsafe extern "C" fn chd_open(
filename: *const c_char,
mode: c_int,
parent: *mut chd_file,
out: *mut *mut chd_file,
) -> chd_error {
if mode == CHD_OPEN_READWRITE {
return chd_error::FileNotWriteable;
}
let parent = if parent.is_null() {
None
} else {
Some(ffi_takeown_chd(parent))
};
let chd = match ffi_open_chd(filename, parent) {
Ok(chd) => chd,
Err(e) => return e,
};
unsafe { *out = ffi_expose_chd(Box::new(chd)) }
chd_error::None
}
#[no_mangle]
pub unsafe extern "C" fn chd_close(chd: *mut chd_file) {
if !chd.is_null() {
unsafe { drop(Box::from_raw(chd)) }
}
}
#[no_mangle]
pub unsafe extern "C" fn chd_error_string(err: chd_error) -> *const c_char {
let err_string = unsafe { CString::new(err.to_string()).unwrap_unchecked() };
err_string.into_raw()
}
fn ffi_chd_get_header(chd: &chd_file) -> chd_header {
match chd.header() {
Header::V5Header(_) => header::get_v5_header(chd),
Header::V1Header(h) | Header::V2Header(h) => h.into(),
Header::V3Header(h) => h.into(),
Header::V4Header(h) => h.into(),
}
}
#[no_mangle]
pub unsafe extern "C" fn chd_get_header(chd: *const chd_file) -> *const chd_header {
match unsafe { chd.as_ref() } {
Some(chd) => {
let header = ffi_chd_get_header(chd);
Box::into_raw(Box::new(header))
}
None => std::ptr::null(),
}
}
#[no_mangle]
pub unsafe extern "C" fn chd_read(
chd: *mut chd_file,
hunknum: u32,
buffer: *mut c_void,
) -> chd_error {
match unsafe { chd.as_mut() } {
None => chd_error::InvalidParameter,
Some(chd) => {
let hunk = chd.hunk(hunknum);
if let Ok(mut hunk) = hunk {
let size = hunk.len();
let mut comp_buf = Vec::new();
let output: &mut [u8] =
unsafe { slice::from_raw_parts_mut(buffer as *mut u8, size) };
let result = hunk.read_hunk_in(&mut comp_buf, output);
match result {
Ok(_) => chd_error::None,
Err(e) => e,
}
} else {
chd_error::HunkOutOfRange
}
}
}
}
fn find_metadata(
chd: &mut chd_file,
search_tag: u32,
mut search_index: u32,
) -> Result<Metadata, Error> {
for entry in chd.metadata_refs() {
if entry.metatag() == search_tag || entry.metatag() == KnownMetadata::Wildcard.metatag() {
if search_index == 0 {
return entry.read(chd.inner());
}
search_index -= 1;
}
}
Err(Error::MetadataNotFound)
}
#[no_mangle]
pub unsafe extern "C" fn chd_get_metadata(
chd: *mut chd_file,
searchtag: u32,
searchindex: u32,
output: *mut c_void,
output_len: u32,
result_len: *mut u32,
result_tag: *mut u32,
result_flags: *mut u8,
) -> chd_error {
match unsafe { chd.as_mut() } {
Some(chd) => {
let entry = find_metadata(chd, searchtag, searchindex);
match (entry, searchtag) {
(Ok(meta), _) => {
unsafe {
let output_len = std::cmp::min(output_len, meta.value.len() as u32);
std::ptr::copy_nonoverlapping(
meta.value.as_ptr() as *const c_void,
output,
output_len as usize,
);
if !result_tag.is_null() {
result_tag.write(meta.metatag)
}
if !result_len.is_null() {
result_len.write(meta.length)
}
if !result_flags.is_null() {
result_flags.write(meta.flags)
}
}
chd_error::None
}
(Err(_), tag) => unsafe {
if (tag == KnownMetadata::HardDisk.metatag()
|| tag == KnownMetadata::Wildcard.metatag())
&& searchindex == 0
{
let header = chd.header();
if let Header::V1Header(header) = header {
let fake_meta = format!(
"CYLS:{},HEADS:{},SECS:{},BPS:{}",
header.cylinders,
header.heads,
header.sectors,
header.hunk_bytes / header.hunk_size
);
let cstring = CString::from_vec_unchecked(fake_meta.into_bytes());
let bytes = cstring.into_bytes_with_nul();
let len = bytes.len();
let output_len = std::cmp::min(output_len, len as u32);
std::ptr::copy_nonoverlapping(
bytes.as_ptr() as *const c_void,
output,
output_len as usize,
);
if !result_tag.is_null() {
result_tag.write(KnownMetadata::HardDisk.metatag())
}
if !result_len.is_null() {
result_len.write(len as u32)
}
return chd_error::None;
}
}
chd_error::MetadataNotFound
},
}
}
None => chd_error::InvalidParameter,
}
}
#[no_mangle]
pub extern "C" fn chd_codec_config(
_chd: *const chd_file,
_param: i32,
_config: *mut c_void,
) -> chd_error {
chd_error::InvalidParameter
}
#[no_mangle]
pub unsafe extern "C" fn chd_read_header(
filename: *const c_char,
header: *mut MaybeUninit<chd_header>,
) -> chd_error {
let chd = ffi_open_chd(filename, None);
match chd {
Ok(chd) => {
let chd_header = ffi_chd_get_header(&chd);
match unsafe { header.as_mut() } {
None => Error::InvalidParameter,
Some(header) => {
header.write(chd_header);
Error::None
}
}
}
Err(e) => e,
}
}
#[no_mangle]
#[cfg(feature = "chd_core_file")]
#[cfg_attr(docsrs, doc(cfg(chd_core_file)))]
pub unsafe extern "C" fn chd_core_file(chd: *mut chd_file) -> *mut chdcorefile_sys::core_file {
if chd.is_null() {
return std::ptr::null_mut();
}
let (file, _) = ffi_takeown_chd(chd).into_inner();
let file_ref = file.as_any();
let pointer = match file_ref.downcast_ref::<crate::chdcorefile::CoreFile>() {
None => std::ptr::null_mut(),
Some(file) => file.file,
};
std::mem::forget(file);
pointer
}
#[no_mangle]
#[cfg(feature = "chd_core_file")]
#[cfg_attr(docsrs, doc(cfg(chd_core_file)))]
pub unsafe extern "C" fn chd_open_file(
file: *mut chdcorefile_sys::core_file,
mode: c_int,
parent: *mut chd_file,
out: *mut *mut chd_file,
) -> chd_error {
if mode == CHD_OPEN_READWRITE {
return chd_error::FileNotWriteable;
}
let parent = if parent.is_null() {
None
} else {
Some(ffi_takeown_chd(parent))
};
let core_file = Box::new(crate::chdcorefile::CoreFile { file: file }) as Box<dyn SeekRead>;
let chd = match Chd::open(core_file, parent) {
Ok(chd) => chd,
Err(e) => return e,
};
unsafe { *out = ffi_expose_chd(Box::new(chd)) }
chd_error::None
}
#[no_mangle]
#[cfg(feature = "chd_virtio")]
#[cfg_attr(docsrs, doc(cfg(chd_virtio)))]
pub unsafe extern "C" fn chd_open_core_file(
file: *mut chdcorefile_sys::core_file,
mode: c_int,
parent: *mut chd_file,
out: *mut *mut chd_file,
) -> chd_error {
unsafe { chd_open_file(file, mode, parent, out) }
}
#[no_mangle]
pub extern "C" fn chd_get_codec_name(_codec: u32) -> *const c_char {
b"Unknown\0".as_ptr() as *const c_char
}
#[cfg(feature = "chd_precache")]
use std::io::SeekFrom;
#[cfg(feature = "chd_precache")]
#[cfg_attr(docsrs, doc(cfg(chd_precache)))]
pub const PRECACHE_CHUNK_SIZE: usize = 16 * 1024 * 1024;
#[no_mangle]
#[cfg(feature = "chd_precache")]
#[cfg_attr(docsrs, doc(cfg(chd_precache)))]
pub unsafe extern "C" fn chd_precache_progress(
chd: *mut chd_file,
progress: Option<unsafe extern "C" fn(pos: usize, total: usize, param: *mut c_void)>,
param: *mut c_void,
) -> chd_error {
let chd_file = if let Some(chd) = unsafe { chd.as_mut() } {
chd
} else {
return chd_error::InvalidParameter;
};
if chd_file.inner().as_any().is::<Cursor<Vec<u8>>>() {
return chd_error::None;
}
let file = chd_file.inner();
let length = if let Ok(length) = file.seek(SeekFrom::End(0)) {
length as usize
} else {
return chd_error::ReadError;
};
let mut buffer = Vec::new();
if let Err(_) = buffer.try_reserve_exact(length as usize) {
return chd_error::OutOfMemory;
}
let mut done: usize = 0;
let mut last_update_done: usize = 0;
let update_interval: usize = (length + 99) / 100;
if let Err(_) = file.seek(SeekFrom::Start(0)) {
return chd_error::ReadError;
}
while done < length {
let req_count = std::cmp::max(length - done, PRECACHE_CHUNK_SIZE);
if let Err(_) = file.read_exact(&mut buffer[done..req_count]) {
return chd_error::ReadError;
}
done += req_count;
if let Some(progress) = progress {
if (done - last_update_done) >= update_interval && done != length {
last_update_done = done;
unsafe {
progress(done, length, param);
}
}
}
}
let stream = Box::new(Cursor::new(buffer)) as Box<dyn SeekRead>;
let chd_file = ffi_takeown_chd(chd);
let (_file, parent) = chd_file.into_inner();
let buffered_chd = match Chd::open(stream, parent) {
Err(e) => return e,
Ok(chd) => Box::new(chd),
};
let buffered_chd = ffi_expose_chd(buffered_chd);
unsafe { chd.swap(buffered_chd) };
chd_error::None
}
#[no_mangle]
#[cfg(feature = "chd_precache")]
#[cfg_attr(docsrs, doc(cfg(chd_precache)))]
pub unsafe extern "C" fn chd_precache(chd: *mut chd_file) -> chd_error {
unsafe { chd_precache_progress(chd, None, std::ptr::null_mut()) }
}