comparison src/th06/ecl.rs @ 650:f6bfc9e6dab0

Add an ECL parser.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Thu, 08 Aug 2019 17:03:00 +0200
parents
children 5f02984dd12a
comparison
equal deleted inserted replaced
649:967627181a76 650:f6bfc9e6dab0
1 //! ECL enemy script format support.
2
3 use nom::{
4 IResult,
5 bytes::complete::take_while_m_n,
6 number::complete::{le_u8, le_u16, le_u32, le_i16, le_i32, le_f32},
7 };
8 use encoding_rs::SHIFT_JIS;
9
10 /// A single instruction, part of a `Script`.
11 #[derive(Debug, Clone)]
12 pub struct CallSub {
13 /// Time at which this instruction will be called.
14 pub time: u32,
15
16 /// TODO
17 pub rank_mask: u16,
18
19 /// TODO
20 pub param_mask: u16,
21
22 /// The instruction to call.
23 pub instr: SubInstruction,
24 }
25
26 /// Script driving an animation.
27 #[derive(Debug, Clone)]
28 pub struct Sub {
29 /// List of instructions in this script.
30 pub instructions: Vec<CallSub>,
31 }
32
33 /// A single instruction, part of a `Script`.
34 #[derive(Debug, Clone)]
35 pub struct CallMain {
36 /// Time at which this instruction will be called.
37 pub time: u16,
38
39 /// Subroutine to call for this enemy.
40 pub sub: u16,
41
42 /// The instruction to call.
43 pub instr: MainInstruction,
44 }
45
46 /// Script driving an animation.
47 #[derive(Debug, Clone)]
48 pub struct Main {
49 /// List of instructions in this script.
50 pub instructions: Vec<CallMain>,
51 }
52
53 /// Main struct of the ANM0 animation format.
54 #[derive(Debug, Clone)]
55 pub struct Ecl {
56 /// A list of subs.
57 pub subs: Vec<Sub>,
58
59 /// A list of mains.
60 pub mains: Vec<Main>,
61 }
62
63 impl Ecl {
64 /// Parse a slice of bytes into an `Ecl` struct.
65 pub fn from_slice(data: &[u8]) -> IResult<&[u8], Ecl> {
66 parse_ecl(data)
67 }
68 }
69
70 macro_rules! declare_main_instructions {
71 ($($opcode:tt => fn $name:ident($($arg:ident: $arg_type:ident),*)),*,) => {
72 /// Available instructions in an `Ecl`.
73 #[allow(missing_docs)]
74 #[derive(Debug, Clone, Copy)]
75 pub enum MainInstruction {
76 $(
77 $name($($arg_type),*)
78 ),*
79 }
80
81 fn parse_main_instruction_args(input: &[u8], opcode: u16) -> IResult<&[u8], MainInstruction> {
82 let mut i = &input[..];
83 let instr = match opcode {
84 $(
85 $opcode => {
86 $(
87 let (i2, $arg) = concat_idents!(le_, $arg_type)(i)?;
88 i = i2;
89 )*
90 MainInstruction::$name($($arg),*)
91 }
92 )*
93 _ => unreachable!()
94 };
95 Ok((i, instr))
96 }
97 };
98 }
99
100 /// XXX
101 pub fn le_String(i: &[u8]) -> IResult<&[u8], String> {
102 let (string, encoding, replaced) = SHIFT_JIS.decode(i);
103 Ok((&i[34..], string.into_owned()))
104 }
105
106 macro_rules! declare_sub_instructions {
107 ($($opcode:tt => fn $name:ident($($arg:ident: $arg_type:ident),*)),*,) => {
108 /// Available instructions in an `Ecl`.
109 #[allow(missing_docs)]
110 #[derive(Debug, Clone)]
111 pub enum SubInstruction {
112 $(
113 $name($($arg_type),*)
114 ),*
115 }
116
117 fn parse_sub_instruction_args(input: &[u8], opcode: u16) -> IResult<&[u8], SubInstruction> {
118 let mut i = &input[..];
119 let instr = match opcode {
120 $(
121 $opcode => {
122 $(
123 let (i2, $arg) = concat_idents!(le_, $arg_type)(i)?;
124 i = i2;
125 )*
126 SubInstruction::$name($($arg),*)
127 }
128 )*
129 _ => unreachable!()
130 };
131 Ok((i, instr))
132 }
133 };
134 }
135
136 declare_main_instructions!{
137 0 => fn SpawnEnemy(x: f32, y: f32, z: f32, life: i16, bonus_dropped: i16, die_score: u32),
138 2 => fn SpawnEnemyMirrored(x: f32, y: f32, z: f32, life: i16, bonus_dropped: i16, die_score: u32),
139 4 => fn SpawnEnemyRandom(x: f32, y: f32, z: f32, life: i16, bonus_dropped: i16, die_score: u32),
140 6 => fn SpawnEnemyMirroredRandom(x: f32, y: f32, z: f32, life: i16, bonus_dropped: i16, die_score: u32),
141 8 => fn CallMessage(),
142 9 => fn WaitMessage(),
143 10 => fn ResumeEcl(x: f32, y: f32),
144 12 => fn WaitForBossDeath(),
145 }
146
147 declare_sub_instructions!{
148 0 => fn Noop(),
149 1 => fn Destroy(unused: u32),
150 2 => fn RelativeJump(frame: u32, ip: i32),
151 3 => fn RelativeJumpEx(frame: u32, ip: i32, variable_id: i32),
152 4 => fn SetInt(var: i32, value: i32),
153 5 => fn SetFloat(var: i32, value: f32),
154 6 => fn SetRandomInt(var: i32, max: i32),
155 8 => fn SetRandomFloat(var: i32, max: f32),
156 9 => fn SetRandomFloat2(var: i32, amplitude: f32, min: f32),
157 10 => fn StoreX(var: i32),
158 13 => fn AddInt(var: i32, a: i32, b: i32),
159 14 => fn SubstractInt(var: i32, a: i32, b: i32),
160 15 => fn MultiplyInt(var: i32, a: i32, b: i32),
161 16 => fn DivideInt(var: i32, a: i32, b: i32),
162 17 => fn Modulo(var: i32, a: i32, b: i32),
163 18 => fn Increment(var: i32),
164 20 => fn AddFloat(var: i32, a: f32, b: f32),
165 21 => fn SubstractFloat(var: i32, a: f32, b: f32),
166 23 => fn DivideFloat(var: i32, a: f32, b: f32),
167 25 => fn GetDirection(var: i32, x1: f32, y1: f32, x2: f32, y2: f32),
168 26 => fn FloatToUnitCircle(var: i32),
169 27 => fn CompareInts(a: i32, b: i32),
170 28 => fn CompareFloats(a: f32, b: f32),
171 29 => fn RelativeJumpIfLowerThan(frame: i32, ip: i32),
172 30 => fn RelativeJumpIfLowerOrEqual(frame: i32, ip: i32),
173 31 => fn RelativeJumpIfEqual(frame: i32, ip: i32),
174 32 => fn RelativeJumpIfGreaterThan(frame: i32, ip: i32),
175 33 => fn RelativeJumpIfGreaterOrEqual(frame: i32, ip: i32),
176 34 => fn RelativeJumpIfNotEqual(frame: i32, ip: i32),
177 35 => fn Call(sub: i32, param1: i32, param2: f32),
178 36 => fn Return(),
179 39 => fn CallIfEqual(sub: i32, param1: i32, param2: f32, a: i32, b: i32),
180 43 => fn SetPosition(x: f32, y: f32, z: f32),
181 45 => fn SetAngleAndSpeed(angle: f32, speed: f32),
182 46 => fn SetRotationSpeed(speed: f32),
183 47 => fn SetSpeed(speed: f32),
184 48 => fn SetAcceleration(acceleration: f32),
185 49 => fn SetRandomAngle(min: f32, max: f32),
186 50 => fn SetRandomAngleEx(min: f32, max: f32),
187 51 => fn TargetPlayer(TODO: f32, speed: f32),
188 52 => fn MoveInDecel(duration: i32, angle: f32, speed: f32),
189 56 => fn MoveToLinear(duration: i32, x: f32, y: f32, z: f32),
190 57 => fn MoveToDecel(duration: i32, x: f32, y: f32, z: f32),
191 59 => fn MoveToAccel(duration: i32, x: f32, y: f32, z: f32),
192 61 => fn StopIn(duration: i32),
193 63 => fn StopInAccel(duration: i32),
194 65 => fn SetScreenBox(xmin: f32, ymin: f32, xmax: f32, ymax: f32),
195 66 => fn ClearScreenBox(),
196 67 => fn SetBulletAttributes1(anim: i16, sprite_index_offset: i16, bullets_per_shot: i32, number_of_shots: i32, speed: f32, speed2: f32, launch_angle: f32, angle: f32, flags: i32),
197 68 => fn SetBulletAttributes2(anim: i16, sprite_index_offset: i16, bullets_per_shot: i32, number_of_shots: i32, speed: f32, speed2: f32, launch_angle: f32, angle: f32, flags: i32),
198 69 => fn SetBulletAttributes3(anim: i16, sprite_index_offset: i16, bullets_per_shot: i32, number_of_shots: i32, speed: f32, speed2: f32, launch_angle: f32, angle: f32, flags: i32),
199 70 => fn SetBulletAttributes4(anim: i16, sprite_index_offset: i16, bullets_per_shot: i32, number_of_shots: i32, speed: f32, speed2: f32, launch_angle: f32, angle: f32, flags: i32),
200 71 => fn SetBulletAttributes5(anim: i16, sprite_index_offset: i16, bullets_per_shot: i32, number_of_shots: i32, speed: f32, speed2: f32, launch_angle: f32, angle: f32, flags: i32),
201 74 => fn SetBulletAttributes6(anim: i16, sprite_index_offset: i16, bullets_per_shot: i32, number_of_shots: i32, speed: f32, speed2: f32, launch_angle: f32, angle: f32, flags: i32),
202 75 => fn SetBulletAttributes7(anim: i16, sprite_index_offset: i16, bullets_per_shot: i32, number_of_shots: i32, speed: f32, speed2: f32, launch_angle: f32, angle: f32, flags: i32),
203 76 => fn SetBulletInterval(interval: i32),
204 77 => fn SetBulletIntervalEx(interval: i32),
205 78 => fn DelayAttack(),
206 79 => fn NoDelayAttack(),
207 81 => fn SetBulletLaunchOffset(x: f32, y: f32, z: f32),
208 82 => fn SetExtendedBulletAttributes(a: i32, b: i32, c: i32, d: i32, e: f32, f: f32, g: f32, h: f32),
209 83 => fn ChangeBulletsInStarBonus(),
210 // 84: ('i', None),
211 85 => fn NewLaser(laser_type: i16, sprite_idx_offset: i16, angle: f32, speed: f32, start_offset: f32, end_offset: f32, max_length: f32, width: f32, start_duration: i32, duration: i32, end_duration: i32, grazing_delay: i32, grazing_extra_duration: i32, UNK1: i32),
212 86 => fn NewLaserTowardsPlayer(laser_type: i16, sprite_idx_offset: i16, angle: f32, speed: f32, start_offset: f32, end_offset: f32, max_length: f32, width: f32, start_duration: i32, duration: i32, end_duration: i32, grazing_delay: i32, grazing_extra_duration: i32, UNK1: i32),
213 87 => fn SetUpcomingLaserId(id: i32),
214 88 => fn AlterLaserAngle(id: i32, delta: f32),
215 90 => fn RepositionLaser(id: i32, ox: f32, oy: f32, oz: f32),
216 92 => fn CanceLaser(id: i32),
217 93 => fn SetSpellcard(face: i16, number: i16, name: String),
218 94 => fn Endspellcard(),
219 95 => fn SpawnEnemy(sub: i32, x: f32, y: f32, z: f32, life: i16, bonus_dropped: i16, die_score: i32),
220 96 => fn KillAllEnemies(),
221 97 => fn SetAnim(script: i32),
222 98 => fn SetMultipleAnims(default: i16, end_left: i16, end_right: i16, left: i16, right: i16, UNUSED: i16),
223 99 => fn SetAuxAnm(number: i32, script: i32),
224 100 => fn SetDeathAnim(sprite_index: i32),
225 101 => fn SetBossMode(value: i32),
226 102 => fn CreateSquares(UNK1: i32, UNK2: f32, UNK3: f32, UNK4: f32, UNK5: f32),
227 103 => fn SetEnemyHitbox(width: f32, height: f32, depth: f32),
228 104 => fn SetCollidable(collidable: i32),
229 105 => fn SetDamageable(damageable: i32),
230 106 => fn PlaySound(index: i32),
231 107 => fn SetDeathFlags(death_flags: i32),
232 108 => fn SetDeathCallback(sub: i32),
233 109 => fn MemoryWriteInt(value: i32, index: i32),
234 111 => fn SetLife(life: i32),
235 112 => fn SetEllapsedTime(frame: i32),
236 113 => fn SetLowLifeTrigger(trigger: i32),
237 114 => fn SetLowLifeCallback(sub: i32),
238 115 => fn SetTimeout(timeout: i32),
239 116 => fn SetTimeoutCallback(sub: i32),
240 117 => fn SetTouchable(touchable: i32),
241 118 => fn DropParticles(anim: i32, number: u32, r: u8, g: u8, b: u8, UNUSED: u8),
242 119 => fn DropBonus(number: i32),
243 120 => fn SetAutomaticOrientation(automatic: i32),
244 121 => fn CallSpecialFunction(function: i32, argument: i32),
245 122 => fn UNK1(TODO: i32),
246 123 => fn SkipFrames(frames: i32),
247 124 => fn DropSpecificBonus(type_: i32),
248 125 => fn UNK2(),
249 126 => fn SetRemainingLives(lives: i32),
250 127 => fn UNK3(TODO: i32),
251 128 => fn Interrupt(event: i32),
252 129 => fn InterruptAux(number: i32, event: i32),
253 130 => fn UNK5(TODO: i32),
254 131 => fn SetDifficultyCoeffs(speed_a: f32, speed_b: f32, nb_a: i32, nb_b: i32, shots_a: i32, shots_b: i32),
255 132 => fn SetInvisible(invisible: i32),
256 133 => fn CopyCallbacks(),
257 134 => fn UNK6(),
258 135 => fn EnableSpellcardBonus(UNKNOW: i32),
259 }
260
261 fn parse_ecl(input: &[u8]) -> IResult<&[u8], Ecl> {
262 let i = input;
263
264 let (i, sub_count) = le_u16(i)?;
265 let (mut i, main_count) = le_u16(i)?;
266 assert_eq!(main_count, 0);
267
268 let mut main_offsets = Vec::new();
269 for _ in 0..3 {
270 let (i2, offset) = le_u32(i)?;
271 main_offsets.push(offset as usize);
272 i = i2;
273 }
274
275 let mut sub_offsets = Vec::new();
276 for _ in 0..sub_count {
277 let (i2, offset) = le_u32(i)?;
278 sub_offsets.push(offset as usize);
279 i = i2;
280 }
281
282 // Read all subs.
283 let mut subs = Vec::new();
284 for offset in sub_offsets {
285 let mut i = &input[offset..];
286 let mut instructions = Vec::new();
287 loop {
288 let (i2, time) = le_u32(i)?;
289 let (i2, opcode) = le_u16(i2)?;
290 if time == 0xffffffff || opcode == 0xffff {
291 break;
292 }
293
294 let (i2, size) = le_u16(i2)?;
295 let (i2, rank_mask) = le_u16(i2)?;
296 let (i2, param_mask) = le_u16(i2)?;
297 // FIXME: this - 12 can trigger a panic, fuzz it!
298 let data = &i2[..size as usize - 12];
299 let (data, instr) = parse_sub_instruction_args(data, opcode)?;
300 assert_eq!(data.len(), 0);
301 instructions.push(CallSub { time, rank_mask, param_mask, instr });
302 i = &i[size as usize..];
303 }
304 println!("{:#?}", instructions);
305 subs.push(Sub { instructions });
306 }
307
308 // Read all mains (always a single one atm).
309 let mut mains = Vec::new();
310 for offset in main_offsets {
311 if offset == 0 {
312 break;
313 }
314
315 let mut i = &input[offset..];
316 let mut instructions = Vec::new();
317 loop {
318 let (i2, time) = le_u16(i)?;
319 let (i2, sub) = le_u16(i2)?;
320 if time == 0xffff && sub == 4 {
321 break;
322 }
323
324 let (i2, opcode) = le_u16(i2)?;
325 let (i2, size) = le_u16(i2)?;
326 // FIXME: this - 8 can trigger a panic, fuzz it!
327 let data = &i2[..size as usize - 8];
328 let (data, instr) = parse_main_instruction_args(data, opcode)?;
329 assert_eq!(data.len(), 0);
330 instructions.push(CallMain { time, sub, instr });
331 i = &i[size as usize..];
332 }
333 mains.push(Main { instructions });
334 }
335
336 let ecl = Ecl {
337 subs,
338 mains,
339 };
340 Ok((b"", ecl))
341 }
342
343 #[cfg(test)]
344 mod tests {
345 use super::*;
346 use std::io::{self, Read};
347 use std::fs::File;
348
349 #[test]
350 fn ecl() {
351 let file = File::open("EoSD/ST/ecldata1.ecl").unwrap();
352 let mut file = io::BufReader::new(file);
353 let mut buf = vec![];
354 file.read_to_end(&mut buf).unwrap();
355 let (_, ecl) = Ecl::from_slice(&buf).unwrap();
356 assert_eq!(ecl.subs.len(), 24);
357 assert_eq!(ecl.mains.len(), 1);
358 }
359 }