mod deserializer;
mod header;
pub use self::deserializer::from_words;
pub mod helpers;
#[cfg(feature = "serialize")]
mod serializer;
#[cfg(feature = "serialize")]
pub use self::serializer::Serializer;
use core::{fmt, slice::SliceIndex};
use self::header::BinHeader;
#[macro_export]
macro_rules! include_bytes_as_u32 {
($path:literal) => {
const {
#[repr(align(4))]
pub struct AlignedAs<Bytes: ?Sized> {
pub bytes: Bytes,
}
const B: &[u8] = &AlignedAs {
bytes: *include_bytes!($path),
}
.bytes;
unsafe {
core::slice::from_raw_parts(B.as_ptr() as *const u32, B.len() / size_of::<u32>())
}
}
};
}
pub fn determine_endianness(resb: &[u8]) -> Result<Endianness, BinaryDeserializerError> {
let header = BinHeader::try_from(resb)?;
Ok(header.repr_info.endianness)
}
#[allow(dead_code)]
struct BinIndex {
field_count: u32,
keys_end: u32,
resources_end: u32,
bundle_end: u32,
largest_table_entry_count: u32,
bundle_attributes: Option<u32>,
data_16_bit_end: Option<u32>,
pool_checksum: Option<u32>,
}
macro_rules! primitive_enum {
($type:ty, $(#[$meta:meta])* $vis:vis enum $name:ident {
$($(#[$variant_meta:meta])* $variant:ident = $value:expr,)*
}) => {
$(#[$meta])*
#[repr($type)]
$vis enum $name {
$($(#[$variant_meta])* $variant = $value,)*
}
impl From<$name> for $type {
fn from(v: $name) -> Self {
v as $type
}
}
impl TryFrom<$type> for $name {
type Error = BinaryDeserializerError;
fn try_from(value: $type) -> Result<Self, Self::Error> {
match value {
$(x if x == $name::$variant as $type => Ok($name::$variant),)*
_ => Err(BinaryDeserializerError::invalid_data(
concat!("unrecognized value for ", stringify!($name))
)),
}
}
}
}
}
primitive_enum!(
u8,
#[derive(Clone, Copy, Debug, PartialEq)]
#[allow(clippy::exhaustive_enums)]
#[allow(missing_docs)]
pub enum Endianness {
Little = 0,
Big = 1,
}
);
primitive_enum!(
u8,
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum CharsetFamily {
Ascii = 0,
Ebcdic = 1,
}
);
primitive_enum!(
u16,
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ResourceReprType {
_String = 0,
Binary = 1,
Table = 2,
_Alias = 3,
_Table32 = 4,
Table16 = 5,
StringV2 = 6,
Int = 7,
Array = 8,
Array16 = 9,
IntVector = 14,
}
);
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
#[repr(u32)]
enum FormatVersion {
V1_0,
V1_1,
V1_2,
V1_3,
V2_0,
V3_0,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct ResDescriptor {
resource_type: ResourceReprType,
value: u32,
}
impl ResDescriptor {
pub const fn new(resource_type: ResourceReprType, value: u32) -> Self {
Self {
resource_type,
value,
}
}
pub const fn _new_empty(resource_type: ResourceReprType) -> Self {
Self::new(resource_type, 0)
}
pub fn is_empty(self) -> bool {
self.value == 0
}
fn value_as_16_bit_offset(self) -> usize {
(self.value as usize) * size_of::<u16>()
}
fn value_as_32_bit_offset(self) -> usize {
(self.value as usize) * size_of::<u32>()
}
fn value_as_signed_int(self) -> i32 {
((self.value as i32) << 4) >> 4
}
fn value_as_unsigned_int(self) -> u32 {
self.value
}
pub fn resource_type(self) -> ResourceReprType {
self.resource_type
}
}
#[derive(Clone, Copy, Debug)]
pub struct BinaryDeserializerError {
kind: ErrorKind,
message: &'static str,
}
impl BinaryDeserializerError {
pub const fn invalid_data(message: &'static str) -> Self {
Self {
kind: ErrorKind::InvalidData,
message,
}
}
pub const fn resource_type_mismatch(message: &'static str) -> Self {
Self {
kind: ErrorKind::ResourceTypeMismatch,
message,
}
}
pub const fn unsupported_format(message: &'static str) -> Self {
Self {
kind: ErrorKind::UnsupportedFormat,
message,
}
}
pub const fn unknown(message: &'static str) -> Self {
Self {
kind: ErrorKind::Unknown,
message,
}
}
}
impl fmt::Display for BinaryDeserializerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let description = match self.kind {
ErrorKind::InvalidData => "Invalid resource bundle data",
ErrorKind::ResourceTypeMismatch => "Resource did not match expected data type",
ErrorKind::UnsupportedFormat => "Unsupported resource bundle format",
ErrorKind::Unknown => "Unknown error",
};
write!(f, "{description}: {}", self.message)
}
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
enum ErrorKind {
ResourceTypeMismatch,
InvalidData,
UnsupportedFormat,
Unknown,
}
fn get_subslice<I>(input: &[u8], index: I) -> Result<&[u8], BinaryDeserializerError>
where
I: SliceIndex<[u8], Output = [u8]>,
{
input
.get(index)
.ok_or(BinaryDeserializerError::invalid_data(
"unexpected end of input",
))
}
fn read_u16(input: &[u8]) -> Result<(u16, &[u8]), BinaryDeserializerError> {
#[expect(clippy::unwrap_used)]
let bytes = get_subslice(input, ..size_of::<u16>())?.try_into().unwrap();
let value = u16::from_ne_bytes(bytes);
let rest = get_subslice(input, size_of::<u16>()..)?;
Ok((value, rest))
}