use super::error::Ps2mcError;
use std::str;
pub const ICON_SYS_SIZE: usize = 964;
pub const ICON_SYS_MAGIC: &[u8; 4] = b"PS2D";
#[derive(Debug, Clone, PartialEq)]
pub struct IconSys {
pub subtitle: (String, String), pub background_transparency: u32,
pub bg_colors: [[u32; 4]; 4], pub light_dir: [[f32; 4]; 3], pub light_colors: [[f32; 4]; 3], pub ambient: [f32; 4], pub icon_file_normal: String,
pub icon_file_copy: String,
pub icon_file_delete: String,
}
impl IconSys {
pub fn read_from(bytes: &[u8]) -> Result<Self, Ps2mcError> {
if bytes.len() != ICON_SYS_SIZE {
return Err(Ps2mcError::InvalidIconSysLength {
expected: ICON_SYS_SIZE,
actual: bytes.len(),
});
}
if !bytes.starts_with(ICON_SYS_MAGIC) {
return Err(Ps2mcError::InvalidIconSysMagic);
}
let subtitle_line_break = u16::from_le_bytes([bytes[6], bytes[7]]);
let background_transparency = u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
let mut bg_colors = [[0u32; 4]; 4];
for (i, chunk) in bytes[16..80].chunks_exact(16).enumerate() {
bg_colors[i] = [
u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]),
u32::from_le_bytes([chunk[4], chunk[5], chunk[6], chunk[7]]),
u32::from_le_bytes([chunk[8], chunk[9], chunk[10], chunk[11]]),
u32::from_le_bytes([chunk[12], chunk[13], chunk[14], chunk[15]]),
];
}
let mut float_off = 80;
let mut read_f32_vec4 = || -> [f32; 4] {
let mut vec4 = [0.0f32; 4];
for component in &mut vec4 {
*component = f32::from_le_bytes([
bytes[float_off],
bytes[float_off + 1],
bytes[float_off + 2],
bytes[float_off + 3],
]);
float_off += 4;
}
vec4
};
let light_dir = [read_f32_vec4(), read_f32_vec4(), read_f32_vec4()];
let light_colors = [read_f32_vec4(), read_f32_vec4(), read_f32_vec4()];
let ambient = read_f32_vec4();
let raw_subtitle = &bytes[192..260];
let clean_subtitle = zero_terminate(raw_subtitle);
let l_break = subtitle_line_break as usize;
let subtitle = if l_break < clean_subtitle.len() {
let (part1, part2) = clean_subtitle.split_at(l_break);
(decode_sjis(part1), decode_sjis(zero_terminate(part2)))
} else {
(decode_sjis(clean_subtitle), String::new())
};
let icon_file_normal = parse_ascii_string(&bytes[260..324])?;
let icon_file_copy = parse_ascii_string(&bytes[324..388])?;
let icon_file_delete = parse_ascii_string(&bytes[388..452])?;
Ok(Self {
subtitle,
background_transparency,
bg_colors,
light_dir,
light_colors,
ambient,
icon_file_normal,
icon_file_copy,
icon_file_delete,
})
}
pub fn print_info(&self) {
println!("subtitle: {:?}", self.subtitle);
println!("background_transparency: {}", self.background_transparency);
println!("bg_colors: {:?}", self.bg_colors);
println!("light_dir: {:?}", self.light_dir);
println!("light_colors: {:?}", self.light_colors);
println!("ambient: {:?}", self.ambient);
println!("icon_file_normal: {}", self.icon_file_normal);
println!("icon_file_copy: {}", self.icon_file_copy);
println!("icon_file_delete: {}", self.icon_file_delete);
}
}
fn zero_terminate(bytes: &[u8]) -> &[u8] {
bytes.split(|&b| b == 0).next().unwrap_or(bytes)
}
fn parse_ascii_string(bytes: &[u8]) -> Result<String, Ps2mcError> {
let clean = zero_terminate(bytes);
str::from_utf8(clean)
.map(|s| s.to_string())
.map_err(|_| Ps2mcError::InvalidAsciiField { field: "icon filename" })
}
fn decode_sjis(bytes: &[u8]) -> String {
if bytes.is_empty() {
return String::new();
}
let (res, _, has_error) = encoding_rs::SHIFT_JIS.decode(bytes);
if has_error {
String::from_utf8_lossy(bytes).into_owned()
} else {
res.into_owned()
}
}