Skip to content

Commit

Permalink
feat: add "classic" version of the plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
Naxdy committed Oct 20, 2024
1 parent dcdf9f1 commit d921ea9
Show file tree
Hide file tree
Showing 9 changed files with 371 additions and 241 deletions.
21 changes: 21 additions & 0 deletions .github/workflows/check-flake.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Code quality

on: pull_request

concurrency:
group: "${{ github.head_ref }}"
cancel-in-progress: true

jobs:
check:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main

- name: Check
run: |
nix flake check --print-build-logs -j auto
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "latency-slider-de"
version = "0.3.2"
version = "0.4.0"
authors = []
edition = "2021"

Expand All @@ -14,7 +14,11 @@ crate-type = ["cdylib"]
ninput = { git = "https://github.com/blu-dev/ninput", version = "0.1.0" }
skyline_smash = { git = "https://github.com/jugeeya/skyline-smash.git", branch = "patch-2" }
skyline = { git = "https://github.com/ultimate-research/skyline-rs.git" }
once_cell = { version = "1.20.2" }
once_cell = { version = "1.20.2", default-features = true }

[features]
default = ["css_ui"]
css_ui = []

[profile.dev]
panic = "abort"
Expand Down
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ You will need a moddable switch with atmosphere already installed on it. If you

Congratulations, Smash Ultimate online is now actually playable!

### What is `liblatency_slider_de_classic.nro`?

The "classic" version of the plugin is closer to the original latency slider in functionality:

- You can only see and adjust your desired latency in arenas, D-pad inputs on any CSS are ignored.
- The latency you select in arenas carries over to all gamemodes, including quickplay / elite.

The reason you'd pick classic over mainline is mod compatibility. Because it's much more barebones, it has a higher chance of being compatible with big, complex mods like the CSK collection. If you don't have any issues with mod compatibility however, there is no reason to use "classic" over the regular version.

## Building

The project can be reproducibly built using only the Nix package manager.
Expand Down Expand Up @@ -88,14 +97,22 @@ NO. These essentially do the same thing but differently, running multiple of the

#### Is this mod compatible with the VSync mod?

The VSync mod, aka "1 frame less delay", aka `less-delay` is compatible with this mod. I maintain a repo for that one as well, over here: https://github.com/xNaxdy/less-delay
The VSync mod, aka "1 frame less delay", aka `less-delay` is compatible with this mod. I maintain a repo for that one as well, over here: https://github.com/Naxdy/less-delay

#### Is this mod compatible with the Ultimate Training Modpack?

Yesn't. Both Latency Slider DE and the Ultimate Training Modpack attempt to hook the game's `draw` function, by trying to find its signature. The thing is, once one of the mods hooks that function, its signature changes, so whichever mod is loaded _second_ will fail to find that function. Therefore, if Latency Slider DE detects that you have Ultimate Training Modpack installed, it will display a warning message letting you know that it will run with reduced functionality in order to avoid crashing the game, making it so that it will not be able to display the input latency on the character select screen in Elite Smash.

It will, however, remain fully functional otherwise (including applying your selected latency in all online modes), and you will still be able to select your desired latency in arenas, as well as adjust it "blindly" on any CSS.

#### Is this mod compatible with [insert other mod here]?

Don't know, don't care. SSBU modding is kind of like chemistry, except you don't know the chemicals you're using. Any concoction has the chance to blow up in your face. The answer is: Try it. If it works, great. If not, you'll have to pick which mod you want enabled.

I _highly_ suggest using a mod manager of some sort to enable / disable mods as you need, to avoid potential complications.

There's also the `liblatency_slider_classic.nro` version of the plugin, which is a more barebones build, closer in functionality of the original latency slider (with the exception that it too works in all online modes). Because it is more barebones, there's a higher chance that it may work together with mods that present issues with the mainline latency slider.

Unfortunately I can't really invest any time into ensuring that latency-slider-de is compatible with each and every mod out there. However, if you submit a PR that improves compatibility with other mods, I will happily accept it!

#### Are you fine with people using this mod to cheat in online tournaments, or on qp?
Expand All @@ -108,6 +125,14 @@ Additionally, even without latency slider, there are mods (software and hardware

The bottom line here is that online tournaments have never been - and will never be - "fair", and if there's a will to cheat, there's a way (even without this mod). Unless you can ensure a 100% stable connection, equal and consistent input delay for both parties, and proctor all players to ensure they aren't using any special hardware to improve their play, this entire discussion is pointless to me.

#### Did you steal the source code of this plugin?

No. This project's source code has been licensed under the AGPL by the original authors, which is a license that grants everyone the right to view, modify, and redistribute the project's source code, so long as the original license is preserved. See [this project's license](./LICENSE), as well as [the license for HDR](https://github.com/HDR-Development/HewDraw-Remix/blob/pre-release/LICENSE) - the project this functionality was originally developed for.

I would also like to stress at this point that the AGPL regards "network interaction" as "distribution", meaning that if you download the source code for this plugin, modify it locally, and then use it to play online with others, you _have_ to publish your modifications to the source code, otherwise you are in violation of the license. According to section 13 of the [license](./LICENSE):

> Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph.
## Final Note

This fork is published under the original project's AGPL license. Though I pinky promise to never take this repo offline or privatize the source code in any way, I encourage you to fork it and re-share it as much as your heart desires.
6 changes: 3 additions & 3 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 44 additions & 7 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,57 @@
{ self
, flake-utils
, nix-skyline-rs
, ...
}: (flake-utils.lib.eachDefaultSystem (system: {
, nixpkgs
}: (flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
};
in
{
packages = {
default = self.packages.${system}.latency-slider-de;

latency-slider-de =
let
cargoTOML = builtins.fromTOML (builtins.readFile ./Cargo.toml);
in
nix-skyline-rs.lib.${system}.mkNroPackage {
pname = cargoTOML.package.name;
version = cargoTOML.package.version;
src = self;
};
pkgs.callPackage
({ mode ? "build", copyLibs ? true, cargoBuildOptions ? old: old, overrideMain ? old: old }:
nix-skyline-rs.lib.${system}.mkNroPackage {
pname = cargoTOML.package.name;
version = cargoTOML.package.version;
src = self;

inherit cargoBuildOptions overrideMain mode copyLibs;
})
{ };

latency-slider-de-classic = pkgs.callPackage
({ mode ? "build", copyLibs ? true }: self.packages.${system}.latency-slider-de.override {
inherit mode copyLibs;

cargoBuildOptions = old: old ++ [
"--no-default-features"
];

overrideMain = old: {
postInstall = (if (old ? postInstall) && (old.postInstall != false) then old.postInstall else "") + (pkgs.lib.optionalString (mode == "build") ''
mv $out/lib/liblatency_slider_de.nro $out/lib/liblatency_slider_de_classic.nro
'');
};
})
{ };
};

checks = {
clippy = self.packages.${system}.latency-slider-de.override {
mode = "clippy";
};

clippy-classic = self.packages.${system}.latency-slider-de-classic.override {
mode = "clippy";
};
};

devShells.default = nix-skyline-rs.devShells.${system}.default;
Expand Down
149 changes: 149 additions & 0 deletions src/css_ui.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
use std::path::Path;

use skyline::{
hooks::InlineCtx,
nn::ui2d::{Layout, Pane},
};
use smash::ui2d::SmashPane;

use crate::{
ensure_hooks, handle_user_input,
offsets::css_ui::{
LOC_DISPLAY_CSS, LOC_DRAW, LOC_INGAME_SCENE, LOC_MAIN_MENU_SCENE,
LOC_MELEE_NORMAL_SEQUENCE_SCENE, LOC_ONLINE_MELEE_ANY_SCENE, LOC_UPDATE_CSS,
},
set_text_string, CURRENT_INPUT_BUFFER, MOST_RECENT_AUTO, STEALTH_MODE,
};

static mut ORIG_VIP_TEXT: String = String::new();
pub static mut IS_CSS: bool = false;

const TRAINING_MODPACK_LOCATION: &str =
"sd:/atmosphere/contents/01006A800016E000/romfs/skyline/plugins/libtraining_modpack.nro";

const DONTANNOYME_LOCATION: &str = "sd:/dontannoyme.txt";

unsafe fn draw_ui(root_pane: &Pane) {
let vip_pane_00 = root_pane.find_pane_by_name_recursive("txt_vip_title_00");
let vip_pane_01 = root_pane.find_pane_by_name_recursive("txt_vip_title_01");

if ORIG_VIP_TEXT.is_empty() {
match (vip_pane_00, vip_pane_01) {
(Some(x), _) | (_, Some(x)) => {
ORIG_VIP_TEXT = dbg!(String::from_utf16(std::slice::from_raw_parts(
x.as_textbox().text_buf as *mut u16,
x.as_textbox().text_buf_len as usize,
))
.unwrap());
}
_ => (),
}
} else if let (Some(x), Some(y)) = (vip_pane_00, vip_pane_01) {
let s = if !STEALTH_MODE {
if CURRENT_INPUT_BUFFER != -1 {
format!("Input Latency: {}\0", CURRENT_INPUT_BUFFER)
} else if MOST_RECENT_AUTO == -1 {
"Input Latency: Auto\0".to_string()
} else {
format!("Input Latency: Auto ({})\0", MOST_RECENT_AUTO)
}
} else {
ORIG_VIP_TEXT.clone()
};

[x, y]
.into_iter()
.for_each(|e| set_text_string(e as *mut Pane as u64, s.as_str().as_ptr()));
}
}

#[skyline::hook(offset = LOC_DISPLAY_CSS.get_offset_in_memory().unwrap(), inline)]
unsafe fn display_css_hook(_: &InlineCtx) {
use crate::STEALTH_MODE;

if !STEALTH_MODE {
IS_CSS = true;
}
}

#[skyline::hook(offset = LOC_MELEE_NORMAL_SEQUENCE_SCENE.get_offset_in_memory().unwrap(), inline)]
unsafe fn melee_normal_sequence_scene_hook(_: &InlineCtx) {
IS_CSS = false;
}

#[skyline::hook(offset = LOC_MAIN_MENU_SCENE.get_offset_in_memory().unwrap(), inline)]
unsafe fn main_menu_scene_hook(_: &InlineCtx) {
IS_CSS = false;
}

#[skyline::hook(offset = LOC_ONLINE_MELEE_ANY_SCENE.get_offset_in_memory().unwrap(), inline)]
unsafe fn online_melee_any_scene_hook(_: &InlineCtx) {
IS_CSS = false;
}

#[skyline::hook(offset = LOC_INGAME_SCENE.get_offset_in_memory().unwrap(), inline)]
unsafe fn ingame_scene_hook(_: &InlineCtx) {
IS_CSS = false;
}

#[skyline::hook(offset = LOC_DRAW.get_offset_in_memory().unwrap())]
unsafe fn handle_draw_hook(layout: *mut Layout, draw_info: u64, cmd_buffer: u64) {
if IS_CSS {
let root_pane = &mut *(*layout).root_pane;

draw_ui(root_pane);
}

call_original!(layout, draw_info, cmd_buffer);
}

#[skyline::hook(offset = LOC_UPDATE_CSS.get_offset_in_memory().unwrap())]
unsafe fn update_css_hook(arg: u64) {
handle_user_input(true);

call_original!(arg)
}

pub fn hook_css_funcs() {
if ensure_hooks!(
LOC_UPDATE_CSS,
LOC_DISPLAY_CSS,
LOC_INGAME_SCENE,
LOC_ONLINE_MELEE_ANY_SCENE,
LOC_MAIN_MENU_SCENE,
LOC_MELEE_NORMAL_SEQUENCE_SCENE
) {
skyline::install_hooks!(
update_css_hook,
display_css_hook,
melee_normal_sequence_scene_hook,
main_menu_scene_hook,
online_melee_any_scene_hook,
ingame_scene_hook
);
}

// only hook draw if training mode modpack doesn't exist
if Path::new(TRAINING_MODPACK_LOCATION).exists() {
if !Path::new(DONTANNOYME_LOCATION).exists() {
skyline::error::show_error(
69420,
"Latency Slider DE will run with reduced features. View details for more info.\0",
format!("{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}\0",
"Latency Slider DE has detected the presence of the Ultimate Training Modpack in your plugins folder.",
"Due to conflicting functionality, Latency Slider DE will NOT be able to display your desired latency on the quickplay / Elite Smash screen.",
"The mod will remain fully functional otherwise, and you will still be able to see your desired latency in arenas, which will carry over to all online modes (including quickplay / Elite).",
"You can also still \"blindly\" adjust your desired latency on any character select screen, even if the latency is not displayed.",
"If you wish to see your latency on the quickplay / Elite Smash screen again, you will have to (temporarily) remove / disable the Ultimate Training Modpack.",
"If you don't want to see this message ever again, create an empty file named \"dontannoyme.txt\" and place it in the root (the topmost) folder of your SD card."
).as_str()
);
} else {
println!(
"[latency-slider-de] NOT enabling draw hook; user doesn't wish to be informed."
)
}
} else if ensure_hooks!(LOC_DRAW) {
skyline::install_hooks!(handle_draw_hook);
}
}
Loading

0 comments on commit d921ea9

Please sign in to comment.