From e9d46c700c9fdf816756e445155f7cdf20be8cd3 Mon Sep 17 00:00:00 2001 From: Glubus <50545507+Glubus@users.noreply.github.com> Date: Sun, 13 Jul 2025 13:51:56 +0200 Subject: [PATCH] feat: added contigous memory read in one go instead one by one for hits beatmapstats or even some value in User --- examples/beatmap3.rs | 8 ++++ examples/user.rs | 4 -- src/reader/beatmap/stable/memory.rs | 48 ++++++++++++++------- src/reader/beatmap/stable/offset.rs | 6 +-- src/reader/gameplay/stable/memory.rs | 55 +++++++++++++++++------- src/reader/gameplay/stable/offset.rs | 12 +++--- src/reader/resultscreen/stable/memory.rs | 22 +++++++--- src/reader/resultscreen/stable/offset.rs | 12 +++--- src/reader/user/stable/memory.rs | 22 +++++++--- src/reader/user/stable/offset.rs | 15 ++++--- 10 files changed, 135 insertions(+), 69 deletions(-) diff --git a/examples/beatmap3.rs b/examples/beatmap3.rs index 71adf76..011efb5 100644 --- a/examples/beatmap3.rs +++ b/examples/beatmap3.rs @@ -110,5 +110,13 @@ fn main() -> Result<(), Error> { Ok(star_rating) => println!("Current beatmap star rating: {star_rating:?}"), Err(e) => println!("Error: {e:?}"), } + match beatmap_reader.info() { + Ok(info) => println!("Current beatmap info: {info:?}"), + Err(e) => println!("Error: {e:?}"), + } + match beatmap_reader.stats() { + Ok(stats) => println!("Current beatmap stats: {stats:?}"), + Err(e) => println!("Error: {e:?}"), + } Ok(()) } diff --git a/examples/user.rs b/examples/user.rs index 1d83d10..0746668 100644 --- a/examples/user.rs +++ b/examples/user.rs @@ -54,10 +54,6 @@ fn main() -> Result<(), Error> { Ok(level) => println!("Current level: {level}"), Err(e) => println!("Error: {e:?}"), } - match user_reader.playmode() { - Ok(playmode) => println!("Current playmode: {playmode}"), - Err(e) => println!("Error: {e:?}"), - } Ok(()) } diff --git a/src/reader/beatmap/stable/memory.rs b/src/reader/beatmap/stable/memory.rs index 3d7e547..695be8a 100644 --- a/src/reader/beatmap/stable/memory.rs +++ b/src/reader/beatmap/stable/memory.rs @@ -38,12 +38,19 @@ generate_offset_getter! { pub fn stats(p: &Process, state: &mut State) -> Result { let beatmap_addr = beatmap_addr(p, state)?; - // faster than using read_fn because we dont need to reload addr everytime + let mut buffer = [0u8; size_of::() * 4]; + p.read( + beatmap_addr + 0x2c, + size_of::() * 4, + &mut buffer, + )?; + + Ok(BeatmapStats { - ar: p.read_f32(beatmap_addr + BEATMAP_OFFSET.stats.ar)?, - od: p.read_f32(beatmap_addr + BEATMAP_OFFSET.stats.od)?, - cs: p.read_f32(beatmap_addr + BEATMAP_OFFSET.stats.cs)?, - hp: p.read_f32(beatmap_addr + BEATMAP_OFFSET.stats.hp)?, + ar: f32::from_le_bytes(buffer[0..4].try_into().unwrap()), + cs: f32::from_le_bytes(buffer[4..8].try_into().unwrap()), + hp: f32::from_le_bytes(buffer[8..12].try_into().unwrap()), + od: f32::from_le_bytes(buffer[12..].try_into().unwrap()), length: p.read_i32(beatmap_addr + BEATMAP_OFFSET.stats.total_length)?, star_rating: crate::reader::beatmap::stable::file::star_rating(p, state)?, object_count: p.read_i32(beatmap_addr + BEATMAP_OFFSET.stats.object_count)?, @@ -53,7 +60,25 @@ pub fn stats(p: &Process, state: &mut State) -> Result { pub fn info(p: &Process, state: &mut State) -> Result { let beatmap_addr = beatmap_addr(p, state)?; - // done like that to be more efficient reading the string one by one would need to reload addr everytime which cost more + + let mut buffer = [0u8; size_of::() * 4]; + p.read( + beatmap_addr + 0x2c, + size_of::() * 4, + &mut buffer, + )?; + + + let stats =BeatmapStats { + ar: f32::from_le_bytes(buffer[0..4].try_into().unwrap()), + cs: f32::from_le_bytes(buffer[4..8].try_into().unwrap()), + hp: f32::from_le_bytes(buffer[8..12].try_into().unwrap()), + od: f32::from_le_bytes(buffer[12..].try_into().unwrap()), + length: p.read_i32(beatmap_addr + BEATMAP_OFFSET.stats.total_length)?, + star_rating: crate::reader::beatmap::stable::file::star_rating(p, state)?, + object_count: p.read_i32(beatmap_addr + BEATMAP_OFFSET.stats.object_count)?, + slider_count: p.read_i32(beatmap_addr + BEATMAP_OFFSET.stats.slider_count)?, + }; Ok(BeatmapInfo { technical: BeatmapTechnicalInfo { md5: p.read_string(beatmap_addr + BEATMAP_OFFSET.technical.md5)?, @@ -73,16 +98,7 @@ pub fn info(p: &Process, state: &mut State) -> Result { difficulty: p.read_string(beatmap_addr + BEATMAP_OFFSET.metadata.difficulty)?, tags: p.read_string(beatmap_addr + BEATMAP_OFFSET.metadata.tags)?, }, - stats: BeatmapStats { - ar: p.read_f32(beatmap_addr + BEATMAP_OFFSET.stats.ar)?, - od: p.read_f32(beatmap_addr + BEATMAP_OFFSET.stats.od)?, - cs: p.read_f32(beatmap_addr + BEATMAP_OFFSET.stats.cs)?, - hp: p.read_f32(beatmap_addr + BEATMAP_OFFSET.stats.hp)?, - length: p.read_i32(beatmap_addr + BEATMAP_OFFSET.stats.total_length)?, - star_rating: crate::reader::beatmap::stable::file::star_rating(p, state)?, - object_count: p.read_i32(beatmap_addr + BEATMAP_OFFSET.stats.object_count)?, - slider_count: p.read_i32(beatmap_addr + BEATMAP_OFFSET.stats.slider_count)?, - }, + stats, location: BeatmapLocation { folder: p.read_string(beatmap_addr + BEATMAP_OFFSET.location.folder)?, filename: p.read_string(beatmap_addr + BEATMAP_OFFSET.location.filename)?, diff --git a/src/reader/beatmap/stable/offset.rs b/src/reader/beatmap/stable/offset.rs index 9c35c7e..595bf16 100644 --- a/src/reader/beatmap/stable/offset.rs +++ b/src/reader/beatmap/stable/offset.rs @@ -13,9 +13,9 @@ pub(crate) const BEATMAP_LOCATION_OFFSET: BeatmapLocationOffset = BeatmapLocatio pub(crate) const BEATMAP_STATS_OFFSET: BeatmapStatsOffset = BeatmapStatsOffset { ar: 0x2c, - od: 0x30, - cs: 0x34, - hp: 0x38, + cs: 0x30, + hp: 0x34, + od: 0x38, object_count: 0xf8, total_length: 0x134, drain_time: 0x0, // TODO diff --git a/src/reader/gameplay/stable/memory.rs b/src/reader/gameplay/stable/memory.rs index 177b7d3..1d584f2 100644 --- a/src/reader/gameplay/stable/memory.rs +++ b/src/reader/gameplay/stable/memory.rs @@ -9,6 +9,7 @@ use crate::{ generate_offset_getter, reader::helpers::{read_f64, read_i16, read_i32, read_string, read_u64}, }; +use std::mem::size_of; use rosu_mem::process::{Process, ProcessTraits}; pub fn rulesets_addr(p: &Process, state: &mut State) -> Result { @@ -60,15 +61,24 @@ pub fn retries(p: &Process, state: &mut State) -> Result { pub fn hits(p: &Process, state: &mut State) -> Result { let score_base = score_base(p, state)?; - // TODO: check issue for reading the full block and - // separating bits + + // Read all hits data in one memory operation + let mut hits_buffer = [0u8; size_of::() * 6]; + p.read( + score_base + GAMEPLAY_OFFSET.hits._100, + size_of::() * 6, + &mut hits_buffer, + )?; + + // Safety: unwrap here because buffer is already initialized and filled + // with zeros, the worst case scenario is hits going to be zeros Ok(Hit { - _300: p.read_i16(score_base + GAMEPLAY_OFFSET.hits._300)?, - _100: p.read_i16(score_base + GAMEPLAY_OFFSET.hits._100)?, - _50: p.read_i16(score_base + GAMEPLAY_OFFSET.hits._50)?, - _miss: p.read_i16(score_base + GAMEPLAY_OFFSET.hits._miss)?, - _geki: p.read_i16(score_base + GAMEPLAY_OFFSET.hits._geki)?, - _katu: p.read_i16(score_base + GAMEPLAY_OFFSET.hits._katu)?, + _100: i16::from_le_bytes(hits_buffer[0..2].try_into().unwrap()), + _300: i16::from_le_bytes(hits_buffer[2..4].try_into().unwrap()), + _50: i16::from_le_bytes(hits_buffer[4..6].try_into().unwrap()), + _geki: i16::from_le_bytes(hits_buffer[6..8].try_into().unwrap()), + _katu: i16::from_le_bytes(hits_buffer[8..10].try_into().unwrap()), + _miss: i16::from_le_bytes(hits_buffer[10..12].try_into().unwrap()), }) } @@ -78,6 +88,26 @@ pub fn info(p: &Process, state: &mut State) -> Result { let hp = hp(p, state)?; let mods = mods(p, state)?; + + // Read all hits data in one memory operation + let mut hits_buffer = [0u8; size_of::() * 6]; + p.read( + score_base + GAMEPLAY_OFFSET.hits._100, + size_of::() * 6, + &mut hits_buffer, + )?; + + // Safety: unwrap here because buffer is already initialized and filled + // with zeros, the worst case scenario is hits going to be zeros + let hits = Hit { + _100: i16::from_le_bytes(hits_buffer[0..2].try_into().unwrap()), + _300: i16::from_le_bytes(hits_buffer[2..4].try_into().unwrap()), + _50: i16::from_le_bytes(hits_buffer[4..6].try_into().unwrap()), + _geki: i16::from_le_bytes(hits_buffer[6..8].try_into().unwrap()), + _katu: i16::from_le_bytes(hits_buffer[8..10].try_into().unwrap()), + _miss: i16::from_le_bytes(hits_buffer[10..12].try_into().unwrap()), + }; + Ok(GameplayInfo { score: p.read_i32(score_base + GAMEPLAY_OFFSET.score)?, mods, @@ -87,13 +117,6 @@ pub fn info(p: &Process, state: &mut State) -> Result { username: p.read_string(score_base + GAMEPLAY_OFFSET.username)?, ig_time: game_time(p, state)?, // different base retries: retries(p, state)?, // different base - hits: Hit { - _300: p.read_i16(score_base + GAMEPLAY_OFFSET.hits._300)?, - _100: p.read_i16(score_base + GAMEPLAY_OFFSET.hits._100)?, - _50: p.read_i16(score_base + GAMEPLAY_OFFSET.hits._50)?, - _miss: p.read_i16(score_base + GAMEPLAY_OFFSET.hits._miss)?, - _geki: p.read_i16(score_base + GAMEPLAY_OFFSET.hits._geki)?, - _katu: p.read_i16(score_base + GAMEPLAY_OFFSET.hits._katu)?, - }, + hits, }) } diff --git a/src/reader/gameplay/stable/offset.rs b/src/reader/gameplay/stable/offset.rs index 4530d7a..dcd55a3 100644 --- a/src/reader/gameplay/stable/offset.rs +++ b/src/reader/gameplay/stable/offset.rs @@ -46,10 +46,10 @@ pub struct GameplayHitsOffset { } pub const GAMEPLAY_HITS_OFFSET: GameplayHitsOffset = GameplayHitsOffset { - _300: 0x8a, - _100: 0x88, - _50: 0x8c, - _miss: 0x92, - _geki: 0x8e, - _katu: 0x90, + _100: 0x88, // 136 + _300: 0x8A, // 138 + _50: 0x8C, // 140 + _geki: 0x8E, // 142 + _katu: 0x90, // 144 + _miss: 0x92 // 146 }; diff --git a/src/reader/resultscreen/stable/memory.rs b/src/reader/resultscreen/stable/memory.rs index 95e9a4c..a995ba0 100644 --- a/src/reader/resultscreen/stable/memory.rs +++ b/src/reader/resultscreen/stable/memory.rs @@ -19,13 +19,23 @@ pub fn result_screen_ptr(p: &Process, state: &mut State) -> Result { pub fn hits(p: &Process, state: &mut State) -> Result { let score_base = result_screen_base(p, state)?; + // Read all hits data in one memory operation + let mut hits_buffer = [0u8; size_of::() * 6]; + p.read( + score_base + RESULT_SCREEN_OFFSET.hits._100, + size_of::() * 6, + &mut hits_buffer, + )?; + + // Safety: unwrap here because buffer is already initialized and filled + // with zeros, the worst case scenario is hits going to be zeros Ok(Hit { - _300: p.read_i16(score_base + RESULT_SCREEN_OFFSET.hits._300)?, - _100: p.read_i16(score_base + RESULT_SCREEN_OFFSET.hits._100)?, - _50: p.read_i16(score_base + RESULT_SCREEN_OFFSET.hits._50)?, - _miss: p.read_i16(score_base + RESULT_SCREEN_OFFSET.hits._miss)?, - _geki: p.read_i16(score_base + RESULT_SCREEN_OFFSET.hits._geki)?, - _katu: p.read_i16(score_base + RESULT_SCREEN_OFFSET.hits._katu)?, + _100: i16::from_le_bytes(hits_buffer[0..2].try_into().unwrap()), + _300: i16::from_le_bytes(hits_buffer[2..4].try_into().unwrap()), + _50: i16::from_le_bytes(hits_buffer[4..6].try_into().unwrap()), + _geki: i16::from_le_bytes(hits_buffer[6..8].try_into().unwrap()), + _katu: i16::from_le_bytes(hits_buffer[8..10].try_into().unwrap()), + _miss: i16::from_le_bytes(hits_buffer[10..12].try_into().unwrap()), }) } diff --git a/src/reader/resultscreen/stable/offset.rs b/src/reader/resultscreen/stable/offset.rs index e66b4b8..44ab508 100644 --- a/src/reader/resultscreen/stable/offset.rs +++ b/src/reader/resultscreen/stable/offset.rs @@ -30,10 +30,10 @@ pub struct ResultScreenHitsOffset { } pub(crate) const RESULT_SCREEN_HITS_OFFSET: ResultScreenHitsOffset = ResultScreenHitsOffset { - _300: 0x8A, - _100: 0x88, - _50: 0x8C, - _miss: 0x92, - _geki: 0x8E, - _katu: 0x90, + _100: 0x88, // 136 + _300: 0x8A, // 138 + _50: 0x8C, // 140 + _geki: 0x8E, // 142 + _katu: 0x90, // 144 + _miss: 0x92 // 146 }; diff --git a/src/reader/user/stable/memory.rs b/src/reader/user/stable/memory.rs index cc2c2dd..9c6b4f9 100644 --- a/src/reader/user/stable/memory.rs +++ b/src/reader/user/stable/memory.rs @@ -5,6 +5,7 @@ use crate::reader::user::common::UserInfo; use crate::reader::user::stable::offset::USER_PROFILE_OFFSET; use crate::Error; use rosu_mem::process::{Process, ProcessTraits}; +use std::mem::size_of; pub fn user_base(p: &Process, state: &mut State) -> Result { Ok(p.read_i32(p.read_i32(state.addresses.user_profile + USER_PROFILE_OFFSET.ptr)?)?) @@ -25,19 +26,30 @@ generate_offset_getter! { pub fn info(p: &Process, state: &mut State) -> Result { let user_profile_base = user_base(p, state)?; + let mut buffer = [0u8; size_of::() * 5]; + p.read( + user_profile_base + USER_PROFILE_OFFSET.playcount, + size_of::() * 5, + &mut buffer, + )?; + let playcount = i32::from_le_bytes(buffer[0..4].try_into().unwrap()); + let playmode = i32::from_le_bytes(buffer[4..8].try_into().unwrap()); + let rank = i32::from_le_bytes(buffer[8..12].try_into().unwrap()); + let pp = i32::from_le_bytes(buffer[12..16].try_into().unwrap()); + let bancho_status = i32::from_le_bytes(buffer[16..20].try_into().unwrap()); let user_profile = UserInfo { id: p.read_i32(user_profile_base + USER_PROFILE_OFFSET.id)?, username: p.read_string(user_profile_base + USER_PROFILE_OFFSET.username)?, - pp: p.read_i32(user_profile_base + USER_PROFILE_OFFSET.pp)?, + pp, rankedscore: p.read_i64(user_profile_base + USER_PROFILE_OFFSET.rankedscore)?, level: p.read_f32(user_profile_base + USER_PROFILE_OFFSET.level)?, - playcount: p.read_i32(user_profile_base + USER_PROFILE_OFFSET.playcount)?, - rank: p.read_i32(user_profile_base + USER_PROFILE_OFFSET.rank)?, - playmode: p.read_i32(user_profile_base + USER_PROFILE_OFFSET.playmode)?, + playcount, + rank, + playmode, accuracy: p.read_f64(user_profile_base + USER_PROFILE_OFFSET.accuracy)?, country_code: p.read_i32(user_profile_base + USER_PROFILE_OFFSET.country_code)?, - bancho_status: p.read_i32(user_profile_base + USER_PROFILE_OFFSET.bancho_status)?, + bancho_status, }; Ok(user_profile) } diff --git a/src/reader/user/stable/offset.rs b/src/reader/user/stable/offset.rs index 7499756..d623e9c 100644 --- a/src/reader/user/stable/offset.rs +++ b/src/reader/user/stable/offset.rs @@ -15,15 +15,16 @@ pub struct UserProfileOffset { pub(crate) const USER_PROFILE_OFFSET: UserProfileOffset = UserProfileOffset { ptr: 0x7, - id: 0x70, - username: 0x30, - pp: 0x88, + accuracy: 0x4, rankedscore: 0xC, + username: 0x30, + id: 0x70, level: 0x74, - playcount: 0x7C, - rank: 0x84, + playcount: 0x7C, // start read playmode: 0x80, - accuracy: 0x4, + rank: 0x84, + pp: 0x88, + bancho_status: 0x8c, // end read country_code: 0x9c, - bancho_status: 0x8c, + };