comparison formats/src/th06/anm0.rs @ 757:21b186be2590

Split the Rust version into multiple crates.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Tue, 05 Jan 2021 02:16:32 +0100
parents src/th06/anm0.rs@fc937d93a57c
children 2a5279168d5a
comparison
equal deleted inserted replaced
756:4d91790cf8ab 757:21b186be2590
1 //! ANM0 animation format support.
2
3 use nom::{
4 IResult,
5 bytes::complete::{tag, take_while_m_n},
6 number::complete::{le_u8, le_u16, le_u32, le_i32, le_f32},
7 sequence::tuple,
8 multi::{many_m_n, many0},
9 };
10 use std::collections::HashMap;
11
12 /// Coordinates of a sprite into the image.
13 #[derive(Debug, Clone)]
14 pub struct Sprite {
15 /// Index inside the anm0.
16 pub index: u32,
17
18 /// X coordinate in the sprite sheet.
19 pub x: f32,
20
21 /// Y coordinate in the sprite sheet.
22 pub y: f32,
23
24 /// Width of the sprite.
25 pub width: f32,
26
27 /// Height of the sprite.
28 pub height: f32,
29 }
30
31 /// A single instruction, part of a `Script`.
32 #[derive(Debug, Clone)]
33 pub struct Call {
34 /// Time at which this instruction will be called.
35 pub time: u16,
36
37 /// The instruction to call.
38 pub instr: Instruction,
39 }
40
41 /// Script driving an animation.
42 #[derive(Debug, Clone)]
43 pub struct Script {
44 /// List of instructions in this script.
45 pub instructions: Vec<Call>,
46
47 /// List of interrupts in this script.
48 pub interrupts: HashMap<i32, u8>
49 }
50
51 /// Main struct of the ANM0 animation format.
52 #[derive(Debug, Clone)]
53 pub struct Anm0 {
54 /// Resolution of the image used by this ANM.
55 pub size: (u32, u32),
56
57 /// Format of this ANM.
58 pub format: u32,
59
60 /// File name of the main image.
61 pub png_filename: String,
62
63 /// File name of an alpha channel image.
64 pub alpha_filename: Option<String>,
65
66 /// A list of sprites, coordinates into the attached image.
67 pub sprites: Vec<Sprite>,
68
69 /// A map of scripts.
70 pub scripts: HashMap<u8, Script>,
71 }
72
73 impl Anm0 {
74 /// Parse a slice of bytes into an `Anm0` struct.
75 pub fn from_slice(data: &[u8]) -> IResult<&[u8], Vec<Anm0>> {
76 many0(parse_anm0)(data)
77 }
78
79 /// TODO
80 pub fn inv_size(&self) -> (f32, f32) {
81 let (x, y) = self.size;
82 (1. / x as f32, 1. / y as f32)
83 }
84 }
85
86 fn parse_name(i: &[u8]) -> IResult<&[u8], String> {
87 let (_, slice) = take_while_m_n(0, 32, |c| c != 0)(i)?;
88 let string = match String::from_utf8(slice.to_vec()) {
89 Ok(string) => string,
90 // XXX: use a more specific error instead.
91 Err(_) => return Err(nom::Err::Failure(nom::error::Error::new(i, nom::error::ErrorKind::Eof)))
92 };
93 Ok((i, string))
94 }
95
96 fn parse_sprite(i: &[u8]) -> IResult<&[u8], Sprite> {
97 let (i, (index, x, y, width, height)) = tuple((le_u32, le_f32, le_f32, le_f32, le_f32))(i)?;
98 Ok((i, Sprite {
99 index,
100 x,
101 y,
102 width,
103 height,
104 }))
105 }
106
107 macro_rules! declare_anm_instructions {
108 ($($opcode:tt => fn $name:ident($($arg:ident: $arg_type:ident),*)),*,) => {
109 /// Available instructions in an `Anm0`.
110 #[allow(missing_docs)]
111 #[derive(Debug, Clone, Copy)]
112 pub enum Instruction {
113 $(
114 $name($($arg_type),*)
115 ),*
116 }
117
118 fn parse_instruction_args(mut i: &[u8], opcode: u8) -> IResult<&[u8], Instruction> {
119 let instr = match opcode {
120 $(
121 $opcode => {
122 $(
123 let (i2, $arg) = concat_idents!(le_, $arg_type)(i)?;
124 i = i2;
125 )*
126 Instruction::$name($($arg),*)
127 }
128 )*
129 // XXX: use a more specific error instead.
130 _ => return Err(nom::Err::Failure(nom::error::Error::new(i, nom::error::ErrorKind::Eof)))
131 };
132 Ok((i, instr))
133 }
134 };
135 }
136
137 declare_anm_instructions!{
138 0 => fn Delete(),
139 1 => fn LoadSprite(sprite_number: u32),
140 2 => fn SetScale(sx: f32, sy: f32),
141 3 => fn SetAlpha(alpha: u32),
142 4 => fn SetColor(red: u8, green: u8, blue: u8/*, XXX: x8*/),
143 5 => fn Jump(instruction: u32),
144 7 => fn ToggleMirrored(),
145 9 => fn SetRotations3d(x: f32, y: f32, z: f32),
146 10 => fn SetRotationsSpeed3d(x: f32, y: f32, z: f32),
147 11 => fn SetScaleSpeed(sx: f32, sy: f32),
148 12 => fn Fade(alpha: u32, duration: u32),
149 13 => fn SetBlendmodeAdd(),
150 14 => fn SetBlendmodeAlphablend(),
151 15 => fn KeepStill(),
152 16 => fn LoadRandomSprite(min_index: u32, amplitude: u32),
153 17 => fn Move(x: f32, y: f32, z: f32),
154 18 => fn MoveToLinear(x: f32, y: f32, z: f32, duration: u32),
155 19 => fn MoveToDecel(x: f32, y: f32, z: f32, duration: u32),
156 20 => fn MoveToAccel(x: f32, y: f32, z: f32, duration: u32),
157 21 => fn Wait(),
158 22 => fn InterruptLabel(label: i32),
159 23 => fn SetCornerRelativePlacement(),
160 24 => fn WaitEx(),
161 25 => fn SetAllowOffset(allow: u32), // TODO: better name
162 26 => fn SetAutomaticOrientation(automatic: u32),
163 27 => fn ShiftTextureX(dx: f32),
164 28 => fn ShiftTextureY(dy: f32),
165 29 => fn SetVisible(visible: u32),
166 30 => fn ScaleIn(sx: f32, sy: f32, duration: u32),
167 31 => fn Todo(todo: u32),
168 }
169
170 fn parse_anm0(input: &[u8]) -> IResult<&[u8], Anm0> {
171 let (i, (num_sprites, num_scripts, _, width, height, format, _unknown1,
172 first_name_offset, _unused, second_name_offset, version, _unknown2,
173 _texture_offset, has_data, _next_offset, unknown3)) =
174 tuple((le_u32, le_u32, tag(b"\0\0\0\0"), le_u32, le_u32, le_u32, le_u32, le_u32,
175 le_u32, le_u32, le_u32, le_u32, le_u32, le_u32, le_u32, le_u32))(input)?;
176
177 assert_eq!(version, 0);
178 assert_eq!(unknown3, 0);
179 assert_eq!(has_data, 0);
180 let num_sprites = num_sprites as usize;
181 let num_scripts = num_scripts as usize;
182
183 let (i, sprite_offsets) = many_m_n(num_sprites, num_sprites, le_u32)(i)?;
184 let (_, script_offsets) = many_m_n(num_scripts, num_scripts, tuple((le_u32, le_u32)))(i)?;
185
186 let png_filename = if first_name_offset > 0 {
187 if input.len() < first_name_offset as usize {
188 return Err(nom::Err::Failure(nom::error::Error::new(input, nom::error::ErrorKind::Eof)));
189 }
190 let i = &input[first_name_offset as usize..];
191 let (_, name) = parse_name(i)?;
192 name
193 } else {
194 String::new()
195 };
196
197 let alpha_filename = if second_name_offset > 0 {
198 if input.len() < second_name_offset as usize {
199 return Err(nom::Err::Failure(nom::error::Error::new(input, nom::error::ErrorKind::Eof)));
200 }
201 let i = &input[second_name_offset as usize..];
202 let (_, name) = parse_name(i)?;
203 Some(name)
204 } else {
205 None
206 };
207
208 let mut sprites = vec![];
209 let mut i = &input[..];
210 for offset in sprite_offsets.into_iter().map(|x| x as usize) {
211 if input.len() < offset {
212 return Err(nom::Err::Failure(nom::error::Error::new(input, nom::error::ErrorKind::Eof)));
213 }
214 i = &input[offset..];
215 let (_, sprite) = parse_sprite(i)?;
216 sprites.push(sprite);
217 }
218
219 let mut scripts = HashMap::new();
220 for (index, offset) in script_offsets.into_iter().map(|(index, offset)| (index as u8, offset as usize)) {
221 if input.len() < offset {
222 return Err(nom::Err::Failure(nom::error::Error::new(input, nom::error::ErrorKind::Eof)));
223 }
224 i = &input[offset..];
225 let mut instruction_offsets = vec![];
226
227 let mut instructions = vec![];
228 loop {
229 let tell = input.len() - i.len();
230 instruction_offsets.push(tell - offset);
231 // TODO: maybe check against the size of parsed data?
232 let (i2, (time, opcode, _size)) = tuple((le_u16, le_u8, le_u8))(i)?;
233 let (i2, instr) = parse_instruction_args(i2, opcode)?;
234 instructions.push(Call { time, instr });
235 i = i2;
236 if opcode == 0 {
237 break;
238 }
239 }
240 let mut interrupts = HashMap::new();
241 let mut j = 0;
242 for Call { time: _, instr } in &mut instructions {
243 match instr {
244 Instruction::Jump(ref mut offset) => {
245 let result = instruction_offsets.binary_search(&(*offset as usize));
246 match result {
247 Ok(ptr) => *offset = ptr as u32,
248 Err(ptr) => {
249 // XXX: use a more specific error instead.
250 return Err(nom::Err::Failure(nom::error::Error::new(input, nom::error::ErrorKind::Eof)));
251 //println!("Instruction offset not found for pointer: {}", ptr);
252 }
253 }
254 }
255 Instruction::InterruptLabel(interrupt) => {
256 interrupts.insert(*interrupt, j + 1);
257 }
258 _ => ()
259 }
260 j += 1;
261 }
262 scripts.insert(index, Script {
263 instructions,
264 interrupts,
265 });
266 }
267
268 let anm0 = Anm0 {
269 size: (width, height),
270 format,
271 png_filename,
272 alpha_filename,
273 sprites,
274 scripts,
275 };
276 Ok((i, anm0))
277 }
278
279 #[cfg(test)]
280 mod tests {
281 use super::*;
282 use std::io::{self, Read};
283 use std::fs::File;
284
285 #[test]
286 fn anm0() {
287 let file = File::open("EoSD/CM/player01.anm").unwrap();
288 let mut file = io::BufReader::new(file);
289 let mut buf = vec![];
290 file.read_to_end(&mut buf).unwrap();
291 let (_, mut anms) = Anm0::from_slice(&buf).unwrap();
292 assert_eq!(anms.len(), 1);
293 let anm0 = anms.pop().unwrap();
294 assert_eq!(anm0.size, (256, 256));
295 assert_eq!(anm0.format, 5);
296 }
297 }