Implement support for musicdb

master
Simon Bruder 2020-06-28 20:18:27 +02:00
parent 56d36f2732
commit 63d75fd6b6
No known key found for this signature in database
GPG Key ID: 6F03E0000CC5B62F
7 changed files with 225 additions and 5 deletions

42
Cargo.lock generated
View File

@ -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"

View File

@ -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"] }

View File

@ -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

View File

@ -1,2 +1,3 @@
pub mod arc;
pub mod musicdb;
pub mod ssq;

95
src/ddr/musicdb.rs Normal file
View 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())
}
}

View File

@ -8,5 +8,5 @@ pub mod converter;
pub mod ddr;
mod mini_parser;
pub mod osu;
mod utils;
pub mod utils;
pub mod xact3;

View File

@ -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