Mercurial > touhou
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 } |
