Skip to content

Instantly share code, notes, and snippets.

@Coolsonickirby
Last active March 13, 2024 13:54
Show Gist options
  • Save Coolsonickirby/1ebfb8e59d4121376b0a95a0de2de2a3 to your computer and use it in GitHub Desktop.
Save Coolsonickirby/1ebfb8e59d4121376b0a95a0de2de2a3 to your computer and use it in GitHub Desktop.
former wip audio table expansion (shoutouts to jozz for telling me about the easier way)
use crate::api::cpu::*;
use once_cell::sync::Lazy;
use skyline::hooks::{getRegionAddress, InlineCtx, Region};
use std::collections::HashMap;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct NarrationCharacallEntry {
pub unk_1: u64,
pub nus3bank_path: u64,
pub tonelabel_path: u64,
pub nus3audio_path: u64,
pub unk_2: u64,
}
// this is gonna be a wip for a while
pub static mut NARRATION_CHARACALL_TABLE: Lazy<NarrationCharacallTable> =
Lazy::new(|| NarrationCharacallTable::new(0x4545230, 0x21));
// (ptr, data)
pub static mut HOOK_DATA_FOR_ID: Lazy<HashMap<u16, Data>> = Lazy::new(|| HashMap::new());
pub struct NarrationCharacallTable {
pub offset: usize,
pub refs: Vec<PatchLocation>,
pub table: Vec<NarrationCharacallEntry>,
}
#[derive(Debug)]
pub struct Data {
pub ptr: usize,
pub register: u8,
}
impl<'a, 'b> PartialEq<Data> for Data {
fn eq(&self, other: &Data) -> bool {
self.ptr == other.ptr && self.register == other.register
}
}
static mut DATA_ID: u16 = 0;
fn get_data_id() -> u16 {
unsafe {
let d = DATA_ID;
DATA_ID += 1;
d
}
}
pub fn get_id_if_data_exists(data: &Data) -> Option<u16> {
let mut r: Option<u16> = None;
unsafe {
for (key, value) in &*HOOK_DATA_FOR_ID {
if *data == *value {
r = Some(*key);
break;
}
}
}
r
}
pub fn insert_data(data: Data) -> u16 {
unsafe {
let id = get_data_id();
HOOK_DATA_FOR_ID.insert(id, data);
id
}
}
pub struct MovZ {
pub imm16: u32,
pub rd: u8,
}
impl MovZ {
const MASKED: u32 = 0b110100101_00_0000000000000000_00000;
pub fn encode(&self) -> u32 {
Self::MASKED
| (if self.imm16 > 0xFFFF { 1 } else { 0 } << 21)
| (if self.imm16 > 0xFFFF {
self.imm16 >> 16
} else {
self.imm16
} << 5)
| (self.rd as u32)
}
}
use skyline::{hooks::*, libc};
extern "C" {
pub fn A64InlineHook(symbol: *const libc::c_void, replace: *const libc::c_void);
}
#[no_mangle]
pub extern "C" fn inline_hook_for_table_adrp_skyline_internal_original_fn(ctx: &mut InlineCtx) {
unsafe {
let id = *ctx.registers[18].x.as_ref() as u16;
let data = HOOK_DATA_FOR_ID.get(&id).unwrap();
*ctx.registers[(*data).register as usize].x.as_mut() = (*data).ptr as u64;
}
}
pub fn patch_id_and_jump_at_offset(ptr: usize, register: u8, text_offset: usize) {
let target_ptr = {
if [0x23f0b40, 0x240bcb0, 0x240c00c].contains(&text_offset) {
ptr + 32
} else {
ptr
}
};
let data = Data { ptr: target_ptr, register };
let movz = MovZ {
imm16: match get_id_if_data_exists(&data) {
Some(id) => id as u32,
None => insert_data(data) as u32,
},
rd: 18,
};
skyline::patching::Patch::in_text(text_offset)
.data(movz.encode())
.unwrap();
skyline::patching::Patch::in_text(text_offset + 4)
.nop()
.unwrap();
unsafe {
println!(
"HOOK LOCATION: {:#x} - {:#x}",
text_offset,
getRegionAddress(Region::Text) as usize + text_offset
);
A64InlineHook(
(getRegionAddress(Region::Text) as usize + text_offset + 4) as _,
inline_hook_for_table_adrp_skyline_internal_original_fn as _,
);
}
}
pub fn patch_id_and_jump_xrefs(ptr: usize, xrefs: &Vec<PatchLocation>) {
for xref in xrefs {
patch_id_and_jump_at_offset(ptr, xref.adrp.rd, xref.text_offset);
}
}
fn get_text() -> &'static [u8] {
unsafe {
let ptr = getRegionAddress(Region::Text) as *const u8;
let size = (getRegionAddress(Region::Rodata) as usize) - (ptr as usize);
std::slice::from_raw_parts(ptr, size)
}
}
impl NarrationCharacallTable {
pub fn new(offset: usize, size: usize) -> NarrationCharacallTable {
// clone table with offset and size
unsafe {
let data = getRegionAddress(Region::Text) as usize;
// Subtract start of data offset
let mut current_offset = offset;
let mut table: Vec<NarrationCharacallEntry> = vec![];
for _ in 0..size {
let narration_chara_call_entry = *((data + current_offset)
as *const NarrationCharacallEntry);
table.push(narration_chara_call_entry);
current_offset +=
core::mem::size_of::<NarrationCharacallEntry>();
}
let mut patch_locations: Vec<PatchLocation> = TableRefIter::new(&get_text(), 0x4545228)
.into_iter()
.collect();
for x in &patch_locations {
println!("Location: {:#x} - {}", x.text_offset, x.adrp.rd);
}
NarrationCharacallTable {
offset: offset,
refs: patch_locations,
table: table,
}
}
}
pub fn push(&mut self, entry: NarrationCharacallEntry) {
self.table.push(entry);
}
pub fn patch(&mut self) {
let table_ptr = self.table.as_ptr() as usize;
patch_id_and_jump_xrefs(table_ptr, &self.refs);
println!("{:#x?}", self.table);
println!("{:#x}", table_ptr);
}
}
/* Everything here was written by jam1garner and can be found in the following repo:
* https://github.com/jam1garner/stage-table-refs
*
* Changed StageRef to TableRef for more generic naming
*/
use std::iter::{Copied, Enumerate, StepBy};
use std::slice::ArrayWindows;
pub struct TableRefIter<I: Iterator<Item = [u8; 8]>> {
pub table_offset: usize,
pub inner: Enumerate<I>,
}
const BOTTOM_12_BITS: usize = 0b1111_1111_1111;
pub struct PatchLocation {
pub text_offset: usize,
pub offset_from_table_start: usize,
pub adrp: Adrp,
pub add: AddImm,
}
impl<I: Iterator<Item = [u8; 8]>> Iterator for TableRefIter<I> {
type Item = PatchLocation;
fn next(&mut self) -> Option<Self::Item> {
while let Some((i, bytes)) = self.inner.next() {
let adrp = u32::from_le_bytes(bytes[..4].try_into().unwrap());
let add = u32::from_le_bytes(bytes[4..].try_into().unwrap());
match (Adrp::decode(adrp), AddImm::decode(add)) {
(Some(adrp), Some(add)) => {
if add.rd != add.rn || add.rd != adrp.rd {
continue;
}
let adrp_offset = adrp.imm;
let add_offset = add.imm12;
let instr_loc = i * 4;
let offset = (instr_loc & !BOTTOM_12_BITS)
+ (adrp_offset as usize)
+ (add_offset as usize);
if (self.table_offset..self.table_offset + 0x100).contains(&offset) {
return Some(PatchLocation {
add,
adrp,
text_offset: i * 4,
offset_from_table_start: offset - self.table_offset,
});
}
}
_ => continue,
}
}
None
}
}
type TextIter8<'a> = Copied<StepBy<ArrayWindows<'a, u8, 8>>>;
impl<'a> TableRefIter<TextIter8<'a>> {
pub fn new(text: &'a [u8], table_offset: usize) -> Self {
Self {
table_offset,
inner: text.array_windows().step_by(4).copied().enumerate(),
}
}
}
pub struct CmpPatchLocation {
pub text_offset: usize,
pub instr: CmpImmediate,
}
type TextIter<'a> = Copied<StepBy<ArrayWindows<'a, u8, 4>>>;
pub struct TableCountRefIter<I: Iterator<Item = [u8; 4]>> {
pub inner: Enumerate<I>,
pub count_check: u16,
}
impl<'a> TableCountRefIter<TextIter<'a>> {
pub fn new(text: &'a [u8], count_check: u16) -> Self {
Self {
inner: text.array_windows().step_by(4).copied().enumerate(),
count_check,
}
}
}
impl<I: Iterator<Item = [u8; 4]>> Iterator for TableCountRefIter<I> {
type Item = CmpPatchLocation;
fn next(&mut self) -> Option<Self::Item> {
while let Some((i, bytes)) = self.inner.next() {
let instr = u32::from_le_bytes(bytes);
// if let Some(instr @ CmpImmediate { imm12: 0x76, .. }) = CmpImmediate::decode(instr) {
// return Some(CmpPatchLocation { instr, text_offset: i * 4})
// }
match CmpImmediate::decode(instr) {
Some(instr) => {
if instr.imm12 == self.count_check {
return Some(CmpPatchLocation {
instr,
text_offset: i * 4,
});
}
}
None => {}
}
}
None
}
}
pub struct TableRefs {
pub refs: Vec<CmpPatchLocation>, // References to locations
pub blacklisted_offsets: Vec<usize>, // A list of offsets not to patch
}
impl TableRefs {
pub fn new(count_to_check: u16, blacklisted_offsets: Vec<usize>) -> TableRefs {
TableRefs {
refs: TableCountRefIter::new(get_text(), count_to_check)
.into_iter()
.collect(),
blacklisted_offsets,
}
}
pub fn set_count(&mut self, count: u16) {
for xref in &self.refs {
let new_cmp: CmpImmediate = CmpImmediate {
imm12: count,
reg: xref.instr.reg,
is_64_bit: xref.instr.is_64_bit,
};
// println!(
// ".text+{:#x?} - patch `{}` with `{}` {:#x}",
// xref.text_offset,
// xref.instr,
// new_cmp,
// new_cmp.encode()
// );
// std::thread::sleep(std::time::Duration::from_millis(1000));
//
if !self.blacklisted_offsets.contains(&xref.text_offset) {
skyline::patching::Patch::in_text(xref.text_offset)
.data(new_cmp.encode())
.unwrap();
}
}
}
}
pub fn install() {
let mut table = NarrationCharacallTable::new(0x4545228, 0x21);
table.push(NarrationCharacallEntry {
unk_1: 0x46,
nus3bank_path: 0x3b87269209, // same path as normal vc narration stuff with vc_narration_characall_silver instead
tonelabel_path: 0x3ccfbfc5ab,
nus3audio_path: 0x3cd12e503e,
unk_2: 0x1,
});
table.patch();
TableRefs::new(0x21, vec![]).set_count(table.table.len() as _);
}
/* Everything here (excluding the instructions not found in the original code)
* was written by jam1garner and can be found in the following repo:
* https://github.com/jam1garner/stage-table-refs
*/
pub struct Adrp {
pub imm: u32,
pub rd: u8,
}
impl Adrp {
const MASK: u32 = 0b1_00_11111_0000000000000000000_00000;
const MASKED: u32 = 0b1_00_10000_0000000000000000000_00000;
pub fn decode(instr: u32) -> Option<Self> {
if instr & Self::MASK == Self::MASKED {
let rd = (instr & 0b11111) as u8;
let immhi = (instr >> 5) & 0x7FFFF;
let immlo = (instr >> 29) & 0b11;
let imm = (immhi << 14) + (immlo << 12);
Some(Self { imm, rd })
} else {
None
}
}
pub fn encode(&self) -> u32 {
let immlo = (self.imm >> 12) & 0b11;
let immhi = self.imm >> 14;
Self::MASKED | (immlo << 29) | (immhi << 5) | (self.rd as u32)
}
}
pub struct AddImm {
pub imm12: u16,
pub shift: u8,
pub rn: u8,
pub rd: u8,
pub is_64_bit: bool,
}
impl AddImm {
const ADD_MASK: u32 = 0b01111111_00_000000000000_00000_00000; // 64-bit ADD immediate
const ADD_MASKED: u32 = 0b00010001_00_000000000000_00000_00000;
// 0b10010001_00_000000000001_00000_00000
// 0b10010001_00_000000000001_00001_00000
pub fn encode(&self) -> u32 {
let size = if self.is_64_bit { 1 } else { 0 };
let imm12 = (self.imm12 as u32) << 10;
let rn = (self.rn as u32) << 5;
let rd = self.rd as u32;
Self::ADD_MASKED | (size << 31) | imm12 | rn | rd
}
pub fn decode(instr: u32) -> Option<Self> {
if instr & Self::ADD_MASK == Self::ADD_MASKED {
let rn = ((instr >> 5) & 0x1F) as u8;
let rd = (instr & 0x1F) as u8;
let imm12 = ((instr >> 10) & 0xFFF) as u16;
let shift = ((instr >> 22) & 0x3) as u8;
let is_64_bit = (instr >> 31) == 1;
Some(AddImm {
imm12,
shift,
rn,
rd,
is_64_bit,
})
} else {
None
}
}
}
pub struct LdrImmediate {
pub imm9: u16,
pub rn: u8,
pub rt: u8,
pub is_64_bit: bool,
}
impl LdrImmediate {
const MASK: u32 = 0b10_111_1_11_11_1_000000000_11_00000_00000;
const MASKED: u32 = 0b10_111_0_00_01_0_000000000_01_00000_00000;
pub fn encode(&self) -> u32 {
let size = if self.is_64_bit { 1 } else { 0 };
let imm9 = (self.imm9 as u32) << 12;
let rn = (self.rn as u32) << 5;
let rt = self.rt as u32;
Self::MASKED | size | imm9 | rn | rt
}
}
#[derive(Debug)]
pub struct CmpImmediate {
pub imm12: u16,
pub reg: u8,
pub is_64_bit: bool,
}
impl CmpImmediate {
const MASK: u32 = 0b011111111_0_000000000000_00000_11111;
const MASKED: u32 = 0b011100010_0_000000000000_00000_11111;
pub fn decode(instr: u32) -> Option<Self> {
if instr & Self::MASK == Self::MASKED {
let reg = ((instr >> 5) & 0b11111) as u8;
let imm12 = ((instr >> 10) & 0b111111111111) as u16;
let is_64_bit = (instr >> 31) == 1;
Some(Self {
reg,
imm12,
is_64_bit,
})
} else {
None
}
}
pub fn encode(&self) -> u32 {
let sh = 0;
let sf = if self.is_64_bit { 1 } else { 0 };
let imm12 = (self.imm12 & 0b111111111111) as u32;
let rn = (self.reg & 0b11111) as u32;
Self::MASKED | (imm12 << 10) | (rn << 5) | (sf << 31) | (sh << 22)
}
}
pub struct MovK {
pub imm16: u32,
pub rd: u8,
pub lsl_16: bool,
pub lsl_32: bool
}
impl MovK {
const MASKED: u32 = 0b111100101_00_0000000000000000_00000;
// 0b111100101_00_0010000110000001_00000
// LSL #16 0b111100101_01_0010000110000001_00000
// Normal mov 0b110100101_01_0000000010011100_00000
// 0b111100101_10_0000000000000000_00000
// 0b111100101_01_0000000000000000_00000
// movk x8, #0x52, lsl #16 0b111100101_01_0000000001010010_01000
// movk x8, #0x52, lsl #32 0b111100101_10_0000000001010010_01000
pub fn encode(&self) -> u32 {
Self::MASKED | (if self.lsl_32 {1} else {0} << 22) | (if self.lsl_16 {1} else {0} << 21) | (self.imm16 << 5) | (self.rd as u32)
}
}
pub struct MovZ {
pub imm16: u32,
pub rd: u8,
}
impl MovZ {
const MASKED: u32 = 0b110100101_00_0000000000000000_00000;
pub fn encode(&self) -> u32 {
Self::MASKED | (if self.imm16 > 0xFFFF {1} else {0} << 21) | (if self.imm16 > 0xFFFF {self.imm16 >> 16} else {self.imm16} << 5) | (self.rd as u32)
}
}
pub struct LdrLiteralX {
pub imm19: u32,
pub rd: u8,
}
impl LdrLiteralX {
const MASKED: u32 = 0b01011000_0000000000000000000_00000;
pub fn encode(&self) -> u32 {
Self::MASKED | ({self.imm19} << 5) | (self.rd as u32)
}
}
pub struct BLR {
pub imm26: u64,
}
impl BLR {
const MASKED: u32 = 0b100101_000_00000000000000000000000;
// 0b100101_111_01110100011100011111000
// 0b101111_111_01110100011100011111000
pub fn encode(&self) -> u32 {
let overflow = self.imm26 > (u32::MAX as u64);
let imm16: u32 = if overflow {
(self.imm26 & 0xFFFFFF) as u32
} else {
self.imm26 as u32
};
Self::MASKED | if overflow { 0b111 << 23 } else {0} | (imm16 >> 2)
}
}
pub struct BL {
pub imm26: u64,
}
impl BL {
const MASKED: u32 = 0b000101_000_00000000000000000000000;
pub fn encode(&self) -> u32 {
let overflow = self.imm26 > (u32::MAX as u64);
let imm16: u32 = if overflow {
(self.imm26 & 0xFFFFFF) as u32
} else {
self.imm26 as u32
};
Self::MASKED | if overflow { 0b111 << 23 } else {0} | (imm16 >> 2)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment