ps2mc 0.1.0

A Rust library for reading, extracting, and rewriting PlayStation 2 memory card images.
Documentation
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), // (第一行, 第二行) 解码自 Shift-JIS
    pub background_transparency: u32,
    pub bg_colors: [[u32; 4]; 4],    // 左上, 右上, 左下, 右下
    pub light_dir: [[f32; 4]; 3],    // 光源 1, 2, 3
    pub light_colors: [[f32; 4]; 3], // 光照颜色 1, 2, 3
    pub ambient: [f32; 4],           // 环境光
    pub icon_file_normal: String,
    pub icon_file_copy: String,
    pub icon_file_delete: String,
}

impl IconSys {
    /// 从 964 字节的切片中安全解析出 icon.sys 结构
    pub fn read_from(bytes: &[u8]) -> Result<Self, Ps2mcError> {
        // 1. 验证长度
        if bytes.len() != ICON_SYS_SIZE {
            return Err(Ps2mcError::InvalidIconSysLength {
                expected: ICON_SYS_SIZE,
                actual: bytes.len(),
            });
        }

        // 2. 验证 Magic 幻数
        if !bytes.starts_with(ICON_SYS_MAGIC) {
            return Err(Ps2mcError::InvalidIconSysMagic);
        }

        // 3. 解析基础元数据
        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]]);

        // 4. 解析背景颜色组 (16个 u32 -> 4组 [u32; 4])
        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]]),
            ];
        }

        // 5. 解析 3D 光源方向与颜色 (28个 f32)
        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();

        // 6. 处理 Shift-JIS 编码的字幕 (对应 Python 的字幕切割与解码)
        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())
        };

        // 7. 解析 3D 模型文件名 (ASCII 字符串)
        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,
        })
    }

    /// 完美的复刻 Python 的 print_info 打印函数
    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);
    }
}

/// 截取 C 风格以 \0 结尾的字符串切片视图
fn zero_terminate(bytes: &[u8]) -> &[u8] {
    bytes.split(|&b| b == 0).next().unwrap_or(bytes)
}

/// 解析标准的无损 ASCII 字符串
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" })
}

/// 使用 encoding_rs 库将 Shift-JIS 转换为 Rust 的标准 UTF-8 String
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()
    }
}