#![allow(unused_parens)]
use binrw::io::{Read, Seek, Write};
use binrw::{binrw, BinRead, BinResult, BinWrite, Endian, NullWideString};
use modular_bitfield::prelude::*;
use std::ffi::OsStr;
use super::xor::XorStream;
use crate::error::{Error, Result};
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(big)]
pub enum AnlzTag {
#[brw(magic = b"PMAI")]
File,
#[brw(magic = b"PQTZ")]
BeatGrid,
#[brw(magic = b"PQT2")]
ExtendedBeatGrid,
#[brw(magic = b"PCOB")]
CueList,
#[brw(magic = b"PCO2")]
ExtendedCueList,
#[brw(magic = b"PCP2")]
ExtendedCue,
#[brw(magic = b"PCPT")]
Cue,
#[brw(magic = b"PPTH")]
Path,
#[brw(magic = b"PVBR")]
VBR,
#[brw(magic = b"PWAV")]
WaveformPreview,
#[brw(magic = b"PWV2")]
TinyWaveformPreview,
#[brw(magic = b"PWV3")]
WaveformDetail,
#[brw(magic = b"PWV4")]
WaveformColorPreview,
#[brw(magic = b"PWV5")]
WaveformColorDetail,
#[brw(magic = b"PWV6")]
Waveform3BandPreview,
#[brw(magic = b"PWV7")]
Waveform3BandDetail,
#[brw(magic = b"PSSI")]
SongStructure,
Unknown([u8; 4]),
}
impl AnlzTag {
pub fn to_string(&self) -> String {
match self {
AnlzTag::File => "File".to_string(),
AnlzTag::BeatGrid => "BeatGrid".to_string(),
AnlzTag::ExtendedBeatGrid => "ExtendedBeatGrid".to_string(),
AnlzTag::CueList => "CueList".to_string(),
AnlzTag::ExtendedCueList => "ExtendedCueList".to_string(),
AnlzTag::ExtendedCue => "ExtendedCue".to_string(),
AnlzTag::Cue => "Cue".to_string(),
AnlzTag::Path => "Path".to_string(),
AnlzTag::VBR => "VBR".to_string(),
AnlzTag::WaveformPreview => "WaveformPreview".to_string(),
AnlzTag::TinyWaveformPreview => "TinyWaveformPreview".to_string(),
AnlzTag::WaveformDetail => "WaveformDetail".to_string(),
AnlzTag::WaveformColorPreview => "WaveformColorPreview".to_string(),
AnlzTag::WaveformColorDetail => "WaveformColorDetail".to_string(),
AnlzTag::Waveform3BandPreview => "Waveform3BandPreview".to_string(),
AnlzTag::Waveform3BandDetail => "Waveform3BandDetail".to_string(),
AnlzTag::SongStructure => "SongStructure".to_string(),
_ => format!("Unknown({:?})", self),
}
}
}
impl From<String> for AnlzTag {
fn from(tag: String) -> Self {
match tag.as_str() {
"File" => AnlzTag::File,
"BeatGrid" => AnlzTag::BeatGrid,
"ExtendedBeatGrid" => AnlzTag::ExtendedBeatGrid,
"CueList" => AnlzTag::CueList,
"ExtendedCueList" => AnlzTag::ExtendedCueList,
"ExtendedCue" => AnlzTag::ExtendedCue,
"Cue" => AnlzTag::Cue,
"Path" => AnlzTag::Path,
"VBR" => AnlzTag::VBR,
"WaveformPreview" => AnlzTag::WaveformPreview,
"TinyWaveformPreview" => AnlzTag::TinyWaveformPreview,
"WaveformDetail" => AnlzTag::WaveformDetail,
"WaveformColorPreview" => AnlzTag::WaveformColorPreview,
"WaveformColorDetail" => AnlzTag::WaveformColorDetail,
"Waveform3BandPreview" => AnlzTag::Waveform3BandPreview,
"Waveform3BandDetail" => AnlzTag::Waveform3BandDetail,
_ => panic!("Unknown tag type: {}", tag),
}
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(big)]
pub struct Header {
pub tag: AnlzTag,
pub size: u32,
pub total_size: u32,
}
impl Header {
fn remaining_size(&self) -> u32 {
self.size - 12
}
fn content_size(&self) -> u32 {
self.total_size - self.size
}
}
trait SizedSection {
fn size(&self) -> u32;
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(big)]
pub struct Beat {
pub beat_number: u16,
pub tempo: u16,
pub time: u32,
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct BeatGrid {
#[br(temp)]
#[br(assert(header_size == 24))]
#[bw(calc = 24)]
header_size: u32,
total_size: u32,
#[br(temp)]
#[br(assert(u1 == 0))]
#[bw(calc = 0)]
u1: u32,
#[br(temp)]
#[br(assert(u2 == 0x80000))]
#[bw(calc = 0x80000)]
u2: u32,
#[br(temp)]
#[bw(calc = beats.len() as u32)]
n_beats: u32,
#[br(count = n_beats)]
pub beats: Vec<Beat>,
}
impl SizedSection for BeatGrid {
fn size(&self) -> u32 {
self.total_size
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(big)]
pub struct ExtBeat {
pub beat_number: u8,
unknown: u8,
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ExtendedBeatGrid {
#[br(temp)]
#[br(assert(header_size == 56))]
#[bw(calc = 56)]
header_size: u32,
total_size: u32,
#[br(temp)]
#[br(assert(pad1 == 0))]
#[bw(calc = 0)]
pad1: u32,
#[br(temp)]
#[br(assert(u1 == 0x01000002))]
#[bw(calc = 0x01000002)]
u1: u32,
#[br(temp)]
#[br(assert(pad2 == 0))]
#[bw(calc = 0)]
pad2: u32,
#[br(count = 2)]
pub bpm: Vec<Beat>,
#[br(temp)]
#[bw(calc = beats.len() as u32)]
n_beats: u32,
u2: u32,
#[br(temp)]
#[br(assert(u3 == 0))]
#[bw(calc = 0)]
u3: u32,
#[br(temp)]
#[br(assert(u4 == 0))]
#[bw(calc = 0)]
u4: u32,
#[br(count = n_beats)]
pub beats: Vec<ExtBeat>,
}
impl SizedSection for ExtendedBeatGrid {
fn size(&self) -> u32 {
self.total_size
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(repr = u8)]
pub enum CueType {
Point = 1,
Loop = 2,
}
impl Into<u16> for CueType {
fn into(self) -> u16 {
match self {
CueType::Point => 1,
CueType::Loop => 2,
}
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(big, repr = u32)]
pub enum CueListType {
MemoryCues = 0,
HotCues = 1,
}
impl Into<u16> for CueListType {
fn into(self) -> u16 {
match self {
CueListType::MemoryCues => 0,
CueListType::HotCues => 1,
}
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(repr = u32)]
pub enum CueStatus {
Disabled = 0,
Active = 4,
}
impl Into<u16> for CueStatus {
fn into(self) -> u16 {
match self {
CueStatus::Disabled => 0,
CueStatus::Active => 4,
}
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(big)]
pub struct Cue {
header: Header,
pub hot_cue: u32,
pub status: CueStatus,
#[br(temp)]
#[br(assert(u1 == 0x10000))]
#[bw(calc = 0x10000)]
u1: u32,
pub order_first: u16,
pub order_last: u16,
pub cue_type: CueType,
#[br(temp)]
#[br(assert(u2 == 0))]
#[bw(calc = 0)]
u2: u8,
#[br(temp)]
#[br(assert(u3 == 1000))]
#[bw(calc = 1000)]
u3: u16,
pub time: u32,
pub loop_time: u32,
u4: u32,
u5: u32,
u6: u32,
u7: u32,
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(big)]
pub struct ExtendedCue {
header: Header,
pub hot_cue: u32,
pub cue_type: CueType,
#[br(temp)]
#[br(assert(u1 == 0))]
#[bw(calc = 0)]
u1: u8,
#[br(temp)]
#[br(assert(u2 == 1000))]
#[bw(calc = 1000)]
u2: u16,
pub time: u32,
pub loop_time: u32,
pub color: u8,
u3: u8,
u4: u16,
u5: u32,
pub loop_numerator: u16,
pub loop_denominator: u16,
#[br(temp)]
#[bw(calc = (comment.len() as u32 + 1) * 2)]
len_comment: u32,
#[br(assert((comment.len() as u32 + 1) * 2 == len_comment))]
pub comment: NullWideString,
pub hot_cue_color_index: u8,
pub hot_cue_color_rgb: (u8, u8, u8),
u6: u32,
u7: u32,
u8: u32,
u9: u32,
u10: u32,
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct CueList {
#[br(temp)]
#[br(assert(header_size == 24))]
#[bw(calc = 24)]
header_size: u32,
total_size: u32,
pub list_type: CueListType,
#[br(temp)]
#[br(assert(u1 == 0))]
#[bw(calc = 0)]
u1: u16,
#[br(temp)]
#[bw(calc = cues.len() as u16)]
len_cues: u16,
#[br(temp)]
#[br(assert(u2 == 0xFFFFFFFF))]
#[bw(calc = 0xFFFFFFFF)]
u2: u32,
#[br(count = usize::from(len_cues))]
pub cues: Vec<Cue>,
}
impl SizedSection for CueList {
fn size(&self) -> u32 {
self.total_size
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ExtendedCueList {
#[br(temp)]
#[br(assert(header_size == 20))]
#[bw(calc = 20)]
header_size: u32,
total_size: u32,
pub list_type: CueListType,
#[br(temp)]
#[bw(calc = cues.len() as u16)]
len_cues: u16,
#[br(temp)]
#[br(assert(u1 == 0))]
#[bw(calc = 0)]
u1: u16,
#[br(count = usize::from(len_cues))]
pub cues: Vec<ExtendedCue>,
}
impl SizedSection for ExtendedCueList {
fn size(&self) -> u32 {
self.total_size
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Path {
#[br(temp)]
#[br(assert(header_size == 16))]
#[bw(calc = 16)]
header_size: u32,
total_size: u32,
#[br(temp)]
#[br(assert(len_path == total_size - header_size))]
#[bw(calc = ((path.len() as u32) + 1) * 2)]
len_path: u32,
#[br(assert(len_path == total_size - header_size))]
#[br(assert((path.len() as u32 + 1) * 2 == len_path))]
pub path: NullWideString,
}
impl SizedSection for Path {
fn size(&self) -> u32 {
self.total_size
}
}
impl Path {
pub fn new<P: AsRef<std::path::Path> + AsRef<OsStr>>(path: P) -> Self {
let string = NullWideString::from("");
let mut item = Self {
total_size: 0,
path: string,
};
item.set(path);
item
}
pub fn set<P: AsRef<std::path::Path> + AsRef<OsStr>>(&mut self, path: P) {
let p = std::path::Path::new(&path);
let string = NullWideString::from(p.as_os_str().to_str().unwrap());
self.path = string.clone();
self.total_size = ((string.len() as u32) + 1) * 2 + 16;
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct VBR {
#[br(assert(header_size == 16))]
header_size: u32,
total_size: u32,
#[br(temp)]
#[br(assert(unknown == 0))]
#[bw(calc = 0)]
unknown: u32,
#[br(count = total_size - header_size)]
pub data: Vec<u8>,
}
impl SizedSection for VBR {
fn size(&self) -> u32 {
self.total_size
}
}
#[bitfield]
#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
#[br(big, map = Self::from_bytes)]
#[bw(big, map = |x: &TinyWaveformColumn| x.into_bytes())]
pub struct TinyWaveformColumn {
#[allow(dead_code)]
unused: B4,
pub height: B4,
}
impl Default for TinyWaveformColumn {
fn default() -> Self {
Self::new()
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct TinyWaveformPreview {
#[br(temp)]
#[br(assert(header_size == 20))]
#[bw(calc = 20)]
header_size: u32,
total_size: u32,
#[br(temp)]
#[br(assert(len_preview == total_size - header_size))]
#[bw(calc = data.len() as u32)]
len_preview: u32,
#[br(temp)]
#[br(assert(unknown == 0x00010000))]
#[bw(calc = 0x00010000)]
unknown: u32,
#[br(count = len_preview)]
pub data: Vec<TinyWaveformColumn>,
}
impl SizedSection for TinyWaveformPreview {
fn size(&self) -> u32 {
self.total_size
}
}
#[bitfield]
#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
#[br(big, map = Self::from_bytes)]
#[bw(big, map = |x: &WaveformColumn| x.into_bytes())]
pub struct WaveformColumn {
pub height: B5,
pub whiteness: B3,
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct WaveformPreview {
#[br(temp)]
#[br(assert(header_size == 20))]
#[bw(calc = 20)]
header_size: u32,
total_size: u32,
#[br(temp)]
#[br(assert(len_preview == total_size - header_size))]
#[bw(calc = data.len() as u32)]
len_preview: u32,
#[br(temp)]
#[br(assert(unknown == 0x00010000))]
#[bw(calc = 0x00010000)]
unknown: u32,
#[br(count = len_preview)]
pub data: Vec<WaveformColumn>,
}
impl SizedSection for WaveformPreview {
fn size(&self) -> u32 {
self.total_size
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct WaveformDetail {
#[br(temp)]
#[br(assert(header_size == 24))]
#[bw(calc = 24)]
header_size: u32,
total_size: u32,
#[br(temp)]
#[br(assert(len_entry_bytes == 1))]
#[bw(calc = 1u32)]
len_entry_bytes: u32,
#[br(temp)]
#[bw(calc = data.len() as u32)]
#[br(assert((len_entry_bytes * len_entries) == total_size - header_size))]
len_entries: u32,
#[br(temp)]
#[br(assert(unknown == 0x00960000))]
#[bw(calc = 0x00960000)]
unknown: u32,
#[br(count = len_entries)]
pub data: Vec<WaveformColumn>,
}
impl SizedSection for WaveformDetail {
fn size(&self) -> u32 {
self.total_size
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[brw(big)]
pub struct WaveformColorPreviewColumn {
u1: u8,
u2: u8,
pub energy_bottom_half_freq: u8,
pub energy_bottom_third_freq: u8,
pub energy_mid_third_freq: u8,
pub energy_top_third_freq: u8,
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct WaveformColorPreview {
#[br(temp)]
#[br(assert(header_size == 24))]
#[bw(calc = 24)]
header_size: u32,
total_size: u32,
#[br(temp)]
#[br(assert(len_entry_bytes == 6))]
#[bw(calc = 6u32)]
len_entry_bytes: u32,
#[br(temp)]
#[bw(calc = data.len() as u32)]
#[br(assert((len_entry_bytes * len_entries) == total_size - header_size))]
len_entries: u32,
#[br(temp)]
#[br(assert(unknown == 0))]
#[bw(calc = 0)]
unknown: u32,
#[br(count = len_entries)]
pub data: Vec<WaveformColorPreviewColumn>,
}
impl SizedSection for WaveformColorPreview {
fn size(&self) -> u32 {
self.total_size
}
}
#[bitfield]
#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
#[br(map = Self::from_bytes)]
#[bw(big, map = |x: &WaveformColorDetailColumn| x.into_bytes())]
pub struct WaveformColorDetailColumn {
pub red: B3,
pub green: B3,
pub blue: B3,
pub height: B5,
#[allow(dead_code)]
unknown: B2,
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct WaveformColorDetail {
#[br(temp)]
#[br(assert(header_size == 24))]
#[bw(calc = 24)]
header_size: u32,
total_size: u32,
#[br(temp)]
#[br(assert(len_entry_bytes == 2))]
#[bw(calc = 2u32)]
len_entry_bytes: u32,
#[br(temp)]
#[bw(calc = data.len() as u32)]
#[br(assert((len_entry_bytes * len_entries) == total_size - header_size))]
len_entries: u32,
#[br(temp)]
#[br(assert(unknown == 0x00960305))]
#[bw(calc = 0x00960305)]
unknown: u32,
#[br(count = len_entries)]
pub data: Vec<WaveformColorDetailColumn>,
}
impl SizedSection for WaveformColorDetail {
fn size(&self) -> u32 {
self.total_size
}
}
#[bitfield]
#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
#[br(map = Self::from_bytes)]
#[bw(big, map = |x: &Waveform3BandColumn| x.into_bytes())]
pub struct Waveform3BandColumn {
pub mid: B8,
pub high: B8,
pub low: B8,
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Waveform3BandPreview {
#[br(temp)]
#[br(assert(header_size == 20))]
#[bw(calc = 20)]
header_size: u32,
total_size: u32,
#[br(temp)]
#[br(assert(len_entry_bytes == 3))]
#[bw(calc = 3u32)]
len_entry_bytes: u32,
#[br(temp)]
#[bw(calc = data.len() as u32)]
#[br(assert((len_entry_bytes * len_entries) == total_size - header_size))]
len_entries: u32,
#[br(count = len_entries)]
pub data: Vec<Waveform3BandColumn>,
}
impl SizedSection for Waveform3BandPreview {
fn size(&self) -> u32 {
self.total_size
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Waveform3BandDetail {
#[br(temp)]
#[br(assert(header_size == 24))]
#[bw(calc = 24)]
header_size: u32,
total_size: u32,
#[br(temp)]
#[br(assert(len_entry_bytes == 3))]
#[bw(calc = 3u32)]
len_entry_bytes: u32,
#[br(temp)]
#[bw(calc = data.len() as u32)]
#[br(assert((len_entry_bytes * len_entries) == total_size - header_size))]
len_entries: u32,
#[br(temp)]
#[br(assert(unknown == 0x00960000))]
#[bw(calc = 0x00960000)]
unknown: u32,
#[br(count = len_entries)]
pub data: Vec<Waveform3BandColumn>,
}
impl SizedSection for Waveform3BandDetail {
fn size(&self) -> u32 {
self.total_size
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[brw(big, repr = u16)]
pub enum Mood {
High = 1,
Mid,
Low,
}
impl Into<u16> for Mood {
fn into(self) -> u16 {
match self {
Mood::High => 1,
Mood::Mid => 2,
Mood::Low => 3,
}
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[brw(repr = u8)]
pub enum Bank {
Default = 0,
Cool,
Natural,
Hot,
Subtle,
Warm,
Vivid,
Club1,
Club2,
}
impl Into<u16> for Bank {
fn into(self) -> u16 {
match self {
Bank::Default => 0,
Bank::Cool => 1,
Bank::Natural => 2,
Bank::Hot => 3,
Bank::Subtle => 4,
Bank::Warm => 5,
Bank::Vivid => 6,
Bank::Club1 => 7,
Bank::Club2 => 8,
}
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[brw(big)]
pub struct Phrase {
pub index: u16,
pub beat: u16,
pub kind: u16,
#[allow(dead_code)]
u1: u8,
pub k1: u8,
#[allow(dead_code)]
u2: u8,
pub k2: u8,
#[allow(dead_code)]
u3: u8,
pub b: u8,
pub beat2: u16,
pub beat3: u16,
pub beat4: u16,
#[allow(dead_code)]
u4: u8,
pub k3: u8,
#[allow(dead_code)]
u5: u8,
pub fill: u8,
pub beat_fill: u16,
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[br(import(len_entries: u16))]
pub struct SongStructureData {
pub mood: Mood,
u1: u32,
u2: u16,
pub end_beat: u16,
u3: u16,
pub bank: Bank,
u4: u8,
#[br(count = usize::from(len_entries))]
pub phrases: Vec<Phrase>,
}
impl SongStructureData {
const KEY_DATA: [u8; 19] = [
0xCB, 0xE1, 0xEE, 0xFA, 0xE5, 0xEE, 0xAD, 0xEE, 0xE9, 0xD2, 0xE9, 0xEB, 0xE1, 0xE9, 0xF3,
0xE8, 0xE9, 0xF4, 0xE1,
];
fn get_key(len_entries: u16) -> impl Iterator<Item = u8> {
Self::KEY_DATA.into_iter().map(move |x: u8| -> u8 {
let value = u16::from(x) + len_entries;
(value % 256) as u8
})
}
fn check_if_encrypted(raw_mood: [u8; 2], len_entries: u16) -> bool {
let buffer: Vec<u8> = raw_mood
.iter()
.zip(Self::get_key(len_entries).take(2))
.map(|(byte, key)| byte ^ key)
.collect();
let mut reader = binrw::io::Cursor::new(buffer);
Mood::read(&mut reader).is_ok()
}
fn read_encrypted<R: Read + Seek>(
reader: &mut R,
endian: Endian,
(is_encrypted, len_entries): (bool, u16),
) -> BinResult<Self> {
if is_encrypted {
let key: Vec<u8> = Self::get_key(len_entries).collect();
let mut xor_reader = XorStream::with_key(reader, key);
Self::read_options(&mut xor_reader, endian, (len_entries,))
} else {
Self::read_options(reader, endian, (len_entries,))
}
}
fn write_encrypted<W: Write + Seek>(
&self,
writer: &mut W,
endian: Endian,
(is_encrypted, len_entries): (bool, u16),
) -> BinResult<()> {
if is_encrypted {
let key: Vec<u8> = Self::get_key(len_entries).collect();
let mut xor_writer = XorStream::with_key(writer, key);
self.write_options(&mut xor_writer, endian, ())
} else {
self.write_options(writer, endian, ())
}
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct SongStructure {
#[br(temp)]
#[br(assert(header_size == 32))]
#[bw(calc = 32)]
header_size: u32,
total_size: u32,
#[br(temp)]
#[br(assert(len_entry_bytes == 24))]
#[bw(calc = 24u32)]
len_entry_bytes: u32,
#[br(temp)]
#[br(assert((len_entry_bytes * (len_entries as u32)) == total_size - header_size))]
#[bw(calc = data.phrases.len() as u16)]
len_entries: u16,
#[br(restore_position, map = |raw_mood: [u8; 2]| SongStructureData::check_if_encrypted(raw_mood, len_entries))]
#[bw(ignore)]
pub is_encrypted: bool,
#[br(args(is_encrypted, len_entries), parse_with = SongStructureData::read_encrypted)]
#[bw(args(*is_encrypted, len_entries), write_with = SongStructureData::write_encrypted)]
pub data: SongStructureData,
}
impl SizedSection for SongStructure {
fn size(&self) -> u32 {
self.total_size
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Unknown {
header_size: u32,
total_size: u32,
#[br(count = header_size - 12)]
header_data: Vec<u8>,
#[br(count = total_size - header_size)]
content_data: Vec<u8>,
}
impl SizedSection for Unknown {
fn size(&self) -> u32 {
self.total_size
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[br(import(tag: AnlzTag))]
pub enum Content {
#[br(pre_assert(tag == AnlzTag::BeatGrid))]
BeatGrid(BeatGrid),
#[br(pre_assert(tag == AnlzTag::ExtendedBeatGrid))]
ExtendedBeatGrid(ExtendedBeatGrid),
#[br(pre_assert(tag == AnlzTag::CueList))]
CueList(CueList),
#[br(pre_assert(tag == AnlzTag::ExtendedCueList))]
ExtendedCueList(ExtendedCueList),
#[br(pre_assert(tag == AnlzTag::Path))]
Path(Path),
#[br(pre_assert(tag == AnlzTag::VBR))]
VBR(VBR),
#[br(pre_assert(tag == AnlzTag::WaveformPreview))]
WaveformPreview(WaveformPreview),
#[br(pre_assert(tag == AnlzTag::TinyWaveformPreview))]
TinyWaveformPreview(TinyWaveformPreview),
#[br(pre_assert(tag == AnlzTag::WaveformDetail))]
WaveformDetail(WaveformDetail),
#[br(pre_assert(tag == AnlzTag::WaveformColorPreview))]
WaveformColorPreview(WaveformColorPreview),
#[br(pre_assert(tag == AnlzTag::WaveformColorDetail))]
WaveformColorDetail(WaveformColorDetail),
#[br(pre_assert(tag == AnlzTag::Waveform3BandPreview))]
Waveform3BandPreview(Waveform3BandPreview),
#[br(pre_assert(tag == AnlzTag::Waveform3BandDetail))]
Waveform3BandDetail(Waveform3BandDetail),
#[br(pre_assert(tag == AnlzTag::SongStructure))]
SongStructure(SongStructure),
#[br(pre_assert(matches!(tag, AnlzTag::Unknown(_))))]
Unknown(Unknown),
}
impl SizedSection for Content {
fn size(&self) -> u32 {
match *self {
Content::BeatGrid(ref x) => x.size(),
Content::ExtendedBeatGrid(ref x) => x.size(),
Content::CueList(ref x) => x.size(),
Content::ExtendedCueList(ref x) => x.size(),
Content::Path(ref x) => x.size(),
Content::VBR(ref x) => x.size(),
Content::WaveformPreview(ref x) => x.size(),
Content::TinyWaveformPreview(ref x) => x.size(),
Content::WaveformDetail(ref x) => x.size(),
Content::WaveformColorPreview(ref x) => x.size(),
Content::WaveformColorDetail(ref x) => x.size(),
Content::Waveform3BandPreview(ref x) => x.size(),
Content::Waveform3BandDetail(ref x) => x.size(),
Content::SongStructure(ref x) => x.size(),
Content::Unknown(ref x) => x.size(),
}
}
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Section {
pub tag: AnlzTag,
#[br(args(tag.clone()))]
pub content: Content,
}
#[binrw]
#[derive(Debug, PartialEq, Eq, Clone)]
#[brw(big)]
pub struct AnlzData {
#[br(assert(header.tag == AnlzTag::File))]
pub header: Header,
#[br(count = header.remaining_size())]
pub header_data: Vec<u8>,
#[br(parse_with = Self::parse_sections, args(header.content_size()))]
pub sections: Vec<Section>,
}
impl AnlzData {
fn parse_sections<R: Read + Seek>(
reader: &mut R,
endian: Endian,
args: (u32,),
) -> BinResult<Vec<Section>> {
let (content_size,) = args;
let final_position = reader.stream_position()? + u64::from(content_size);
let mut sections: Vec<Section> = vec![];
while reader.stream_position()? < final_position {
let section = Section::read_options(reader, endian, ())?;
sections.push(section);
}
Ok(sections)
}
pub fn update_total_size(&mut self) {
let mut total = self.header.size;
for section in self.sections.iter_mut() {
total += section.content.size();
}
self.header.total_size = total;
}
}
pub struct Anlz {
path: std::path::PathBuf,
pub data: AnlzData,
}
impl Anlz {
pub fn load<P: AsRef<std::path::Path> + AsRef<OsStr>>(path: P) -> Result<Self> {
let p = std::path::Path::new(&path).to_path_buf();
let mut file = std::fs::File::open(&p).expect("File not found");
let data = AnlzData::read(&mut file).expect("Can't read ANLZ");
Ok(Anlz { path: p, data })
}
pub fn dump_copy<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<()> {
self.data.update_total_size();
let mut file = std::fs::File::create(path).expect("Failed to create file");
self.data.write(&mut file)?;
Ok(())
}
pub fn dump(&mut self) -> Result<()> {
let path = &self.path.clone();
self.dump_copy(path)?;
Ok(())
}
pub fn find_section_by_tag(&mut self, tag: AnlzTag) -> Option<&mut Section> {
self.data.sections.iter_mut().find(|s| s.tag == tag)
}
pub fn find_sections_by_tag(&mut self, tag: AnlzTag) -> Vec<&mut Section> {
self.data
.sections
.iter_mut()
.filter(|s| s.tag == tag)
.collect()
}
pub fn contains(&self, tag: AnlzTag) -> bool {
self.data.sections.iter().any(|s| s.tag == tag)
}
pub fn get_tags(&self) -> Result<Vec<String>> {
let mut names = Vec::new();
for section in &self.data.sections {
let name = §ion.tag;
names.push(name.to_string());
}
Ok(names)
}
pub fn get_beat_grid(&mut self) -> Option<&mut BeatGrid> {
let section = self.find_section_by_tag(AnlzTag::BeatGrid);
if let Some(section) = section {
if let Content::BeatGrid(content) = &mut section.content {
return Some(content);
}
}
None
}
pub fn get_extended_beat_grid(&mut self) -> Option<&mut ExtendedBeatGrid> {
let section = self.find_section_by_tag(AnlzTag::ExtendedBeatGrid);
if let Some(section) = section {
if let Content::ExtendedBeatGrid(content) = &mut section.content {
return Some(content);
}
}
None
}
pub fn get_hot_cues(&mut self) -> Option<&mut CueList> {
let sections = self.find_sections_by_tag(AnlzTag::CueList);
for section in sections {
if let Content::CueList(content) = &mut section.content {
if content.list_type == CueListType::HotCues {
return Some(content);
}
}
}
None
}
pub fn get_memory_cues(&mut self) -> Option<&mut CueList> {
let sections = self.find_sections_by_tag(AnlzTag::CueList);
for section in sections {
if let Content::CueList(content) = &mut section.content {
if content.list_type == CueListType::MemoryCues {
return Some(content);
}
}
}
None
}
pub fn get_extended_hot_cues(&mut self) -> Option<&mut ExtendedCueList> {
let sections = self.find_sections_by_tag(AnlzTag::ExtendedCueList);
for section in sections {
if let Content::ExtendedCueList(content) = &mut section.content {
if content.list_type == CueListType::HotCues {
return Some(content);
}
}
}
None
}
pub fn get_extended_memory_cues(&mut self) -> Option<&mut ExtendedCueList> {
let sections = self.find_sections_by_tag(AnlzTag::ExtendedCueList);
for section in sections {
if let Content::ExtendedCueList(content) = &mut section.content {
if content.list_type == CueListType::MemoryCues {
return Some(content);
}
}
}
None
}
pub fn get_path(&mut self) -> Option<String> {
let section = self.find_section_by_tag(AnlzTag::Path);
if let Some(section) = section {
if let Content::Path(content) = §ion.content {
return Some(content.path.to_string());
}
}
None
}
pub fn set_path<P: AsRef<std::path::Path> + AsRef<OsStr>>(&mut self, path: P) -> Result<()> {
let section = self.find_section_by_tag(AnlzTag::Path);
if let Some(section) = section {
if let Content::Path(content) = &mut section.content {
content.set(path);
} else {
return Err(Error::AnlzError("Path not found".into()));
}
} else {
let content = Path::new(path);
let section = Section {
tag: AnlzTag::Path,
content: Content::Path(content),
};
self.data.sections.push(section);
}
self.data.update_total_size();
Ok(())
}
pub fn get_vbr_data(&mut self) -> Option<Vec<u8>> {
let section = self.find_section_by_tag(AnlzTag::VBR);
if let Some(section) = section {
if let Content::VBR(content) = §ion.content {
return Some(content.data.clone());
}
}
None
}
pub fn get_tiny_waveform_preview(&mut self) -> Option<&mut TinyWaveformPreview> {
let section = self.find_section_by_tag(AnlzTag::TinyWaveformPreview);
if let Some(section) = section {
if let Content::TinyWaveformPreview(content) = &mut section.content {
return Some(content);
}
}
None
}
pub fn get_waveform_preview(&mut self) -> Option<&mut WaveformPreview> {
let section = self.find_section_by_tag(AnlzTag::WaveformPreview);
if let Some(section) = section {
if let Content::WaveformPreview(content) = &mut section.content {
return Some(content);
}
}
None
}
pub fn get_waveform_detail(&mut self) -> Option<&mut WaveformDetail> {
let section = self.find_section_by_tag(AnlzTag::WaveformDetail);
if let Some(section) = section {
if let Content::WaveformDetail(content) = &mut section.content {
return Some(content);
}
}
None
}
pub fn get_waveform_color_preview(&mut self) -> Option<&mut WaveformColorPreview> {
let section = self.find_section_by_tag(AnlzTag::WaveformColorPreview);
if let Some(section) = section {
if let Content::WaveformColorPreview(content) = &mut section.content {
return Some(content);
}
}
None
}
pub fn get_waveform_color_detail(&mut self) -> Option<&mut WaveformColorDetail> {
let section = self.find_section_by_tag(AnlzTag::WaveformColorDetail);
if let Some(section) = section {
if let Content::WaveformColorDetail(content) = &mut section.content {
return Some(content);
}
}
None
}
pub fn get_waveform_3band_preview(&mut self) -> Option<&mut Waveform3BandPreview> {
let section = self.find_section_by_tag(AnlzTag::Waveform3BandPreview);
if let Some(section) = section {
if let Content::Waveform3BandPreview(content) = &mut section.content {
return Some(content);
}
}
None
}
pub fn get_waveform_3band_detail(&mut self) -> Option<&mut Waveform3BandDetail> {
let section = self.find_section_by_tag(AnlzTag::Waveform3BandDetail);
if let Some(section) = section {
if let Content::Waveform3BandDetail(content) = &mut section.content {
return Some(content);
}
}
None
}
pub fn get_song_structure(&mut self) -> Option<SongStructureData> {
let section = self.find_section_by_tag(AnlzTag::SongStructure);
if let Some(section) = section {
if let Content::SongStructure(content) = &mut section.content {
let data = content.data.clone();
return Some(data);
}
}
None
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct AnlzPaths {
pub dat: std::path::PathBuf,
pub ext: Option<std::path::PathBuf>,
pub ex2: Option<std::path::PathBuf>,
}
pub struct AnlzFiles {
pub dat: Anlz,
pub ext: Option<Anlz>,
pub ex2: Option<Anlz>,
}
pub fn find_anlz_files<P: AsRef<std::path::Path> + AsRef<OsStr>>(
root: P,
) -> Result<Option<AnlzPaths>> {
let root = std::path::Path::new(&root).to_path_buf();
let mut dat: Option<std::path::PathBuf> = None;
let mut ext: Option<std::path::PathBuf> = None;
let mut ex2: Option<std::path::PathBuf> = None;
let paths = std::fs::read_dir(root)?;
for path in paths {
let file = path?.path();
let extension = file.extension();
if let Some(extension) = extension {
match extension.to_ascii_uppercase().to_str().unwrap() {
"DAT" => dat = Some(file.clone()),
"EXT" => ext = Some(file.clone()),
"2EX" => ex2 = Some(file.clone()),
&_ => {}
}
}
}
if dat.is_none() {
return Ok(None);
}
let dat = dat.unwrap();
let files = AnlzPaths { dat, ext, ex2 };
Ok(Some(files))
}