diff --git a/Cargo.toml b/Cargo.toml
index 64f050c..c73956a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,6 +15,7 @@ bitflags = "1.2.1"
 parking_lot = { version = "0.12.0", features = ["nightly"] }
 include-flate = "0.1.4"
 lazy_static = "1.4.0"
+modular-bitfield = "0.11.2"
 owo-colors = "2.1.0"
 once_cell = "1.12.0"
 paste = "1.0"
diff --git a/src/common/dev_config.rs b/src/common/dev_config.rs
index f65c72f..93bf98e 100644
--- a/src/common/dev_config.rs
+++ b/src/common/dev_config.rs
@@ -3,9 +3,9 @@ use std::fs;
 use lazy_static::lazy_static;
 use parking_lot::Mutex;
 use serde::Deserialize;
-use skyline::nn::hid::NpadGcState;
 use toml;
 
+use crate::common::input::*;
 use crate::consts::DEV_TOML_PATH;
 use crate::logging::info;
 
@@ -55,17 +55,9 @@ impl DevConfig {
     }
 }
 
-pub fn handle_get_npad_state(state: *mut NpadGcState, _controller_id: *const u32) {
-    let a_press = 1 << 0;
-    let l_press = 1 << 6;
-    let r_press = 1 << 7;
-    let buttons;
-    unsafe {
-        buttons = (*state).Buttons;
-    }
-
-    // Occurs on L+R+A
-    if (buttons & a_press > 0) && (buttons & l_press > 0) && (buttons & r_press > 0) {
+pub fn handle_final_input_mapping(player_idx: i32, controller_struct: &mut SomeControllerStruct) {
+    let current_buttons = controller_struct.controller.current_buttons;
+    if player_idx == 0 && current_buttons.l() && current_buttons.r() && current_buttons.a() {
         let mut dev_config = DEV_CONFIG.lock();
         *dev_config = DevConfig::load_from_toml();
     }
diff --git a/src/common/input.rs b/src/common/input.rs
new file mode 100644
index 0000000..6ca7dd4
--- /dev/null
+++ b/src/common/input.rs
@@ -0,0 +1,291 @@
+#![allow(dead_code)] // TODO: Yeah don't do this
+use bitflags::bitflags;
+use modular_bitfield::{bitfield, specifiers::*};
+
+// 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
+#[bitfield]
+#[derive(Debug, Default, Copy, Clone)]
+#[repr(C)]
+pub struct ButtonBitfield {
+    pub dpad_up: bool,
+    pub dpad_right: bool,
+    pub dpad_down: bool,
+    pub dpad_left: bool,
+    pub x: bool,
+    pub a: bool,
+    pub b: bool,
+    pub y: bool,
+    pub l: bool,
+    pub r: bool,
+    pub zl: bool,
+    pub zr: bool,
+    pub left_sl: bool,
+    pub left_sr: bool,
+    pub right_sl: bool,
+    pub right_sr: bool,
+    pub stick_l: bool,
+    pub stick_r: bool,
+    pub plus: bool,
+    pub minus: bool,
+    pub l_up: bool,
+    pub l_right: bool,
+    pub l_down: bool,
+    pub l_left: bool,
+    pub r_up: bool,
+    pub r_right: bool,
+    pub r_down: bool,
+    pub r_left: bool,
+    pub real_digital_l: bool,
+    pub real_digital_r: bool,
+    pub unused: B2,
+}
+
+/// Controller style declaring what kind of controller is being used
+#[derive(PartialEq, Eq, Default, Debug, Copy, Clone)]
+#[repr(u32)]
+pub enum ControllerStyle {
+    #[default]
+    Handheld = 0x1,
+    DualJoycon = 0x2,
+    LeftJoycon = 0x3,
+    RightJoycon = 0x4,
+    ProController = 0x5,
+    DebugPad = 0x6, // probably
+    GCController = 0x7,
+}
+
+#[derive(Debug, Default, Copy, Clone)]
+#[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, Copy, Clone)]
+#[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
+#[derive(Debug, Default, Copy, Clone)]
+#[repr(C)]
+pub struct Controller {
+    pub vtable: 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],
+    pub 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,
+        }
+    }
+}
diff --git a/src/common/menu.rs b/src/common/menu.rs
index ebfdcd8..35ae78c 100644
--- a/src/common/menu.rs
+++ b/src/common/menu.rs
@@ -2,7 +2,7 @@ use std::fs;
 
 use lazy_static::lazy_static;
 use parking_lot::Mutex;
-use skyline::nn::hid::{GetNpadStyleSet, NpadGcState};
+use skyline::nn::hid::GetNpadStyleSet;
 use training_mod_consts::MenuJsonStruct;
 
 use training_mod_tui::AppPage;
@@ -10,12 +10,12 @@ use training_mod_tui::AppPage;
 use crate::common::*;
 use crate::consts::MENU_OPTIONS_PATH;
 use crate::events::{Event, EVENT_QUEUE};
+use crate::input::*;
 use crate::logging::*;
 
 // This is a special frame counter that will tick on draw()
 // We'll count how long the menu has been open
 pub static mut FRAME_COUNTER: u32 = 0;
-const MENU_INPUT_WAIT_FRAMES: u32 = 30;
 const MENU_CLOSE_WAIT_FRAMES: u32 = 60;
 pub static mut QUICK_MENU_ACTIVE: bool = false;
 
@@ -76,177 +76,6 @@ pub fn spawn_menu() {
     }
 }
 
-pub struct ButtonPresses {
-    pub a: ButtonPress,
-    pub b: ButtonPress,
-    pub x: ButtonPress,
-    pub y: ButtonPress,
-    pub r: ButtonPress,
-    pub l: ButtonPress,
-    pub zr: ButtonPress,
-    pub zl: ButtonPress,
-    pub left: ButtonPress,
-    pub right: ButtonPress,
-    pub up: ButtonPress,
-    pub down: ButtonPress,
-}
-
-pub struct ButtonPress {
-    pub prev_frame_is_pressed: bool,
-    pub is_pressed: bool,
-    pub lockout_frames: usize,
-}
-
-impl ButtonPress {
-    pub fn read_press(&mut self) -> bool {
-        let is_pressed = self.is_pressed;
-        if self.is_pressed {
-            self.is_pressed = false;
-            if self.lockout_frames == 0 {
-                self.prev_frame_is_pressed = true;
-                self.lockout_frames = 10;
-                return true;
-            }
-        }
-
-        if self.lockout_frames > 0 {
-            self.lockout_frames -= 1;
-        }
-
-        self.prev_frame_is_pressed = is_pressed;
-        false
-    }
-}
-
-pub static mut BUTTON_PRESSES: ButtonPresses = ButtonPresses {
-    a: ButtonPress {
-        prev_frame_is_pressed: false,
-        is_pressed: false,
-        lockout_frames: 0,
-    },
-    b: ButtonPress {
-        prev_frame_is_pressed: false,
-        is_pressed: false,
-        lockout_frames: 0,
-    },
-    x: ButtonPress {
-        prev_frame_is_pressed: false,
-        is_pressed: false,
-        lockout_frames: 0,
-    },
-    y: ButtonPress {
-        prev_frame_is_pressed: false,
-        is_pressed: false,
-        lockout_frames: 0,
-    },
-    r: ButtonPress {
-        prev_frame_is_pressed: false,
-        is_pressed: false,
-        lockout_frames: 0,
-    },
-    l: ButtonPress {
-        prev_frame_is_pressed: false,
-        is_pressed: false,
-        lockout_frames: 0,
-    },
-    zr: ButtonPress {
-        prev_frame_is_pressed: false,
-        is_pressed: false,
-        lockout_frames: 0,
-    },
-    zl: ButtonPress {
-        prev_frame_is_pressed: false,
-        is_pressed: false,
-        lockout_frames: 0,
-    },
-    left: ButtonPress {
-        prev_frame_is_pressed: false,
-        is_pressed: false,
-        lockout_frames: 0,
-    },
-    right: ButtonPress {
-        prev_frame_is_pressed: false,
-        is_pressed: false,
-        lockout_frames: 0,
-    },
-    up: ButtonPress {
-        prev_frame_is_pressed: false,
-        is_pressed: false,
-        lockout_frames: 0,
-    },
-    down: ButtonPress {
-        prev_frame_is_pressed: false,
-        is_pressed: false,
-        lockout_frames: 0,
-    },
-};
-
-pub fn handle_get_npad_state(state: *mut NpadGcState, controller_id: *const u32) {
-    unsafe {
-        let update_count = (*state).updateCount;
-        let flags = (*state).Flags;
-        if QUICK_MENU_ACTIVE {
-            if (*state).Buttons & (1 << 0) > 0 {
-                BUTTON_PRESSES.a.is_pressed = true;
-            }
-            if (*state).Buttons & (1 << 1) > 0 {
-                BUTTON_PRESSES.b.is_pressed = true;
-            }
-            if (*state).Buttons & (1 << 2) > 0 {
-                BUTTON_PRESSES.x.is_pressed = true;
-            }
-            if (*state).Buttons & (1 << 3) > 0 {
-                BUTTON_PRESSES.y.is_pressed = true;
-            }
-            if (*state).Buttons & (1 << 6) > 0 {
-                BUTTON_PRESSES.l.is_pressed = true;
-            }
-            if (*state).Buttons & (1 << 7) > 0 {
-                BUTTON_PRESSES.r.is_pressed = true;
-            }
-            // Special case for frame-by-frame
-            if FRAME_COUNTER > MENU_INPUT_WAIT_FRAMES && (*state).Buttons & (1 << 8) > 0 {
-                BUTTON_PRESSES.zl.is_pressed = true;
-            }
-            if (*state).Buttons & (1 << 9) > 0 {
-                BUTTON_PRESSES.zr.is_pressed = true;
-            }
-            if (*state).Buttons & ((1 << 12) | (1 << 16)) > 0 {
-                BUTTON_PRESSES.left.is_pressed = true;
-            }
-            if (*state).Buttons & ((1 << 14) | (1 << 18)) > 0 {
-                BUTTON_PRESSES.right.is_pressed = true;
-            }
-            if (*state).Buttons & ((1 << 15) | (1 << 19)) > 0 {
-                BUTTON_PRESSES.down.is_pressed = true;
-            }
-            // Special case for "UP" in menu open button combo
-            if FRAME_COUNTER > MENU_INPUT_WAIT_FRAMES
-                && (*state).Buttons & ((1 << 13) | (1 << 17)) > 0
-            {
-                BUTTON_PRESSES.up.is_pressed = true;
-            }
-
-            // For digital triggers: these pressed 1/3 of the way mean we should consider a press
-            if controller_is_gcc(*controller_id) {
-                if (*state).LTrigger >= 0x2AAA {
-                    BUTTON_PRESSES.l.is_pressed = true;
-                }
-
-                if (*state).RTrigger >= 0x2AAA {
-                    BUTTON_PRESSES.r.is_pressed = true;
-                }
-            }
-
-            // If we're here, remove all other Npad presses...
-            // Should we exclude the home button?
-            (*state) = NpadGcState::default();
-            (*state).updateCount = update_count;
-            (*state).Flags = flags;
-        }
-    }
-}
-
 lazy_static! {
     pub static ref QUICK_MENU_APP: Mutex<training_mod_tui::App<'static>> = Mutex::new(
         training_mod_tui::App::new(unsafe { ui_menu(MENU) }, unsafe {
@@ -256,22 +85,26 @@ lazy_static! {
             )
         })
     );
+    pub static ref P1_CONTROLLER_STATE: Mutex<Controller> = Mutex::new(Controller::default());
 }
 
-pub unsafe fn controller_is_gcc(controller_id: u32) -> bool {
-    let style_set = GetNpadStyleSet(&controller_id as *const _);
-    (style_set.flags & (1 << 5)) > 0
-}
-
-pub unsafe fn p1_controller_is_gcc() -> bool {
-    let p1_controller_id = crate::training::input_delay::p1_controller_id();
-    controller_is_gcc(p1_controller_id)
+pub fn handle_final_input_mapping(
+    player_idx: i32,
+    controller_struct: &mut SomeControllerStruct,
+    out: *mut MappedInputs,
+) {
+    unsafe {
+        if QUICK_MENU_ACTIVE && player_idx == 0 {
+            *P1_CONTROLLER_STATE.lock() = *controller_struct.controller;
+            // If we're here, remove all other presses
+            *out = MappedInputs::default();
+        }
+    }
 }
 
 pub unsafe fn quick_menu_loop() {
     loop {
         std::thread::sleep(std::time::Duration::from_secs(10));
-        let button_presses = &mut BUTTON_PRESSES;
         let mut received_input = true;
         loop {
             std::thread::sleep(std::time::Duration::from_millis(16));
@@ -291,15 +124,16 @@ pub unsafe fn quick_menu_loop() {
                 continue;
             }
 
-            let is_gcc = p1_controller_is_gcc();
+            let p1_controller_state = *P1_CONTROLLER_STATE.data_ptr();
+            let is_gcc = p1_controller_state.style == ControllerStyle::GCController;
+            let button_presses = p1_controller_state.just_down;
 
             let app = &mut *QUICK_MENU_APP.data_ptr();
-            button_presses.a.read_press().then(|| {
+            button_presses.a().then(|| {
                 app.on_a();
                 received_input = true;
             });
-            let b_press = &mut button_presses.b;
-            b_press.read_press().then(|| {
+            button_presses.b().then(|| {
                 received_input = true;
                 if app.page != AppPage::SUBMENU {
                     app.on_b()
@@ -312,21 +146,21 @@ pub unsafe fn quick_menu_loop() {
                     EVENT_QUEUE.push(Event::menu_open(menu_json));
                 }
             });
-            button_presses.x.read_press().then(|| {
+            button_presses.x().then(|| {
                 app.save_defaults();
                 received_input = true;
             });
-            button_presses.y.read_press().then(|| {
+            button_presses.y().then(|| {
                 app.reset_all_submenus();
                 received_input = true;
             });
-            button_presses.l.read_press().then(|| {
+            button_presses.l().then(|| {
                 if is_gcc {
                     app.previous_tab();
                 }
                 received_input = true;
             });
-            button_presses.r.read_press().then(|| {
+            button_presses.r().then(|| {
                 if is_gcc {
                     app.next_tab();
                 } else {
@@ -334,13 +168,13 @@ pub unsafe fn quick_menu_loop() {
                 }
                 received_input = true;
             });
-            button_presses.zl.read_press().then(|| {
+            button_presses.zl().then(|| {
                 if !is_gcc {
                     app.previous_tab();
                 }
                 received_input = true;
             });
-            button_presses.zr.read_press().then(|| {
+            button_presses.zr().then(|| {
                 if !is_gcc {
                     app.next_tab();
                 } else {
@@ -348,19 +182,19 @@ pub unsafe fn quick_menu_loop() {
                 }
                 received_input = true;
             });
-            button_presses.left.read_press().then(|| {
+            button_presses.l_left().then(|| {
                 app.on_left();
                 received_input = true;
             });
-            button_presses.right.read_press().then(|| {
+            button_presses.l_right().then(|| {
                 app.on_right();
                 received_input = true;
             });
-            button_presses.up.read_press().then(|| {
+            button_presses.l_up().then(|| {
                 app.on_up();
                 received_input = true;
             });
-            button_presses.down.read_press().then(|| {
+            button_presses.l_down().then(|| {
                 app.on_down();
                 received_input = true;
             });
diff --git a/src/common/mod.rs b/src/common/mod.rs
index b38f8e5..ce7e947 100644
--- a/src/common/mod.rs
+++ b/src/common/mod.rs
@@ -10,6 +10,7 @@ pub mod button_config;
 pub mod consts;
 pub mod dev_config;
 pub mod events;
+pub mod input;
 pub mod menu;
 pub mod raygun_printer;
 pub mod release;
diff --git a/src/training/input_delay.rs b/src/training/input_delay.rs
index 2a4fcac..5277d59 100644
--- a/src/training/input_delay.rs
+++ b/src/training/input_delay.rs
@@ -1,51 +1,30 @@
 use std::collections::VecDeque;
 
+use crate::common::input::*;
 use lazy_static::lazy_static;
 use parking_lot::Mutex;
-use skyline::nn::hid::{GetNpadStyleSet, NpadGcState};
 
 use crate::common::MENU;
 
 lazy_static! {
-    static ref P1_DELAYED_NPAD_STATES: Mutex<VecDeque<NpadGcState>> = Mutex::new(VecDeque::new());
+    static ref P1_DELAYED_INPUT_MAPPINGS: Mutex<VecDeque<MappedInputs>> =
+        Mutex::new(VecDeque::new());
 }
 
-pub unsafe fn p1_controller_id() -> u32 {
-    let min_controller_id = (0..8)
-        .filter(|i| GetNpadStyleSet(i as *const _).flags != 0)
-        .min()
-        .unwrap_or(0);
-
-    let handheld_id = 0x20;
-    if GetNpadStyleSet(&handheld_id as *const _).flags != 0 {
-        handheld_id
-    } else {
-        min_controller_id
-    }
-}
-
-pub fn handle_get_npad_state(state: *mut NpadGcState, controller_id: *const u32) {
+pub fn handle_final_input_mapping(player_idx: i32, out: *mut MappedInputs) {
     unsafe {
-        if crate::common::is_training_mode() && *controller_id == p1_controller_id() {
-            let mut delayed_states = P1_DELAYED_NPAD_STATES.lock();
-            let actual_state = *state;
+        if player_idx == 0 {
+            let mut delayed_mappings = P1_DELAYED_INPUT_MAPPINGS.lock();
+            let actual_mapping = *out;
 
-            if delayed_states.len() < MENU.input_delay.into_delay() as usize {
-                let update_count = (*state).updateCount;
-                let attributes = (*state).Flags;
-                *state = NpadGcState::default();
-                (*state).updateCount = update_count;
-                (*state).Flags = attributes;
-            } else if let Some(delayed_state) = delayed_states.back() {
-                let update_count = (*state).updateCount;
-                let attributes = (*state).Flags;
-                *state = *delayed_state;
-                (*state).updateCount = update_count;
-                (*state).Flags = attributes;
+            if delayed_mappings.len() < MENU.input_delay.into_delay() as usize {
+                *out = MappedInputs::default();
+            } else if let Some(delayed_mapping) = delayed_mappings.back() {
+                *out = *delayed_mapping;
             }
 
-            delayed_states.push_front(actual_state);
-            delayed_states.truncate(MENU.input_delay.into_delay() as usize);
+            delayed_mappings.push_front(actual_mapping);
+            delayed_mappings.truncate(MENU.input_delay.into_delay() as usize);
         }
     }
 }
diff --git a/src/training/input_record.rs b/src/training/input_record.rs
index 44558a9..a9cced4 100644
--- a/src/training/input_record.rs
+++ b/src/training/input_record.rs
@@ -1,7 +1,7 @@
 use crate::common::button_config;
 use crate::common::consts::{FighterId, HitstunPlayback, OnOff};
+use crate::common::input::*;
 use crate::common::{get_module_accessor, is_in_hitstun, is_in_shieldstun, MENU};
-use crate::training::input_recording::structures::*;
 use crate::training::mash;
 use crate::training::ui::notifications::{clear_notifications, color_notification};
 use lazy_static::lazy_static;
@@ -372,18 +372,7 @@ pub unsafe fn is_end_standby() -> bool {
     lstick_movement || rstick_movement || buttons_pressed
 }
 
-static FIM_OFFSET: usize = 0x17504a0;
-// TODO: Should we define all of our offsets in one file? Should at least be a good start for changing to be based on ASM instructions
-#[skyline::hook(offset = FIM_OFFSET)]
-unsafe fn handle_final_input_mapping(
-    mappings: *mut ControllerMapping,
-    player_idx: i32, // Is this the player index, or plugged in controller index? Need to check, assuming player for now - is this 0 indexed or 1?
-    out: *mut MappedInputs,
-    controller_struct: &mut SomeControllerStruct,
-    arg: bool,
-) {
-    // go through the original mapping function first
-    original!()(mappings, player_idx, out, controller_struct, arg);
+pub unsafe fn handle_final_input_mapping(player_idx: i32, out: *mut MappedInputs) {
     if player_idx == 0 {
         // if player 1
         if INPUT_RECORD == Record {
@@ -524,5 +513,5 @@ extern "C" {
 }
 
 pub fn init() {
-    skyline::install_hooks!(set_cpu_controls, handle_final_input_mapping,);
+    skyline::install_hooks!(set_cpu_controls);
 }
diff --git a/src/training/mod.rs b/src/training/mod.rs
index d9d702e..e6bd3fc 100644
--- a/src/training/mod.rs
+++ b/src/training/mod.rs
@@ -1,5 +1,4 @@
 use skyline::hooks::{getRegionAddress, InlineCtx, Region};
-use skyline::nn::hid::*;
 use skyline::nn::ro::LookupSymbol;
 use smash::app::{self, enSEType, lua_bind::*, utility};
 use smash::lib::lua_const::*;
@@ -11,6 +10,7 @@ use crate::common::{
     is_training_mode, menu, FIGHTER_MANAGER_ADDR, ITEM_MANAGER_ADDR, STAGE_MANAGER_ADDR,
 };
 use crate::hitbox_visualizer;
+use crate::input::*;
 use crate::logging::*;
 use crate::training::character_specific::items;
 
@@ -35,7 +35,6 @@ mod fast_fall;
 mod full_hop;
 pub mod input_delay;
 mod input_record;
-mod input_recording;
 mod mash;
 mod reset;
 pub mod save_states;
@@ -615,25 +614,30 @@ pub unsafe fn handle_reused_ui(
     original!()(fighter_data, param_2)
 }
 
-#[allow(improper_ctypes)]
-extern "C" {
-    fn add_nn_hid_hook(callback: fn(*mut NpadGcState, *const u32));
+static FIM_OFFSET: usize = 0x17504a0;
+// TODO: Should we define all of our offsets in one file? Should at least be a good start for changing to be based on ASM instructions
+#[skyline::hook(offset = FIM_OFFSET)]
+unsafe fn handle_final_input_mapping(
+    mappings: *mut ControllerMapping,
+    player_idx: i32, // Is this the player index, or plugged in controller index? Need to check, assuming player for now - is this 0 indexed or 1?
+    out: *mut MappedInputs,
+    controller_struct: &mut SomeControllerStruct,
+    arg: bool,
+) {
+    // go through the original mapping function first
+    original!()(mappings, player_idx, out, controller_struct, arg);
+    if !is_training_mode() {
+        return;
+    }
+    menu::handle_final_input_mapping(player_idx, controller_struct, out);
+    dev_config::handle_final_input_mapping(player_idx, controller_struct);
+    input_delay::handle_final_input_mapping(player_idx, out);
+    input_record::handle_final_input_mapping(player_idx, out);
 }
 
 pub fn training_mods() {
     info!("Applying training mods.");
 
-    // Input Mods
-    unsafe {
-        if let Some(_f) = (add_nn_hid_hook as *const ()).as_ref() {
-            add_nn_hid_hook(input_delay::handle_get_npad_state);
-            add_nn_hid_hook(menu::handle_get_npad_state);
-            add_nn_hid_hook(dev_config::handle_get_npad_state);
-        } else {
-            panic!("The NN-HID hook plugin could not be found and is required to add NRO hooks. Make sure libnn_hid_hook.nro is installed.");
-        }
-    }
-
     unsafe {
         LookupSymbol(
             &mut FIGHTER_MANAGER_ADDR,
@@ -705,6 +709,8 @@ pub fn training_mods() {
         handle_star_ko,
         // Clatter
         clatter::hook_start_clatter,
+        // Input
+        handle_final_input_mapping
     );
 
     combo::init();
diff --git a/src/training/ui/menu.rs b/src/training/ui/menu.rs
index 16df712..c9bc76d 100644
--- a/src/training/ui/menu.rs
+++ b/src/training/ui/menu.rs
@@ -6,7 +6,7 @@ use smash::ui2d::{SmashPane, SmashTextBox};
 use training_mod_tui::gauge::GaugeState;
 use training_mod_tui::{App, AppPage};
 
-use crate::{common, common::menu::QUICK_MENU_ACTIVE};
+use crate::{common, common::menu::QUICK_MENU_ACTIVE, input::*};
 
 pub static NUM_MENU_TEXT_OPTIONS: usize = 33;
 pub static _NUM_MENU_TABS: usize = 3;
@@ -405,7 +405,8 @@ pub unsafe fn draw(root_pane: &Pane) {
     };
     let tab_titles = [prev_tab, tab_selected, next_tab].map(|idx| app_tabs[idx]);
 
-    let is_gcc = common::menu::p1_controller_is_gcc();
+    let is_gcc =
+        (*common::menu::P1_CONTROLLER_STATE.data_ptr()).style == ControllerStyle::GCController;
     let button_mapping = if is_gcc {
         GCC_BUTTON_MAPPING.clone()
     } else {