ps2mc 0.1.0

A Rust library for reading, extracting, and rewriting PlayStation 2 memory card images.
Documentation
use std::borrow::Cow;
use std::fs::File;
use std::io::Write;
use std::path::Path;

use super::error::Ps2mcError;

const ICON_MAGIC: u32 = 0x0001_0000;
const ANIM_HEADER_MAGIC: u32 = 0x01;
const TEX_WIDTH: usize = 128;
const TEX_HEIGHT: usize = 128;
const UNCOMPRESSED_TEX_SIZE: usize = TEX_WIDTH * TEX_HEIGHT * 2; // 32,768 字节
const RGB_TEX_SIZE: usize = TEX_WIDTH * TEX_HEIGHT * 3; // 49,152 字节

#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct VertexCoord {
    pub x: i16,
    pub y: i16,
    pub z: i16,
    pub w: u16,
}

#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct NormalDir {
    pub x: i16,
    pub y: i16,
    pub z: i16,
    pub w: u16,
}

#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct TexUV {
    pub u: i16,
    pub v: i16,
}

#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct VertexColor {
    pub r: u8,
    pub g: u8,
    pub b: u8,
    pub a: u8,
}

/// 合并顶点属性,取代多个独立 Vec,提高内存局部性。
#[derive(Debug, Clone, PartialEq)]
pub struct Vertex {
    pub normal: NormalDir,
    pub uv: TexUV,
    pub color: VertexColor,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Icon {
    pub animation_shapes: usize,
    pub tex_type: u32,
    pub vertex_count: usize,
    pub coords: Vec<VertexCoord>,
    pub vertices: Vec<Vertex>,
    pub frame_length: u32,
    pub anim_speed: f32,
    pub play_offset: u32,
    pub frame_count: usize,
    pub texture_rgb: Option<Vec<u8>>, // RGB888 数据
}

impl Icon {
    /// 从字节流安全解析 3D 图标。
    pub fn parse(bytes: &[u8]) -> Result<Self, Ps2mcError> {
        let mut off = 0;

        // 1. 图标头部
        let magic = read_u32(bytes, &mut off)?;
        if magic != ICON_MAGIC {
            return Err(Ps2mcError::InvalidIconMagic);
        }
        let animation_shapes = read_u32(bytes, &mut off)? as usize;
        let tex_type = read_u32(bytes, &mut off)?;
        let _reserved = read_u32(bytes, &mut off)?;
        let vertex_count = read_u32(bytes, &mut off)? as usize;

        // 2. 顶点数据
        let mut coords = Vec::with_capacity(vertex_count * animation_shapes);
        let mut vertices = Vec::with_capacity(vertex_count);

        for _ in 0..vertex_count {
            for _ in 0..animation_shapes {
                coords.push(VertexCoord {
                    x: read_i16(bytes, &mut off)?,
                    y: read_i16(bytes, &mut off)?,
                    z: read_i16(bytes, &mut off)?,
                    w: read_u16(bytes, &mut off)?,
                });
            }
            let normal = NormalDir {
                x: read_i16(bytes, &mut off)?,
                y: read_i16(bytes, &mut off)?,
                z: read_i16(bytes, &mut off)?,
                w: read_u16(bytes, &mut off)?,
            };
            let uv = TexUV {
                u: read_i16(bytes, &mut off)?,
                v: read_i16(bytes, &mut off)?,
            };
            let color = VertexColor {
                r: read_u8(bytes, &mut off)?,
                g: read_u8(bytes, &mut off)?,
                b: read_u8(bytes, &mut off)?,
                a: read_u8(bytes, &mut off)?,
            };
            vertices.push(Vertex { normal, uv, color });
        }

        // 3. 动画头部
        let anim_magic = read_u32(bytes, &mut off)?;
        if anim_magic != ANIM_HEADER_MAGIC {
            return Err(Ps2mcError::InvalidAnimationHeader);
        }
        let frame_length = read_u32(bytes, &mut off)?;
        let anim_speed = read_f32(bytes, &mut off)?;
        let play_offset = read_u32(bytes, &mut off)?;
        let frame_count = read_u32(bytes, &mut off)? as usize;

        // 4. 跳过动画关键帧数据
        for _ in 0..frame_count {
            let _shape_id = read_u32(bytes, &mut off)?;
            let key_count = read_u32(bytes, &mut off)?;
            let _res_a = read_u32(bytes, &mut off)?;
            let _res_b = read_u32(bytes, &mut off)?;
            if key_count > 1 {
                let skip = 8 * (key_count as usize - 1);
                check_bounds(bytes, off, skip, "keyframe data")?;
                off += skip;
            }
        }

        // 5. 纹理处理(可选)
        let texture_rgb = if tex_type & 0b100 != 0 {
            let raw = load_texture(bytes, &mut off, tex_type)?;
            Some(decode_texture(&raw)?)
        } else {
            None
        };

        Ok(Self {
            animation_shapes,
            tex_type,
            vertex_count,
            coords,
            vertices,
            frame_length,
            anim_speed,
            play_offset,
            frame_count,
            texture_rgb,
        })
    }

    /// 将 RGB 纹理写入文件。
    pub fn export_texture<P: AsRef<Path>>(&self, path: P) -> std::io::Result<()> {
        if let Some(ref data) = self.texture_rgb {
            let mut f = File::create(path)?;
            f.write_all(data)?;
            return Ok(());
        }

        Err(std::io::Error::other("icon does not contain texture data"))
    }

    /// 打印基本信息(调试用)。
    pub fn print_info(&self) {
        println!("animation_shapes: {}", self.animation_shapes);
        println!("tex_type: {:#b}", self.tex_type);
        println!("vertex_count: {}", self.vertex_count);
        println!("frame_length: {}", self.frame_length);
        println!("anim_speed: {}", self.anim_speed);
        println!("play_offset: {}", self.play_offset);
        println!("frame_count: {}", self.frame_count);
        if let Some(ref tex) = self.texture_rgb {
            println!("texture_rgb size: {} bytes", tex.len());
        }
    }

    /// 是否使用平滑着色 (Gouraud Shading)
    pub fn is_smooth_shaded(&self) -> bool {
        (self.tex_type & 0b0001) != 0
    }

    /// 是否包含纹理
    pub fn has_texture(&self) -> bool {
        (self.tex_type & 0b0100) != 0
    }

    /// 纹理是否使用 RLE 压缩
    pub fn is_texture_compressed(&self) -> bool {
        (self.tex_type & 0b1000) != 0
    }
}

fn read_u8(bytes: &[u8], off: &mut usize) -> Result<u8, Ps2mcError> {
    check_bounds(bytes, *off, 1, "u8")?;
    let val = bytes[*off];
    *off += 1;
    Ok(val)
}

fn read_i16(bytes: &[u8], off: &mut usize) -> Result<i16, Ps2mcError> {
    check_bounds(bytes, *off, 2, "i16")?;
    let val = i16::from_le_bytes([bytes[*off], bytes[*off + 1]]);
    *off += 2;
    Ok(val)
}

fn read_u16(bytes: &[u8], off: &mut usize) -> Result<u16, Ps2mcError> {
    check_bounds(bytes, *off, 2, "u16")?;
    let val = u16::from_le_bytes([bytes[*off], bytes[*off + 1]]);
    *off += 2;
    Ok(val)
}

fn read_u32(bytes: &[u8], off: &mut usize) -> Result<u32, Ps2mcError> {
    check_bounds(bytes, *off, 4, "u32")?;
    let val = u32::from_le_bytes([bytes[*off], bytes[*off + 1], bytes[*off + 2], bytes[*off + 3]]);
    *off += 4;
    Ok(val)
}

fn read_f32(bytes: &[u8], off: &mut usize) -> Result<f32, Ps2mcError> {
    check_bounds(bytes, *off, 4, "f32")?;
    let val = f32::from_le_bytes([bytes[*off], bytes[*off + 1], bytes[*off + 2], bytes[*off + 3]]);
    *off += 4;
    Ok(val)
}

fn check_bounds(bytes: &[u8], offset: usize, need: usize, ctx: &'static str) -> Result<(), Ps2mcError> {
    if bytes.len() < offset + need {
        return Err(Ps2mcError::UnexpectedEof {
            context: ctx,
            expected: need,
            actual: bytes.len().saturating_sub(offset),
        });
    }
    Ok(())
}

fn load_texture<'a>(bytes: &'a [u8], off: &mut usize, tex_type: u32) -> Result<Cow<'a, [u8]>, Ps2mcError> {
    if tex_type & 0b1000 != 0 {
        // 有压缩,解码生成新 Vec,用 Cow::Owned 包装
        let compressed_size = read_u32(bytes, off)? as usize;
        check_bounds(bytes, *off, compressed_size, "compressed texture")?;
        let data = &bytes[*off..*off + compressed_size];
        *off += compressed_size;
        Ok(Cow::Owned(decode_rle(data)?))
    } else {
        // 无压缩,直接借用原始数据,零拷贝,用 Cow::Borrowed 包装
        check_bounds(bytes, *off, UNCOMPRESSED_TEX_SIZE, "uncompressed texture")?;
        let data = &bytes[*off..*off + UNCOMPRESSED_TEX_SIZE];
        *off += UNCOMPRESSED_TEX_SIZE;
        Ok(Cow::Borrowed(data))
    }
}

fn decode_rle(data: &[u8]) -> Result<Vec<u8>, Ps2mcError> {
    let mut output = Vec::with_capacity(UNCOMPRESSED_TEX_SIZE);
    let mut pos = 0;
    while pos < data.len() {
        if pos + 2 > data.len() {
            return Err(Ps2mcError::UnexpectedEof {
                context: "RLE code",
                expected: 2,
                actual: data.len() - pos,
            });
        }
        let code = u16::from_le_bytes([data[pos], data[pos + 1]]);
        pos += 2;
        if code & 0x8000 != 0 {
            // 直接拷贝后续字节
            let word_count = (0x8000 - (code ^ 0x8000)) as usize;
            let byte_count = word_count * 2;
            // 在 extend 之前进行安全校验,防范恶意文件造成的内存暴增
            if output.len() + byte_count > UNCOMPRESSED_TEX_SIZE {
                return Err(Ps2mcError::InvalidTextureEncoding);
            }
            output.extend_from_slice(&data[pos..pos + byte_count]);
            pos += byte_count;
        } else if code > 0 {
            // 重复一个 16 位字 code 次
            let add_bytes = (code as usize) * 2;
            if output.len() + add_bytes > UNCOMPRESSED_TEX_SIZE {
                return Err(Ps2mcError::InvalidTextureEncoding);
            }
            let word = [data[pos], data[pos + 1]];
            for _ in 0..code as usize {
                output.extend_from_slice(&word);
            }
            pos += 2;
        }
        if output.len() > UNCOMPRESSED_TEX_SIZE {
            return Err(Ps2mcError::InvalidTextureEncoding);
        }
    }
    if output.len() != UNCOMPRESSED_TEX_SIZE {
        return Err(Ps2mcError::InvalidTextureSize {
            expected: UNCOMPRESSED_TEX_SIZE,
            actual: output.len(),
        });
    }
    Ok(output)
}

fn decode_texture(raw: &[u8]) -> Result<Vec<u8>, Ps2mcError> {
    if raw.len() != UNCOMPRESSED_TEX_SIZE {
        return Err(Ps2mcError::InvalidTextureSize {
            expected: UNCOMPRESSED_TEX_SIZE,
            actual: raw.len(),
        });
    }
    let mut rgb = vec![0u8; RGB_TEX_SIZE];
    for (i, chunk) in raw.chunks_exact(2).enumerate() {
        let pixel = u16::from_le_bytes([chunk[0], chunk[1]]);
        let r = (pixel & 0x1F) << 3;
        let g = ((pixel >> 5) & 0x1F) << 3;
        let b = ((pixel >> 10) & 0x1F) << 3;
        let idx = i * 3;
        rgb[idx] = r as u8;
        rgb[idx + 1] = g as u8;
        rgb[idx + 2] = b as u8;
    }
    Ok(rgb)
}