Mercurial > touhou
comparison 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 |
comparison
equal
deleted
inserted
replaced
637:afa012bb8021 | 638:a806f28e94fc |
---|---|
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_f32}, | |
7 }; | |
8 use std::collections::HashMap; | |
9 | |
10 /// Coordinates of a sprite into the image. | |
11 #[derive(Debug, Clone)] | |
12 pub struct Sprite { | |
13 index: u32, | |
14 x: f32, | |
15 y: f32, | |
16 width: f32, | |
17 height: f32, | |
18 } | |
19 | |
20 /// A single instruction, part of a `Script`. | |
21 #[derive(Debug, Clone)] | |
22 struct Instruction { | |
23 time: u16, | |
24 opcode: u8, | |
25 args: Vec<Arg>, | |
26 } | |
27 | |
28 /// Script driving an animation. | |
29 #[derive(Debug, Clone)] | |
30 pub struct Script { | |
31 instructions: Vec<Instruction>, | |
32 interrupts: HashMap<u32, u8> | |
33 } | |
34 | |
35 /// Main struct of the ANM0 animation format. | |
36 #[derive(Debug, Clone)] | |
37 pub struct Anm0 { | |
38 /// Resolution of the image used by this ANM. | |
39 pub size: (u32, u32), | |
40 | |
41 /// Format of this ANM. | |
42 pub format: u32, | |
43 | |
44 /// File name of the main image. | |
45 pub first_name: String, | |
46 | |
47 /// File name of an alpha channel image. | |
48 pub second_name: String, | |
49 | |
50 /// A list of sprites, coordinates into the attached image. | |
51 pub sprites: Vec<Sprite>, | |
52 | |
53 /// A map of scripts. | |
54 pub scripts: HashMap<u8, Script>, | |
55 } | |
56 | |
57 impl Anm0 { | |
58 /// Parse a slice of bytes into an `Anm0` struct. | |
59 pub fn from_slice(data: &[u8]) -> Result<Anm0, ()> { | |
60 // XXX: report the exact nom error instead! | |
61 let (_, anm0) = parse_anm0(data).or_else(|_| Err(()))?; | |
62 assert_eq!(anm0.len(), 1); | |
63 Ok(anm0[0].clone()) | |
64 } | |
65 } | |
66 | |
67 fn parse_name(i: &[u8], is_present: bool) -> IResult<&[u8], String> { | |
68 if !is_present { | |
69 return Ok((i, String::new())); | |
70 } | |
71 let (_, slice) = take_while_m_n(0, 32, |c| c != 0)(i)?; | |
72 // XXX: no unwrap! | |
73 let string = String::from_utf8(slice.to_vec()).unwrap(); | |
74 Ok((i, string)) | |
75 } | |
76 | |
77 fn parse_sprite(i: &[u8]) -> IResult<&[u8], Sprite> { | |
78 let (i, index) = le_u32(i)?; | |
79 let (i, x) = le_f32(i)?; | |
80 let (i, y) = le_f32(i)?; | |
81 let (i, width) = le_f32(i)?; | |
82 let (i, height) = le_f32(i)?; | |
83 Ok((i, Sprite { | |
84 index, | |
85 x, | |
86 y, | |
87 width, | |
88 height, | |
89 })) | |
90 } | |
91 | |
92 #[derive(Debug, Clone, Copy)] | |
93 enum Arg { | |
94 U8(u8), | |
95 U32(u32), | |
96 F32(f32), | |
97 Ptr(u8), | |
98 } | |
99 | |
100 fn parse_u8_arg(i: &[u8]) -> IResult<&[u8], Arg> { | |
101 let (i, value) = le_u8(i)?; | |
102 Ok((i, Arg::U8(value))) | |
103 } | |
104 | |
105 fn parse_u32_arg(i: &[u8]) -> IResult<&[u8], Arg> { | |
106 let (i, value) = le_u32(i)?; | |
107 Ok((i, Arg::U32(value))) | |
108 } | |
109 | |
110 fn parse_f32_arg(i: &[u8]) -> IResult<&[u8], Arg> { | |
111 let (i, value) = le_f32(i)?; | |
112 Ok((i, Arg::F32(value))) | |
113 } | |
114 | |
115 macro_rules! declare_anm_instructions { | |
116 ($($opcode:tt => fn $name:ident($($arg:ident: $arg_type:ty),*)),*,) => { | |
117 fn parse_instruction_args(input: &[u8], opcode: u8) -> IResult<&[u8], Vec<Arg>> { | |
118 let mut args = vec![]; | |
119 let mut i = &input[..]; | |
120 match opcode { | |
121 $( | |
122 $opcode => { | |
123 $( | |
124 let (i2, data) = match stringify!($arg_type) { | |
125 "u8" => parse_u8_arg(i), | |
126 "u32" => parse_u32_arg(i), | |
127 "f32" => parse_f32_arg(i), | |
128 "x8" => parse_u8_arg(i), | |
129 _ => unreachable!(), | |
130 }?; | |
131 i = i2; | |
132 if stringify!($arg_type) != "x8" { | |
133 args.push(data); | |
134 } | |
135 )* | |
136 } | |
137 )* | |
138 _ => unreachable!() | |
139 } | |
140 Ok((i, args)) | |
141 } | |
142 }; | |
143 } | |
144 | |
145 declare_anm_instructions!{ | |
146 0 => fn delete(), | |
147 1 => fn set_sprite(sprite_number: u32), | |
148 2 => fn set_scale(sx: f32, sy: f32), | |
149 3 => fn set_alpha(alpha: u32), | |
150 4 => fn set_color(red: u8, green: u8, blue: u8, XXX: x8), | |
151 5 => fn jump(instruction: u32), | |
152 7 => fn toggle_mirrored(), | |
153 9 => fn set_3d_rotations(x: f32, y: f32, z: f32), | |
154 10 => fn set_3d_rotations_speed(x: f32, y: f32, z: f32), | |
155 11 => fn set_scale_speed(sx: f32, sy: f32), | |
156 12 => fn fade(alpha: u32, duration: u32), | |
157 13 => fn set_blendmode_add(), | |
158 14 => fn set_blendmode_alphablend(), | |
159 15 => fn keep_still(), | |
160 16 => fn set_random_sprite(min_index: u32, amplitude: u32), | |
161 17 => fn set_3d_translation(x: f32, y: f32, z: f32), | |
162 18 => fn move_to_linear(x: f32, y: f32, z: f32, duration: u32), | |
163 19 => fn move_to_decel(x: f32, y: f32, z: f32, duration: u32), | |
164 20 => fn move_to_accel(x: f32, y: f32, z: f32, duration: u32), | |
165 21 => fn wait(), | |
166 22 => fn interrupt_label(label: u32), | |
167 23 => fn set_corner_relative_placement(), | |
168 24 => fn wait_ex(), | |
169 25 => fn set_allow_offset(allow: u32), // TODO: better name | |
170 26 => fn set_automatic_orientation(automatic: u32), | |
171 27 => fn shift_texture_x(dx: f32), | |
172 28 => fn shift_texture_y(dy: f32), | |
173 29 => fn set_visible(visible: u32), | |
174 30 => fn scale_in(sx: f32, sy: f32, duration: u32), | |
175 31 => fn TODO(TODO: u32), | |
176 } | |
177 | |
178 fn parse_anm0(input: &[u8]) -> IResult<&[u8], Vec<Anm0>> { | |
179 let mut list = vec![]; | |
180 let start_offset = 0; | |
181 loop { | |
182 let i = &input[start_offset..]; | |
183 let (i, num_sprites) = le_u32(i)?; | |
184 let (i, num_scripts) = le_u32(i)?; | |
185 let (i, _) = tag(b"\0\0\0\0")(i)?; | |
186 let (i, width) = le_u32(i)?; | |
187 let (i, height) = le_u32(i)?; | |
188 let (i, format) = le_u32(i)?; | |
189 let (i, _unknown1) = le_u32(i)?; | |
190 let (i, first_name_offset) = le_u32(i)?; | |
191 let (i, _unused) = le_u32(i)?; | |
192 let (i, second_name_offset) = le_u32(i)?; | |
193 let (i, version) = le_u32(i)?; | |
194 let (i, _unknown2) = le_u32(i)?; | |
195 let (i, _texture_offset) = le_u32(i)?; | |
196 let (i, has_data) = le_u32(i)?; | |
197 let (i, _next_offset) = le_u32(i)?; | |
198 let (mut i, unknown3) = le_u32(i)?; | |
199 | |
200 assert_eq!(version, 0); | |
201 assert_eq!(unknown3, 0); | |
202 assert_eq!(has_data, 0); | |
203 | |
204 let mut sprite_offsets = vec![]; | |
205 for _ in 0..num_sprites { | |
206 let (i2, offset) = le_u32(i)?; | |
207 sprite_offsets.push(offset as usize); | |
208 i = i2; | |
209 } | |
210 | |
211 let mut script_offsets = vec![]; | |
212 for _ in 0..num_scripts { | |
213 let (i2, index) = le_u32(i)?; | |
214 let (i2, offset) = le_u32(i2)?; | |
215 script_offsets.push((index as u8, offset as usize)); | |
216 i = i2; | |
217 } | |
218 | |
219 let i = &input[start_offset + first_name_offset as usize..]; | |
220 let (_, first_name) = parse_name(i, first_name_offset > 0)?; | |
221 | |
222 let i = &input[start_offset + second_name_offset as usize..]; | |
223 let (_, second_name) = parse_name(i, second_name_offset > 0)?; | |
224 | |
225 let mut sprites = vec![]; | |
226 let mut i; | |
227 for offset in sprite_offsets { | |
228 i = &input[start_offset + offset..]; | |
229 let (_, sprite) = parse_sprite(i)?; | |
230 sprites.push(sprite); | |
231 } | |
232 | |
233 let mut scripts = HashMap::new(); | |
234 for (index, offset) in script_offsets { | |
235 i = &input[start_offset + offset..]; | |
236 let mut instruction_offsets = vec![]; | |
237 | |
238 let mut instructions = vec![]; | |
239 loop { | |
240 let tell = input.len() - i.len(); | |
241 instruction_offsets.push(tell - (start_offset + offset)); | |
242 let (i2, time) = le_u16(i)?; | |
243 let (i2, opcode) = le_u8(i2)?; | |
244 // TODO: maybe check against the size of parsed data? | |
245 let (i2, _size) = le_u8(i2)?; | |
246 let (i2, args) = parse_instruction_args(i2, opcode)?; | |
247 instructions.push(Instruction { time, opcode, args }); | |
248 i = i2; | |
249 if opcode == 0 { | |
250 break; | |
251 } | |
252 } | |
253 let mut interrupts = HashMap::new(); | |
254 let mut j = 0; | |
255 for Instruction { time: _, opcode, args } in &mut instructions { | |
256 match opcode { | |
257 5 => { | |
258 let offset = match args[0] { | |
259 Arg::U32(offset) => offset as usize, | |
260 _ => panic!("Wrong argument type for jump!"), | |
261 }; | |
262 let result = instruction_offsets.binary_search(&offset); | |
263 let ptr = match result { | |
264 Ok(ptr) => ptr as u8, | |
265 // TODO: make that a recoverable error instead. | |
266 Err(ptr) => panic!("Instruction offset not found for pointer: {}", ptr), | |
267 }; | |
268 args[0] = Arg::Ptr(ptr); | |
269 } | |
270 22 => { | |
271 // TODO: maybe also remove this instruction, as the label is already | |
272 // present. | |
273 if let Arg::U32(interrupt) = args[0] { | |
274 interrupts.insert(interrupt, j + 1); | |
275 } else { | |
276 panic!("Wrong argument type for interrupt!"); | |
277 } | |
278 } | |
279 _ => () | |
280 } | |
281 j += 1; | |
282 } | |
283 scripts.insert(index, Script { | |
284 instructions, | |
285 interrupts, | |
286 }); | |
287 } | |
288 | |
289 assert!(has_data == 0); | |
290 | |
291 let anm0 = Anm0 { | |
292 size: (width, height), | |
293 format, | |
294 first_name, | |
295 second_name, | |
296 sprites, | |
297 scripts, | |
298 }; | |
299 list.push(anm0); | |
300 break; | |
301 } | |
302 Ok((b"", list)) | |
303 } | |
304 | |
305 #[cfg(test)] | |
306 mod tests { | |
307 use super::*; | |
308 use std::io::{self, Read}; | |
309 use std::fs::File; | |
310 | |
311 #[test] | |
312 fn anm0() { | |
313 let file = File::open("/home/linkmauve/games/pc/東方/TH06 ~ The Embodiment of Scarlet Devil/CM/player01.anm").unwrap(); | |
314 let mut file = io::BufReader::new(file); | |
315 let mut buf = vec![]; | |
316 file.read_to_end(&mut buf).unwrap(); | |
317 let anm0 = Anm0::from_slice(&buf).unwrap(); | |
318 assert_eq!(anm0.size, (256, 256)); | |
319 assert_eq!(anm0.format, 5); | |
320 } | |
321 } |