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