1use fancy_slice::FancySlice;
2
3use crate::wii_memory::WiiMemory;
4
5pub(crate) fn scripts(
6 parent_data: FancySlice,
7 offset_data: FancySlice,
8 num: usize,
9 wii_memory: &WiiMemory,
10) -> Vec<Script> {
11 let mut result = vec![];
12 for i in 0..num {
13 let offset = offset_data.u32_be(i * 4);
14 result.push(new_script(parent_data, offset, wii_memory));
15 }
16 result
17}
18
19pub(crate) fn fragment_scripts(
21 parent_data: FancySlice,
22 known_scripts: &[&[Script]],
23 ignore_origins: &[i32],
24 wii_memory: &WiiMemory,
25) -> Vec<Script> {
26 let mut fragments: Vec<Script> = vec![];
27 for scripts in known_scripts.iter() {
28 for script in scripts.iter() {
29 for event in &script.events {
30 let mut found_offset = None;
31 if event.namespace == 0x00 && (event.code == 0x07 || event.code == 0x09) {
32 if let Some(Argument::Offset(Offset { offset, origin })) =
34 event.arguments.first()
35 && !ignore_origins.contains(origin)
36 {
37 found_offset = Some(*offset);
38 }
39
40 if let Some(Argument::Value(offset)) = event.arguments.first() {
41 found_offset = Some(*offset);
42 }
43 }
44 if event.namespace == 0x0D && (event.code == 0x00 || event.code == 0x05) {
45 if let Some(Argument::Offset(Offset { offset, origin })) =
47 event.arguments.get(1)
48 && !ignore_origins.contains(origin)
49 {
50 found_offset = Some(*offset);
51 }
52 }
53 if let Some(offset) = found_offset {
54 let mut is_action = false;
55 'outer: for check_scripts in known_scripts.iter() {
56 for check_script in check_scripts.iter() {
57 if check_script.offset == offset {
58 is_action = true;
59 break 'outer;
60 }
61 }
62 }
63 let already_added = fragments.iter().any(|x| x.offset == offset);
64
65 if !is_action && !already_added {
66 fragments.push(new_script(parent_data, offset as u32, wii_memory));
67 }
68 }
69 }
70 }
71 }
72
73 if !fragments.is_empty() {
74 let mut all = known_scripts.to_vec();
76 all.push(&fragments);
77 let inner_fragments = fragment_scripts(parent_data, &all, ignore_origins, wii_memory);
78 fragments.extend(inner_fragments);
79 }
80 fragments
81}
82
83pub fn new_script(parent_data: FancySlice, offset: u32, wii_memory: &WiiMemory) -> Script {
84 let buffer = if offset == 0 || offset as i32 == -1 {
85 return Script {
86 events: vec![],
87 offset: offset as i32,
88 };
89 } else if offset > 0 && offset < (parent_data.len() as u32) {
90 parent_data.relative_fancy_slice(offset as usize..)
91 } else if offset < 0x8000_0000 {
92 return Script {
93 events: vec![],
94 offset: offset as i32,
95 };
96 } else {
97 wii_memory.fancy_slice_from(offset as usize)
98 };
99
100 let mut events = vec![];
101 let mut event_offset = 0;
102 loop {
103 let namespace = buffer.u8(event_offset as usize);
104 let code = buffer.u8(event_offset as usize + 1);
105 let num_arguments = buffer.u8(event_offset as usize + 2);
106 let unk1 = buffer.u8(event_offset as usize + 3);
107 let raw_id = buffer.u32_be(event_offset as usize);
108
109 if code == 0 && namespace == 0 {
110 break;
112 }
113
114 if raw_id != 0xFADEF00D && raw_id != 0xFADE0D8A {
118 let argument_offset = buffer.u32_be(event_offset as usize + 4);
119
120 let argument_buffer = if argument_offset as usize >= parent_data.len() {
121 wii_memory.fancy_slice_from(argument_offset as usize)
122 } else {
123 parent_data.relative_fancy_slice(argument_offset as usize..)
124 };
125
126 let arguments = arguments(argument_buffer, argument_offset, num_arguments as usize);
127 events.push(Event {
128 namespace,
129 code,
130 unk1,
131 arguments,
132 });
133 }
134
135 event_offset += EVENT_SIZE as u32;
136 }
137 Script {
138 events,
139 offset: offset as i32,
140 }
141}
142
143#[rustfmt::skip]
144fn arguments(data: FancySlice, origin: u32, num_arguments: usize) -> Vec<Argument> {
145 let mut arguments = vec!();
146 for i in 0..num_arguments as i32 {
147 let argument_offset = i * ARGUMENT_SIZE as i32;
148
149 if argument_offset + 8 > data.len() as i32 {
150 error!("Script argument parsing tried to read out of bounds via offset {} into data of size {}", argument_offset, data.len());
151 break;
152 }
153
154 let ty = data.i32_be(argument_offset as usize );
155 let value = data.i32_be(argument_offset as usize + 4);
156
157 let argument = match ty {
158 0 => Argument::Value (value),
159 1 => Argument::Scalar (value as f32 / 60000.0),
160 2 => Argument::Offset (Offset { offset: value, origin: origin as i32 + argument_offset + 4}),
161 3 => Argument::Bool (value == 1),
162 4 => Argument::File (value),
163 5 => {
164 let value = value as u32;
165 let memory_type = ((value & 0xF0000000) >> 28) as u8;
166 let data_type = ((value & 0x0F000000) >> 24) as u8;
167 let address = value & 0x00FFFFFF;
168
169 let memory_type = VariableMemoryType::new(memory_type);
170 let data_type = VariableDataType::new(data_type);
171
172 Argument::Variable (Variable { memory_type, data_type, address })
173 }
174 6 => Argument::new(value as u32),
175 _ => Argument::Unknown (ty, value),
176 };
177 arguments.push(argument);
178 }
179
180 arguments
181}
182
183#[derive(Clone, Debug)]
184pub struct Script {
185 pub events: Vec<Event>,
186 pub offset: i32,
187}
188
189const EVENT_SIZE: usize = 0x8;
191#[derive(Serialize, Deserialize, Clone, Debug)]
192pub struct Event {
193 pub namespace: u8,
194 pub code: u8,
195 pub unk1: u8,
196 pub arguments: Vec<Argument>,
197}
198
199impl Event {
200 pub fn raw_id(&self) -> u32 {
201 let num_args = self.arguments.len();
202 assert!(num_args < 0x100);
203 ((self.namespace as u32) << 24) | ((self.code as u32) << 16) | ((num_args as u32) << 8)
204 }
205}
206
207const ARGUMENT_SIZE: usize = 0x8;
208#[derive(Serialize, Deserialize, Clone, Debug)]
209pub enum Argument {
210 Value(i32),
211 Scalar(f32),
212 Offset(Offset),
213 Bool(bool),
214 File(i32),
215 Variable(Variable),
216 Requirement { flip: bool, ty: Requirement },
217 Unknown(i32, i32),
218}
219
220#[derive(Serialize, Deserialize, Clone, Debug)]
221pub struct Variable {
222 pub memory_type: VariableMemoryType,
223 pub data_type: VariableDataType,
224 pub address: u32,
225}
226
227#[derive(Serialize, Deserialize, Clone, Debug)]
228pub struct Offset {
229 pub offset: i32,
230 pub origin: i32,
231}
232
233#[derive(Debug)]
234pub enum OffsetType {
235 Internal(i32),
236 External(String),
237}
238
239#[derive(Serialize, Deserialize, Clone, Debug)]
240pub enum VariableMemoryType {
241 InternalConstant,
243 LongtermAccess,
245 RandomAccess,
247 Unknown(u8),
248}
249
250impl VariableMemoryType {
251 fn new(value: u8) -> VariableMemoryType {
252 match value {
253 0 => VariableMemoryType::InternalConstant,
254 1 => VariableMemoryType::LongtermAccess,
255 2 => VariableMemoryType::RandomAccess,
256 _ => VariableMemoryType::Unknown(value),
257 }
258 }
259}
260
261#[derive(Serialize, Deserialize, Clone, Debug)]
262pub enum VariableDataType {
263 Int,
265 Float,
267 Bool,
269 Unknown(u8),
270}
271
272impl VariableDataType {
273 fn new(value: u8) -> VariableDataType {
274 match value {
275 0 => VariableDataType::Int,
276 1 => VariableDataType::Float,
277 2 => VariableDataType::Bool,
278 _ => VariableDataType::Unknown(value),
279 }
280 }
281}
282
283#[derive(Serialize, Deserialize, Clone, Debug)]
284pub enum Requirement {
285 CharacterExists,
286 AnimationEnd,
287 AnimationHasLooped,
288 OnGround,
289 InAir,
290 HoldingALedge,
291 OnAPassableFloor,
292 Comparison,
293 BoolIsTrue,
294 FacingRight,
295 FacingLeft,
296 HitboxConnects,
297 TouchingAFloorWallOrCeiling,
298 IsThrowingSomeone,
299 ButtonTap,
300 EnteringOrIsInHitLag,
301 ArticleExists,
302 IsOversteppingAnEdge,
303 HasAFloorBelowThePlayer,
304 ChangeInAirGroundState,
305 ArticleAvailable,
306 CurrentTriggeredStatusID,
307 HoldingItem,
308 HoldingItemOfType,
309 LightItemIsInGrabRange,
310 HeavyItemIsInGrabRange,
311 ItemOfTypeIsInGrabbingRange,
312 TurningWithItem,
313 InWater,
314 RollADie,
315 SubactionExists,
316 ButtonMashingOrStatusExpiredSleepBuryFreeze,
317 IsNotInDamagingLens,
318 ButtonPress,
319 ButtonRelease,
320 ButtonHeld,
321 ButtonNotPressed,
322 StickDirectionPressed,
323 StickDirectionNotPressed,
324 IsBeingThrownBySomeone1,
325 IsBeingThrownBySomeone2,
326 HasntTethered3Times,
327 HasPassedOverAnEdgeForward,
328 HasPassedOverAnEdgeBackward,
329 IsHoldingSomeoneInGrab,
330 HitboxHasConnected,
331 PickUpItem,
332 SDIInput,
334 ShieldInputPress,
336 ShieldInputHeld,
338 TauntInputPress,
340 TauntInputHeld,
342 HitByCapeEffect,
343 ThreadIsNull,
345 Always,
346 InWalljump,
347 InWallCling,
348 InFootstoolRange,
349 IsFallingOrHitDown,
350 HasSmashBall,
351 CanPickupAnotherItem,
352 FSmashShortcut,
353 TapJumpOn,
354 Unknown(u32),
355}
356
357impl Argument {
358 fn new(value: u32) -> Argument {
359 let flip = value >> 31 == 1;
360 let ty = match value & 0xFFFF {
361 0x0000 => Requirement::CharacterExists,
362 0x0001 => Requirement::AnimationEnd,
363 0x0002 => Requirement::AnimationHasLooped,
364 0x0003 => Requirement::OnGround,
365 0x0004 => Requirement::InAir,
366 0x0005 => Requirement::HoldingALedge,
367 0x0006 => Requirement::OnAPassableFloor,
368 0x0007 => Requirement::Comparison,
369 0x0008 => Requirement::BoolIsTrue,
370 0x0009 => Requirement::FacingRight,
371 0x000A => Requirement::FacingLeft,
372 0x000B => Requirement::HitboxConnects,
373 0x000C => Requirement::TouchingAFloorWallOrCeiling,
374 0x000D => Requirement::IsThrowingSomeone,
375 0x000F => Requirement::ButtonTap,
376 0x0014 => Requirement::EnteringOrIsInHitLag,
377 0x0015 => Requirement::ArticleExists,
378 0x0016 => Requirement::IsOversteppingAnEdge,
379 0x0017 => Requirement::HasAFloorBelowThePlayer,
380 0x001B => Requirement::ChangeInAirGroundState,
381 0x001C => Requirement::ArticleAvailable,
382 0x001D => Requirement::CurrentTriggeredStatusID,
383 0x001F => Requirement::HoldingItem,
384 0x0020 => Requirement::HoldingItemOfType,
385 0x0021 => Requirement::LightItemIsInGrabRange,
386 0x0022 => Requirement::HeavyItemIsInGrabRange,
387 0x0023 => Requirement::ItemOfTypeIsInGrabbingRange,
388 0x0024 => Requirement::TurningWithItem,
389 0x002A => Requirement::InWater,
390 0x002B => Requirement::RollADie,
391 0x002C => Requirement::SubactionExists,
392 0x002E => Requirement::ButtonMashingOrStatusExpiredSleepBuryFreeze,
393 0x002F => Requirement::IsNotInDamagingLens,
394 0x0030 => Requirement::ButtonPress,
395 0x0031 => Requirement::ButtonRelease,
396 0x0032 => Requirement::ButtonHeld,
397 0x0033 => Requirement::ButtonNotPressed,
398 0x0034 => Requirement::StickDirectionPressed,
399 0x0035 => Requirement::StickDirectionNotPressed,
400 0x0037 => Requirement::IsBeingThrownBySomeone1,
401 0x0038 => Requirement::IsBeingThrownBySomeone2,
402 0x0039 => Requirement::HasntTethered3Times,
403 0x003a => Requirement::HasPassedOverAnEdgeForward,
404 0x003b => Requirement::HasPassedOverAnEdgeBackward,
405 0x003c => Requirement::IsHoldingSomeoneInGrab,
406 0x003d => Requirement::HitboxHasConnected,
407 0x0047 => Requirement::PickUpItem,
408 0x004C => Requirement::HitByCapeEffect,
409 0x004D => Requirement::SDIInput,
410 0x004E => Requirement::ShieldInputPress,
411 0x004f => Requirement::ShieldInputHeld,
412 0x0050 => Requirement::TauntInputPress,
413 0x0051 => Requirement::TauntInputHeld,
414 0x0060 => Requirement::ThreadIsNull,
415 0x00FF => Requirement::Always,
416 0x2711 => Requirement::InWalljump,
417 0x2712 => Requirement::InWallCling,
418 0x2713 => Requirement::InFootstoolRange,
419 0x2716 => Requirement::IsFallingOrHitDown,
420 0x2717 => Requirement::HasSmashBall,
421 0x2719 => Requirement::CanPickupAnotherItem,
422 0x271D => Requirement::FSmashShortcut,
423 0x2725 => Requirement::TapJumpOn,
424 v => Requirement::Unknown(v),
425 };
426 Argument::Requirement { ty, flip }
427 }
428}