rosu_memory_lib/reader/beatmap/stable/
file.rs1use std::path::PathBuf;
2
3use crate::common::GameMode;
4use crate::reader::beatmap::common::{
5 BeatmapInfo, BeatmapLocation, BeatmapMetadata, BeatmapStarRating, BeatmapStats, BeatmapStatus,
6 BeatmapTechnicalInfo,
7};
8use crate::reader::beatmap::stable::memory::{audio, filename, folder};
9use crate::reader::beatmap::stable::{beatmap_addr, offset::BEATMAP_OFFSET};
10use crate::reader::common::stable::memory::path_folder;
11use crate::reader::structs::State;
12use crate::Error;
13use rosu_map::section::hit_objects::HitObjectKind;
14use rosu_map::Beatmap as RmBeatmap;
15use rosu_mem::process::{Process, ProcessTraits};
16
17pub fn path(p: &Process, state: &mut State) -> Result<PathBuf, Error> {
18 let folder = folder(p, state)?;
19 let filename = filename(p, state)?;
20 let songs_path = path_folder(p, state)?;
21
22 Ok(songs_path.join(folder).join(filename))
23}
24
25pub fn audio_path(p: &Process, state: &mut State) -> Result<PathBuf, Error> {
26 let folder = folder(p, state)?;
27 let audio = audio(p, state)?;
28 let songs_path = path_folder(p, state)?;
29
30 Ok(songs_path.join(folder).join(audio))
31}
32
33macro_rules! generate_beatmap_field_getter {
35 (
36 $( $fn_name:ident : $ret_ty:ty = $field:ident ; )*
37 ) => {
38 $(
39 pub fn $fn_name(p: &Process, state: &mut State) -> Result<$ret_ty, Error> {
40 let path = path(p, state)?;
41 let b = RmBeatmap::from_path(path)?;
42 Ok(b.$field)
43 }
44 )*
45 };
46}
47macro_rules! generate_beatmap_custom_getter_safe {
49 (
50 $( $fn_name:ident : $ret_ty:ty = |$b:ident| $body:block )*
51 ) => {
52 $(
53 pub fn $fn_name(p: &Process, state: &mut State) -> Result<$ret_ty, Error> {
54 let path = path(p, state)?;
55 let $b = RmBeatmap::from_path(path)?;
56 $body
57 }
58 )*
59 };
60}
61
62generate_beatmap_field_getter! {
63 beatmap_id: i32 = beatmap_id;
64 beatmap_set_id: i32 = beatmap_set_id;
65 author: String = artist;
66 creator: String = creator;
67 title_romanized: String = title;
68 title: String = title_unicode;
69 difficulty: String = version;
70 tags: String = tags;
71 od: f32 = overall_difficulty;
72 ar: f32 = approach_rate;
73 cs: f32 = circle_size;
74 hp: f32 = hp_drain_rate;
75}
76
77generate_beatmap_custom_getter_safe! {
78 slider_count: i32 = |b| {
79 Ok(b.hit_objects.iter()
80 .filter(|h| matches!(h.kind, HitObjectKind::Slider(_)))
81 .count() as i32)
82 }
83 object_count: u32 = |b| {
84 Ok(b.hit_objects.len() as u32)
85 }
86 length: i32 = |b| {
87 let last = b.hit_objects.last().ok_or_else(|| Error::Other("Empty hitobject list".into()))?;
88
89 let duration = match &last.kind {
90 HitObjectKind::Hold(hold_data) => last.start_time + hold_data.duration,
91 _ => last.start_time,
92 };
93
94 Ok(duration as i32)
95 }
96 drain_time: i32 = |b| {
97 let first = b.hit_objects.first().ok_or_else(|| Error::Other("Empty hitobject list".into()))?;
98 let last = b.hit_objects.last().ok_or_else(|| Error::Other("Empty hitobject list".into()))?;
99
100 let duration = match &last.kind {
101 HitObjectKind::Hold(hold_data) => last.start_time + hold_data.duration,
102 _ => last.start_time,
103 };
104
105 Ok((duration - first.start_time) as i32)
106 }
107 mode: GameMode = |b| {
108 Ok(GameMode::from(b.mode as u32))
109 }
110
111}
112
113pub fn status(p: &Process, state: &mut State) -> Result<BeatmapStatus, Error> {
115 crate::reader::beatmap::stable::memory::status(p, state)
117}
118
119pub fn star_rating(p: &Process, state: &mut State) -> Result<BeatmapStarRating, Error> {
120 let folder = folder(p, state)?;
121 let filename = filename(p, state)?;
122 let songs_path = path_folder(p, state)?;
123 let path = songs_path.join(folder).join(filename);
124 let b = rosu_pp::Beatmap::from_path(path)?;
125 let diff_attrs = rosu_pp::Difficulty::new().calculate(&b);
126 let diff_dt = rosu_pp::Difficulty::new().mods(64).calculate(&b);
127 let diff_ht = rosu_pp::Difficulty::new().mods(256).calculate(&b);
128 let no_mod = diff_attrs.stars();
129 let dt = diff_dt.stars();
130 let ht = diff_ht.stars();
131 Ok(BeatmapStarRating { no_mod, dt, ht })
132}
133
134pub fn md5(p: &Process, state: &mut State) -> Result<String, Error> {
135 crate::reader::beatmap::stable::memory::md5(p, state)
137}
138pub fn stats(p: &Process, state: &mut State) -> Result<BeatmapStats, Error> {
139 let beatmap_addr = path(p, state)?;
140 let b = RmBeatmap::from_path(beatmap_addr)?;
141 Ok(BeatmapStats {
142 ar: b.approach_rate,
143 od: b.overall_difficulty,
144 cs: b.circle_size,
145 hp: b.hp_drain_rate,
146 length: length(p, state)?,
147 star_rating: star_rating(p, state)?,
148 object_count: b.hit_objects.len() as i32,
149 slider_count: b
150 .hit_objects
151 .iter()
152 .filter(|h| matches!(h.kind, HitObjectKind::Slider(_)))
153 .count() as i32,
154 })
155}
156
157pub fn info(p: &Process, state: &mut State) -> Result<BeatmapInfo, Error> {
158 let beatmap_file = path(p, state)?;
159 let beatmap_addr = beatmap_addr(p, state)?;
160 let b = RmBeatmap::from_path(beatmap_file)?;
161 Ok(BeatmapInfo {
163 technical: BeatmapTechnicalInfo {
164 md5: crate::reader::beatmap::stable::file::md5(p, state)?,
165 id: b.beatmap_id,
166 set_id: b.beatmap_set_id,
167 mode: GameMode::Osu,
168 ranked_status: status(p, state)?,
169 },
170 metadata: BeatmapMetadata {
171 author: b.artist,
172 creator: b.creator,
173 title_romanized: b.title,
174 title_original: b.title_unicode,
175 difficulty: b.version,
176 tags: b.tags,
177 },
178 stats: BeatmapStats {
179 ar: b.approach_rate,
180 od: b.overall_difficulty,
181 cs: b.circle_size,
182 hp: b.hp_drain_rate,
183 length: length(p, state)?,
184 star_rating: star_rating(p, state)?,
185 object_count: b.hit_objects.len() as i32,
186 slider_count: b
187 .hit_objects
188 .iter()
189 .filter(|h| matches!(h.kind, HitObjectKind::Slider(_)))
190 .count() as i32,
191 },
192 location: BeatmapLocation {
193 folder: p.read_string(beatmap_addr + BEATMAP_OFFSET.location.folder)?,
194 filename: p.read_string(beatmap_addr + BEATMAP_OFFSET.location.filename)?,
195 audio: p.read_string(beatmap_addr + BEATMAP_OFFSET.location.audio)?,
196 cover: p.read_string(beatmap_addr + BEATMAP_OFFSET.location.cover)?,
197 },
198 })
199}