From 6fd7361ca11b9313131c98f5a3ddc24e054b5227 Mon Sep 17 00:00:00 2001 From: mokurin000 <1348292515a@gmail.com> Date: Fri, 1 Aug 2025 02:03:47 +0800 Subject: [PATCH] feat: simple musicDB for title --- Cargo.lock | 1 + sdgb-api/Cargo.toml | 1 + sdgb-api/src/helper/mod.rs | 5 +- sdgb-api/src/helper/music_db/mod.rs | 32 ++++++++ .../title/model/get_user_rating_api/mod.rs | 5 ++ sdgb-cli/src/main.rs | 3 + utils/music_db_dump.py | 77 +++++++++++++++++++ 7 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 sdgb-api/src/helper/music_db/mod.rs create mode 100644 utils/music_db_dump.py diff --git a/Cargo.lock b/Cargo.lock index ea72f99..f7980f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1459,6 +1459,7 @@ dependencies = [ "hmac-sha256", "md5", "nyquest", + "rustc-hash", "serde", "serde_json", "snafu", diff --git a/sdgb-api/Cargo.toml b/sdgb-api/Cargo.toml index 6eeef08..f5b8dbf 100644 --- a/sdgb-api/Cargo.toml +++ b/sdgb-api/Cargo.toml @@ -43,3 +43,4 @@ bincode = { version = "2.0.1", optional = true } # magic macro crabtime = { git = "https://github.com/wdanilo/crabtime.git", rev = "2ed856f5" } +rustc-hash = "2.1.1" diff --git a/sdgb-api/src/helper/mod.rs b/sdgb-api/src/helper/mod.rs index c3414c4..6ff2a5a 100644 --- a/sdgb-api/src/helper/mod.rs +++ b/sdgb-api/src/helper/mod.rs @@ -10,6 +10,5 @@ pub fn level_name(level: u32) -> &'static str { } } -// TODO: MusicDB lazy load -// struct MusicDB; -// static MUSIC_DB: LazyLock = LazyLock::new(|| unimplemented!()); +mod music_db; +pub use music_db::{MUSIC_DB, MusicInfo,}; diff --git a/sdgb-api/src/helper/music_db/mod.rs b/sdgb-api/src/helper/music_db/mod.rs new file mode 100644 index 0000000..06ec456 --- /dev/null +++ b/sdgb-api/src/helper/music_db/mod.rs @@ -0,0 +1,32 @@ +use std::{fs::OpenOptions, sync::LazyLock}; + +use rustc_hash::FxHashMap; +use serde::{Deserialize, Serialize}; +use spdlog::{info, warn}; + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MusicInfo { + pub id: u32, + pub name: String, + pub version: i64, +} + +type MusicDB = FxHashMap; + +pub static MUSIC_DB: LazyLock> = LazyLock::new(|| { + info!("loading musicDB..."); + + let json = OpenOptions::new() + .read(true) + .create(false) + .open("musicDB.json") + .inspect_err(|e| warn!("failed to load musicDB: {e}")) + .ok()?; + + let db: Vec = serde_json::from_reader(json) + .inspect_err(|e| warn!("failed to load musicDB: {e}")) + .ok()?; + + Some(db.into_iter().map(|entry| (entry.id, entry)).collect()) +}); diff --git a/sdgb-api/src/title/model/get_user_rating_api/mod.rs b/sdgb-api/src/title/model/get_user_rating_api/mod.rs index 15aa33c..2ba7fa3 100644 --- a/sdgb-api/src/title/model/get_user_rating_api/mod.rs +++ b/sdgb-api/src/title/model/get_user_rating_api/mod.rs @@ -3,6 +3,7 @@ use std::fmt::Display; use serde::Deserialize; use serde::Serialize; +use crate::helper::MUSIC_DB; use crate::helper::level_name; #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -109,6 +110,10 @@ impl Display for GetUserRatingApiResp { impl Display for MusicRating { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("歌曲ID: \t{}\n", self.music_id))?; + if let Some(info) = MUSIC_DB.as_ref().map(|db| db.get(&self.music_id)).flatten() { + let title = &info.name; + f.write_fmt(format_args!("曲目标题: \t{title}\n"))?; + } f.write_fmt(format_args!( "谱面版本: \t{}\n", match (self.music_id / 10000) % 10 { diff --git a/sdgb-cli/src/main.rs b/sdgb-cli/src/main.rs index 3843d04..3cb8e22 100644 --- a/sdgb-cli/src/main.rs +++ b/sdgb-cli/src/main.rs @@ -7,6 +7,7 @@ use spdlog::{Level, LevelFilter::MoreSevereEqual}; use sdgb_api::{ all_net::QRCode, auth_lite::{SDGB, SDHJ, delivery_raw}, + helper::MUSIC_DB, title::{ MaiVersionExt, Sdgb1_40, Sdgb1_50, methods::APIMethod, @@ -52,6 +53,8 @@ async fn main() -> Result<(), Box> { } })?; + let _ = &*MUSIC_DB; + let Cli { command, machine_readable, diff --git a/utils/music_db_dump.py b/utils/music_db_dump.py new file mode 100644 index 0000000..fa78616 --- /dev/null +++ b/utils/music_db_dump.py @@ -0,0 +1,77 @@ +# forked from maimaiDX-Api +import json +import xml.dom.minidom as minidom +from pathlib import Path + +ONLY_REMOVED = True + + +def makeMusicDBJson(): + """ + 从 HDD 的文件来生成 music_db.json + 推荐的是如果要国服用 那就用国际服的文件来生成 + 免得国服每次更新还要重新生成太麻烦 + """ + # 记得改 + A000_DIR = Path( + "C:/MaimaiDX/SDEZ-1.56-B/Standard/Package/Sinmai_Data/StreamingAssets/A000" + ) + OPTION_DIR = Path("C:/MaimaiDX/SDGA-1.50-G/NoMovieData/StreamingAssets") + + music_db: list[dict[str, str | int]] = [] + DEST_PATH = Path("./musicDB.json") + + dup_count = 0 + music_ids = set() + + music_folders = [f for f in (A000_DIR / "music").iterdir() if f.is_dir()] + for option_dir in OPTION_DIR.iterdir(): + # only removed ones + if ONLY_REMOVED and option_dir.name != "A100": + continue + + if (option_dir / "music").exists(): + music_folders.extend( + [f for f in (option_dir / "music").iterdir() if f.is_dir()] + ) + + for folder in music_folders: + xml_path = folder / "Music.xml" + if xml_path.exists(): + xml = minidom.parse(xml_path.as_posix()) + data = xml.getElementsByTagName("MusicData")[0] + music_id = int( + data.getElementsByTagName("name")[0] + .getElementsByTagName("id")[0] + .firstChild.data + ) + music_name = ( + data.getElementsByTagName("name")[0] + .getElementsByTagName("str")[0] + .firstChild.data + ) + music_version = ( + data.getElementsByTagName("AddVersion")[0] + .getElementsByTagName("id")[0] + .firstChild.data + ) + + if music_id not in music_ids: + music_ids.add(music_id) + music_db.append( + {"id": music_id, "name": music_name, "version": int(music_version)} + ) + else: + # e.g. SDEZ-only song + dup_count += 1 + + print(f"Found {len(music_db)} music data") + print(f"Found {dup_count} duplications") + + with open(DEST_PATH, "w", encoding="utf-8") as f: + json.dump(music_db, f, ensure_ascii=False) + + +if __name__ == "__main__": + makeMusicDBJson() + print("Done.")