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);
     }