Mercurial > touhou
view src/th06/anm0.rs @ 638:a806f28e94fc
Add anm0 support.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Wed, 03 Jul 2019 22:17:04 +0200 |
parents | |
children | a8e0219162b6 |
line wrap: on
line source
//! ANM0 animation format support. use nom::{ IResult, bytes::complete::{tag, take_while_m_n}, number::complete::{le_u8, le_u16, le_u32, le_f32}, }; use std::collections::HashMap; /// Coordinates of a sprite into the image. #[derive(Debug, Clone)] pub struct Sprite { index: u32, x: f32, y: f32, width: f32, height: f32, } /// A single instruction, part of a `Script`. #[derive(Debug, Clone)] struct Instruction { time: u16, opcode: u8, args: Vec<Arg>, } /// Script driving an animation. #[derive(Debug, Clone)] pub struct Script { instructions: Vec<Instruction>, interrupts: HashMap<u32, u8> } /// Main struct of the ANM0 animation format. #[derive(Debug, Clone)] pub struct Anm0 { /// Resolution of the image used by this ANM. pub size: (u32, u32), /// Format of this ANM. pub format: u32, /// File name of the main image. pub first_name: String, /// File name of an alpha channel image. pub second_name: String, /// A list of sprites, coordinates into the attached image. pub sprites: Vec<Sprite>, /// A map of scripts. pub scripts: HashMap<u8, Script>, } impl Anm0 { /// Parse a slice of bytes into an `Anm0` struct. pub fn from_slice(data: &[u8]) -> Result<Anm0, ()> { // XXX: report the exact nom error instead! let (_, anm0) = parse_anm0(data).or_else(|_| Err(()))?; assert_eq!(anm0.len(), 1); Ok(anm0[0].clone()) } } fn parse_name(i: &[u8], is_present: bool) -> IResult<&[u8], String> { if !is_present { return Ok((i, String::new())); } let (_, slice) = take_while_m_n(0, 32, |c| c != 0)(i)?; // XXX: no unwrap! let string = String::from_utf8(slice.to_vec()).unwrap(); Ok((i, string)) } fn parse_sprite(i: &[u8]) -> IResult<&[u8], Sprite> { let (i, index) = le_u32(i)?; let (i, x) = le_f32(i)?; let (i, y) = le_f32(i)?; let (i, width) = le_f32(i)?; let (i, height) = le_f32(i)?; Ok((i, Sprite { index, x, y, width, height, })) } #[derive(Debug, Clone, Copy)] enum Arg { U8(u8), U32(u32), F32(f32), Ptr(u8), } fn parse_u8_arg(i: &[u8]) -> IResult<&[u8], Arg> { let (i, value) = le_u8(i)?; Ok((i, Arg::U8(value))) } fn parse_u32_arg(i: &[u8]) -> IResult<&[u8], Arg> { let (i, value) = le_u32(i)?; Ok((i, Arg::U32(value))) } fn parse_f32_arg(i: &[u8]) -> IResult<&[u8], Arg> { let (i, value) = le_f32(i)?; Ok((i, Arg::F32(value))) } macro_rules! declare_anm_instructions { ($($opcode:tt => fn $name:ident($($arg:ident: $arg_type:ty),*)),*,) => { fn parse_instruction_args(input: &[u8], opcode: u8) -> IResult<&[u8], Vec<Arg>> { let mut args = vec![]; let mut i = &input[..]; match opcode { $( $opcode => { $( let (i2, data) = match stringify!($arg_type) { "u8" => parse_u8_arg(i), "u32" => parse_u32_arg(i), "f32" => parse_f32_arg(i), "x8" => parse_u8_arg(i), _ => unreachable!(), }?; i = i2; if stringify!($arg_type) != "x8" { args.push(data); } )* } )* _ => unreachable!() } Ok((i, args)) } }; } declare_anm_instructions!{ 0 => fn delete(), 1 => fn set_sprite(sprite_number: u32), 2 => fn set_scale(sx: f32, sy: f32), 3 => fn set_alpha(alpha: u32), 4 => fn set_color(red: u8, green: u8, blue: u8, XXX: x8), 5 => fn jump(instruction: u32), 7 => fn toggle_mirrored(), 9 => fn set_3d_rotations(x: f32, y: f32, z: f32), 10 => fn set_3d_rotations_speed(x: f32, y: f32, z: f32), 11 => fn set_scale_speed(sx: f32, sy: f32), 12 => fn fade(alpha: u32, duration: u32), 13 => fn set_blendmode_add(), 14 => fn set_blendmode_alphablend(), 15 => fn keep_still(), 16 => fn set_random_sprite(min_index: u32, amplitude: u32), 17 => fn set_3d_translation(x: f32, y: f32, z: f32), 18 => fn move_to_linear(x: f32, y: f32, z: f32, duration: u32), 19 => fn move_to_decel(x: f32, y: f32, z: f32, duration: u32), 20 => fn move_to_accel(x: f32, y: f32, z: f32, duration: u32), 21 => fn wait(), 22 => fn interrupt_label(label: u32), 23 => fn set_corner_relative_placement(), 24 => fn wait_ex(), 25 => fn set_allow_offset(allow: u32), // TODO: better name 26 => fn set_automatic_orientation(automatic: u32), 27 => fn shift_texture_x(dx: f32), 28 => fn shift_texture_y(dy: f32), 29 => fn set_visible(visible: u32), 30 => fn scale_in(sx: f32, sy: f32, duration: u32), 31 => fn TODO(TODO: u32), } fn parse_anm0(input: &[u8]) -> IResult<&[u8], Vec<Anm0>> { let mut list = vec![]; let start_offset = 0; loop { let i = &input[start_offset..]; let (i, num_sprites) = le_u32(i)?; let (i, num_scripts) = le_u32(i)?; let (i, _) = tag(b"\0\0\0\0")(i)?; let (i, width) = le_u32(i)?; let (i, height) = le_u32(i)?; let (i, format) = le_u32(i)?; let (i, _unknown1) = le_u32(i)?; let (i, first_name_offset) = le_u32(i)?; let (i, _unused) = le_u32(i)?; let (i, second_name_offset) = le_u32(i)?; let (i, version) = le_u32(i)?; let (i, _unknown2) = le_u32(i)?; let (i, _texture_offset) = le_u32(i)?; let (i, has_data) = le_u32(i)?; let (i, _next_offset) = le_u32(i)?; let (mut i, unknown3) = le_u32(i)?; assert_eq!(version, 0); assert_eq!(unknown3, 0); assert_eq!(has_data, 0); let mut sprite_offsets = vec![]; for _ in 0..num_sprites { let (i2, offset) = le_u32(i)?; sprite_offsets.push(offset as usize); i = i2; } let mut script_offsets = vec![]; for _ in 0..num_scripts { let (i2, index) = le_u32(i)?; let (i2, offset) = le_u32(i2)?; script_offsets.push((index as u8, offset as usize)); i = i2; } let i = &input[start_offset + first_name_offset as usize..]; let (_, first_name) = parse_name(i, first_name_offset > 0)?; let i = &input[start_offset + second_name_offset as usize..]; let (_, second_name) = parse_name(i, second_name_offset > 0)?; let mut sprites = vec![]; let mut i; for offset in sprite_offsets { i = &input[start_offset + offset..]; let (_, sprite) = parse_sprite(i)?; sprites.push(sprite); } let mut scripts = HashMap::new(); for (index, offset) in script_offsets { i = &input[start_offset + offset..]; let mut instruction_offsets = vec![]; let mut instructions = vec![]; loop { let tell = input.len() - i.len(); instruction_offsets.push(tell - (start_offset + offset)); let (i2, time) = le_u16(i)?; let (i2, opcode) = le_u8(i2)?; // TODO: maybe check against the size of parsed data? let (i2, _size) = le_u8(i2)?; let (i2, args) = parse_instruction_args(i2, opcode)?; instructions.push(Instruction { time, opcode, args }); i = i2; if opcode == 0 { break; } } let mut interrupts = HashMap::new(); let mut j = 0; for Instruction { time: _, opcode, args } in &mut instructions { match opcode { 5 => { let offset = match args[0] { Arg::U32(offset) => offset as usize, _ => panic!("Wrong argument type for jump!"), }; let result = instruction_offsets.binary_search(&offset); let ptr = match result { Ok(ptr) => ptr as u8, // TODO: make that a recoverable error instead. Err(ptr) => panic!("Instruction offset not found for pointer: {}", ptr), }; args[0] = Arg::Ptr(ptr); } 22 => { // TODO: maybe also remove this instruction, as the label is already // present. if let Arg::U32(interrupt) = args[0] { interrupts.insert(interrupt, j + 1); } else { panic!("Wrong argument type for interrupt!"); } } _ => () } j += 1; } scripts.insert(index, Script { instructions, interrupts, }); } assert!(has_data == 0); let anm0 = Anm0 { size: (width, height), format, first_name, second_name, sprites, scripts, }; list.push(anm0); break; } Ok((b"", list)) } #[cfg(test)] mod tests { use super::*; use std::io::{self, Read}; use std::fs::File; #[test] fn anm0() { let file = File::open("/home/linkmauve/games/pc/東方/TH06 ~ The Embodiment of Scarlet Devil/CM/player01.anm").unwrap(); let mut file = io::BufReader::new(file); let mut buf = vec![]; file.read_to_end(&mut buf).unwrap(); let anm0 = Anm0::from_slice(&buf).unwrap(); assert_eq!(anm0.size, (256, 256)); assert_eq!(anm0.format, 5); } }