diff --git a/crates/smb2_practice_mod/Cargo.toml b/crates/smb2_practice_mod/Cargo.toml index 80a2ef27..c730a75b 100644 --- a/crates/smb2_practice_mod/Cargo.toml +++ b/crates/smb2_practice_mod/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] arrayvec = { version = "0.7.6", default-features = false } -critical-section = "1.1.3" +critical-section = { version = "1.1.3", features = ["restore-state-u32"] } num_enum = { version = "0.7.3", default-features = false } once_cell = { version = "1.20.2", default-features = false, features = [ "critical-section", diff --git a/crates/smb2_practice_mod/src/app.rs b/crates/smb2_practice_mod/src/app.rs index ed290c0e..e0df7ef7 100644 --- a/crates/smb2_practice_mod/src/app.rs +++ b/crates/smb2_practice_mod/src/app.rs @@ -21,7 +21,7 @@ use crate::{ pad::Pad, pref::{BoolPref, Pref}, }, - utils::{libsavestate::LibSaveState, misc::with_mutex, relutil::ModuleId}, + utils::{libsavestate::LibSaveState, relutil::ModuleId}, }; static APP_CONTEXT: Lazy>> = @@ -31,22 +31,6 @@ pub fn with_app(f: impl FnOnce(&mut AppContext) -> T) -> T { critical_section::with(|cs| f(&mut APP_CONTEXT.borrow_ref_mut(cs))) } -struct Globals { - process_inputs_hook: ProcessInputsHook, - draw_debug_text_hook: DrawDebugTextHook, - oslink_hook: OSLinkHook, - game_ready_init_hook: GameReadyInitHook, - game_play_tick_hook: GamePlayTickHook, -} - -static GLOBALS: Mutex = Mutex::new(Globals { - process_inputs_hook: ProcessInputsHook::new(), - draw_debug_text_hook: DrawDebugTextHook::new(), - oslink_hook: OSLinkHook::new(), - game_ready_init_hook: GameReadyInitHook::new(), - game_play_tick_hook: GamePlayTickHook::new(), -}); - #[derive(Default)] pub struct AppContext { pub pad: Pad, @@ -72,32 +56,35 @@ pub struct AppContext { pub il_mark: IlMark, pub camera: Camera, pub stage_edits: StageEdits, - pub _hide: Hide, + pub hide: Hide, pub sfx: Sfx, pub scratch: Scratch, pub validate: Validate, pub draw: Draw, pub pref: Pref, + + process_inputs_hook: ProcessInputsHook, + draw_debug_text_hook: DrawDebugTextHook, + oslink_hook: OSLinkHook, + game_ready_init_hook: GameReadyInitHook, + game_play_tick_hook: GamePlayTickHook, } impl AppContext { fn new() -> Self { crate::systems::heap::HEAP.init(); - with_mutex(&GLOBALS, |cx| { - cx.process_inputs_hook.hook(); - cx.draw_debug_text_hook.hook(); - cx.oslink_hook.hook(); - }); - Self::default() + let app_context = Self::default(); + app_context.process_inputs_hook.hook(); + app_context.draw_debug_text_hook.hook(); + app_context.oslink_hook.hook(); + app_context } } // Tick functions hook hook!(ProcessInputsHook => (), mkb::process_inputs, || { - with_mutex(&GLOBALS, |cx| { - cx.process_inputs_hook.call(); - }); + with_app(|cx| cx.process_inputs_hook.clone()).call(); with_app(|cx| { cx.pad.tick(); @@ -140,28 +127,25 @@ hook!(ProcessInputsHook => (), mkb::process_inputs, || { // Draw functions hook hook!(DrawDebugTextHook => (), mkb::draw_debugtext, || { - with_mutex(&GLOBALS, |cx| { - cx.draw_debug_text_hook.call(); - }); - - // When the game is paused, screenshot the game's draw buffer before we draw our custom UI - // elements. The original screenshot call is nopped. - unsafe { - if mkb::g_pause_status == 1 { - mkb::take_pausemenu_screenshot( - &raw mut mkb::fullscreen_texture_buf as *mut _, - 0, - 0, - (*mkb::current_render_mode).fbWidth as i16, - (*mkb::current_render_mode).efbHeight as i16, - mkb::GX_TF_RGB5A3, - ); - } - }; + with_app(|cx| cx.draw_debug_text_hook.clone()).call(); with_app(|cx| { - cx.draw.predraw(); + // When the game is paused, screenshot the game's draw buffer before we draw our custom UI + // elements. The original screenshot call is nopped. + unsafe { + if mkb::g_pause_status == 1 { + mkb::take_pausemenu_screenshot( + &raw mut mkb::fullscreen_texture_buf as *mut _, + 0, + 0, + (*mkb::current_render_mode).fbWidth as i16, + (*mkb::current_render_mode).efbHeight as i16, + mkb::GX_TF_RGB5A3, + ); + } + }; + cx.draw.predraw(); cx.timer.draw(&cx.pref, &cx.freecam, &cx.validate); cx.iw.draw(&cx.pref, &cx.freecam); cx.il_battle.draw(&cx.pref, &cx.freecam, &cx.binds, &cx.pad); @@ -192,32 +176,30 @@ hook!(OSLinkHook, mkb::OSLink, |rel_buffer, bss_buffer| { - with_mutex(&GLOBALS, |cx| { - let ret = cx.oslink_hook.call(rel_buffer, bss_buffer); + let ret = with_app(|cx| cx.oslink_hook.clone()).call(rel_buffer, bss_buffer); + + with_app(|cx| { let module_id = ModuleId::try_from(unsafe{*rel_buffer}.info.id); if let Ok(ModuleId::MainGame) = module_id { cx.game_ready_init_hook.hook(); cx.game_play_tick_hook.hook(); } - ret - }) + }); + + ret }); hook!(GameReadyInitHook => (), mkb::smd_game_ready_init, || { with_app(|cx| { cx.stage_edits.on_game_ready_init(&cx.pref); cx.ball_color.switch_monkey(&cx.pref); - }); - with_mutex(&GLOBALS, |cx| { - cx.game_ready_init_hook.call(); - }); + cx.game_ready_init_hook.clone() + }).call(); }); hook!(GamePlayTickHook => (), mkb::smd_game_play_tick, || { - with_mutex(&GLOBALS, |cx| { - cx.game_play_tick_hook.call(); - }); + with_app(|cx| cx.game_play_tick_hook.clone()).call(); with_app(|cx| { cx.validate.validate_run(&cx.pref, &cx.lib_save_state, &cx.menu_impl, &cx.physics, &cx.pad); diff --git a/crates/smb2_practice_mod/src/lib.rs b/crates/smb2_practice_mod/src/lib.rs index 42e8aa9f..3ac911d0 100644 --- a/crates/smb2_practice_mod/src/lib.rs +++ b/crates/smb2_practice_mod/src/lib.rs @@ -22,6 +22,8 @@ use core::{ use critical_section::RawRestoreState; +use mkb::mkb; + // Disable unless we're buliding with our custom target explicitly, so rust-analyzer doesn't trip up // on conflicting panic handler implementations with std #[cfg(target_arch = "powerpc")] @@ -99,12 +101,18 @@ unsafe fn perform_assembly_patches() { ); } -// We're never running multiple "threads" or hooking interrupts, so establishing a critical section -// is a no-op +// We disable interrupts while basically any of our code runs, even though we never hook anything +// called in interrupt context as far as I know. It still seems to help prevent random crashes +// somehow. +// I guess another possibility is for an interrupt to trigger at a point in our code with high +// stack usage, using enough additional stack to cause a stack overflow. struct MyCriticalSection; critical_section::set_impl!(MyCriticalSection); unsafe impl critical_section::Impl for MyCriticalSection { - unsafe fn acquire() -> RawRestoreState {} - - unsafe fn release(_token: RawRestoreState) {} + unsafe fn acquire() -> RawRestoreState { + mkb::OSDisableInterrupts() as u32 + } + unsafe fn release(token: RawRestoreState) { + mkb::OSRestoreInterrupts(token as i32); + } } diff --git a/crates/smb2_practice_mod/src/mods/ballcolor.rs b/crates/smb2_practice_mod/src/mods/ballcolor.rs index ccfada73..57405f59 100644 --- a/crates/smb2_practice_mod/src/mods/ballcolor.rs +++ b/crates/smb2_practice_mod/src/mods/ballcolor.rs @@ -1,4 +1,3 @@ -use critical_section::Mutex; use num_enum::TryFromPrimitive; use mkb::mkb; @@ -10,24 +9,13 @@ use crate::{ draw, pref::{Pref, U8Pref}, }, - utils::misc::with_mutex, }; -struct Globals { - load_stagedef_hook: LoadStagedefHook, -} - -static GLOBALS: Mutex = Mutex::new(Globals { - load_stagedef_hook: LoadStagedefHook::new(), -}); - pub const COLOR_MIN: u8 = 0; pub const COLOR_MAX: u8 = 0xff; hook!(LoadStagedefHook, stage_id: u32 => (), mkb::load_stagedef, |stage_id| { - with_mutex(&GLOBALS, |cx| { - cx.load_stagedef_hook.call(stage_id); - }); + with_app(|cx| cx.ball_color.load_stagedef_hook.clone()).call(stage_id); with_app(|cx| { cx.ball_color.switch_monkey(&cx.pref); }); @@ -64,17 +52,19 @@ pub struct BallColor { rainbow: u32, default_color: mkb::GXColor, current_color: mkb::GXColor, + + load_stagedef_hook: LoadStagedefHook, } impl Default for BallColor { fn default() -> Self { - with_mutex(&GLOBALS, |cx| { - cx.load_stagedef_hook.hook(); - }); + let load_stagedef_hook = LoadStagedefHook::new(); + load_stagedef_hook.hook(); Self { rainbow: 0, default_color: unsafe { *(0x80472a34 as *const mkb::GXColor) }, current_color: Default::default(), + load_stagedef_hook, } } } diff --git a/crates/smb2_practice_mod/src/mods/cmseg.rs b/crates/smb2_practice_mod/src/mods/cmseg.rs index 51e4804b..3377e991 100644 --- a/crates/smb2_practice_mod/src/mods/cmseg.rs +++ b/crates/smb2_practice_mod/src/mods/cmseg.rs @@ -1,6 +1,5 @@ use core::{ffi::c_long, ptr::null_mut}; -use critical_section::Mutex; use mkb::mkb; use num_enum::TryFromPrimitive; @@ -11,23 +10,13 @@ use crate::{ draw, pref::{BoolPref, Pref, U8Pref}, }, - utils::{misc::with_mutex, timerdisp}, + utils::timerdisp, }; use super::freecam::Freecam; -struct Globals { - reset_cm_course_hook: ResetCmCourseHook, -} - -static GLOBALS: Mutex = Mutex::new(Globals { - reset_cm_course_hook: ResetCmCourseHook::new(), -}); - hook!(ResetCmCourseHook => (), mkb::g_reset_cm_course, || { - with_mutex(&GLOBALS, |cx| { - cx.reset_cm_course_hook.call(); - }); + with_app(|cx| cx.cm_seg.reset_cm_course_hook.clone()).call(); with_app(|cx| { cx.cm_seg.on_reset_cm_course(); }); @@ -82,13 +71,14 @@ pub struct CmSeg { overwritten_opcode: mkb::CourseCommandOpcode, overwritten_starting_monkeys: i8, pbs: [u32; 14], + + reset_cm_course_hook: ResetCmCourseHook, } impl Default for CmSeg { fn default() -> Self { - with_mutex(&GLOBALS, |cx| { - cx.reset_cm_course_hook.hook(); - }); + let reset_cm_course_hook = ResetCmCourseHook::new(); + reset_cm_course_hook.hook(); Self { state: Default::default(), seg_request: Default::default(), @@ -99,6 +89,8 @@ impl Default for CmSeg { overwritten_opcode: 0, overwritten_starting_monkeys: 0, pbs: [u32::MAX; 14], + + reset_cm_course_hook, } } } diff --git a/crates/smb2_practice_mod/src/mods/dpad.rs b/crates/smb2_practice_mod/src/mods/dpad.rs index e5f90263..e5f61729 100644 --- a/crates/smb2_practice_mod/src/mods/dpad.rs +++ b/crates/smb2_practice_mod/src/mods/dpad.rs @@ -2,15 +2,11 @@ use crate::{ app::with_app, hook, systems::pref::{BoolPref, Pref}, - utils::misc::with_mutex, }; -use critical_section::Mutex; use mkb::mkb; hook!(PadReadHook, statuses: *mut mkb::PADStatus => u32, mkb::PADRead, |statuses| { - let ret = with_mutex(&GLOBALS, |cx| { - cx.pad_read_hook.call(statuses) - }); + let ret = with_app(|cx| cx.dpad.pad_read_hook.clone()).call(statuses); with_app(|cx| { cx.dpad.on_pad_read(statuses, &cx.pref); }); @@ -18,30 +14,24 @@ hook!(PadReadHook, statuses: *mut mkb::PADStatus => u32, mkb::PADRead, |statuses }); hook!(CreateSpeedSpritesHook, x: f32, y: f32 => (), mkb::create_speed_sprites, |x, y| { - with_mutex(&GLOBALS, |cx| { - cx.create_speed_sprites_hook.call(x + 5.0, y); - }); + with_app(|cx| cx.dpad.create_speed_sprites_hook.clone()).call(x + 5.0, y); }); -struct Globals { +pub struct Dpad { pad_read_hook: PadReadHook, create_speed_sprites_hook: CreateSpeedSpritesHook, } -static GLOBALS: Mutex = Mutex::new(Globals { - pad_read_hook: PadReadHook::new(), - create_speed_sprites_hook: CreateSpeedSpritesHook::new(), -}); - -pub struct Dpad {} - impl Default for Dpad { fn default() -> Self { - with_mutex(&GLOBALS, |cx| { - cx.pad_read_hook.hook(); - cx.create_speed_sprites_hook.hook(); - }); - Self {} + let pad_read_hook = PadReadHook::new(); + pad_read_hook.hook(); + let create_speed_sprites_hook = CreateSpeedSpritesHook::new(); + create_speed_sprites_hook.hook(); + Self { + pad_read_hook, + create_speed_sprites_hook, + } } } diff --git a/crates/smb2_practice_mod/src/mods/fallout.rs b/crates/smb2_practice_mod/src/mods/fallout.rs index 5fd475cb..4f694a4d 100644 --- a/crates/smb2_practice_mod/src/mods/fallout.rs +++ b/crates/smb2_practice_mod/src/mods/fallout.rs @@ -1,11 +1,6 @@ -use crate::{ - app::with_app, - hook, - utils::misc::with_mutex, -}; +use crate::{app::with_app, hook}; use core::ffi::c_int; -use critical_section::Mutex; use mkb::mkb; use num_enum::TryFromPrimitive; @@ -33,29 +28,15 @@ enum TimerType { CountUpwards, } -struct Globals { - did_ball_fallout_hook: DidBallFalloutHook, - load_stagedef_hook: LoadStagedefHook, -} - -static GLOBALS: Mutex = Mutex::new(Globals { - did_ball_fallout_hook: DidBallFalloutHook::new(), - load_stagedef_hook: LoadStagedefHook::new(), -}); - hook!(DidBallFalloutHook, ball: *mut mkb::Ball => c_int, mkb::did_ball_fallout, |ball| { - let ret = with_mutex(&GLOBALS, |cx| { - cx.did_ball_fallout_hook.call(ball) - }); + let ret = with_app(|cx| cx.fallout.did_ball_fallout_hook.clone()).call(ball); with_app(|cx| { cx.fallout.on_did_ball_fallout(ball, ret, &cx.pref) }) }); hook!(LoadStagedefHook, stage_id: u32 => (), mkb::load_stagedef, |stage_id| { - with_mutex(&GLOBALS, |cx| { - cx.load_stagedef_hook.call(stage_id); - }); + with_app(|cx| cx.fallout.load_stagedef_hook.clone()).call(stage_id); with_app(|cx| { // Set the current default values before loading the stagedef unsafe { @@ -75,18 +56,25 @@ pub struct Fallout { timeover_condition: usize, // Timeover at 0.00 timer_increment: usize, // Add -1 to timer each frame toggled_freecam: bool, + + did_ball_fallout_hook: DidBallFalloutHook, + load_stagedef_hook: LoadStagedefHook, } impl Default for Fallout { fn default() -> Self { - with_mutex(&GLOBALS, |cx| { - cx.did_ball_fallout_hook.hook(); - cx.load_stagedef_hook.hook(); - }); + let did_ball_fallout_hook = DidBallFalloutHook::new(); + did_ball_fallout_hook.hook(); + let load_stagedef_hook = LoadStagedefHook::new(); + load_stagedef_hook.hook(); + Self { timeover_condition: 0x2c000000, timer_increment: 0x3803ffff, toggled_freecam: false, + + did_ball_fallout_hook, + load_stagedef_hook, } } } diff --git a/crates/smb2_practice_mod/src/mods/freecam.rs b/crates/smb2_practice_mod/src/mods/freecam.rs index d82f88c6..e20affbb 100644 --- a/crates/smb2_practice_mod/src/mods/freecam.rs +++ b/crates/smb2_practice_mod/src/mods/freecam.rs @@ -1,13 +1,11 @@ -use critical_section::Mutex; use mkb::mkb; -use once_cell::sync::Lazy; use crate::app::with_app; use crate::systems::binds::Binds; use crate::systems::draw::{self, Draw, NotifyDuration}; use crate::systems::pad::{self, Pad, Prio}; use crate::systems::pref::{self, BoolPref, Pref, U8Pref}; -use crate::utils::misc::{for_c_arr, with_mutex}; +use crate::utils::misc::for_c_arr; use crate::utils::patch; use crate::{fmt, hook}; use mkb::{S16Vec, Vec}; @@ -18,20 +16,10 @@ pub const TURBO_SPEED_MAX: u8 = 200; hook!(EventCameraTickHook => (), mkb::event_camera_tick, || { with_app(|cx| { cx.freecam.on_event_camera_tick(&mut cx.pref); - }); - - with_mutex(&GLOBALS, |cx| { - cx.event_camera_tick_hook.call(); - }); + cx.freecam.event_camera_tick_hook.clone() + }).call(); }); -#[derive(Default)] -struct Globals { - event_camera_tick_hook: EventCameraTickHook, -} - -static GLOBALS: Lazy> = Lazy::new(|| Mutex::new(Globals::default())); - struct Context<'a> { pref: &'a mut Pref, pad: &'a Pad, @@ -45,6 +33,8 @@ pub struct Freecam { enabled_this_tick: bool, enabled_prev_tick: bool, + + event_camera_tick_hook: EventCameraTickHook, } impl Default for Freecam { @@ -52,12 +42,14 @@ impl Default for Freecam { unsafe { patch::write_branch_bl(0x8028353c as *mut _, Self::call_camera_func_hook as *mut _); } - with_mutex(&GLOBALS, |cx| cx.event_camera_tick_hook.hook()); + let event_camera_tick_hook = EventCameraTickHook::new(); + event_camera_tick_hook.hook(); Self { eye: Default::default(), rot: Default::default(), enabled_this_tick: false, enabled_prev_tick: false, + event_camera_tick_hook, } } } diff --git a/crates/smb2_practice_mod/src/mods/hide.rs b/crates/smb2_practice_mod/src/mods/hide.rs index 02d858a3..7cc6225f 100644 --- a/crates/smb2_practice_mod/src/mods/hide.rs +++ b/crates/smb2_practice_mod/src/mods/hide.rs @@ -1,124 +1,95 @@ -use critical_section::Mutex; use mkb::mkb; use crate::{ app::with_app, hook, systems::pref::{BoolPref, Pref}, - utils::{misc::with_mutex, patch}, + utils::patch, }; -// BG -struct Globals { - draw_bg_hook: DrawBgHook, - clear_hook: ClearHook, - draw_sprite_hook: DrawSpriteHook, - draw_minimap_hook: DrawMinimapHook, - draw_stage_hook: DrawStageHook, - draw_ball_hook: DrawBallHook, - draw_items_hook: DrawItemsHook, - draw_stobjs_hook: DrawStobjsHook, - draw_effects_hook: DrawEffectsHook, -} - -static GLOBALS: Mutex = Mutex::new(Globals { - draw_bg_hook: DrawBgHook::new(), - clear_hook: ClearHook::new(), - draw_sprite_hook: DrawSpriteHook::new(), - draw_minimap_hook: DrawMinimapHook::new(), - draw_stage_hook: DrawStageHook::new(), - draw_ball_hook: DrawBallHook::new(), - draw_items_hook: DrawItemsHook::new(), - draw_stobjs_hook: DrawStobjsHook::new(), - draw_effects_hook: DrawEffectsHook::new(), -}); - hook!(DrawBgHook => (), mkb::g_draw_bg, || { - let should_hide = with_app(|cx| { - should_hide_bg(&cx.pref) - }); - - with_mutex(&GLOBALS, |cx| { - if !should_hide { - cx.draw_bg_hook.call(); - } + let (should_hide, hook) = with_app(|cx| { + (should_hide_bg(&cx.pref), cx.hide.draw_bg_hook.clone()) }); + if !should_hide { + hook.call(); + } }); hook!(ClearHook => (), mkb::g_set_clear_color, || { - let should_hide = with_app(|cx| { - should_hide_bg(&cx.pref) + let (should_hide, hook) = with_app(|cx| { + (should_hide_bg(&cx.pref), cx.hide.clear_hook.clone()) }); - with_mutex(&GLOBALS, |cx| { - if should_hide { - unsafe { - let backup_color = mkb::g_some_theme_color; - let backup_override_r = mkb::g_override_clear_r; - let backup_override_g = mkb::g_override_clear_g; - let backup_override_b = mkb::g_override_clear_b; - - mkb::g_some_theme_color = mkb::GXColor {r: 0, g: 0, b: 0, a: 0xff}; - mkb::g_override_clear_r = 0; - mkb::g_override_clear_g = 0; - mkb::g_override_clear_b = 0; - - cx.clear_hook.call(); - - mkb::g_some_theme_color = backup_color; - mkb::g_override_clear_r = backup_override_r; - mkb::g_override_clear_g = backup_override_g; - mkb::g_override_clear_b = backup_override_b; - } - } else { - cx.clear_hook.call(); + if should_hide { + unsafe { + let backup_color = mkb::g_some_theme_color; + let backup_override_r = mkb::g_override_clear_r; + let backup_override_g = mkb::g_override_clear_g; + let backup_override_b = mkb::g_override_clear_b; + + mkb::g_some_theme_color = mkb::GXColor {r: 0, g: 0, b: 0, a: 0xff}; + mkb::g_override_clear_r = 0; + mkb::g_override_clear_g = 0; + mkb::g_override_clear_b = 0; + + hook.call(); + + mkb::g_some_theme_color = backup_color; + mkb::g_override_clear_r = backup_override_r; + mkb::g_override_clear_g = backup_override_g; + mkb::g_override_clear_b = backup_override_b; } - }); + } else { + hook.call(); + } }); // HUD hook!(DrawSpriteHook, sprite: *mut mkb::Sprite => (), mkb::draw_sprite, |sprite| { - let (hide_hud, freecam_hide) = with_app(|cx| { - (cx.pref.get_bool(BoolPref::HideHud), cx.freecam.should_hide_hud(&cx.pref)) - }); - - with_mutex(&GLOBALS, |cx| { - unsafe { - let correct_mode = mkb::main_mode == mkb::MD_GAME; - let disp_func = (*sprite).disp_func; - let is_pausemenu_sprite = disp_func == Some(mkb::sprite_pausemenu_disp); - if !((hide_hud || freecam_hide) && correct_mode && !is_pausemenu_sprite) { - cx.draw_sprite_hook.call(sprite); - } + let (hide_hud, freecam_hide, hook) = with_app(|cx| { + ( + cx.pref.get_bool(BoolPref::HideHud), + cx.freecam.should_hide_hud(&cx.pref), + cx.hide.draw_sprite_hook.clone(), + ) + }); + + unsafe { + let correct_mode = mkb::main_mode == mkb::MD_GAME; + let disp_func = (*sprite).disp_func; + let is_pausemenu_sprite = disp_func == Some(mkb::sprite_pausemenu_disp); + if !((hide_hud || freecam_hide) && correct_mode && !is_pausemenu_sprite) { + hook.call(sprite); } - }); + } }); hook!(DrawMinimapHook => (), mkb::g_draw_minimap, || { - let (hide_hud, freecam_hide) = with_app(|cx| { + let (hide_hud, freecam_hide, hook) = with_app(|cx| { let pref = &cx.pref; let freecam = &cx.freecam; - (pref.get_bool(BoolPref::HideHud), freecam.should_hide_hud(pref)) + ( + pref.get_bool(BoolPref::HideHud), + freecam.should_hide_hud(pref), + cx.hide.draw_minimap_hook.clone(), + ) }); - with_mutex(&GLOBALS, |cx| { - if !(hide_hud || freecam_hide) { - cx.draw_minimap_hook.call(); - } - }); + if !(hide_hud || freecam_hide) { + hook.call(); + } }); // Stage hook!(DrawStageHook => (), mkb::g_draw_stage, || { - let should_hide = with_app(|cx| { - cx.pref.get_bool(BoolPref::HideStage) + let (should_hide, hook) = with_app(|cx| { + (cx.pref.get_bool(BoolPref::HideStage), cx.hide.draw_stage_hook.clone()) }); - with_mutex(&GLOBALS, |cx| { - if !should_hide { - cx.draw_stage_hook.call(); - } - }); + if !should_hide { + hook.call(); + } }); // Ball @@ -127,50 +98,42 @@ hook!(DrawBallHook => (), mkb::g_draw_ball_and_ape, || { cx.pref.get_bool(BoolPref::HideBall) }); - with_mutex(&GLOBALS, |cx| { - if !should_hide { - cx.draw_ball_hook.call(); - } - }); + if !should_hide { + with_app(|cx| cx.hide.draw_ball_hook.clone()).call(); + } }); // Items hook!(DrawItemsHook => (), mkb::draw_items, || { - let should_hide = with_app(|cx| { - cx.pref.get_bool(BoolPref::HideItems) + let (should_hide, hook) = with_app(|cx| { + (cx.pref.get_bool(BoolPref::HideItems), cx.hide.draw_items_hook.clone()) }); - with_mutex(&GLOBALS, |cx| { - if !should_hide { - cx.draw_items_hook.call(); - } - }); + if !should_hide { + hook.call(); + } }); // Stage objects hook!(DrawStobjsHook => (), mkb::g_draw_stobjs, || { - let should_hide = with_app(|cx| { - cx.pref.get_bool(BoolPref::HideStobjs) + let (should_hide, hook) = with_app(|cx| { + (cx.pref.get_bool(BoolPref::HideStobjs), cx.hide.draw_stobjs_hook.clone()) }); - with_mutex(&GLOBALS, |cx| { - if !should_hide { - cx.draw_stobjs_hook.call(); - } - }); + if !should_hide { + hook.call(); + } }); // Effects hook!(DrawEffectsHook => (), mkb::g_draw_effects, || { - let should_hide = with_app(|cx| { - cx.pref.get_bool(BoolPref::HideEffects) + let (should_hide, hook) = with_app(|cx| { + (cx.pref.get_bool(BoolPref::HideEffects), cx.hide.draw_effects_hook.clone()) }); - with_mutex(&GLOBALS, |cx| { - if !should_hide { - cx.draw_effects_hook.call(); - } - }); + if !should_hide { + hook.call(); + } }); fn should_hide_bg(pref: &Pref) -> bool { @@ -198,25 +161,53 @@ unsafe extern "C" fn nl2ngc_set_fog_color_hook(r: u8, g: u8, b: u8) { } } -pub struct Hide {} +pub struct Hide { + draw_bg_hook: DrawBgHook, + clear_hook: ClearHook, + draw_sprite_hook: DrawSpriteHook, + draw_minimap_hook: DrawMinimapHook, + draw_stage_hook: DrawStageHook, + draw_ball_hook: DrawBallHook, + draw_items_hook: DrawItemsHook, + draw_stobjs_hook: DrawStobjsHook, + draw_effects_hook: DrawEffectsHook, +} impl Default for Hide { fn default() -> Self { - with_mutex(&GLOBALS, |cx| { - unsafe { - patch::write_branch_bl(0x80352e58 as *mut _, avdisp_set_fog_color_hook as *mut _); - patch::write_branch_bl(0x80352eac as *mut _, nl2ngc_set_fog_color_hook as *mut _); - } - cx.draw_bg_hook.hook(); - cx.clear_hook.hook(); - cx.draw_sprite_hook.hook(); - cx.draw_minimap_hook.hook(); - cx.draw_stage_hook.hook(); - cx.draw_ball_hook.hook(); - cx.draw_items_hook.hook(); - cx.draw_stobjs_hook.hook(); - cx.draw_effects_hook.hook(); - }); - Self {} + let draw_bg_hook = DrawBgHook::new(); + draw_bg_hook.hook(); + let clear_hook = ClearHook::new(); + clear_hook.hook(); + let draw_sprite_hook = DrawSpriteHook::new(); + draw_sprite_hook.hook(); + let draw_minimap_hook = DrawMinimapHook::new(); + draw_minimap_hook.hook(); + let draw_stage_hook = DrawStageHook::new(); + draw_stage_hook.hook(); + let draw_ball_hook = DrawBallHook::new(); + draw_ball_hook.hook(); + let draw_items_hook = DrawItemsHook::new(); + draw_items_hook.hook(); + let draw_stobjs_hook = DrawStobjsHook::new(); + draw_stobjs_hook.hook(); + let draw_effects_hook = DrawEffectsHook::new(); + draw_effects_hook.hook(); + + unsafe { + patch::write_branch_bl(0x80352e58 as *mut _, avdisp_set_fog_color_hook as *mut _); + patch::write_branch_bl(0x80352eac as *mut _, nl2ngc_set_fog_color_hook as *mut _); + } + Self { + draw_bg_hook, + clear_hook, + draw_sprite_hook, + draw_minimap_hook, + draw_stage_hook, + draw_ball_hook, + draw_items_hook, + draw_stobjs_hook, + draw_effects_hook, + } } } diff --git a/crates/smb2_practice_mod/src/mods/savestates_ui.rs b/crates/smb2_practice_mod/src/mods/savestates_ui.rs index 1624b6eb..4e4bd4b9 100644 --- a/crates/smb2_practice_mod/src/mods/savestates_ui.rs +++ b/crates/smb2_practice_mod/src/mods/savestates_ui.rs @@ -32,17 +32,18 @@ static GLOBALS: Mutex = Mutex::new(Globals { // Re-entrant hook, cannot use app state hook!(SetMinimapModeHook, mode: mkb::MinimapMode => (), mkb::set_minimap_mode, |mode| { - with_mutex(&GLOBALS, |cx| { + let (enabled, hook) = with_mutex(&GLOBALS, |cx| { let enabled = !( cx.savestates_enabled.get() && unsafe {mkb::main_mode} == mkb::MD_GAME && unsafe {mkb::main_game_mode} == mkb::PRACTICE_MODE && mode == mkb::MINIMAP_SHRINK ); - if enabled { - cx.set_minimap_mode_hook.call(mode); - } + (enabled, cx.set_minimap_mode_hook.clone()) }); + if enabled { + hook.call(mode); + } }); struct Context<'a> { diff --git a/crates/smb2_practice_mod/src/mods/sfx.rs b/crates/smb2_practice_mod/src/mods/sfx.rs index 073d2691..e5e3f63b 100644 --- a/crates/smb2_practice_mod/src/mods/sfx.rs +++ b/crates/smb2_practice_mod/src/mods/sfx.rs @@ -18,11 +18,12 @@ hook!(SoftStreamStartHook, looping_state: u32, g_bgm_id: mkb::BgmTrack, param_3: // Re-entrant hook, cannot use app state hook!(SoundReqIdHook, sfx_idx: u32 => (), mkb::call_SoundReqID_arg_0, |sfx_idx| { - with_mutex(&GLOBALS, |cx| { - if !(cx.mute_timer_ding.get() && sfx_idx == 0x0003d806) { - cx.sound_req_id_hook.call(sfx_idx); - } + let (mute_timer_ding, hook) = with_mutex(&GLOBALS, |cx| { + (cx.mute_timer_ding.get(), cx.sound_req_id_hook.clone()) }); + if !(mute_timer_ding && sfx_idx == 0x0003d806) { + hook.call(sfx_idx); + } }); hook!(SpriteGoDispHook, sprite: *mut mkb::Sprite => (), mkb::sprite_go_disp, |sprite| { @@ -72,22 +73,20 @@ hook!(SpriteGoDispHook, sprite: *mut mkb::Sprite => (), mkb::sprite_go_disp, |sp }); struct Globals { - soft_stream_start_hook: SoftStreamStartHook, sound_req_id_hook: SoundReqIdHook, - sprite_go_disp_hook: SpriteGoDispHook, mute_timer_ding: Cell, } static GLOBALS: Mutex = Mutex::new(Globals { - soft_stream_start_hook: SoftStreamStartHook::new(), sound_req_id_hook: SoundReqIdHook::new(), - sprite_go_disp_hook: SpriteGoDispHook::new(), mute_timer_ding: Cell::new(false), }); #[derive(Default)] pub struct Sfx { initialized: bool, + soft_stream_start_hook: SoftStreamStartHook, + sprite_go_disp_hook: SpriteGoDispHook, } impl Sfx { @@ -97,10 +96,10 @@ impl Sfx { if pref.get_bool(BoolPref::MuteBgm) { // Only hook if the preference is initially set, so we don't affect background music until game // is rebooted - cx.soft_stream_start_hook.hook(); + self.soft_stream_start_hook.hook(); } cx.sound_req_id_hook.hook(); - cx.sprite_go_disp_hook.hook(); + self.sprite_go_disp_hook.hook(); self.initialized = true; } diff --git a/crates/smb2_practice_mod/src/mods/stage_edits.rs b/crates/smb2_practice_mod/src/mods/stage_edits.rs index c44ba381..35a8c99e 100644 --- a/crates/smb2_practice_mod/src/mods/stage_edits.rs +++ b/crates/smb2_practice_mod/src/mods/stage_edits.rs @@ -1,4 +1,3 @@ -use critical_section::Mutex; use mkb::mkb; use num_enum::TryFromPrimitive; @@ -7,13 +6,10 @@ use crate::{ app::with_app, hook, systems::pref::{Pref, U8Pref}, - utils::misc::with_mutex, }; hook!(LoadStagedefHook, stage_id: u32 => (), mkb::load_stagedef, |stage_id| { - with_mutex(&GLOBALS, |cx| { - cx.load_stagedef_hook.call(stage_id); - }); + with_app(|cx| cx.stage_edits.load_stagedef_hook.clone()).call(stage_id); with_app(|cx| { cx.stage_edits.on_load_stagedef(&cx.pref); }); @@ -33,25 +29,18 @@ pub struct StageEdits { current_mode: ActiveMode, rev_goal_idx: u32, new_goal: bool, -} - -struct Globals { load_stagedef_hook: LoadStagedefHook, } -static GLOBALS: Mutex = Mutex::new(Globals { - load_stagedef_hook: LoadStagedefHook::new(), -}); - impl Default for StageEdits { fn default() -> Self { - with_mutex(&GLOBALS, |cx| { - cx.load_stagedef_hook.hook(); - }); + let load_stagedef_hook = LoadStagedefHook::new(); + load_stagedef_hook.hook(); Self { current_mode: Default::default(), rev_goal_idx: 0, new_goal: false, + load_stagedef_hook, } } } diff --git a/crates/smb2_practice_mod/src/mods/validate.rs b/crates/smb2_practice_mod/src/mods/validate.rs index acfe4f97..7ccd4922 100644 --- a/crates/smb2_practice_mod/src/mods/validate.rs +++ b/crates/smb2_practice_mod/src/mods/validate.rs @@ -1,10 +1,8 @@ use core::ffi::c_int; -use critical_section::Mutex; use mkb::mkb; use mkb::{byte, Vec}; use crate::app::with_app; -use crate::utils::misc::with_mutex; use crate::{ hook, systems::{ @@ -21,9 +19,9 @@ hook!(DidBallEnterGoalHook, ball: *mut mkb::Ball, out_stage_goal_idx: *mut c_int out_itemgroup_id: *mut c_int, out_goal_flags: *mut byte => u8, mkb::did_ball_enter_goal, |ball, out_stage_goal_idx, out_itemgroup_id, out_goal_flags| { - let result = with_mutex(&GLOBALS, |cx| { - cx.did_ball_enter_goal_hook.call(ball, out_stage_goal_idx, out_itemgroup_id, out_goal_flags) - }); + let result = with_app(|cx| { + cx.validate.did_ball_enter_goal_hook.clone() + }).call(ball, out_stage_goal_idx, out_itemgroup_id, out_goal_flags); with_app(|cx| { if result != 0 { @@ -31,16 +29,8 @@ hook!(DidBallEnterGoalHook, ball: *mut mkb::Ball, out_stage_goal_idx: *mut c_int cx.validate.find_framesave(ball); cx.validate.entered_goal = result != 0; } - result - }) -}); - -struct Globals { - did_ball_enter_goal_hook: DidBallEnterGoalHook, -} - -static GLOBALS: Mutex = Mutex::new(Globals { - did_ball_enter_goal_hook: DidBallEnterGoalHook::new(), + }); + result }); pub struct Validate { @@ -49,19 +39,20 @@ pub struct Validate { used_mods: bool, has_paused: bool, loaded_savestate: bool, + did_ball_enter_goal_hook: DidBallEnterGoalHook, } impl Default for Validate { fn default() -> Self { - with_mutex(&GLOBALS, |cx| { - cx.did_ball_enter_goal_hook.hook(); - }); + let did_ball_enter_goal_hook = DidBallEnterGoalHook::new(); + did_ball_enter_goal_hook.hook(); Self { framesave: 0, entered_goal: false, used_mods: false, has_paused: false, loaded_savestate: false, + did_ball_enter_goal_hook, } } } diff --git a/crates/smb2_practice_mod/src/systems/cardio.rs b/crates/smb2_practice_mod/src/systems/cardio.rs index 24b573bb..af189854 100644 --- a/crates/smb2_practice_mod/src/systems/cardio.rs +++ b/crates/smb2_practice_mod/src/systems/cardio.rs @@ -90,81 +90,91 @@ impl CardIo { } } - // Synchronous at the moment. Also, do not call while write_file() is running! - pub fn read_file(&mut self, file_name: &str) -> Result, CARDResult> { - unsafe { - let _fake_gamecode = FakeGamecode::new(); - let mut res; - - // Probe card - loop { - res = to_card_result(mkb::CARDProbeEx(0, null_mut(), null_mut())); - if res != CARDResult::Busy { - break; - } - } - if res != CARDResult::Ready { - return Err(res); - } - - // Mount card - mkb::CARDMountAsync( - 0, - self.card_work_area.as_mut_ptr() as *mut c_void, - null_mut(), - null_mut(), - ); - loop { - res = to_card_result(mkb::CARDGetResultCode(0)); - if res != CARDResult::Busy { - break; - } - } - if res != CARDResult::Ready { - return Err(res); - } - // Open file - res = to_card_result(mkb::CARDOpen( - 0, - cstr!(16, file_name), - &mut self.card_file_info, - )); - if res != CARDResult::Ready { - mkb::CARDUnmount(0); - return Err(res); + unsafe fn read_file_internal(&mut self, file_name: &str) -> Result, CARDResult> { + let _fake_gamecode = FakeGamecode::new(); + let mut res; + + // Probe card + loop { + res = to_card_result(mkb::CARDProbeEx(0, null_mut(), null_mut())); + if res != CARDResult::Busy { + break; } + } + if res != CARDResult::Ready { + return Err(res); + } - // Get file size - let mut stat = mkb::CARDStat::default(); - res = to_card_result(mkb::CARDGetStatus(0, self.card_file_info.fileNo, &mut stat)); - if res != CARDResult::Ready { - mkb::CARDUnmount(0); - return Err(res); + // Mount card + mkb::CARDMountAsync( + 0, + self.card_work_area.as_mut_ptr() as *mut c_void, + null_mut(), + null_mut(), + ); + loop { + res = to_card_result(mkb::CARDGetResultCode(0)); + if res != CARDResult::Busy { + break; } + } + if res != CARDResult::Ready { + return Err(res); + } + // Open file + res = to_card_result(mkb::CARDOpen( + 0, + cstr!(16, file_name), + &mut self.card_file_info, + )); + if res != CARDResult::Ready { + mkb::CARDUnmount(0); + return Err(res); + } - let buf_size = math::round_up_pow2(stat.length as usize, CARD_READ_SIZE as usize); - let mut buf = vec![0u8; buf_size]; + // Get file size + let mut stat = mkb::CARDStat::default(); + res = to_card_result(mkb::CARDGetStatus(0, self.card_file_info.fileNo, &mut stat)); + if res != CARDResult::Ready { + mkb::CARDUnmount(0); + return Err(res); + } - mkb::CARDReadAsync( - &mut self.card_file_info as *mut _, - buf.as_mut_ptr() as *mut _, - buf_size as c_long, - 0, - null_mut(), - ); - loop { - res = to_card_result(mkb::CARDGetResultCode(0)); - if res != CARDResult::Busy { - break; - } - } - if res != CARDResult::Ready { - mkb::CARDUnmount(0); - return Err(res); + let buf_size = math::round_up_pow2(stat.length as usize, CARD_READ_SIZE as usize); + let mut buf = vec![0u8; buf_size]; + + mkb::CARDReadAsync( + &mut self.card_file_info as *mut _, + buf.as_mut_ptr() as *mut _, + buf_size as c_long, + 0, + null_mut(), + ); + loop { + res = to_card_result(mkb::CARDGetResultCode(0)); + if res != CARDResult::Busy { + break; } - + } + if res != CARDResult::Ready { mkb::CARDUnmount(0); - Ok(buf) + return Err(res); + } + + mkb::CARDUnmount(0); + Ok(buf) + } + + // Synchronous at the moment. Also, do not call while write_file() is running! + pub fn read_file(&mut self, file_name: &str) -> Result, CARDResult> { + unsafe { + // HACK: re-enable interrupts during reading so we don't block forever. We wouldn't need to if + // we read async like we do for writing, but if e.g. SMB2WorkshopMod also did this on startup + // we'd have to coordinate non-overlapping reads of stuff at startup. + let interrupts = mkb::OSEnableInterrupts(); + let result = self.read_file_internal(file_name); + mkb::OSRestoreInterrupts(interrupts); + result } } diff --git a/crates/smb2_practice_mod/src/systems/pad.rs b/crates/smb2_practice_mod/src/systems/pad.rs index 2225dea2..45998b79 100644 --- a/crates/smb2_practice_mod/src/systems/pad.rs +++ b/crates/smb2_practice_mod/src/systems/pad.rs @@ -1,11 +1,6 @@ -use critical_section::Mutex; use mkb::mkb; -use crate::{ - app::with_app, - hook, - utils::misc::{for_c_arr_idx, with_mutex}, -}; +use crate::{app::with_app, hook, utils::misc::for_c_arr_idx}; #[derive(Clone, Copy, PartialEq, Eq)] pub enum Dir { @@ -58,9 +53,9 @@ const DIR_REPEAT_PERIOD: u32 = 3; const DIR_REPEAT_WAIT: u32 = 14; hook!(PadReadHook, statuses: *mut mkb::PADStatus => u32, mkb::PADRead, |statuses| { - let ret = with_mutex(&GLOBALS, |cx| { - cx.pad_read_hook.call(statuses) - }); + let ret = with_app(|cx| { + cx.pad.pad_read_hook.clone() + }).call(statuses); with_app(|cx| { let status_array = unsafe { core::slice::from_raw_parts(statuses, 4)}; @@ -71,14 +66,6 @@ hook!(PadReadHook, statuses: *mut mkb::PADStatus => u32, mkb::PADRead, |statuses ret }); -struct Globals { - pad_read_hook: PadReadHook, -} - -static GLOBALS: Mutex = Mutex::new(Globals { - pad_read_hook: PadReadHook::new(), -}); - pub struct Pad { analog_state: AnalogState, curr_priority: Prio, @@ -91,13 +78,14 @@ pub struct Pad { analog_inputs: [mkb::AnalogInputGroup; 4], dir_down_time: [u8; 8], + + pad_read_hook: PadReadHook, } impl Default for Pad { fn default() -> Self { - with_mutex(&GLOBALS, |cx| { - cx.pad_read_hook.hook(); - }); + let pad_read_hook = PadReadHook::new(); + pad_read_hook.hook(); Self { analog_state: Default::default(), curr_priority: Default::default(), @@ -110,6 +98,7 @@ impl Default for Pad { analog_inputs: Default::default(), dir_down_time: Default::default(), + pad_read_hook, } } } diff --git a/crates/smb2_practice_mod/src/utils/hook.rs b/crates/smb2_practice_mod/src/utils/hook.rs index 9ae8ee11..13f447cb 100644 --- a/crates/smb2_practice_mod/src/utils/hook.rs +++ b/crates/smb2_practice_mod/src/utils/hook.rs @@ -1,11 +1,10 @@ #[macro_export] macro_rules! hook { ($name:ident $(, $argname:ident: $arg:ty)* => $ret:ty, $their_func:expr, $our_func:expr) => { + #[derive(Clone)] pub struct $name { instrs: [core::cell::Cell; 2], - chained_func: core::cell::Cell $ret>>, - their_func: unsafe extern "C" fn($($arg,)*) -> $ret, - our_func_c: extern "C" fn($($arg,)*) -> $ret, + jumpback_addr: core::cell::Cell, } impl Default for $name { @@ -18,33 +17,39 @@ macro_rules! hook { const fn new() -> Self { Self { instrs: [core::cell::Cell::new(0), core::cell::Cell::new(0)], - chained_func: core::cell::Cell::new(None), - their_func: $their_func, - our_func_c: Self::c_hook, + jumpback_addr: core::cell::Cell::new(0), } } pub fn hook(&self) { + let their_func = $their_func; unsafe { - let mut chained_func_addr = core::ptr::null(); - $crate::patch::hook_function( - self.their_func as *mut usize, - self.our_func_c as *mut usize, - &self.instrs, - &mut chained_func_addr, + let hook_info = $crate::patch::hook_function_part1( + their_func as *mut usize, + Self::c_hook as *const usize, ); - let chained_func = core::mem::transmute::< - *const core::ffi::c_void, - extern "C" fn($($arg,)*) -> $ret - >(chained_func_addr); - self.chained_func.set(Some(chained_func)); + self.instrs[0].set(hook_info.first_instr); + self.jumpback_addr.set(hook_info.jumpback_addr as usize); } } // We sometimes want to replace the hooked function entirely and never call the original #[allow(dead_code)] - pub fn call(&self $(, $argname: $arg)*) -> $ret { - (self.chained_func.get().unwrap())($($argname,)*) + pub fn call(self $(, $argname: $arg)*) -> $ret { + // We take self as a compile-time reminder that hooks must be cloned and moved out + // of Mutex scope before being called + unsafe { + $crate::patch::hook_function_part2( + &self.instrs, + self.jumpback_addr.get() as *const usize, + ); + let chained_func = + core::mem::transmute::< + *const core::ffi::c_void, + extern "C" fn($($arg,)*) -> $ret + >(self.instrs.as_ptr() as *const core::ffi::c_void); + chained_func($($argname,)*) + } } extern "C" fn c_hook($($argname: $arg, )*) -> $ret { diff --git a/crates/smb2_practice_mod/src/utils/libsavestate.rs b/crates/smb2_practice_mod/src/utils/libsavestate.rs index be47fa40..34e671e2 100644 --- a/crates/smb2_practice_mod/src/utils/libsavestate.rs +++ b/crates/smb2_practice_mod/src/utils/libsavestate.rs @@ -10,11 +10,13 @@ use crate::{hook, mods::timer::Timer}; // Re-entrant hook, cannot use app state hook!(SoundReqIdHook, sfx_idx: u32 => (), mkb::call_SoundReqID_arg_0, |sfx_idx| { - with_mutex(&GLOBALS, |cx| { - if !cx.state_loaded_this_frame.get() { - cx.sound_req_id_hook.call(sfx_idx); - } - }); + let (loaded_this_frame, hook) = with_mutex(&GLOBALS, |cx| ( + cx.state_loaded_this_frame.get(), + cx.sound_req_id_hook.clone() + )); + if !loaded_this_frame { + hook.call(sfx_idx); + } }); struct Globals { diff --git a/crates/smb2_practice_mod/src/utils/patch.rs b/crates/smb2_practice_mod/src/utils/patch.rs index c363993e..ab5a289e 100644 --- a/crates/smb2_practice_mod/src/utils/patch.rs +++ b/crates/smb2_practice_mod/src/utils/patch.rs @@ -71,12 +71,12 @@ pub unsafe fn write_nop(ptr: *mut usize) -> usize { write_word(ptr, 0x60000000) } -pub unsafe fn hook_function( - func: *mut usize, - dest: *mut usize, - tramp_instrs: &[Cell; 2], - tramp_dest: &mut *const c_void, -) { +pub struct HookInfo { + pub first_instr: usize, + pub jumpback_addr: *const c_void, +} + +pub unsafe fn hook_function_part1(func: *mut usize, dest: *const usize) -> HookInfo { if (*func & ppc::B_OPCODE_MASK) == ppc::B_OPCODE { // Func has been hooked already, chain the hooks @@ -89,27 +89,35 @@ pub unsafe fn hook_function( let old_dest = func as usize + old_dest_offset; // Hook to our new func instead - write_branch(func, dest as *mut _); + write_branch(func, dest as *const _); - // Use the old hooked func as the trampoline dest - *tramp_dest = old_dest as *const c_void; - } else { - // Func has not been hooked yet - // Original instruction - tramp_instrs[0].set(*func); - clear_dc_ic_cache(tramp_instrs.as_ptr() as *mut _, size_of::()); - - // Branch to original func past hook - write_branch( - &tramp_instrs[1], - transmute::<*const usize, *const c_void>(func.add(1) as *const _), - ); + let mut first_instr = 0; + write_nop(&raw mut first_instr); - // The function pointer to run as the original function is the addr of the trampoline - // instructions array - *tramp_dest = transmute::<*const Cell, *const c_void>(tramp_instrs.as_ptr()); + HookInfo { + first_instr, + jumpback_addr: old_dest as *const c_void, + } + } else { + let first_instr = *func; // Write actual hook - write_branch(func, dest as *mut _); + write_branch(func, dest as *const _); + + HookInfo { + first_instr, + jumpback_addr: func.add(1) as *const c_void, + } } } + +pub unsafe fn hook_function_part2(instrs: &[Cell; 2], jumpback_addr: *const usize) { + // Update branch instruction to reflect hook's new position in memory + write_branch( + &instrs[1], + transmute::<*const usize, *const c_void>(jumpback_addr), + ); + // Flush/Invalidate caches for both instructions, as this function is meant to be called after + // cloning a hook + clear_dc_ic_cache(instrs.as_ptr() as *const _, size_of::<[Cell; 2]>()); +}