use byteorder::{BigEndian, ReadBytesExt};
use crate::wii_memory::WiiMemory;
pub(crate) fn scripts(parent_data: &[u8], offset_data: &[u8], num: usize, wii_memory: &WiiMemory) -> Vec<Script> {
let mut result = vec!();
for i in 0..num {
let offset = (&offset_data[i * 4..]).read_u32::<BigEndian>().unwrap();
result.push(new_script(parent_data, offset, wii_memory));
}
result
}
pub(crate) fn fragment_scripts(parent_data: &[u8], known_scripts: &[&[Script]], ignore_origins: &[i32], wii_memory: &WiiMemory) -> Vec<Script> {
let mut fragments: Vec<Script> = vec!();
for scripts in known_scripts.iter() {
for script in scripts.iter() {
for event in &script.events {
let mut found_offset = None;
if event.namespace == 0x00 && (event.code == 0x07 || event.code == 0x09) {
if let Some(Argument::Offset (Offset { offset, origin })) = event.arguments.get(0) {
if !ignore_origins.contains(origin) {
found_offset = Some(*offset);
}
}
if let Some(Argument::Value (offset)) = event.arguments.get(0) {
found_offset = Some(*offset);
}
}
if event.namespace == 0x0D && event.code == 0x00 {
if let Some(Argument::Offset (Offset { offset, origin })) = event.arguments.get(1) {
if !ignore_origins.contains(origin) {
found_offset = Some(*offset);
}
}
}
if let Some(offset) = found_offset {
let mut is_action = false;
'outer: for check_scripts in known_scripts.iter() {
for check_script in check_scripts.iter() {
if check_script.offset == offset {
is_action = true;
break 'outer;
}
}
}
let already_added = fragments.iter().any(|x| x.offset == offset);
if !is_action && !already_added {
fragments.push(new_script(parent_data, offset as u32, wii_memory));
}
}
}
}
}
if fragments.len() > 0 {
let mut all = known_scripts.to_vec();
all.push(&fragments);
fragments.extend(fragment_scripts(parent_data, &all, ignore_origins, wii_memory));
}
fragments
}
pub fn new_script(parent_data: &[u8], offset: u32, wii_memory: &WiiMemory) -> Script {
let buffer = if offset == 0 || offset as i32 == -1 {
return Script { events: vec!(), offset: offset as i32 }
} else if offset > 0 && offset < (parent_data.len() as u32) {
&parent_data[offset as usize ..]
} else if offset < 8000_0000 {
return Script { events: vec!(), offset: offset as i32 }
} else {
wii_memory.buffer_from(offset as usize)
};
let mut events = vec!();
let mut event_offset = 0;
loop {
let namespace = buffer[event_offset as usize];
let code = buffer[event_offset as usize + 1];
let num_arguments = buffer[event_offset as usize + 2];
let unk1 = buffer[event_offset as usize + 3];
let raw_id = (&buffer[event_offset as usize ..]).read_u32::<BigEndian>().unwrap();
if code == 0 && namespace == 0 {
break
}
if raw_id != 0xFADEF00D && raw_id != 0xFADE0D8A {
let argument_offset = (&buffer[event_offset as usize + 4 ..]).read_u32::<BigEndian>().unwrap();
let argument_buffer = if argument_offset as usize >= parent_data.len() {
wii_memory.buffer_from(argument_offset as usize)
} else {
&parent_data[argument_offset as usize..]
};
let arguments = arguments(argument_buffer, argument_offset, num_arguments as usize);
events.push(Event {
namespace,
code,
unk1,
arguments,
});
}
event_offset += EVENT_SIZE as u32;
}
Script { events, offset: offset as i32 }
}
fn arguments(buffer: &[u8], origin: u32, num_arguments: usize) -> Vec<Argument> {
let mut arguments = vec!();
for i in 0..num_arguments as i32 {
let argument_offset = i * ARGUMENT_SIZE as i32;
if argument_offset + 8 > buffer.len() as i32 {
error!("Script argument parsing tried to read out of bounds via offset {} into buffer of size {}", argument_offset, buffer.len());
break;
}
let ty = (&buffer[argument_offset as usize ..]).read_i32::<BigEndian>().unwrap();
let data = (&buffer[argument_offset as usize + 4 ..]).read_i32::<BigEndian>().unwrap();
let argument = match ty {
0 => Argument::Value (data),
1 => Argument::Scalar (data as f32 / 60000.0),
2 => Argument::Offset (Offset { offset: data, origin: origin as i32 + argument_offset + 4}),
3 => Argument::Bool (data == 1),
4 => Argument::File (data),
5 => {
let data = data as u32;
let memory_type = ((data & 0xF0000000) >> 28) as u8;
let data_type = ((data & 0x0F000000) >> 24) as u8;
let address = (data & 0x00FFFFFF) as u32;
let memory_type = VariableMemoryType::new(memory_type);
let data_type = VariableDataType::new(data_type);
Argument::Variable (Variable { memory_type, data_type, address })
}
6 => Requirement::new(data as u32),
_ => Argument::Unknown (ty, data),
};
arguments.push(argument);
}
arguments
}
#[derive(Clone, Debug)]
pub struct Script {
pub events: Vec<Event>,
pub offset: i32,
}
const EVENT_SIZE: usize = 0x8;
#[derive(Serialize, Clone, Debug)]
pub struct Event {
pub namespace: u8,
pub code: u8,
pub unk1: u8,
pub arguments: Vec<Argument>,
}
impl Event {
pub fn raw_id(&self) -> u32 {
let num_args = self.arguments.len();
assert!(num_args < 0x100);
(self.namespace as u32) << 24 | (self.code as u32) << 16 | (num_args as u32) << 8
}
}
const ARGUMENT_SIZE: usize = 0x8;
#[derive(Serialize, Clone, Debug)]
pub enum Argument {
Value (i32),
Scalar (f32),
Offset (Offset),
Bool (bool),
File (i32),
Variable (Variable),
Requirement { flip: bool, ty: Requirement },
Unknown (i32, i32)
}
#[derive(Serialize, Clone, Debug)]
pub struct Variable {
pub memory_type: VariableMemoryType,
pub data_type: VariableDataType,
pub address: u32,
}
#[derive(Serialize, Clone, Debug)]
pub struct Offset {
pub offset: i32,
pub origin: i32,
}
#[derive(Debug)]
pub enum OffsetType {
Internal (i32),
External (String, ),
}
#[derive(Serialize, Clone, Debug)]
pub enum VariableMemoryType {
InternalConstant,
LongtermAccess,
RandomAccess,
Unknown (u8),
}
impl VariableMemoryType {
fn new(value: u8) -> VariableMemoryType {
match value {
0 => VariableMemoryType::InternalConstant,
1 => VariableMemoryType::LongtermAccess,
2 => VariableMemoryType::RandomAccess,
_ => VariableMemoryType::Unknown (value),
}
}
}
#[derive(Serialize, Clone, Debug)]
pub enum VariableDataType {
Int,
Float,
Bool,
Unknown (u8)
}
impl VariableDataType {
fn new(value: u8) -> VariableDataType {
match value {
0 => VariableDataType::Int,
1 => VariableDataType::Float,
2 => VariableDataType::Bool,
_ => VariableDataType::Unknown (value),
}
}
}
#[derive(Serialize, Clone, Debug)]
pub enum Requirement {
CharacterExists,
AnimationEnd,
AnimationHasLooped,
OnGround,
InAir,
HoldingALedge,
OnAPassableFloor,
Comparison,
BoolIsTrue,
FacingRight,
FacingLeft,
HitboxConnects,
TouchingAFloorWallOrCeiling,
IsThrowingSomeone,
ButtonTap,
EnteringOrIsInHitLag,
ArticleExists,
HasAFloorBelowThePlayer,
ChangeInAirGroundState,
ArticleAvailable,
HoldingItem,
HoldingItemOfType,
LightItemIsInGrabRange,
HeavyItemIsInGrabRange,
ItemOfTypeIsInGrabbingRange,
TurningWithItem,
InWater,
RollADie,
SubactionExists,
ButtonMashingOrStatusExpiredSleepBuryFreeze,
IsNotInDamagingLens,
ButtonPress,
ButtonRelease,
ButtonPressed,
ButtonNotPressed,
StickDirectionPressed,
StickDirectionNotPressed,
IsBeingThrownBySomeone1,
IsBeingThrownBySomeone2,
HasntTethered3Times,
HasPassedOverAnEdgeForward,
HasPassedOverAnEdgeBackward,
IsHoldingSomeoneInGrab,
HitboxHasConnected,
PickUpItem,
HitByCapeEffect,
Always,
InWalljump,
InWallCling,
InFootstoolRange,
IsFallingOrHitDown,
HasSmashBall,
CanPickupAnotherItem,
FSmashShortcut,
TapJumpOn,
Unknown (u32)
}
impl Requirement {
fn new(value: u32) -> Argument {
let flip = value >> 31 == 1;
let ty = match value & 0xFFFF {
0x0000 => Requirement::CharacterExists,
0x0001 => Requirement::AnimationEnd,
0x0002 => Requirement::AnimationHasLooped,
0x0003 => Requirement::OnGround,
0x0004 => Requirement::InAir,
0x0005 => Requirement::HoldingALedge,
0x0006 => Requirement::OnAPassableFloor,
0x0007 => Requirement::Comparison,
0x0008 => Requirement::BoolIsTrue,
0x0009 => Requirement::FacingRight,
0x000A => Requirement::FacingLeft,
0x000B => Requirement::HitboxConnects,
0x000C => Requirement::TouchingAFloorWallOrCeiling,
0x000D => Requirement::IsThrowingSomeone,
0x000F => Requirement::ButtonTap,
0x0014 => Requirement::EnteringOrIsInHitLag,
0x0015 => Requirement::ArticleExists,
0x0017 => Requirement::HasAFloorBelowThePlayer,
0x001B => Requirement::ChangeInAirGroundState,
0x001C => Requirement::ArticleAvailable,
0x001F => Requirement::HoldingItem,
0x0020 => Requirement::HoldingItemOfType,
0x0021 => Requirement::LightItemIsInGrabRange,
0x0022 => Requirement::HeavyItemIsInGrabRange,
0x0023 => Requirement::ItemOfTypeIsInGrabbingRange,
0x0024 => Requirement::TurningWithItem,
0x002A => Requirement::InWater,
0x002B => Requirement::RollADie,
0x002C => Requirement::SubactionExists,
0x002E => Requirement::ButtonMashingOrStatusExpiredSleepBuryFreeze,
0x002F => Requirement::IsNotInDamagingLens,
0x0030 => Requirement::ButtonPress,
0x0031 => Requirement::ButtonRelease,
0x0032 => Requirement::ButtonPressed,
0x0033 => Requirement::ButtonNotPressed,
0x0034 => Requirement::StickDirectionPressed,
0x0035 => Requirement::StickDirectionNotPressed,
0x0037 => Requirement::IsBeingThrownBySomeone1,
0x0038 => Requirement::IsBeingThrownBySomeone2,
0x0039 => Requirement::HasntTethered3Times,
0x003a => Requirement::HasPassedOverAnEdgeForward,
0x003b => Requirement::HasPassedOverAnEdgeBackward,
0x003c => Requirement::IsHoldingSomeoneInGrab,
0x003d => Requirement::HitboxHasConnected,
0x0047 => Requirement::PickUpItem,
0x004C => Requirement::HitByCapeEffect,
0x00FF => Requirement::Always,
0x2711 => Requirement::InWalljump,
0x2712 => Requirement::InWallCling,
0x2713 => Requirement::InFootstoolRange,
0x2716 => Requirement::IsFallingOrHitDown,
0x2717 => Requirement::HasSmashBall,
0x2719 => Requirement::CanPickupAnotherItem,
0x271D => Requirement::FSmashShortcut,
0x2725 => Requirement::TapJumpOn,
v => Requirement::Unknown (v),
};
Argument::Requirement { ty, flip }
}
}