diff --git a/src/main.rs b/src/main.rs index 778f43a..22490e2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod structs; +use structs::ReplayFrame; use tracy_client::*; use crate::structs::{ @@ -316,6 +317,14 @@ fn process_reading_loop( .timing_point_at(values.playtime as f64) .beat_len; } + + let frames_addr = p.read_i32(score_base + 0x34)? as usize; + { + let _span = span!("reading replay frames"); + + let mut frames = p.read_struct_ptr_array::(frames_addr, values.frames.len())?; + values.frames.append(&mut frames); + } // Placing at the very end cuz we should // keep up with current_bpm & unstable rate diff --git a/src/memory/process.rs b/src/memory/process.rs index 48975e4..71e523a 100644 --- a/src/memory/process.rs +++ b/src/memory/process.rs @@ -12,6 +12,7 @@ #![allow(clippy::size_of_in_element_count)] +use std::mem::{size_of, align_of}; use std::path::PathBuf; use super::error::ProcessError; @@ -106,6 +107,105 @@ pub trait ProcessTraits where Self: Sized { buff: &mut [u8] ) -> Result<(), ProcessError>; + fn read_struct( + &self, + addr: usize + ) -> Result { + let mut uninit = std::mem::MaybeUninit::::uninit(); + let ptr: *mut u8 = uninit.as_mut_ptr().cast(); + + let byte_buff = unsafe { + std::slice::from_raw_parts_mut( + ptr, + std::mem::size_of::() + ) + }; + + self.read(addr, byte_buff.len(), byte_buff)?; + + Ok(unsafe { uninit.assume_init() }) + } + + fn read_struct_array( + &self, + addr: usize, + len: usize + ) -> Result, ProcessError> { + let mut buff: Vec<_> = std::iter::repeat_with(std::mem::MaybeUninit::::uninit) + .take(len) + .collect(); + + let ptr: *mut u8 = buff.as_mut_ptr().cast(); + let byte_buff = unsafe { + std::slice::from_raw_parts_mut( + ptr, + size_of::() * len + ) + }; + + self.read(addr, byte_buff.len(), byte_buff)?; + + let ptr: *mut T = buff.as_mut_ptr().cast(); + let len = buff.len(); + let cap = buff.capacity(); + + std::mem::forget(buff); + + Ok(unsafe { Vec::from_raw_parts(ptr, len, cap) }) + } + + fn read_struct_ptr_array( + &self, + addr: usize, + skip: usize + ) -> Result, ProcessError> { + let mut ptrs = Vec::new(); + self.read_u32_array(addr, &mut ptrs)?; + + let len = ptrs.len() - skip; + + let mut arr = Vec::with_capacity(len); + if len == 0 { + return Ok(arr) + } + let size = size_of::(); + let size_with_align = size + align_of::(); + let mut chunk: usize = 1; + let mut last_ptr = 0; + + // Reading all values one-by-one is slow and wasteful + // but List<> elements are stored in chunks + // so we can find those and read multiple values at once + for (i, ptr) in ptrs.iter().skip(skip).enumerate() { + if i == 0 { last_ptr = *ptr; continue } + // BRUH + if (ptr.overflowing_sub(last_ptr).0) as usize == size_with_align { + chunk += 1; + } else { + if chunk > 1 { + let mut a = self.read_struct_array(last_ptr as usize - size_with_align * (chunk - 1), chunk)?; + arr.append(&mut a); + chunk = 1; + } else { + let a = self.read_struct(last_ptr as usize)?; + arr.push(a); + } + } + last_ptr = *ptr; + } + + if chunk > 1 { + let mut a = self.read_struct_array(last_ptr as usize - size_with_align * (chunk - 1), chunk)?; + assert!(a.len() == chunk); + arr.append(&mut a); + } else { + let a = self.read_struct(last_ptr as usize)?; + arr.push(a); + } + + Ok(arr) + } + fn read_uleb128( &self, mut addr: usize diff --git a/src/structs.rs b/src/structs.rs index 694d64b..e1979e5 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -65,6 +65,22 @@ impl From for BeatmapStatus { } } +#[repr(C)] +#[derive(Debug, Clone)] +pub struct ReplayFrame { + pub v_table: u32, + pub mouse_x: f32, + pub mouse_y: f32, + pub mouse_bits: i32, + pub time: i32, + pub mouse_left: u8, + pub mouse_right: u8, + pub mouse_left1: u8, + pub mouse_right1: u8, + pub mouse_left2: u8, + pub mouse_right2: u8, +} + #[derive(Default)] pub struct StaticAddresses { pub base: usize, @@ -134,6 +150,9 @@ pub struct Values { pub current_bpm: f64, pub kiai_now: bool, + #[serde(skip)] + pub frames: Vec, + // Calculated each iteration pub current_pp: f64, pub fc_pp: f64, @@ -182,6 +201,8 @@ impl Values { self.bpm = 0.0; self.current_bpm = 0.0; self.kiai_now = false; + + self.frames.clear(); } // TODO PR to rosu-pp to add From trait?