use std::io::{Read, Seek};
use super::binary_reader::WzBinaryReader;
use super::error::{WzError, WzResult};
use super::keys::WzKey;
use super::properties::WzProperty;
use super::types::WzPngFormat;
use crate::crypto::{WZ_BMSCLASSIC_IV, WZ_GMSIV, WZ_MSEAIV};
const KNOWN_IVS: [[u8; 4]; 3] = [WZ_BMSCLASSIC_IV, WZ_GMSIV, WZ_MSEAIV];
fn try_iv_fallback<R: Read + Seek>(
reader: &mut WzBinaryReader<R>,
offset: u64,
read_prop_string: impl Fn(&mut WzBinaryReader<R>) -> Result<String, WzError>,
) -> WzResult<Vec<(String, WzProperty)>> {
for &iv in &KNOWN_IVS {
reader.wz_key = WzKey::new(iv);
if let Ok(s) = read_prop_string(reader) {
if s == "Property" {
return parse_property_list(reader, offset);
}
}
}
Err(WzError::InvalidImageHeader(0))
}
pub fn parse_image<R: Read + Seek>(
reader: &mut WzBinaryReader<R>,
) -> WzResult<Vec<(String, WzProperty)>> {
let offset = reader.position()?; let header_byte = reader.read_u8()?;
match header_byte {
0x73 => {
let pos_after_header = reader.position()?;
let prop_str = reader.read_wz_string()?;
let val = reader.read_u16()?;
if prop_str == "Property" && val == 0 {
return parse_property_list(reader, offset);
}
try_iv_fallback(reader, offset, |r| {
r.seek(pos_after_header)?;
let s = r.read_wz_string()?;
let v = r.read_u16()?;
if v == 0 { Ok(s) } else { Err(WzError::InvalidImageHeader(0x73)) }
})
}
0x1B => {
let str_offset = reader.read_i32()?;
let string_pos = offset.wrapping_add(str_offset as i64 as u64);
let prop_str = reader.read_string_at_offset(string_pos)?;
let val = reader.read_u16()?;
if prop_str == "Property" && val == 0 {
return parse_property_list(reader, offset);
}
if val != 0 {
return Err(WzError::InvalidImageHeader(header_byte));
}
try_iv_fallback(reader, offset, |r| r.read_string_at_offset(string_pos))
}
0x01 => {
let data = read_lua_data(reader)?;
Ok(vec![("Script".to_string(), WzProperty::Lua(data))])
}
other => Err(WzError::InvalidImageHeader(other)),
}
}
pub fn parse_property_list<R: Read + Seek>(
reader: &mut WzBinaryReader<R>,
offset: u64,
) -> WzResult<Vec<(String, WzProperty)>> {
let count = reader.read_compressed_int()?;
if !(0..=super::MAX_PROPERTY_COUNT).contains(&count) {
return Err(WzError::Custom(format!("Invalid property count: {}", count)));
}
let mut properties = Vec::with_capacity(count as usize);
for _ in 0..count {
let name = reader.read_string_block(offset)?;
if let Some(prop) = parse_property_value(reader, offset)? {
properties.push((name, prop));
}
}
Ok(properties)
}
fn parse_property_value<R: Read + Seek>(
reader: &mut WzBinaryReader<R>,
offset: u64,
) -> WzResult<Option<WzProperty>> {
let prop_type = reader.read_u8()?;
match prop_type {
0x00 => Ok(Some(WzProperty::Null)),
0x02 | 0x0B => {
let val = reader.read_i16()?;
Ok(Some(WzProperty::Short(val)))
}
0x03 | 0x13 => {
let val = reader.read_compressed_int()?;
Ok(Some(WzProperty::Int(val)))
}
0x14 => {
let val = reader.read_compressed_long()?;
Ok(Some(WzProperty::Long(val)))
}
0x04 => {
let indicator = reader.read_u8()?;
match indicator {
0x80 => Ok(Some(WzProperty::Float(reader.read_f32()?))),
0x00 => Ok(Some(WzProperty::Float(0.0))),
_ => Ok(None),
}
}
0x05 => {
let val = reader.read_f64()?;
Ok(Some(WzProperty::Double(val)))
}
0x08 => {
let val = reader.read_string_block(offset)?;
Ok(Some(WzProperty::String(val)))
}
0x09 => {
let block_size = reader.read_u32()?;
let end_of_block = reader.position()? + block_size as u64;
let result = parse_extended_property(reader, offset)?;
if reader.position()? != end_of_block {
reader.seek(end_of_block)?;
}
Ok(Some(result))
}
other => Err(WzError::UnknownPropertyType(format!("0x{:02X}", other))),
}
}
fn parse_extended_property<R: Read + Seek>(
reader: &mut WzBinaryReader<R>,
offset: u64,
) -> WzResult<WzProperty> {
let type_byte = reader.read_u8()?;
let type_str = match type_byte {
0x01 | 0x1B => {
let str_offset = reader.read_i32()?;
reader.read_string_at_offset(offset.wrapping_add(str_offset as i64 as u64))?
}
0x00 | 0x73 => {
reader.read_wz_string()?
}
_ => {
return Err(WzError::Custom(format!(
"Invalid extended prop type byte: 0x{:02X}",
type_byte
)));
}
};
use super::{WZ_TYPE_PROPERTY, WZ_TYPE_CANVAS, WZ_TYPE_VECTOR, WZ_TYPE_CONVEX, WZ_TYPE_SOUND, WZ_TYPE_UOL, WZ_TYPE_RAW_DATA, WZ_TYPE_VIDEO};
match type_str.as_str() {
WZ_TYPE_PROPERTY => {
let _padding = reader.read_u16()?;
let properties = parse_property_list(reader, offset)?;
Ok(WzProperty::SubProperty { properties })
}
WZ_TYPE_CANVAS => parse_canvas_property(reader, offset),
WZ_TYPE_VECTOR => {
let x = reader.read_compressed_int()?;
let y = reader.read_compressed_int()?;
Ok(WzProperty::Vector { x, y })
}
WZ_TYPE_CONVEX => {
let count = reader.read_compressed_int()?;
if !(0..=super::MAX_CONVEX_POINTS).contains(&count) {
return Err(WzError::Custom(format!("Invalid convex point count: {}", count)));
}
let mut points = Vec::with_capacity(count as usize);
for i in 0..count {
points.push((i.to_string(), parse_extended_property(reader, offset)?));
}
Ok(WzProperty::Convex { points })
}
WZ_TYPE_SOUND => parse_sound_property(reader),
WZ_TYPE_UOL => {
let _skip = reader.read_u8()?;
let uol_type = reader.read_u8()?;
let path = match uol_type {
0x00 => reader.read_wz_string()?,
0x01 => {
let str_offset = reader.read_i32()?;
reader.read_string_at_offset(offset.wrapping_add(str_offset as i64 as u64))?
}
other => {
return Err(WzError::Custom(format!(
"Unsupported UOL type: 0x{:02X}",
other
)));
}
};
Ok(WzProperty::Uol(path))
}
WZ_TYPE_RAW_DATA => {
let raw_type = reader.read_u8()?;
let properties = if raw_type == 1 {
let has_props = reader.read_u8()?;
if has_props == 1 {
let _padding = reader.read_u16()?;
parse_property_list(reader, offset)?
} else {
Vec::new()
}
} else {
Vec::new()
};
let len = reader.read_compressed_int()? as usize;
let data = reader.read_bytes(len)?;
Ok(WzProperty::RawData { raw_type, properties, data })
}
WZ_TYPE_VIDEO => {
let _skip = reader.read_u8()?;
let has_props = reader.read_u8()?;
let properties = read_optional_properties(reader, offset, has_props)?;
let video_type = reader.read_u8()?;
let data_len = reader.read_compressed_int()?;
let data_offset = reader.position()?;
let video_data = reader.read_bytes(data_len as usize)?;
let mcv_header = if video_data.len() >= 36 {
super::mcv::parse_mcv_header(&video_data[..36]).ok()
} else {
None
};
Ok(WzProperty::Video {
video_type,
properties,
data_offset,
data_length: data_len as u32,
mcv_header,
video_data: Some(video_data),
})
}
other => {
Ok(WzProperty::String(other.to_string()))
}
}
}
fn read_optional_properties<R: Read + Seek>(
reader: &mut WzBinaryReader<R>,
offset: u64,
flag: u8,
) -> WzResult<Vec<(String, WzProperty)>> {
if flag == 1 {
let _padding = reader.read_u16()?;
parse_property_list(reader, offset)
} else {
Ok(Vec::new())
}
}
fn parse_canvas_property<R: Read + Seek>(
reader: &mut WzBinaryReader<R>,
offset: u64,
) -> WzResult<WzProperty> {
let _skip = reader.read_u8()?;
let has_children = reader.read_u8()?;
let properties = read_optional_properties(reader, offset, has_children)?;
let width = reader.read_compressed_int()?;
let height = reader.read_compressed_int()?;
let format_low = reader.read_compressed_int()?;
let format_high = reader.read_compressed_int()?;
let _zero = reader.read_i32()?;
let raw_data_len = reader.read_i32()?;
let _header_byte = reader.read_u8()?;
if raw_data_len <= 1 {
return Err(WzError::Custom(format!(
"Invalid PNG data length: {}",
raw_data_len
)));
}
let png_data = reader.read_bytes((raw_data_len - 1) as usize)?;
let format = WzPngFormat::from_raw(format_low, format_high);
Ok(WzProperty::Canvas {
width,
height,
format,
properties,
png_data,
})
}
const SOUND_HEADER_LEN: usize = 51; const WAVE_FORMAT_SIZE: usize = 18;
fn try_decrypt_wave_format(wav_header: &mut [u8], wz_key: &[u8]) -> bool {
if wav_header.len() < WAVE_FORMAT_SIZE {
return false;
}
let extra_size = u16::from_le_bytes([wav_header[16], wav_header[17]]) as usize;
if WAVE_FORMAT_SIZE + extra_size == wav_header.len() {
return false;
}
for i in 0..wav_header.len() {
if i < wz_key.len() {
wav_header[i] ^= wz_key[i];
}
}
let extra_size = u16::from_le_bytes([wav_header[16], wav_header[17]]) as usize;
WAVE_FORMAT_SIZE + extra_size == wav_header.len()
}
fn parse_sound_property<R: Read + Seek>(
reader: &mut WzBinaryReader<R>,
) -> WzResult<WzProperty> {
let _padding = reader.read_u8()?;
let sound_data_len = reader.read_compressed_int()?;
let duration = reader.read_compressed_int()?;
let header_off = reader.position()?;
reader.seek(header_off + SOUND_HEADER_LEN as u64)?;
let wav_format_len = reader.read_u8()? as usize;
reader.seek(header_off)?;
let sound_header_bytes = reader.read_bytes(SOUND_HEADER_LEN)?;
let unk1 = reader.read_bytes(1)?;
let mut wav_format_bytes = reader.read_bytes(wav_format_len)?;
let key_slice = reader.wz_key.get_slice(0, wav_format_len.max(1));
try_decrypt_wave_format(&mut wav_format_bytes, key_slice);
let mut header = Vec::with_capacity(SOUND_HEADER_LEN + 1 + wav_format_len);
header.extend_from_slice(&sound_header_bytes);
header.extend_from_slice(&unk1);
header.extend_from_slice(&wav_format_bytes);
let audio_data = reader.read_bytes(sound_data_len as usize)?;
Ok(WzProperty::Sound {
duration_ms: duration,
data: audio_data,
header,
})
}
fn read_lua_data<R: Read + Seek>(
reader: &mut WzBinaryReader<R>,
) -> WzResult<Vec<u8>> {
let len = reader.read_compressed_int()? as usize;
reader.read_bytes(len)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::wz::test_utils::*;
fn encode_ascii_with_iv(s: &str, iv: [u8; 4]) -> Vec<u8> {
let len = s.len();
assert!(len > 0 && len < 128);
let mut key = WzKey::new(iv);
key.ensure_size(len);
let indicator = -(len as i8);
let mut out = vec![indicator as u8];
let mut mask: u8 = 0xAA;
for (i, b) in s.bytes().enumerate() {
out.push(b ^ mask ^ key[i]);
mask = mask.wrapping_add(1);
}
out
}
fn property_image_header_with_iv(iv: [u8; 4]) -> Vec<u8> {
let mut out = vec![0x73u8];
out.extend_from_slice(&encode_ascii_with_iv("Property", iv));
out.extend_from_slice(&0u16.to_le_bytes());
out
}
#[test]
fn test_parse_image_0x73_iv_fallback() {
let mut data = property_image_header_with_iv(WZ_GMSIV);
data.push(0);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
assert!(props.is_empty());
}
#[test]
fn test_parse_image_0x73_iv_fallback_ems() {
let mut data = property_image_header_with_iv(WZ_MSEAIV);
data.push(0);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
assert!(props.is_empty());
}
#[test]
fn test_parse_image_0x73_empty_property_list() {
let mut data = property_image_header();
data.push(0); let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
assert!(props.is_empty());
}
#[test]
fn test_parse_image_invalid_header() {
let data = vec![0xFF];
let mut reader = make_reader(data);
let err = parse_image(&mut reader).unwrap_err();
matches!(err, WzError::InvalidImageHeader(0xFF));
}
#[test]
fn test_parse_image_lua() {
let lua_bytes = b"print('hello')";
let mut data = vec![0x01u8];
data.push(lua_bytes.len() as u8); data.extend_from_slice(lua_bytes);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
assert_eq!(props.len(), 1);
assert_eq!(props[0].0, "Script");
if let WzProperty::Lua(ref d) = props[0].1 {
assert_eq!(d, lua_bytes);
} else {
panic!("Expected Lua property");
}
}
#[test]
fn test_parse_null_property() {
let data = build_image_with_property("n", &[0x00]);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
assert_eq!(props.len(), 1);
assert_eq!(props[0].0, "n");
assert!(matches!(props[0].1, WzProperty::Null));
}
#[test]
fn test_parse_short_property() {
let mut value = vec![0x02u8];
value.extend_from_slice(&42i16.to_le_bytes());
let data = build_image_with_property("s", &value);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
assert_eq!(props[0].1.as_int(), Some(42));
}
#[test]
fn test_parse_int_property_small() {
let value = vec![0x03u8, 99];
let data = build_image_with_property("i", &value);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
assert_eq!(props[0].1.as_int(), Some(99));
}
#[test]
fn test_parse_int_property_large() {
let mut value = vec![0x03u8, 0x80];
value.extend_from_slice(&100_000i32.to_le_bytes());
let data = build_image_with_property("i", &value);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
assert_eq!(props[0].1.as_int(), Some(100_000));
}
#[test]
fn test_parse_long_property() {
let mut value = vec![0x14u8, 0x80]; value.extend_from_slice(&9_999_999i64.to_le_bytes());
let data = build_image_with_property("l", &value);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
assert_eq!(props[0].1.as_int(), Some(9_999_999));
}
#[test]
fn test_parse_float_property_value() {
let mut value = vec![0x04u8, 0x80]; value.extend_from_slice(&1.5f32.to_le_bytes());
let data = build_image_with_property("f", &value);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
let v = props[0].1.as_float().unwrap();
assert!((v - 1.5).abs() < f64::EPSILON);
}
#[test]
fn test_parse_float_property_zero() {
let value = vec![0x04u8, 0x00]; let data = build_image_with_property("f", &value);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
assert_eq!(props[0].1.as_float(), Some(0.0));
}
#[test]
fn test_parse_float_property_unknown_indicator_skipped() {
let value = vec![0x04u8, 0x42];
let data = build_image_with_property("f", &value);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
assert!(props.is_empty());
}
#[test]
fn test_parse_double_property() {
let mut value = vec![0x05u8];
value.extend_from_slice(&3.14f64.to_le_bytes());
let data = build_image_with_property("d", &value);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
let v = props[0].1.as_float().unwrap();
assert!((v - 3.14).abs() < f64::EPSILON);
}
#[test]
fn test_parse_string_property() {
let mut value = vec![0x08u8];
value.extend_from_slice(&string_block("hello"));
let data = build_image_with_property("str", &value);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
assert_eq!(props[0].1.as_str(), Some("hello"));
}
#[test]
fn test_parse_vector_property() {
let mut inner = vec![0x73u8]; inner.extend_from_slice(&encode_wz_ascii("Shape2D#Vector2D"));
inner.push(10); inner.push(20);
let mut value = vec![0x09u8];
value.extend_from_slice(&(inner.len() as u32).to_le_bytes()); value.extend_from_slice(&inner);
let data = build_image_with_property("v", &value);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
if let WzProperty::Vector { x, y } = &props[0].1 {
assert_eq!(*x, 10);
assert_eq!(*y, 20);
} else {
panic!("Expected Vector, got {:?}", props[0].1);
}
}
#[test]
fn test_parse_multiple_properties() {
let mut data = property_image_header();
data.push(3);
data.extend_from_slice(&string_block("a"));
data.push(0x00);
data.extend_from_slice(&string_block("b"));
data.push(0x02);
data.extend_from_slice(&7i16.to_le_bytes());
data.extend_from_slice(&string_block("c"));
data.push(0x03);
data.push(42);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
assert_eq!(props.len(), 3);
assert_eq!(props[0].0, "a");
assert!(matches!(props[0].1, WzProperty::Null));
assert_eq!(props[1].0, "b");
assert_eq!(props[1].1.as_int(), Some(7));
assert_eq!(props[2].0, "c");
assert_eq!(props[2].1.as_int(), Some(42));
}
#[test]
fn test_parse_canvas_property_no_children() {
let png_payload = vec![0xAA, 0xBB, 0xCC]; let raw_data_len: i32 = png_payload.len() as i32 + 1;
let mut inner = Vec::new();
inner.push(0x00); inner.push(0x00); inner.push(4); inner.push(8); inner.push(2); inner.push(0); inner.extend_from_slice(&0i32.to_le_bytes()); inner.extend_from_slice(&raw_data_len.to_le_bytes()); inner.push(0x00); inner.extend_from_slice(&png_payload);
let value = build_extended_property("Canvas", &inner);
let data = build_image_with_property("img", &value);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
assert_eq!(props.len(), 1);
assert_eq!(props[0].0, "img");
if let WzProperty::Canvas { width, height, format, properties, png_data, .. } = &props[0].1 {
assert_eq!(*width, 4);
assert_eq!(*height, 8);
assert_eq!(*format, WzPngFormat::Bgra8888);
assert!(properties.is_empty());
assert_eq!(png_data, &png_payload);
} else {
panic!("Expected Canvas, got {:?}", props[0].1);
}
}
#[test]
fn test_parse_canvas_property_with_children() {
let png_payload = vec![0xDD, 0xEE];
let raw_data_len: i32 = png_payload.len() as i32 + 1;
let mut inner = Vec::new();
inner.push(0x00); inner.push(0x01); inner.extend_from_slice(&0u16.to_le_bytes()); inner.push(1); inner.extend_from_slice(&string_block("delay"));
inner.push(0x03); inner.push(100); inner.push(16); inner.push(16); inner.push(1); inner.push(0); inner.extend_from_slice(&0i32.to_le_bytes());
inner.extend_from_slice(&raw_data_len.to_le_bytes());
inner.push(0x00);
inner.extend_from_slice(&png_payload);
let value = build_extended_property("Canvas", &inner);
let data = build_image_with_property("icon", &value);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
if let WzProperty::Canvas { width, height, format, properties, .. } = &props[0].1 {
assert_eq!(*width, 16);
assert_eq!(*height, 16);
assert_eq!(*format, WzPngFormat::Bgra4444);
assert_eq!(properties.len(), 1);
assert_eq!(properties[0].0, "delay");
assert_eq!(properties[0].1.as_int(), Some(100));
} else {
panic!("Expected Canvas");
}
}
#[test]
fn test_parse_canvas_invalid_data_len() {
let mut inner = Vec::new();
inner.push(0x00); inner.push(0x00); inner.push(1); inner.push(1); inner.push(2); inner.push(0); inner.extend_from_slice(&0i32.to_le_bytes());
inner.extend_from_slice(&0i32.to_le_bytes()); inner.push(0x00);
let value = build_extended_property("Canvas", &inner);
let data = build_image_with_property("bad", &value);
let mut reader = make_reader(data);
let err = parse_image(&mut reader).unwrap_err();
assert!(matches!(err, WzError::Custom(_)));
}
#[test]
fn test_parse_sound_property() {
let audio_data = vec![0x01, 0x02, 0x03, 0x04]; let sound_header = vec![0xAA; SOUND_HEADER_LEN]; let wav_format_len: u8 = 4;
let wav_format = vec![0xBB; wav_format_len as usize];
let mut inner = Vec::new();
inner.push(0x00); inner.push(audio_data.len() as u8); inner.push(100); inner.extend_from_slice(&sound_header); inner.push(wav_format_len); inner.extend_from_slice(&wav_format); inner.extend_from_slice(&audio_data);
let value = build_extended_property("Sound_DX8", &inner);
let data = build_image_with_property("snd", &value);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
assert_eq!(props.len(), 1);
assert_eq!(props[0].0, "snd");
if let WzProperty::Sound { duration_ms, data, header, .. } = &props[0].1 {
assert_eq!(*duration_ms, 100);
assert_eq!(data, &audio_data);
assert_eq!(header.len(), SOUND_HEADER_LEN + 1 + wav_format_len as usize);
} else {
panic!("Expected Sound, got {:?}", props[0].1);
}
}
#[test]
fn test_parse_sound_property_zero_wav_format() {
let audio_data = vec![0xFF; 2];
let sound_header = vec![0x00; SOUND_HEADER_LEN];
let wav_format_len: u8 = 0;
let mut inner = Vec::new();
inner.push(0x00); inner.push(audio_data.len() as u8);
inner.push(50); inner.extend_from_slice(&sound_header);
inner.push(wav_format_len); inner.extend_from_slice(&audio_data);
let value = build_extended_property("Sound_DX8", &inner);
let data = build_image_with_property("s2", &value);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
if let WzProperty::Sound { duration_ms, data, header, .. } = &props[0].1 {
assert_eq!(*duration_ms, 50);
assert_eq!(data, &audio_data);
assert_eq!(header.len(), SOUND_HEADER_LEN + 1);
} else {
panic!("Expected Sound");
}
}
#[test]
fn test_parse_uol_property_inline() {
let mut inner = Vec::new();
inner.push(0x00); inner.push(0x00); inner.extend_from_slice(&encode_wz_ascii("../stand/0"));
let value = build_extended_property("UOL", &inner);
let data = build_image_with_property("link", &value);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
assert_eq!(props.len(), 1);
assert_eq!(props[0].0, "link");
if let WzProperty::Uol(path) = &props[0].1 {
assert_eq!(path, "../stand/0");
} else {
panic!("Expected Uol, got {:?}", props[0].1);
}
}
#[test]
fn test_parse_uol_unsupported_type() {
let mut inner = Vec::new();
inner.push(0x00); inner.push(0x99);
let value = build_extended_property("UOL", &inner);
let data = build_image_with_property("bad", &value);
let mut reader = make_reader(data);
let err = parse_image(&mut reader).unwrap_err();
assert!(matches!(err, WzError::Custom(_)));
}
#[test]
fn test_parse_convex_property() {
let mut inner = Vec::new();
inner.push(2); inner.push(0x73);
inner.extend_from_slice(&encode_wz_ascii("Shape2D#Vector2D"));
inner.push(1); inner.push(2); inner.push(0x73);
inner.extend_from_slice(&encode_wz_ascii("Shape2D#Vector2D"));
inner.push(3); inner.push(4);
let value = build_extended_property("Shape2D#Convex2D", &inner);
let data = build_image_with_property("cv", &value);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
if let WzProperty::Convex { points } = &props[0].1 {
assert_eq!(points.len(), 2);
assert!(matches!(points[0].1, WzProperty::Vector { x: 1, y: 2 }));
assert!(matches!(points[1].1, WzProperty::Vector { x: 3, y: 4 }));
} else {
panic!("Expected Convex");
}
}
#[test]
fn test_parse_sub_property_extended() {
let mut inner = Vec::new();
inner.extend_from_slice(&0u16.to_le_bytes()); inner.push(1); inner.extend_from_slice(&string_block("val"));
inner.push(0x00);
let value = build_extended_property("Property", &inner);
let data = build_image_with_property("sub", &value);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
if let WzProperty::SubProperty { properties, .. } = &props[0].1 {
assert_eq!(properties.len(), 1);
assert_eq!(properties[0].0, "val");
assert!(matches!(properties[0].1, WzProperty::Null));
} else {
panic!("Expected SubProperty");
}
}
#[test]
fn test_try_decrypt_wave_format_already_valid() {
let mut wav = vec![0u8; WAVE_FORMAT_SIZE];
wav[16] = 0; wav[17] = 0; let key = vec![0xFF; 18];
let original = wav.clone();
let result = try_decrypt_wave_format(&mut wav, &key);
assert!(!result); assert_eq!(wav, original); }
#[test]
fn test_try_decrypt_wave_format_too_short() {
let mut wav = vec![0u8; 10]; let result = try_decrypt_wave_format(&mut wav, &[]);
assert!(!result);
}
#[test]
fn test_try_decrypt_wave_format_decrypts() {
let mut plain = vec![0u8; 20];
plain[16] = 2; plain[17] = 0;
let key = vec![0x55u8; 20];
let mut encrypted: Vec<u8> = plain.iter().zip(key.iter()).map(|(a, b)| a ^ b).collect();
let extra_before = u16::from_le_bytes([encrypted[16], encrypted[17]]) as usize;
assert_ne!(WAVE_FORMAT_SIZE + extra_before, encrypted.len());
let result = try_decrypt_wave_format(&mut encrypted, &key);
assert!(result);
assert_eq!(encrypted, plain); }
#[test]
fn test_parse_unknown_property_type_error() {
let value = vec![0xFEu8]; let data = build_image_with_property("x", &value);
let mut reader = make_reader(data);
let err = parse_image(&mut reader).unwrap_err();
matches!(err, WzError::UnknownPropertyType(_));
}
#[test]
fn test_parse_invalid_property_count() {
let mut data = property_image_header();
data.push(0x80);
data.extend_from_slice(&600_000i32.to_le_bytes());
let mut reader = make_reader(data);
let err = parse_image(&mut reader).unwrap_err();
matches!(err, WzError::Custom(_));
}
#[test]
fn test_parse_extended_invalid_type_byte() {
let inner = vec![0xFFu8];
let mut value = vec![0x09u8];
value.extend_from_slice(&(inner.len() as u32).to_le_bytes());
value.extend_from_slice(&inner);
let data = build_image_with_property("bad", &value);
let mut reader = make_reader(data);
let err = parse_image(&mut reader).unwrap_err();
assert!(matches!(err, WzError::Custom(_)));
}
#[test]
fn test_parse_unknown_extended_type_returns_string() {
let inner: Vec<u8> = Vec::new();
let value = build_extended_property("SomeUnknownType", &inner);
let data = build_image_with_property("unk", &value);
let mut reader = make_reader(data);
let props = parse_image(&mut reader).unwrap();
assert_eq!(props[0].1.as_str(), Some("SomeUnknownType"));
}
}