diff --git a/ryujinx_build.ps1 b/ryujinx_build.ps1 index 8d602a1..ba78ce7 100644 --- a/ryujinx_build.ps1 +++ b/ryujinx_build.ps1 @@ -1,5 +1,8 @@ $IP=(Test-Connection -ComputerName (hostname) -Count 1 | Select -ExpandProperty IPV4Address).IPAddressToString cargo skyline build --release --features layout_arc_from_file +if (($lastexitcode -ne 0)) { + exit $lastexitcode +} # Set up symlinks $RYUJINX_LAYOUT_ARC_PATH="C:\Users\Josh\AppData\Roaming\Ryujinx\sdcard\ultimate\TrainingModpack\layout.arc" diff --git a/src/common/button_config.rs b/src/common/button_config.rs index 8c6508d..2da2745 100644 --- a/src/common/button_config.rs +++ b/src/common/button_config.rs @@ -46,6 +46,113 @@ pub fn button_mapping( } } +pub fn name_to_font_glyph(button: ButtonConfig, style: ControllerStyle) -> Option<u16> { + let is_gcc = style == ControllerStyle::GCController; + Some(match button { + ButtonConfig::A => 0xE0E0, + ButtonConfig::B => 0xE0E1, + ButtonConfig::X => { + if is_gcc { + 0xE206 + } else { + 0xE0E2 + } + } + ButtonConfig::Y => { + if is_gcc { + 0xE207 + } else { + 0xE0E3 + } + } + ButtonConfig::L => { + if is_gcc { + return None; + } else { + 0xE0E4 + } + } + ButtonConfig::R => { + if is_gcc { + 0xE205 + } else { + 0xE0E5 + } + } + ButtonConfig::ZL => { + if is_gcc { + 0xE204 + } else { + 0xE0E6 + } + } + ButtonConfig::ZR => { + if is_gcc { + 0xE208 + } else { + 0xE0E7 + } + } + ButtonConfig::DPAD_UP => { + if is_gcc { + 0xE209 + } else { + 0xE0EB + } + } + ButtonConfig::DPAD_DOWN => { + if is_gcc { + 0xE20A + } else { + 0xE0EC + } + } + ButtonConfig::DPAD_LEFT => { + if is_gcc { + 0xE20B + } else { + 0xE0ED + } + } + ButtonConfig::DPAD_RIGHT => { + if is_gcc { + 0xE20C + } else { + 0xE0EE + } + } + ButtonConfig::PLUS => { + if is_gcc { + 0xE20D + } else { + 0xE0EF + } + } + ButtonConfig::MINUS => { + if is_gcc { + return None; + } else { + 0xE0F0 + } + } + ButtonConfig::LSTICK => { + if is_gcc { + return None; + } else { + 0xE104 + } + } + ButtonConfig::RSTICK => { + if is_gcc { + return None; + } else { + 0xE105 + } + } + _ => return None, + }) +} + #[derive(Debug, EnumIter, PartialEq, Eq, Hash, Copy, Clone)] pub enum ButtonCombo { OpenMenu, diff --git a/src/common/mod.rs b/src/common/mod.rs index e916512..17b8a51 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -41,14 +41,25 @@ pub fn is_emulator() -> bool { } pub fn get_module_accessor(fighter_id: FighterId) -> *mut app::BattleObjectModuleAccessor { + try_get_module_accessor(fighter_id).unwrap() +} + +pub fn try_get_module_accessor( + fighter_id: FighterId, +) -> Option<*mut app::BattleObjectModuleAccessor> { let entry_id_int = fighter_id as i32; let entry_id = app::FighterEntryID(entry_id_int); unsafe { let mgr = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager); let fighter_entry = FighterManager::get_fighter_entry(mgr, entry_id) as *mut app::FighterEntry; + if fighter_entry.is_null() { + return None; + } let current_fighter_id = FighterEntry::current_fighter_id(fighter_entry); - app::sv_battle_object::module_accessor(current_fighter_id as u32) + Some(app::sv_battle_object::module_accessor( + current_fighter_id as u32, + )) } } diff --git a/src/static/layout.arc b/src/static/layout.arc index a3c97e1..130ff5d 100644 Binary files a/src/static/layout.arc and b/src/static/layout.arc differ diff --git a/src/training/input_log.rs b/src/training/input_log.rs new file mode 100644 index 0000000..6d030ee --- /dev/null +++ b/src/training/input_log.rs @@ -0,0 +1,23 @@ +use std::collections::VecDeque; + +use crate::common::input::*; +use lazy_static::lazy_static; +use parking_lot::Mutex; + +lazy_static! { + pub static ref P1_INPUT_MAPPINGS: Mutex<VecDeque<MappedInputs>> = Mutex::new(VecDeque::new()); +} + +// TODO: how many +const NUM_INPUTS: usize = 120; + +pub fn handle_final_input_mapping(player_idx: i32, out: *mut MappedInputs) { + unsafe { + if player_idx == 0 { + let mut mappings = P1_INPUT_MAPPINGS.lock(); + + mappings.push_front(*out); + mappings.truncate(NUM_INPUTS); + } + } +} diff --git a/src/training/input_record.rs b/src/training/input_record.rs index b59b9e2..9bba2a2 100644 --- a/src/training/input_record.rs +++ b/src/training/input_record.rs @@ -1,7 +1,9 @@ use crate::common::button_config; use crate::common::consts::{FighterId, HitstunPlayback, OnOff, RecordTrigger}; use crate::common::input::*; -use crate::common::{get_module_accessor, is_in_hitstun, is_in_shieldstun, MENU}; +use crate::common::{ + get_module_accessor, is_in_hitstun, is_in_shieldstun, try_get_module_accessor, MENU, +}; use crate::training::mash; use crate::training::ui::notifications::{clear_notifications, color_notification}; use lazy_static::lazy_static; @@ -434,7 +436,14 @@ unsafe fn set_cpu_controls(p_data: *mut *mut u8) { should_mash_playback(); } - let cpu_module_accessor = get_module_accessor(FighterId::CPU); + let cpu_module_accessor = try_get_module_accessor(FighterId::CPU); + + // Sometimes we can try to grab their module accessor before they are valid? + if cpu_module_accessor.is_none() { + return; + } + let cpu_module_accessor = cpu_module_accessor.unwrap(); + if INPUT_RECORD == Pause { match LOCKOUT_FRAME.cmp(&0) { Ordering::Greater => LOCKOUT_FRAME -= 1, diff --git a/src/training/input_recording/mod.rs b/src/training/input_recording/mod.rs deleted file mode 100644 index dd3a638..0000000 --- a/src/training/input_recording/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[macro_use] -pub mod structures; diff --git a/src/training/input_recording/structures.rs b/src/training/input_recording/structures.rs deleted file mode 100644 index ed21a04..0000000 --- a/src/training/input_recording/structures.rs +++ /dev/null @@ -1,307 +0,0 @@ -#![allow(dead_code)] // TODO: Yeah don't do this -use crate::common::events::smash_version; -use crate::common::release::CURRENT_VERSION; -use crate::training::save_states::SavedState; -use bitflags::bitflags; -use training_mod_consts::TrainingModpackMenu; - -use crate::default_save_state; -use crate::training::character_specific::steve; -use crate::training::charge::ChargeState; -use crate::training::save_states::SaveState::NoAction; - -// Need to define necesary structures here. Probably should move to consts or something. Realistically, should be in skyline smash prob tho. - -// Final final controls used for controlmodule -// can I actually derive these? -#[derive(Debug, Copy, Clone)] -#[repr(C)] -pub struct ControlModuleInternal { - pub vtable: *mut u8, - pub controller_index: i32, - pub buttons: Buttons, - pub stick_x: f32, - pub stick_y: f32, - pub padding: [f32; 2], - pub unk: [u32; 8], - pub clamped_lstick_x: f32, - pub clamped_lstick_y: f32, - pub padding2: [f32; 2], - pub clamped_rstick_x: f32, - pub clamped_rstick_y: f32, -} - -impl ControlModuleInternal { - pub fn _clear(&mut self) { - // Try to nullify controls so we can't control player 1 during recording - self.stick_x = 0.0; - self.stick_y = 0.0; - self.buttons = Buttons::empty(); - self.clamped_lstick_x = 0.0; - self.clamped_lstick_y = 0.0; - self.clamped_rstick_x = 0.0; - self.clamped_rstick_y = 0.0; - } -} - -#[derive(Debug, Copy, Clone)] -#[repr(C)] -pub struct ControlModuleStored { - // Custom type for saving only necessary controls/not saving vtable - pub buttons: Buttons, - pub stick_x: f32, - pub stick_y: f32, - pub padding: [f32; 2], - pub unk: [u32; 8], - pub clamped_lstick_x: f32, - pub clamped_lstick_y: f32, - pub padding2: [f32; 2], - pub clamped_rstick_x: f32, - pub clamped_rstick_y: f32, -} - -// Re-ordered bitfield the game uses for buttons - TODO: Is this a problem? What's the original order? -pub type ButtonBitfield = i32; // may need to actually implement? Not for now though - -/// Controller style declaring what kind of controller is being used -#[derive(PartialEq, Eq, Debug, Copy, Clone)] -#[repr(u32)] -pub enum ControllerStyle { - Handheld = 0x1, - DualJoycon = 0x2, - LeftJoycon = 0x3, - RightJoycon = 0x4, - ProController = 0x5, - DebugPad = 0x6, // probably - GCController = 0x7, -} - -#[repr(C)] -pub struct AutorepeatInfo { - field: [u8; 0x18], -} - -// Can map any of these over any button - what does this mean? -#[repr(u8)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum InputKind { - Attack = 0x0, - Special = 0x1, - Jump = 0x2, - Guard = 0x3, - Grab = 0x4, - SmashAttack = 0x5, - AppealHi = 0xA, - AppealS = 0xB, - AppealLw = 0xC, - Unset = 0xD, -} - -// 0x50 Byte struct containing the information for controller mappings -#[derive(Debug)] -#[repr(C)] -pub struct ControllerMapping { - pub gc_l: InputKind, - pub gc_r: InputKind, - pub gc_z: InputKind, - pub gc_dup: InputKind, - pub gc_dlr: InputKind, - pub gc_ddown: InputKind, - pub gc_a: InputKind, - pub gc_b: InputKind, - pub gc_cstick: InputKind, - pub gc_y: InputKind, - pub gc_x: InputKind, - pub gc_rumble: bool, - pub gc_absmash: bool, - pub gc_tapjump: bool, - pub gc_sensitivity: u8, - // 0xF - pub pro_l: InputKind, - pub pro_r: InputKind, - pub pro_zl: InputKind, - pub pro_zr: InputKind, - pub pro_dup: InputKind, - pub pro_dlr: InputKind, - pub pro_ddown: InputKind, - pub pro_a: InputKind, - pub pro_b: InputKind, - pub pro_cstick: InputKind, - pub pro_x: InputKind, - pub pro_y: InputKind, - pub pro_rumble: bool, - pub pro_absmash: bool, - pub pro_tapjump: bool, - pub pro_sensitivity: u8, - // 0x1F - pub joy_shoulder: InputKind, - pub joy_zshoulder: InputKind, - pub joy_sl: InputKind, - pub joy_sr: InputKind, - pub joy_up: InputKind, - pub joy_right: InputKind, - pub joy_left: InputKind, - pub joy_down: InputKind, - pub joy_rumble: bool, - pub joy_absmash: bool, - pub joy_tapjump: bool, - pub joy_sensitivity: u8, - // 0x2B - pub _2b: u8, - pub _2c: u8, - pub _2d: u8, - pub _2e: u8, - pub _2f: u8, - pub _30: u8, - pub _31: u8, - pub _32: u8, - pub is_absmash: bool, - pub _34: [u8; 0x1C], -} - -//type Buttons = u32; // may need to actually implement (like label and such)? Not for now though -bitflags! { - pub struct Buttons: u32 { - const ATTACK = 0x1; - const SPECIAL = 0x2; - const JUMP = 0x4; - const GUARD = 0x8; - const CATCH = 0x10; - const SMASH = 0x20; - const JUMP_MINI = 0x40; - const CSTICK_ON = 0x80; - const STOCK_SHARE = 0x100; - const ATTACK_RAW = 0x200; - const APPEAL_HI = 0x400; - const SPECIAL_RAW = 0x800; - const APPEAL_LW = 0x1000; - const APPEAL_SL = 0x2000; - const APPEAL_SR = 0x4000; - const FLICK_JUMP = 0x8000; - const GUARD_HOLD = 0x10000; - const SPECIAL_RAW2 = 0x20000; - } -} - -// Controller class used internally by the game -#[repr(C)] -pub struct Controller { - pub vtable: *const u64, - pub current_buttons: ButtonBitfield, - pub previous_buttons: ButtonBitfield, - pub left_stick_x: f32, - pub left_stick_y: f32, - pub left_trigger: f32, - pub _left_padding: u32, - pub right_stick_x: f32, - pub right_stick_y: f32, - pub right_trigger: f32, - pub _right_padding: u32, - pub gyro: [f32; 4], - pub button_timespan: AutorepeatInfo, - pub lstick_timespan: AutorepeatInfo, - pub rstick_timespan: AutorepeatInfo, - pub just_down: ButtonBitfield, - pub just_release: ButtonBitfield, - pub autorepeat_keys: u32, - pub autorepeat_threshold: u32, - pub autorepeat_initial_press_threshold: u32, - pub style: ControllerStyle, - pub controller_id: u32, - pub primary_controller_color1: u32, - pub primary_controller_color2: u32, - pub secondary_controller_color1: u32, - pub secondary_controller_color2: u32, - pub led_pattern: u8, - pub button_autorepeat_initial_press: bool, - pub lstick_autorepeat_initial_press: bool, - pub rstick_autorepeat_initial_press: bool, - pub is_valid_controller: bool, - pub _x_b9: [u8; 2], - pub is_connected: bool, - pub is_left_connected: bool, - pub is_right_connected: bool, - pub is_wired: bool, - pub is_left_wired: bool, - pub is_right_wired: bool, - pub _x_c1: [u8; 3], - pub npad_number: u32, - pub _x_c8: [u8; 8], -} - -// SomeControllerStruct used in hooked function - need to ask blujay what this is again -#[repr(C)] -pub struct SomeControllerStruct { - padding: [u8; 0x10], - controller: &'static mut Controller, -} - -// Define struct used for final controller inputs -#[derive(Copy, Clone)] -#[repr(C)] -pub struct MappedInputs { - pub buttons: Buttons, - pub lstick_x: i8, - pub lstick_y: i8, - pub rstick_x: i8, - pub rstick_y: i8, -} - -impl MappedInputs { - // pub needed? - pub fn default() -> MappedInputs { - MappedInputs { - buttons: Buttons::empty(), - lstick_x: 0, - lstick_y: 0, - rstick_x: 0, - rstick_y: 0, - } - } -} - -// Final Structure containing all input recording slots, menu options, and save states. -// 5 Input Recording Slots should be fine for now for most mix up scenarios -// When loading a "scenario", we want to load all menu options (with maybe overrides in the config?), load savestate(s), and load input recording slots. -// If we have submenus for input recording slots, we need to get that info as well, and we want to apply saved damage from save states to the menu. -// Damage range seems to be saved in menu for range of damage, so that's taken care of with menu. - -#[derive(Clone)] -#[repr(C)] -pub struct Scenario { - pub record_slots: Vec<Vec<MappedInputs>>, - pub starting_statuses: Vec<i32>, - pub menu: TrainingModpackMenu, - pub save_states: Vec<SavedState>, - pub player_char: i32, // fighter_kind - pub cpu_char: i32, // fighter_kind - pub stage: i32, // index of stage, but -1 = random - pub title: String, - pub description: String, - pub mod_version: String, - pub smash_version: String, - // depending on version, we need to modify newly added menu options, so that regardless of their defaults they reflect the previous version to minimize breakage of old scenarios - // we may also add more scenario parts to the struct in the future etc. - // pub screenshot: image???? - // datetime? - // author? - // mirroring? -} - -impl Scenario { - pub fn default() -> Scenario { - Scenario { - record_slots: vec![vec![MappedInputs::default(); 600]; 5], - starting_statuses: vec![0], - menu: crate::common::consts::DEFAULTS_MENU, - save_states: vec![default_save_state!(); 5], - player_char: 0, - cpu_char: 0, - stage: -1, // index of stage, but -1 = random/any stage - title: "Scenario Title".to_string(), - description: "Description...".to_string(), - mod_version: CURRENT_VERSION.to_string(), - smash_version: smash_version(), - } - } -} diff --git a/src/training/mod.rs b/src/training/mod.rs index 7f9eb24..f1a6a0b 100644 --- a/src/training/mod.rs +++ b/src/training/mod.rs @@ -34,6 +34,7 @@ mod character_specific; mod fast_fall; mod full_hop; pub mod input_delay; +mod input_log; mod input_record; mod mash; mod reset; @@ -712,15 +713,34 @@ unsafe fn handle_final_input_mapping( controller_struct: &mut SomeControllerStruct, arg: bool, ) { - // go through the original mapping function first + // Order of hooks here REALLY matters. Tread lightly + + // Go through the original mapping function first original!()(mappings, player_idx, out, controller_struct, arg); if !is_training_mode() { return; } + + // Grab button input requests from player + // Non-mutable pull button_config::handle_final_input_mapping(player_idx, controller_struct); + + // Grab menu inputs from player + // Non-mutable pull menu::handle_final_input_mapping(player_idx, controller_struct, out); + + // Check if we should apply hot reload configs + // Non-mutable pull dev_config::handle_final_input_mapping(player_idx, controller_struct); + + // Potentially apply input delay + // MUTATES controller state input_delay::handle_final_input_mapping(player_idx, out); + + input_log::handle_final_input_mapping(player_idx, out); + + // Potentially apply input recording, thus with delay + // MUTATES controller state input_record::handle_final_input_mapping(player_idx, out); } diff --git a/src/training/ui/input_log.rs b/src/training/ui/input_log.rs new file mode 100644 index 0000000..2e80935 --- /dev/null +++ b/src/training/ui/input_log.rs @@ -0,0 +1,69 @@ +use skyline::nn::ui2d::*; +use smash::ui2d::{SmashPane, SmashTextBox}; +use training_mod_consts::ButtonConfig; + +use crate::{ + common::{ + button_config::name_to_font_glyph, + input::Buttons, + menu::{P1_CONTROLLER_STYLE, QUICK_MENU_ACTIVE}, + }, + training::input_log::P1_INPUT_MAPPINGS, +}; + +macro_rules! log_parent_fmt { + ($x:ident) => { + format!("TrModInputLog{}", $x).as_str() + }; +} + +pub unsafe fn draw(root_pane: &Pane) { + let log_number = 0; + let log_pane = root_pane + .find_pane_by_name_recursive(log_parent_fmt!(log_number)) + .unwrap(); + + // TODO: And menu option for input log is on + log_pane.set_visible(!QUICK_MENU_ACTIVE); + + let logs_ptr = P1_INPUT_MAPPINGS.data_ptr(); + if logs_ptr.is_null() { + return; + } + let logs = &*logs_ptr; + let first_log = logs.front(); + if first_log.is_none() { + return; + } + let first_log = first_log.unwrap(); + + let p1_style_ptr = P1_CONTROLLER_STYLE.data_ptr(); + if p1_style_ptr.is_null() { + return; + } + + let input_pane = log_pane + .find_pane_by_name_recursive("InputTxt") + .unwrap() + .as_textbox(); + + input_pane.set_text_string("NONE"); + + if first_log.buttons.contains(Buttons::ATTACK) { + let potential_font_glyph = name_to_font_glyph(ButtonConfig::A, *p1_style_ptr); + if let Some(font_glyph) = potential_font_glyph { + input_pane.set_text_string(""); + + let it = input_pane.text_buf as *mut u16; + input_pane.text_len = 1; + *it = font_glyph; + *(it.add(1)) = 0x0; + } + } + + log_pane + .find_pane_by_name_recursive("FrameTxt") + .unwrap() + .as_textbox() + .set_text_string(format!("{}", logs.len()).as_str()); +} diff --git a/src/training/ui/mod.rs b/src/training/ui/mod.rs index e906910..50136a4 100644 --- a/src/training/ui/mod.rs +++ b/src/training/ui/mod.rs @@ -10,6 +10,7 @@ use crate::consts::LAYOUT_ARC_PATH; mod damage; mod display; +mod input_log; mod menu; pub mod notifications; @@ -38,6 +39,7 @@ pub unsafe fn handle_draw(layout: *mut Layout, draw_info: u64, cmd_buffer: u64) damage::draw(root_pane, &layout_name); if layout_name == "info_training" { + input_log::draw(root_pane); display::draw(root_pane); menu::draw(root_pane); }