From 6a40d207ca9ec4d51aa8675dc1751f35683a57b5 Mon Sep 17 00:00:00 2001 From: Naxdy Date: Tue, 20 Feb 2024 22:52:07 +0100 Subject: [PATCH] add ability to change latency on CSS, add readme --- .gitignore | 6 ++- Cargo.lock | 99 ++++++++++++++++++++++++++++++++++++----- Cargo.toml | 7 ++- README.md | 118 +++++++++++++++++++------------------------------ README_orig.md | 73 ++++++++++++++++++++++++++++++ flake.nix | 2 +- src/lib.rs | 76 ++++++++++++++++++++++++++++++- src/offsets.rs | 54 ++++++++++++++++++++++ 8 files changed, 344 insertions(+), 91 deletions(-) create mode 100644 README_orig.md create mode 100644 src/offsets.rs diff --git a/.gitignore b/.gitignore index dec3586..fd8c744 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ /target /.direnv -/result \ No newline at end of file +/result +/.gdbinit +/.gdbinit.switch +/attach.py +/print_addr_setup.py \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index f928647..ceccb36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,20 +2,34 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "arena-latency-slider" -version = "0.2.0" -dependencies = [ - "ninput", - "skyline", -] - [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "compile-time-lua-bind-hash" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "660bcccd4aaa4836cb685952d9ea4fb2b57d2565f4fd0c492fce4d2c0a1be0c0" +dependencies = [ + "lua_bind_hash", + "quote", + "syn", +] + +[[package]] +name = "latency-slider-de" +version = "0.2.0" +dependencies = [ + "lazy_static", + "ninput", + "paste", + "skyline 0.2.1 (git+https://github.com/ultimate-research/skyline-rs.git)", + "skyline_smash", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -28,6 +42,18 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6823e4bff6dee10a75dfee95afe1c5a7f93a1b99a20013663e0cb04b3a64f867" +[[package]] +name = "libc-nnsdk" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99102e4669e461772fcdfeb04ce9dec03c5fade700a3c12b1a4c2a27d97e9b68" + +[[package]] +name = "lua_bind_hash" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee9675fd198b39d86a7d5d20c59ae9639a3b4157f28ebd6cc09b9f1397ab118a" + [[package]] name = "ninput" version = "0.1.0" @@ -42,9 +68,24 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0f4537c7470ba25f406d42c8cbfe26f64099af4b613e715c6bcb3d25856896c" dependencies = [ - "libc-nnsdk", + "libc-nnsdk 0.2.0", +] + +[[package]] +name = "nnsdk" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df4385052e8e8252e9a6b87282f74e94fca6ab074cdc1415d4ad5c4cc3920fe3" +dependencies = [ + "libc-nnsdk 0.3.0", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "proc-macro2" version = "1.0.78" @@ -69,9 +110,19 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9950fc129490cff3f4755aa523df16aeeb80130699abfc59cc7c6df8fc5802ad" dependencies = [ - "libc-nnsdk", - "nnsdk", - "skyline_macro", + "libc-nnsdk 0.2.0", + "nnsdk 0.2.0", + "skyline_macro 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "skyline" +version = "0.2.1" +source = "git+https://github.com/ultimate-research/skyline-rs.git#01a55d6a72d30b36b3e4b478161ebb4e513ea909" +dependencies = [ + "libc-nnsdk 0.2.0", + "nnsdk 0.3.0", + "skyline_macro 0.2.0 (git+https://github.com/ultimate-research/skyline-rs.git)", ] [[package]] @@ -86,6 +137,30 @@ dependencies = [ "syn", ] +[[package]] +name = "skyline_macro" +version = "0.2.0" +source = "git+https://github.com/ultimate-research/skyline-rs.git#01a55d6a72d30b36b3e4b478161ebb4e513ea909" +dependencies = [ + "lazy_static", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "skyline_smash" +version = "0.1.0" +source = "git+https://github.com/jugeeya/skyline-smash.git?branch=patch-2#84f9dd9a843392768c03764bc4aee28bc4ac05b6" +dependencies = [ + "compile-time-lua-bind-hash", + "lazy_static", + "libc-nnsdk 0.2.0", + "nnsdk 0.3.0", + "paste", + "skyline 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/Cargo.toml b/Cargo.toml index 0be2291..dbe3e77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "arena-latency-slider" +name = "latency-slider-de" version = "0.2.0" authors = [] edition = "2021" @@ -12,7 +12,10 @@ crate-type = ["cdylib"] [dependencies] ninput = { git = "https://github.com/blu-dev/ninput", version = "0.1.0" } -skyline = "0.2.1" +skyline_smash = { git = "https://github.com/jugeeya/skyline-smash.git", branch = "patch-2" } +skyline = { git = "https://github.com/ultimate-research/skyline-rs.git" } +paste = "1.0.14" +lazy_static = "1.4.0" [profile.dev] panic = "abort" diff --git a/README.md b/README.md index ffd5612..473a939 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,45 @@ -Well, I suppose that the cat is out of the bag, yeah? Someone has ported the input latency adjustment part of HDR to vanilla. - -Since I co-authored this feature, I think it is fair that I explain how it works so that misinformation about it being "fair" or anything like that doesn't spread. - -# Client Side Only -Honestly, we aren't quite sure why the SSBU development team did not synchronize this between players, it should have been one of the key things they did synchronize to ensure fair play. - -The easiest way to explain this is: -- Player A: Has the mod and has set their latency to 2 frames. -- Player B: Does not have the mod, Smash Ultimate assigns them 5 frames by default. - -Assuming there is no stuttering in this game, Player A is about to play the *same exact game* as player B, but with 3 frames less (48 milliseconds) delay than player B. - -This is very problematic. It means that in any online situation, one player can have a very real and very influential advantage. - -48ms is far less than the average human reaction time, for sure. However in vanilla Ultimate online, you are guaranteed a minimum of 10 frames of input delay: 2 frames from the game being triple buffered, 4 frames from the Nintendo Switch's input drivers being slow, and 4 frames as the bare minimum amount you can get online. When are dealing with a reference value of 10 frames, any single frame makes a big impact on both game feel and ability to react. This advantage is real, and will hurt competition. - -It is important to note, as much confusion has presented itself, that instead of the 10f base that you have in Vanilla Ultimate, you can now have a 7f base for input delay. The input slider value replaces the 4f provided by ultimate. - -# "How does it not lag?" -This gets a little bit complex, but bare with me. - -It is not feasible, in a production environment, to synchronize enough information between all parties in order to properly get one-way ping. This means that when your ping is calculated, it is calculated as a round trip time between you and the other party. What does this mean? - -It means that the ping that you see is calculated via something like this: -``` -roundtrip_ping = your_ping_to_them + their_ping_to_you -``` -`your_ping_to_them` and `their_ping_to_you` we don't know the value of, only their sum. - -So we do the next best thing and average it, the ping you get reported is -``` -your_ping = roundtrip_ping / 2 = (your_ping_to_them + their_ping_to_you) / 2 -``` -So let's think about what that means if your calculated ping to another player is supposedly 120ms -``` -your_ping = 120ms -120ms = roundtrip_ping / 2 -240ms = roundtrip_ping -240ms = your_ping_to_them + their_ping_to_you -``` - -With those last two values, anything that properly sums to 240ms are valid, meaning one player could have `40ms` one-way ping and the other could have `200ms` one-way ping. This allows players to have different input latency on client side. Ideally they would be synchronized online to whichever one is higher to ensure fair play, however it's a safe assumption on the Ultimate devs' side of things that the roundtrip ping would be the same between both players and therefore both values of input latency would be identical - -# So why does this matter? -When we released this for HewDraw Remix (please just call it HDR guys I beg), there were a few things that we had going in our environment that we could, not necessarily safely, but fairly ignore the glaring issues with introducing something like this: -- It was included in the HDR plugin, so it could not be disabled or enabled directly -- HDR disables the quickplay button, meaning you cannot play quickplay at all. -- HDR immediately desyncs if the other player in a battle arena does not have HDR installed. - -All of these combined ensure that in any situation where you are able to use the online latency adjuster, the other player(s) are able to use it as well. - -That's no longer the case. - -The released plugin (which, as a side note, violated HDR license by not including the modified source code. they also attributed the feature to the wrong plugin, good job random GameBanana user) was advertised to be able to work on quickplay as long as you set the latency in an arena first. You can play with literally anybody, whether that is MkLeo or little Timmy who just got a Switch for Christmas. Playing with *anybody* and giving yourself an advantage is the problem here. - -# "So you care about competitive integrity?" -Hell no. I don't, basically none of the HDR team does (at least for WiFi tournaments). But you know who does? Nintendo. - -You know who hosts tournaments, even if they are basically for scraps, on WiFi? Nintendo. - -This kind of modding could also very easily be considered illegal in Japan, (where it would have a much bigger impact as their network infrastructure and geographical location is much better for this kind of mod), so it is entirely likely that if this gets spread around and used for illegitimate reasons, Nintendo will take action against the users, and if they update Smash again likely against the modding scene. - -It's frustrating that something I've worked on even has the potential to bring about this result but it is what it is, there's no going back at this point. - -This Twitter thread does a great job explaining what I am too emotionally exhausted to present: https://twitter.com/willy_crescent/status/1602701257365389312 - -# "How many frames does it actually save?" -In offline Ultimate, there are 6 frames of input delay on a good monitor. 2 of those are from the game's graphics pipeline being triple-buffered, and the other 4 are from the Nintendo Switch's input drivers. - -When playing online, Ultimate assigns you a bare minimum of 4 frames of additional input delay, with more being added on depending on the ping (see above) you have to the other player(s) you are playing with. - -The mod allows you to pick any number for those additional frames, between 0 and 25. If both players have a good connection, i.e. fiber + LAN or something equivalent, and are somewhat close geographically, you can set that number down to 2f consistently, sometimes 1f. I (AZ) can play with someone else (CA) consistently on 2f and then when my internet is having a good day I can play on 1f. +# Latency Slider (Definitive Edition) + +This is a fork of - and an improvement upon - the original "Arena Latency Slider". Unfortunately, upon SSBU updating to 13.0.2, the original author decided to remove the source code from public view, and in its place publish a severely neutered version of the original skyline plugin. + +However, the beauty of the Internet is that nothing is truly lost, and the beauty of open source is that anyone can make changes and publish different versions of a piece of software. + +Hence, I present to you Latency Slider DE (Definitive Edition). + +**IMPORTANT:** This build is for use with SSBU v13.0.2 ONLY! + +## Features + +- As with the original mod, you can select your desired input delay in any arena (host or otherwise). +- Additionally, you can also select your desired input delay on the character select screen. If you're in the CSS for quickplay / elite, the selected latency will also be displayed at the top. +- The selected input delay will carry over to quickplay / elite / tournament mode. +- You can use D-Pad up / down in the arena screen or CSS to show / hide the input delay text (useful e.g. if you're streaming and afraid to get reported to the Nintento ninjas). + +## How does it work? + +Refer to the original author's explanation in [the original README](README_orig.md). + + +## Building + +If you have the necessary tooling (`rustup`, `cargo` and `cargo-skyline`), you can simply run + +```shell +cargo skyline build --release +``` + +If you do NOT have the necessary tooling, the quickest way for you to get set up is to [download and install Nix](https://nixos.org/download), then proceed to [enable Nix flakes](https://nixos.wiki/wiki/Flakes), and run this command to enter a dev shell: + +```shell +nix develop . +``` + +Nix will download and make available all the necessary dev tools for you to build this plugin. Then you can proceed to run the build command from above. + +## Contributing + +If there are any Nix enjoyers out there who feel like packaging skyline's rust toolchain so we don't have to rely on `cargo-skyline` anymore and can introduce fully reproducible builds, feel free to submit a pull request! If there are any modding enjoyers out there who want to expand upon this plugin, feel free to do the same! + +## 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. \ No newline at end of file diff --git a/README_orig.md b/README_orig.md new file mode 100644 index 0000000..6d80699 --- /dev/null +++ b/README_orig.md @@ -0,0 +1,73 @@ +Well, I suppose that the cat is out of the bag, yeah? Someone has ported the input latency adjustment part of HDR to vanilla. + +Since I co-authored this feature, I think it is fair that I explain how it works so that misinformation about it being "fair" or anything like that doesn't spread. + +# Client Side Only +Honestly, we aren't quite sure why the SSBU development team did not synchronize this between players, it should have been one of the key things they did synchronize to ensure fair play. + +The easiest way to explain this is: +- Player A: Has the mod and has set their latency to 2 frames. +- Player B: Does not have the mod, Smash Ultimate assigns them 5 frames by default. + +Assuming there is no stuttering in this game, Player A is about to play the *same exact game* as player B, but with 3 frames less (48 milliseconds) delay than player B. + +This is very problematic. It means that in any online situation, one player can have a very real and very influential advantage. + +48ms is far less than the average human reaction time, for sure. However in vanilla Ultimate online, you are guaranteed a minimum of 10 frames of input delay: 2 frames from the game being triple buffered, 4 frames from the Nintendo Switch's input drivers being slow, and 4 frames as the bare minimum amount you can get online. When are dealing with a reference value of 10 frames, any single frame makes a big impact on both game feel and ability to react. This advantage is real, and will hurt competition. + +It is important to note, as much confusion has presented itself, that instead of the 10f base that you have in Vanilla Ultimate, you can now have a 7f base for input delay. The input slider value replaces the 4f provided by ultimate. + +# "How does it not lag?" +This gets a little bit complex, but bare with me. + +It is not feasible, in a production environment, to synchronize enough information between all parties in order to properly get one-way ping. This means that when your ping is calculated, it is calculated as a round trip time between you and the other party. What does this mean? + +It means that the ping that you see is calculated via something like this: +``` +roundtrip_ping = your_ping_to_them + their_ping_to_you +``` +`your_ping_to_them` and `their_ping_to_you` we don't know the value of, only their sum. + +So we do the next best thing and average it, the ping you get reported is +``` +your_ping = roundtrip_ping / 2 = (your_ping_to_them + their_ping_to_you) / 2 +``` +So let's think about what that means if your calculated ping to another player is supposedly 120ms +``` +your_ping = 120ms +120ms = roundtrip_ping / 2 +240ms = roundtrip_ping +240ms = your_ping_to_them + their_ping_to_you +``` + +With those last two values, anything that properly sums to 240ms are valid, meaning one player could have `40ms` one-way ping and the other could have `200ms` one-way ping. This allows players to have different input latency on client side. Ideally they would be synchronized online to whichever one is higher to ensure fair play, however it's a safe assumption on the Ultimate devs' side of things that the roundtrip ping would be the same between both players and therefore both values of input latency would be identical + +# So why does this matter? +When we released this for HewDraw Remix (please just call it HDR guys I beg), there were a few things that we had going in our environment that we could, not necessarily safely, but fairly ignore the glaring issues with introducing something like this: +- It was included in the HDR plugin, so it could not be disabled or enabled directly +- HDR disables the quickplay button, meaning you cannot play quickplay at all. +- HDR immediately desyncs if the other player in a battle arena does not have HDR installed. + +All of these combined ensure that in any situation where you are able to use the online latency adjuster, the other player(s) are able to use it as well. + +That's no longer the case. + +The released plugin (which, as a side note, violated HDR license by not including the modified source code. they also attributed the feature to the wrong plugin, good job random GameBanana user) was advertised to be able to work on quickplay as long as you set the latency in an arena first. You can play with literally anybody, whether that is MkLeo or little Timmy who just got a Switch for Christmas. Playing with *anybody* and giving yourself an advantage is the problem here. + +# "So you care about competitive integrity?" +Hell no. I don't, basically none of the HDR team does (at least for WiFi tournaments). But you know who does? Nintendo. + +You know who hosts tournaments, even if they are basically for scraps, on WiFi? Nintendo. + +This kind of modding could also very easily be considered illegal in Japan, (where it would have a much bigger impact as their network infrastructure and geographical location is much better for this kind of mod), so it is entirely likely that if this gets spread around and used for illegitimate reasons, Nintendo will take action against the users, and if they update Smash again likely against the modding scene. + +It's frustrating that something I've worked on even has the potential to bring about this result but it is what it is, there's no going back at this point. + +This Twitter thread does a great job explaining what I am too emotionally exhausted to present: https://twitter.com/willy_crescent/status/1602701257365389312 + +# "How many frames does it actually save?" +In offline Ultimate, there are 6 frames of input delay on a good monitor. 2 of those are from the game's graphics pipeline being triple-buffered, and the other 4 are from the Nintendo Switch's input drivers. + +When playing online, Ultimate assigns you a bare minimum of 4 frames of additional input delay, with more being added on depending on the ping (see above) you have to the other player(s) you are playing with. + +The mod allows you to pick any number for those additional frames, between 0 and 25. If both players have a good connection, i.e. fiber + LAN or something equivalent, and are somewhat close geographically, you can set that number down to 2f consistently, sometimes 1f. I (AZ) can play with someone else (CA) consistently on 2f and then when my internet is having a good day I can play on 1f. diff --git a/flake.nix b/flake.nix index 2213cb7..eab5317 100644 --- a/flake.nix +++ b/flake.nix @@ -70,7 +70,7 @@ # because skyline has some weird customizations that # I don't feel like getting into at this point in time # this means no reproducible builds (for now), but I can live with that - inherit (pkgs) rustup; + inherit (pkgs) rustup gdb python311; }; # this is mainly for rust-analyzer to not get confused diff --git a/src/lib.rs b/src/lib.rs index f41d586..75dc328 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,11 @@ #![feature(restricted_std)] +use offsets::OFFSET_DRAW; use skyline::hooks::InlineCtx; +use skyline::nn::ui2d::{Layout, Pane}; +use smash::ui2d::{SmashPane, SmashTextBox}; + +mod offsets; #[skyline::from_offset(0x37a1ef0)] unsafe fn set_text_string(pane: u64, string: *const u8); @@ -10,13 +15,14 @@ static mut CURRENT_ARENA_ID: String = String::new(); static mut CURRENT_INPUT_BUFFER: isize = 4; static mut MOST_RECENT_AUTO: isize = -1; static mut STEALTH_MODE: bool = false; +static mut ORIG_VIP_TEXT: String = String::new(); const MAX_INPUT_BUFFER: isize = 25; const MIN_INPUT_BUFFER: isize = -1; -#[skyline::hook(offset = 0x18881d0, inline)] -unsafe fn non_hdr_update_room_hook(_: &skyline::hooks::InlineCtx) { +unsafe fn handle_user_input() { static mut CURRENT_COUNTER: usize = 0; + if ninput::any::is_press(ninput::Buttons::RIGHT) { if CURRENT_COUNTER == 0 { CURRENT_INPUT_BUFFER += 1; @@ -38,6 +44,11 @@ unsafe fn non_hdr_update_room_hook(_: &skyline::hooks::InlineCtx) { } CURRENT_INPUT_BUFFER = CURRENT_INPUT_BUFFER.clamp(MIN_INPUT_BUFFER, MAX_INPUT_BUFFER); +} + +#[skyline::hook(offset = 0x18881d0, inline)] +unsafe fn non_hdr_update_room_hook(_: &skyline::hooks::InlineCtx) { + handle_user_input(); if STEALTH_MODE { set_text_string( @@ -75,6 +86,23 @@ unsafe fn non_hdr_update_room_hook(_: &skyline::hooks::InlineCtx) { } } +#[skyline::hook(offset = *OFFSET_DRAW)] +unsafe fn handle_draw_hook(layout: *mut Layout, draw_info: u64, cmd_buffer: u64) { + // let layout_name = skyline::from_c_str((*layout).layout_name); + let root_pane = &mut *(*layout).root_pane; + + draw_ui(&root_pane); + + call_original!(layout, draw_info, cmd_buffer); +} + +#[skyline::hook(offset = 0x1a12f40)] +unsafe fn update_css_hook(arg: u64) { + handle_user_input(); + + call_original!(arg) +} + #[skyline::hook(offset = 0x1887afc, inline)] unsafe fn non_hdr_set_room_id(ctx: &skyline::hooks::InlineCtx) { let panel = *((*((*ctx.registers[0].x.as_ref() + 8) as *const u64) + 0x10) as *const u64); @@ -96,11 +124,55 @@ unsafe fn non_hdr_set_online_latency(ctx: &InlineCtx) { } } +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() || ORIG_VIP_TEXT.len() <= 0 { + match (vip_pane_00, vip_pane_01) { + (Some(x), _) | (_, Some(x)) => { + // get from raw using x.as_textbox().text_buf and x.as_textbox().text_buf_len + ORIG_VIP_TEXT = 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 { + match (vip_pane_00, vip_pane_01) { + (Some(x), Some(y)) => { + let s = if !STEALTH_MODE { + if CURRENT_INPUT_BUFFER != -1 { + format!("Input Latency: {}", CURRENT_INPUT_BUFFER) + } else { + if MOST_RECENT_AUTO == -1 { + format!("Input Latency: Auto") + } else { + format!("Input Latency: Auto ({})", MOST_RECENT_AUTO) + } + } + } else { + ORIG_VIP_TEXT.clone() + }; + + for e in [x, y] { + e.as_textbox().set_text_string(s.as_str()); + } + } + _ => (), + } + } +} + #[skyline::main(name = "latency-slider-de")] pub fn main() { skyline::install_hooks!( non_hdr_set_room_id, non_hdr_update_room_hook, non_hdr_set_online_latency, + update_css_hook, + handle_draw_hook ); } diff --git a/src/offsets.rs b/src/offsets.rs new file mode 100644 index 0000000..0396c6c --- /dev/null +++ b/src/offsets.rs @@ -0,0 +1,54 @@ +use lazy_static::lazy_static; + +// Stolen from Ultimate Training Modpack who stole it from HDR who stole it from Arcropolis +// https://github.com/HDR-Development/HewDraw-Remix/blob/dev/dynamic/src/util.rs +// https://github.com/jugeeya/UltimateTrainingModpack/blob/5b0b5490cc30af4964ed7f8d8d1ed8cfe38f6ff1/src/common/offsets.rs +pub fn byte_search(needle: &[T]) -> Option { + let text = unsafe { + let start = skyline::hooks::getRegionAddress(skyline::hooks::Region::Text) as *const T; + let end = skyline::hooks::getRegionAddress(skyline::hooks::Region::Rodata) as *const T; + let length = end.offset_from(start) as usize; + std::slice::from_raw_parts(start, length) + }; + + text.windows(needle.len()) + .position(|window| window == needle) +} + +// Wrapper around byte_search() with some additional logging +fn find_offset(name: &str, needle: &[u8]) -> Option { + println!("Searching for {}", name); + let offset_opt = byte_search(needle); + match offset_opt { + Some(offset) => { + println!("Found offset for {} at {:#x}", name, offset); + Some(offset) + } + None => { + println!("ERROR: Cound not find offset for {}", name); + None + } + } +} + +macro_rules! impl_offset { + ($fn_name:ident) => { + paste::paste! { + lazy_static! { + pub static ref []: usize = find_offset(stringify!($fn_name), []).expect(stringify!(Failed to find offset for $fn_name)); + } + } + } +} + +static NEEDLE_DRAW: &[u8] = &[ + 0x08, 0x0c, 0x40, 0xf9, // These comments are to prevent rustfmt from destroying + 0xc8, 0x03, 0x00, 0xb4, // this while still allowing rustfmt in general + 0xff, 0x83, 0x01, 0xd1, // + 0xf5, 0x1b, 0x00, 0xf9, // + 0xf4, 0x4f, 0x04, 0xa9, // + 0xfd, 0x7b, 0x05, 0xa9, // + 0xfd, 0x43, 0x01, 0x91, // + 0xf4, 0x03, 0x00, 0xaa, +]; +impl_offset!(DRAW);