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; const RGB_TEX_SIZE: usize = TEX_WIDTH * TEX_HEIGHT * 3;
#[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,
}
#[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>>, }
impl Icon {
pub fn parse(bytes: &[u8]) -> Result<Self, Ps2mcError> {
let mut off = 0;
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;
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 });
}
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;
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;
}
}
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,
})
}
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());
}
}
pub fn is_smooth_shaded(&self) -> bool {
(self.tex_type & 0b0001) != 0
}
pub fn has_texture(&self) -> bool {
(self.tex_type & 0b0100) != 0
}
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 {
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 {
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;
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 {
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)
}