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