Mercurial > touhou
comparison src/th06/std.rs @ 671:266911c0ccfd
Add a parser for the stage background format.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Mon, 12 Aug 2019 20:41:27 +0200 |
parents | |
children | 6020e33d4fc4 |
comparison
equal
deleted
inserted
replaced
670:cdb484115c5b | 671:266911c0ccfd |
---|---|
1 //! STD background 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 combinator::map, | |
9 }; | |
10 use encoding_rs::SHIFT_JIS; | |
11 use std::collections::HashMap; | |
12 | |
13 /// A float position in the 3D space. | |
14 #[derive(Debug, Clone)] | |
15 pub struct Position { | |
16 /// X component. | |
17 pub x: f32, | |
18 | |
19 /// Y component. | |
20 pub y: f32, | |
21 | |
22 /// Z component. | |
23 pub z: f32, | |
24 } | |
25 | |
26 /// A 3D box around something. | |
27 #[derive(Debug, Clone)] | |
28 struct Box3D { | |
29 width: f32, | |
30 height: f32, | |
31 depth: f32, | |
32 } | |
33 | |
34 /// A 2D box around something. | |
35 #[derive(Debug, Clone)] | |
36 pub struct Box2D { | |
37 width: f32, | |
38 height: f32, | |
39 } | |
40 | |
41 /// A quad in the 3D space. | |
42 #[derive(Debug, Clone)] | |
43 pub struct Quad { | |
44 /// The anm script to run for this quad. | |
45 pub anm_script: u16, | |
46 | |
47 /// The position of this quad in the 3D space. | |
48 pub pos: Position, | |
49 | |
50 /// The size of this quad. | |
51 pub size_override: Box2D, | |
52 } | |
53 | |
54 /// A model formed of multiple quads in space. | |
55 #[derive(Debug, Clone)] | |
56 pub struct Model { | |
57 /// TODO: find what that is. | |
58 pub unknown: u16, | |
59 | |
60 /// The bounding box around this model. | |
61 pub bounding_box: [f32; 6], | |
62 | |
63 /// The quads composing this model. | |
64 pub quads: Vec<Quad>, | |
65 } | |
66 | |
67 /// An instance of a model. | |
68 #[derive(Debug, Clone)] | |
69 pub struct Instance { | |
70 /// The instance identifier. | |
71 pub id: u16, | |
72 | |
73 /// Where to position the instance of this model. | |
74 pub pos: Position, | |
75 } | |
76 | |
77 /// A single instruction, part of a `Script`. | |
78 #[derive(Debug, Clone)] | |
79 pub struct Call { | |
80 /// Time at which this instruction will be called. | |
81 pub time: u32, | |
82 | |
83 /// The instruction to call. | |
84 pub instr: Instruction, | |
85 } | |
86 | |
87 /// Parse a SHIFT_JIS byte string of length 128 into a String. | |
88 pub fn le_String(i: &[u8]) -> IResult<&[u8], String> { | |
89 let data = i.splitn(2, |c| *c == b'\0').nth(0).unwrap(); | |
90 let (string, encoding, replaced) = SHIFT_JIS.decode(data); | |
91 Ok((&i[128..], string.into_owned())) | |
92 } | |
93 | |
94 /// Main struct of the STD stage format. | |
95 #[derive(Debug, Clone)] | |
96 pub struct Stage { | |
97 /// The name of the stage. | |
98 pub name: String, | |
99 | |
100 /// A list of (name, path) of background music. | |
101 // TODO: there are maximum four of them, and in practice never more than 2. | |
102 pub musics: Vec<Option<(String, String)>>, | |
103 | |
104 /// List of models. | |
105 pub models: Vec<Model>, | |
106 | |
107 /// List of instances. | |
108 pub instances: Vec<Instance>, | |
109 | |
110 /// List of instructions in the script. | |
111 pub script: Vec<Call>, | |
112 } | |
113 | |
114 impl Stage { | |
115 /// Parse a slice of bytes into an `Stage` struct. | |
116 pub fn from_slice(data: &[u8]) -> IResult<&[u8], Stage> { | |
117 parse_stage(data) | |
118 } | |
119 } | |
120 | |
121 macro_rules! declare_stage_instructions { | |
122 ($($opcode:tt => fn $name:ident($($arg:ident: $arg_type:ident),*)),*,) => { | |
123 /// Available instructions in an `Stage`. | |
124 #[allow(missing_docs)] | |
125 #[derive(Debug, Clone, Copy)] | |
126 pub enum Instruction { | |
127 $( | |
128 $name($($arg_type),*) | |
129 ),* | |
130 } | |
131 | |
132 fn parse_instruction_args(input: &[u8], opcode: u16) -> IResult<&[u8], Instruction> { | |
133 let mut i = &input[..]; | |
134 let instr = match opcode { | |
135 $( | |
136 $opcode => { | |
137 $( | |
138 let (i2, $arg) = concat_idents!(le_, $arg_type)(i)?; | |
139 i = i2; | |
140 )* | |
141 Instruction::$name($($arg),*) | |
142 } | |
143 )* | |
144 _ => unreachable!() | |
145 }; | |
146 Ok((i, instr)) | |
147 } | |
148 }; | |
149 } | |
150 | |
151 declare_stage_instructions!{ | |
152 0 => fn SetViewpos(x: f32, y: f32, z: f32), | |
153 1 => fn SetFog(r: u8, g: u8, b: u8, a: u8, near: f32, far: f32), | |
154 2 => fn SetViewpos2(x: f32, y: f32, z: f32), | |
155 3 => fn StartInterpolatingViewpos2(frame: i32, _unused: i32, _unused: i32), | |
156 4 => fn StartInterpolatingFog(frame: i32, _unused: i32, _unused: i32), | |
157 5 => fn Unknown(_unused: i32, _unused: i32, _unused: i32), | |
158 } | |
159 | |
160 fn parse_stage(input: &[u8]) -> IResult<&[u8], Stage> { | |
161 let start_offset = 0; | |
162 let i = &input[start_offset..]; | |
163 let (i, (num_models, num_faces, object_instances_offset, script_offset, _)) = tuple((le_u16, le_u16, le_u32, le_u32, tag(b"\0\0\0\0")))(i)?; | |
164 let object_instances_offset = object_instances_offset as usize; | |
165 let script_offset = script_offset as usize; | |
166 | |
167 let (i, name) = le_String(i)?; | |
168 let (i, music_names) = map(tuple((le_String, le_String, le_String, le_String)), |(a, b, c, d)| [a, b, c, d])(i)?; | |
169 let (mut i, music_paths) = map(tuple((le_String, le_String, le_String, le_String)), |(a, b, c, d)| [a, b, c, d])(i)?; | |
170 let musics = music_names.iter().zip(&music_paths).map(|(name, path)| if name == " " { None } else { Some((name.clone(), path.clone())) }).collect(); | |
171 | |
172 let mut offsets = vec![]; | |
173 for _ in 0..num_models { | |
174 let (i2, offset) = le_u32(i)?; | |
175 offsets.push(offset as usize); | |
176 i = i2; | |
177 } | |
178 | |
179 // Read model definitions. | |
180 let mut models = vec![]; | |
181 for offset in offsets { | |
182 let i = &input[offset..]; | |
183 let (mut i, (id, unknown, x, y, z, width, height, depth)) = tuple((le_u16, le_u16, le_f32, le_f32, le_f32, le_f32, le_f32, le_f32))(i)?; | |
184 let bounding_box = [x, y, z, width, height, depth]; | |
185 let mut quads = vec![]; | |
186 loop { | |
187 let (i2, (unk1, size)) = tuple((le_u16, le_u16))(i)?; | |
188 if unk1 == 0xffff { | |
189 break; | |
190 } | |
191 assert_eq!(size, 0x1c); | |
192 let (i2, (anm_script, _, x, y, z, width, height)) = tuple((le_u16, tag(b"\0\0"), le_f32, le_f32, le_f32, le_f32, le_f32))(i2)?; | |
193 let quad = Quad { | |
194 anm_script, | |
195 pos: Position { x, y, z }, | |
196 size_override: Box2D { width, height }, | |
197 }; | |
198 quads.push(quad); | |
199 i = i2; | |
200 } | |
201 let model = Model { | |
202 unknown, | |
203 bounding_box, | |
204 quads, | |
205 }; | |
206 models.push(model); | |
207 } | |
208 | |
209 // Read object usage. | |
210 let mut instances = vec![]; | |
211 let mut i = &input[object_instances_offset..]; | |
212 loop { | |
213 let (i2, (id, unknown, x, y, z)) = tuple((le_u16, le_u16, le_f32, le_f32, le_f32))(i)?; | |
214 if id == 0xffff && unknown == 0xffff { | |
215 break; | |
216 } | |
217 assert_eq!(unknown, 0x100); | |
218 let instance = Instance { | |
219 id, | |
220 pos: Position { x, y, z }, | |
221 }; | |
222 instances.push(instance); | |
223 i = i2; | |
224 } | |
225 | |
226 // Read the script. | |
227 let mut script = vec![]; | |
228 let mut i = &input[script_offset..]; | |
229 loop { | |
230 let (i2, (time, opcode, size)) = tuple((le_u32, le_u16, le_u16))(i)?; | |
231 if time == 0xffffffff && opcode == 0xffff && size == 0xffff { | |
232 break; | |
233 } | |
234 assert_eq!(size, 12); | |
235 let data = &i2[..12]; | |
236 let (data, instr) = parse_instruction_args(data, opcode)?; | |
237 assert_eq!(data.len(), 0); | |
238 println!("{:?}", instr); | |
239 script.push(Call { time, instr }); | |
240 i = &i2[12..]; | |
241 } | |
242 | |
243 let stage = Stage { | |
244 name, | |
245 musics, | |
246 models, | |
247 instances, | |
248 script, | |
249 }; | |
250 Ok((b"", stage)) | |
251 } | |
252 | |
253 #[cfg(test)] | |
254 mod tests { | |
255 use super::*; | |
256 use std::io::{self, Read}; | |
257 use std::fs::File; | |
258 | |
259 #[test] | |
260 fn std() { | |
261 let file = File::open("EoSD/ST/stage1.std").unwrap(); | |
262 let mut file = io::BufReader::new(file); | |
263 let mut buf = vec![]; | |
264 file.read_to_end(&mut buf).unwrap(); | |
265 let (_, stage) = Stage::from_slice(&buf).unwrap(); | |
266 assert_eq!(stage.name, "夢幻夜行絵巻 ~ Mystic Flier"); | |
267 assert_eq!(stage.musics.len(), 4); | |
268 assert_eq!(stage.models.len(), 13); | |
269 assert_eq!(stage.instances.len(), 90); | |
270 assert_eq!(stage.script.len(), 21); | |
271 } | |
272 } |