1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
use crate::libc::{c_void, size_t, strlen};
use crate::error::{SwitchResult, Error, ErrorKind};
use crate::hooks::{Region, getRegionAddress};

#[cfg(not(feature = "std"))]
use alloc::string::String;

extern "C" {
    pub fn sky_memcpy(dst: *const c_void, src: *const c_void, size: size_t) -> SwitchResult;
}

/// Overwrite a string in read-only data with a Rust string given the offset from the start of .text
pub unsafe fn patch_str(offset: usize, string: &str) -> Result<(), Error> {
    let text_ptr = getRegionAddress(Region::Text) as *const u8;
    let str_ptr = text_ptr.offset(offset as isize);

    let len = strlen(str_ptr);

    if len < string.len() {
        return Err(Error::Skyline { kind: ErrorKind::StringTooLong })
    }

    let string = String::from(string) + "\0";
    
    sky_memcpy(str_ptr as _, string.as_ptr() as _, string.len()).ok()?;

    Ok(())
}

/// Overwrite a value in read-only data with a passed value given an offset from the start of .text
pub unsafe fn patch_data<T: Sized + Copy>(offset: usize, val: &T) -> Result<(), Error> {
    let text_ptr = getRegionAddress(Region::Text) as *const u8;
    patch_data_from_text(text_ptr, offset, val)
}

/// Overwrite a value in read-only data with a passed value given an offset from the start of .text
pub unsafe fn patch_data_from_text<T: Sized + Copy>(text_offset: *const u8, offset: usize, val: &T) -> Result<(), Error> {
    let text_ptr = text_offset;
    let data_ptr = text_ptr.offset(offset as isize);

    sky_memcpy(data_ptr as _, val as *const _ as _, core::mem::size_of::<T>()).ok()?;

    Ok(())
}

enum BranchType {
    Branch,
    BranchLink
}

/// A builder type to help when replacing branches in games
///
/// Example:
///
/// ```rust
/// // Replace the instruction at `main` + 0x14a8504 with a branch
/// // to `main` + 0x14a853C
/// BranchBuilder::branch()
///     .branch_offset(0x14a8504)
///     .branch_to_offset(0x14a853C)
///     .replace()
///
/// // Replace the instruction at `main` + 0x14a8504 with a branch
/// // to `replacement_function`
/// BranchBuilder::branch()
///     .branch_offset(0x14a8504)
///     .branch_to_ptr(replacement_function as *const ())
///     .replace()
/// ```
pub struct BranchBuilder {
    branch_type: BranchType,
    offset: Option<usize>,
    ptr: Option<*const ()>,
    // TODO: add NRO support
}

impl BranchBuilder {
    fn internal_new() -> Self {
        Self {
            branch_type: BranchType::Branch,
            offset: None,
            ptr: None
        }
    }

    /// Create new branch builder for a `b` ARM instruction
    pub fn branch() -> Self {
        Self {
            branch_type: BranchType::Branch,
            ..BranchBuilder::internal_new()
        }
    }

    /// Create new branch builder for a `bl` ARM instruction
    pub fn branch_link() -> Self {
        Self {
            branch_type: BranchType::BranchLink,
            ..BranchBuilder::internal_new()
        }
    }

    /// Set the offset within the executable of the instruction to replace
    pub fn branch_offset(mut self, offset: usize) -> Self {
        self.offset = Some(offset);

        self
    }

    /// Offset within the executable for the branch to jump to
    pub fn branch_to_offset(mut self, offset: usize) -> Self {
        unsafe {
            self.ptr = Some(
                 (getRegionAddress(Region::Text) as *const u8)
                    .offset(offset as isize) as *const ()
            );
        }

        self
    }

    /// Set a pointer for the branch to be jumped to. Must be within +/- 128 MiB of the given offset
    pub fn branch_to_ptr<T>(mut self, ptr: *const T) -> Self {
        self.ptr = Some(ptr as *const ());

        self
    }

    ///
    /// Replaces an instruction at the provided offset with a branch to the given pointer.
    ///
    /// # Panics
    ///
    /// Panics if an offset/ptr hasn't been provided or if the pointer is out of range of the
    /// branch
    #[track_caller]
    pub fn replace(self) {
        let offset = match self.offset {
            Some(offset) => offset,
            None => panic!("Offset is required to replace")
        };

        let instr_magic = match self.branch_type {
            BranchType::Branch => 0b000101,
            BranchType::BranchLink => 0b100101,
        } << 26;

        let branch_ptr = unsafe {
            (getRegionAddress(Region::Text) as *const u8).offset(offset as isize)
        } as isize;

        let branch_to_ptr = match self.ptr {
            Some(ptr) => ptr as *const u8,
            None => panic!("Either branch_to_ptr or branch_to_offset is required to replace")
        } as isize;

        let imm26 = match (branch_to_ptr - branch_ptr) / 4 {
            distance if within_branch_range(distance)
                => ((branch_to_ptr - branch_ptr) as usize) >> 2,
            _ => panic!("Branch target is out of range, must be within +/- 128 MiB")
        };

        let instr: u64 = (instr_magic | imm26) as u64;

        unsafe {
            if let Err(err) = patch_data(offset, &instr) {
                panic!("Failed to patch data, error: {:?}", err)
            }
        }
    }
}

#[allow(non_upper_case_globals)]
const MiB: isize = 0x100000;
const BRANCH_RANGE: isize = 128 * MiB;

fn within_branch_range(distance: isize) -> bool {
    (-BRANCH_RANGE..BRANCH_RANGE).contains(&distance)
}