diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3e54806..2a653ea 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ // https://github.com/microsoft/vscode-dev-containers/tree/v0.140.1/containers/docker-from-docker { "name": "Cargo Skyline", - "image": "jugeeya/cargo-skyline:3.2.0", + "image": "jugeeya/cargo-skyline:3.2.0-no-dkp", "mounts": [ "source=ultimatetrainingmodpack-bashhistory,target=/commandhistory,type=volume" ], diff --git a/.gitattributes b/.gitattributes index 4f15109..6358286 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,4 @@ .devcontainer/** linguist-vendored .vscode/** linguist-vendored +ryujinx_build.ps1 linguist-vendored +ryujinx_build.sh linguist-vendored diff --git a/Cargo.toml b/Cargo.toml index 9d2f93f..5167240 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ skyline = { git = "https://github.com/ultimate-research/skyline-rs.git" } skyline_smash = { git = "https://github.com/ultimate-research/skyline-smash.git", branch = "no-cache" } skyline-web = { git = "https://github.com/skyline-rs/skyline-web.git" } bitflags = "1.2.1" +bitfield-struct = "0.1.8" parking_lot = { version = "0.12.0", features = ["nightly"] } lazy_static = "1.4.0" owo-colors = "2.1.0" diff --git a/ryujinx_build.ps1 b/ryujinx_build.ps1 new file mode 100644 index 0000000..c65fa26 --- /dev/null +++ b/ryujinx_build.ps1 @@ -0,0 +1,4 @@ +$IP=(Test-Connection -ComputerName (hostname) -Count 1 | Select -ExpandProperty IPV4Address).IPAddressToString +cargo skyline build --release +Copy-Item target/aarch64-skyline-switch/release/libtraining_modpack.nro 'C:\Users\Jdsam\AppData\Roaming\Ryujinx\mods\contents\01006A800016E000\romfs\skyline\plugins\' +cargo skyline listen --ip=$IP \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index c6d03c8..139c437 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ #![feature(const_mut_refs)] #![feature(exclusive_range_pattern)] #![feature(once_cell)] +#![feature(c_variadic)] #![allow( clippy::borrow_interior_mutable_const, clippy::not_unsafe_ptr_arg_deref, @@ -84,6 +85,8 @@ pub fn main() { EVENT_QUEUE.push(Event::smash_open()); } + training::ui_hacks::install_hooks(); + hitbox_visualizer::hitbox_visualization(); hazard_manager::hazard_manager(); training::training_mods(); diff --git a/src/training/combo.rs b/src/training/combo.rs index 0178bc0..c5a888c 100644 --- a/src/training/combo.rs +++ b/src/training/combo.rs @@ -44,15 +44,9 @@ unsafe fn is_actionable(module_accessor: *mut app::BattleObjectModuleAccessor) - }) || CancelModule::is_enable_cancel(module_accessor) } -fn update_frame_advantage( - module_accessor: *mut app::BattleObjectModuleAccessor, - new_frame_adv: i32, -) { +fn update_frame_advantage(new_frame_adv: i32) { unsafe { FRAME_ADVANTAGE = new_frame_adv; - if MENU.frame_advantage == consts::OnOff::On { - raygun_printer::print_string(&mut *module_accessor, &format!("{}", FRAME_ADVANTAGE)); - } } } @@ -87,7 +81,6 @@ pub unsafe fn is_enable_transition_term( let cpu_module_accessor = get_module_accessor(FighterId::CPU); if was_in_hitstun(cpu_module_accessor) || was_in_shieldstun(cpu_module_accessor) { update_frame_advantage( - module_accessor, (CPU_ACTIVE_FRAME as i64 - PLAYER_ACTIVE_FRAME as i64) as i32, ); } @@ -139,10 +132,7 @@ pub unsafe fn get_command_flag_cat(module_accessor: &mut app::BattleObjectModule // if both are now active if PLAYER_ACTIONABLE && CPU_ACTIONABLE && FRAME_ADVANTAGE_CHECK { if was_in_hitstun(cpu_module_accessor) || was_in_shieldstun(cpu_module_accessor) { - update_frame_advantage( - player_module_accessor, - (CPU_ACTIVE_FRAME as i64 - PLAYER_ACTIVE_FRAME as i64) as i32, - ); + update_frame_advantage((CPU_ACTIVE_FRAME as i64 - PLAYER_ACTIVE_FRAME as i64) as i32); } frame_counter::stop_counting(FRAME_COUNTER_INDEX); diff --git a/src/training/mod.rs b/src/training/mod.rs index f1bf3b6..b99d595 100644 --- a/src/training/mod.rs +++ b/src/training/mod.rs @@ -23,6 +23,8 @@ pub mod sdi; pub mod shield; pub mod tech; pub mod throw; +pub mod ui; +pub mod ui_hacks; mod air_dodge_direction; mod attack_angle; diff --git a/src/training/ui/mod.rs b/src/training/ui/mod.rs new file mode 100644 index 0000000..2a8d67b --- /dev/null +++ b/src/training/ui/mod.rs @@ -0,0 +1,432 @@ +#![allow(dead_code)] + +use bitfield_struct::bitfield; + +mod resources; +pub use resources::*; + +macro_rules! c_str { + ($l:tt) => { + [$l.as_bytes(), "\u{0}".as_bytes()].concat().as_ptr() + }; +} + +#[repr(C)] +#[derive(Debug)] +pub struct ResAnimationContent { + name: [skyline::libc::c_char; 28], + count: u8, + anim_content_type: u8, + padding: [skyline::libc::c_char; 2], +} + +/** + * Block Header Kind + * + * ANIM_TAG: pat1 + * ANIM_SHARE: pah1 + * ANIM_INFO: pai1 + */ + +#[repr(C)] +#[derive(Debug)] +pub struct ResAnimationBlock { + block_header_kind: u32, + block_header_size: u32, + num_frames: u16, + is_loop: bool, + pad: [skyline::libc::c_char; 1], + file_count: u16, + anim_cont_count: u16, + anim_cont_offsets_offset: u32, +} + +#[repr(C)] +pub struct AnimTransform { + res_animation_block: *mut ResAnimationBlock, + frame: f32, + enabled: bool, +} + +impl AnimTransform { + pub unsafe fn parse_anim_transform(&mut self) { + let res_animation_block_data_start = (*self).res_animation_block as u64; + let res_animation_block = &*(*self).res_animation_block; + let mut anim_cont_offsets = (res_animation_block_data_start + + res_animation_block.anim_cont_offsets_offset as u64) + as *const u32; + for anim_cont_idx in 0..res_animation_block.anim_cont_count { + let anim_cont_offset = *anim_cont_offsets; + let res_animation_cont = (res_animation_block_data_start + anim_cont_offset as u64) + as *const ResAnimationContent; + + let name = skyline::try_from_c_str((*res_animation_cont).name.as_ptr()) + .unwrap_or("UNKNOWN".to_string()); + let anim_type = (*res_animation_cont).anim_content_type; + let frame = (*self).frame; + println!( + "animTransform/resAnimationContent_{anim_cont_idx}: {name} of type {anim_type} on frame {frame}", + ); + // AnimContentType 1 == MATERIAL + if (name == "dig_3_anim" || name == "set_dmg_num_3") && anim_type == 1 { + (*self).frame = 4.0; + } + if (name == "dig_2_anim" || name == "set_dmg_num_2") && anim_type == 1 { + (*self).frame = 2.0; + } + if (name == "dig_1_anim" || name == "set_dmg_num_1") && anim_type == 1 { + (*self).frame = 8.0; + } + + anim_cont_offsets = anim_cont_offsets.add(1); + } + } +} + +#[repr(C)] +#[derive(Debug)] +pub struct AnimTransformNode { + prev: *mut AnimTransformNode, + next: *mut AnimTransformNode, +} + +impl AnimTransformNode { + pub unsafe fn iterate_anim_list(&mut self) { + let mut curr = self as *mut AnimTransformNode; + let mut _anim_idx = 0; + while !curr.is_null() { + // Only if valid + if curr != (*curr).next { + let anim_transform = (curr as *mut u64).add(2) as *mut AnimTransform; + anim_transform.as_mut().unwrap().parse_anim_transform(); + } + + curr = (*curr).next; + _anim_idx += 1; + if curr == self as *mut AnimTransformNode || curr == (*curr).next { + break; + } + } + } +} + +#[repr(C)] +pub struct AnimTransformList { + root: AnimTransformNode, +} + +#[repr(C, align(8))] +#[derive(Debug, Copy, Clone)] +pub struct Pane { + vtable: u64, + pub link: PaneNode, + pub parent: *mut Pane, + pub children_list: PaneNode, + pub pos_x: f32, + pub pos_y: f32, + pos_z: f32, + rot_x: f32, + rot_y: f32, + rot_z: f32, + pub scale_x: f32, + pub scale_y: f32, + pub size_x: f32, + pub size_y: f32, + pub flags: u8, + pub alpha: u8, + pub global_alpha: u8, + base_position: u8, + flag_ex: u8, + // This is supposed to be 3 bytes padding + flags of 4 bytes + padding of 4 bytes + pad: [u8; 3 + 4 + 4 + 8], + global_matrix: [[f32; 3]; 4], + user_matrix: *const u64, + ext_user_data_list: *const u64, + pub name: [skyline::libc::c_char; 25], + user_data: [skyline::libc::c_char; 9], +} + +impl Pane { + pub unsafe fn find_pane_by_name_recursive(&self, s: &str) -> Option<&mut Pane> { + find_pane_by_name_recursive(self, c_str!(s)).as_mut() + } + + pub unsafe fn find_pane_by_name(&self, s: &str, recursive: bool) -> Option<&mut Pane> { + find_pane_by_name(self, c_str!(s), recursive).as_mut() + } + + pub unsafe fn set_text_string(&self, s: &str) { + pane_set_text_string(self, c_str!(s)); + } + + pub unsafe fn remove_child(&self, child: &Pane) { + pane_remove_child(self, child as *const Pane); + } + + pub unsafe fn append_child(&self, child: &Pane) { + pane_append_child(self, child as *const Pane); + } + + pub unsafe fn as_parts(&mut self) -> *mut Parts { + self as *mut Pane as *mut Parts + } +} + +#[repr(C)] +#[derive(Debug)] +pub struct Parts { + pub pane: Pane, + // Some IntrusiveList + link: PaneNode, + pub layout: *mut Layout, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct Picture { + pub pane: Pane, + material: *mut u8, + vertex_colors: [[u8; 4]; 4], + shared_memory: *mut u8, +} + +#[bitfield(u16)] +pub struct TextBoxBits { + #[bits(2)] + text_alignment: u8, + #[bits(1)] + is_ptdirty: u8, + shadow_enabled: bool, + invisible_border_enabled: bool, + double_drawn_border_enabled: bool, + width_limit_enabled: bool, + per_character_transform_enabled: bool, + center_ceiling_enabled: bool, + per_character_transform_split_by_char_width: bool, + per_character_transform_auto_shadow_alpha: bool, + draw_from_right_to_left: bool, + per_character_transform_origin_to_center: bool, + per_character_transform_fix_space: bool, + linefeed_by_character_height_enabled: bool, + per_character_transform_split_by_char_width_insert_space_enabled: bool, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct TextBox { + pub pane: Pane, + // Actually a union + m_text_buf: *const skyline::libc::c_char, + m_p_text_id: *const skyline::libc::c_char, + m_text_colors: [[u8; 4]; 2], + m_p_font: *const skyline::libc::c_void, + m_font_size_x: f32, + m_font_size_y: f32, + m_line_space: f32, + m_char_space: f32, + + // Actually a union + m_p_tag_processor: *const skyline::libc::c_char, + + m_text_buf_len: u16, + m_text_len: u16, + + m_bits: TextBoxBits, + m_text_position: u8, + + m_is_utf8: bool, + + m_italic_ratio: f32, + + m_shadow_offset_x: f32, + m_shadow_offset_y: f32, + m_shadow_scale_x: f32, + m_shadow_scale_y: f32, + m_shadow_top_color: [u8; 4], + m_shadow_bottom_color: [u8; 4], + m_shadow_italic_ratio: f32, + + m_p_line_width_offset: *const skyline::libc::c_void, + + pub m_p_material: *mut Material, + m_p_disp_string_buf: *const skyline::libc::c_void, + + m_p_per_character_transform: *const skyline::libc::c_void, +} + +impl TextBox { + pub fn set_color(&mut self, r: u8, g: u8, b: u8, a: u8) { + let input_color = [r, g, b, a]; + let mut dirty: bool = false; + self.m_text_colors + .iter_mut() + .for_each(|top_or_bottom_color| { + if *top_or_bottom_color != input_color { + dirty = true; + } + *top_or_bottom_color = input_color; + }); + + if dirty { + self.m_bits.set_is_ptdirty(1); + } + } +} + +#[repr(C)] +pub union MaterialColor { + byte_color: [[u8; 4]; 2], + p_float_color: *mut *mut f32, +} + +use std::fmt; +impl fmt::Debug for MaterialColor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + unsafe { + f.debug_struct("MaterialColor") + .field("byteColor", &self.byte_color) + .field("pFloatColor", &self.p_float_color) + .finish() + } + } +} + +#[repr(C)] +#[derive(Debug, PartialEq)] +pub enum MaterialColorType { + BlackColor, + WhiteColor, +} + +#[repr(C)] +#[derive(Debug, PartialEq)] +pub enum MaterialFlags { + FlagsUserAllocated, + FlagsTextureOnly, + FlagsThresholdingAlphaInterpolation, + FlagsBlackColorFloat, + FlagsWhiteColorFloat, + FlagsDynamicAllocatedColorData, +} + +#[repr(C)] +#[derive(Debug)] +pub struct Material { + vtable: u64, + m_colors: MaterialColor, + // Actually a struct + m_mem_cap: u32, + // Actually a struct + m_mem_count: u32, + m_p_mem: *mut skyline::libc::c_void, + m_p_shader_info: *const skyline::libc::c_void, + m_p_name: *const skyline::libc::c_char, + m_vertex_shader_constant_buffer_offset: u32, + m_pixel_shader_constant_buffer_offset: u32, + m_p_user_shader_constant_buffer_information: *const skyline::libc::c_void, + m_p_blend_state: *const skyline::libc::c_void, + m_packed_values: u8, + m_flag: u8, + m_shader_variation: u16, +} + +impl Material { + pub fn set_color_int(&mut self, idx: usize, r: u8, g: u8, b: u8, a: u8) { + let input_color = [r, g, b, a]; + unsafe { + self.m_colors.byte_color[idx] = input_color; + } + } + + pub fn set_color_float(&mut self, idx: usize, r: f32, g: f32, b: f32, a: f32) { + unsafe { + *(*(self.m_colors.p_float_color.add(idx)).add(0)) = r; + *(*(self.m_colors.p_float_color.add(idx)).add(1)) = g; + *(*(self.m_colors.p_float_color.add(idx)).add(2)) = b; + *(*(self.m_colors.p_float_color.add(idx)).add(3)) = a; + } + } + + pub fn set_color(&mut self, color_type: MaterialColorType, r: f32, g: f32, b: f32, a: f32) { + let (is_float_flag, idx) = if color_type == MaterialColorType::BlackColor { + (MaterialFlags::FlagsBlackColorFloat as u8, 0) + } else { + (MaterialFlags::FlagsWhiteColorFloat as u8, 1) + }; + if self.m_flag & (0x1 << is_float_flag) != 0 { + self.set_color_float(idx, r, g, b, a); + } else { + self.set_color_int(idx, r as u8, g as u8, b as u8, a as u8); + } + } + + pub fn set_white_color(&mut self, r: f32, g: f32, b: f32, a: f32) { + self.set_color(MaterialColorType::WhiteColor, r, g, b, a); + } + + pub fn set_black_color(&mut self, r: f32, g: f32, b: f32, a: f32) { + self.set_color(MaterialColorType::BlackColor, r, g, b, a); + } +} + +#[repr(C)] +#[derive(Debug)] +pub struct RawLayout { + pub anim_trans_list: AnimTransformNode, + pub root_pane: *const Pane, + group_container: u64, + layout_size: f64, + pub layout_name: *const skyline::libc::c_char, +} + +#[derive(Debug, Copy, Clone)] +pub struct PaneNode { + pub prev: *mut PaneNode, + pub next: *mut PaneNode, +} + +#[repr(C)] +pub struct Group { + pane_list: PaneNode, + name: *const skyline::libc::c_char, +} + +#[repr(C)] +pub struct GroupContainer {} + +#[repr(C)] +#[derive(Debug)] +pub struct Layout { + vtable: u64, + pub raw_layout: RawLayout, +} + +#[skyline::from_offset(0x59970)] +pub unsafe fn find_pane_by_name_recursive( + pane: *const Pane, + s: *const skyline::libc::c_char, +) -> *mut Pane; + +#[skyline::from_offset(0x583c0)] +pub unsafe fn find_pane_by_name( + pane: *const Pane, + s: *const skyline::libc::c_char, + recursive: bool, +) -> *mut Pane; + +#[skyline::from_offset(0x37a1270)] +pub unsafe fn pane_set_text_string(pane: *const Pane, s: *const skyline::libc::c_char); + +#[skyline::from_offset(0x58290)] +pub unsafe fn pane_remove_child(pane: *const Pane, child: *const Pane); + +#[skyline::from_offset(0x58250)] +pub unsafe fn pane_append_child(pane: *const Pane, child: *const Pane); + +pub unsafe fn get_typeinfo_name(cls_vtable: u64) -> String { + let typeinfo_ptr_addr = (cls_vtable - 8) as *const u64; + let typeinfo_addr = *typeinfo_ptr_addr; + let typeinfo_name_ptr_addr = (typeinfo_addr + 8) as *const u64; + let type_info_name_addr = (*typeinfo_name_ptr_addr) as *const skyline::libc::c_char; + skyline::from_c_str(type_info_name_addr) +} diff --git a/src/training/ui/resources.rs b/src/training/ui/resources.rs new file mode 100644 index 0000000..236599c --- /dev/null +++ b/src/training/ui/resources.rs @@ -0,0 +1,170 @@ +// Maybe needs a vtable. +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ResVec2 { + x: f32, + y: f32, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ResVec3 { + x: f32, + y: f32, + z: f32, +} + +impl ResVec3 { + pub fn default() -> ResVec3 { + ResVec3 { + x: 0.0, + y: 0.0, + z: 0.0, + } + } + + pub fn new(x: f32, y: f32, z: f32) -> ResVec3 { + ResVec3 { x, y, z } + } +} + +// Maybe needs a vtable. +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ResColor { + r: u8, + g: u8, + b: u8, + a: u8, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ResPane { + block_header_kind: u32, + block_header_size: u32, + flag: u8, + base_position: u8, + alpha: u8, + flag_ex: u8, + pub name: [skyline::libc::c_char; 24], + pub user_data: [skyline::libc::c_char; 8], + pub pos: ResVec3, + rot_x: f32, + rot_y: f32, + rot_z: f32, + pub scale_x: f32, + pub scale_y: f32, + pub size_x: f32, + pub size_y: f32, +} + +impl ResPane { + // For null pane + pub fn new(name: &str) -> ResPane { + let mut pane = ResPane { + block_header_kind: u32::from_le_bytes([b'p', b'a', b'n', b'1']), + block_header_size: 84, + /// Visible | InfluencedAlpha + flag: 0x3, + base_position: 0, + alpha: 0xFF, + flag_ex: 0, + name: [0; 24], + user_data: [0; 8], + pos: ResVec3 { + x: 0.0, + y: 0.0, + z: 0.0, + }, + rot_x: 0.0, + rot_y: 0.0, + rot_z: 0.0, + scale_x: 1.0, + scale_y: 1.0, + size_x: 30.0, + size_y: 40.0, + }; + pane.set_name(name); + pane + } + + pub fn set_name(&mut self, name: &str) { + assert!( + name.len() <= 24, + "Name of pane must be at most 24 characters" + ); + unsafe { + std::ptr::copy_nonoverlapping(name.as_ptr(), self.name.as_mut_ptr(), name.len()); + } + } + + pub fn set_pos(&mut self, pos: ResVec3) { + self.pos = pos; + } + + pub fn name_matches(&self, other: &str) -> bool { + self.name + .iter() + .take_while(|b| **b != 0) + .map(|b| *b as char) + .collect::() + == other + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ResTextBox { + pub pane: ResPane, + text_buf_bytes: u16, + text_str_bytes: u16, + material_idx: u16, + font_idx: u16, + text_position: u8, + text_alignment: u8, + text_box_flag: u16, + italic_ratio: f32, + text_str_offset: u32, + text_cols: [ResColor; 2], + font_size: ResVec2, + char_space: f32, + line_space: f32, + text_id_offset: u32, + shadow_offset: ResVec2, + shadow_scale: ResVec2, + shadow_cols: [ResColor; 2], + shadow_italic_ratio: f32, + line_width_offset_offset: u32, + per_character_transform_offset: u32, + /* Additional Info + uint16_t text[]; // Text. + char textId[]; // The text ID. + u8 lineWidthOffsetCount; // The quantity of widths and offsets for each line. + float lineOffset[]; // The offset for each line. + float lineWidth[]; // The width of each line. + ResPerCharacterTransform perCharacterTransform // Information for per-character animation. + ResAnimationInfo perCharacterTransformAnimationInfo; // Animation information for per-character animation. + */ +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ResPicture { + pub pane: ResPane, + vtx_cols: [ResColor; 4], + material_idx: u16, + tex_coord_count: u8, + flags: u8, + /* Additional Info + ResVec2 texCoords[texCoordCount][VERTEX_MAX]; + uint32_t shapeBinaryIndex; + */ +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ResPictureWithTex { + pub picture: ResPicture, + tex_coords: [[ResVec2; TEX_COORD_COUNT]; 4], +} diff --git a/src/training/ui_hacks.rs b/src/training/ui_hacks.rs new file mode 100644 index 0000000..8968dd3 --- /dev/null +++ b/src/training/ui_hacks.rs @@ -0,0 +1,191 @@ +use crate::training::combo::FRAME_ADVANTAGE; +use crate::training::ui::*; +use training_mod_consts::OnOff; + +#[skyline::hook(offset = 0x4b620)] +pub unsafe fn handle_draw(layout: *mut Layout, draw_info: u64, cmd_buffer: u64) { + let layout_name = skyline::from_c_str((*layout).raw_layout.layout_name); + let layout_root_pane = &*(*layout).raw_layout.root_pane; + let _anim_list = &mut (*layout).raw_layout.anim_trans_list; + // anim_list.iterate_anim_list(); + + if layout_name == "info_training" { + if let Some(parent) = layout_root_pane.find_pane_by_name_recursive("trMod_disp_0") { + if crate::common::MENU.frame_advantage == OnOff::On { + parent.alpha = 255; + parent.global_alpha = 255; + } else { + parent.alpha = 0; + parent.global_alpha = 0; + } + } + + if let Some(header) = layout_root_pane.find_pane_by_name_recursive("trMod_disp_0_header") { + header.set_text_string("Frame Advantage"); + } + + if let Some(text) = layout_root_pane.find_pane_by_name_recursive("trMod_disp_0_txt") { + text.set_text_string(format!("{FRAME_ADVANTAGE}").as_str()); + let text = text as *mut Pane as *mut TextBox; + if FRAME_ADVANTAGE < 0 { + (*text).set_color(200, 8, 8, 255); + } else if FRAME_ADVANTAGE == 0 { + (*text).set_color(0, 0, 0, 255); + } else { + (*text).set_color(31, 198, 0, 255); + } + } + } + + original!()(layout, draw_info, cmd_buffer); +} + +#[skyline::hook(offset = 0x493a0)] +pub unsafe fn layout_build_parts_impl( + layout: *mut Layout, + out_build_result_information: *mut u8, + device: *const u8, + data: *mut u8, + parts_build_data_set: *const u8, + build_arg_set: *const u8, + build_res_set: *const u8, + kind: u32, +) -> *mut Pane { + let layout_name = skyline::from_c_str((*layout).raw_layout.layout_name); + let _kind_str: String = kind.to_le_bytes().map(|b| b as char).iter().collect(); + + if layout_name != "info_training" { + return original!()( + layout, + out_build_result_information, + device, + data, + parts_build_data_set, + build_arg_set, + build_res_set, + kind, + ); + } + + let root_pane = (*layout).raw_layout.root_pane; + + let block = data as *mut ResPane; + let num_display_panes = 1; + (0..num_display_panes).for_each(|idx| { + let mod_prefix = "trMod_disp_"; + let parent_name = format!("{mod_prefix}{idx}"); + let pic_name = format!("{mod_prefix}{idx}_base"); + let header_name = format!("{mod_prefix}{idx}_header"); + let txt_name = format!("{mod_prefix}{idx}_txt"); + + if (*block).name_matches("pic_numbase_01") { + let block = block as *mut ResPictureWithTex<1>; + let mut pic_block = (*block).clone(); + pic_block.picture.pane.set_name(pic_name.as_str()); + pic_block.picture.pane.set_pos(ResVec3::default()); + let pic_pane = original!()( + layout, + out_build_result_information, + device, + &mut pic_block as *mut ResPictureWithTex<1> as *mut u8, + parts_build_data_set, + build_arg_set, + build_res_set, + kind, + ); + (*(*pic_pane).parent).remove_child(&*pic_pane); + + // pic is loaded first, we can create our parent pane here. + let disp_pane_kind = u32::from_le_bytes([b'p', b'a', b'n', b'1']); + let mut disp_pane_block = ResPane::new(parent_name.as_str()); + disp_pane_block.set_pos(ResVec3::new(806.0, 390.0 - (idx as f32 * 110.0), 0.0)); + let disp_pane = original!()( + layout, + out_build_result_information, + device, + &mut disp_pane_block as *mut ResPane as *mut u8, + parts_build_data_set, + build_arg_set, + build_res_set, + disp_pane_kind, + ); + (*(*disp_pane).parent).remove_child(&*disp_pane); + (*root_pane).append_child(&*disp_pane); + (*disp_pane).append_child(&*pic_pane); + } + + if (*block).name_matches("set_txt_num_01") { + let disp_pane = (*root_pane) + .find_pane_by_name(parent_name.as_str(), true) + .unwrap(); + + let block = data as *mut ResTextBox; + let mut text_block = (*block).clone(); + text_block.pane.set_name(txt_name.as_str()); + text_block.pane.set_pos(ResVec3::new(-10.0, -25.0, 0.0)); + let text_pane = original!()( + layout, + out_build_result_information, + device, + &mut text_block as *mut ResTextBox as *mut u8, + parts_build_data_set, + build_arg_set, + build_res_set, + kind, + ); + (*text_pane).set_text_string(format!("Pane {idx}!").as_str()); + // Ensure Material Colors are not hardcoded so we can just use SetTextColor. + (*((*(text_pane as *mut TextBox)).m_p_material)) + .set_white_color(255.0, 255.0, 255.0, 255.0); + (*((*(text_pane as *mut TextBox)).m_p_material)).set_black_color(0.0, 0.0, 0.0, 255.0); + (*(*text_pane).parent).remove_child(&*text_pane); + (*disp_pane).append_child(&*text_pane); + } + + if (*block).name_matches("txt_cap_01") { + let disp_pane = (*root_pane) + .find_pane_by_name(parent_name.as_str(), true) + .unwrap(); + + let block = data as *mut ResTextBox; + let mut header_block = (*block).clone(); + header_block.pane.set_name(header_name.as_str()); + header_block.pane.set_pos(ResVec3::new(0.0, 25.0, 0.0)); + let header_pane = original!()( + layout, + out_build_result_information, + device, + &mut header_block as *mut ResTextBox as *mut u8, + parts_build_data_set, + build_arg_set, + build_res_set, + kind, + ); + (*header_pane).set_text_string(format!("Header {idx}").as_str()); + // Ensure Material Colors are not hardcoded so we can just use SetTextColor. + (*((*(header_pane as *mut TextBox)).m_p_material)) + .set_white_color(255.0, 255.0, 255.0, 255.0); + (*((*(header_pane as *mut TextBox)).m_p_material)) + .set_black_color(0.0, 0.0, 0.0, 255.0); + // Header should be white text + (*(header_pane as *mut TextBox)).set_color(255, 255, 255, 255); + (*(*header_pane).parent).remove_child(&*header_pane); + (*disp_pane).append_child(&*header_pane); + } + }); + + original!()( + layout, + out_build_result_information, + device, + data, + parts_build_data_set, + build_arg_set, + build_res_set, + kind, + ) +} + +pub fn install_hooks() { + skyline::install_hooks!(handle_draw, layout_build_parts_impl,); +}