brawllib_rs/
script.rs

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
19/// finds any scripts that are pointed to by Goto's and Subroutines but dont exist yet.
20pub(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 the event is a subroutine or goto
33                    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 the event is a CallEveryFrame or IndependentSubroutine
46                    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        // the fragment scripts may refer to their own fragment scripts
75        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            // end of script
111            break;
112        }
113
114        // PSA fills empty space with these bytes:
115        // const long FADEDATA = 0xFADE0D8A; // Constant for the tag FADE0D8A representing the end of useable space.
116        // const long FADEFOOD = 0xFADEF00D; // Constant for the tag FADEF00D representing empty, useable space.
117        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
189// Events are like lines of code in a script
190const 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    /// Known as IC in existing tools
242    InternalConstant,
243    /// Known as LA in existing tools
244    LongtermAccess,
245    /// Known as RA in existing tools
246    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    /// Known as Basic in existing tools
264    Int,
265    /// Known as Float in existing tools
266    Float,
267    /// Known as Bit in existing tools
268    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    /// PM Only
333    SDIInput,
334    /// PM Only
335    ShieldInputPress,
336    /// PM Only
337    ShieldInputHeld,
338    /// PM Only
339    TauntInputPress,
340    /// PM Only
341    TauntInputHeld,
342    HitByCapeEffect,
343    /// Independent Subroutine WiiRD code Only
344    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}