Implement support for musicdb
This commit is contained in:
parent
56d36f2732
commit
63d75fd6b6
42
Cargo.lock
generated
42
Cargo.lock
generated
|
@ -56,8 +56,11 @@ dependencies = [
|
|||
"num-derive",
|
||||
"num-traits",
|
||||
"pretty_env_logger",
|
||||
"quick-xml",
|
||||
"quickcheck",
|
||||
"quickcheck_macros",
|
||||
"serde",
|
||||
"tabwriter",
|
||||
"thiserror",
|
||||
"zip",
|
||||
]
|
||||
|
@ -317,6 +320,16 @@ version = "1.2.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cc440ee4802a86e357165021e3e255a9143724da31db1e2ea540214c96a0f82"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quickcheck"
|
||||
version = "0.9.2"
|
||||
|
@ -408,6 +421,26 @@ version = "0.6.18"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
|
@ -436,6 +469,15 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tabwriter"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36205cfc997faadcc4b0b87aaef3fbedafe20d38d4959a7ca6ff803564051111"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.0"
|
||||
|
|
|
@ -8,11 +8,14 @@ edition = "2018"
|
|||
anyhow = "1.0.31"
|
||||
byteorder = "1.3.4"
|
||||
clap = "3.0.0-beta.1"
|
||||
log = "0.4.8"
|
||||
konami-lz77 = { git = "https://github.com/sbruder/konami-lz77" }
|
||||
log = "0.4.8"
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
pretty_env_logger = "0.4"
|
||||
quick-xml = { version = "0.18", features = [ "serialize" ] }
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
tabwriter = "1.2.1"
|
||||
thiserror = "1.0.20"
|
||||
zip = { version = "0.5.5", default-features = false, features = ["deflate"] }
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# BRD
|
||||
|
||||
BRD is a tool for working with [*Dance Dance Revolution*][ddr] step charts and
|
||||
wave banks. For currently supported features, see the [Modes](#modes) section.
|
||||
BRD is a tool for working with [*Dance Dance Revolution*][ddr] related data.
|
||||
For currently supported features, see the [Modes](#modes) section.
|
||||
|
||||
## Installation
|
||||
|
||||
|
@ -54,6 +54,11 @@ Basic Usage:
|
|||
brd unarc file.arc
|
||||
brd unarc -l file.arc
|
||||
|
||||
### musicdb
|
||||
|
||||
This lists all entries from `musicdb.xml` or `startup.arc` files (only DDR A is
|
||||
supported).
|
||||
|
||||
#### Known Problems
|
||||
|
||||
* It only supports sounds in [ADPCM][ADPCM] format. If you want to extract
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
pub mod arc;
|
||||
pub mod musicdb;
|
||||
pub mod ssq;
|
||||
|
|
95
src/ddr/musicdb.rs
Normal file
95
src/ddr/musicdb.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use quick_xml::de::{from_str, DeError};
|
||||
use serde::de;
|
||||
use serde::Deserialize;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::ddr::arc;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("“data/gamedata/musicdb.xml” not found in archive")]
|
||||
NotInArchive,
|
||||
#[error(transparent)]
|
||||
DeError(#[from] DeError),
|
||||
#[error(transparent)]
|
||||
ArcError(#[from] arc::Error),
|
||||
#[error(transparent)]
|
||||
FromUtf8Error(#[from] std::string::FromUtf8Error),
|
||||
}
|
||||
|
||||
/// Type that implements [`serde::de::Deserialize`] for space separated lists in xml tag bodies.
|
||||
///
|
||||
/// [`serde::de::Deserialize`]: ../../../serde/de/trait.Deserialize.html
|
||||
#[derive(Debug)]
|
||||
pub struct XMLList<T>(Vec<T>);
|
||||
|
||||
impl<'de, T> serde::de::Deserialize<'de> for XMLList<T>
|
||||
where
|
||||
T: FromStr,
|
||||
T::Err: std::fmt::Display,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::de::Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
Ok(Self(
|
||||
s.split(' ')
|
||||
.map(|x| x.parse().map_err(de::Error::custom))
|
||||
.collect::<Result<Vec<T>, _>>()?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for XMLList<T> {
|
||||
type Target = Vec<T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// This currently only includes fields present in every entry.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Entry {
|
||||
pub mcode: u32,
|
||||
pub basename: String,
|
||||
pub title: String,
|
||||
pub artist: String,
|
||||
pub bpmmax: u16,
|
||||
pub series: u8,
|
||||
#[serde(rename = "diffLv")]
|
||||
pub diff_lv: XMLList<u8>,
|
||||
}
|
||||
|
||||
/// Holds entries from `musicdb.xml` and can be deserialized from it with [`parse`]
|
||||
///
|
||||
/// [`parse`]: fn.parse.html
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct MusicDB {
|
||||
pub music: Vec<Entry>,
|
||||
}
|
||||
|
||||
impl MusicDB {
|
||||
/// Parses `musicdb.xml` found in the `startup.arc` archive of DDR A. Currently does not work
|
||||
/// for older versions.
|
||||
pub fn parse(data: &str) -> Result<Self, DeError> {
|
||||
from_str(data)
|
||||
}
|
||||
|
||||
/// Convenience function that reads `musicdb.xml` from `startup.arc` and then parses it.
|
||||
pub fn parse_from_startup_arc(data: &[u8]) -> Result<Self, Error> {
|
||||
let arc = arc::ARC::parse(&data)?;
|
||||
|
||||
let musicdb_data = arc
|
||||
.files
|
||||
.get(&PathBuf::from("data/gamedata/musicdb.xml"))
|
||||
.ok_or(Error::NotInArchive)?;
|
||||
|
||||
Self::parse(&String::from_utf8(musicdb_data.to_vec())?).map_err(|err| err.into())
|
||||
}
|
||||
}
|
|
@ -8,5 +8,5 @@ pub mod converter;
|
|||
pub mod ddr;
|
||||
mod mini_parser;
|
||||
pub mod osu;
|
||||
mod utils;
|
||||
pub mod utils;
|
||||
pub mod xact3;
|
||||
|
|
76
src/main.rs
76
src/main.rs
|
@ -1,13 +1,17 @@
|
|||
use std::fs;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use clap::Clap;
|
||||
use log::{debug, info, warn};
|
||||
use tabwriter::TabWriter;
|
||||
|
||||
use brd::converter;
|
||||
use brd::ddr::{arc::ARC, ssq::SSQ};
|
||||
use brd::ddr::{arc::ARC, musicdb, ssq::SSQ};
|
||||
use brd::osu;
|
||||
use brd::utils;
|
||||
use brd::xact3::xwb::{Sound as XWBSound, WaveBank};
|
||||
|
||||
#[derive(Clap)]
|
||||
|
@ -31,6 +35,12 @@ enum SubCommand {
|
|||
display_order = 1
|
||||
)]
|
||||
UnARC(UnARC),
|
||||
#[clap(
|
||||
name = "musicdb",
|
||||
about = "Shows entries from musicdb (supports musicdb.xml and startup.arc from DDR A)",
|
||||
display_order = 1
|
||||
)]
|
||||
MusicDB(MusicDB),
|
||||
#[clap(
|
||||
about = "Converts DDR step charts to osu!mania beatmaps",
|
||||
display_order = 1
|
||||
|
@ -58,6 +68,12 @@ struct UnXWB {
|
|||
file: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
struct MusicDB {
|
||||
#[clap(name = "file")]
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
struct DDR2osu {
|
||||
#[clap(
|
||||
|
@ -173,6 +189,64 @@ fn main() -> Result<()> {
|
|||
}
|
||||
}
|
||||
}
|
||||
SubCommand::MusicDB(opts) => {
|
||||
let extension = opts
|
||||
.file
|
||||
.extension()
|
||||
.and_then(|ext| ext.to_str())
|
||||
.unwrap_or("");
|
||||
let musicdb = match extension {
|
||||
"arc" => {
|
||||
let arc_data = fs::read(&opts.file).with_context(|| {
|
||||
format!("failed to read musicdb ARC file {}", &opts.file.display())
|
||||
})?;
|
||||
|
||||
musicdb::MusicDB::parse_from_startup_arc(&arc_data)
|
||||
.context("failed to parse musicdb from ARC file")?
|
||||
}
|
||||
_ => {
|
||||
if extension != "xml" {
|
||||
warn!("Did not find known extension (arc, xml), trying to parse as XML");
|
||||
}
|
||||
|
||||
let musicdb_data = fs::read_to_string(&opts.file).with_context(|| {
|
||||
format!("failed to read musicdb XML file {}", &opts.file.display())
|
||||
})?;
|
||||
|
||||
musicdb::MusicDB::parse(&musicdb_data).context("failed to parse musicdb XML")?
|
||||
}
|
||||
};
|
||||
|
||||
let mut tw = TabWriter::new(io::stdout());
|
||||
|
||||
writeln!(
|
||||
tw,
|
||||
"Code\tBasename\tName\tArtist\tBPM\tSeries\tDifficulties (Single)\t(Double)"
|
||||
)?;
|
||||
|
||||
for song in musicdb.music {
|
||||
// Filter 0s
|
||||
let diff_lv: (Vec<&u8>, Vec<&u8>) = (
|
||||
song.diff_lv[..5].iter().filter(|x| **x != 0).collect(),
|
||||
song.diff_lv[5..].iter().filter(|x| **x != 0).collect(),
|
||||
);
|
||||
|
||||
writeln!(
|
||||
tw,
|
||||
"{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}",
|
||||
song.mcode,
|
||||
song.basename,
|
||||
song.title,
|
||||
song.artist,
|
||||
song.bpmmax,
|
||||
song.series,
|
||||
utils::join_display_values(diff_lv.0, ", "),
|
||||
utils::join_display_values(diff_lv.1, ", ")
|
||||
)?;
|
||||
}
|
||||
|
||||
tw.flush()?;
|
||||
}
|
||||
SubCommand::DDR2osu(opts) => {
|
||||
let sound_name =
|
||||
&opts
|
||||
|
|
Reference in a new issue