osu_db/
score.rs

1//! Parsing for the `scores.db` osu file, which contains partial replay data locally.
2
3use crate::{
4    prelude::*,
5    replay::{replay, Replay},
6};
7
8/// A score database, usually coming from a `scores.db` file.
9#[cfg_attr(feature = "ser-de", derive(Serialize, Deserialize))]
10#[derive(Debug, Clone, PartialEq)]
11pub struct ScoreList {
12    pub version: u32,
13    pub beatmaps: Vec<BeatmapScores>,
14}
15impl ScoreList {
16    /// Read a score database from its raw bytes.
17    pub fn from_bytes(bytes: &[u8]) -> Result<ScoreList, Error> {
18        scores(bytes).map(|(_rem, scores)| scores)
19    }
20
21    /// Read a score database from a `scores.db` file.
22    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<ScoreList, Error> {
23        Self::from_bytes(&fs::read(path)?)
24    }
25
26    /// Write the score database to an arbitrary writer.
27    pub fn to_writer<W: Write>(&self, mut out: W) -> io::Result<()> {
28        self.wr(&mut out)
29    }
30
31    /// Similar to `to_writer` but writes the scores to a file.
32    pub fn save<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
33        self.to_writer(BufWriter::new(File::create(path)?))
34    }
35}
36
37/// The scores for a single beatmap.
38#[cfg_attr(feature = "ser-de", derive(Serialize, Deserialize))]
39#[derive(Debug, Clone, PartialEq)]
40pub struct BeatmapScores {
41    /// The beatmap hash.
42    /// Should be redundant with the individual replay hashes.
43    pub hash: Option<String>,
44    /// All the scored replays for this beatmap.
45    pub scores: Vec<Replay>,
46}
47
48fn scores(bytes: &[u8]) -> Result<(&[u8], ScoreList), Error> {
49    let (rem, version) = int(bytes)?;
50    let (mut rem, len) = int(rem)?;
51    let mut beatmaps = Vec::with_capacity(len as usize);
52
53    for _ in 0..len {
54        let (rem_, beatmap_scores) = beatmap_scores(rem)?;
55        beatmaps.push(beatmap_scores);
56        rem = rem_;
57    }
58
59    let list = ScoreList { version, beatmaps };
60
61    Ok((rem, list))
62}
63
64fn beatmap_scores(bytes: &[u8]) -> Result<(&[u8], BeatmapScores), Error> {
65    let (rem, hash) = opt_string(bytes)?;
66    let (mut rem, len) = int(rem)?;
67    let mut scores = Vec::with_capacity(len as usize);
68
69    for _ in 0..len {
70        let (rem_, replay) = replay(rem, false)?;
71        rem = rem_;
72        scores.push(replay);
73    }
74
75    let scores = BeatmapScores { hash, scores };
76
77    Ok((rem, scores))
78}
79
80writer!(ScoreList [this,out] {
81    this.version.wr(out)?;
82    PrefixedList(&this.beatmaps).wr(out)?;
83});
84writer!(BeatmapScores [this,out] {
85    this.hash.wr(out)?;
86    PrefixedList(&this.scores).wr_args(out,None)?;
87});