use std::fs::File; use std::io::prelude::*; use std::path::PathBuf; use anyhow::{anyhow, Result}; use clap::Clap; use log::{debug, error, info}; use brd::converter; use brd::ddr::ssq::SSQ; use brd::osu; use brd::xact3::xwb::WaveBank; #[derive(Clap)] #[clap()] struct Opts { #[clap(subcommand)] subcmd: SubCommand, } #[derive(Clap)] enum SubCommand { #[clap( name = "unxwb", about = "Extracts sounds from XWB wave banks", display_order = 1 )] UnXWB(UnXWB), #[clap( about = "Converts DDR step charts to osu!mania beatmaps", display_order = 1 )] DDR2osu(DDR2osu), } #[derive(Clap)] struct UnXWB { #[clap(short, long, about = "List available sounds and exit")] list_entries: bool, #[clap(short = "e", long, about = "Only extract this entry")] single_entry: Option, #[clap(name = "file")] file: PathBuf, } #[derive(Clap)] struct DDR2osu { #[clap( short = "s", long = "ssq", name = "file.ssq", about = "DDR step chart file", display_order = 1 )] ssq_file: PathBuf, #[clap( short = "x", long = "xwb", name = "file.xwb", about = "XAC3 wave bank file", display_order = 2 )] xwb_file: PathBuf, #[clap( short = "o", long = "out", name = "file.osz", about = "osu! beatmap archive", display_order = 3 )] out_file: PathBuf, #[clap( short = "n", name = "sound name", about = "Sound in wave bank, otherwise inferred from SSQ filename", display_order = 4 )] sound_name: Option, #[clap(flatten)] convert: converter::ddr2osu::Config, } fn error(message: String) -> Result<()> { error!("{}", message); Err(anyhow!(message)) } fn read_file(name: &PathBuf) -> Result> { let mut file = File::open(name)?; let mut data = vec![]; file.read_to_end(&mut data)?; Ok(data) } fn get_basename(path: &PathBuf) -> Option<&str> { match path.file_stem() { Some(stem) => stem.to_str(), None => None, } } fn main() -> Result<()> { pretty_env_logger::init(); let opts: Opts = Opts::parse(); match opts.subcmd { SubCommand::UnXWB(opts) => { let xwb_data = read_file(&opts.file)?; let wave_bank = WaveBank::parse(&xwb_data)?; info!("Opened wave bank “{}” from {:?}", wave_bank.name, opts.file); match opts.single_entry { Some(name) => { let sound = match wave_bank.sounds.get(&name) { Some(sound) => sound, None => return error(format!("Entry {} not found in wave bank", name)), }; let out_file = format!("{}.wav", name); let mut wav_file = File::create(out_file)?; wav_file.write_all(&sound.to_wav()?)?; } None => { for (name, sound) in wave_bank.sounds { if opts.list_entries { println!("{}", name); continue; } info!("Extracting {}", name); let out_file = format!("{}.wav", name); let mut wav_file = File::create(out_file)?; wav_file.write_all(&sound.to_wav()?)?; } } } } SubCommand::DDR2osu(opts) => { let sound_name = &opts .sound_name .unwrap_or(match get_basename(&opts.ssq_file) { Some(basename) => basename.to_string(), None => return error( "Could not extract chart id from file name. Please specify it manually." .to_string(), ), }); debug!( "Converting {:?} and sound {} from {:?} to {:?}", opts.ssq_file, sound_name, opts.xwb_file, opts.out_file ); let ssq_data = read_file(&opts.ssq_file)?; let ssq = SSQ::parse(&ssq_data)?; let convert_config = opts.convert; let beatmaps = ssq.to_beatmaps(&convert_config)?; let xwb_data = read_file(&opts.xwb_file)?; let wave_bank = WaveBank::parse(&xwb_data)?; let audio_data = if wave_bank.sounds.contains_key(sound_name) { wave_bank.sounds.get(sound_name).unwrap().to_wav()? } else { return error(format!( "Could not find sound with chart name {} in wave bank", sound_name, )); }; let osz = osu::osz::Archive { beatmaps, assets: vec![("audio.wav", &audio_data)], }; osz.write(&opts.out_file)?; } } Ok(()) }