pub const RESOURCE_MAGIC: u32 = 0xBEEF_CACE;
use crate::{file::parser::Parser, utils::compressed_uint_size, Error::TypeError, Result};
#[derive(Debug, Clone, PartialEq)]
pub enum ResourceType {
Null,
String(String),
Boolean(bool),
Char(char),
Byte(u8),
SByte(i8),
Int16(i16),
UInt16(u16),
Int32(i32),
UInt32(u32),
Int64(i64),
UInt64(u64),
Single(f32),
Double(f64),
Decimal {
lo: i32,
mid: i32,
hi: i32,
flags: i32,
},
DateTime(i64),
TimeSpan(i64),
ByteArray(Vec<u8>),
Stream(Vec<u8>),
StartOfUserTypes,
}
impl ResourceType {
#[must_use]
pub fn as_str(&self) -> Option<&'static str> {
match self {
ResourceType::String(_) => Some("System.String"),
ResourceType::Boolean(_) => Some("System.Boolean"),
ResourceType::Char(_) => Some("System.Char"),
ResourceType::Byte(_) => Some("System.Byte"),
ResourceType::SByte(_) => Some("System.SByte"),
ResourceType::Int16(_) => Some("System.Int16"),
ResourceType::UInt16(_) => Some("System.UInt16"),
ResourceType::Int32(_) => Some("System.Int32"),
ResourceType::UInt32(_) => Some("System.UInt32"),
ResourceType::Int64(_) => Some("System.Int64"),
ResourceType::UInt64(_) => Some("System.UInt64"),
ResourceType::Single(_) => Some("System.Single"),
ResourceType::Double(_) => Some("System.Double"),
ResourceType::Decimal { .. } => Some("System.Decimal"),
ResourceType::DateTime(_) => Some("System.DateTime"),
ResourceType::TimeSpan(_) => Some("System.TimeSpan"),
ResourceType::ByteArray(_) => Some("System.Byte[]"),
ResourceType::Stream(_) => Some("System.IO.Stream"),
ResourceType::Null | ResourceType::StartOfUserTypes => None,
}
}
#[must_use]
pub fn index(&self) -> Option<u32> {
match self {
ResourceType::Boolean(_) => Some(0),
ResourceType::Byte(_) => Some(1),
ResourceType::SByte(_) => Some(2),
ResourceType::Char(_) => Some(3),
ResourceType::Int16(_) => Some(4),
ResourceType::UInt16(_) => Some(5),
ResourceType::Int32(_) => Some(6),
ResourceType::UInt32(_) => Some(7),
ResourceType::Int64(_) => Some(8),
ResourceType::UInt64(_) => Some(9),
ResourceType::Single(_) => Some(10),
ResourceType::Double(_) => Some(11),
ResourceType::String(_) => Some(12),
ResourceType::Decimal { .. } => Some(13),
ResourceType::DateTime(_) => Some(14),
ResourceType::TimeSpan(_) => Some(15),
ResourceType::ByteArray(_) => Some(16),
ResourceType::Stream(_) => Some(17),
ResourceType::Null | ResourceType::StartOfUserTypes => None,
}
}
#[must_use]
pub fn type_code(&self) -> Option<u32> {
match self {
ResourceType::String(_) => Some(0x01),
ResourceType::Boolean(_) => Some(0x02),
ResourceType::Char(_) => Some(0x03),
ResourceType::Byte(_) => Some(0x04),
ResourceType::SByte(_) => Some(0x05),
ResourceType::Int16(_) => Some(0x06),
ResourceType::UInt16(_) => Some(0x07),
ResourceType::Int32(_) => Some(0x08),
ResourceType::UInt32(_) => Some(0x09),
ResourceType::Int64(_) => Some(0x0A),
ResourceType::UInt64(_) => Some(0x0B),
ResourceType::Single(_) => Some(0x0C),
ResourceType::Double(_) => Some(0x0D),
ResourceType::Decimal { .. } => Some(0x0E),
ResourceType::DateTime(_) => Some(0x0F),
ResourceType::TimeSpan(_) => Some(0x10),
ResourceType::ByteArray(_) => Some(0x20),
ResourceType::Stream(_) => Some(0x21),
ResourceType::Null | ResourceType::StartOfUserTypes => None,
}
}
#[must_use]
pub fn data_size(&self) -> Option<u32> {
match self {
ResourceType::String(s) => {
let utf8_byte_count = s.len();
let utf8_size = u32::try_from(utf8_byte_count).ok()?;
let prefix_size = u32::try_from(compressed_uint_size(utf8_size as usize)).ok()?;
Some(prefix_size + utf8_size)
}
ResourceType::Boolean(_) | ResourceType::Byte(_) | ResourceType::SByte(_) => Some(1), ResourceType::Char(_) | ResourceType::Int16(_) | ResourceType::UInt16(_) => Some(2), ResourceType::Int32(_) | ResourceType::UInt32(_) | ResourceType::Single(_) => Some(4), ResourceType::Int64(_)
| ResourceType::UInt64(_)
| ResourceType::Double(_)
| ResourceType::DateTime(_) | ResourceType::TimeSpan(_) => Some(8), ResourceType::Decimal { .. } => Some(16), ResourceType::ByteArray(data) | ResourceType::Stream(data) => {
let data_size = u32::try_from(data.len()).ok()?;
Some(4 + data_size)
}
ResourceType::Null | ResourceType::StartOfUserTypes => None,
}
}
pub fn from_type_byte(byte: u8, parser: &mut Parser) -> Result<Self> {
match byte {
0x0 => {
Ok(ResourceType::Null)
}
0x1 => {
Ok(ResourceType::String(parser.read_prefixed_string_utf8()?))
}
0x2 => Ok(ResourceType::Boolean(parser.read_le::<u8>()? > 0)),
0x3 => {
let code_unit = parser.read_le::<u16>()?;
Ok(ResourceType::Char(
char::from_u32(u32::from(code_unit)).ok_or_else(|| {
TypeError("Invalid UTF-16 code unit for Char".to_string())
})?,
))
}
0x4 => Ok(ResourceType::Byte(parser.read_le::<u8>()?)),
0x5 => Ok(ResourceType::SByte(parser.read_le::<i8>()?)),
0x6 => Ok(ResourceType::Int16(parser.read_le::<i16>()?)),
0x7 => Ok(ResourceType::UInt16(parser.read_le::<u16>()?)),
0x8 => Ok(ResourceType::Int32(parser.read_le::<i32>()?)),
0x9 => Ok(ResourceType::UInt32(parser.read_le::<u32>()?)),
0xA => Ok(ResourceType::Int64(parser.read_le::<i64>()?)),
0xB => Ok(ResourceType::UInt64(parser.read_le::<u64>()?)),
0xC => Ok(ResourceType::Single(parser.read_le::<f32>()?)),
0xD => Ok(ResourceType::Double(parser.read_le::<f64>()?)),
0xE => {
let lo = parser.read_le::<i32>()?;
let mid = parser.read_le::<i32>()?;
let hi = parser.read_le::<i32>()?;
let flags = parser.read_le::<i32>()?;
Ok(ResourceType::Decimal { lo, mid, hi, flags })
}
0xF => {
let binary_value = parser.read_le::<i64>()?;
Ok(ResourceType::DateTime(binary_value))
}
0x10 => {
let ticks = parser.read_le::<i64>()?;
Ok(ResourceType::TimeSpan(ticks))
}
0x20 => {
let length = parser.read_le::<u32>()?;
let start_pos = parser.pos();
let end_pos = start_pos + length as usize;
if end_pos > parser.data().len() {
return Err(out_of_bounds_error!());
}
let data = parser.data()[start_pos..end_pos].to_vec();
if end_pos < parser.data().len() {
parser.seek(end_pos)?;
}
Ok(ResourceType::ByteArray(data))
}
0x21 => {
let length = parser.read_le::<u32>()?;
let start_pos = parser.pos();
let end_pos = start_pos + length as usize;
if end_pos > parser.data().len() {
return Err(out_of_bounds_error!());
}
let data = parser.data()[start_pos..end_pos].to_vec();
if end_pos < parser.data().len() {
parser.seek(end_pos)?;
}
Ok(ResourceType::Stream(data))
}
0x40..=0xFF => {
Err(TypeError(format!(
"TypeByte - {byte:X} is a user type (>=0x40) but requires type table resolution which is not yet implemented"
)))
}
_ => Err(TypeError(format!(
"TypeByte - {byte:X} is currently not supported"
))),
}
}
pub fn from_type_name(type_name: &str, parser: &mut Parser) -> Result<Self> {
match type_name {
"System.Null" => ResourceType::from_type_byte(0x0, parser),
"System.String" => ResourceType::from_type_byte(0x1, parser),
"System.Boolean" => ResourceType::from_type_byte(0x2, parser),
"System.Char" => ResourceType::from_type_byte(0x3, parser),
"System.Byte" => ResourceType::from_type_byte(0x4, parser),
"System.SByte" => ResourceType::from_type_byte(0x5, parser),
"System.Int16" => ResourceType::from_type_byte(0x6, parser),
"System.UInt16" => ResourceType::from_type_byte(0x7, parser),
"System.Int32" => ResourceType::from_type_byte(0x8, parser),
"System.UInt32" => ResourceType::from_type_byte(0x9, parser),
"System.Int64" => ResourceType::from_type_byte(0xA, parser),
"System.UInt64" => ResourceType::from_type_byte(0xB, parser),
"System.Single" => ResourceType::from_type_byte(0xC, parser),
"System.Double" => ResourceType::from_type_byte(0xD, parser),
"System.Decimal" => ResourceType::from_type_byte(0xE, parser),
"System.DateTime" => ResourceType::from_type_byte(0xF, parser),
"System.TimeSpan" => ResourceType::from_type_byte(0x10, parser),
"System.Byte[]" => ResourceType::from_type_byte(0x20, parser),
"System.IO.Stream" => ResourceType::from_type_byte(0x21, parser),
_ => Err(TypeError(format!(
"TypeName - {type_name} is currently not supported"
))),
}
}
}
pub struct ResourceEntry {
pub name: String,
pub name_hash: u32,
pub data: ResourceType,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResourceTypeRef<'a> {
Null,
String(&'a str),
Boolean(bool),
Char(char),
Byte(u8),
SByte(i8),
Int16(i16),
UInt16(u16),
Int32(i32),
UInt32(u32),
Int64(i64),
UInt64(u64),
Single(f32),
Double(f64),
Decimal {
lo: i32,
mid: i32,
hi: i32,
flags: i32,
},
DateTime(i64),
TimeSpan(i64),
ByteArray(&'a [u8]),
Stream(&'a [u8]),
StartOfUserTypes,
}
pub struct ResourceEntryRef<'a> {
pub name: String,
pub name_hash: u32,
pub data: ResourceTypeRef<'a>,
}
impl<'a> ResourceTypeRef<'a> {
pub fn from_type_byte_ref(byte: u8, parser: &mut Parser<'a>, data: &'a [u8]) -> Result<Self> {
match byte {
0x0 => {
Ok(ResourceTypeRef::Null)
}
0x1 => {
Ok(ResourceTypeRef::String(
parser.read_prefixed_string_utf8_ref()?,
))
}
0x2 => Ok(ResourceTypeRef::Boolean(parser.read_le::<u8>()? > 0)),
0x3 => {
let code_unit = parser.read_le::<u16>()?;
Ok(ResourceTypeRef::Char(
char::from_u32(u32::from(code_unit)).ok_or_else(|| {
TypeError("Invalid UTF-16 code unit for Char".to_string())
})?,
))
}
0x4 => Ok(ResourceTypeRef::Byte(parser.read_le::<u8>()?)),
0x5 => Ok(ResourceTypeRef::SByte(parser.read_le::<i8>()?)),
0x6 => Ok(ResourceTypeRef::Int16(parser.read_le::<i16>()?)),
0x7 => Ok(ResourceTypeRef::UInt16(parser.read_le::<u16>()?)),
0x8 => Ok(ResourceTypeRef::Int32(parser.read_le::<i32>()?)),
0x9 => Ok(ResourceTypeRef::UInt32(parser.read_le::<u32>()?)),
0xA => Ok(ResourceTypeRef::Int64(parser.read_le::<i64>()?)),
0xB => Ok(ResourceTypeRef::UInt64(parser.read_le::<u64>()?)),
0xC => Ok(ResourceTypeRef::Single(parser.read_le::<f32>()?)),
0xD => Ok(ResourceTypeRef::Double(parser.read_le::<f64>()?)),
0xE => {
let lo = parser.read_le::<i32>()?;
let mid = parser.read_le::<i32>()?;
let hi = parser.read_le::<i32>()?;
let flags = parser.read_le::<i32>()?;
Ok(ResourceTypeRef::Decimal { lo, mid, hi, flags })
}
0xF => {
let binary_value = parser.read_le::<i64>()?;
Ok(ResourceTypeRef::DateTime(binary_value))
}
0x10 => {
let ticks = parser.read_le::<i64>()?;
Ok(ResourceTypeRef::TimeSpan(ticks))
}
0x20 => {
let length = parser.read_le::<u32>()?;
let start_pos = parser.pos();
let end_pos = start_pos + length as usize;
if end_pos > data.len() {
return Err(out_of_bounds_error!());
}
if end_pos < data.len() {
parser.seek(end_pos)?;
}
Ok(ResourceTypeRef::ByteArray(&data[start_pos..end_pos]))
}
0x21 => {
let length = parser.read_le::<u32>()?;
let start_pos = parser.pos();
let end_pos = start_pos + length as usize;
if end_pos > data.len() {
return Err(out_of_bounds_error!());
}
if end_pos < data.len() {
parser.seek(end_pos)?;
}
Ok(ResourceTypeRef::Stream(&data[start_pos..end_pos]))
}
0x40..=0xFF => {
Err(TypeError(format!(
"TypeByte - {byte:X} is a user type (>=0x40) but requires type table resolution which is not yet implemented"
)))
}
_ => Err(TypeError(format!(
"TypeByte - {byte:X} is currently not supported"
))),
}
}
pub fn from_type_name_ref(
type_name: &str,
parser: &mut Parser<'a>,
data: &'a [u8],
) -> Result<Self> {
match type_name {
"System.Null" => ResourceTypeRef::from_type_byte_ref(0x0, parser, data),
"System.String" => ResourceTypeRef::from_type_byte_ref(0x1, parser, data),
"System.Boolean" => ResourceTypeRef::from_type_byte_ref(0x2, parser, data),
"System.Char" => ResourceTypeRef::from_type_byte_ref(0x3, parser, data),
"System.Byte" => ResourceTypeRef::from_type_byte_ref(0x4, parser, data),
"System.SByte" => ResourceTypeRef::from_type_byte_ref(0x5, parser, data),
"System.Int16" => ResourceTypeRef::from_type_byte_ref(0x6, parser, data),
"System.UInt16" => ResourceTypeRef::from_type_byte_ref(0x7, parser, data),
"System.Int32" => ResourceTypeRef::from_type_byte_ref(0x8, parser, data),
"System.UInt32" => ResourceTypeRef::from_type_byte_ref(0x9, parser, data),
"System.Int64" => ResourceTypeRef::from_type_byte_ref(0xA, parser, data),
"System.UInt64" => ResourceTypeRef::from_type_byte_ref(0xB, parser, data),
"System.Single" => ResourceTypeRef::from_type_byte_ref(0xC, parser, data),
"System.Double" => ResourceTypeRef::from_type_byte_ref(0xD, parser, data),
"System.Decimal" => ResourceTypeRef::from_type_byte_ref(0xE, parser, data),
"System.DateTime" => ResourceTypeRef::from_type_byte_ref(0xF, parser, data),
"System.TimeSpan" => ResourceTypeRef::from_type_byte_ref(0x10, parser, data),
"System.Byte[]" => ResourceTypeRef::from_type_byte_ref(0x20, parser, data),
"System.IO.Stream" => ResourceTypeRef::from_type_byte_ref(0x21, parser, data),
_ => Err(TypeError(format!(
"TypeName - {type_name} is currently not supported"
))),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::file::parser::Parser;
#[test]
fn test_resource_magic_constant() {
assert_eq!(RESOURCE_MAGIC, 0xBEEFCACE);
}
#[test]
fn test_from_type_byte_string() {
let data = b"\x05hello";
let mut parser = Parser::new(data);
let result = ResourceType::from_type_byte(0x1, &mut parser).unwrap();
if let ResourceType::String(s) = result {
assert_eq!(s, "hello");
} else {
panic!("Expected String variant");
}
}
#[test]
fn test_from_type_byte_boolean_true() {
let data = b"\x01";
let mut parser = Parser::new(data);
let result = ResourceType::from_type_byte(0x2, &mut parser).unwrap();
if let ResourceType::Boolean(b) = result {
assert!(b);
} else {
panic!("Expected Boolean variant");
}
}
#[test]
fn test_from_type_byte_boolean_false() {
let data = b"\x00";
let mut parser = Parser::new(data);
let result = ResourceType::from_type_byte(0x2, &mut parser).unwrap();
if let ResourceType::Boolean(b) = result {
assert!(!b);
} else {
panic!("Expected Boolean variant");
}
}
#[test]
fn test_from_type_byte_char() {
let data = &[0x41, 0x00];
let mut parser = Parser::new(data);
let result = ResourceType::from_type_byte(0x3, &mut parser).unwrap();
if let ResourceType::Char(c) = result {
assert_eq!(c, 'A');
} else {
panic!("Expected Char variant");
}
let data_euro = &[0xAC, 0x20];
let mut parser_euro = Parser::new(data_euro);
let result_euro = ResourceType::from_type_byte(0x3, &mut parser_euro).unwrap();
if let ResourceType::Char(c) = result_euro {
assert_eq!(c, '€');
} else {
panic!("Expected Char variant for euro sign");
}
}
#[test]
fn test_from_type_byte_byte() {
let data = b"\xFF";
let mut parser = Parser::new(data);
let result = ResourceType::from_type_byte(0x4, &mut parser).unwrap();
if let ResourceType::Byte(b) = result {
assert_eq!(b, 255);
} else {
panic!("Expected Byte variant");
}
}
#[test]
fn test_from_type_byte_sbyte() {
let data = b"\xFF";
let mut parser = Parser::new(data);
let result = ResourceType::from_type_byte(0x5, &mut parser).unwrap();
if let ResourceType::SByte(sb) = result {
assert_eq!(sb, -1);
} else {
panic!("Expected SByte variant");
}
}
#[test]
fn test_from_type_byte_int16() {
let data = b"\xFF\xFF";
let mut parser = Parser::new(data);
let result = ResourceType::from_type_byte(0x6, &mut parser).unwrap();
if let ResourceType::Int16(i) = result {
assert_eq!(i, -1);
} else {
panic!("Expected Int16 variant");
}
}
#[test]
fn test_from_type_byte_uint16() {
let data = b"\xFF\xFF";
let mut parser = Parser::new(data);
let result = ResourceType::from_type_byte(0x7, &mut parser).unwrap();
if let ResourceType::UInt16(u) = result {
assert_eq!(u, 65535);
} else {
panic!("Expected UInt16 variant");
}
}
#[test]
fn test_from_type_byte_int32() {
let data = b"\x2A\x00\x00\x00";
let mut parser = Parser::new(data);
let result = ResourceType::from_type_byte(0x8, &mut parser).unwrap();
if let ResourceType::Int32(i) = result {
assert_eq!(i, 42);
} else {
panic!("Expected Int32 variant");
}
}
#[test]
fn test_from_type_byte_uint32() {
let data = b"\xFF\xFF\xFF\xFF";
let mut parser = Parser::new(data);
let result = ResourceType::from_type_byte(0x9, &mut parser).unwrap();
if let ResourceType::UInt32(u) = result {
assert_eq!(u, 4294967295);
} else {
panic!("Expected UInt32 variant");
}
}
#[test]
fn test_from_type_byte_int64() {
let data = b"\x2A\x00\x00\x00\x00\x00\x00\x00";
let mut parser = Parser::new(data);
let result = ResourceType::from_type_byte(0xA, &mut parser).unwrap();
if let ResourceType::Int64(i) = result {
assert_eq!(i, 42);
} else {
panic!("Expected Int64 variant");
}
}
#[test]
fn test_from_type_byte_uint64() {
let data = b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";
let mut parser = Parser::new(data);
let result = ResourceType::from_type_byte(0xB, &mut parser).unwrap();
if let ResourceType::UInt64(u) = result {
assert_eq!(u, 18446744073709551615);
} else {
panic!("Expected UInt64 variant");
}
}
#[test]
fn test_from_type_byte_single() {
let data = b"\x00\x00\x28\x42"; let mut parser = Parser::new(data);
let result = ResourceType::from_type_byte(0xC, &mut parser).unwrap();
if let ResourceType::Single(f) = result {
assert_eq!(f, 42.0);
} else {
panic!("Expected Single variant");
}
}
#[test]
fn test_from_type_byte_double() {
let data = b"\x00\x00\x00\x00\x00\x00\x45\x40"; let mut parser = Parser::new(data);
let result = ResourceType::from_type_byte(0xD, &mut parser).unwrap();
if let ResourceType::Double(d) = result {
assert_eq!(d, 42.0);
} else {
panic!("Expected Double variant");
}
}
#[test]
fn test_from_type_byte_unsupported() {
let data = b"";
let mut parser = Parser::new(data);
let result = ResourceType::from_type_byte(0xFF, &mut parser);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("FF is a user type (>=0x40) but requires type table resolution which is not yet implemented"));
}
#[test]
fn test_from_type_name_null() {
let data = b"";
let mut parser = Parser::new(data);
let result = ResourceType::from_type_name("System.Null", &mut parser);
assert!(result.is_ok());
assert_eq!(result.unwrap(), ResourceType::Null);
}
#[test]
fn test_from_type_name_string() {
let data = b"\x05hello";
let mut parser = Parser::new(data);
let result = ResourceType::from_type_name("System.String", &mut parser).unwrap();
if let ResourceType::String(s) = result {
assert_eq!(s, "hello");
} else {
panic!("Expected String variant");
}
}
#[test]
fn test_from_type_name_boolean() {
let data = b"\x01";
let mut parser = Parser::new(data);
let result = ResourceType::from_type_name("System.Boolean", &mut parser).unwrap();
if let ResourceType::Boolean(b) = result {
assert!(b);
} else {
panic!("Expected Boolean variant");
}
}
#[test]
fn test_from_type_name_all_supported_types() {
let mut parser = Parser::new(b"\x05hello");
assert!(ResourceType::from_type_name("System.String", &mut parser).is_ok());
let mut parser = Parser::new(b"\x01");
assert!(ResourceType::from_type_name("System.Boolean", &mut parser).is_ok());
let mut parser = Parser::new(&[0x41, 0x00]); assert!(ResourceType::from_type_name("System.Char", &mut parser).is_ok());
let mut parser = Parser::new(b"\xFF");
assert!(ResourceType::from_type_name("System.Byte", &mut parser).is_ok());
let mut parser = Parser::new(b"\xFF");
assert!(ResourceType::from_type_name("System.SByte", &mut parser).is_ok());
let mut parser = Parser::new(b"\xFF\xFF");
assert!(ResourceType::from_type_name("System.Int16", &mut parser).is_ok());
let mut parser = Parser::new(b"\xFF\xFF");
assert!(ResourceType::from_type_name("System.UInt16", &mut parser).is_ok());
let mut parser = Parser::new(b"\x2A\x00\x00\x00");
assert!(ResourceType::from_type_name("System.Int32", &mut parser).is_ok());
let mut parser = Parser::new(b"\xFF\xFF\xFF\xFF");
assert!(ResourceType::from_type_name("System.UInt32", &mut parser).is_ok());
let mut parser = Parser::new(b"\x2A\x00\x00\x00\x00\x00\x00\x00");
assert!(ResourceType::from_type_name("System.Int64", &mut parser).is_ok());
let mut parser = Parser::new(b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF");
assert!(ResourceType::from_type_name("System.UInt64", &mut parser).is_ok());
let mut parser = Parser::new(b"\x00\x00\x28\x42");
assert!(ResourceType::from_type_name("System.Single", &mut parser).is_ok());
let mut parser = Parser::new(b"\x00\x00\x00\x00\x00\x00\x45\x40");
assert!(ResourceType::from_type_name("System.Double", &mut parser).is_ok());
}
#[test]
fn test_from_type_name_unsupported() {
let data = b"";
let mut parser = Parser::new(data);
let result = ResourceType::from_type_name("System.NotSupported", &mut parser);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("System.NotSupported is currently not supported"));
}
#[test]
fn test_resource_entry_creation() {
let entry = ResourceEntry {
name: "TestResource".to_string(),
name_hash: 12345,
data: ResourceType::String("test_data".to_string()),
};
assert_eq!(entry.name, "TestResource");
assert_eq!(entry.name_hash, 12345);
if let ResourceType::String(s) = &entry.data {
assert_eq!(s, "test_data");
} else {
panic!("Expected String data");
}
}
#[test]
fn test_resource_type_debug() {
let resource = ResourceType::String("test".to_string());
let debug_str = format!("{resource:?}");
assert!(debug_str.contains("String"));
assert!(debug_str.contains("test"));
}
#[test]
fn test_resource_type_clone() {
let original = ResourceType::Int32(42);
let cloned = original.clone();
assert_eq!(original, cloned);
if let (ResourceType::Int32(orig), ResourceType::Int32(clone)) = (&original, &cloned) {
assert_eq!(orig, clone);
} else {
panic!("Clone should preserve type and value");
}
}
#[test]
fn test_resource_type_partial_eq() {
let res1 = ResourceType::String("test".to_string());
let res2 = ResourceType::String("test".to_string());
let res3 = ResourceType::String("different".to_string());
let res4 = ResourceType::Int32(42);
assert_eq!(res1, res2);
assert_ne!(res1, res3);
assert_ne!(res1, res4);
}
#[test]
fn test_resource_type_as_str() {
assert_eq!(
ResourceType::String("test".to_string()).as_str(),
Some("System.String")
);
assert_eq!(ResourceType::Boolean(true).as_str(), Some("System.Boolean"));
assert_eq!(ResourceType::Int32(42).as_str(), Some("System.Int32"));
assert_eq!(
ResourceType::ByteArray(vec![1, 2, 3]).as_str(),
Some("System.Byte[]")
);
assert_eq!(
ResourceType::Double(std::f64::consts::PI).as_str(),
Some("System.Double")
);
assert_eq!(ResourceType::Null.as_str(), None);
assert_eq!(ResourceType::StartOfUserTypes.as_str(), None);
assert_eq!(
ResourceType::Decimal {
lo: 0,
mid: 0,
hi: 0,
flags: 0
}
.as_str(),
Some("System.Decimal")
);
assert_eq!(ResourceType::DateTime(0).as_str(), Some("System.DateTime"));
assert_eq!(ResourceType::TimeSpan(0).as_str(), Some("System.TimeSpan"));
}
#[test]
fn test_resource_type_index() {
assert_eq!(ResourceType::Boolean(true).index(), Some(0));
assert_eq!(ResourceType::Byte(255).index(), Some(1));
assert_eq!(ResourceType::SByte(-1).index(), Some(2));
assert_eq!(ResourceType::Char('A').index(), Some(3));
assert_eq!(ResourceType::Int16(42).index(), Some(4));
assert_eq!(ResourceType::UInt16(65535).index(), Some(5));
assert_eq!(ResourceType::Int32(42).index(), Some(6));
assert_eq!(ResourceType::UInt32(42).index(), Some(7));
assert_eq!(ResourceType::Int64(42).index(), Some(8));
assert_eq!(ResourceType::UInt64(42).index(), Some(9));
assert_eq!(ResourceType::Single(std::f32::consts::PI).index(), Some(10));
assert_eq!(ResourceType::Double(std::f64::consts::PI).index(), Some(11));
assert_eq!(ResourceType::String("test".to_string()).index(), Some(12));
assert_eq!(
ResourceType::Decimal {
lo: 0,
mid: 0,
hi: 0,
flags: 0
}
.index(),
Some(13)
);
assert_eq!(ResourceType::DateTime(0).index(), Some(14));
assert_eq!(ResourceType::TimeSpan(0).index(), Some(15));
assert_eq!(ResourceType::ByteArray(vec![1, 2, 3]).index(), Some(16));
assert_eq!(ResourceType::Stream(vec![]).index(), Some(17));
assert_eq!(ResourceType::Null.index(), None);
assert_eq!(ResourceType::StartOfUserTypes.index(), None);
}
#[test]
fn test_resource_type_index_consistency() {
let test_types = [
ResourceType::Boolean(false),
ResourceType::Byte(0),
ResourceType::SByte(0),
ResourceType::Char('A'),
ResourceType::Int16(0),
ResourceType::UInt16(0),
ResourceType::Int32(0),
ResourceType::UInt32(0),
ResourceType::Int64(0),
ResourceType::UInt64(0),
ResourceType::Single(0.0),
ResourceType::Double(0.0),
ResourceType::String("".to_string()),
ResourceType::ByteArray(vec![]),
];
for resource_type in &test_types {
if resource_type.as_str().is_some() {
assert!(
resource_type.index().is_some(),
"Type {resource_type:?} has as_str() but no index()"
);
}
if resource_type.index().is_some() {
assert!(
resource_type.as_str().is_some(),
"Type {resource_type:?} has index() but no as_str()"
);
}
}
}
#[test]
fn test_resource_type_data_size() {
assert_eq!(ResourceType::Boolean(true).data_size(), Some(1));
assert_eq!(ResourceType::Byte(255).data_size(), Some(1));
assert_eq!(ResourceType::SByte(-1).data_size(), Some(1));
assert_eq!(ResourceType::Char('A').data_size(), Some(2)); assert_eq!(ResourceType::Int16(42).data_size(), Some(2));
assert_eq!(ResourceType::UInt16(42).data_size(), Some(2));
assert_eq!(ResourceType::Int32(42).data_size(), Some(4));
assert_eq!(ResourceType::UInt32(42).data_size(), Some(4));
assert_eq!(ResourceType::Int64(42).data_size(), Some(8));
assert_eq!(ResourceType::UInt64(42).data_size(), Some(8));
assert_eq!(
ResourceType::Single(std::f32::consts::PI).data_size(),
Some(4)
);
assert_eq!(
ResourceType::Double(std::f64::consts::PI).data_size(),
Some(8)
);
assert_eq!(
ResourceType::String("hello".to_string()).data_size(),
Some(6)
); assert_eq!(ResourceType::String("".to_string()).data_size(), Some(1)); assert_eq!(ResourceType::ByteArray(vec![1, 2, 3]).data_size(), Some(7)); assert_eq!(ResourceType::ByteArray(vec![]).data_size(), Some(4));
assert_eq!(
ResourceType::Decimal {
lo: 0,
mid: 0,
hi: 0,
flags: 0
}
.data_size(),
Some(16) );
assert_eq!(ResourceType::DateTime(0).data_size(), Some(8)); assert_eq!(ResourceType::TimeSpan(0).data_size(), Some(8)); assert_eq!(ResourceType::Stream(vec![1, 2, 3]).data_size(), Some(7));
assert_eq!(ResourceType::Null.data_size(), None);
assert_eq!(ResourceType::StartOfUserTypes.data_size(), None);
}
#[test]
fn test_resource_type_full_consistency() {
let test_types = [
ResourceType::Boolean(false),
ResourceType::Byte(0),
ResourceType::SByte(0),
ResourceType::Char('A'),
ResourceType::Int16(0),
ResourceType::UInt16(0),
ResourceType::Int32(0),
ResourceType::UInt32(0),
ResourceType::Int64(0),
ResourceType::UInt64(0),
ResourceType::Single(0.0),
ResourceType::Double(0.0),
ResourceType::String("test".to_string()),
ResourceType::ByteArray(vec![1, 2, 3]),
];
for resource_type in &test_types {
assert!(
resource_type.as_str().is_some(),
"Type {resource_type:?} should have as_str()"
);
assert!(
resource_type.index().is_some(),
"Type {resource_type:?} should have index()"
);
assert!(
resource_type.data_size().is_some(),
"Type {resource_type:?} should have data_size()"
);
}
}
}