1
0
Fork 0
mirror of https://github.com/jugeeya/UltimateTrainingModpack.git synced 2024-11-20 00:46:34 +00:00

Use layout.arc to Build UI (#473)

* Update lib.rs

* Remaining fixes

* Initial

* Forgot to commit

* Clippy

* Initial

* Use feature gate

* Fixes

* layout.arc actually working. Add training_mod_menu_button

* Add docstring, fix from static

* Use include_flate to keep binary smaller

* Add most menu features back

* Add help, tabs

* Add help, tabs

* Small refactor

* Fix clippy, some other issues

* Updated menu buttons, inactive text colour (btns), layouts

---------

Co-authored-by: Matthew Edell <edell.matthew@gmail.com>
This commit is contained in:
jugeeya 2023-02-09 07:51:40 -08:00 committed by GitHub
parent d23dcdf968
commit 3d6ea34eeb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 304 additions and 1169 deletions

View file

@ -13,10 +13,10 @@ skyline_smash = { git = "https://github.com/ultimate-research/skyline-smash.git"
skyline-web = { git = "https://github.com/skyline-rs/skyline-web.git" }
bitflags = "1.2.1"
parking_lot = { version = "0.12.0", features = ["nightly"] }
include-flate = "0.1.4"
lazy_static = "1.4.0"
owo-colors = "2.1.0"
once_cell = "1.12.0"
ramhorns = "0.12.0"
paste = "1.0"
num = "0.4.0"
num-derive = "0.3"
@ -25,6 +25,7 @@ wsl = "0.1.0"
strum = "0.21.0"
strum_macros = "0.21.0"
minreq = { version = "2", features = ["https-native", "json-using-serde"] }
sarc = { version = "1.2.0", features = [], default_features = false }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
toml = "0.5.9"
@ -54,4 +55,4 @@ plugin-dependencies = [
[features]
outside_training_mode = []
web_session_single_thread = []
layout_arc_from_file = []

2
ryujinx_build.ps1 vendored
View file

@ -1,4 +1,4 @@
$IP=(Test-Connection -ComputerName (hostname) -Count 1 | Select -ExpandProperty IPV4Address).IPAddressToString
cargo skyline build --release
cargo skyline build --release --features layout_arc_from_file
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

2
ryujinx_build.sh vendored
View file

@ -6,7 +6,7 @@ SMASH_APPLICATION_PATH="C:\Users\Jdsam\Downloads\Super Smash Bros. Ultimate (Wor
RYUJINX_SMASH_SKYLINE_PLUGINS_PATH="/mnt/c/Users/Jdsam/AppData/Roaming/Ryujinx/mods/contents/01006a800016e000/romfs/skyline/plugins"
# Build with release feature
cargo skyline build --release
cargo skyline build --release --features layout_arc_from_file
# Copy over to plugins path
cp target/aarch64-skyline-switch/release/libtraining_modpack.nro $RYUJINX_SMASH_SKYLINE_PLUGINS_PATH

BIN
src/static/layout.arc Normal file

Binary file not shown.

View file

@ -2,29 +2,21 @@ use crate::training::ui;
use skyline::nn::ui2d::*;
use smash::ui2d::{SmashPane, SmashTextBox};
pub static NUM_DISPLAY_PANES: usize = 1;
macro_rules! display_parent_fmt {
($x:ident) => {
format!("trMod_disp_{}", $x).as_str()
};
}
macro_rules! display_pic_fmt {
($x:ident) => {
format!("trMod_disp_{}_base", $x).as_str()
format!("TrModDisp{}", $x).as_str()
};
}
macro_rules! display_header_fmt {
($x:ident) => {
format!("trMod_disp_{}_header", $x).as_str()
format!("TrModDisp{}Header", $x).as_str()
};
}
macro_rules! display_txt_fmt {
($x:ident) => {
format!("trMod_disp_{}_txt", $x).as_str()
format!("TrModDisp{}Txt", $x).as_str()
};
}
@ -57,91 +49,7 @@ pub unsafe fn draw(root_pane: &mut Pane) {
if let Some(text) = root_pane.find_pane_by_name_recursive(display_txt_fmt!(notification_idx)) {
let text = text.as_textbox();
text.set_text_string(message);
text.set_default_material_colors();
text.set_color(color.r, color.g, color.b, color.a);
}
}
pub static BUILD_PIC_BASE: ui::PaneCreationCallback = |_, root_pane, original_build, layout, out_build_result_information, device, block, parts_build_data_set, build_arg_set, build_res_set, kind| unsafe {
macro_rules! build {
($block: ident, $resTyp: ty, $kind:ident, $typ: ty) => {
paste::paste! {
&mut *(original_build(layout, out_build_result_information, device, &mut $block as *mut $resTyp as *mut ResPane, parts_build_data_set, build_arg_set, build_res_set, $kind,) as *mut $typ)
}
};
}
(0..NUM_DISPLAY_PANES).for_each(|idx| {
let block = block as *mut ResPictureWithTex<1>;
let mut pic_block = *block;
pic_block.set_name(display_pic_fmt!(idx));
pic_block.set_pos(ResVec3::default());
let pic_pane = build!(pic_block, ResPictureWithTex<1>, kind, Picture);
pic_pane.detach();
// 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(display_parent_fmt!(idx));
disp_pane_block.set_pos(ResVec3::new(806.0, -50.0 - (idx as f32 * 110.0), 0.0));
let disp_pane = build!(disp_pane_block, ResPane, disp_pane_kind, Pane);
disp_pane.detach();
root_pane.append_child(disp_pane);
disp_pane.append_child(pic_pane);
});
};
pub static BUILD_PANE_TXT: ui::PaneCreationCallback = |_, root_pane, original_build, layout, out_build_result_information, device, block, parts_build_data_set, build_arg_set, build_res_set, kind| unsafe {
macro_rules! build {
($block: ident, $resTyp: ty, $kind:ident, $typ: ty) => {
paste::paste! {
&mut *(original_build(layout, out_build_result_information, device, &mut $block as *mut $resTyp as *mut ResPane, parts_build_data_set, build_arg_set, build_res_set, $kind,) as *mut $typ)
}
};
}
(0..NUM_DISPLAY_PANES).for_each(|idx| {
let disp_pane = root_pane
.find_pane_by_name(display_parent_fmt!(idx), true)
.unwrap();
let block = block as *mut ResTextBox;
let mut text_block = *block;
text_block.set_name(display_txt_fmt!(idx));
text_block.set_pos(ResVec3::new(-10.0, -25.0, 0.0));
let text_pane = build!(text_block, ResTextBox, kind, TextBox);
text_pane.set_text_string(format!("Pane {idx}!").as_str());
// Ensure Material Colors are not hardcoded so we can just use SetTextColor.
text_pane.set_default_material_colors();
text_pane.detach();
disp_pane.append_child(text_pane);
});
};
pub static BUILD_HEADER_TXT: ui::PaneCreationCallback = |_, root_pane, original_build, layout, out_build_result_information, device, block, parts_build_data_set, build_arg_set, build_res_set, kind| unsafe {
macro_rules! build {
($block: ident, $resTyp: ty, $kind:ident, $typ: ty) => {
paste::paste! {
&mut *(original_build(layout, out_build_result_information, device, &mut $block as *mut $resTyp as *mut ResPane, parts_build_data_set, build_arg_set, build_res_set, $kind,) as *mut $typ)
}
};
}
(0..NUM_DISPLAY_PANES).for_each(|idx| {
let disp_pane = root_pane
.find_pane_by_name(display_parent_fmt!(idx), true)
.unwrap();
let block = block as *mut ResTextBox;
let mut header_block = *block;
header_block.set_name(display_header_fmt!(idx));
header_block.set_pos(ResVec3::new(0.0, 25.0, 0.0));
let header_pane = build!(header_block, ResTextBox, kind, TextBox);
header_pane.set_text_string(format!("Header {idx}").as_str());
// Ensure Material Colors are not hardcoded so we can just use SetTextColor.
header_pane.set_default_material_colors();
// Header should be white text
header_pane.set_color(255, 255, 255, 255);
header_pane.detach();
disp_pane.append_child(header_pane);
});
};
}

File diff suppressed because it is too large Load diff

View file

@ -1,77 +1,13 @@
use crate::common::{is_ready_go, is_training_mode};
use sarc::SarcFile;
use skyline::nn::ui2d::*;
use training_mod_consts::{OnOff, MENU};
use std::collections::HashMap;
use parking_lot::Mutex;
use skyline::libc::c_void;
mod damage;
mod display;
mod menu;
pub mod notifications;
type PaneCreationCallback = for<'a, 'b> unsafe fn(&'a str, &'b mut Pane,
extern "C" fn(*mut Layout, *mut u8, *const u8, *mut ResPane, *const u8, *const u8, *const u8, u32) -> *mut Pane,
*mut Layout, *mut u8, *const u8, *mut ResPane,
*const u8, *const u8, *const u8, u32);
type PaneCreationMap = HashMap<
(String, String), Vec<(bool, PaneCreationCallback)>
>;
lazy_static::lazy_static! {
static ref PANE_CREATED: Mutex<PaneCreationMap> = Mutex::new(HashMap::from([
(
(String::from("info_training"), String::from("pic_numbase_01")),
vec![
(false, menu::BUILD_CONTAINER_PANE),
(false, display::BUILD_PIC_BASE),
(false, menu::BUILD_SLIDER_CONTAINER_PANE),
]
),
(
(String::from("info_training"), String::from("pic_help_bg_00")),
vec![(false, menu::BUILD_FOOTER_BG)]
),
(
(String::from("info_training"), String::from("set_txt_help_00")),
vec![(false, menu::BUILD_FOOTER_TXT)]
),
(
(String::from("info_training"), String::from("set_txt_num_01")),
vec![
(false, menu::BUILD_TAB_TXTS),
(false, menu::BUILD_OPT_TXTS),
(false, menu::BUILD_SLIDER_TXTS),
(false, display::BUILD_PANE_TXT),
]
),
(
(String::from("info_training"), String::from("txt_cap_01")),
vec![
(false, display::BUILD_HEADER_TXT),
(false, menu::BUILD_SLIDER_HEADER_TXT),
]
),
(
(String::from("info_training_btn0_00_item"), String::from("icn_bg_main")),
vec![(false, menu::BUILD_BG_LEFTS)]
),
(
(String::from("info_training_btn0_00_item"), String::from("btn_bg")),
vec![(false, menu::BUILD_BG_BACKS)]
),
]));
}
pub unsafe fn reset_creation() {
let pane_created = &mut *PANE_CREATED.data_ptr();
pane_created.iter_mut().for_each(|(_identifier, creators)| {
creators.iter_mut().for_each(|(created, _callback)| {
*created = false;
})
})
}
#[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).layout_name);
@ -94,76 +30,102 @@ pub unsafe fn handle_draw(layout: *mut Layout, draw_info: u64, cmd_buffer: u64)
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,
block: *mut ResPane,
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).layout_name);
let root_pane = &mut *(*layout).root_pane;
// We'll keep some sane max size here; we shouldn't reach above 600KiB is the idea,
// but we can try higher if we need to.
#[cfg(feature = "layout_arc_from_file")]
static mut LAYOUT_ARC : &mut [u8; 600000] = &mut [0u8; 600000];
let block_name = (*block).get_name();
let identifier = (layout_name.to_string(), block_name);
let pane_created = &mut *PANE_CREATED.data_ptr();
let panes = pane_created.get_mut(&identifier);
if let Some(panes) = panes {
panes.iter_mut().for_each(|(has_created, callback)| {
if !*has_created {
callback(layout_name,
root_pane,
original!(),
layout,
out_build_result_information,
device,
block,
parts_build_data_set,
build_arg_set,
build_res_set,
kind
);
// Special case: Menu init should always occur
if ("info_training".to_string(), "pic_numbase_01".to_string()) != identifier {
*has_created = true;
}
}
});
/// We are editing the info_training/layout.arc and replacing the original file with our
/// modified version from `sd://TrainingModpack/layout.arc` or, in the case of Ryujinx for the cool
/// kids `${RYUJINX_DIR}/sdcard/TrainingModpack/layout.arc`
///
/// When we edit the layout we are doing two things.
///
/// 1. Creating a new BFLYT inside the layout.arc for whatever component we are making. For example,
/// the slider menu button.
///
/// 2. Adding a Parts pane to the info_training.bflyt with the "Part Name" matching the name of
/// our new BFLYT without the file extension (mimicking how it's done with native Parts panes)
///
/// # Warnings
/// When creating a BFLYT from an existing one we need to edit the names of the panes so that
/// the default animations no longer modify them. Otherwise the game will override properties,
/// i.e. material colours, and we won't be able to control them properly.
///
/// Once we have the file edited and saved to the correct location we can access the pane
/// from the layout as we normally would in our Draw function
/// `(root_pane.find_pane_by_name_recursive("name_of_parts_pane")`.
///
/// # Usage
/// Now say I want to edit background colour of the button's label.
/// I would have to grab the parts pane and find the pane I want to modify on it, then I'd be able
/// to make the modifications as I normally would.
///
/// ```rust
/// let slider_button = root_pane.find_pane_by_name_recursive("name_of_parts_pane");
/// let label_bg = slider_button.find_pane_by_name_recursive("name_of_picture_pane");
///
/// let label_material = &mut *label_bg.as_picture().material;
///
/// label_material.set_white_res_color(LABEL_WHITE_SELECTED_COLOR);
/// label_material.set_black_res_color(LABEL_BLACK_SELECTED_COLOR);
/// ```
#[skyline::hook(offset = 0x37730d4, inline)]
unsafe fn handle_layout_arc_malloc(
ctx: &mut skyline::hooks::InlineCtx
) {
if !is_training_mode() {
return;
}
original!()(
layout,
out_build_result_information,
device,
block,
parts_build_data_set,
build_arg_set,
build_res_set,
kind,
)
}
let decompressed_file = *ctx.registers[21].x.as_ref() as *const u8;
let decompressed_size = *ctx.registers[1].x.as_ref() as usize;
let layout_arc = SarcFile::read(
std::slice::from_raw_parts(decompressed_file,decompressed_size)
).unwrap();
let training_layout = layout_arc.files.iter().find(|f| {
f.name.is_some() && f.name.as_ref().unwrap() == &String::from("blyt/info_training.bflyt")
});
if training_layout.is_none() {
return;
}
#[skyline::hook(offset = 0x32cace0)]
pub unsafe fn handle_load_layout_files(
meta_layout_root: *mut c_void,
loading_list: *mut c_void,
layout_arc_hash: *const u32,
param_4: i32
) -> u64 {
println!("Layout.arc hash: {:x}", *layout_arc_hash);
original!()(meta_layout_root, loading_list, layout_arc_hash, param_4)
let inject_arc;
let inject_arc_size : u64;
#[cfg(feature = "layout_arc_from_file")] {
let inject_arc_from_file = std::fs::read("sd:/TrainingModpack/layout.arc").unwrap();
inject_arc_size = inject_arc_from_file.len() as u64;
// Copy read file to global
inject_arc_from_file
.iter()
.enumerate()
.for_each(|(idx, byte)| LAYOUT_ARC[idx] = *byte);
inject_arc = LAYOUT_ARC.as_ptr();
}
#[cfg(not(feature = "layout_arc_from_file"))] {
include_flate::flate!(static INJECT_ARC_FROM_FILE: [u8] from "src/static/layout.arc");
inject_arc = INJECT_ARC_FROM_FILE.as_ptr();
inject_arc_size = INJECT_ARC_FROM_FILE.len() as u64;
}
// Decompressed file pointer
let decompressed_file = ctx.registers[21].x.as_mut();
*decompressed_file = inject_arc as u64;
// Decompressed size is in each of these registers
*ctx.registers[1].x.as_mut() = inject_arc_size;
*ctx.registers[23].x.as_mut() = inject_arc_size;
*ctx.registers[24].x.as_mut() = inject_arc_size;
}
pub fn init() {
skyline::install_hooks!(
handle_draw,
layout_build_parts_impl,
// handle_load_layout_files
handle_layout_arc_malloc
);
}

View file

@ -10,7 +10,6 @@ strum_macros = "0.21.0"
num = "0.4.0"
num-derive = "0.3"
num-traits = "0.2"
ramhorns = "0.12.0"
paste = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_repr = "0.1.8"

View file

@ -8,7 +8,6 @@ extern crate bitflags_serde_shim;
extern crate num_derive;
use core::f64::consts::PI;
use ramhorns::Content;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
#[cfg(feature = "smash")]
@ -1238,7 +1237,7 @@ pub static DEFAULTS_MENU: TrainingModpackMenu = TrainingModpackMenu {
pub static mut MENU: TrainingModpackMenu = DEFAULTS_MENU;
#[derive(Content, Clone, Serialize)]
#[derive(Clone, Serialize)]
pub struct Slider {
pub selected_min: u32,
pub selected_max: u32,
@ -1246,14 +1245,14 @@ pub struct Slider {
pub abs_max: u32,
}
#[derive(Content, Clone, Serialize)]
#[derive(Clone, Serialize)]
pub struct Toggle<'a> {
pub toggle_value: u32,
pub toggle_title: &'a str,
pub checked: bool,
}
#[derive(Content, Clone, Serialize)]
#[derive(Clone, Serialize)]
pub struct SubMenu<'a> {
pub submenu_title: &'a str,
pub submenu_id: &'a str,
@ -1327,7 +1326,7 @@ impl<'a> SubMenu<'a> {
}
}
#[derive(Content, Serialize, Clone)]
#[derive(Serialize, Clone)]
pub struct Tab<'a> {
pub tab_id: &'a str,
pub tab_title: &'a str,
@ -1370,7 +1369,7 @@ impl<'a> Tab<'a> {
}
}
#[derive(Content, Serialize, Clone)]
#[derive(Serialize, Clone)]
pub struct UiMenu<'a> {
pub tabs: Vec<Tab<'a>>,
}