From 8ab1d8a0d8c24a144974d24895f3c2d583e98c40 Mon Sep 17 00:00:00 2001 From: jugeeya Date: Thu, 30 Apr 2020 16:13:49 -0700 Subject: [PATCH] initial rust migration --- Cargo.toml | 5 +- src/common/consts.rs | 93 ++++++++++++ src/common/mod.rs | 90 ++++++++++++ src/hitbox_visualizer/mod.rs | 272 +++++++++++++++++++++++++++++++++++ src/lib.rs | 78 +++++++--- 5 files changed, 516 insertions(+), 22 deletions(-) create mode 100644 src/common/consts.rs create mode 100644 src/common/mod.rs create mode 100644 src/hitbox_visualizer/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 6964ac8..24625b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "skyline_rs_template" +name = "training_modpack" version = "0.1.0" authors = ["jam1garner "] edition = "2018" @@ -17,3 +17,6 @@ panic = "abort" [profile.release] panic = "abort" lto = true + +[package.metadata.skyline] +titleid = "01006A800016E000" diff --git a/src/common/consts.rs b/src/common/consts.rs new file mode 100644 index 0000000..bbbb186 --- /dev/null +++ b/src/common/consts.rs @@ -0,0 +1,93 @@ +pub const NONE : i32 = 0; + +// Side Taunt + +// DI +/* + 0, 0.785398, 1.570796, 2.356194, -3.14159, -2.356194, -1.570796, -0.785398 + 0, pi/4, pi/2, 3pi/4, pi, 5pi/4, 3pi/2, 7pi/4 +*/ + +/* DI */ +pub static mut DI_STATE : i32 = NONE; +pub const DI_RANDOM_IN_AWAY : i32 = 9; +// const std::vector di_items{"None", "Out", "Up Out", "Up", "Up In", "In", "Down In", "Down", "Down Out", "Random"}; + +// Attack Option +pub const MASH_NAIR : i32 = 0; +pub const MASH_FAIR : i32 = 1; +pub const MASH_BAIR : i32 = 2; +pub const MASH_UPAIR : i32 = 3; +pub const MASH_DAIR : i32 = 4; +pub const MASH_NEUTRAL_B : i32 = 5; +pub const MASH_SIDE_B : i32 = 6; +pub const MASH_UP_B : i32 = 7; +pub const MASH_DOWN_B : i32 = 8; +pub const MASH_UP_SMASH : i32 = 9; +pub const MASH_GRAB : i32 = 10; +// pub const std::vector attack_items{"Neutral Air", "Forward Air", "Back Air", "Up Air", "Down Air", "Neutral B", "Side B", "Up B", "Down B", "Up Smash", "Grab"}; + +// Ledge Option +pub const RANDOM_LEDGE : i32 = 1; +pub const NEUTRAL_LEDGE : i32 = 2; +pub const ROLL_LEDGE : i32 = 3; +pub const JUMP_LEDGE : i32 = 4; +pub const ATTACK_LEDGE : i32 = 5; +// pub const std::vector ledge_items{"None", "Random", "Ntrl. Getup", "Roll", "Jump", "Attack"}; + +// Tech Option +pub const RANDOM_TECH : i32 = 1; +pub const TECH_IN_PLACE : i32 = 2; +pub const TECH_ROLL : i32 = 3; +pub const TECH_MISS : i32 = 4; +// pub const std::vector tech_items{"None", "Random", "In-Place", "Roll", "Miss Tech"}; + +// Mash States +pub const MASH_AIRDODGE : i32 = 1; +pub const MASH_JUMP : i32 = 2; +pub const MASH_ATTACK : i32 = 3; +pub const MASH_SPOTDODGE : i32 = 4; +pub const MASH_RANDOM : i32 = 5; +// pub const std::vector mash_items{"None", "Airdodge", "Jump", "Attack", "Spotdodge", "Random"}; + +// Shield States +pub const SHIELD_INFINITE : i32 = 1; +pub const SHIELD_HOLD : i32 = 2; +// pub const std::vector shield_items{"None", "Infinite", "Hold"}; + +// Defensive States +pub const RANDOM_DEFENSIVE : i32 = 1; +pub const DEFENSIVE_SPOTDODGE : i32 = 2; +pub const DEFENSIVE_ROLL : i32 = 3; +pub const DEFENSIVE_JAB : i32 = 4; +pub const DEFENSIVE_SHIELD : i32 = 5; +// pub const std::vector defensive_items{"None", "Random", "Spotdodge", "Roll", "Jab", "Flash Shield"}; + +#[repr(C)] +pub struct TrainingModpackMenu +{ + pub HITBOX_VIS : bool, + pub DI_STATE : i32, + pub ATTACK_STATE : i32, + pub LEDGE_STATE : i32, + pub TECH_STATE : i32, + pub MASH_STATE : i32, + pub SHIELD_STATE : i32, + pub DEFENSIVE_STATE : i32, +} + +impl Default for TrainingModpackMenu { + fn default() -> TrainingModpackMenu { + TrainingModpackMenu { + HITBOX_VIS : true, + DI_STATE : NONE, + ATTACK_STATE : MASH_NAIR, + LEDGE_STATE : RANDOM_LEDGE, + TECH_STATE : RANDOM_TECH, + MASH_STATE : NONE, + SHIELD_STATE : NONE, + DEFENSIVE_STATE : RANDOM_DEFENSIVE, + } + } + +} \ No newline at end of file diff --git a/src/common/mod.rs b/src/common/mod.rs new file mode 100644 index 0000000..4388f9d --- /dev/null +++ b/src/common/mod.rs @@ -0,0 +1,90 @@ +pub mod consts; + +use smash::lib::lua_const::{*}; +use crate::common::consts::*; +use smash::app::{self}; +use smash::app::lua_bind::*; +use smash::hash40; + +pub static menu : consts::TrainingModpackMenu = consts::TrainingModpackMenu{ + HITBOX_VIS : true, + DI_STATE : NONE, + ATTACK_STATE : MASH_NAIR, + LEDGE_STATE : RANDOM_LEDGE, + TECH_STATE : RANDOM_TECH, + MASH_STATE : NONE, + SHIELD_STATE : NONE, + DEFENSIVE_STATE : RANDOM_DEFENSIVE, +}; + +static fighter_manager_addr: u64 = 0; + +extern "C" { + #[link_name = "\u{1}_ZN3app9smashball16is_training_modeEv"] + pub fn is_training_mode() -> bool; +} + +// pub fn get_category(module_accessor: &mut app::BattleObjectModuleAccessor) -> u8 { +// return (u8)(*(u32*)(module_accessor + 8) >> 28); +// } + +// pub fn is_operation_cpu(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool{ +// if (get_category(module_accessor) != BATTLE_OBJECT_CATEGORY_FIGHTER) +// return false; + +// let entry_id = WorkModule::get_int(module_accessor, FIGHTER_INSTANCE_WORK_ID_INT_ENTRY_ID) as i32; +// u64 fighter_information = FighterManager::get_fighter_information(LOAD64(fighter_manager_addr), entry_id); + +// return FighterInformation::is_operation_cpu(fighter_information); +// } + +pub unsafe fn is_in_hitstun(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + let status_kind = StatusModule::status_kind(module_accessor) as i32; + return status_kind >= FIGHTER_STATUS_KIND_DAMAGE && + status_kind <= FIGHTER_STATUS_KIND_DAMAGE_FALL; +} + +pub unsafe fn is_in_shieldstun(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + let status_kind = StatusModule::status_kind(module_accessor) as i32; + let prev_status = StatusModule::prev_status_kind(module_accessor, 0) as i32; + // If we are taking shield damage or we are droping shield from taking shield damage we are in hitstun + if status_kind == FIGHTER_STATUS_KIND_GUARD_DAMAGE || + (prev_status == FIGHTER_STATUS_KIND_GUARD_DAMAGE && status_kind == FIGHTER_STATUS_KIND_GUARD_OFF) { + return true + } + + false +} + + +pub unsafe fn is_in_landing(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool { + let status_kind = StatusModule::status_kind(module_accessor) as i32; + (FIGHTER_STATUS_KIND_LANDING..FIGHTER_STATUS_KIND_LANDING_DAMAGE_LIGHT) + .contains(&status_kind) +} + + +// pub fn perform_defensive_option(module_accessor: &mut app::BattleObjectModuleAccessor, flag: &i32) { +// if menu.DEFENSIVE_STATE == RANDOM_DEFENSIVE { +// let NUM_DEFENSIVE_CMDS = 4; +// let random_cmds = vec![ +// FIGHTER_PAD_CMD_CAT1_FLAG_ESCAPE, +// FIGHTER_PAD_CMD_CAT1_FLAG_ESCAPE_F, +// FIGHTER_PAD_CMD_CAT1_FLAG_ESCAPE_B, +// FIGHTER_PAD_CMD_CAT1_FLAG_ATTACK_N +// ]; + +// let random_cmd_index = app::sv_math::rand(hash40("fighter"), random_cmds.len() as i32) as usize; +// flag |= random_cmds[random_cmd_index]; +// } else if menu.DEFENSIVE_STATE == DEFENSIVE_ROLL { +// if app::sv_math::rand(hash40("fighter"), 2) == 0 { +// flag |= FIGHTER_PAD_CMD_CAT1_FLAG_ESCAPE_F; +// } else { +// flag |= FIGHTER_PAD_CMD_CAT1_FLAG_ESCAPE_B; +// } +// } else if menu.DEFENSIVE_STATE == DEFENSIVE_SPOTDODGE { +// flag |= FIGHTER_PAD_CMD_CAT1_FLAG_ESCAPE; +// } else if menu.DEFENSIVE_STATE == DEFENSIVE_JAB { +// flag |= FIGHTER_PAD_CMD_CAT1_FLAG_ATTACK_N; +// } +// } \ No newline at end of file diff --git a/src/hitbox_visualizer/mod.rs b/src/hitbox_visualizer/mod.rs new file mode 100644 index 0000000..bc0fcbc --- /dev/null +++ b/src/hitbox_visualizer/mod.rs @@ -0,0 +1,272 @@ +use smash::hash40; +use smash::app::BattleObjectModuleAccessor; +use smash::app::sv_animcmd::{self}; +use smash::app::lua_bind::*; +use smash::lib::{self, L2CAgent, L2CValue}; +use smash::phx::{Hash40, Vector3f}; +use smash::lib::lua_const::{*}; +use smash::app::sv_system::{self}; +use smash::app::{self}; +use skyline::logging::hex_dump_ptr; +use crate::common::*; + +/** + * Rounds a number to the nearest multiple of another number. + */ + pub fn round_to(val: f32, align: f32) -> f32 { (val / align).round() * align } + + /** + * Linearly interpolates between two numbers, without bounds checking. + */ +pub fn lerp(min: f32, max: f32, t: f32) -> f32 { min + (max - min) * t } + +pub fn unlerp(min: f32, max: f32, val: f32) -> f32 { (val - min) / (max - min) } + + /** + * Linearly interpolates between two numbers, with bounds checking. + */ +pub fn lerp_bounded(min: f32, max: f32, t: f32) -> f32 { + if t <= 0.0 { min } else { if t >= 1.0 { max } else { lerp(min, max, t) } } +} + +pub fn unlerp_bounded(min: f32, max: f32, val: f32) -> f32 { + if val <= min { 0.0 } else { if val >= max { 1.0 } else { unlerp(min, max, val)} } +} + + /** + * Linearly nterpolates between two colors, with bounds checking, accounting for + * gamma. arguments: + * - min_color (Vector3f) -- xyz maps to rgb, components are usually in the + * range [0.0f, 1.0f] but can go beyond to account for super-bright or + * super-dark colors + * - max_Color (Vector3f) -- same as minColor + * - t (float) -- how far to interpolate between the colors + * - gamma (float = 2.0f) -- used for color correction, helps avoid ugly dark + * colors when interpolating b/t bright colors + */ + +pub fn color_lerp(min_color: Vector3f, max_color: Vector3f, t: f32, + gamma: f32) -> Vector3f { + let gamma_inv = 1.0 / gamma; + let align = + 1.0 / 255.0; // color components must be a multiple of 1/255 + Vector3f{x: round_to(lerp_bounded(min_color.x.powf(gamma), + max_color.x.powf(gamma), t).powf( + gamma_inv), + align), + y: round_to(lerp_bounded(min_color.y.powf(gamma), + max_color.y.powf(gamma), t).powf( + gamma_inv), + align), + z: round_to(lerp_bounded(min_color.z.powf(gamma), + max_color.z.powf(gamma), t).powf( + gamma_inv), + align)} + } + +const ID_COLORS: &[Vector3f] = &[ + // used to tint the hitbox effects -- make sure that at least one component + // is equal to 1.0 + Vector3f{x: 1.0, y: 0.0, z: 0.0}, // #ff0000 (red) + Vector3f{x: 1.0, y: 0.4, z: 0.0}, // #ff9900 (orange) + Vector3f{x: 0.8, y: 1.0, z: 0.0}, // #ccff00 (yellow) + Vector3f{x: 0.2, y: 1.0, z: 0.2}, // #00ff33 (green) + Vector3f{x: 0.0, y: 0.8, z: 1.0}, // #00ccff (sky blue) + Vector3f{x: 0.4, y: 0.4, z: 1.0}, // #6666ff (blue) + Vector3f{x: 0.8, y: 0.0, z: 1.0}, // #cc00ff (purple) + Vector3f{x: 1.0, y: 0.2, z: 0.8}, // #ff33cc (pink) +]; +const MAX_EFFECTS_PER_HITBOX: i32 = 16; // max # of circles drawn for an extended hitbox + +pub unsafe fn wrap(func: unsafe extern "C" fn(lua_state: u64), agent: &mut L2CAgent, vals: &mut Vec::) { + agent.clear_lua_stack(); + for val in vals { + agent.push_lua_stack(val); + } + func(agent.lua_state_agent); + agent.clear_lua_stack(); +} + +pub unsafe fn generate_hitbox_effects(l2c_agent: &mut L2CAgent, bone: L2CValue, + size: L2CValue, x: L2CValue, y: L2CValue, + z: L2CValue, x2: L2CValue, y2: L2CValue, + z2: L2CValue, color: Vector3f) { + let red = L2CValue::new_num(color.x); + let green = L2CValue::new_num(color.y); + let blue = L2CValue::new_num(color.z); + + let size_mult = 19.0 / 200.0; + + let shield_effect = L2CValue::new_int(hash40("sys_shield")); + let zero_rot = L2CValue::new_num(0.0); + let terminate = L2CValue::new_bool(true); + let effect_size = L2CValue::new_num(size.get_num() * size_mult); + + let rate = L2CValue::new_num(8.0); + + let x_dist : f32; + let y_dist : f32; + let z_dist : f32; + let mut n_effects : i32; + if let lib::L2CValueType::Void = x2.val_type{ // && let lib::L2CValueType::Void = y2.val_type && let lib::L2CValueType::Void = z2.val_type { // extended hitbox + x_dist = 0.0; y_dist = 0.0; z_dist = 0.0; + n_effects = 1; + } + else { // non-extended hitbox + x_dist = x2.get_num() - x.get_num(); + y_dist = y2.get_num() - y.get_num(); + z_dist = z2.get_num() - z.get_num(); + let dist_sq : f32 = x_dist * x_dist + y_dist * y_dist + z_dist * z_dist; + let dist = dist_sq.sqrt(); + n_effects = ((dist / (size.get_num() * 1.75)) + 1.0).ceil() as i32; // just enough effects to form a continuous line + if n_effects < 2 { + n_effects = 2; + } else if n_effects > MAX_EFFECTS_PER_HITBOX { + n_effects = MAX_EFFECTS_PER_HITBOX; + } + } + + for i in 0..n_effects { + let mut t = 0.0; + if n_effects > 1 { + t = (i as f32) / ((n_effects - 1) as f32); + } + let x_curr = L2CValue::new_num(x.get_num() + x_dist * t); + let y_curr = L2CValue::new_num(y.get_num() + y_dist * t); + let z_curr = L2CValue::new_num(z.get_num() + z_dist * t); + + wrap(sv_animcmd::EFFECT_FOLLOW_NO_SCALE, + l2c_agent, + &mut [ + shield_effect, bone, x_curr, + y_curr, z_curr, zero_rot, zero_rot, + zero_rot, effect_size, terminate].to_vec()); + + // set to hitbox ID color + wrap(sv_animcmd::LAST_EFFECT_SET_COLOR, l2c_agent, + &mut [red, green, blue].to_vec()); + + // speed up animation by rate to remove pulsing effect + wrap(sv_animcmd::LAST_EFFECT_SET_RATE, l2c_agent, + &mut [rate].to_vec()); + } +} + +#[allow(unused_unsafe)] +#[skyline::hook(replace = sv_animcmd::ATTACK)] +unsafe fn handle_attack(lua_state: u64) { + let mut l2c_agent = L2CAgent::new(lua_state); + + // get all necessary hitbox params + let id = l2c_agent.pop_lua_stack(1); // int + let bone = l2c_agent.pop_lua_stack(3); // hash40 + let damage = l2c_agent.pop_lua_stack(4); // float + let _angle = l2c_agent.pop_lua_stack(5); // int + let kbg = l2c_agent.pop_lua_stack(6); // int + let fkb = l2c_agent.pop_lua_stack(7); // int + let bkb = l2c_agent.pop_lua_stack(8); // int + let size = l2c_agent.pop_lua_stack(9); // float + let x = l2c_agent.pop_lua_stack(10); // float + let y = l2c_agent.pop_lua_stack(11); // float + let z = l2c_agent.pop_lua_stack(12); // float + let x2 = l2c_agent.pop_lua_stack(13); // float or void + let y2 = l2c_agent.pop_lua_stack(14); // float or void + let z2 = l2c_agent.pop_lua_stack(15); // float or void + + original!()(lua_state); + + if menu.HITBOX_VIS && is_training_mode() { // generate hitbox effect(s) + let color_scale: f32; + if false { // color intensity scales with damage + color_scale = unlerp_bounded(1.0, 18.0, damage.get_num()); + } else { // color intensity scales with total KB + // calculate the expected KB a character with 95 weight will receive + // at 80% pre-hit + let target_percent = 80.0; + let target_weight = 95.0; + let percent_component: f32; + if fkb.get_int() > 0 { + percent_component = (10.0 + fkb.get_int() as f32) * 0.1 * (1.0 + fkb.get_int() as f32 * 0.5); + } else { + percent_component = (target_percent + damage.get_num()) * 0.1 * + (1.0 + damage.get_num() * 0.5); + } + let weight_component: f32 = 200.0 / (target_weight + 100.0); + let kb: f32 = (percent_component * weight_component * 1.4 + 18.0) * + (kbg.get_int() as f32 * 0.01) + bkb.get_int() as f32; + color_scale = unlerp_bounded(50.0, 200.0, kb); + } + // non-linear scaling to magnify + // differences at lower values + let color_t: f32 = 0.8 + 0.2 * color_scale.powf(0.5); + let color = color_lerp( + Vector3f{x: 1.0, y: 1.0, z: 1.0}, + ID_COLORS[(id.get_int() % 8) as usize], + color_t, + 2.0 + ); + generate_hitbox_effects(&mut l2c_agent, bone, size, x, y, z, x2, y2, z2, color); + } +} + +#[allow(unused_unsafe)] +#[skyline::hook(replace = sv_animcmd::CATCH)] +unsafe fn handle_catch(lua_state: u64) { + let mut l2c_agent = L2CAgent::new(lua_state); + + // get all necessary grabbox params + let id = l2c_agent.pop_lua_stack(1); // int + let joint = l2c_agent.pop_lua_stack(2); // hash40 + let size = l2c_agent.pop_lua_stack(3); // float + let x = l2c_agent.pop_lua_stack(4); // float + let y = l2c_agent.pop_lua_stack(5); // float + let z = l2c_agent.pop_lua_stack(6); // float + let x2 = l2c_agent.pop_lua_stack(7); // float or void + let y2 = l2c_agent.pop_lua_stack(8); // float or void + let z2 = l2c_agent.pop_lua_stack(9); // float or void + + original!()(lua_state); + + if menu.HITBOX_VIS && is_training_mode() { + generate_hitbox_effects(&mut l2c_agent, joint, size, x, y, z, x2, y2, z2, ID_COLORS[(id.get_int() + 3 % 8) as usize]); + } +} + +pub unsafe fn is_shielding(module_accessor: *mut BattleObjectModuleAccessor) -> bool { + let status_kind = StatusModule::status_kind(module_accessor) as i32; + (FIGHTER_STATUS_KIND_GUARD_ON..=FIGHTER_STATUS_KIND_GUARD_OFF).contains(&status_kind) +} + +#[allow(unused_unsafe)] +#[skyline::hook(replace = AttackModule::clear_all)] +pub unsafe fn handle_clear_all(module_accessor: *mut BattleObjectModuleAccessor) { + if is_training_mode() { + // only if we're not shielding + if !is_shielding(module_accessor) { + EffectModule::kill_kind(module_accessor, Hash40{hash: hash40("sys_shield")}, false, true); + } + } + + original!()(module_accessor); +} + +#[allow(unused_unsafe)] +#[skyline::hook(replace = GrabModule::set_rebound)] +pub unsafe fn handle_set_rebound(module_accessor: *mut BattleObjectModuleAccessor, rebound: bool) { + if is_training_mode() && rebound == false { + // only if we're not shielding + if !is_shielding(module_accessor) { + EffectModule::kill_kind(module_accessor, Hash40{hash: hash40("sys_shield")}, false, true); + } + } + + original!()(module_accessor, rebound); +} + +pub fn hitbox_visualization() { + println!("Applying hitbox visualization mods."); + skyline::install_hook!(handle_attack); + skyline::install_hook!(handle_catch); + skyline::install_hook!(handle_clear_all); + skyline::install_hook!(handle_set_rebound); +} diff --git a/src/lib.rs b/src/lib.rs index 98dec29..7aba96c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,30 +1,66 @@ #![feature(proc_macro_hygiene)] +#![allow(unused_imports)] +#![allow(unused_variables)] +#![allow(dead_code)] +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![feature(with_options)] -use skyline::{hook, install_hook}; +mod hitbox_visualizer; +mod common; -extern "C" fn test() -> u32 { - 2 +use smash::hash40; +use smash::lib::lua_const::{*}; +use smash::lib::{self, L2CAgent, L2CValue}; +use smash::app::{self}; +use smash::app::lua_bind::{*}; +use smash::app::sv_animcmd::{self}; +use smash::app::sv_system::{self}; +use skyline::libc::{size_t, c_int, c_void, strlen}; +use smash::Result; +use skyline::nn; +use skyline::patching::patch_data_from_text; +use skyline::{from_c_str, c_str}; +use std::fs; + +#[allow(unused_unsafe)] +#[skyline::hook(replace = nn::ro::LoadModule)] +pub unsafe fn handle_load_module( + p_out_module: *mut skyline::nn::ro::Module, + p_image: *const c_void, + buffer: *mut c_void, + buffer_size: size_t, + flag: c_int) -> Result { + + let ret = original!()(p_out_module, p_image, buffer, buffer_size, flag); + + let name = from_c_str(&(*p_out_module).Name as *const u8); + println!("[handleLoadModule] NRO name: {}\n", name); + let text_start = (*(*p_out_module).ModuleObject).module_base; + println!("Module base: {}\n", text_start); + if name.starts_with("common") { + println!("Is common!"); + // raw const_value_table is at : 0x635b70 + let fighter_status_kind_fall : u64 = 0x8ee6c39e9be4f0b5; + let res = match patch_data_from_text(text_start as *const u8, 0x6362b8, &fighter_status_kind_fall) { + Ok(v) => format!("Patched!"), + Err(e) => format!("Error patching with e: {}", e) + }; + + println!("{}", res); + } + + ret } -#[hook(replace = test)] -fn test_replacement() -> u32 { - - let original_test = original!(); - - let val = original_test(); - - println!("[override] original value: {}", val); // 2 - - val + 1 -} - -#[skyline::main(name = "skyline_rs_template")] +#[skyline::main(name = "test")] pub fn main() { - println!("Hello from Skyline Rust Plugin!"); + println!("Training modpack initialized."); + hitbox_visualizer::hitbox_visualization(); - install_hook!(test_replacement); + println!("OpenMode_Write: {} {}", nn::fs::OpenMode_OpenMode_Write, nn::fs::OpenMode_OpenMode_Write as i32); + let buffer = format!("{:x}", &common::menu as *const _ as u64); + println!("Writing training_modpack.log with {}...\n", buffer); - let x = test(); - - println!("[main] test returned: {}", x); // 3 + // skyline::install_hook!(handle_load_module); }