use rosu_mem::process::Process;
use rosu_memory_lib::init_loop;
use rosu_memory_lib::reader::beatmap::stable::file::path;
use rosu_memory_lib::reader::common::stable::memory::menu_game_mode;
use rosu_memory_lib::reader::structs::State;
use rosu_memory_lib::Error;
use rosu_mods::GameModsLegacy;
use rosu_pp::Beatmap;
use rosu_pp::{Difficulty, Performance};
use std::path::{Path, PathBuf};
use std::time::Duration;
struct CalculatorState {
current_pp: f64,
current_mods: i32,
current_beatmap: Beatmap,
current_beatmap_path: PathBuf,
}
impl CalculatorState {
fn new() -> Self {
Self {
current_pp: 0.0,
current_mods: 0,
current_beatmap: Beatmap::default(),
current_beatmap_path: PathBuf::new(),
}
}
fn update_mods(&mut self, new_mods: i32) -> bool {
if new_mods != self.current_mods {
self.current_mods = new_mods;
let mods_readable = GameModsLegacy::from_bits(self.current_mods as u32).to_string();
println!("Mods: {mods_readable}");
true
} else {
false
}
}
fn update_beatmap<P: AsRef<Path>>(&mut self, new_path: P) -> Result<bool, Error> {
if new_path.as_ref() != self.current_beatmap_path {
println!("Loading new beatmap: {}", new_path.as_ref().display());
let beatmap = Beatmap::from_path(&new_path)?;
if let Err(suspicion) = beatmap.check_suspicion() {
eprintln!("Warning: Suspicious beatmap detected: {suspicion:?}");
return Ok(false);
}
self.current_beatmap = beatmap;
self.current_beatmap_path = new_path.as_ref().to_path_buf();
println!("Beatmap loaded successfully!");
Ok(true)
} else {
Ok(false)
}
}
fn update_pp(&mut self) {
let diff_attrs = Difficulty::new()
.mods(self.current_mods as u32)
.calculate(&self.current_beatmap);
let new_pp = Performance::new(diff_attrs).calculate().pp();
if (new_pp - self.current_pp).abs() > f64::EPSILON {
self.current_pp = new_pp;
println!("PP for current beatmap: {:.2}", self.current_pp);
}
}
}
fn process_game_state(
process: &Process,
state: &mut State,
calc_state: &mut CalculatorState,
) -> Result<(), Error> {
let mut mods_changed = false;
match path(process, state) {
Ok(beatmap_path) => {
println!("Menu game mode: {}", menu_game_mode(process, state)?);
if let Ok(new_mods) = menu_game_mode(process, state) {
mods_changed = calc_state.update_mods(new_mods as i32);
}
let beatmap_updated = calc_state.update_beatmap(beatmap_path)?;
if beatmap_updated || mods_changed {
calc_state.update_pp();
}
}
Err(e) => {
eprintln!("Failed to read beatmap path: {e}");
}
}
Ok(())
}
fn main() -> Result<(), Error> {
let (mut state, process) = init_loop(500)?;
println!("Successfully connected to osu! process!");
let mut calc_state = CalculatorState::new();
loop {
if let Err(e) = process_game_state(&process, &mut state, &mut calc_state) {
eprintln!("Error during processing: {e}");
}
std::thread::sleep(Duration::from_millis(1000));
}
}