rosu_memory_lib/reader/helpers.rs
1use crate::reader::common::GameMode;
2use crate::reader::structs::Hit;
3use crate::reader::structs::State;
4use crate::Error;
5use rosu_mem::process::{Process, ProcessTraits};
6
7
8/// Generates standardized memory reading functions.
9///
10/// This macro creates functions that read specific data types from process memory
11/// using a base address and offset common usage for base types to not do getbaseaddr everytime.
12///
13/// # Syntax
14///
15/// ```rust
16/// generate_reader_fn! {
17/// function_name, return_type, read_method
18/// }
19/// ```
20///
21/// # Arguments
22///
23/// * `function_name` - Name of the generated function most likely read_<type>
24/// * `return_type` - Rust type to return (e.g., `i32`, `String`, `f64`)
25/// * `read_method` - Method name on Process trait (e.g., `read_i32`, `read_string`) most of the time from rosu_mem
26///
27/// # Examples
28///
29/// ```rust
30/// generate_reader_fn! {
31/// read_score, i32, read_i32
32/// }
33///
34/// ```
35macro_rules! generate_reader_fn {
36 (
37 $name:ident, $ret_ty:ty, $read_fn:ident
38 ) => {
39 pub(crate) fn $name(
40 p: &Process,
41 state: &mut State,
42 offset: i32,
43 get_base_addr: fn(&Process, &mut State) -> Result<i32, Error>,
44 ) -> Result<$ret_ty, Error> {
45 let base_addr = get_base_addr(p, state)?;
46 Ok(p.$read_fn(base_addr + offset)?)
47 }
48 };
49}
50
51/// Generates offset-based getter functions for memory access.
52///
53/// This macro creates functions that read data from specific memory offsets
54/// using a base address getter function.
55///
56/// # Syntax
57///
58/// ```rust
59/// generate_offset_getter! {
60/// function_name: return_type = read_method(offset, base_getter);
61/// another_function: another_type = another_read_method(another_offset, another_base);
62/// }
63/// ```
64///
65/// # Arguments
66///
67/// Each getter definition consists of:
68/// * `function_name` - Name of the generated function
69/// * `return_type` - Rust type to return
70/// * `read_method` - Memory reading method to use
71/// * `offset` - Memory offset from base address
72/// * `base_getter` - Function that returns the base address (most of the time from generate_reader_fn)
73///
74/// # Examples
75///
76/// ```rust
77/// generate_offset_getter! {
78/// score: i32 = read_i32(0x10, score_base),
79/// combo: i16 = read_i16(0x14, score_base),
80/// username: String = read_string(0x20, score_base),
81/// hp: f64 = read_f64(0x30, hp_base),
82/// }
83///
84/// // Generates functions like:
85/// // pub fn score(p: &Process, state: &mut State) -> Result<i32, Error> {
86/// // Ok(<i32>::from(read_i32(p, state, 0x10, score_base)?))
87/// // }
88/// ```
89///
90/// # Generated Functions
91///
92/// Each definition generates a function with signature:
93/// ```rust
94/// pub fn function_name(p: &Process, state: &mut State) -> Result<return_type, Error>
95/// ```
96///
97/// # Memory Safety
98///
99/// The generated functions assume the offsets are valid and the base address
100/// getter returns a valid memory address.
101#[macro_export]
102macro_rules! generate_offset_getter {
103 (
104 $( $fn_name:ident : $ret_ty:ty = $read_fn:ident ( $offset:expr , $get_base:ident ); )*
105 ) => {
106 $(
107 pub fn $fn_name(p: &Process, state: &mut State) -> Result<$ret_ty, Error> {
108 Ok(<$ret_ty>::from($read_fn(p, state, $offset, $get_base)?))
109 }
110 )*
111 };
112}
113
114/// Generates OSU memory accessor methods with automatic client dispatching.
115///
116/// This macro eliminates boilerplate code by automatically generating
117/// client-specific accessor methods that handle different OSU client types
118/// (Stable, Lazer) with consistent error handling.
119///
120/// # Syntax
121///
122/// ```rust
123/// impl_osu_accessor! {
124/// fn method_name() -> return_type => implementation_path,
125/// fn another_method() -> another_type => another_implementation,
126/// }
127/// ```
128///
129/// # Arguments
130///
131/// * `method_name` - Name of the generated method (e.g., `game_state`, `score`)
132/// * `return_type` - Rust type to return (e.g., `GameState`, `i32`, `String`)
133/// * `implementation_path` - Path to the actual implementation (e.g., `stable::memory::game_state`)
134///
135/// # Examples
136///
137/// ```rust
138/// impl<'a> CommonReader<'a> {
139/// impl_osu_accessor! {
140/// fn game_state() -> GameState => stable::memory::game_state,
141/// fn menu_game_mode() -> GameMode => stable::memory::menu_game_mode,
142/// fn path_folder() -> PathBuf => stable::memory::path_folder,
143/// }
144/// }
145/// ```
146///
147/// # Generated Methods
148///
149/// For each definition, generates a method like:
150/// ```rust
151/// pub fn method_name(&mut self) -> Result<return_type, Error> {
152/// match self.osu_type {
153/// OsuClientKind::Stable => implementation_path(self.process, self.state),
154/// _ => Err(Error::Unsupported("Unsupported osu type for now".to_string())),
155/// }
156/// }
157/// ```
158///
159/// # Benefits
160///
161/// * **Reduces code duplication** - ~1000+ lines eliminated across the project
162/// * **Consistent API** - All accessors follow the same pattern
163/// * **Type safety** - Compile-time guarantees for return types
164/// * **Future-proof** - Easy to add new client types
165/// * **Maintainable** - Changes only need to be made in one place
166///
167/// # Errors
168///
169/// Generated methods return `Error::Unsupported` for client types that don't
170/// have implementations yet (currently only Stable is supported).
171///
172/// # Performance
173///
174/// No runtime overhead - all dispatching is done at compile time.
175#[macro_export]
176macro_rules! impl_osu_accessor {
177 ($(fn $name:ident() -> $ret:ty => $call:path),* $(,)?) => {
178 $(
179 pub fn $name(&mut self) -> Result<$ret, Error> {
180 match self.osu_type {
181 OsuClientKind::Stable => $call(self.process, self.state),
182 _ => Err(Error::Unsupported(
183 "Unsupported osu type for now".to_string(),
184 )),
185 }
186 }
187 )*
188 };
189}
190
191// TODO : idk where to put this
192#[inline]
193pub fn calculate_accuracy(gamemode: &GameMode, hit: &Hit) -> Result<f64, Error> {
194 let acc = match gamemode {
195 GameMode::Osu => {
196 let total = (hit._300 + hit._100 + hit._50 + hit._miss) as f64;
197 if total == 0.0 {
198 return Ok(0.0);
199 }
200 let score = hit._300 as f64 * 6.0 + hit._100 as f64 * 2.0 + hit._50 as f64;
201 (score / (total * 6.0)) * 100.0
202 }
203 GameMode::Taiko => {
204 let total = (hit._300 + hit._100 + hit._50 + hit._miss) as f64;
205 if total == 0.0 {
206 return Ok(0.0);
207 }
208 let score = hit._300 as f64 * 2.0 + hit._100 as f64;
209 (score / (total * 2.0)) * 100.0
210 }
211 GameMode::Catch => {
212 let caught = (hit._300 + hit._100 + hit._50) as f64;
213 let total = (hit._300 + hit._100 + hit._50 + hit._katu + hit._miss) as f64;
214 if total == 0.0 {
215 return Ok(0.0);
216 }
217 (caught / total) * 100.0
218 }
219 GameMode::Mania => {
220 let total = (hit._geki + hit._300 + hit._katu + hit._100 + hit._50 + hit._miss) as f64;
221 if total == 0.0 {
222 return Ok(0.0);
223 }
224 let score = (hit._geki + hit._300) as f64 * 6.0
225 + hit._katu as f64 * 4.0
226 + hit._100 as f64 * 2.0
227 + hit._50 as f64;
228 (score / (total * 6.0)) * 100.0
229 }
230 _ => return Ok(0.0),
231 };
232
233 Ok(acc)
234}
235
236
237
238// Macro usage
239generate_reader_fn!(read_string, String, read_string);
240generate_reader_fn!(read_i16, i16, read_i16);
241generate_reader_fn!(read_i32, i32, read_i32);
242generate_reader_fn!(read_u32, u32, read_u32);
243generate_reader_fn!(read_i64, i64, read_i64);
244generate_reader_fn!(read_u64, u64, read_u64);
245generate_reader_fn!(read_f32, f32, read_f32);
246generate_reader_fn!(read_f64, f64, read_f64);