Switch libchdr to chd-rs
This commit is contained in:
parent
186a4a16f4
commit
6cab4a4f99
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
/target/
|
|
@ -0,0 +1,243 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
|
||||
|
||||
[[package]]
|
||||
name = "bitreader"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdd859c9d97f7c468252795b35aeccc412bdbb1e90ee6969c4fa6328272eaeff"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chd"
|
||||
version = "0.3.1"
|
||||
source = "git+https://github.com/SnowflakePowered/chd-rs#891b4296dccbe5be383284fd22b7a015956d43e6"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitreader",
|
||||
"byteorder",
|
||||
"claxon",
|
||||
"crc",
|
||||
"flate2",
|
||||
"lzma-rs-perf-exp",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"ruzstd",
|
||||
"take_mut",
|
||||
"text_io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chd-capi"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"chd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "claxon"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bfbf56724aa9eca8afa4fcfadeb479e722935bb2a0900c2d37e0cc477af0688"
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
|
||||
dependencies = [
|
||||
"crc-catalog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc-catalog"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lzma-rs-perf-exp"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38435c1305548bb408c98242841c3cf161246323e72a3e4433787f3c05bf18ee"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"crc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.60",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruzstd"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5174a470eeb535a721ae9fdd6e291c2411a906b96592182d05217591d5c5cf7b"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"derive_more",
|
||||
"twox-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "take_mut"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
|
||||
|
||||
[[package]]
|
||||
name = "text_io"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5f0c8eb2ad70c12a6a69508f499b3051c924f4b1cfeae85bfad96e6bc5bba46"
|
||||
|
||||
[[package]]
|
||||
name = "twox-hash"
|
||||
version = "1.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "chd-capi"
|
||||
version = "0.3.1"
|
||||
edition = "2021"
|
||||
authors = ["Ronny Chan <ronny@ronnychan.ca>"]
|
||||
description = "libchdr-compatible C API for a Rust implementation of the CHD File Format"
|
||||
license = "BSD-3-Clause"
|
||||
repository = "https://github.com/SnowflakePowered/chd-rs"
|
||||
readme = "README.md"
|
||||
categories = ["emulators", "compression", "encoding"]
|
||||
keywords = ["mame", "chd", "decompression"]
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
chd_precache = []
|
||||
verify_block_crc = ["chd/verify_block_crc"]
|
||||
|
||||
[dependencies]
|
||||
chd = { version = "0.3.1", git = "https://github.com/SnowflakePowered/chd-rs" }
|
|
@ -0,0 +1,43 @@
|
|||
# `chd-capi`
|
||||
|
||||
⚠️*The C API has not been heavily tested. Use at your own risk.* ⚠️
|
||||
|
||||
chd-rs provides a C API compatible with [chd.h](https://github.com/rtissera/libchdr/blob/6eeb6abc4adc094d489c8ba8cafdcff9ff61251b/include/libchdr/chd.h).
|
||||
ABI compatibility is detailed below but is untested when compiling as a dynamic library. The intended consumption for this crate is not via cargo, but by vendoring
|
||||
the [sources of the C API](https://github.com/SnowflakePowered/chd-rs/tree/master/chd-rs-capi) in tree, along with a compatible `libchdcorefile` implementation
|
||||
for your platform.
|
||||
|
||||
## Features
|
||||
### `verify_block_crc`
|
||||
Enables the `verify_block_crc` of the `chd` crate to verify decompressed CHD hunks with their internal hash.
|
||||
|
||||
### `chd_core_file`
|
||||
Enables `core_file*` and the`chd_open_file`, and `chd_core_file` APIs. This feature requires a `libchdcorefile` implementation,
|
||||
or the default POSIX compatible implementation (where `core_file*` is `FILE*`) will be used.
|
||||
|
||||
Note that by default, `core_file*` is not an opaque pointer and is a C `FILE*` stream. This allows the underlying
|
||||
file pointer to be changed unsafely beneath the memory safety guarantees of chd-rs. We strongly encourage using
|
||||
`chd_open` instead of `chd_open_file`.
|
||||
|
||||
If you need `core_file*` support, chd-capi should have the `chd_core_file` feature enabled, which will wrap
|
||||
`FILE*` to be usable in Rust with a lightweight wrapper in `libchdcorefile`. If the default implementation
|
||||
is not suitable, you may need to implement `libchdcorefile` yourself. The `chd_core_file` feature requires
|
||||
CMake and Clang to be installed.
|
||||
|
||||
### `chd_virtio`
|
||||
Enables the [virtual I/O](https://github.com/rtissera/libchdr/pull/78) functions `chd_open_core_file`.
|
||||
Because this C API requires `core_file` to be an opaque pointer, there is no difference between `chd_open_file` and
|
||||
`chd_open_core_file` unlike libchdr, and `chd_open_core_file` is simply an alias for `chd_open_file`. All functions that
|
||||
take `core_file*` require a `libchdcorefile` implementation.
|
||||
|
||||
### `chd_precache`
|
||||
Enables precaching of the underlying file into memory with the `chd_precache_progress` and `chd_precache` functions.
|
||||
|
||||
## ABI compatibility
|
||||
|
||||
chd-rs makes the following ABI-compatibility guarantees compared to libchdr when compiled statically.
|
||||
* `chd_error` is ABI and API-compatible with [chd.h](https://github.com/rtissera/libchdr/blob/cdcb714235b9ff7d207b703260706a364282b063/include/libchdr/chd.h#L258)
|
||||
* `chd_header` is ABI and API-compatible [chd.h](https://github.com/rtissera/libchdr/blob/cdcb714235b9ff7d207b703260706a364282b063/include/libchdr/chd.h#L302)
|
||||
* `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)
|
||||
* The layout of `core_file *` is user-defined when the `chd_core_file` feature is enabled.
|
||||
* 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`.
|
|
@ -0,0 +1,3 @@
|
|||
@cargo b --release
|
||||
@copy target\release\chd_capi.dll ..\..\Assets\dll
|
||||
@copy target\release\chd_capi.dll ..\..\output\dll
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/sh
|
||||
if [ -z "$BIZHAWKBUILD_HOME" ]; then export BIZHAWKBUILD_HOME="$(realpath "$(dirname "$0")/../..")"; fi
|
||||
|
||||
cargo b --release
|
||||
|
||||
cp target/release/libchd_capi.so "$BIZHAWKBUILD_HOME/Assets/dll"
|
||||
if [ -e "$BIZHAWKBUILD_HOME/output" ]; then
|
||||
cp target/release/libchd_capi.so "$BIZHAWKBUILD_HOME/output/dll"
|
||||
fi
|
|
@ -0,0 +1,168 @@
|
|||
use crate::chd_file;
|
||||
use chd::header::{Header, HeaderV1, HeaderV3, HeaderV4, HeaderV5};
|
||||
use chd::map::Map;
|
||||
use std::mem;
|
||||
|
||||
pub const CHD_MD5_BYTES: usize = 16;
|
||||
pub const CHD_SHA1_BYTES: usize = 20;
|
||||
|
||||
#[repr(C)]
|
||||
#[allow(non_camel_case_types)]
|
||||
/// libchdr-compatible CHD header struct.
|
||||
/// This struct is ABI-compatible with [chd.h](https://github.com/rtissera/libchdr/blob/cdcb714235b9ff7d207b703260706a364282b063/include/libchdr/chd.h#L302)
|
||||
pub struct chd_header {
|
||||
length: u32,
|
||||
version: u32,
|
||||
flags: u32,
|
||||
compression: [u32; 4],
|
||||
hunkbytes: u32,
|
||||
totalhunks: u32,
|
||||
logicalbytes: u64,
|
||||
metaoffset: u64,
|
||||
mapoffset: u64,
|
||||
md5: [u8; CHD_MD5_BYTES],
|
||||
parentmd5: [u8; CHD_MD5_BYTES],
|
||||
sha1: [u8; CHD_SHA1_BYTES],
|
||||
rawsha1: [u8; CHD_SHA1_BYTES],
|
||||
parentsha1: [u8; CHD_SHA1_BYTES],
|
||||
unitbytes: u32,
|
||||
unitcount: u64,
|
||||
hunkcount: u32,
|
||||
mapentrybytes: u32,
|
||||
rawmap: *mut u8,
|
||||
obsolete_cylinders: u32,
|
||||
obsolete_sectors: u32,
|
||||
obsolete_heads: u32,
|
||||
obsolete_hunksize: u32,
|
||||
}
|
||||
|
||||
impl From<&HeaderV1> for chd_header {
|
||||
fn from(header: &HeaderV1) -> Self {
|
||||
chd_header {
|
||||
length: header.length,
|
||||
version: header.version as u32,
|
||||
flags: header.flags,
|
||||
compression: [header.compression, 0, 0, 0],
|
||||
hunkbytes: header.hunk_bytes,
|
||||
totalhunks: header.total_hunks,
|
||||
logicalbytes: header.logical_bytes,
|
||||
metaoffset: 0,
|
||||
mapoffset: 0,
|
||||
md5: header.md5,
|
||||
parentmd5: header.parent_md5,
|
||||
sha1: [0u8; CHD_SHA1_BYTES],
|
||||
rawsha1: [0u8; CHD_SHA1_BYTES],
|
||||
parentsha1: [0u8; CHD_SHA1_BYTES],
|
||||
unitbytes: header.unit_bytes,
|
||||
unitcount: header.unit_count,
|
||||
hunkcount: header.total_hunks,
|
||||
mapentrybytes: 0,
|
||||
rawmap: std::ptr::null_mut(),
|
||||
obsolete_cylinders: header.cylinders,
|
||||
obsolete_sectors: header.sectors,
|
||||
obsolete_heads: header.heads,
|
||||
obsolete_hunksize: header.hunk_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&HeaderV3> for chd_header {
|
||||
fn from(header: &HeaderV3) -> Self {
|
||||
chd_header {
|
||||
length: header.length,
|
||||
version: header.version as u32,
|
||||
flags: header.flags,
|
||||
compression: [header.compression, 0, 0, 0],
|
||||
hunkbytes: header.hunk_bytes,
|
||||
totalhunks: header.total_hunks,
|
||||
logicalbytes: header.logical_bytes,
|
||||
metaoffset: header.meta_offset,
|
||||
mapoffset: 0,
|
||||
md5: header.md5,
|
||||
parentmd5: header.parent_md5,
|
||||
sha1: header.sha1,
|
||||
rawsha1: [0u8; CHD_SHA1_BYTES],
|
||||
parentsha1: header.parent_sha1,
|
||||
unitbytes: header.unit_bytes,
|
||||
unitcount: header.unit_count,
|
||||
hunkcount: header.total_hunks,
|
||||
mapentrybytes: 0,
|
||||
rawmap: std::ptr::null_mut(),
|
||||
obsolete_cylinders: 0,
|
||||
obsolete_sectors: 0,
|
||||
obsolete_heads: 0,
|
||||
obsolete_hunksize: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&HeaderV4> for chd_header {
|
||||
fn from(header: &HeaderV4) -> Self {
|
||||
chd_header {
|
||||
length: header.length,
|
||||
version: header.version as u32,
|
||||
flags: header.flags,
|
||||
compression: [header.compression, 0, 0, 0],
|
||||
hunkbytes: header.hunk_bytes,
|
||||
totalhunks: header.total_hunks,
|
||||
logicalbytes: header.logical_bytes,
|
||||
metaoffset: header.meta_offset,
|
||||
mapoffset: 0,
|
||||
md5: [0u8; CHD_MD5_BYTES],
|
||||
parentmd5: [0u8; CHD_MD5_BYTES],
|
||||
sha1: header.sha1,
|
||||
rawsha1: header.raw_sha1,
|
||||
parentsha1: header.parent_sha1,
|
||||
unitbytes: header.unit_bytes,
|
||||
unitcount: header.unit_count,
|
||||
hunkcount: header.total_hunks,
|
||||
mapentrybytes: 0,
|
||||
rawmap: std::ptr::null_mut(),
|
||||
obsolete_cylinders: 0,
|
||||
obsolete_sectors: 0,
|
||||
obsolete_heads: 0,
|
||||
obsolete_hunksize: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_v5_header(chd: &chd_file) -> chd_header {
|
||||
let header: HeaderV5 = match chd.header() {
|
||||
Header::V5Header(h) => h.clone(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut map_data: Vec<u8> = match chd.map() {
|
||||
Map::V5(map) => map.into(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let version = header.version;
|
||||
let map_ptr = map_data.as_mut_ptr();
|
||||
mem::forget(map_data);
|
||||
|
||||
chd_header {
|
||||
length: header.length,
|
||||
version: version as u32,
|
||||
// libchdr just reads garbage for V5 flags, we will give it as 0.
|
||||
flags: 0,
|
||||
compression: header.compression,
|
||||
hunkbytes: header.hunk_bytes,
|
||||
totalhunks: header.hunk_count,
|
||||
logicalbytes: header.logical_bytes,
|
||||
metaoffset: header.meta_offset,
|
||||
mapoffset: header.map_offset,
|
||||
md5: [0u8; CHD_MD5_BYTES],
|
||||
parentmd5: [0u8; CHD_MD5_BYTES],
|
||||
sha1: header.sha1,
|
||||
rawsha1: header.raw_sha1,
|
||||
parentsha1: header.parent_sha1,
|
||||
unitbytes: header.unit_bytes,
|
||||
unitcount: header.unit_count,
|
||||
hunkcount: header.hunk_count,
|
||||
mapentrybytes: header.map_entry_bytes,
|
||||
rawmap: map_ptr,
|
||||
obsolete_cylinders: 0,
|
||||
obsolete_sectors: 0,
|
||||
obsolete_heads: 0,
|
||||
obsolete_hunksize: 0,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,595 @@
|
|||
#![cfg_attr(docsrs, feature(doc_cfg, doc_cfg_hide))]
|
||||
#![deny(unsafe_op_in_unsafe_fn)]
|
||||
//! A (mostly) [libchdr](https://github.com/rtissera/libchdr) compatible C-API for [chd-rs](https://crates.io/crates/chd).
|
||||
//!
|
||||
//! For Rust consumers, consider using [chd-rs](https://crates.io/crates/chd) instead.
|
||||
//!
|
||||
//! 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
|
||||
//! into your project, with a compatible implementation of [libchdcorefile](https://github.com/SnowflakePowered/chd-rs/tree/master/chd-rs-capi/libchdcorefile)
|
||||
//! for your platform as required.
|
||||
//!
|
||||
//! ## ABI compatibility with libchdr
|
||||
//!
|
||||
//! chd-rs-capi makes the following ABI-compatibility guarantees compared to libchdr when compiled statically.
|
||||
//! * `chd_error` is ABI and API-compatible with [chd.h](https://github.com/rtissera/libchdr/blob/cdcb714235b9ff7d207b703260706a364282b063/include/libchdr/chd.h#L258)
|
||||
//! * `chd_header` is ABI and API-compatible [chd.h](https://github.com/rtissera/libchdr/blob/cdcb714235b9ff7d207b703260706a364282b063/include/libchdr/chd.h#L302)
|
||||
//! * `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)
|
||||
//! * The layout of `core_file *` is user-defined when the `chd_core_file` feature is enabled.
|
||||
//! * 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`.
|
||||
|
||||
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;
|
||||
|
||||
/// Open a CHD for reading.
|
||||
pub const CHD_OPEN_READ: i32 = 1;
|
||||
/// Open a CHD for reading and writing. This mode is not supported and will always return an error
|
||||
/// when passed into a constructor function such as [`chd_open`](crate::chd_open).
|
||||
pub const CHD_OPEN_READWRITE: i32 = 2;
|
||||
|
||||
/// Trait alias for `Read + Seek + Any`.
|
||||
#[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)]
|
||||
/// An opaque type for an opened CHD file.
|
||||
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)
|
||||
}
|
||||
|
||||
/// Opens a CHD file by file name, with a layout-undefined backing file pointer owned by
|
||||
/// the library.
|
||||
///
|
||||
/// The result of passing an object created by this function into [`chd_core_file`](crate::chd_core_file)
|
||||
/// is strictly undefined. Instead, all `chd_file*` pointers with provenance from `chd_open` should be
|
||||
/// closed with [`chd_close`](crate::chd_close).
|
||||
///
|
||||
/// # Safety
|
||||
/// * `filename` is a valid, null-terminated **UTF-8** string.
|
||||
/// * `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).
|
||||
/// * `out` is aligned and can store a pointer to a `chd_file*`. On success, `out` will point to a valid `chd_file*`.
|
||||
/// * 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`.
|
||||
#[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 {
|
||||
// we don't support READWRITE mode
|
||||
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]
|
||||
/// Close a CHD file.
|
||||
///
|
||||
/// # Safety
|
||||
/// * `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).
|
||||
/// * If `chd` is `NULL`, this does nothing.
|
||||
pub unsafe extern "C" fn chd_close(chd: *mut chd_file) {
|
||||
if !chd.is_null() {
|
||||
unsafe { drop(Box::from_raw(chd)) }
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
/// Returns an error string for the corresponding CHD error.
|
||||
///
|
||||
/// # Safety
|
||||
/// The returned string 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**.
|
||||
pub unsafe extern "C" fn chd_error_string(err: chd_error) -> *const c_char {
|
||||
// SAFETY: This will leak, but this is much safer than
|
||||
// potentially allowing the C caller to corrupt internal state
|
||||
// by returning an internal pointer to an interned string.
|
||||
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]
|
||||
/// Returns a pointer to the extracted CHD header data.
|
||||
/// # Safety
|
||||
/// * `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).
|
||||
/// * If `chd` is `NULL`, returns `NULL`.
|
||||
/// * 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).
|
||||
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]
|
||||
/// Read a single hunk from the CHD file.
|
||||
///
|
||||
/// # Safety
|
||||
/// * `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).
|
||||
/// * `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).
|
||||
/// * If `chd` is `NULL`, returns `CHDERR_INVALID_PARAMETER`.
|
||||
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();
|
||||
// SAFETY: The output buffer *must* be initialized and
|
||||
// have a length of exactly the hunk size.
|
||||
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]
|
||||
/// Get indexed metadata of the given search tag and index.
|
||||
///
|
||||
/// # Safety
|
||||
/// * `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).
|
||||
/// * `output` must be an aligned pointer to a block of initialized memory of size exactly `output_len` that is valid for writes.
|
||||
/// * `result_len` must be either NULL or an aligned pointer to a `uint32_t` that is valid for writes.
|
||||
/// * `result_tag` must be either NULL or an aligned pointer to a `uint32_t` that is valid for writes.
|
||||
/// * `result_flags` must be either NULL or an aligned pointer to a `uint8_t` that is valid for writes.
|
||||
/// * If `chd` is `NULL`, returns `CHDERR_INVALID_PARAMETER`.
|
||||
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]
|
||||
/// Set codec internal parameters.
|
||||
///
|
||||
/// This function is not supported and always returns `CHDERR_INVALID_PARAMETER`.
|
||||
pub extern "C" fn chd_codec_config(
|
||||
_chd: *const chd_file,
|
||||
_param: i32,
|
||||
_config: *mut c_void,
|
||||
) -> chd_error {
|
||||
chd_error::InvalidParameter
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
/// Read CHD header data from the file into the pointed struct.
|
||||
///
|
||||
/// # Safety
|
||||
/// * `filename` is a valid, null-terminated **UTF-8** string.
|
||||
/// * `header` is either `NULL`, or an aligned pointer to a possibly uninitialized `chd_header` struct.
|
||||
/// * If `header` is `NULL`, returns `CHDERR_INVALID_PARAMETER`
|
||||
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)))]
|
||||
/// Returns the associated `core_file*`.
|
||||
///
|
||||
/// This method has different semantics than `chd_core_file` in libchdr.
|
||||
///
|
||||
/// The input `chd_file*` will be dropped, and all prior references to
|
||||
/// to the input `chd_file*` are rendered invalid, with the same semantics as `chd_close`.
|
||||
///
|
||||
/// The provenance of the `chd_file*` is important to keep in mind.
|
||||
///
|
||||
/// If the input `chd_file*` was opened with [`chd_open`](crate::chd_open), the input `chd_file*` will be closed,
|
||||
/// and the return value should be considered undefined. For now it is `NULL`, but relying on this
|
||||
/// behaviour is unstable and may change in the future.
|
||||
///
|
||||
/// If the input `chd_file*` was opened with `chd_open_file` and the `chd_core_file` crate feature
|
||||
/// is enabled, this method will return the same pointer as passed to `chd_input_file`, which may
|
||||
/// be possible to cast to `FILE*` depending on the implementation of `libchdcorefile` that was
|
||||
/// linked.
|
||||
///
|
||||
/// # Safety
|
||||
/// * `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).
|
||||
/// * If `chd` is `NULL`, returns `NULL`.
|
||||
/// * If `chd` has provenance from [`chd_open`](crate::chd_open), the returned pointer is undefined and must not be used.
|
||||
/// * `chd` is **no longer valid** upon return of this function, and subsequent reuse of the `chd_file*` pointer is **undefined behaviour**.
|
||||
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.0,
|
||||
};
|
||||
std::mem::forget(file);
|
||||
pointer
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "chd_core_file")]
|
||||
#[cfg_attr(docsrs, doc(cfg(chd_core_file)))]
|
||||
/// Open an existing CHD file from an opened `core_file` object.
|
||||
///
|
||||
/// Ownership is taken of the `core_file*` object and should not be modified until
|
||||
/// `chd_core_file` is called to retake ownership of the `core_file*`.
|
||||
///
|
||||
/// # Safety
|
||||
/// * `file` is a valid pointer to a `core_file` with respect to the implementation of libchdcorefile that was linked.
|
||||
/// * `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).
|
||||
/// * `out` is aligned and can store a pointer to a `chd_file*`. On success, `out` will point to a valid `chd_file*`.
|
||||
/// * 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.
|
||||
/// * 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`.
|
||||
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 {
|
||||
// we don't support READWRITE mode
|
||||
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)) 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)))]
|
||||
/// Open an existing CHD file from an opened `core_file` object.
|
||||
///
|
||||
/// Ownership is taken of the `core_file*` object and should not be modified until
|
||||
/// `chd_core_file` is called to retake ownership of the `core_file*`.
|
||||
///
|
||||
/// # Safety
|
||||
/// * `file` is a valid pointer to a `core_file` with respect to the implementation of libchdcorefile that was linked.
|
||||
/// * `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).
|
||||
/// * `out` is aligned and can store a pointer to a `chd_file*`. On success, `out` will point to a valid `chd_file*`.
|
||||
/// * 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.
|
||||
/// * 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`.
|
||||
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 {
|
||||
chd_open_file(file, mode, parent, out)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
/// Get the name of a particular codec.
|
||||
///
|
||||
/// This method always returns the string "Unknown"
|
||||
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)))]
|
||||
/// The chunk size to read when pre-caching the underlying file stream into memory.
|
||||
pub const PRECACHE_CHUNK_SIZE: usize = 16 * 1024 * 1024;
|
||||
|
||||
#[no_mangle]
|
||||
#[cfg(feature = "chd_precache")]
|
||||
#[cfg_attr(docsrs, doc(cfg(chd_precache)))]
|
||||
/// Precache the underlying file into memory with an optional callback to report progress.
|
||||
///
|
||||
/// The underlying stream of the input `chd_file` is swapped with a layout-undefined in-memory stream.
|
||||
///
|
||||
/// If the provenance of the original `chd_file` is from [`chd_open`](crate::chd_open), then the underlying
|
||||
/// stream is safely dropped.
|
||||
///
|
||||
/// If instead the underlying stream is a `core_file` opened from [`chd_open_file`](crate::chd_open_file),
|
||||
/// or [`chd_open_core_file`](crate::chd_open_core_file), then the same semantics of calling [`chd_core_file`](crate::chd_core_file)
|
||||
/// applies, and ownership of the underlying stream is released to the caller.
|
||||
///
|
||||
/// After precaching, the input `chd_file` no longer returns a valid inner stream when passed to [`chd_core_file`](crate::chd_core_file),
|
||||
/// and should be treated as having the same provenance as being from [`chd_open`](crate::chd_open).
|
||||
///
|
||||
/// # Safety
|
||||
/// * `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).
|
||||
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 the inner is already a cursor over Vec<u8>, then it's already cached.
|
||||
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);
|
||||
|
||||
// todo: this is kind of sus...
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// replace underlying stream of chd_file
|
||||
let stream = Box::new(Cursor::new(buffer)) as Box<dyn SeekRead>;
|
||||
|
||||
// take ownership of the existing chd file
|
||||
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)))]
|
||||
/// Precache the underlying file into memory.
|
||||
///
|
||||
/// The underlying stream of the input `chd_file` is swapped with a layout-undefined in-memory stream.
|
||||
///
|
||||
/// If the provenance of the original `chd_file` is from [`chd_open`](crate::chd_open), then the underlying
|
||||
/// stream is safely dropped.
|
||||
///
|
||||
/// If instead the underlying stream is a `core_file` opened from [`chd_open_file`](crate::chd_open_file),
|
||||
/// or [`chd_open_core_file`](crate::chd_open_core_file), then the same semantics of calling [`chd_core_file`](crate::chd_core_file)
|
||||
/// applies, and ownership of the underlying stream is released to the caller.
|
||||
///
|
||||
/// After precaching, the input `chd_file` no longer returns a valid inner stream when passed to [`chd_core_file`](crate::chd_core_file),
|
||||
/// and should be treated as having the same provenance as being from [`chd_open`](crate::chd_open).
|
||||
///
|
||||
/// # Safety
|
||||
/// * `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).
|
||||
pub unsafe extern "C" fn chd_precache(chd: *mut chd_file) -> chd_error {
|
||||
chd_precache_progress(chd, None, std::ptr::null_mut())
|
||||
}
|
|
@ -5,16 +5,14 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
{
|
||||
internal class Blob_CHD : IBlob
|
||||
{
|
||||
private LibChdr.CoreFileStreamWrapper _coreFile;
|
||||
private IntPtr _chdFile;
|
||||
|
||||
private readonly uint _hunkSize;
|
||||
private readonly byte[] _hunkCache;
|
||||
private int _currentHunk;
|
||||
|
||||
public Blob_CHD(LibChdr.CoreFileStreamWrapper coreFile, IntPtr chdFile, uint hunkSize)
|
||||
public Blob_CHD(IntPtr chdFile, uint hunkSize)
|
||||
{
|
||||
_coreFile = coreFile;
|
||||
_chdFile = chdFile;
|
||||
_hunkSize = hunkSize;
|
||||
_hunkCache = new byte[hunkSize];
|
||||
|
@ -25,12 +23,9 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
{
|
||||
if (_chdFile != IntPtr.Zero)
|
||||
{
|
||||
LibChdr.chd_close(_chdFile);
|
||||
LibChd.chd_close(_chdFile);
|
||||
_chdFile = IntPtr.Zero;
|
||||
}
|
||||
|
||||
_coreFile?.Dispose();
|
||||
_coreFile = null;
|
||||
}
|
||||
|
||||
public int Read(long byte_pos, byte[] buffer, int offset, int count)
|
||||
|
@ -41,8 +36,8 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
var targetHunk = (uint)(byte_pos / _hunkSize);
|
||||
if (targetHunk != _currentHunk)
|
||||
{
|
||||
var err = LibChdr.chd_read(_chdFile, targetHunk, _hunkCache);
|
||||
if (err != LibChdr.chd_error.CHDERR_NONE)
|
||||
var err = LibChd.chd_read(_chdFile, targetHunk, _hunkCache);
|
||||
if (err != LibChd.chd_error.CHDERR_NONE)
|
||||
{
|
||||
// shouldn't ever happen in practice, unless something has gone terribly wrong
|
||||
throw new IOException($"CHD read failed with error {err}");
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Buffers.Binary;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
|
@ -11,7 +12,7 @@ using BizHawk.Emulation.DiscSystem.CUE;
|
|||
|
||||
#pragma warning disable BHI1005
|
||||
|
||||
// MAME CHD images, using the standard libchdr for reading
|
||||
// MAME CHD images, using chd-rs for reading
|
||||
// helpful reference: https://problemkaputt.de/psxspx-cdrom-disk-images-chd-mame.htm
|
||||
|
||||
namespace BizHawk.Emulation.DiscSystem
|
||||
|
@ -20,24 +21,19 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
{
|
||||
/// <summary>
|
||||
/// Represents a CHD file.
|
||||
/// This isn't particularly faithful to the format, but rather it just wraps libchdr's chd_file
|
||||
/// This isn't particularly faithful to the format, but rather it just wraps a chd_file
|
||||
/// </summary>
|
||||
public class CHDFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper of a C# stream to a chd_core_file
|
||||
/// </summary>
|
||||
public LibChdr.CoreFileStreamWrapper CoreFile;
|
||||
|
||||
/// <summary>
|
||||
/// chd_file* to be used for libchdr functions
|
||||
/// chd_file* to be used for chd_ functions
|
||||
/// </summary>
|
||||
public IntPtr ChdFile;
|
||||
|
||||
/// <summary>
|
||||
/// CHD header, interpreted by libchdr
|
||||
/// CHD header, interpreted by chd-rs
|
||||
/// </summary>
|
||||
public LibChdr.chd_header Header;
|
||||
public LibChd.chd_header Header;
|
||||
|
||||
/// <summary>
|
||||
/// CHD CD metadata for each track
|
||||
|
@ -65,12 +61,12 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// <summary>
|
||||
/// Track type
|
||||
/// </summary>
|
||||
public LibChdr.chd_track_type TrackType;
|
||||
public LibChd.chd_track_type TrackType;
|
||||
|
||||
/// <summary>
|
||||
/// Subcode type
|
||||
/// </summary>
|
||||
public LibChdr.chd_sub_type SubType;
|
||||
public LibChd.chd_sub_type SubType;
|
||||
|
||||
/// <summary>
|
||||
/// Size of each sector
|
||||
|
@ -104,12 +100,12 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
/// <summary>
|
||||
/// Pregap track type
|
||||
/// </summary>
|
||||
public LibChdr.chd_track_type PregapTrackType;
|
||||
public LibChd.chd_track_type PregapTrackType;
|
||||
|
||||
/// <summary>
|
||||
/// Pregap subcode type
|
||||
/// </summary>
|
||||
public LibChdr.chd_sub_type PregapSubType;
|
||||
public LibChd.chd_sub_type PregapSubType;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether pregap is in the CHD
|
||||
|
@ -129,30 +125,30 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
public CHDParseException(string message, Exception ex) : base(message, ex) { }
|
||||
}
|
||||
|
||||
private static LibChdr.chd_track_type GetTrackType(string type)
|
||||
private static LibChd.chd_track_type GetTrackType(string type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
"MODE1" => LibChdr.chd_track_type.CD_TRACK_MODE1,
|
||||
"MODE1/2048" => LibChdr.chd_track_type.CD_TRACK_MODE1,
|
||||
"MODE1_RAW" => LibChdr.chd_track_type.CD_TRACK_MODE1_RAW,
|
||||
"MODE1/2352" => LibChdr.chd_track_type.CD_TRACK_MODE1_RAW,
|
||||
"MODE2" => LibChdr.chd_track_type.CD_TRACK_MODE2,
|
||||
"MODE2/2336" => LibChdr.chd_track_type.CD_TRACK_MODE2,
|
||||
"MODE2_FORM1" => LibChdr.chd_track_type.CD_TRACK_MODE2_FORM1,
|
||||
"MODE2/2048" => LibChdr.chd_track_type.CD_TRACK_MODE2_FORM1,
|
||||
"MODE2_FORM2" => LibChdr.chd_track_type.CD_TRACK_MODE2_FORM2,
|
||||
"MODE2/2324" => LibChdr.chd_track_type.CD_TRACK_MODE2_FORM2,
|
||||
"MODE2_FORM_MIX" => LibChdr.chd_track_type.CD_TRACK_MODE2_FORM_MIX,
|
||||
"MODE2_RAW" => LibChdr.chd_track_type.CD_TRACK_MODE2_RAW,
|
||||
"MODE2/2352" => LibChdr.chd_track_type.CD_TRACK_MODE2_RAW,
|
||||
"CDI/2352" => LibChdr.chd_track_type.CD_TRACK_MODE2_RAW,
|
||||
"AUDIO" => LibChdr.chd_track_type.CD_TRACK_AUDIO,
|
||||
"MODE1" => LibChd.chd_track_type.CD_TRACK_MODE1,
|
||||
"MODE1/2048" => LibChd.chd_track_type.CD_TRACK_MODE1,
|
||||
"MODE1_RAW" => LibChd.chd_track_type.CD_TRACK_MODE1_RAW,
|
||||
"MODE1/2352" => LibChd.chd_track_type.CD_TRACK_MODE1_RAW,
|
||||
"MODE2" => LibChd.chd_track_type.CD_TRACK_MODE2,
|
||||
"MODE2/2336" => LibChd.chd_track_type.CD_TRACK_MODE2,
|
||||
"MODE2_FORM1" => LibChd.chd_track_type.CD_TRACK_MODE2_FORM1,
|
||||
"MODE2/2048" => LibChd.chd_track_type.CD_TRACK_MODE2_FORM1,
|
||||
"MODE2_FORM2" => LibChd.chd_track_type.CD_TRACK_MODE2_FORM2,
|
||||
"MODE2/2324" => LibChd.chd_track_type.CD_TRACK_MODE2_FORM2,
|
||||
"MODE2_FORM_MIX" => LibChd.chd_track_type.CD_TRACK_MODE2_FORM_MIX,
|
||||
"MODE2_RAW" => LibChd.chd_track_type.CD_TRACK_MODE2_RAW,
|
||||
"MODE2/2352" => LibChd.chd_track_type.CD_TRACK_MODE2_RAW,
|
||||
"CDI/2352" => LibChd.chd_track_type.CD_TRACK_MODE2_RAW,
|
||||
"AUDIO" => LibChd.chd_track_type.CD_TRACK_AUDIO,
|
||||
_ => throw new CHDParseException("Malformed CHD format: Invalid track type!"),
|
||||
};
|
||||
}
|
||||
|
||||
private static (LibChdr.chd_track_type TrackType, bool ChdContainsPregap) GetTrackTypeForPregap(string type)
|
||||
private static (LibChd.chd_track_type TrackType, bool ChdContainsPregap) GetTrackTypeForPregap(string type)
|
||||
{
|
||||
if (type.Length > 0 && type[0] == 'V')
|
||||
{
|
||||
|
@ -162,40 +158,40 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
return (GetTrackType(type), false);
|
||||
}
|
||||
|
||||
private static uint GetSectorSize(LibChdr.chd_track_type type)
|
||||
private static uint GetSectorSize(LibChd.chd_track_type type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE1 => 2048,
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE1_RAW => 2352,
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE2 => 2336,
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE2_FORM1 => 2048,
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE2_FORM2 => 2324,
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE2_FORM_MIX => 2336,
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE2_RAW => 2352,
|
||||
LibChdr.chd_track_type.CD_TRACK_AUDIO => 2352,
|
||||
LibChd.chd_track_type.CD_TRACK_MODE1 => 2048,
|
||||
LibChd.chd_track_type.CD_TRACK_MODE1_RAW => 2352,
|
||||
LibChd.chd_track_type.CD_TRACK_MODE2 => 2336,
|
||||
LibChd.chd_track_type.CD_TRACK_MODE2_FORM1 => 2048,
|
||||
LibChd.chd_track_type.CD_TRACK_MODE2_FORM2 => 2324,
|
||||
LibChd.chd_track_type.CD_TRACK_MODE2_FORM_MIX => 2336,
|
||||
LibChd.chd_track_type.CD_TRACK_MODE2_RAW => 2352,
|
||||
LibChd.chd_track_type.CD_TRACK_AUDIO => 2352,
|
||||
_ => throw new CHDParseException("Malformed CHD format: Invalid track type!"),
|
||||
};
|
||||
}
|
||||
|
||||
private static LibChdr.chd_sub_type GetSubType(string type)
|
||||
private static LibChd.chd_sub_type GetSubType(string type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
"RW" => LibChdr.chd_sub_type.CD_SUB_NORMAL,
|
||||
"RW_RAW" => LibChdr.chd_sub_type.CD_SUB_RAW,
|
||||
"NONE" => LibChdr.chd_sub_type.CD_SUB_NONE,
|
||||
"RW" => LibChd.chd_sub_type.CD_SUB_NORMAL,
|
||||
"RW_RAW" => LibChd.chd_sub_type.CD_SUB_RAW,
|
||||
"NONE" => LibChd.chd_sub_type.CD_SUB_NONE,
|
||||
_ => throw new CHDParseException("Malformed CHD format: Invalid sub type!"),
|
||||
};
|
||||
}
|
||||
|
||||
private static uint GetSubSize(LibChdr.chd_sub_type type)
|
||||
private static uint GetSubSize(LibChd.chd_sub_type type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
LibChdr.chd_sub_type.CD_SUB_NORMAL => 96,
|
||||
LibChdr.chd_sub_type.CD_SUB_RAW => 96,
|
||||
LibChdr.chd_sub_type.CD_SUB_NONE => 0,
|
||||
LibChd.chd_sub_type.CD_SUB_NORMAL => 96,
|
||||
LibChd.chd_sub_type.CD_SUB_RAW => 96,
|
||||
LibChd.chd_sub_type.CD_SUB_NONE => 0,
|
||||
_ => throw new CHDParseException("Malformed CHD format: Invalid sub type!"),
|
||||
};
|
||||
}
|
||||
|
@ -312,8 +308,8 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
};
|
||||
if (bigEndian)
|
||||
{
|
||||
cdMetadata.TrackType = (LibChdr.chd_track_type)BinaryPrimitives.ReadUInt32BigEndian(track);
|
||||
cdMetadata.SubType = (LibChdr.chd_sub_type)BinaryPrimitives.ReadUInt32BigEndian(track[..4]);
|
||||
cdMetadata.TrackType = (LibChd.chd_track_type)BinaryPrimitives.ReadUInt32BigEndian(track);
|
||||
cdMetadata.SubType = (LibChd.chd_sub_type)BinaryPrimitives.ReadUInt32BigEndian(track[..4]);
|
||||
cdMetadata.SectorSize = BinaryPrimitives.ReadUInt32BigEndian(track[..8]);
|
||||
cdMetadata.SubSize = BinaryPrimitives.ReadUInt32BigEndian(track[..12]);
|
||||
cdMetadata.Frames = BinaryPrimitives.ReadUInt32BigEndian(track[..16]);
|
||||
|
@ -321,8 +317,8 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
}
|
||||
else
|
||||
{
|
||||
cdMetadata.TrackType = (LibChdr.chd_track_type)BinaryPrimitives.ReadUInt32LittleEndian(track);
|
||||
cdMetadata.SubType = (LibChdr.chd_sub_type)BinaryPrimitives.ReadUInt32LittleEndian(track[..4]);
|
||||
cdMetadata.TrackType = (LibChd.chd_track_type)BinaryPrimitives.ReadUInt32LittleEndian(track);
|
||||
cdMetadata.SubType = (LibChd.chd_sub_type)BinaryPrimitives.ReadUInt32LittleEndian(track[..4]);
|
||||
cdMetadata.SectorSize = BinaryPrimitives.ReadUInt32LittleEndian(track[..8]);
|
||||
cdMetadata.SubSize = BinaryPrimitives.ReadUInt32LittleEndian(track[..12]);
|
||||
cdMetadata.Frames = BinaryPrimitives.ReadUInt32LittleEndian(track[..16]);
|
||||
|
@ -350,32 +346,52 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
}
|
||||
|
||||
/// <exception cref="CHDParseException">malformed chd format</exception>
|
||||
public static CHDFile ParseFrom(Stream stream)
|
||||
public static CHDFile ParseFrom(string path)
|
||||
{
|
||||
var chdf = new CHDFile();
|
||||
try
|
||||
{
|
||||
chdf.CoreFile = new(stream);
|
||||
var err = LibChdr.chd_open_core_file(chdf.CoreFile.CoreFile, LibChdr.CHD_OPEN_READ, IntPtr.Zero, out chdf.ChdFile);
|
||||
if (err != LibChdr.chd_error.CHDERR_NONE)
|
||||
// .NET Standard 2.0 doesn't have UnmanagedType.LPUTF8Str :(
|
||||
// (although .NET Framework has it just fine along with modern .NET)
|
||||
var nb = Encoding.UTF8.GetMaxByteCount(path.Length);
|
||||
var ptr = Marshal.AllocCoTaskMem(checked(nb + 1));
|
||||
try
|
||||
{
|
||||
throw new CHDParseException($"Malformed CHD format: Failed to open chd, got error {err}");
|
||||
unsafe
|
||||
{
|
||||
fixed (char* c = path)
|
||||
{
|
||||
var pbMem = (byte*)ptr;
|
||||
var nbWritten = Encoding.UTF8.GetBytes(c, path.Length, pbMem!, nb);
|
||||
pbMem[nbWritten] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
var err = LibChd.chd_open(ptr, LibChd.CHD_OPEN_READ, IntPtr.Zero, out chdf.ChdFile);
|
||||
if (err != LibChd.chd_error.CHDERR_NONE)
|
||||
{
|
||||
throw new CHDParseException($"Malformed CHD format: Failed to open chd, got error {err}");
|
||||
}
|
||||
|
||||
err = LibChd.chd_read_header(ptr, ref chdf.Header);
|
||||
if (err != LibChd.chd_error.CHDERR_NONE)
|
||||
{
|
||||
throw new CHDParseException($"Malformed CHD format: Failed to read chd header, got error {err}");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeCoTaskMem(ptr);
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
var header = (LibChdr.chd_header*)LibChdr.chd_get_header(chdf.ChdFile);
|
||||
chdf.Header = *header;
|
||||
}
|
||||
|
||||
if (chdf.Header.hunkbytes == 0 || chdf.Header.hunkbytes % LibChdr.CD_FRAME_SIZE != 0)
|
||||
if (chdf.Header.hunkbytes == 0 || chdf.Header.hunkbytes % LibChd.CD_FRAME_SIZE != 0)
|
||||
{
|
||||
throw new CHDParseException("Malformed CHD format: Invalid hunk size");
|
||||
}
|
||||
|
||||
// libchdr puts the correct value here for older versions of chds which don't have this
|
||||
// chd-rs puts the correct value here for older versions of chds which don't have this
|
||||
// for newer chds, it is left as is, which might be invalid
|
||||
if (chdf.Header.unitbytes != LibChdr.CD_FRAME_SIZE)
|
||||
if (chdf.Header.unitbytes != LibChd.CD_FRAME_SIZE)
|
||||
{
|
||||
throw new CHDParseException("Malformed CHD format: Invalid unit size");
|
||||
}
|
||||
|
@ -383,18 +399,18 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
var metadataOutput = new byte[256];
|
||||
for (uint i = 0; i < 99; i++)
|
||||
{
|
||||
err = LibChdr.chd_get_metadata(chdf.ChdFile, LibChdr.CDROM_TRACK_METADATA2_TAG,
|
||||
var err = LibChd.chd_get_metadata(chdf.ChdFile, LibChd.CDROM_TRACK_METADATA2_TAG,
|
||||
i, metadataOutput, (uint)metadataOutput.Length, out var resultLen, out _, out _);
|
||||
if (err == LibChdr.chd_error.CHDERR_NONE)
|
||||
if (err == LibChd.chd_error.CHDERR_NONE)
|
||||
{
|
||||
var metadata = Encoding.ASCII.GetString(metadataOutput, 0, (int)resultLen).TrimEnd('\0');
|
||||
chdf.CdMetadatas.Add(ParseMetadata2(metadata));
|
||||
continue;
|
||||
}
|
||||
|
||||
err = LibChdr.chd_get_metadata(chdf.ChdFile, LibChdr.CDROM_TRACK_METADATA_TAG,
|
||||
err = LibChd.chd_get_metadata(chdf.ChdFile, LibChd.CDROM_TRACK_METADATA_TAG,
|
||||
i, metadataOutput, (uint)metadataOutput.Length, out resultLen, out _, out _);
|
||||
if (err == LibChdr.chd_error.CHDERR_NONE)
|
||||
if (err == LibChd.chd_error.CHDERR_NONE)
|
||||
{
|
||||
var metadata = Encoding.ASCII.GetString(metadataOutput, 0, (int)resultLen).TrimEnd('\0');
|
||||
chdf.CdMetadatas.Add(ParseMetadata(metadata));
|
||||
|
@ -415,9 +431,9 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
{
|
||||
// if no metadata was present, we might have "old" metadata instead (which has all track info stored in one entry)
|
||||
metadataOutput = new byte[4 + 24 * 99];
|
||||
err = LibChdr.chd_get_metadata(chdf.ChdFile, LibChdr.CDROM_OLD_METADATA_TAG,
|
||||
var err = LibChd.chd_get_metadata(chdf.ChdFile, LibChd.CDROM_OLD_METADATA_TAG,
|
||||
0, metadataOutput, (uint)metadataOutput.Length, out var resultLen, out _, out _);
|
||||
if (err == LibChdr.chd_error.CHDERR_NONE)
|
||||
if (err == LibChd.chd_error.CHDERR_NONE)
|
||||
{
|
||||
if (resultLen != metadataOutput.Length)
|
||||
{
|
||||
|
@ -447,7 +463,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
}
|
||||
|
||||
// pad expected sectors up to the next hunk
|
||||
var sectorsPerHunk = chdf.Header.hunkbytes / LibChdr.CD_FRAME_SIZE;
|
||||
var sectorsPerHunk = chdf.Header.hunkbytes / LibChd.CD_FRAME_SIZE;
|
||||
chdExpectedNumSectors = (chdExpectedNumSectors + sectorsPerHunk - 1) / sectorsPerHunk * sectorsPerHunk;
|
||||
|
||||
var chdActualNumSectors = chdf.Header.hunkcount * sectorsPerHunk;
|
||||
|
@ -462,15 +478,9 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
{
|
||||
if (chdf.ChdFile != IntPtr.Zero)
|
||||
{
|
||||
LibChdr.chd_close(chdf.ChdFile);
|
||||
LibChd.chd_close(chdf.ChdFile);
|
||||
}
|
||||
|
||||
if (chdf.CoreFile == null)
|
||||
{
|
||||
stream.Dispose();
|
||||
}
|
||||
|
||||
chdf.CoreFile?.Dispose();
|
||||
throw ex as CHDParseException ?? new("Malformed CHD format: An unknown exception was thrown while parsing", ex);
|
||||
}
|
||||
}
|
||||
|
@ -493,8 +503,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
{
|
||||
if (!File.Exists(path)) throw new CHDParseException("Malformed CHD format: Nonexistent CHD file!");
|
||||
|
||||
var infCHD = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
ret.ParsedCHDFile = ParseFrom(infCHD);
|
||||
ret.ParsedCHDFile = ParseFrom(path);
|
||||
ret.Valid = true;
|
||||
}
|
||||
catch (CHDParseException ex)
|
||||
|
@ -571,7 +580,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
try
|
||||
{
|
||||
var chdf = loadResults.ParsedCHDFile;
|
||||
IBlob chdBlob = new Blob_CHD(chdf.CoreFile, chdf.ChdFile, chdf.Header.hunkbytes);
|
||||
IBlob chdBlob = new Blob_CHD(chdf.ChdFile, chdf.Header.hunkbytes);
|
||||
disc.DisposableResources.Add(chdBlob);
|
||||
|
||||
// chds only support 1 session
|
||||
|
@ -584,7 +593,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
var q = default(SubchannelQ);
|
||||
//absent some kind of policy for how to set it, this is a safe assumption
|
||||
const byte kADR = 1;
|
||||
var control = cdMetadata.TrackType != LibChdr.chd_track_type.CD_TRACK_AUDIO
|
||||
var control = cdMetadata.TrackType != LibChd.chd_track_type.CD_TRACK_AUDIO
|
||||
? EControlQ.DATA
|
||||
: EControlQ.None;
|
||||
q.SetStatus(kADR, control);
|
||||
|
@ -597,36 +606,36 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
return new() { QData = q };
|
||||
}
|
||||
|
||||
static SS_Base CreateSynth(LibChdr.chd_track_type trackType)
|
||||
static SS_Base CreateSynth(LibChd.chd_track_type trackType)
|
||||
{
|
||||
return trackType switch
|
||||
{
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE1 => new SS_Mode1_2048(),
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE1_RAW => new SS_2352(),
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE2 => new SS_Mode2_2336(),
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE2_FORM1 => new SS_Mode2_Form1_2048(),
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE2_FORM2 => new SS_Mode2_Form2_2324(),
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE2_FORM_MIX => new SS_Mode2_2336(),
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE2_RAW => new SS_2352(),
|
||||
LibChdr.chd_track_type.CD_TRACK_AUDIO => new SS_CHD_Audio(),
|
||||
LibChd.chd_track_type.CD_TRACK_MODE1 => new SS_Mode1_2048(),
|
||||
LibChd.chd_track_type.CD_TRACK_MODE1_RAW => new SS_2352(),
|
||||
LibChd.chd_track_type.CD_TRACK_MODE2 => new SS_Mode2_2336(),
|
||||
LibChd.chd_track_type.CD_TRACK_MODE2_FORM1 => new SS_Mode2_Form1_2048(),
|
||||
LibChd.chd_track_type.CD_TRACK_MODE2_FORM2 => new SS_Mode2_Form2_2324(),
|
||||
LibChd.chd_track_type.CD_TRACK_MODE2_FORM_MIX => new SS_Mode2_2336(),
|
||||
LibChd.chd_track_type.CD_TRACK_MODE2_RAW => new SS_2352(),
|
||||
LibChd.chd_track_type.CD_TRACK_AUDIO => new SS_CHD_Audio(),
|
||||
_ => throw new InvalidOperationException(),
|
||||
};
|
||||
}
|
||||
|
||||
static CueTrackType ToCueTrackType(LibChdr.chd_track_type chdTrackType, bool isCdi)
|
||||
static CueTrackType ToCueTrackType(LibChd.chd_track_type chdTrackType, bool isCdi)
|
||||
{
|
||||
// rough matches, not too important if these are somewhat wrong (they're just used for generated gaps)
|
||||
return chdTrackType switch
|
||||
{
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE1 => CueTrackType.Mode1_2048,
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE1_RAW => CueTrackType.Mode1_2352,
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE2 => CueTrackType.Mode2_2336,
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE2_FORM1 => CueTrackType.Mode2_2336,
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE2_FORM2 => CueTrackType.Mode2_2336,
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE2_FORM_MIX => CueTrackType.Mode2_2336,
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE2_RAW when isCdi => CueTrackType.CDI_2352,
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE2_RAW => CueTrackType.Mode2_2352,
|
||||
LibChdr.chd_track_type.CD_TRACK_AUDIO => CueTrackType.Audio,
|
||||
LibChd.chd_track_type.CD_TRACK_MODE1 => CueTrackType.Mode1_2048,
|
||||
LibChd.chd_track_type.CD_TRACK_MODE1_RAW => CueTrackType.Mode1_2352,
|
||||
LibChd.chd_track_type.CD_TRACK_MODE2 => CueTrackType.Mode2_2336,
|
||||
LibChd.chd_track_type.CD_TRACK_MODE2_FORM1 => CueTrackType.Mode2_2336,
|
||||
LibChd.chd_track_type.CD_TRACK_MODE2_FORM2 => CueTrackType.Mode2_2336,
|
||||
LibChd.chd_track_type.CD_TRACK_MODE2_FORM_MIX => CueTrackType.Mode2_2336,
|
||||
LibChd.chd_track_type.CD_TRACK_MODE2_RAW when isCdi => CueTrackType.CDI_2352,
|
||||
LibChd.chd_track_type.CD_TRACK_MODE2_RAW => CueTrackType.Mode2_2352,
|
||||
LibChd.chd_track_type.CD_TRACK_AUDIO => CueTrackType.Audio,
|
||||
_ => throw new InvalidOperationException(),
|
||||
};
|
||||
}
|
||||
|
@ -657,7 +666,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
synth.Policy = IN_DiscMountPolicy;
|
||||
const byte kADR = 1;
|
||||
var control = cdMetadata.PregapTrackType != LibChdr.chd_track_type.CD_TRACK_AUDIO
|
||||
var control = cdMetadata.PregapTrackType != LibChd.chd_track_type.CD_TRACK_AUDIO
|
||||
? EControlQ.DATA
|
||||
: EControlQ.None;
|
||||
synth.sq.SetStatus(kADR, control);
|
||||
|
@ -676,14 +685,14 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
// wrap the base synth with our special synth if we have subcode in the chd
|
||||
ISectorSynthJob2448 chdSynth = cdMetadata.PregapSubType switch
|
||||
{
|
||||
LibChdr.chd_sub_type.CD_SUB_NORMAL => new SS_CHD_Sub(synth, isInterleaved: true),
|
||||
LibChdr.chd_sub_type.CD_SUB_RAW => new SS_CHD_Sub(synth, isInterleaved: false),
|
||||
LibChdr.chd_sub_type.CD_SUB_NONE => synth,
|
||||
LibChd.chd_sub_type.CD_SUB_NORMAL => new SS_CHD_Sub(synth, isInterleaved: true),
|
||||
LibChd.chd_sub_type.CD_SUB_RAW => new SS_CHD_Sub(synth, isInterleaved: false),
|
||||
LibChd.chd_sub_type.CD_SUB_NONE => synth,
|
||||
_ => throw new InvalidOperationException(),
|
||||
};
|
||||
|
||||
disc._Sectors.Add(chdSynth);
|
||||
chdOffset += LibChdr.CD_FRAME_SIZE;
|
||||
chdOffset += LibChd.CD_FRAME_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -708,7 +717,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
synth.BlobOffset = chdOffset;
|
||||
synth.Policy = IN_DiscMountPolicy;
|
||||
const byte kADR = 1;
|
||||
var control = cdMetadata.TrackType != LibChdr.chd_track_type.CD_TRACK_AUDIO
|
||||
var control = cdMetadata.TrackType != LibChd.chd_track_type.CD_TRACK_AUDIO
|
||||
? EControlQ.DATA
|
||||
: EControlQ.None;
|
||||
synth.sq.SetStatus(kADR, control);
|
||||
|
@ -721,17 +730,17 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
synth.Pause = false;
|
||||
ISectorSynthJob2448 chdSynth = cdMetadata.SubType switch
|
||||
{
|
||||
LibChdr.chd_sub_type.CD_SUB_NORMAL => new SS_CHD_Sub(synth, isInterleaved: true),
|
||||
LibChdr.chd_sub_type.CD_SUB_RAW => new SS_CHD_Sub(synth, isInterleaved: false),
|
||||
LibChdr.chd_sub_type.CD_SUB_NONE => synth,
|
||||
LibChd.chd_sub_type.CD_SUB_NORMAL => new SS_CHD_Sub(synth, isInterleaved: true),
|
||||
LibChd.chd_sub_type.CD_SUB_RAW => new SS_CHD_Sub(synth, isInterleaved: false),
|
||||
LibChd.chd_sub_type.CD_SUB_NONE => synth,
|
||||
_ => throw new InvalidOperationException(),
|
||||
};
|
||||
disc._Sectors.Add(chdSynth);
|
||||
chdOffset += LibChdr.CD_FRAME_SIZE;
|
||||
chdOffset += LibChd.CD_FRAME_SIZE;
|
||||
relMSF++;
|
||||
}
|
||||
|
||||
chdOffset += cdMetadata.Padding * LibChdr.CD_FRAME_SIZE;
|
||||
chdOffset += cdMetadata.Padding * LibChd.CD_FRAME_SIZE;
|
||||
|
||||
for (var i = 0; i < cdMetadata.PostGap; i++)
|
||||
{
|
||||
|
@ -741,7 +750,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
Policy = IN_DiscMountPolicy
|
||||
};
|
||||
const byte kADR = 1;
|
||||
var control = cdMetadata.TrackType != LibChdr.chd_track_type.CD_TRACK_AUDIO
|
||||
var control = cdMetadata.TrackType != LibChd.chd_track_type.CD_TRACK_AUDIO
|
||||
? EControlQ.DATA
|
||||
: EControlQ.None;
|
||||
synth.sq.SetStatus(kADR, control);
|
||||
|
@ -766,11 +775,11 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
return SessionFormat.Type10_CDI;
|
||||
}
|
||||
|
||||
if (cdMetadata.TrackType is LibChdr.chd_track_type.CD_TRACK_MODE2
|
||||
or LibChdr.chd_track_type.CD_TRACK_MODE2_FORM1
|
||||
or LibChdr.chd_track_type.CD_TRACK_MODE2_FORM2
|
||||
or LibChdr.chd_track_type.CD_TRACK_MODE2_FORM_MIX
|
||||
or LibChdr.chd_track_type.CD_TRACK_MODE2_RAW)
|
||||
if (cdMetadata.TrackType is LibChd.chd_track_type.CD_TRACK_MODE2
|
||||
or LibChd.chd_track_type.CD_TRACK_MODE2_FORM1
|
||||
or LibChd.chd_track_type.CD_TRACK_MODE2_FORM2
|
||||
or LibChd.chd_track_type.CD_TRACK_MODE2_FORM_MIX
|
||||
or LibChd.chd_track_type.CD_TRACK_MODE2_RAW)
|
||||
{
|
||||
return SessionFormat.Type20_CDXA;
|
||||
}
|
||||
|
@ -874,20 +883,20 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
// write header
|
||||
// note CHD header has values in big endian, while BinaryWriter will write in little endian
|
||||
bw.Write(_chdTag);
|
||||
bw.Write(BinaryPrimitives.ReverseEndianness(LibChdr.CHD_V5_HEADER_SIZE));
|
||||
bw.Write(BinaryPrimitives.ReverseEndianness(LibChdr.CHD_HEADER_VERSION));
|
||||
bw.Write(BinaryPrimitives.ReverseEndianness(LibChd.CHD_V5_HEADER_SIZE));
|
||||
bw.Write(BinaryPrimitives.ReverseEndianness(LibChd.CHD_HEADER_VERSION));
|
||||
// v5 chd allows for 4 different compression types
|
||||
// we only have 1 implemented here
|
||||
bw.Write(BinaryPrimitives.ReverseEndianness(LibChdr.CHD_CODEC_ZSTD));
|
||||
bw.Write(BinaryPrimitives.ReverseEndianness(LibChd.CHD_CODEC_ZSTD));
|
||||
bw.Write(0);
|
||||
bw.Write(0);
|
||||
bw.Write(0);
|
||||
bw.Write(0L); // total size of all uncompressed data (written later)
|
||||
bw.Write(0L); // offset to hunk map (written later)
|
||||
bw.Write(0L); // offset to first metadata (written later)
|
||||
bw.Write(BinaryPrimitives.ReverseEndianness(LibChdr.CD_FRAME_SIZE * CD_FRAMES_PER_HUNK)); // bytes per hunk
|
||||
bw.Write(BinaryPrimitives.ReverseEndianness(LibChdr.CD_FRAME_SIZE)); // bytes per sector (always CD_FRAME_SIZE)
|
||||
var blankSha1 = new byte[LibChdr.CHD_SHA1_BYTES];
|
||||
bw.Write(BinaryPrimitives.ReverseEndianness(LibChd.CD_FRAME_SIZE * CD_FRAMES_PER_HUNK)); // bytes per hunk
|
||||
bw.Write(BinaryPrimitives.ReverseEndianness(LibChd.CD_FRAME_SIZE)); // bytes per sector (always CD_FRAME_SIZE)
|
||||
var blankSha1 = new byte[LibChd.CHD_SHA1_BYTES];
|
||||
bw.Write(blankSha1); // SHA1 of raw data (written later)
|
||||
bw.Write(blankSha1); // SHA1 of raw data + metadata (written later)
|
||||
bw.Write(blankSha1); // SHA1 of raw data + metadata for parent (N/A, always 0 for us)
|
||||
|
@ -909,12 +918,12 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
IsCDI = track.Mode == 2 && session.TOC.SessionFormat == SessionFormat.Type10_CDI,
|
||||
TrackType = track.Mode switch
|
||||
{
|
||||
0 => LibChdr.chd_track_type.CD_TRACK_AUDIO,
|
||||
1 => LibChdr.chd_track_type.CD_TRACK_MODE1_RAW,
|
||||
2 => LibChdr.chd_track_type.CD_TRACK_MODE2_RAW,
|
||||
0 => LibChd.chd_track_type.CD_TRACK_AUDIO,
|
||||
1 => LibChd.chd_track_type.CD_TRACK_MODE1_RAW,
|
||||
2 => LibChd.chd_track_type.CD_TRACK_MODE2_RAW,
|
||||
_ => throw new InvalidOperationException(),
|
||||
},
|
||||
SubType = LibChdr.chd_sub_type.CD_SUB_RAW,
|
||||
SubType = LibChd.chd_sub_type.CD_SUB_RAW,
|
||||
SectorSize = 2352,
|
||||
SubSize = 96,
|
||||
Frames = (uint)(track.NextTrack.LBA - firstIndexLba),
|
||||
|
@ -934,12 +943,12 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
using var sha1Inc = IncrementalHash.CreateHash(HashAlgorithmName.SHA1);
|
||||
using var zstd = new Zstd();
|
||||
var dsr = new DiscSectorReader(disc) { Policy = { DeinterleavedSubcode = true, DeterministicClearBuffer = true } };
|
||||
var sectorBuf = new byte[LibChdr.CD_FRAME_SIZE];
|
||||
var sectorBuf = new byte[LibChd.CD_FRAME_SIZE];
|
||||
var cdLba = 0;
|
||||
uint chdLba = 0, chdPos;
|
||||
var curHunk = new byte[LibChdr.CD_FRAME_SIZE * CD_FRAMES_PER_HUNK];
|
||||
var curHunk = new byte[LibChd.CD_FRAME_SIZE * CD_FRAMES_PER_HUNK];
|
||||
#if false // TODO: cdzs
|
||||
const uint COMPRESSION_LEN_BYTES = LibChdr.CD_FRAME_SIZE * CD_FRAMES_PER_HUNK < 65536 ? 2 : 3;
|
||||
const uint COMPRESSION_LEN_BYTES = LibChd.CD_FRAME_SIZE * CD_FRAMES_PER_HUNK < 65536 ? 2 : 3;
|
||||
const uint ECC_BYTES = (CD_FRAMES_PER_HUNK + 7) / 8;
|
||||
var hunkHeader = new byte[COMPRESSION_LEN_BYTES + ECC_BYTES];
|
||||
#endif
|
||||
|
@ -949,9 +958,6 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
var hunkOffset = bw.BaseStream.Position;
|
||||
|
||||
// TODO: adjust compression level?
|
||||
// note: it's fairly important a high compression level is chosen
|
||||
// libchdr will assume a compressed hunk with not be larger than an uncompressed hunk
|
||||
// but with low compression levels, this is not necessarily true
|
||||
using (var cstream = zstd.CreateZstdCompressionStream(bw.BaseStream, Zstd.MaxCompressionLevel))
|
||||
{
|
||||
cstream.Write(curHunk, 0, curHunk.Length);
|
||||
|
@ -976,7 +982,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
|
||||
// audio samples are byteswapped, so make sure to account for that
|
||||
var trackType = i < cdMetadata.Pregap ? cdMetadata.PregapTrackType : cdMetadata.TrackType;
|
||||
if (trackType == LibChdr.chd_track_type.CD_TRACK_AUDIO)
|
||||
if (trackType == LibChd.chd_track_type.CD_TRACK_AUDIO)
|
||||
{
|
||||
EndiannessUtils.MutatingByteSwap16(sectorBuf.AsSpan()[..2352]);
|
||||
}
|
||||
|
@ -986,11 +992,11 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
Buffer.BlockCopy(sectorBuf, 0, curHunk, (int)(2352U * chdPos), 2352);
|
||||
Buffer.BlockCopy(sectorBuf, 2352, curHunk, (int)(2352U * CD_FRAMES_PER_HUNK + 96U * chdPos), 96);
|
||||
#else
|
||||
Buffer.BlockCopy(sectorBuf, 0, curHunk, (int)(LibChdr.CD_FRAME_SIZE * chdPos), (int)LibChdr.CD_FRAME_SIZE);
|
||||
Buffer.BlockCopy(sectorBuf, 0, curHunk, (int)(LibChd.CD_FRAME_SIZE * chdPos), (int)LibChd.CD_FRAME_SIZE);
|
||||
#endif
|
||||
if (chdPos == CD_FRAMES_PER_HUNK - 1)
|
||||
{
|
||||
EndHunk(CD_FRAMES_PER_HUNK * LibChdr.CD_FRAME_SIZE);
|
||||
EndHunk(CD_FRAMES_PER_HUNK * LibChd.CD_FRAME_SIZE);
|
||||
}
|
||||
|
||||
cdLba++;
|
||||
|
@ -1002,7 +1008,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
chdPos = chdLba % CD_FRAMES_PER_HUNK;
|
||||
if (chdPos == CD_FRAMES_PER_HUNK - 1)
|
||||
{
|
||||
EndHunk(CD_FRAMES_PER_HUNK * LibChdr.CD_FRAME_SIZE);
|
||||
EndHunk(CD_FRAMES_PER_HUNK * LibChd.CD_FRAME_SIZE);
|
||||
}
|
||||
|
||||
chdLba++;
|
||||
|
@ -1013,18 +1019,18 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
chdPos = chdLba % CD_FRAMES_PER_HUNK;
|
||||
if (chdPos != 0)
|
||||
{
|
||||
EndHunk(chdPos * LibChdr.CD_FRAME_SIZE);
|
||||
EndHunk(chdPos * LibChd.CD_FRAME_SIZE);
|
||||
}
|
||||
|
||||
static string TrackTypeStr(LibChdr.chd_track_type trackType, bool isCdi)
|
||||
static string TrackTypeStr(LibChd.chd_track_type trackType, bool isCdi)
|
||||
{
|
||||
// ReSharper disable once SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault
|
||||
return trackType switch
|
||||
{
|
||||
LibChdr.chd_track_type.CD_TRACK_AUDIO => "AUDIO",
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE1_RAW => "MODE1_RAW",
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE2_RAW when isCdi => "CDI/2352",
|
||||
LibChdr.chd_track_type.CD_TRACK_MODE2_RAW => "MODE2_RAW",
|
||||
LibChd.chd_track_type.CD_TRACK_AUDIO => "AUDIO",
|
||||
LibChd.chd_track_type.CD_TRACK_MODE1_RAW => "MODE1_RAW",
|
||||
LibChd.chd_track_type.CD_TRACK_MODE2_RAW when isCdi => "CDI/2352",
|
||||
LibChd.chd_track_type.CD_TRACK_MODE2_RAW => "MODE2_RAW",
|
||||
_ => throw new InvalidOperationException(),
|
||||
};
|
||||
}
|
||||
|
@ -1045,8 +1051,8 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
var metadataStr = $"TRACK:{cdMetadata.Track} TYPE:{trackType} SUBTYPE:RW_RAW FRAMES:{cdMetadata.Frames} PREGAP:{cdMetadata.Pregap} PGTYPE:{pgTrackType} PGSUB:RW_RAW POSTGAP:0\0";
|
||||
var metadataBytes = Encoding.ASCII.GetBytes(metadataStr);
|
||||
|
||||
bw.Write(BinaryPrimitives.ReverseEndianness(LibChdr.CDROM_TRACK_METADATA2_TAG));
|
||||
bw.Write(LibChdr.CHD_MDFLAGS_CHECKSUM);
|
||||
bw.Write(BinaryPrimitives.ReverseEndianness(LibChd.CDROM_TRACK_METADATA2_TAG));
|
||||
bw.Write(LibChd.CHD_MDFLAGS_CHECKSUM);
|
||||
var chunkDataSize = new byte[3]; // 24 bit integer
|
||||
chunkDataSize[0] = (byte)((metadataBytes.Length >> 16) & 0xFF);
|
||||
chunkDataSize[1] = (byte)((metadataBytes.Length >> 8) & 0xFF);
|
||||
|
@ -1162,7 +1168,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
bw.Write(BinaryPrimitives.ReverseEndianness((uint)(hunkMapEnd - hunkMapOffset - 16)));
|
||||
|
||||
bw.BaseStream.Seek(0x20, SeekOrigin.Begin);
|
||||
bw.Write(BinaryPrimitives.ReverseEndianness(chdLba * (long)LibChdr.CD_FRAME_SIZE));
|
||||
bw.Write(BinaryPrimitives.ReverseEndianness(chdLba * (long)LibChd.CD_FRAME_SIZE));
|
||||
bw.Write(BinaryPrimitives.ReverseEndianness(hunkMapOffset));
|
||||
bw.Write(BinaryPrimitives.ReverseEndianness(metadataOffset));
|
||||
|
||||
|
@ -1191,10 +1197,10 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
// tag is hashed alongside the hash
|
||||
// we use the same tag every time, so we can just reuse this array
|
||||
var metadataTag = new byte[4];
|
||||
metadataTag[0] = (byte)((LibChdr.CDROM_TRACK_METADATA2_TAG >> 24) & 0xFF);
|
||||
metadataTag[1] = (byte)((LibChdr.CDROM_TRACK_METADATA2_TAG >> 16) & 0xFF);
|
||||
metadataTag[2] = (byte)((LibChdr.CDROM_TRACK_METADATA2_TAG >> 8) & 0xFF);
|
||||
metadataTag[3] = (byte)(LibChdr.CDROM_TRACK_METADATA2_TAG & 0xFF);
|
||||
metadataTag[0] = (byte)((LibChd.CDROM_TRACK_METADATA2_TAG >> 24) & 0xFF);
|
||||
metadataTag[1] = (byte)((LibChd.CDROM_TRACK_METADATA2_TAG >> 16) & 0xFF);
|
||||
metadataTag[2] = (byte)((LibChd.CDROM_TRACK_METADATA2_TAG >> 8) & 0xFF);
|
||||
metadataTag[3] = (byte)(LibChd.CDROM_TRACK_METADATA2_TAG & 0xFF);
|
||||
foreach (var metadataHash in metadataHashes)
|
||||
{
|
||||
sha1Inc.AppendData(metadataTag);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
#pragma warning disable IDE1006
|
||||
|
@ -10,10 +9,11 @@ using System.Runtime.InteropServices;
|
|||
namespace BizHawk.Emulation.DiscSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// libchdr bindings
|
||||
/// Bindings matching libchdr's chd.h
|
||||
/// In practice, we use chd-rs, whose c api matches libchdr's
|
||||
/// TODO: should this be common-ized? chd isn't limited to discs, it could be used for hard disk images (e.g. for MAME)
|
||||
/// </summary>
|
||||
public static class LibChdr
|
||||
public static class LibChd
|
||||
{
|
||||
public const uint CHD_HEADER_VERSION = 5;
|
||||
public const uint CHD_V5_HEADER_SIZE = 124;
|
||||
|
@ -69,148 +69,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
CHDERR_UNSUPPORTED_FORMAT
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct chd_core_file
|
||||
{
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate ulong FSizeDelegate(IntPtr file);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate nuint FReadDelegate(IntPtr buffer, nuint size, nuint count, IntPtr file);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate int FCloseDelegate(IntPtr file);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate int FSeekDelegate(IntPtr file, long offset, SeekOrigin origin);
|
||||
|
||||
public IntPtr argp;
|
||||
[MarshalAs(UnmanagedType.FunctionPtr)]
|
||||
public FSizeDelegate fsize;
|
||||
[MarshalAs(UnmanagedType.FunctionPtr)]
|
||||
public FReadDelegate fread;
|
||||
[MarshalAs(UnmanagedType.FunctionPtr)]
|
||||
public FCloseDelegate fclose;
|
||||
[MarshalAs(UnmanagedType.FunctionPtr)]
|
||||
public FSeekDelegate fseek;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience chd_core_file wrapper against a generic Stream
|
||||
/// </summary>
|
||||
public class CoreFileStreamWrapper : IDisposable
|
||||
{
|
||||
private const uint READ_BUFFER_LEN = 8 * CD_FRAME_SIZE; // 8 frames, usual uncompressed hunk size
|
||||
private readonly byte[] _readBuffer = new byte[READ_BUFFER_LEN];
|
||||
|
||||
private Stream _s;
|
||||
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
private readonly chd_core_file _coreFile;
|
||||
public readonly IntPtr CoreFile;
|
||||
|
||||
private ulong FSize(IntPtr file)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (ulong)_s.Length;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.Error.WriteLine(e);
|
||||
return unchecked((ulong)-1);
|
||||
}
|
||||
}
|
||||
|
||||
private nuint FRead(IntPtr buffer, nuint size, nuint count, IntPtr file)
|
||||
{
|
||||
nuint ret = 0;
|
||||
try
|
||||
{
|
||||
// note: size will always be 1, so this should never overflow
|
||||
var numBytesToRead = (uint)Math.Min(size * (ulong)count, uint.MaxValue);
|
||||
while (numBytesToRead > 0)
|
||||
{
|
||||
var numRead = _s.Read(_readBuffer, 0, (int)Math.Min(READ_BUFFER_LEN, numBytesToRead));
|
||||
if (numRead == 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
Marshal.Copy(_readBuffer, 0, buffer, numRead);
|
||||
buffer += numRead;
|
||||
ret += (uint)numRead;
|
||||
numBytesToRead -= (uint)numRead;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.Error.WriteLine(e);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
private int FClose(IntPtr file)
|
||||
{
|
||||
if (_s == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
_s.Dispose();
|
||||
_s = null;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int FSeek(IntPtr file, long offset, SeekOrigin origin)
|
||||
{
|
||||
try
|
||||
{
|
||||
_s.Seek(offset, origin);
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine(ex);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public CoreFileStreamWrapper(Stream s)
|
||||
{
|
||||
if (!s.CanRead || !s.CanSeek)
|
||||
{
|
||||
throw new NotSupportedException("The underlying CHD stream must support reading and seeking!");
|
||||
}
|
||||
|
||||
_s = s;
|
||||
_coreFile.fsize = FSize;
|
||||
_coreFile.fread = FRead;
|
||||
_coreFile.fclose = FClose;
|
||||
_coreFile.fseek = FSeek;
|
||||
// the pointer here must stay alloc'd on the unmanaged size
|
||||
// as libchdr expects the memory to not move around
|
||||
CoreFile = Marshal.AllocCoTaskMem(Marshal.SizeOf<chd_core_file>());
|
||||
Marshal.StructureToPtr(_coreFile, CoreFile, fDeleteOld: false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Marshal.DestroyStructure<chd_core_file>(CoreFile);
|
||||
Marshal.FreeCoTaskMem(CoreFile);
|
||||
_s?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("chdr")]
|
||||
public static extern chd_error chd_open_core_file(IntPtr file, int mode, IntPtr parent, out IntPtr chd);
|
||||
|
||||
[DllImport("chdr")]
|
||||
public static extern void chd_close(IntPtr chd);
|
||||
|
||||
// extracted chd header (not the same as the one on disk, but rather an interpreted one by libchdr)
|
||||
// extracted chd header (not the same as the one on disk, but rather an interpreted one by chd-rs)
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct chd_header
|
||||
{
|
||||
|
@ -239,12 +98,18 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
public uint obsolete_hunksize; // obsolete field -- do not use!
|
||||
}
|
||||
|
||||
[DllImport("chdr")]
|
||||
public static extern IntPtr chd_get_header(IntPtr chd);
|
||||
[DllImport("chd_capi")]
|
||||
public static extern chd_error chd_open(IntPtr filename, int mode, IntPtr parent, out IntPtr chd);
|
||||
|
||||
[DllImport("chdr")]
|
||||
[DllImport("chd_capi")]
|
||||
public static extern void chd_close(IntPtr chd);
|
||||
|
||||
[DllImport("chd_capi")]
|
||||
public static extern chd_error chd_read(IntPtr chd, uint hunknum, byte[] buffer);
|
||||
|
||||
[DllImport("chd_capi")]
|
||||
public static extern chd_error chd_read_header(IntPtr filename, ref chd_header header);
|
||||
|
||||
public enum chd_track_type : uint
|
||||
{
|
||||
CD_TRACK_MODE1 = 0, // mode 1 2048 bytes/sector
|
||||
|
@ -267,7 +132,7 @@ namespace BizHawk.Emulation.DiscSystem
|
|||
// hunks should be a multiple of this for cd chds
|
||||
public const uint CD_FRAME_SIZE = 2352 + 96;
|
||||
|
||||
[DllImport("chdr")]
|
||||
[DllImport("chd_capi")]
|
||||
public static extern chd_error chd_get_metadata(
|
||||
IntPtr chd, uint searchtag, uint searchindex, byte[] output, uint outputlen, out uint resultlen, out uint resulttag, out byte resultflags);
|
||||
}
|
Loading…
Reference in New Issue