├── .gitignore ├── Cargo.toml ├── README.md ├── aarch64-skyline-switch.json ├── skyline_macro ├── Cargo.toml ├── build.rs └── src │ ├── attributes │ └── mod.rs │ ├── install_fn.rs │ └── lib.rs └── src ├── build.rs ├── error.rs ├── extern_alloc.rs ├── hooks.rs ├── info.rs ├── lib.rs ├── logging.rs ├── mod0.s ├── nn.rs ├── nro.rs ├── patching.rs ├── text_iter.rs └── unix_alloc.rs /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "skyline" 3 | version = "0.2.1" 4 | authors = ["jam1garner "] 5 | edition = "2018" 6 | license = "MIT" 7 | repository = "https://github.com/ultimate-research/skyline-rs" 8 | description = "A library for helping patch and modify Nintendo Switch games" 9 | readme = "README.md" 10 | 11 | [dependencies] 12 | skyline_macro = { path = "./skyline_macro", version = "0.2.0" } 13 | nnsdk = { git = "https://github.com/ultimate-research/nnsdk-rs" } 14 | libc-nnsdk = "0.2.0" 15 | 16 | [features] 17 | default = ["std"] 18 | std = ["skyline_macro/std"] 19 | nro_internal = [] 20 | nso = ["skyline_macro/nso"] 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # skyline-rs 2 | 3 | A Rust library for working with [Skyline](https://github.com/shadowninja108/Skyline) to allow you to write game code modification 4 | for Nintendo Switch games using Rust. 5 | 6 | For `no_std` use, disable the `std` feature (enabled by default). 7 | 8 | Suggested for use with [`cargo-skyline`](https://github.com/jam1garner/cargo-skyline). 9 | 10 | Example: 11 | 12 | ```rust 13 | extern "C" fn test() -> u32 { 14 | 2 15 | } 16 | 17 | #[skyline::hook(replace = test)] 18 | fn test_replacement() -> u32 { 19 | 20 | let original_test = original!(); 21 | 22 | let val = original_test(); 23 | 24 | println!("[override] original value: {}", val); // 2 25 | 26 | val + 1 27 | } 28 | 29 | #[skyline::main(name = "skyline_rs_template")] 30 | pub fn main() { 31 | println!("Hello from Skyline Rust Plugin!"); 32 | 33 | skyline::install_hook!(test_replacement); 34 | 35 | let x = test(); 36 | 37 | println!("[main] test returned: {}", x); // 3 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /aarch64-skyline-switch.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi-blacklist": [ 3 | "stdcall", 4 | "fastcall", 5 | "vectorcall", 6 | "thiscall", 7 | "win64", 8 | "sysv64" 9 | ], 10 | "arch": "aarch64", 11 | "crt-static-default": false, 12 | "crt-static-respected": false, 13 | "data-layout": "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128", 14 | "dynamic-linking": true, 15 | "dynamic-linking-available": true, 16 | "executables": true, 17 | "has-elf-tls": false, 18 | "has-rpath": false, 19 | "linker": "rust-lld", 20 | "linker-flavor": "ld.lld", 21 | "llvm-target": "aarch64-unknown-none", 22 | "max-atomic-width": 128, 23 | "os": "switch", 24 | "panic-strategy": "abort", 25 | "position-independent-executables": true, 26 | "pre-link-args": { 27 | "ld.lld": [ 28 | "-Tlink.T", 29 | "-init=__custom_init", 30 | "-fini=__custom_fini", 31 | "--export-dynamic" 32 | ] 33 | }, 34 | "post-link-args": { 35 | "ld.lld": [ 36 | "--no-gc-sections", 37 | "--eh-frame-hdr" 38 | ] 39 | }, 40 | "relro-level": "off", 41 | "target-c-int-width": "32", 42 | "target-endian": "little", 43 | "target-pointer-width": "64", 44 | "vendor": "roblabla" 45 | } 46 | -------------------------------------------------------------------------------- /skyline_macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "skyline_macro" 3 | version = "0.2.0" 4 | authors = ["jam1garner "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "Macros for helping power skyline-rs" 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | syn = { version = "1", features = ["full", "extra-traits"] } 14 | quote = "1" 15 | proc-macro2 = "1" 16 | lazy_static = "1.4" 17 | 18 | [features] 19 | std = [] 20 | nso = [] 21 | -------------------------------------------------------------------------------- /skyline_macro/build.rs: -------------------------------------------------------------------------------- 1 | const ENV_VAR: &str = "SKYLINE_ADD_NRO_HEADER"; 2 | 3 | fn main() { 4 | println!("cargo:rerun-if-env-changed={}", ENV_VAR); 5 | if std::env::var_os(ENV_VAR).is_some() { 6 | println!("cargo:rustc-cfg=nro_header"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /skyline_macro/src/attributes/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream as TokenStream2; 2 | use quote::ToTokens; 3 | use syn::parse::{Parse, ParseStream}; 4 | use syn::{parenthesized, token, Token}; 5 | 6 | pub struct MainAttrs { 7 | pub name: String, 8 | } 9 | 10 | mod kw { 11 | syn::custom_keyword!(inline); 12 | syn::custom_keyword!(name); 13 | syn::custom_keyword!(replace); 14 | syn::custom_keyword!(symbol); 15 | syn::custom_keyword!(pointer_offset); 16 | syn::custom_keyword!(offset); 17 | } 18 | 19 | impl Parse for MainAttrs { 20 | fn parse(input: ParseStream) -> syn::Result { 21 | if input.peek(kw::name) { 22 | let meta: syn::MetaNameValue = input.parse()?; 23 | 24 | match meta.lit { 25 | syn::Lit::Str(string) => Ok(MainAttrs { 26 | name: string.value(), 27 | }), 28 | _ => panic!("Invalid literal, must be a string"), 29 | } 30 | } else { 31 | Ok(MainAttrs { 32 | name: "skyline_rust_plugin".into(), 33 | }) 34 | } 35 | } 36 | } 37 | 38 | impl ToTokens for MainAttrs { 39 | fn to_tokens(&self, tokens: &mut TokenStream2) { 40 | let name = &self.name[..]; 41 | quote::quote!( 42 | ::skyline::set_module_name!(#name); 43 | ) 44 | .to_tokens(tokens); 45 | } 46 | } 47 | 48 | #[derive(Default, Debug)] 49 | pub struct HookAttrs { 50 | pub replace: Option, 51 | pub symbol: Option, 52 | pub pointer_offset: Option, 53 | pub offset: Option, 54 | pub inline: bool, 55 | } 56 | 57 | fn merge(attr1: HookAttrs, attr2: HookAttrs) -> HookAttrs { 58 | let ( 59 | HookAttrs { 60 | replace: r1, 61 | symbol: s1, 62 | pointer_offset: so1, 63 | offset: o1, 64 | inline: i1, 65 | }, 66 | HookAttrs { 67 | replace: r2, 68 | symbol: s2, 69 | pointer_offset: so2, 70 | offset: o2, 71 | inline: i2, 72 | }, 73 | ) = (attr1, attr2); 74 | 75 | HookAttrs { 76 | replace: r1.or(r2), 77 | offset: o1.or(o2), 78 | symbol: s1.or(s2), 79 | pointer_offset: so1.or(so2), 80 | inline: i1 || i2, 81 | } 82 | } 83 | 84 | impl Parse for HookAttrs { 85 | fn parse(input: ParseStream) -> syn::Result { 86 | let look = input.lookahead1(); 87 | let attr = if look.peek(kw::symbol) { 88 | let MetaItem:: { item: string, .. } = input.parse()?; 89 | 90 | let mut a = HookAttrs::default(); 91 | a.symbol = Some(string); 92 | a 93 | } else if look.peek(kw::offset) { 94 | let MetaItem:: { item: offset, .. } = input.parse()?; 95 | 96 | let mut a = HookAttrs::default(); 97 | a.offset = Some(offset); 98 | a 99 | } else if look.peek(kw::pointer_offset) { 100 | let MetaItem:: { 101 | item: pointer_offset, 102 | .. 103 | } = input.parse()?; 104 | 105 | let mut a = HookAttrs::default(); 106 | a.pointer_offset = Some(pointer_offset); 107 | a 108 | } else if look.peek(kw::replace) { 109 | let MetaItem:: { item: replace, .. } = input.parse()?; 110 | 111 | let mut a = HookAttrs::default(); 112 | a.replace = Some(replace); 113 | a 114 | } else if look.peek(kw::inline) { 115 | let _: kw::inline = input.parse()?; 116 | let mut a = HookAttrs::default(); 117 | a.inline = true; 118 | a 119 | } else { 120 | return Err(look.error()); 121 | }; 122 | 123 | Ok(if input.peek(Token![,]) { 124 | let _: Token![,] = input.parse()?; 125 | if input.is_empty() { 126 | attr 127 | } else { 128 | merge(attr, input.parse()?) 129 | } 130 | } else { 131 | attr 132 | }) 133 | } 134 | } 135 | 136 | #[derive(Debug, Clone)] 137 | pub struct MetaItem { 138 | pub ident: Keyword, 139 | pub item: Item, 140 | } 141 | 142 | impl Parse for MetaItem { 143 | fn parse(input: ParseStream) -> syn::Result { 144 | let ident = input.parse()?; 145 | let item = if input.peek(token::Paren) { 146 | let content; 147 | parenthesized!(content in input); 148 | content.parse()? 149 | } else { 150 | input.parse::()?; 151 | input.parse()? 152 | }; 153 | 154 | Ok(Self { ident, item }) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /skyline_macro/src/install_fn.rs: -------------------------------------------------------------------------------- 1 | use quote::{quote, quote_spanned, ToTokens}; 2 | use super::attributes::HookAttrs; 3 | use proc_macro2::Span; 4 | 5 | pub fn generate(name: &syn::Ident, orig: &syn::Ident, attrs: &HookAttrs) -> impl ToTokens { 6 | let _install_fn = quote::format_ident!("{}_skyline_internal_install_hook", name); 7 | let pointer_offset = attrs 8 | .pointer_offset 9 | .as_ref() 10 | .map(ToTokens::into_token_stream) 11 | .unwrap_or(quote! {0}); 12 | 13 | let replace = attrs.replace 14 | .as_ref() 15 | .map(ToTokens::into_token_stream) 16 | .or_else(||{ 17 | attrs.offset.as_ref().map(|offset|{ 18 | quote! { 19 | unsafe { 20 | ::skyline::hooks::getRegionAddress( 21 | ::skyline::hooks::Region::Text 22 | ) as *mut u8 23 | }.add(#offset) 24 | } 25 | }) 26 | }) 27 | .unwrap_or_else(||{ 28 | quote_spanned!(Span::call_site() => 29 | compile_error!("Missing 'replace' item in hook macro"); 30 | ) 31 | }); 32 | 33 | if attrs.inline { 34 | quote!{ 35 | const _: fn() = ||{ 36 | trait InlineCtxRef {} 37 | 38 | impl InlineCtxRef for &::skyline::hooks::InlineCtx {} 39 | impl InlineCtxRef for &mut ::skyline::hooks::InlineCtx {} 40 | 41 | fn assert_inline_ctx(_: unsafe extern "C" fn(T)) {} 42 | 43 | assert_inline_ctx(#name); 44 | }; 45 | pub fn #_install_fn() { 46 | if (::skyline::hooks::A64InlineHook as *const ()).is_null() { 47 | panic!("A64InlineHook is null"); 48 | } 49 | 50 | unsafe { 51 | ::skyline::hooks::A64InlineHook( 52 | ((#replace as *const u8).offset(#pointer_offset) as *const ::skyline::libc::c_void), 53 | #name as *const ::skyline::libc::c_void, 54 | ) 55 | } 56 | } 57 | } 58 | } else { 59 | quote!{ 60 | pub fn #_install_fn() { 61 | if (::skyline::hooks::A64HookFunction as *const ()).is_null() { 62 | panic!("A64HookFunction is null"); 63 | } 64 | 65 | unsafe { 66 | #[allow(static_mut_refs)] 67 | ::skyline::hooks::A64HookFunction( 68 | ((#replace as *const u8).offset(#pointer_offset) as *const ::skyline::libc::c_void), 69 | #name as *const ::skyline::libc::c_void, 70 | &mut #orig as *mut *mut ::skyline::libc::c_void 71 | ) 72 | } 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /skyline_macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | use quote::{ToTokens, quote}; 2 | use proc_macro::TokenStream; 3 | use syn::{parse_quote, parse_macro_input, token, Ident, AttrStyle, Stmt, Lit}; 4 | use syn::{punctuated::Punctuated, FnArg, BareFnArg, token::Comma}; 5 | use proc_macro2::{Span, TokenStream as TokenStream2}; 6 | 7 | mod attributes; 8 | mod install_fn; 9 | 10 | fn new_attr(attr_name: &str) -> syn::Attribute { 11 | syn::Attribute { 12 | pound_token: token::Pound { spans: [Span::call_site()] }, 13 | style: AttrStyle::Outer, 14 | bracket_token: token::Bracket { span: Span::call_site() }, 15 | path: Ident::new(attr_name, Span::call_site()).into(), 16 | tokens: TokenStream2::new() 17 | } 18 | } 19 | 20 | #[proc_macro_attribute] 21 | pub fn main(attrs: TokenStream, item: TokenStream) -> TokenStream { 22 | let mut main_function = parse_macro_input!(item as syn::ItemFn); 23 | 24 | let attr_code = parse_macro_input!(attrs as attributes::MainAttrs); 25 | 26 | let asm_string = 27 | r#" 28 | .section .nro_header 29 | .global __nro_header_start 30 | .word 0 31 | .word _mod_header 32 | .word 0 33 | .word 0 34 | 35 | .section .rodata.mod0 36 | .global _mod_header 37 | _mod_header: 38 | .ascii "MOD0" 39 | .word __dynamic_start - _mod_header 40 | .word __bss_start - _mod_header 41 | .word __bss_end - _mod_header 42 | .word __eh_frame_hdr_start - _mod_header 43 | .word __eh_frame_hdr_end - _mod_header 44 | .word __nx_module_runtime - _mod_header // runtime-generated module object offset 45 | 46 | .global IS_NRO 47 | IS_NRO: 48 | .word 1 49 | 50 | .section .bss.module_runtime 51 | __nx_module_runtime: 52 | .space 0xD0 53 | "#; 54 | 55 | let asm = syn::LitStr::new(asm_string, Span::call_site()); 56 | 57 | if cfg!(feature = "nso") { 58 | main_function.attrs.push( 59 | syn::parse_quote!( #[export_name = "nnMain"] ) 60 | ); 61 | } else { 62 | main_function.attrs.push( 63 | syn::parse_quote!( #[export_name = "main"] ) 64 | ) 65 | } 66 | 67 | // extern "C" 68 | main_function.sig.abi = Some(syn::Abi { 69 | extern_token: syn::token::Extern { span: Span::call_site() }, 70 | name: Some(syn::LitStr::new("C", Span::call_site())) 71 | }); 72 | 73 | let mut output = TokenStream2::new(); 74 | 75 | quote! { 76 | #attr_code 77 | ::skyline::setup!(); 78 | ::std::arch::global_asm!(#asm); 79 | }.to_tokens(&mut output); 80 | 81 | quote!( 82 | // this is both fine and normal and don't think too hard about it 83 | const _: fn() = || { 84 | use ::skyline::libc::{pthread_mutex_t, pthread_key_t, c_int, c_void}; 85 | 86 | // re-export pthread_mutex_lock as __pthread_mutex_lock 87 | // 88 | // this is done in order to fix the fact that switch libstd depends on libc-nnsdk 89 | // which itself links against symbol aliases only present in certain versions of 90 | // nnsdk. 91 | #[export_name = "__pthread_mutex_lock"] 92 | pub unsafe extern "C" fn _skyline_internal_pthread_mutex_lock_shim(lock: *mut pthread_mutex_t) -> c_int { 93 | extern "C" { 94 | fn pthread_mutex_lock(lock: *mut pthread_mutex_t) -> c_int; 95 | } 96 | 97 | pthread_mutex_lock(lock) 98 | } 99 | 100 | #[export_name = "__pthread_key_create"] 101 | pub unsafe extern "C" fn _skyline_internal_pthread_key_create_shim(key: *mut pthread_key_t, func: extern fn(*mut c_void)) -> c_int { 102 | extern "C" { 103 | fn pthread_key_create( 104 | key: *mut pthread_key_t, func: extern fn(*mut c_void) 105 | ) -> c_int; 106 | } 107 | 108 | pthread_key_create(key, func) 109 | } 110 | 111 | #[export_name = "__pthread_key_delete"] 112 | pub unsafe extern "C" fn _skyline_internal_pthread_key_delete_shim(key: pthread_key_t) -> c_int { 113 | extern "C" { 114 | fn pthread_key_delete( 115 | key: pthread_key_t 116 | ) -> c_int; 117 | } 118 | 119 | pthread_key_delete(key) 120 | } 121 | }; 122 | 123 | #output 124 | #main_function 125 | ).into() 126 | } 127 | 128 | fn remove_mut(arg: &syn::FnArg) -> syn::FnArg { 129 | let mut arg = arg.clone(); 130 | 131 | if let syn::FnArg::Typed(ref mut arg) = arg { 132 | if let syn::Pat::Ident(ref mut arg) = *arg.pat { 133 | arg.by_ref = None; 134 | arg.mutability = None; 135 | arg.subpat = None; 136 | } 137 | } 138 | 139 | arg 140 | } 141 | 142 | #[proc_macro_attribute] 143 | pub fn hook(attrs: TokenStream, input: TokenStream) -> TokenStream { 144 | let mut mod_fn = parse_macro_input!(input as syn::ItemFn); 145 | let attrs = parse_macro_input!(attrs as attributes::HookAttrs); 146 | let mut output = TokenStream2::new(); 147 | 148 | // #[no_mangle] 149 | mod_fn.attrs.push( 150 | new_attr("no_mangle") 151 | ); 152 | 153 | // extern "C" 154 | mod_fn.sig.abi = Some(syn::Abi { 155 | extern_token: syn::token::Extern { span: Span::call_site() }, 156 | name: Some(syn::LitStr::new("C", Span::call_site())) 157 | }); 158 | 159 | let args_tokens = mod_fn.sig.inputs.iter().map(remove_mut); 160 | let return_tokens = mod_fn.sig.output.to_token_stream(); 161 | 162 | let _orig_fn = quote::format_ident!( 163 | "{}_skyline_internal_original_fn", 164 | mod_fn.sig.ident 165 | ); 166 | 167 | // allow original! 168 | if !attrs.inline { 169 | let orig_stmt: Stmt = parse_quote! { 170 | #[allow(unused_macros)] 171 | macro_rules! original { 172 | () => { 173 | { 174 | // Hacky solution to allow `unused_unsafe` to be applied to an expression 175 | #[allow(unused_unsafe)] 176 | if true { 177 | unsafe { 178 | core::mem::transmute::<_, extern "C" fn(#(#args_tokens),*) #return_tokens>( 179 | #_orig_fn as *const() 180 | ) 181 | } 182 | } else { 183 | unreachable!() 184 | } 185 | } 186 | } 187 | } 188 | }; 189 | mod_fn.block.stmts.insert(0, orig_stmt); 190 | let orig_stmt: Stmt = parse_quote! { 191 | #[allow(unused_macros)] 192 | macro_rules! call_original { 193 | ($($args:expr),* $(,)?) => { 194 | original!()($($args),*) 195 | } 196 | } 197 | }; 198 | mod_fn.block.stmts.insert(1, orig_stmt); 199 | } 200 | 201 | mod_fn.to_tokens(&mut output); 202 | 203 | let install_fn = install_fn::generate(&mod_fn.sig.ident, &_orig_fn, &attrs); 204 | 205 | if attrs.inline { 206 | install_fn.to_tokens(&mut output); 207 | } else { 208 | quote!( 209 | #install_fn 210 | 211 | #[allow(non_upper_case_globals)] 212 | pub static mut #_orig_fn: *mut ::skyline::libc::c_void = 0 as *mut ::skyline::libc::c_void; 213 | ).to_tokens(&mut output); 214 | } 215 | 216 | output.into() 217 | } 218 | 219 | #[proc_macro] 220 | pub fn install_hook(input: TokenStream) -> TokenStream { 221 | let mut path = parse_macro_input!(input as syn::Path); 222 | 223 | let last_seg = path.segments.iter_mut().last().unwrap(); 224 | 225 | last_seg.ident = quote::format_ident!("{}_skyline_internal_install_hook", last_seg.ident); 226 | 227 | quote!( 228 | #path(); 229 | ).into() 230 | } 231 | 232 | fn into_bare_args(args: &Punctuated) -> Punctuated { 233 | args.iter() 234 | .map(|arg|{ 235 | if let FnArg::Typed(pat_type) = arg { 236 | BareFnArg { 237 | attrs: pat_type.attrs.clone(), 238 | name: None, 239 | ty: (*pat_type.ty).clone() 240 | } 241 | } else { 242 | todo!() 243 | } 244 | }) 245 | .collect() 246 | } 247 | 248 | fn get_arg_pats(args: &Punctuated) -> Punctuated { 249 | args.iter() 250 | .map(|arg|{ 251 | if let FnArg::Typed(pat_type) = arg { 252 | (*pat_type.pat).clone() 253 | } else { 254 | todo!() 255 | } 256 | }) 257 | .collect() 258 | } 259 | 260 | #[proc_macro_attribute] 261 | pub fn from_offset(attr: TokenStream, input: TokenStream) -> TokenStream { 262 | let mut fn_sig = parse_macro_input!(input as syn::ForeignItemFn); 263 | let offset = parse_macro_input!(attr as syn::Expr); 264 | 265 | let mut inner_fn_type: syn::TypeBareFn = parse_quote!( extern "C" fn() ); 266 | 267 | inner_fn_type.output = fn_sig.sig.output.clone(); 268 | inner_fn_type.variadic = fn_sig.sig.variadic.clone(); 269 | inner_fn_type.inputs = into_bare_args(&fn_sig.sig.inputs); 270 | 271 | let visibility = fn_sig.vis; 272 | fn_sig.sig.unsafety = Some(syn::token::Unsafe { span: Span::call_site() }); 273 | 274 | let sig = fn_sig.sig; 275 | let args = get_arg_pats(&sig.inputs); 276 | 277 | // Generate a shim for the function at the offset 278 | quote!( 279 | #visibility #sig { 280 | let inner = core::mem::transmute::<_,#inner_fn_type>( 281 | unsafe {::skyline::hooks::getRegionAddress( 282 | ::skyline::hooks::Region::Text 283 | ) as *const u8}.offset(#offset as isize) 284 | ); 285 | inner( 286 | #args 287 | ) 288 | } 289 | ).into() 290 | } 291 | 292 | fn lit_to_bytes(lit: &Lit) -> Option> { 293 | match lit { 294 | Lit::Str(lit_str) => { 295 | Some(lit_str.value().into_bytes()) 296 | } 297 | Lit::ByteStr(lit_str) => { 298 | Some(lit_str.value()) 299 | } 300 | _ => { 301 | None 302 | } 303 | } 304 | } 305 | 306 | #[proc_macro] 307 | pub fn to_null_term_bytes(input: TokenStream) -> TokenStream { 308 | let expr = parse_macro_input!(input as Lit); 309 | 310 | match lit_to_bytes(&expr) { 311 | Some(mut bytes) => { 312 | bytes.push(0); 313 | 314 | let bytes = syn::LitByteStr::new(&bytes, expr.span()); 315 | 316 | TokenStream::from(quote! { 317 | (#bytes) 318 | }) 319 | } 320 | None => { 321 | let span = expr.span(); 322 | TokenStream::from(quote::quote_spanned!{span => 323 | compile_error!("Invalid literal"); 324 | }) 325 | } 326 | } 327 | } 328 | 329 | fn sig_to_token_func_call(sig: &syn::Signature) -> (Ident, TokenStream2) { 330 | let ident = quote::format_ident!("__{}_internal_unchecked", sig.ident); 331 | let args: Vec<_> = 332 | sig.inputs 333 | .iter() 334 | .map(|fn_arg|{ 335 | if let syn::FnArg::Typed(pat) = fn_arg { 336 | pat.pat.to_token_stream() 337 | } else { 338 | todo!() 339 | } 340 | }) 341 | .collect(); 342 | 343 | ( 344 | ident.clone(), 345 | quote!( 346 | #ident( 347 | #( 348 | #args 349 | ),* 350 | ) 351 | ) 352 | ) 353 | } 354 | 355 | /// Add a null check to dynamically linked functions. Applied at the extern block level. 356 | /// 357 | /// Example: 358 | /// 359 | /// ```rust 360 | /// #[null_check] 361 | /// extern "C" { 362 | /// fn not_an_available_import() -> u64; 363 | /// } 364 | /// ``` 365 | /// 366 | /// Then, if `not_an_available_import` is not available it will panic with the following message: 367 | /// 368 | /// ```text 369 | /// thread '' panicked at 'not_an_available_import is null (likely unlinked)', src/lib.rs:5:1 370 | /// ``` 371 | /// 372 | /// # Note 373 | /// 374 | /// Due to a bug, this may not consistently panic on release builds, use `--debug` for install/run 375 | /// commands to ensure this does not happen when testing. 376 | #[proc_macro_attribute] 377 | pub fn null_check(_attrs: TokenStream, input: TokenStream) -> TokenStream { 378 | let mut extern_block = parse_macro_input!(input as syn::ItemForeignMod); 379 | 380 | let (vis, sigs): (Vec<_>, Vec<_>) = 381 | extern_block 382 | .items 383 | .iter_mut() 384 | .filter_map(|item|{ 385 | if let syn::ForeignItem::Fn(ref mut func) = item { 386 | let has_link_name = func.attrs.iter().any(|attr|{ 387 | if let Some(ident) = attr.path.get_ident() { 388 | ident.to_string() == "link_name" 389 | } else { 390 | false 391 | } 392 | }); 393 | 394 | if !has_link_name { 395 | let name = func.sig.ident.to_string(); 396 | 397 | let attr: syn::Attribute = parse_quote!( 398 | #[link_name = #name] 399 | ); 400 | 401 | func.attrs.push(attr); 402 | } 403 | 404 | let old_sig = func.sig.clone(); 405 | 406 | func.sig.ident = quote::format_ident!("__{}_internal_unchecked", func.sig.ident); 407 | 408 | Some((func.vis.clone(), old_sig)) 409 | } else { 410 | None 411 | } 412 | }) 413 | .unzip(); 414 | 415 | let (idents, func_calls): (Vec<_>, Vec<_>) = sigs.iter().map(sig_to_token_func_call).unzip(); 416 | 417 | quote!( 418 | #( 419 | #vis unsafe #sigs { 420 | //panic!(concat!(stringify!(#idents), " is 0x{:X}"), (#idents as u64)); 421 | 422 | println!("ptr is 0x{:X}", #idents as u64); 423 | 424 | if (#idents as u64 as *const ()).is_null() { 425 | return panic!(concat!(stringify!(#idents), " is null (likely unlinked)")); 426 | } 427 | 428 | #func_calls 429 | } 430 | )* 431 | 432 | #extern_block 433 | ).into() 434 | } 435 | -------------------------------------------------------------------------------- /src/build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "std"))] 2 | #[lang = "eh_personality"] 3 | extern fn eh_personality() {} 4 | 5 | #[cfg(not(feature = "std"))] 6 | #[macro_export] macro_rules! install_panic_handler { 7 | ($module_name:expr) => { 8 | #[panic_handler] 9 | fn panic(panic_info: &core::panic::PanicInfo) -> ! { 10 | $crate::println!("{} panicked: {}", $module_name, panic_info); 11 | 12 | 13 | loop { 14 | unsafe { 15 | $crate::nn::os::SleepThread( 16 | $crate::nn::TimeSpan::milli(100) 17 | ) 18 | } 19 | } 20 | } 21 | }; 22 | } 23 | 24 | #[cfg(feature = "std")] 25 | #[macro_export] macro_rules! install_panic_handler { 26 | ($module_name:expr) => {}; 27 | } 28 | 29 | #[cfg(not(feature = "std"))] 30 | global_asm!(include_str!("mod0.s")); 31 | 32 | #[macro_export] macro_rules! set_module_name { 33 | ($lit:literal) => { 34 | ::skyline::install_panic_handler!($lit); 35 | 36 | const __SKYLINE_INTERNAL_MODULE_LEN: usize = $lit.len() + 1; 37 | 38 | #[used] 39 | #[link_section = ".rodata.module_name"] 40 | pub static __MODULE_NAME: ::skyline::build::ModuleName<__SKYLINE_INTERNAL_MODULE_LEN> = 41 | ::skyline::build::ModuleName::new( 42 | ::skyline::skyline_macro::to_null_term_bytes!($lit) 43 | ); 44 | }; 45 | } 46 | 47 | #[repr(packed)] 48 | #[allow(unused_variables)] 49 | pub struct ModuleName { 50 | pub unk: u32, 51 | pub name_length: u32, 52 | pub name: [u8; LEN], 53 | } 54 | 55 | impl ModuleName { 56 | pub const fn new(bytes: &[u8; LEN]) -> Self { 57 | Self { 58 | unk: 0, 59 | name_length: LEN as u32 - 1, 60 | name: *bytes, 61 | } 62 | } 63 | } 64 | 65 | /// one-time setup for skyline 66 | #[cfg(not(feature = "std"))] 67 | #[doc(hidden)] 68 | #[macro_export] macro_rules! setup { 69 | () => { 70 | #[global_allocator] 71 | pub static ALLOCATOR: $crate::extern_alloc::Allocator = $crate::extern_alloc::Allocator; 72 | 73 | #[no_mangle] pub unsafe extern "C" fn __custom_init() {} 74 | #[no_mangle] pub extern "C" fn __custom_fini() {} 75 | }; 76 | } 77 | 78 | #[cfg(feature = "std")] 79 | #[macro_export] macro_rules! setup { 80 | () => { 81 | #[no_mangle] pub unsafe extern "C" fn __custom_init() {} 82 | #[no_mangle] pub extern "C" fn __custom_fini() {} 83 | }; 84 | } 85 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use core::num::NonZeroU32; 3 | use core::panic::Location; 4 | use core::str; 5 | 6 | use crate::c_str; 7 | use crate::nn; 8 | 9 | #[cfg(not(feature = "std"))] 10 | use alloc::string::String; 11 | 12 | #[non_exhaustive] 13 | pub enum Error { 14 | Os(OsError), 15 | Skyline { kind: ErrorKind }, 16 | } 17 | 18 | #[non_exhaustive] 19 | #[derive(Debug)] 20 | pub enum ErrorKind { 21 | StringTooLong, 22 | } 23 | 24 | #[repr(transparent)] 25 | pub struct SwitchResult(pub Option); 26 | 27 | pub struct OsError { 28 | code: u32, 29 | caller: &'static Location<'static>, 30 | } 31 | 32 | impl SwitchResult { 33 | #[track_caller] 34 | pub fn ok(self) -> Result<(), OsError> { 35 | if let Some(code) = self.0 { 36 | Err(OsError { 37 | code: code.into(), 38 | caller: Location::caller(), 39 | }) 40 | } else { 41 | Ok(()) 42 | } 43 | } 44 | } 45 | 46 | impl fmt::Display for OsError { 47 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 48 | write!( 49 | f, 50 | "OsError(0x{:X}) at {}:{}:{}", 51 | self.code, 52 | self.caller.file(), 53 | self.caller.line(), 54 | self.caller.column() 55 | ) 56 | } 57 | } 58 | 59 | impl From for Error { 60 | fn from(err: OsError) -> Self { 61 | Self::Os(err) 62 | } 63 | } 64 | 65 | impl fmt::Display for Error { 66 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 67 | match self { 68 | Self::Os(os_err) => write!(f, "{}", os_err), 69 | Self::Skyline { kind } => write!(f, "{:?}", kind), 70 | } 71 | } 72 | } 73 | 74 | impl fmt::Debug for Error { 75 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 76 | match self { 77 | Self::Os(os_err) => write!(f, "{}", os_err), 78 | Self::Skyline { kind } => write!(f, "{:?}", kind), 79 | } 80 | } 81 | } 82 | 83 | pub fn show_error(code: u32, message: &str, details: &str) { 84 | let mut message_bytes = String::from(message).into_bytes(); 85 | let mut details_bytes = String::from(details).into_bytes(); 86 | 87 | if message_bytes.len() >= 2048 { 88 | message_bytes.truncate(2044); 89 | message_bytes.append(&mut String::from("...\0").into_bytes()); 90 | } 91 | if details_bytes.len() >= 2048 { 92 | details_bytes.truncate(2044); 93 | details_bytes.append(&mut String::from("...\0").into_bytes()); 94 | } 95 | unsafe { 96 | let error = nn::err::ApplicationErrorArg::new_with_messages( 97 | code, 98 | c_str(str::from_utf8(&message_bytes).unwrap()), 99 | c_str(str::from_utf8(&details_bytes).unwrap()), 100 | &nn::settings::LanguageCode_Make(nn::settings::Language_Language_English), 101 | ); 102 | 103 | nn::err::ShowApplicationError(&error); 104 | }; 105 | } 106 | 107 | -------------------------------------------------------------------------------- /src/extern_alloc.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "std"))] 2 | use core::alloc::{GlobalAlloc, Layout}; 3 | 4 | #[cfg(not(feature = "std"))] 5 | use crate::libc; 6 | 7 | pub struct Allocator; 8 | 9 | #[cfg(not(feature = "std"))] 10 | unsafe impl GlobalAlloc for Allocator { 11 | #[inline] 12 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 13 | libc::malloc(layout.size()) as *mut u8 14 | } 15 | 16 | #[inline] 17 | unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { 18 | libc::calloc(layout.size(), 1) as *mut u8 19 | } 20 | 21 | #[inline] 22 | unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { 23 | libc::free(ptr as *mut libc::c_void) 24 | } 25 | 26 | #[inline] 27 | unsafe fn realloc(&self, ptr: *mut u8, _layout: Layout, new_size: usize) -> *mut u8 { 28 | libc::realloc(ptr as *mut libc::c_void, new_size) as *mut u8 29 | } 30 | } 31 | 32 | #[cfg(not(feature = "std"))] 33 | #[alloc_error_handler] 34 | fn _alloc_error(_layout: Layout) -> ! { 35 | panic!("Allocation error"); 36 | } 37 | -------------------------------------------------------------------------------- /src/hooks.rs: -------------------------------------------------------------------------------- 1 | use crate::alloc::string::String; 2 | use core::fmt; 3 | use nnsdk::root::nn; 4 | 5 | #[macro_export] 6 | macro_rules! install_hooks { 7 | ( 8 | $( 9 | $hook_paths:path 10 | ),* 11 | $(,)? 12 | ) => { 13 | $( 14 | $crate::install_hook!( 15 | $hook_paths 16 | ); 17 | )* 18 | }; 19 | } 20 | 21 | #[repr(u8)] 22 | pub enum Region { 23 | Text, 24 | Rodata, 25 | Data, 26 | Bss, 27 | Heap, 28 | } 29 | 30 | #[repr(C)] 31 | pub struct InlineCtx { 32 | pub registers: [nn::os::CpuRegister; 29], 33 | } 34 | 35 | impl fmt::Display for InlineCtx { 36 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 37 | for (i, reg) in self.registers.iter().enumerate() { 38 | unsafe { 39 | write!(f, "X[{}]: {:#08x?}\n", i, reg.x.as_ref())?; 40 | } 41 | } 42 | Ok(()) 43 | } 44 | } 45 | 46 | extern "C" { 47 | pub fn A64HookFunction( 48 | symbol: *const libc::c_void, 49 | replace: *const libc::c_void, 50 | result: *mut *mut libc::c_void, 51 | ); 52 | pub fn A64InlineHook(symbol: *const libc::c_void, replace: *const libc::c_void); 53 | pub fn getRegionAddress(region: Region) -> *mut libc::c_void; 54 | } 55 | 56 | pub struct HookInfo { 57 | /// Name of the function being used as the override 58 | pub fn_name: &'static str, 59 | 60 | /// User-given name of what the hook represents 61 | pub name: Option, 62 | 63 | /// Offset of where to install the hook 64 | pub offset: Option, 65 | 66 | /// Symbol of where to install the hook 67 | pub symbol: Option, 68 | 69 | /// Whether or not this is an inline hook 70 | pub inline: bool, 71 | } 72 | 73 | /// Type for representing a hook for this plugin 74 | pub struct Hook { 75 | /// Pointer to the overloading function 76 | pub ptr: *const (), 77 | 78 | /// Info needed to identify and install this hook 79 | pub info: &'static HookInfo, 80 | } 81 | 82 | unsafe impl Sync for Hook {} 83 | 84 | impl Hook { 85 | pub fn install(&self) { 86 | todo!() 87 | } 88 | } 89 | 90 | #[allow(improper_ctypes)] 91 | extern "C" { 92 | static __hook_array_start: Hook; 93 | static __hook_array_end: Hook; 94 | } 95 | 96 | /// Iterate over the loaded hooks for this plugin 97 | pub fn iter_hooks() -> impl Iterator { 98 | let hook_start = unsafe { &__hook_array_start as *const Hook }; 99 | let hook_end = unsafe { &__hook_array_end as *const Hook }; 100 | 101 | let hook_count = ((hook_start as usize) - (hook_end as usize)) / core::mem::size_of::(); 102 | 103 | crate::println!("hook_count: {}", hook_count); 104 | crate::println!("hook_start: {:?}", hook_start); 105 | crate::println!("hook_end: {:?}", hook_start); 106 | 107 | unsafe { core::slice::from_raw_parts(hook_start, hook_count) }.iter() 108 | } 109 | -------------------------------------------------------------------------------- /src/info.rs: -------------------------------------------------------------------------------- 1 | use crate::libc; 2 | 3 | extern "C" { 4 | #[link_name = "get_program_id"] 5 | fn get_program_id_impl() -> u64; 6 | 7 | #[link_name = "get_plugin_addresses"] 8 | fn get_plugin_addresses(internal_addr: *const libc::c_void, start: *mut *mut libc::c_void, end: *mut *mut libc::c_void); 9 | } 10 | 11 | /// Get the program id for the current process. 12 | pub fn get_program_id() -> u64 { 13 | unsafe { 14 | get_program_id_impl() 15 | } 16 | } 17 | 18 | pub unsafe fn containing_plugin(address: *const libc::c_void) -> (u64, u64) { 19 | let mut plug_start: *mut libc::c_void = 0 as _; 20 | let mut plug_end: *mut libc::c_void = 0 as _; 21 | get_plugin_addresses(address, &mut plug_start, &mut plug_end); 22 | (plug_start as u64, plug_end as u64) 23 | } 24 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | #![allow(incomplete_features, stable_features)] 3 | #![feature( 4 | alloc_error_handler, 5 | lang_items, 6 | start, 7 | global_asm, 8 | proc_macro_hygiene, 9 | panic_info_message, 10 | track_caller 11 | )] 12 | 13 | #[cfg(feature = "std")] 14 | use std::str::Utf8Error; 15 | 16 | use libc::strlen; 17 | 18 | #[cfg(not(feature = "std"))] 19 | use alloc::{borrow::ToOwned, string::String}; 20 | 21 | #[cfg(feature = "std")] 22 | pub mod unix_alloc; 23 | 24 | /// The rust core allocation and collections library 25 | pub extern crate alloc; 26 | 27 | #[doc(hidden)] 28 | pub use skyline_macro; 29 | 30 | /// Types and functions for working with hooking 31 | pub mod hooks; 32 | 33 | /// Types and functions for logging and debugging 34 | pub mod logging; 35 | 36 | /// Functions for helping patch executables 37 | pub mod patching; 38 | 39 | /// Functions for iterating through a binary .text section 40 | pub mod text_iter; 41 | 42 | /// Types and helpers related to error-handling 43 | pub mod error; 44 | 45 | /// Types and functions needed to handle NRO hooking 46 | pub mod nro; 47 | 48 | pub mod info; 49 | 50 | #[doc(hidden)] 51 | pub mod extern_alloc; 52 | 53 | #[doc(hidden)] 54 | pub mod build; 55 | 56 | // nnsdk API bindings 57 | pub mod nn; 58 | 59 | #[doc(inline)] 60 | pub use { 61 | error::{Error, ErrorKind}, 62 | hooks::iter_hooks, 63 | libc, 64 | skyline_macro::{from_offset, hook, install_hook, main, null_check}, 65 | }; 66 | 67 | /// Helper to convert a str to a *const u8 (to be replaced) 68 | pub fn c_str(string: &str) -> *const u8 { 69 | string.as_bytes().as_ptr() 70 | } 71 | 72 | /// Helper to convert a C-str to a Rust string 73 | pub unsafe fn from_c_str(c_str: *const u8) -> String { 74 | let name_slice = core::slice::from_raw_parts(c_str as *mut _, strlen(c_str)); 75 | match core::str::from_utf8(&name_slice) { 76 | Ok(v) => v.to_owned(), 77 | Err(e) => panic!("Invalid UTF-8 sequence: {}", e), 78 | } 79 | } 80 | 81 | #[cfg(feature = "std")] 82 | /// Helper to convert a C-str to a Rust string, returning an error if it failed 83 | pub unsafe fn try_from_c_str(c_str: *const u8) -> Result { 84 | let name_slice = core::slice::from_raw_parts(c_str as *mut _, strlen(c_str)); 85 | core::str::from_utf8(&name_slice).map(|string| string.to_owned()) 86 | } 87 | 88 | /// A set of items that will likely be useful to import anyway 89 | /// 90 | /// Designed to be used as such: 91 | /// ``` 92 | /// use skyline::prelude::*; 93 | /// ``` 94 | pub mod prelude { 95 | pub use crate::println; 96 | pub use alloc::format; 97 | pub use alloc::vec; 98 | } 99 | -------------------------------------------------------------------------------- /src/logging.rs: -------------------------------------------------------------------------------- 1 | use crate::hooks::{getRegionAddress, Region}; 2 | use crate::libc::{c_char, free, strlen}; 3 | use core::fmt; 4 | use core::fmt::Display; 5 | use nnsdk::{ 6 | diag::{GetBacktrace, GetSymbolName}, 7 | ro::LookupSymbol, 8 | root::cxa_demangle, 9 | }; 10 | 11 | #[cfg(feature = "std")] 12 | use std::ffi::CStr; 13 | 14 | extern "C" { 15 | fn skyline_tcp_send_raw(bytes: *const u8, usize: u64); 16 | } 17 | 18 | pub fn log(message: &str) { 19 | unsafe { 20 | skyline_tcp_send_raw(message.as_bytes().as_ptr(), message.as_bytes().len() as _); 21 | } 22 | } 23 | 24 | /// Prints to the standard output, with a newline. For use in no_std plugins. 25 | #[macro_export] 26 | macro_rules! println { 27 | () => { 28 | $crate::log(); 29 | }; 30 | ($($arg:tt)*) => { 31 | { 32 | use $crate::alloc::format; 33 | $crate::logging::log(&format!( 34 | $($arg)* 35 | )); 36 | } 37 | }; 38 | } 39 | 40 | /** 41 | Format wrapper used for displaying a [`Sized`] type to hex with 8 byte rows 42 | 43 | Example usage: 44 | ```rust 45 | # use skyline::logging::HexDump; 46 | let val: u32 = 3; 47 | println!("Hexdump:\n {}", HexDump(&val)); 48 | ``` 49 | */ 50 | pub struct HexDump<'a, T: Sized>(pub &'a T); 51 | 52 | impl<'a, T> Display for HexDump<'a, T> { 53 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 54 | hex_dump_value(f, self.0) 55 | } 56 | } 57 | 58 | pub fn hex_dump_ptr(ptr: *const T) { 59 | println!("{}", HexDump(unsafe { &*(ptr as *const u8) })) 60 | } 61 | 62 | pub fn hex_dump_str(ptr: *const c_char) { 63 | let len = unsafe { strlen(ptr) }; 64 | let addr = ptr as usize; 65 | 66 | println!("{}", StrDumper(ptr, addr..addr + len)); 67 | } 68 | 69 | struct StrDumper(pub *const c_char, core::ops::Range); 70 | 71 | impl fmt::Display for StrDumper { 72 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 73 | hex_dump(f, self.0, Some(self.1.clone())) 74 | } 75 | } 76 | 77 | const CHUNK_SIZE: usize = 0x10; 78 | const NUMBERING_HEX: &str = "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F "; 79 | const NUMBERING_SEP: &str = "│"; 80 | const NUMBERING_ASCII: &str = " 0123456789ABCDEF"; 81 | 82 | #[cfg(not(feature = "std"))] 83 | const LOG2_TAB: [usize; 64] = [ 84 | 63, 0, 58, 1, 59, 47, 53, 2, 60, 39, 48, 27, 54, 33, 42, 3, 61, 51, 37, 40, 49, 18, 28, 20, 55, 85 | 30, 34, 11, 43, 14, 22, 4, 62, 57, 46, 52, 38, 26, 32, 41, 50, 36, 17, 19, 29, 10, 13, 21, 56, 86 | 45, 25, 31, 35, 16, 9, 12, 44, 24, 15, 8, 23, 7, 6, 5, 87 | ]; 88 | 89 | #[cfg(not(feature = "std"))] 90 | fn log2(mut value: usize) -> f64 { 91 | value |= value >> 1; 92 | value |= value >> 2; 93 | value |= value >> 4; 94 | value |= value >> 8; 95 | value |= value >> 16; 96 | value |= value >> 32; 97 | LOG2_TAB[((value - (value >> 1)) * 0x07EDD5E59A4E28C2) >> 58] as f64 98 | } 99 | 100 | #[cfg(feature = "std")] 101 | fn log2(value: usize) -> f64 { 102 | (value as f64).log2() 103 | } 104 | 105 | fn hex_num_len(val: usize) -> usize { 106 | (log2(val) / log2(0x10)) as usize + 1 107 | } 108 | 109 | fn to_ascii_dots(x: u8) -> char { 110 | match x { 111 | 0..=0x1F | 0x7F..=0xA0 | 0xAD => '.', 112 | x => x as char, 113 | } 114 | } 115 | 116 | fn dump_hex_line( 117 | f: &mut fmt::Formatter, 118 | line: &[u8], 119 | addr: usize, 120 | highlight: &core::ops::Range, 121 | ) -> fmt::Result { 122 | write!(f, "{:08X}", addr)?; 123 | for (j, half) in line.chunks(8).enumerate() { 124 | write!(f, " ")?; 125 | for (i, x) in half.iter().enumerate() { 126 | let addr = addr + i + (j * 8); 127 | if highlight.contains(&addr) { 128 | write!(f, "\x1b[7m")?; // set highlight 129 | } 130 | write!(f, "{:02X}", x)?; 131 | if !highlight.contains(&(addr + 1)) || (j == 1 && i == 7) { 132 | write!(f, "\x1b[0m")?; // reset colors 133 | } 134 | write!(f, " ")?; 135 | } 136 | } 137 | write!(f, "│ ")?; 138 | for (i, &x) in line.iter().enumerate() { 139 | if highlight.contains(&(addr + i)) { 140 | write!(f, "\x1b[7m")?; // set highlight 141 | } 142 | write!(f, "{}", to_ascii_dots(x))?; 143 | write!(f, "\x1b[0m")?; // reset colors 144 | } 145 | writeln!(f) 146 | } 147 | 148 | fn hex_dump_bytes( 149 | f: &mut fmt::Formatter, 150 | byte_slice: &[u8], 151 | start: usize, 152 | highlight: core::ops::Range, 153 | ) -> fmt::Result { 154 | let num_spaces = hex_num_len(start.saturating_add(CHUNK_SIZE * 6)) + 1; 155 | for _ in 0..num_spaces { 156 | write!(f, " ")?; 157 | } 158 | writeln!(f, "{}{}{}", NUMBERING_HEX, NUMBERING_SEP, NUMBERING_ASCII)?; 159 | for _ in 0..num_spaces { 160 | write!(f, " ")?; 161 | } 162 | for _ in 0..NUMBERING_HEX.len() { 163 | write!(f, "─")?; 164 | } 165 | write!(f, "┼")?; 166 | for _ in 0..NUMBERING_ASCII.len() { 167 | write!(f, "─")?; 168 | } 169 | writeln!(f)?; 170 | 171 | let lines = byte_slice 172 | .chunks(CHUNK_SIZE) 173 | .zip((0..).map(|x| (x * CHUNK_SIZE) + start)); 174 | 175 | for (x, addr) in lines { 176 | dump_hex_line(f, x, addr, &highlight)?; 177 | } 178 | 179 | Ok(()) 180 | } 181 | 182 | fn hex_dump( 183 | f: &mut fmt::Formatter, 184 | addr: *const T, 185 | highlight: Option>, 186 | ) -> fmt::Result { 187 | let addr = addr as usize; 188 | let highlight = highlight.unwrap_or(addr..addr + 1); 189 | let aligned_addr = addr & !0xF; 190 | let start = aligned_addr.saturating_sub(CHUNK_SIZE * 3); 191 | let num_chunks = 7 + ((highlight.end - highlight.start) / CHUNK_SIZE); 192 | let byte_slice = 193 | unsafe { core::slice::from_raw_parts(start as *const u8, CHUNK_SIZE * num_chunks) }; 194 | 195 | hex_dump_bytes(f, byte_slice, start, highlight) 196 | } 197 | 198 | fn hex_dump_value(f: &mut fmt::Formatter, val: &T) -> fmt::Result { 199 | let addr = val as *const T as usize; 200 | let size = core::mem::size_of::(); 201 | hex_dump(f, val as *const _, Some(addr..addr + size)) 202 | } 203 | 204 | #[cfg(feature = "std")] 205 | pub fn print_stack_trace() { 206 | let addresses = &mut [0 as *const u8; 32]; 207 | let addr_count = unsafe { GetBacktrace(addresses.as_mut_ptr(), 32) }; 208 | 209 | for (idx, &addr) in addresses[0..addr_count].iter().enumerate() { 210 | if addr.is_null() { 211 | continue; 212 | } 213 | 214 | let name = &mut [0u8; 255]; 215 | 216 | unsafe { GetSymbolName(name.as_mut_ptr(), name.len() as u64, addr as u64) }; 217 | 218 | let mut symbol_addr = 0; 219 | unsafe { LookupSymbol(&mut symbol_addr, name.as_ptr()) }; 220 | 221 | let mut result = 0; 222 | let demangled_symbol = unsafe { cxa_demangle(name.as_ptr(), 0 as _, 0 as _, &mut result) }; 223 | 224 | let c_name; 225 | 226 | if result == 0 { 227 | c_name = unsafe { 228 | CStr::from_ptr(demangled_symbol as _) 229 | .to_str() 230 | .unwrap_or("None") 231 | }; 232 | } else { 233 | c_name = unsafe { 234 | CStr::from_ptr(name.as_ptr() as _) 235 | .to_str() 236 | .unwrap_or("None") 237 | }; 238 | } 239 | 240 | println!( 241 | "[{}] Address: {:x}, Symbol: {}+{:x}\n", 242 | idx, 243 | (symbol_addr as u64 - unsafe { getRegionAddress(Region::Text) as u64 } + 0x7100000000), 244 | c_name, 245 | addr as u64 - symbol_addr as u64 246 | ); 247 | unsafe { free(demangled_symbol as _) }; 248 | } 249 | } 250 | 251 | -------------------------------------------------------------------------------- /src/mod0.s: -------------------------------------------------------------------------------- 1 | .section .nro_header 2 | .global __nro_header_start 3 | .word 0 4 | .word _mod_header 5 | .word 0 6 | .word 0 7 | 8 | .section .rodata.mod0 9 | .global _mod_header 10 | _mod_header: 11 | .ascii "MOD0" 12 | .word __dynamic_start - _mod_header 13 | .word __bss_start - _mod_header 14 | .word __bss_end - _mod_header 15 | .word __eh_frame_hdr_start - _mod_header 16 | .word __eh_frame_hdr_end - _mod_header 17 | .word __nx_module_runtime - _mod_header // runtime-generated module object offset 18 | .global IS_NRO 19 | IS_NRO: 20 | .word 1 21 | 22 | .section .bss.module_runtime 23 | .space 0xD0 24 | 25 | -------------------------------------------------------------------------------- /src/nn.rs: -------------------------------------------------------------------------------- 1 | pub use nnsdk::root::nn::*; 2 | pub use nnsdk::extensions::*; 3 | -------------------------------------------------------------------------------- /src/nro.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use crate::nn::ro; 3 | 4 | pub type Callback = extern "Rust" fn(&NroInfo); 5 | 6 | /// An error representing the NRO hook plugin not successfully being linked against 7 | #[derive(Debug, Clone, Copy)] 8 | pub struct NroHookPluginMissing; 9 | 10 | #[allow(improper_ctypes)] 11 | extern "C" { 12 | fn add_nro_load_hook(callback: Callback); 13 | fn add_nro_unload_hook(callback: Callback); 14 | } 15 | 16 | /// A function to allow adding a hook for immediately after an NRO has been loaded. 17 | /// 18 | /// **Note:** Requires the NRO hook plugin. Will return an error otherwise. 19 | pub fn add_hook(callback: Callback) -> Result<(), NroHookPluginMissing> { 20 | if (add_nro_load_hook as *const ()).is_null() { 21 | Err(NroHookPluginMissing) 22 | } else { 23 | unsafe { 24 | add_nro_load_hook(callback); 25 | } 26 | Ok(()) 27 | } 28 | 29 | } 30 | 31 | pub fn add_unload_hook(callback: Callback) -> Result<(), NroHookPluginMissing> { 32 | if (add_nro_unload_hook as *const ()).is_null() { 33 | Err(NroHookPluginMissing) 34 | } else { 35 | unsafe { 36 | add_nro_unload_hook(callback); 37 | } 38 | Ok(()) 39 | } 40 | } 41 | 42 | /// A struct to hold information related to the NRO being loaded 43 | #[repr(C)] 44 | #[non_exhaustive] 45 | pub struct NroInfo<'a> { 46 | pub name: &'a str, 47 | pub module: &'a mut ro::Module 48 | } 49 | 50 | impl<'a> NroInfo<'a> { 51 | /// A function only to be used by nro_hook in order to construct NroInfos. Since fields may be 52 | /// added, this API is subject to change. 53 | #[cfg(feature = "nro_internal")] 54 | pub fn new(name: &'a str, module: &'a mut ro::Module) -> Self { 55 | Self { name, module } 56 | } 57 | } 58 | 59 | impl fmt::Display for NroHookPluginMissing { 60 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 61 | write!(f, "The NRO hook plugin could not be found and is required to add NRO hooks. Make sure hook_nro.nro is installed.") 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/patching.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, SwitchResult}; 2 | use crate::hooks::{getRegionAddress, Region}; 3 | use crate::libc::{c_void, size_t}; 4 | 5 | #[cfg(not(feature = "std"))] 6 | use alloc::string::String; 7 | 8 | static NOP: u32 = 0xd503201f; 9 | 10 | extern "C" { 11 | pub fn sky_memcpy(dst: *const c_void, src: *const c_void, size: size_t) -> SwitchResult; 12 | } 13 | 14 | /// Overwrite a string in read-only data with a Rust string given the offset from the start of .text 15 | #[doc(hidden)] 16 | #[deprecated(note = "Use Patch instead.")] 17 | pub unsafe fn patch_str(offset: usize, string: &str) -> Result<(), Error> { 18 | Patch::in_text(offset).cstr(string) 19 | } 20 | 21 | /// Overwrite a value in read-only data with a passed value given an offset from the start of .text 22 | #[doc(hidden)] 23 | #[deprecated(note = "Use Patch instead.")] 24 | pub unsafe fn patch_data(offset: usize, val: &T) -> Result<(), Error> { 25 | Patch::in_text(offset).data(val) 26 | } 27 | 28 | /// Overwrite a value in read-only data with a passed value given an offset from the start of .text 29 | #[doc(hidden)] 30 | #[deprecated(note = "Use Patch instead.")] 31 | pub unsafe fn patch_data_from_text( 32 | text_offset: *const u8, 33 | offset: usize, 34 | val: &T, 35 | ) -> Result<(), Error> { 36 | PatchBuilder(text_offset.add(offset)).data(val) 37 | } 38 | 39 | /// Replace the instruction at the given offset from the start of .text with NOP 40 | #[doc(hidden)] 41 | #[deprecated(note = "Use Patch instead.")] 42 | pub unsafe fn nop_data(offset: usize) -> Result<(), Error> { 43 | Patch::in_text(offset).nop() 44 | } 45 | 46 | /// Overwrite a value in read-only data with a passed value given a pointer plus offset 47 | pub unsafe fn patch_pointer_with_offset( 48 | pointer: *const u8, 49 | offset: isize, 50 | val: &T, 51 | ) -> Result<(), Error> { 52 | sky_memcpy( 53 | pointer.offset(offset) as _, 54 | val as *const _ as _, 55 | core::mem::size_of::(), 56 | ) 57 | .ok()?; 58 | 59 | Ok(()) 60 | } 61 | 62 | /// Overwrite a value in read-only data with a passed value given a pointer 63 | pub unsafe fn patch_pointer(pointer: *const u8, val: &T) -> Result<(), Error> { 64 | patch_pointer_with_offset(pointer, 0, val) 65 | } 66 | 67 | /// Replace the instruction at the given pointer with NOP 68 | pub unsafe fn nop_pointer(pointer: *const u8) -> Result<(), Error> { 69 | patch_pointer(pointer as _, &NOP) 70 | } 71 | 72 | /// Replace the instruction at the given pointer plus offset with NOP 73 | pub unsafe fn nop_pointer_with_offset(pointer: *const u8, offset: isize) -> Result<(), Error> { 74 | patch_pointer(pointer.offset(offset) as _, &NOP) 75 | } 76 | 77 | /// A constructor to acquire a [`PatchBuilder`], which you can use to patch the game's memory. 78 | /// 79 | /// Example: 80 | /// 81 | /// ``` 82 | /// // Replace the instruction at `main` + 0x14a8504 with a branch 83 | /// // to `main` + 0x14a853C 84 | /// let text_builder: PatchBuilder = Patch::in_text(0x69), 85 | /// ``` 86 | pub struct Patch(usize); 87 | 88 | impl Patch { 89 | fn compute_address(offset: usize, region: Region) -> *const u8 { 90 | unsafe { (getRegionAddress(region) as *const u8).add(offset) } 91 | } 92 | 93 | /// Provide the base offset to work with for methods. 94 | /// This offset will be treated as absolute. 95 | /// 96 | /// If you would like to work relative to a region, prefer using the other methods like [Patch::in_text](Patch#in_text). 97 | /// 98 | /// Some methods, such as [branch_to](Patch#branch_to), will assume a Region for you. 99 | /// 100 | /// Example: 101 | /// 102 | /// ``` 103 | /// // In this context, branch_to will overwrite the instruction at offset 0x69 104 | /// // Since branch_to assumes that you are working using Region::Text, your offset will be turned into .text+offset. 105 | /// Patch::at_offset(0x69).branch_to(0x420); 106 | /// ``` 107 | pub fn at_offset(offset: usize) -> Self { 108 | Self(offset) 109 | } 110 | 111 | /// Insert a ``b`` ARM instruction to jump to the destination offset. 112 | /// It is assumed that the offset you provided is relative to the Text region of the running executable 113 | /// 114 | /// Shortcut method for: 115 | /// ``` 116 | /// BranchBuilder::branch().branch_offset().branch_to_offset().replace() 117 | /// ``` 118 | /// 119 | /// Example: 120 | /// 121 | /// ``` 122 | /// // Overwriting the instruction at offset 0x69 with a branch in the .text section that redirects the Program Counter to address 0x420 123 | /// Patch::at_offset(0x69).branch_to(0x420); 124 | /// ``` 125 | pub fn branch_to(self, dest_offset: usize) { 126 | BranchBuilder::branch() 127 | .branch_offset(self.0) 128 | .branch_to_offset(dest_offset) 129 | .replace() 130 | } 131 | 132 | /// Insert a ``b`` ARM instruction to jump to the destination offset. 133 | /// The offset you provide must be relative to the base address provided to the constructor. 134 | /// 135 | /// Shortcut method for: 136 | /// ``` 137 | /// BranchBuilder::branch().branch_offset().branch_to_offset().replace() 138 | /// ``` 139 | /// 140 | /// Example: 141 | /// 142 | /// ``` 143 | /// // Overwriting the instruction at offset 0x69420 in the .text section with a branch that redirects the Program Counter to address 0x420 144 | /// Patch::at_offset(0x69000).branch_to_relative(0x420); 145 | /// ``` 146 | pub fn branch_to_relative(self, dest_offset: usize) { 147 | BranchBuilder::branch() 148 | .branch_offset(self.0) 149 | .branch_to_offset(self.0 + dest_offset) 150 | .replace() 151 | } 152 | 153 | /// Insert a ``bl`` ARM instruction to jump to the destination offset. 154 | /// It is assumed that the offset you provided is relative to the Text region of the running executable 155 | /// 156 | /// Shortcut method for: 157 | /// ``` 158 | /// BranchBuilder::branch_link().branch_offset().branch_to_offset().replace 159 | /// ``` 160 | /// 161 | /// Example: 162 | /// 163 | /// ``` 164 | /// // Overwriting the instruction at offset 0x69 with a branch link in the .text section that redirects the Program Counter to address 0x420 165 | /// Patch::at_offset(0x69).branch_link_to(0x420); 166 | /// ``` 167 | pub fn branch_link_to(self, dest_offset: usize) { 168 | BranchBuilder::branch() 169 | .branch_offset(self.0) 170 | .branch_to_offset(dest_offset) 171 | .replace() 172 | } 173 | 174 | /// Insert a ``bl`` ARM instruction to jump to the destination offset. 175 | /// The offset you provide must be relative to the base address provided to the constructor. 176 | /// 177 | /// Shortcut method for: 178 | /// ``` 179 | /// BranchBuilder::branch_link().branch_offset().branch_to_offset().replace 180 | /// ``` 181 | /// 182 | /// Example: 183 | /// 184 | /// ``` 185 | /// // Overwriting the instruction at offset 0x69420 in the .text section with a branch link that redirects the Program Counter to address 0x420 186 | /// Patch::at_offset(0x69000).branch_link_to_relative(0x420); 187 | /// ``` 188 | pub fn branch_link_to_relative(self, dest_offset: usize) { 189 | BranchBuilder::branch() 190 | .branch_offset(self.0) 191 | .branch_to_offset(self.0 + dest_offset) 192 | .replace() 193 | } 194 | 195 | /// Use the base offset provided to [at_offset](Patch#at_offset) to get an address for a section of the executable. 196 | /// 197 | /// It is preferable that you use the shortcut methods for conciseness. 198 | /// 199 | /// Example: 200 | /// 201 | /// ``` 202 | /// // In this context, branch_to will overwrite the instruction at offset 0x69 203 | /// let builder: PatchBuilder = Patch::at_offset(0x69).in_section(Region::Text); 204 | /// ``` 205 | pub fn in_section(self, region: Region) -> PatchBuilder { 206 | PatchBuilder(Self::compute_address(self.0, region)) 207 | } 208 | 209 | /// Provide a PatchBuilder targeting the .text section 210 | /// 211 | /// Shortcut method for: 212 | /// ``` 213 | /// PatchBuilder::at_offset(offset).in_section(Region::Text) 214 | /// ``` 215 | /// 216 | /// Example: 217 | /// ``` 218 | /// let builder: PatchBuilder = Patch::in_text(offset); 219 | /// ``` 220 | pub fn in_text(offset: usize) -> PatchBuilder { 221 | PatchBuilder(Self::compute_address(offset, Region::Text)) 222 | } 223 | 224 | /// Provide a PatchBuilder targeting the .data section 225 | /// 226 | /// Shortcut method for: 227 | /// ``` 228 | /// PatchBuilder::at_offset(offset).in_section(Region::Data) 229 | /// ``` 230 | /// 231 | /// Example: 232 | /// ``` 233 | /// let builder: PatchBuilder = Patch::in_data(offset); 234 | /// ``` 235 | pub fn in_data(offset: usize) -> PatchBuilder { 236 | PatchBuilder(Self::compute_address(offset, Region::Data)) 237 | } 238 | 239 | /// Provide a PatchBuilder targeting the .rodata section 240 | /// 241 | /// Shortcut method for: 242 | /// ``` 243 | /// PatchBuilder::at_offset(offset).in_section(Region::Rodata) 244 | /// ``` 245 | /// 246 | /// Example: 247 | /// ``` 248 | /// let builder: PatchBuilder = Patch::in_rodata(offset); 249 | /// ``` 250 | pub fn in_rodata(offset: usize) -> PatchBuilder { 251 | PatchBuilder(Self::compute_address(offset, Region::Rodata)) 252 | } 253 | 254 | /// Provide a PatchBuilder targeting the .bss section 255 | /// 256 | /// Shortcut method for: 257 | /// ``` 258 | /// PatchBuilder::at_offset(offset).in_section(Region::Bss) 259 | /// ``` 260 | /// 261 | /// Example: 262 | /// ``` 263 | /// let builder: PatchBuilder = Patch::in_bss(offset); 264 | /// ``` 265 | pub fn in_bss(offset: usize) -> PatchBuilder { 266 | PatchBuilder(Self::compute_address(offset, Region::Bss)) 267 | } 268 | 269 | /// Provide a PatchBuilder targeting the heap 270 | /// 271 | /// Shortcut method for: 272 | /// ``` 273 | /// PatchBuilder::at_offset(offset).in_section(Region::Heap) 274 | /// ``` 275 | /// 276 | /// Example: 277 | /// ``` 278 | /// let builder: PatchBuilder = Patch::in_heap(offset); 279 | /// ``` 280 | pub fn in_heap(offset: usize) -> PatchBuilder { 281 | PatchBuilder(Self::compute_address(offset, Region::Heap)) 282 | } 283 | } 284 | 285 | /// A builder which you can use the patch the game's memory. 286 | /// 287 | /// Example: 288 | /// 289 | /// ``` 290 | /// // Replace the instruction at `main` + 0x69 with a NOP instruction 291 | /// Patch::in_text(0x69).nop().unwrap() 292 | /// ``` 293 | pub struct PatchBuilder(*const u8); 294 | 295 | impl PatchBuilder { 296 | /// Overwrites data at the provided offset with the provided value. 297 | /// Equivalent to memcpy 298 | pub fn data(self, val: T) -> Result<(), Error> { 299 | unsafe { 300 | sky_memcpy( 301 | self.0 as _, 302 | &val as *const _ as _, 303 | core::mem::size_of::(), 304 | ) 305 | .ok()? 306 | }; 307 | Ok(()) 308 | } 309 | 310 | /// Overwrites data at the provided offset with the content of a slice. 311 | /// 312 | /// # Example: 313 | /// ``` 314 | /// // An array of four u8 315 | /// Patch::in_data(0x69).bytes(b"Ferris").unwrap(); 316 | /// 317 | /// // A &str (with no null-terminator) 318 | /// let a_string = String::from("Ferris"); 319 | /// Patch::in_data(0x69).bytes(&a_string).unwrap(); 320 | /// 321 | /// // A &[u8] slice 322 | /// let log_wide = &[0xef, 0xbc, 0xac, 0xef, 0xbd, 0x8f, 0xef, 0xbd, 0x87, 0x00, 0x00]; 323 | /// Patch::in_data(0x69).bytes(log_wide).unwrap(); 324 | /// ``` 325 | pub fn bytes>(self, val: B) -> Result<(), Error> { 326 | let slice = val.as_ref(); 327 | unsafe { sky_memcpy(self.0 as _, slice.as_ptr() as *const _ as _, slice.len()).ok()? }; 328 | 329 | Ok(()) 330 | } 331 | 332 | /// Overwrites data at the provided offset with a C string. 333 | /// The null-terminator is appended for you. 334 | /// 335 | /// If you do not wish for the null-terminator to be added, use [bytes](PatchBuilder#bytes) instead. 336 | /// 337 | /// Example: 338 | /// ``` 339 | /// Patch::in_data(0x69).cstr("Ferris").unwrap(); 340 | /// ``` 341 | pub fn cstr(self, string: &str) -> Result<(), Error> { 342 | let string = String::from(string) + "\0"; 343 | self.bytes(&string) 344 | } 345 | 346 | /// Overwrites bytes at the provided offset with a NOP instruction. 347 | /// 348 | /// Example: 349 | /// ``` 350 | /// Patch::in_text(0x69).nop().unwrap(); 351 | /// ``` 352 | pub fn nop(self) -> Result<(), Error> { 353 | self.data(NOP) 354 | } 355 | } 356 | 357 | enum BranchType { 358 | Branch, 359 | BranchLink, 360 | } 361 | 362 | /// A builder type to help when replacing branches in games 363 | /// 364 | /// Example: 365 | /// 366 | /// ```rust 367 | /// // Replace the instruction at `main` + 0x14a8504 with a branch 368 | /// // to `main` + 0x14a853C 369 | /// BranchBuilder::branch() 370 | /// .branch_offset(0x14a8504) 371 | /// .branch_to_offset(0x14a853C) 372 | /// .replace() 373 | /// 374 | /// // Replace the instruction at `main` + 0x14a8504 with a branch 375 | /// // to `replacement_function` 376 | /// BranchBuilder::branch() 377 | /// .branch_offset(0x14a8504) 378 | /// .branch_to_ptr(replacement_function as *const ()) 379 | /// .replace() 380 | /// ``` 381 | pub struct BranchBuilder { 382 | branch_type: BranchType, 383 | offset: Option, 384 | ptr: Option<*const ()>, 385 | // TODO: add NRO support 386 | } 387 | 388 | impl BranchBuilder { 389 | fn internal_new() -> Self { 390 | Self { 391 | branch_type: BranchType::Branch, 392 | offset: None, 393 | ptr: None, 394 | } 395 | } 396 | 397 | /// Create new branch builder for a `b` ARM instruction 398 | pub fn branch() -> Self { 399 | Self { 400 | branch_type: BranchType::Branch, 401 | ..BranchBuilder::internal_new() 402 | } 403 | } 404 | 405 | /// Create new branch builder for a `bl` ARM instruction 406 | pub fn branch_link() -> Self { 407 | Self { 408 | branch_type: BranchType::BranchLink, 409 | ..BranchBuilder::internal_new() 410 | } 411 | } 412 | 413 | /// Set the offset within the executable of the instruction to replace 414 | pub fn branch_offset(mut self, offset: usize) -> Self { 415 | self.offset = Some(offset); 416 | 417 | self 418 | } 419 | 420 | /// Offset within the executable for the branch to jump to 421 | pub fn branch_to_offset(mut self, offset: usize) -> Self { 422 | unsafe { 423 | self.ptr = Some( 424 | (getRegionAddress(Region::Text) as *const u8).offset(offset as isize) as *const (), 425 | ); 426 | } 427 | 428 | self 429 | } 430 | 431 | /// Set a pointer for the branch to be jumped to. Must be within +/- 128 MiB of the given offset 432 | pub fn branch_to_ptr(mut self, ptr: *const T) -> Self { 433 | self.ptr = Some(ptr as *const ()); 434 | 435 | self 436 | } 437 | 438 | /// 439 | /// Replaces an instruction at the provided offset with a branch to the given pointer. 440 | /// 441 | /// # Panics 442 | /// 443 | /// Panics if an offset/ptr hasn't been provided or if the pointer is out of range of the 444 | /// branch 445 | #[track_caller] 446 | pub fn replace(self) { 447 | let offset = match self.offset { 448 | Some(offset) => offset, 449 | None => panic!("Offset is required to replace"), 450 | }; 451 | 452 | let instr_magic = match self.branch_type { 453 | BranchType::Branch => 0b000101, 454 | BranchType::BranchLink => 0b100101, 455 | } << 26; 456 | 457 | let branch_ptr = 458 | unsafe { (getRegionAddress(Region::Text) as *const u8).offset(offset as isize) } 459 | as isize; 460 | 461 | let branch_to_ptr = match self.ptr { 462 | Some(ptr) => ptr as *const u8, 463 | None => panic!("Either branch_to_ptr or branch_to_offset is required to replace"), 464 | } as isize; 465 | 466 | let imm26 = match (branch_to_ptr - branch_ptr) / 4 { 467 | distance if within_branch_range(distance) => { 468 | ((branch_to_ptr - branch_ptr) as usize) >> 2 469 | } 470 | _ => panic!("Branch target is out of range, must be within +/- 128 MiB"), 471 | }; 472 | 473 | let instr: u64 = (instr_magic | imm26) as u64; 474 | 475 | if let Err(err) = Patch::in_text(offset).data(instr) { 476 | panic!("Failed to patch data, error: {:?}", err) 477 | } 478 | } 479 | } 480 | 481 | #[allow(non_upper_case_globals)] 482 | const MiB: isize = 0x100000; 483 | const BRANCH_RANGE: isize = 128 * MiB; 484 | 485 | fn within_branch_range(distance: isize) -> bool { 486 | (-BRANCH_RANGE..BRANCH_RANGE).contains(&distance) 487 | } 488 | -------------------------------------------------------------------------------- /src/text_iter.rs: -------------------------------------------------------------------------------- 1 | use crate::hooks::{getRegionAddress, Region}; 2 | 3 | use core::{iter::StepBy, ops::Range}; 4 | 5 | pub struct TextIter + Sized> { 6 | inner: InnerIter, 7 | } 8 | 9 | impl TextIter>> { 10 | pub fn new() -> Self { 11 | unsafe { 12 | let text = getRegionAddress(Region::Text) as usize; 13 | let rodata = getRegionAddress(Region::Rodata) as usize; 14 | 15 | Self { 16 | inner: (text..rodata).step_by(4), 17 | } 18 | } 19 | } 20 | } 21 | 22 | impl + Sized> Iterator for TextIter { 23 | type Item = (usize, Instruction); 24 | 25 | fn next(&mut self) -> Option { 26 | let ptr = self.inner.next()? as *const u32; 27 | let raw_instr = unsafe { *ptr }; 28 | Some((ptr as usize, Instruction::from_u32(raw_instr))) 29 | } 30 | } 31 | 32 | const LDR_MASK: u32 = 0b1111111111_000000000000_00000_00000; // 64-bit LDR unsigned offset 33 | const LDR_MASKED: u32 = 0b1111100101_000000000000_00000_00000; 34 | 35 | const ADD_MASK: u32 = 0b11111111_00_000000000000_00000_00000; // 64-bit ADD immediate 36 | const ADD_MASKED: u32 = 0b10010001_00_000000000000_00000_00000; 37 | 38 | const ADRP_MASK: u32 = 0b1_00_11111_0000000000000000000_00000; 39 | const ADRP_MASKED: u32 = 0b1_00_10000_0000000000000000000_00000; 40 | 41 | const LDUR_MASK: u32 = 0b11_111_1_11_11_1_000000000_00_00000_00000; 42 | const LDUR_MASKED: u32 = 0b11_111_0_00_01_0_000000000_00_00000_00000; 43 | 44 | const LDRB_MASK: u32 = 0b11_111_1_11_11_0_00000_000_0_00_00000_00000; // LDRB immediate Unsigned offset 45 | const LDRB_MASKED: u32 = 0b00_111_0_01_01_0_00000_000_0_00_00000_00000; 46 | 47 | const SUB_MASK: u32 = 0b1_1_1_11111_00_0_00000_000000_00000_00000; // 32-bit SUB Immediate 48 | const SUB_MASKED: u32 = 0b0_1_0_10001_00_0_00000_000000_00000_00000; 49 | 50 | const AND_MASK: u32 = 0b1_11_111111_1_000000_000000_00000_00000; // 32-bit AND immediate 51 | const AND_MASKED: u32 = 0b0_00_100100_0_000000_000000_00000_00000; 52 | 53 | const LDRSW_MASK: u32 = 0b11_111_1_11_11_000000000000_00000_00000; // 64-bit LDRSW unsigned offset 54 | const LDRSW_MASKED: u32 = 0b10_111_0_01_10_000000000000_00000_00000; 55 | 56 | const CBZ_MASK: u32 = 0b1_111111_1_0000000000000000000_00000; // 32-bit CBZ 57 | const CBZ_MASKED: u32 = 0b0_011010_0_0000000000000000000_00000; 58 | 59 | const CMP_MASK: u32 = 0b1_1_1_11111_00_000000000000_00000_00000; // 32-bit CMP immediate 60 | const CMP_MASKED: u32 = 0b0_1_1_10001_00_000000000000_00000_00000; 61 | 62 | const BCS_MASK: u32 = 0b1111111_1_0000000000000000000_1_1111; // B.cond jump 63 | const BCS_MASKED: u32 = 0b0101010_0_0000000000000000000_0_0010; 64 | 65 | pub enum Instruction { 66 | Ldr { imm: u16, rn: u8, rt: u8 }, 67 | Add { shift: u8, imm: u16, rn: u8, rd: u8 }, 68 | Adrp { imm: u32, rd: u8 }, 69 | Ldur { imm: u16, rn: u8, rt: u8 }, 70 | Ldrb { imm: u16, rn: u8, rt: u8 }, 71 | Sub { shift: u8, imm: u16, rn: u8, rd: u8 }, 72 | And { imm: u16, rn: u8, rd: u8 }, 73 | Mov { imm: u8, rm: u8, rn: u8, rd: u8 }, 74 | Bl { imm: u32 }, 75 | Ldrsw { imm: u16, rn: u8, rt: u8 }, 76 | Cbz { imm: u32, rt: u8 }, 77 | Cmp { shift: u8, imm: u16, rn: u8 }, 78 | BCs { imm: u32, cond: u8 }, 79 | Unk(u32), 80 | } 81 | 82 | impl Instruction { 83 | fn u32_as_ldr(val: u32) -> Option { 84 | if val & LDR_MASK == LDR_MASKED { 85 | Some(Instruction::Ldr { 86 | imm: ((val >> 10) & 0xFFF) as u16, 87 | rn: ((val >> 5) & 0x1F) as u8, 88 | rt: (val & 0x1F) as u8, 89 | }) 90 | } else { 91 | None 92 | } 93 | } 94 | 95 | fn u32_as_add(val: u32) -> Option { 96 | if val & ADD_MASK == ADD_MASKED { 97 | Some(Instruction::Add { 98 | shift: ((val >> 22) & 0x3) as u8, 99 | imm: ((val >> 10) & 0xFFF) as u16, 100 | rn: ((val >> 5) & 0x1F) as u8, 101 | rd: (val & 0x1F) as u8, 102 | }) 103 | } else { 104 | None 105 | } 106 | } 107 | 108 | fn u32_as_adrp(val: u32) -> Option { 109 | if val & ADRP_MASK == ADRP_MASKED { 110 | let immhi = (val >> 5) & 0x7FFFF; 111 | let immlo = (val >> 29) & 0x3; 112 | Some(Instruction::Adrp { 113 | imm: (immhi << 14) + (immlo << 12), 114 | rd: (val & 0x1F) as u8, 115 | }) 116 | } else { 117 | None 118 | } 119 | } 120 | 121 | fn u32_as_ldur(val: u32) -> Option { 122 | if val & LDUR_MASK == LDUR_MASKED { 123 | Some(Instruction::Ldur { 124 | imm: ((val >> 12) & 0x1FF) as u16, 125 | rn: ((val >> 5) & 0x1F) as u8, 126 | rt: ((val >> 5) & 0x1F) as u8, 127 | }) 128 | } else { 129 | None 130 | } 131 | } 132 | 133 | fn u32_as_ldrb(val: u32) -> Option { 134 | if val & LDRB_MASK == LDRB_MASKED { 135 | Some(Instruction::Ldrb { 136 | imm: ((val >> 10) & 0xFFF) as u16, 137 | rn: ((val >> 5) & 0x1F) as u8, 138 | rt: (val & 0x1F) as u8, 139 | }) 140 | } else { 141 | None 142 | } 143 | } 144 | 145 | fn u32_as_sub(val: u32) -> Option { 146 | if val & SUB_MASK == SUB_MASKED { 147 | Some(Instruction::Sub { 148 | shift: ((val >> 22) & 0x3) as u8, 149 | imm: ((val >> 10) & 0xFFF) as u16, 150 | rn: ((val >> 5) & 0x1F) as u8, 151 | rd: (val & 0x1F) as u8, 152 | }) 153 | } else { 154 | None 155 | } 156 | } 157 | 158 | fn u32_as_and(val: u32) -> Option { 159 | if val & AND_MASK == AND_MASKED { 160 | let immr = (val >> 16) & 0x3F; 161 | let imms = (val >> 10) & 0x3F; 162 | Some(Instruction::And { 163 | imm: ((imms << 6) + immr) as u16, 164 | rn: ((val >> 5) & 0x1F) as u8, 165 | rd: (val & 0x1F) as u8, 166 | }) 167 | } else { 168 | None 169 | } 170 | } 171 | 172 | fn u32_as_cbz(val: u32) -> Option { 173 | if val & CBZ_MASK == CBZ_MASKED { 174 | Some(Instruction::Cbz { 175 | imm: ((val >> 5) & 0x7FFFF), 176 | rt: (val & 0x1F) as u8, 177 | }) 178 | } else { 179 | None 180 | } 181 | } 182 | 183 | fn u32_as_ldrsw(val: u32) -> Option { 184 | if val & LDRSW_MASK == LDRSW_MASKED { 185 | Some(Instruction::Ldrsw { 186 | imm: ((val >> 10) & 0xFFF) as u16, 187 | rn: ((val >> 5) & 0x1F) as u8, 188 | rt: (val & 0x1F) as u8, 189 | }) 190 | } else { 191 | None 192 | } 193 | } 194 | 195 | fn u32_as_cmp(val: u32) -> Option { 196 | if val & CMP_MASK == CMP_MASKED { 197 | Some(Instruction::Cmp { 198 | shift: ((val >> 22) & 0x3) as u8, 199 | imm: ((val >> 10) & 0xFFF) as u16, 200 | rn: ((val >> 5) & 0x1F) as u8, 201 | }) 202 | } else { 203 | None 204 | } 205 | } 206 | 207 | fn u32_as_bcs(val: u32) -> Option { 208 | if val & BCS_MASK == BCS_MASKED { 209 | Some(Instruction::BCs { 210 | imm: ((val >> 5) & 0x7FFFF), 211 | cond: (val & 0xF) as u8, 212 | }) 213 | } else { 214 | None 215 | } 216 | } 217 | 218 | fn from_u32(val: u32) -> Self { 219 | Self::u32_as_ldr(val) 220 | .or_else(|| Self::u32_as_add(val)) 221 | .or_else(|| Self::u32_as_adrp(val)) 222 | .or_else(|| Self::u32_as_ldur(val)) 223 | .or_else(|| Self::u32_as_ldrb(val)) 224 | .or_else(|| Self::u32_as_sub(val)) 225 | .or_else(|| Self::u32_as_and(val)) 226 | .or_else(|| Self::u32_as_cbz(val)) 227 | .or_else(|| Self::u32_as_cmp(val)) 228 | .or_else(|| Self::u32_as_bcs(val)) 229 | .or_else(|| Self::u32_as_ldrsw(val)) 230 | .unwrap_or(Self::Unk(val)) 231 | } 232 | } 233 | 234 | pub fn adrp_get_imm(instr: u32) -> u32 { 235 | let immhi = (instr >> 5) & 0x7FFFF; 236 | let immlo = (instr >> 29) & 0x3; 237 | return (immhi << 14) + (immlo << 12); 238 | } 239 | 240 | pub fn add_get_imm(instr: u32) -> u32 { 241 | return (instr >> 10) & 0xFFF; 242 | } 243 | -------------------------------------------------------------------------------- /src/unix_alloc.rs: -------------------------------------------------------------------------------- 1 | use std::alloc::*; 2 | use crate::libc; 3 | 4 | pub struct UnixAllocator; 5 | 6 | impl UnixAllocator { 7 | pub unsafe fn aligned_malloc(layout: &Layout) -> *mut u8 { 8 | let mut out = std::ptr::null_mut(); 9 | 10 | let align = layout.align().max(std::mem::size_of::()); 11 | let ret = libc::posix_memalign(&mut out, align, layout.size()); 12 | if ret != 0 { 13 | std::ptr::null_mut() 14 | } else { 15 | out as *mut u8 16 | } 17 | } 18 | 19 | pub unsafe fn manual_realloc(ptr: *mut u8, old_layout: Layout, new_size: usize) -> *mut u8 { 20 | let new_layout = Layout::from_size_align_unchecked(new_size, old_layout.align()); 21 | 22 | let new_ptr = Self.alloc(new_layout); 23 | if !new_ptr.is_null() { 24 | let size = std::cmp::min(old_layout.size(), new_size); 25 | std::ptr::copy_nonoverlapping(ptr, new_ptr, size); 26 | Self.dealloc(ptr, old_layout); 27 | } 28 | new_ptr 29 | } 30 | } 31 | 32 | unsafe impl GlobalAlloc for UnixAllocator { 33 | #[inline] 34 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 35 | if layout.align() <= 16 && layout.align() <= layout.size() { 36 | libc::malloc(layout.size()) as *mut u8 37 | } else { 38 | Self::aligned_malloc(&layout) 39 | } 40 | } 41 | 42 | #[inline] 43 | unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { 44 | if layout.align() <= 16 && layout.align() <= layout.size() { 45 | libc::calloc(layout.size(), 1) as *mut u8 46 | } else { 47 | let ptr = self.alloc(layout); 48 | if !ptr.is_null() { 49 | std::ptr::write_bytes(ptr, 0, layout.size()); 50 | } 51 | ptr 52 | } 53 | } 54 | 55 | #[inline] 56 | unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { 57 | libc::free(ptr as _) 58 | } 59 | 60 | #[inline] 61 | unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { 62 | Self::manual_realloc(ptr, layout, new_size) 63 | } 64 | } --------------------------------------------------------------------------------