From f4c0d9dd07979dc2b57aeb051052bce0e309949f Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Thu, 24 Feb 2022 18:38:08 +0100 Subject: [PATCH 1/5] anlz: Use `binrw` instead of `nom` to parse `ANLZ*.DAT` files --- Cargo.lock | 28 ++ Cargo.toml | 1 + src/anlz.rs | 873 +++++++----------------------------- src/bin/rekordcrate-anlz.rs | 10 +- src/util.rs | 13 +- tests/tests_anlz.rs.in | 8 +- 6 files changed, 219 insertions(+), 714 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0605221..fb9e2b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,6 +48,27 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "nom" version = "7.1.0" @@ -89,9 +110,16 @@ version = "0.1.1" dependencies = [ "binrw", "glob", + "modular-bitfield", "nom", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "1.0.86" diff --git a/Cargo.toml b/Cargo.toml index b8beb26..46eabc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ exclude = [".*"] [dependencies] nom = "7" binrw = "0.8" +modular-bitfield = "0.11" [build-dependencies] glob = "0.3" diff --git a/src/anlz.rs b/src/anlz.rs index 137628a..e05ee2e 100644 --- a/src/anlz.rs +++ b/src/anlz.rs @@ -24,87 +24,83 @@ //! - //! - -use crate::util::{nom_input_error_with_kind, ColorIndex}; -use nom::error::ErrorKind; -use nom::IResult; +#![allow(clippy::must_use_candidate)] + +use crate::util::ColorIndex; +use binrw::{ + binread, + io::{Read, Seek}, + BinRead, BinResult, ReadOptions, +}; +use modular_bitfield::prelude::*; -#[derive(Debug)] /// The kind of section. +#[derive(Debug, PartialEq, Clone)] +#[binread] +#[br(big)] pub enum ContentKind { /// File section that contains all other sections. + #[br(magic = b"PMAI")] File, /// All beats found in the track. + #[br(magic = b"PQTZ")] BeatGrid, /// Either memory points and loops or hotcues and hot loops of the track. /// /// *Note:* Since the release of the Nexus 2 series, there also exists the `ExtendedCueList` /// section which can carry additional information. + #[br(magic = b"PCOB")] CueList, /// Extended version of the `CueList` section (since Nexus 2 series). + #[br(magic = b"PCO2")] ExtendedCueList, /// Single cue entry inside a `ExtendedCueList` section. + #[br(magic = b"PCP2")] ExtendedCue, /// Single cue entry inside a `CueList` section. + #[br(magic = b"PCPT")] Cue, /// File path of the audio file. + #[br(magic = b"PPTH")] Path, /// Seek information for variable bitrate files. + #[br(magic = b"PVBR")] VBR, /// Fixed-width monochrome preview of the track waveform. + #[br(magic = b"PWAV")] WaveformPreview, /// Smaller version of the fixed-width monochrome preview of the track waveform (for the /// CDJ-900). + #[br(magic = b"PWV2")] TinyWaveformPreview, /// Variable-width large monochrome version of the track waveform. /// /// Used in `.EXT` files. + #[br(magic = b"PWV3")] WaveformDetail, /// Fixed-width colored version of the track waveform. /// /// Used in `.EXT` files. + #[br(magic = b"PWV4")] WaveformColorPreview, /// Variable-width large colored version of the track waveform. /// /// Used in `.EXT` files. + #[br(magic = b"PWV5")] WaveformColorDetail, /// Describes the structure of a sond (Intro, Chrous, Verse, etc.). /// /// Used in `.EXT` files. + #[br(magic = b"PSSI")] SongStructure, /// Unknown Kind. Unknown([u8; 4]), } -impl ContentKind { - fn parse(input: &[u8]) -> IResult<&[u8], Self> { - let (input, kind_slice) = nom::bytes::complete::take(4usize)(input)?; - let kind = match kind_slice { - b"PMAI" => ContentKind::File, - b"PCO2" => ContentKind::ExtendedCueList, - b"PCOB" => ContentKind::CueList, - b"PCP2" => ContentKind::ExtendedCue, - b"PCPT" => ContentKind::Cue, - b"PPTH" => ContentKind::Path, - b"PQTZ" => ContentKind::BeatGrid, - b"PVBR" => ContentKind::VBR, - b"PWAV" => ContentKind::WaveformPreview, - b"PWV2" => ContentKind::TinyWaveformPreview, - b"PWV3" => ContentKind::WaveformDetail, - b"PWV4" => ContentKind::WaveformColorPreview, - b"PWV5" => ContentKind::WaveformColorDetail, - b"PSSI" => ContentKind::SongStructure, - unk => { - let kind_buffer: [u8; 4] = unk.try_into().unwrap(); - Self::Unknown(kind_buffer) - } - }; - - Ok((input, kind)) - } -} - -#[derive(Debug)] /// Header of a section that contains type and size information. +#[derive(Debug, PartialEq, Clone)] +#[binread] +#[br(big)] pub struct Header { /// Kind of content in this item. pub kind: ContentKind, @@ -115,21 +111,6 @@ pub struct Header { } impl Header { - fn parse(input: &[u8]) -> IResult<&[u8], Self> { - let (input, kind) = ContentKind::parse(input)?; - let (input, size) = nom::number::complete::be_u32(input)?; - let (input, total_size) = nom::number::complete::be_u32(input)?; - - Ok(( - input, - Self { - kind, - size, - total_size, - }, - )) - } - fn remaining_size(&self) -> u32 { self.size - 12 } @@ -139,8 +120,10 @@ impl Header { } } -#[derive(Debug)] /// A single beat inside the beat grid. +#[derive(Debug, PartialEq)] +#[binread] +#[br(big)] pub struct Beat { /// Beat number inside the bar (1-4). pub beat_number: u16, @@ -150,77 +133,36 @@ pub struct Beat { pub time: u32, } -impl Beat { - /// Parse a beat entry. - fn parse(input: &[u8]) -> IResult<&[u8], Self> { - let (input, beat_number) = nom::number::complete::be_u16(input)?; - let (input, tempo) = nom::number::complete::be_u16(input)?; - let (input, time) = nom::number::complete::be_u32(input)?; - - Ok(( - input, - Self { - beat_number, - tempo, - time, - }, - )) - } -} - -#[derive(Debug)] /// Describes the types of entries found in a Cue List section. +#[derive(Debug, PartialEq)] +#[binread] +#[br(big, repr = u32)] pub enum CueListType { /// Memory cues or loops. - MemoryCues, + MemoryCues = 0, /// Hot cues or loops. - HotCues, - /// Unknown type. - Unknown(u32), -} - -impl CueListType { - fn parse(input: &[u8]) -> IResult<&[u8], Self> { - let (input, list_type_id) = nom::number::complete::be_u32(input)?; - let list_type = match list_type_id { - 0 => CueListType::MemoryCues, - 1 => CueListType::HotCues, - x => CueListType::Unknown(x), - }; - Ok((input, list_type)) - } + HotCues = 1, } -#[derive(Debug)] /// Indicates if the cue is point or a loop. +#[derive(Debug, PartialEq)] +#[binread] +#[br(repr = u8)] pub enum CueType { /// Cue is a single point. - Point, + Point = 0, /// Cue is a loop. - Loop, - /// Unknown type. - Unknown(u8), + Loop = 2, } -impl CueType { - fn parse(input: &[u8]) -> IResult<&[u8], Self> { - let (input, cue_type_id) = nom::number::complete::u8(input)?; - let cue_type = match cue_type_id { - 0 => CueType::Point, - 2 => CueType::Loop, - x => CueType::Unknown(x), - }; - Ok((input, cue_type)) - } -} - -#[derive(Debug)] /// A memory or hot cue (or loop). +#[derive(Debug, PartialEq)] +#[binread] +#[br(big)] pub struct Cue { /// Cue entry header. pub header: Header, /// Hot cue number. - /// /// | Value | Hot cue | /// | ----- | -------------- | /// | 0 | Not a hot cue. | @@ -279,50 +221,10 @@ pub struct Cue { pub unknown7: u32, } -impl Cue { - /// Parse a cue entry. - fn parse(input: &[u8]) -> IResult<&[u8], Self> { - let (input, header) = Header::parse(input)?; - let (input, hot_cue) = nom::number::complete::be_u32(input)?; - let (input, status) = nom::number::complete::be_u32(input)?; - let (input, unknown1) = nom::number::complete::be_u32(input)?; - let (input, order_first) = nom::number::complete::be_u16(input)?; - let (input, order_last) = nom::number::complete::be_u16(input)?; - let (input, cue_type) = CueType::parse(input)?; - let (input, unknown2) = nom::number::complete::u8(input)?; - let (input, unknown3) = nom::number::complete::be_u16(input)?; - let (input, time) = nom::number::complete::be_u32(input)?; - let (input, loop_time) = nom::number::complete::be_u32(input)?; - let (input, unknown4) = nom::number::complete::be_u32(input)?; - let (input, unknown5) = nom::number::complete::be_u32(input)?; - let (input, unknown6) = nom::number::complete::be_u32(input)?; - let (input, unknown7) = nom::number::complete::be_u32(input)?; - - Ok(( - input, - Self { - header, - hot_cue, - status, - unknown1, - order_first, - order_last, - cue_type, - unknown2, - unknown3, - time, - loop_time, - unknown4, - unknown5, - unknown6, - unknown7, - }, - )) - } -} - -#[derive(Debug)] /// A memory or hot cue (or loop). +#[derive(Debug, PartialEq)] +#[binread] +#[br(big)] pub struct ExtendedCue { /// Cue entry header. pub header: Header, @@ -359,8 +261,11 @@ pub struct ExtendedCue { pub loop_numerator: u16, /// Represents the loop size denominator (if this is a quantized loop). pub loop_denominator: u16, - /// And UTF-16BE encoded string, followed by a trailing `0x0000`. - pub comment: String, + /// Length of the comment string in bytes. + pub len_comment: u32, + /// An UTF-16BE encoded string, followed by a trailing `0x0000`. + #[br(count = len_comment)] + pub comment: Vec, /// Rekordbox hotcue color index. /// /// | Value | Color | @@ -447,110 +352,34 @@ pub struct ExtendedCue { pub unknown10: u32, } -impl ExtendedCue { - /// Parse an extended cue entry. - fn parse(input: &[u8]) -> IResult<&[u8], Self> { - let (input, header) = Header::parse(input)?; - let (input, hot_cue) = nom::number::complete::be_u32(input)?; - let (input, cue_type) = CueType::parse(input)?; - let (input, unknown1) = nom::number::complete::u8(input)?; - let (input, unknown2) = nom::number::complete::be_u16(input)?; - let (input, time) = nom::number::complete::be_u32(input)?; - let (input, loop_time) = nom::number::complete::be_u32(input)?; - let (input, color) = ColorIndex::parse_u8(input)?; - let (input, unknown3) = nom::number::complete::u8(input)?; - let (input, unknown4) = nom::number::complete::be_u16(input)?; - let (input, unknown5) = nom::number::complete::be_u32(input)?; - let (input, loop_numerator) = nom::number::complete::be_u16(input)?; - let (input, loop_denominator) = nom::number::complete::be_u16(input)?; - let (input, len_comment) = nom::number::complete::be_u32(input)?; - let len_comment = usize::try_from(len_comment) - .map_err(|_| nom_input_error_with_kind(input, ErrorKind::TooLarge))?; - let str_length = len_comment / 2 - 1; - let (input, str_data) = - nom::multi::count(nom::number::complete::be_u16, str_length)(input)?; - let (input, _) = nom::bytes::complete::tag(b"\x00\x00")(input)?; - let comment = String::from_utf16(&str_data) - .map_err(|_| nom_input_error_with_kind(input, ErrorKind::Char))?; - - let (input, hot_cue_color_index) = nom::number::complete::u8(input)?; - let (input, hot_cue_color_red) = nom::number::complete::u8(input)?; - let (input, hot_cue_color_green) = nom::number::complete::u8(input)?; - let (input, hot_cue_color_blue) = nom::number::complete::u8(input)?; - let hot_cue_color_rgb = (hot_cue_color_red, hot_cue_color_green, hot_cue_color_blue); - let (input, unknown6) = nom::number::complete::be_u32(input)?; - let (input, unknown7) = nom::number::complete::be_u32(input)?; - let (input, unknown8) = nom::number::complete::be_u32(input)?; - let (input, unknown9) = nom::number::complete::be_u32(input)?; - let (input, unknown10) = nom::number::complete::be_u32(input)?; - - Ok(( - input, - Self { - header, - hot_cue, - cue_type, - unknown1, - unknown2, - time, - loop_time, - color, - unknown3, - unknown4, - unknown5, - loop_numerator, - loop_denominator, - comment, - hot_cue_color_index, - hot_cue_color_rgb, - unknown6, - unknown7, - unknown8, - unknown9, - unknown10, - }, - )) - } -} - -#[derive(Debug)] /// Single Column value in a Waveform Preview. +#[bitfield] +#[derive(BinRead, Debug, PartialEq)] +#[br(big, map = Self::from_bytes)] pub struct WaveformPreviewColumn { /// Height of the Column in pixels. - pub height: u8, + pub height: B5, /// Shade of white. - pub whiteness: u8, -} - -impl From for WaveformPreviewColumn { - fn from(byte: u8) -> Self { - Self { - height: (byte & 0b00011111), - whiteness: (byte >> 5), - } - } + pub whiteness: B3, } -#[derive(Debug)] /// Single Column value in a Tiny Waveform Preview. +#[bitfield] +#[derive(BinRead, Debug, PartialEq)] +#[br(big, map = Self::from_bytes)] pub struct TinyWaveformPreviewColumn { /// Height of the Column in pixels. - pub height: u8, + pub unused: B4, + pub beight: B4, } -impl From for TinyWaveformPreviewColumn { - fn from(byte: u8) -> Self { - Self { - height: (byte & 0b00001111), - } - } -} - -#[derive(Debug)] /// Single Column value in a Waveform Color Preview. /// /// See these the documentation for details: /// +#[derive(Debug, PartialEq)] +#[binread] +#[br(big)] pub struct WaveformColorPreviewColumn { /// Unknown field (somehow encodes the "whiteness"). pub unknown1: u8, @@ -564,165 +393,89 @@ pub struct WaveformColorPreviewColumn { pub energy_mid_third_freq: u8, /// Sound energy in the top of the frequency range. pub energy_top_third_freq: u8, - /// Combination of the sound energy of the bottom, mid and top thirds of the frequency range. - pub color: u8, - /// Combination of the all other values in this struct. - pub blue: u8, -} - -impl WaveformColorPreviewColumn { - fn parse(input: &[u8]) -> IResult<&[u8], Self> { - let (input, unknown1) = nom::number::complete::u8(input)?; - let (input, unknown2) = nom::number::complete::u8(input)?; - let (input, energy_bottom_half_freq) = nom::number::complete::u8(input)?; - let (input, energy_bottom_third_freq) = nom::number::complete::u8(input)?; - let (input, energy_mid_third_freq) = nom::number::complete::u8(input)?; - let (input, energy_top_third_freq) = nom::number::complete::u8(input)?; - let (input, color) = nom::number::complete::u8(input)?; - let (input, blue) = nom::number::complete::u8(input)?; - - Ok(( - input, - Self { - unknown1, - unknown2, - energy_bottom_half_freq, - energy_bottom_third_freq, - energy_mid_third_freq, - energy_top_third_freq, - color, - blue, - }, - )) - } } -#[derive(Debug)] /// Single Column value in a Waveform Color Detail section. +#[bitfield] +#[derive(BinRead, Debug, PartialEq)] +#[br(map = Self::from_bytes)] pub struct WaveformColorDetailColumn { /// Red color component. - pub red: u8, + pub red: B3, /// Green color component. - pub green: u8, + pub green: B3, /// Blue color component. - pub blue: u8, + pub blue: B3, /// Height of the column. - pub height: u8, + pub height: B5, + /// Unknown field + pub unknown: B2, } -impl WaveformColorDetailColumn { - fn parse(input: &[u8]) -> IResult<&[u8], Self> { - let (input, data) = nom::number::complete::be_u16(input)?; - let height = (data >> 2) as u8 * 0b11111; - let blue = (data >> 7) as u8 * 0b111; - let green = (data >> 10) as u8 * 0b111; - let red = (data >> 13) as u8 * 0b111; - - Ok(( - input, - Self { - red, - green, - blue, - height, - }, - )) - } -} - -#[derive(Debug)] /// Music classification that is used for Lightnight mode and based on rhythm, tempo kick drum and /// sound density. +#[derive(Debug, PartialEq)] +#[binread] +#[br(big)] pub enum Mood { /// Phrase types consist of "Intro", "Up", "Down", "Chorus", and "Outro". Other values in each /// phrase entry cause the intro, chorus, and outro phrases to have their labels subdivided /// into styes "1" or "2" (for example, "Intro 1"), and "up" is subdivided into style "Up 1", /// "Up 2", or "Up 3". + #[br(magic = 1u16)] High, /// Phrase types are labeled "Intro", "Verse 1" through "Verse 6", "Chorus", "Bridge", and /// "Outro". + #[br(magic = 2u16)] Mid, /// Phrase types are labeled "Intro", "Verse 1", "Verse 2", "Chorus", "Bridge", and "Outro". /// There are three different phrase type values for each of "Verse 1" and "Verse 2", but /// rekordbox makes no distinction between them. + #[br(magic = 3u16)] Low, /// Unknown value. Unknown(u16), } -impl Mood { - fn parse(input: &[u8]) -> IResult<&[u8], Self> { - let (input, mood_id) = nom::number::complete::be_u16(input)?; - let mood = Mood::from(mood_id); - - Ok((input, mood)) - } -} - -impl From for Mood { - fn from(mood_id: u16) -> Self { - match mood_id { - 1 => Self::High, - 2 => Self::Mid, - 3 => Self::Low, - x => Self::Unknown(x), - } - } -} - -#[derive(Debug)] /// Stylistic track bank for Lightning mode. +#[derive(Debug, PartialEq)] +#[binread] pub enum Bank { /// Default bank variant, treated as `Cool`. + #[br(magic = 0u8)] Default, /// "Cool" bank variant. + #[br(magic = 1u8)] Cool, /// "Natural" bank variant. + #[br(magic = 2u8)] Natural, /// "Hot" bank variant. + #[br(magic = 3u8)] Hot, /// "Subtle" bank variant. + #[br(magic = 4u8)] Subtle, /// "Warm" bank variant. + #[br(magic = 5u8)] Warm, /// "Vivid" bank variant. + #[br(magic = 6u8)] Vivid, /// "Club 1" bank variant. + #[br(magic = 7u8)] Club1, /// "Club 2" bank variant. + #[br(magic = 8u8)] Club2, /// Unknown value. Unknown(u8), } -impl Bank { - fn parse(input: &[u8]) -> IResult<&[u8], Self> { - let (input, bank_id) = nom::number::complete::u8(input)?; - let bank = Bank::from(bank_id); - - Ok((input, bank)) - } -} - -impl From for Bank { - fn from(bank_id: u8) -> Self { - match bank_id { - 0 => Self::Default, - 1 => Self::Cool, - 2 => Self::Natural, - 3 => Self::Hot, - 4 => Self::Subtle, - 5 => Self::Warm, - 6 => Self::Vivid, - 7 => Self::Club1, - 8 => Self::Club2, - x => Self::Unknown(x), - } - } -} - -#[derive(Debug)] /// A song structure entry that represents a phrase in the track. +#[derive(Debug, PartialEq)] +#[binread] +#[br(big)] pub struct Phrase { /// Phrase number (starting at 1). pub index: u16, @@ -775,55 +528,13 @@ pub struct Phrase { pub beat_fill: u16, } -impl Phrase { - fn parse(input: &[u8]) -> IResult<&[u8], Self> { - let (input, index) = nom::number::complete::be_u16(input)?; - let (input, beat) = nom::number::complete::be_u16(input)?; - let (input, kind) = nom::number::complete::be_u16(input)?; - let (input, unknown1) = nom::number::complete::u8(input)?; - let (input, k1) = nom::number::complete::u8(input)?; - let (input, unknown2) = nom::number::complete::u8(input)?; - let (input, k2) = nom::number::complete::u8(input)?; - let (input, unknown3) = nom::number::complete::u8(input)?; - let (input, b) = nom::number::complete::u8(input)?; - let (input, beat2) = nom::number::complete::be_u16(input)?; - let (input, beat3) = nom::number::complete::be_u16(input)?; - let (input, beat4) = nom::number::complete::be_u16(input)?; - let (input, unknown4) = nom::number::complete::u8(input)?; - let (input, k3) = nom::number::complete::u8(input)?; - let (input, unknown5) = nom::number::complete::u8(input)?; - let (input, fill) = nom::number::complete::u8(input)?; - let (input, beat_fill) = nom::number::complete::be_u16(input)?; - - Ok(( - input, - Self { - index, - beat, - kind, - unknown1, - k1, - unknown2, - k2, - unknown3, - b, - beat2, - beat3, - beat4, - unknown4, - k3, - unknown5, - fill, - beat_fill, - }, - )) - } -} - -#[derive(Debug)] /// Section content which differs depending on the section type. +#[derive(Debug, PartialEq)] +#[binread] +#[br(big, import(header: Header))] pub enum Content { /// All beats in the track. + #[br(pre_assert(header.kind == ContentKind::BeatGrid))] BeatGrid { /// Unknown field. unknown1: u32, @@ -831,78 +542,111 @@ pub enum Content { /// /// According to [@flesniak](https://github.com/flesniak), this is always `00800000`. unknown2: u32, + /// Number of beats in this beatgrid. + len_beats: u32, /// Beats in this beatgrid. + #[br(count = len_beats)] beats: Vec, }, /// List of cue points or loops (either hot cues or memory cues). + #[br(pre_assert(header.kind == ContentKind::CueList))] CueList { /// The types of cues (memory or hot) that this list contains. list_type: CueListType, /// Unknown field unknown: u16, + /// Number of cues. + len_cues: u16, /// Unknown field. memory_count: u32, /// Cues + #[br(count = len_cues)] cues: Vec, }, /// List of cue points or loops (either hot cues or memory cues, extended version). /// /// Variation of the original `CueList` that also adds support for more metadata such as /// comments and colors. Introduces with the Nexus 2 series players. + #[br(pre_assert(header.kind == ContentKind::ExtendedCueList))] ExtendedCueList { /// The types of cues (memory or hot) that this list contains. list_type: CueListType, + /// Number of cues. + len_cues: u16, + /// Unknown field + #[br(assert(unknown == 0))] + unknown: u16, /// Cues + #[br(count = len_cues)] cues: Vec, }, /// Path of the audio file that this analysis belongs to. - Path(String), + #[br(pre_assert(header.kind == ContentKind::Path))] + Path { + /// Length of the path field in bytes. + len_path: u32, + #[br(assert(len_path == header.content_size()), count = len_path)] + /// Path of the audio file. + path: Vec, + }, /// Seek information for variable bitrate files (probably). + #[br(pre_assert(header.kind == ContentKind::VBR))] VBR { /// Unknown field. unknown1: u32, /// Unknown data. + #[br(count = header.content_size())] unknown2: Vec, }, /// Fixed-width monochrome preview of the track waveform. + #[br(pre_assert(header.kind == ContentKind::WaveformPreview))] WaveformPreview { /// Unknown field. len_preview: u32, /// Unknown field (apparently always `0x00100000`) unknown: u32, /// Waveform preview column data. + #[br(count = header.content_size())] data: Vec, }, /// Smaller version of the fixed-width monochrome preview of the track waveform. + #[br(pre_assert(header.kind == ContentKind::TinyWaveformPreview))] TinyWaveformPreview { /// Unknown field. len_preview: u32, /// Unknown field (apparently always `0x00100000`) unknown: u32, /// Waveform preview column data. + #[br(count = header.content_size())] data: Vec, }, /// Variable-width large monochrome version of the track waveform. /// /// Used in `.EXT` files. + #[br(pre_assert(header.kind == ContentKind::WaveformDetail))] WaveformDetail { /// Size of a single entry, always 1. + #[br(assert(len_entry_bytes == 1))] len_entry_bytes: u32, /// Number of entries in this section. len_entries: u32, /// Unknown field (apparently always `0x00960000`) + #[br(assert(unknown == 0x00960000))] unknown: u32, /// Waveform preview column data. /// /// Each entry represents one half-frame of audio data, and there are 75 frames per second, /// so for each second of track audio there are 150 waveform detail entries. + #[br(count = len_entries)] data: Vec, }, /// Variable-width large monochrome version of the track waveform. /// /// Used in `.EXT` files. + #[br(pre_assert(header.kind == ContentKind::WaveformColorPreview))] WaveformColorPreview { - /// Size of a single entry, always 1. + /// Size of a single entry, always 6. + #[br(assert(len_entry_bytes == 6))] len_entry_bytes: u32, /// Number of entries in this section. len_entries: u32, @@ -912,26 +656,32 @@ pub enum Content { /// /// Each entry represents one half-frame of audio data, and there are 75 frames per second, /// so for each second of track audio there are 150 waveform detail entries. + #[br(count = len_entries)] data: Vec, }, /// Variable-width large colored version of the track waveform. /// /// Used in `.EXT` files. + #[br(pre_assert(header.kind == ContentKind::WaveformColorDetail))] WaveformColorDetail { - /// Size of a single entry, always 1. + /// Size of a single entry, always 2. + #[br(assert(len_entry_bytes == 2))] len_entry_bytes: u32, /// Number of entries in this section. len_entries: u32, /// Unknown field. unknown: u32, /// Waveform detail column data. + #[br(count = len_entries)] data: Vec, }, /// Describes the structure of a sond (Intro, Chrous, Verse, etc.). /// /// Used in `.EXT` files. + #[br(pre_assert(header.kind == ContentKind::SongStructure))] SongStructure { - /// Size of a single entry, always 1. + /// Size of a single entry, always 24. + #[br(assert(len_entry_bytes == 24))] len_entry_bytes: u32, /// Number of entries in this section. len_entries: u16, @@ -950,347 +700,66 @@ pub enum Content { /// Unknown field. unknown4: u8, /// Phrase entry data. + #[br(count = len_entries)] data: Vec, }, /// Unknown content. + #[br(pre_assert(matches!(header.kind, ContentKind::Unknown(_))))] Unknown { /// Unknown header data. + #[br(count = header.remaining_size())] header_data: Vec, /// Unknown content data. + #[br(count = header.content_size())] content_data: Vec, }, } -impl Content { - fn parse<'a>(input: &'a [u8], header: &Header) -> IResult<&'a [u8], Self> { - match header.kind { - ContentKind::File => Err(nom_input_error_with_kind(input, ErrorKind::Tag)), - ContentKind::BeatGrid => Self::parse_beatgrid(input, header), - ContentKind::CueList => Self::parse_cuelist(input, header), - ContentKind::ExtendedCueList => Self::parse_extendedcuelist(input, header), - ContentKind::Path => Self::parse_path(input, header), - ContentKind::VBR => Self::parse_vbr(input, header), - ContentKind::WaveformPreview => Self::parse_waveform_preview(input, header), - ContentKind::TinyWaveformPreview => Self::parse_tiny_waveform_preview(input, header), - ContentKind::WaveformDetail => Self::parse_waveform_detail(input, header), - ContentKind::WaveformColorPreview => Self::parse_waveform_color_preview(input, header), - ContentKind::WaveformColorDetail => Self::parse_waveform_color_detail(input, header), - ContentKind::SongStructure => Self::parse_song_structure(input, header), - _ => Self::parse_unknown(input, header), - } - } - - fn parse_beatgrid<'a>(input: &'a [u8], _header: &Header) -> IResult<&'a [u8], Self> { - let (input, unknown1) = nom::number::complete::be_u32(input)?; - let (input, unknown2) = nom::number::complete::be_u32(input)?; - let (input, beats) = - nom::multi::length_count(nom::number::complete::be_u32, Beat::parse)(input)?; - - Ok(( - input, - Content::BeatGrid { - unknown1, - unknown2, - beats, - }, - )) - } - - fn parse_cuelist<'a>(input: &'a [u8], _header: &Header) -> IResult<&'a [u8], Self> { - let (input, list_type) = CueListType::parse(input)?; - let (input, unknown) = nom::number::complete::be_u16(input)?; - let (input, len_cues) = nom::number::complete::be_u16(input)?; - let len_cues = usize::try_from(len_cues) - .map_err(|_| nom_input_error_with_kind(input, ErrorKind::TooLarge))?; - let (input, memory_count) = nom::number::complete::be_u32(input)?; - let (input, cues) = nom::multi::count(Cue::parse, len_cues)(input)?; - - Ok(( - input, - Content::CueList { - list_type, - unknown, - memory_count, - cues, - }, - )) - } - - fn parse_extendedcuelist<'a>(input: &'a [u8], _header: &Header) -> IResult<&'a [u8], Self> { - let (input, list_type) = CueListType::parse(input)?; - let (input, len_cues) = nom::number::complete::be_u16(input)?; - let len_cues = usize::try_from(len_cues) - .map_err(|_| nom_input_error_with_kind(input, ErrorKind::TooLarge))?; - let (input, _) = nom::bytes::complete::tag(b"00")(input)?; - let (input, cues) = nom::multi::count(ExtendedCue::parse, len_cues)(input)?; - - Ok((input, Content::ExtendedCueList { list_type, cues })) - } - - fn parse_path<'a>(input: &'a [u8], _header: &Header) -> IResult<&'a [u8], Self> { - let (input, len_path) = nom::number::complete::be_u32(input)?; - let len_path = usize::try_from(len_path) - .map_err(|_| nom_input_error_with_kind(input, ErrorKind::TooLarge))?; - - let str_length = len_path / 2 - 1; - let (input, str_data) = - nom::multi::count(nom::number::complete::be_u16, str_length)(input)?; - let (input, _) = nom::bytes::complete::tag(b"\x00\x00")(input)?; - let path = String::from_utf16(&str_data) - .map_err(|_| nom_input_error_with_kind(input, ErrorKind::Char))?; - - Ok((input, Content::Path(path))) - } - - fn parse_vbr<'a>(input: &'a [u8], header: &Header) -> IResult<&'a [u8], Self> { - let (input, unknown1) = nom::number::complete::be_u32(input)?; - let (input, content_data_slice) = nom::bytes::complete::take(header.content_size())(input)?; - let unknown2: Vec = content_data_slice.to_owned(); - - Ok((input, Content::VBR { unknown1, unknown2 })) - } - - fn parse_waveform_preview<'a>(input: &'a [u8], header: &Header) -> IResult<&'a [u8], Self> { - let (input, len_preview) = nom::number::complete::be_u32(input)?; - let (input, unknown) = nom::number::complete::be_u32(input)?; - let (input, content_data_slice) = nom::bytes::complete::take(header.content_size())(input)?; - let data: Vec = content_data_slice - .iter() - .cloned() - .map(WaveformPreviewColumn::from) - .collect(); - - Ok(( - input, - Content::WaveformPreview { - len_preview, - unknown, - data, - }, - )) - } - - fn parse_tiny_waveform_preview<'a>( - input: &'a [u8], - header: &Header, - ) -> IResult<&'a [u8], Self> { - let (input, len_preview) = nom::number::complete::be_u32(input)?; - let (input, unknown) = nom::number::complete::be_u32(input)?; - let (input, content_data_slice) = nom::bytes::complete::take(header.content_size())(input)?; - let data: Vec = content_data_slice - .iter() - .cloned() - .map(TinyWaveformPreviewColumn::from) - .collect(); - - Ok(( - input, - Content::TinyWaveformPreview { - len_preview, - unknown, - data, - }, - )) - } - - fn parse_waveform_detail<'a>(input: &'a [u8], header: &Header) -> IResult<&'a [u8], Self> { - let (input, len_entry_bytes) = nom::number::complete::be_u32(input)?; - // All waveform detail entries should be 1 byte long. If we see other values here, - // some reverse-engineering is needed. - if len_entry_bytes != 2 { - return Err(nom_input_error_with_kind(input, ErrorKind::LengthValue)); - } - - let (input, len_entries) = nom::number::complete::be_u32(input)?; - let (input, unknown) = nom::number::complete::be_u32(input)?; - let (input, content_data_slice) = nom::bytes::complete::take(header.content_size())(input)?; - let data: Vec = content_data_slice - .iter() - .cloned() - .map(WaveformPreviewColumn::from) - .collect(); - - Ok(( - input, - Content::WaveformDetail { - len_entry_bytes, - len_entries, - unknown, - data, - }, - )) - } - - fn parse_waveform_color_preview<'a>( - input: &'a [u8], - _header: &Header, - ) -> IResult<&'a [u8], Self> { - let (input, len_entry_bytes) = nom::number::complete::be_u32(input)?; - // All waveform color preview entries should be 6 bytes long. If we see other values here, - // some reverse-engineering is needed. - if len_entry_bytes != 2 { - return Err(nom_input_error_with_kind(input, ErrorKind::LengthValue)); - } - - let (input, len_entries) = nom::number::complete::be_u32(input)?; - let entry_count = usize::try_from(len_entries) - .map_err(|_| nom_input_error_with_kind(input, ErrorKind::TooLarge))?; - - let (input, unknown) = nom::number::complete::be_u32(input)?; - let (input, data) = - nom::multi::count(WaveformColorPreviewColumn::parse, entry_count)(input)?; - - Ok(( - input, - Content::WaveformColorPreview { - len_entry_bytes, - len_entries, - unknown, - data, - }, - )) - } - - fn parse_waveform_color_detail<'a>( - input: &'a [u8], - _header: &Header, - ) -> IResult<&'a [u8], Self> { - let (input, len_entry_bytes) = nom::number::complete::be_u32(input)?; - // All waveform color detail entries should be 2 bytes long. If we see other values here, - // some reverse-engineering is needed. - if len_entry_bytes != 2 { - return Err(nom_input_error_with_kind(input, ErrorKind::LengthValue)); - } - - let (input, len_entries) = nom::number::complete::be_u32(input)?; - let entry_count = usize::try_from(len_entries) - .map_err(|_| nom_input_error_with_kind(input, ErrorKind::TooLarge))?; - - let (input, unknown) = nom::number::complete::be_u32(input)?; - let (input, data) = - nom::multi::count(WaveformColorDetailColumn::parse, entry_count)(input)?; - - Ok(( - input, - Content::WaveformColorDetail { - len_entry_bytes, - len_entries, - unknown, - data, - }, - )) - } - - fn parse_song_structure<'a>(input: &'a [u8], _header: &Header) -> IResult<&'a [u8], Self> { - let (input, len_entry_bytes) = nom::number::complete::be_u32(input)?; - // All phrase entries should be 24 bytes long. If we see other values here, some - // reverse-engineering is needed. - if len_entry_bytes != 24 { - return Err(nom_input_error_with_kind(input, ErrorKind::LengthValue)); - } - - let (input, len_entries) = nom::number::complete::be_u16(input)?; - let entry_count = usize::try_from(len_entries) - .map_err(|_| nom_input_error_with_kind(input, ErrorKind::TooLarge))?; - - let (input, mood) = Mood::parse(input)?; - let (input, unknown1) = nom::number::complete::be_u32(input)?; - let (input, unknown2) = nom::number::complete::be_u16(input)?; - let (input, end_beat) = nom::number::complete::be_u16(input)?; - let (input, unknown3) = nom::number::complete::be_u16(input)?; - let (input, bank) = Bank::parse(input)?; - let (input, unknown4) = nom::number::complete::u8(input)?; - let (input, data) = nom::multi::count(Phrase::parse, entry_count)(input)?; - - Ok(( - input, - Content::SongStructure { - len_entry_bytes, - len_entries, - mood, - unknown1, - unknown2, - end_beat, - unknown3, - bank, - unknown4, - data, - }, - )) - } - - fn parse_unknown<'a>(input: &'a [u8], header: &Header) -> IResult<&'a [u8], Self> { - let (input, header_data_slice) = - nom::bytes::complete::take(header.remaining_size())(input)?; - let header_data: Vec = header_data_slice.to_owned(); - - let (input, content_data_slice) = nom::bytes::complete::take(header.content_size())(input)?; - let content_data: Vec = content_data_slice.to_owned(); - - Ok(( - input, - Content::Unknown { - header_data, - content_data, - }, - )) - } -} - -#[derive(Debug)] /// ANLZ Section. +#[derive(Debug, PartialEq)] +#[binread] pub struct Section { /// The header. pub header: Header, /// The section content. + #[br(args(header.clone()))] pub content: Content, } -impl Section { - fn parse(input: &[u8]) -> IResult<&[u8], Self> { - let (input, header) = Header::parse(input)?; - let (input, content) = Content::parse(input, &header)?; - Ok((input, Self { header, content })) - } -} - -#[derive(Debug)] /// ANLZ file section. /// /// The actual contents are not part of this struct and can parsed on-the-fly by iterating over the /// `ANLZ::sections()` method. +#[derive(Debug, PartialEq)] +#[binread] +#[br(big)] pub struct ANLZ { /// The file header. + #[br(assert(header.kind == ContentKind::File))] pub header: Header, /// The header data. + #[br(count = header.remaining_size())] pub header_data: Vec, + /// The content sections. + #[br(parse_with = Self::parse_sections, args(header.content_size()))] + pub sections: Vec
, } impl ANLZ { - /// Parses the ANLZ header and returns the `ANLZ` structure. - pub fn parse(input: &[u8]) -> IResult<&[u8], Self> { - let (input, _) = nom::combinator::peek(nom::bytes::complete::tag(b"PMAI"))(input)?; - let (input, header) = Header::parse(input)?; - let (input, data) = nom::bytes::complete::take(header.remaining_size())(input)?; - let header_data = data.to_owned(); - - Ok(( - input, - Self { - header, - header_data, - }, - )) - } + fn parse_sections( + reader: &mut R, + ro: &ReadOptions, + args: (u32,), + ) -> BinResult> { + let (content_size,) = args; + let final_position = reader.stream_position()? + u64::from(content_size); + + let mut sections: Vec
= vec![]; + while reader.stream_position()? < final_position { + let section = Section::read_options(reader, ro, ())?; + sections.push(section); + } - /// Iterates over the file sections. - pub fn sections<'a>(&self, input: &'a [u8]) -> impl Iterator + 'a { - (0..).scan(input, |input, _| match Section::parse(input) { - Ok((remaining_input, section)) => { - *input = remaining_input; - Some(section) - } - Err(_) => None, - }) + Ok(sections) } } diff --git a/src/bin/rekordcrate-anlz.rs b/src/bin/rekordcrate-anlz.rs index 23aba67..dd528d2 100644 --- a/src/bin/rekordcrate-anlz.rs +++ b/src/bin/rekordcrate-anlz.rs @@ -6,14 +6,12 @@ // // SPDX-License-Identifier: MPL-2.0 +use binrw::BinRead; use rekordcrate::anlz::ANLZ; fn main() { let path = std::env::args().nth(1).expect("no path given"); - let data = std::fs::read(&path).expect("failed to read file"); - let (input, anlz) = ANLZ::parse(&data).expect("failed to parse header"); - println!("File Header: {:#?}", anlz); - for (i, section) in anlz.sections(input).enumerate() { - println!("#{}: {:#?}", i, section); - } + let mut reader = std::fs::File::open(&path).expect("failed to open file"); + let anlz = ANLZ::read(&mut reader).expect("failed to parse setting file"); + println!("{:#?}", anlz); } diff --git a/src/util.rs b/src/util.rs index 8914451..cbb8622 100644 --- a/src/util.rs +++ b/src/util.rs @@ -8,6 +8,7 @@ //! Common types used in multiple modules. +use binrw::binread; use nom::error::{ErrorKind, ParseError}; use nom::Err; use nom::IResult; @@ -18,26 +19,36 @@ pub fn nom_input_error_with_kind(input: &[u8], kind: ErrorKind) -> Err Date: Fri, 25 Feb 2022 00:53:12 +0100 Subject: [PATCH 2/5] anlz: Split Content enum into separate structs --- src/anlz.rs | 368 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 222 insertions(+), 146 deletions(-) diff --git a/src/anlz.rs b/src/anlz.rs index e05ee2e..7879e4d 100644 --- a/src/anlz.rs +++ b/src/anlz.rs @@ -531,188 +531,264 @@ pub struct Phrase { /// Section content which differs depending on the section type. #[derive(Debug, PartialEq)] #[binread] -#[br(big, import(header: Header))] +#[br(import(header: Header))] pub enum Content { /// All beats in the track. #[br(pre_assert(header.kind == ContentKind::BeatGrid))] - BeatGrid { - /// Unknown field. - unknown1: u32, - /// Unknown field. - /// - /// According to [@flesniak](https://github.com/flesniak), this is always `00800000`. - unknown2: u32, - /// Number of beats in this beatgrid. - len_beats: u32, - /// Beats in this beatgrid. - #[br(count = len_beats)] - beats: Vec, - }, + BeatGrid(BeatGrid), /// List of cue points or loops (either hot cues or memory cues). #[br(pre_assert(header.kind == ContentKind::CueList))] - CueList { - /// The types of cues (memory or hot) that this list contains. - list_type: CueListType, - /// Unknown field - unknown: u16, - /// Number of cues. - len_cues: u16, - /// Unknown field. - memory_count: u32, - /// Cues - #[br(count = len_cues)] - cues: Vec, - }, + CueList(CueList), /// List of cue points or loops (either hot cues or memory cues, extended version). /// /// Variation of the original `CueList` that also adds support for more metadata such as /// comments and colors. Introduces with the Nexus 2 series players. #[br(pre_assert(header.kind == ContentKind::ExtendedCueList))] - ExtendedCueList { - /// The types of cues (memory or hot) that this list contains. - list_type: CueListType, - /// Number of cues. - len_cues: u16, - /// Unknown field - #[br(assert(unknown == 0))] - unknown: u16, - /// Cues - #[br(count = len_cues)] - cues: Vec, - }, + ExtendedCueList(ExtendedCueList), /// Path of the audio file that this analysis belongs to. #[br(pre_assert(header.kind == ContentKind::Path))] - Path { - /// Length of the path field in bytes. - len_path: u32, - #[br(assert(len_path == header.content_size()), count = len_path)] - /// Path of the audio file. - path: Vec, - }, + Path(#[br(args(header.clone()))] Path), /// Seek information for variable bitrate files (probably). #[br(pre_assert(header.kind == ContentKind::VBR))] - VBR { - /// Unknown field. - unknown1: u32, - /// Unknown data. - #[br(count = header.content_size())] - unknown2: Vec, - }, + VBR(#[br(args(header.clone()))] VBR), /// Fixed-width monochrome preview of the track waveform. #[br(pre_assert(header.kind == ContentKind::WaveformPreview))] - WaveformPreview { - /// Unknown field. - len_preview: u32, - /// Unknown field (apparently always `0x00100000`) - unknown: u32, - /// Waveform preview column data. - #[br(count = header.content_size())] - data: Vec, - }, + WaveformPreview(#[br(args(header.clone()))] WaveformPreview), /// Smaller version of the fixed-width monochrome preview of the track waveform. #[br(pre_assert(header.kind == ContentKind::TinyWaveformPreview))] - TinyWaveformPreview { - /// Unknown field. - len_preview: u32, - /// Unknown field (apparently always `0x00100000`) - unknown: u32, - /// Waveform preview column data. - #[br(count = header.content_size())] - data: Vec, - }, + TinyWaveformPreview(#[br(args(header.clone()))] TinyWaveformPreview), /// Variable-width large monochrome version of the track waveform. /// /// Used in `.EXT` files. #[br(pre_assert(header.kind == ContentKind::WaveformDetail))] - WaveformDetail { - /// Size of a single entry, always 1. - #[br(assert(len_entry_bytes == 1))] - len_entry_bytes: u32, - /// Number of entries in this section. - len_entries: u32, - /// Unknown field (apparently always `0x00960000`) - #[br(assert(unknown == 0x00960000))] - unknown: u32, - /// Waveform preview column data. - /// - /// Each entry represents one half-frame of audio data, and there are 75 frames per second, - /// so for each second of track audio there are 150 waveform detail entries. - #[br(count = len_entries)] - data: Vec, - }, + WaveformDetail(WaveformDetail), /// Variable-width large monochrome version of the track waveform. /// /// Used in `.EXT` files. #[br(pre_assert(header.kind == ContentKind::WaveformColorPreview))] - WaveformColorPreview { - /// Size of a single entry, always 6. - #[br(assert(len_entry_bytes == 6))] - len_entry_bytes: u32, - /// Number of entries in this section. - len_entries: u32, - /// Unknown field. - unknown: u32, - /// Waveform preview column data. - /// - /// Each entry represents one half-frame of audio data, and there are 75 frames per second, - /// so for each second of track audio there are 150 waveform detail entries. - #[br(count = len_entries)] - data: Vec, - }, + WaveformColorPreview(WaveformColorPreview), /// Variable-width large colored version of the track waveform. /// /// Used in `.EXT` files. #[br(pre_assert(header.kind == ContentKind::WaveformColorDetail))] - WaveformColorDetail { - /// Size of a single entry, always 2. - #[br(assert(len_entry_bytes == 2))] - len_entry_bytes: u32, - /// Number of entries in this section. - len_entries: u32, - /// Unknown field. - unknown: u32, - /// Waveform detail column data. - #[br(count = len_entries)] - data: Vec, - }, + WaveformColorDetail(WaveformColorDetail), /// Describes the structure of a sond (Intro, Chrous, Verse, etc.). /// /// Used in `.EXT` files. #[br(pre_assert(header.kind == ContentKind::SongStructure))] - SongStructure { - /// Size of a single entry, always 24. - #[br(assert(len_entry_bytes == 24))] - len_entry_bytes: u32, - /// Number of entries in this section. - len_entries: u16, - /// Overall type of phrase structure. - mood: Mood, - /// Unknown field. - unknown1: u32, - /// Unknown field. - unknown2: u16, - /// Number of the beat at which the last recognized phrase ends. - end_beat: u16, - /// Unknown field. - unknown3: u16, - /// Stylistic bank assigned in Lightning Mode. - bank: Bank, - /// Unknown field. - unknown4: u8, - /// Phrase entry data. - #[br(count = len_entries)] - data: Vec, - }, + SongStructure(SongStructure), /// Unknown content. #[br(pre_assert(matches!(header.kind, ContentKind::Unknown(_))))] - Unknown { - /// Unknown header data. - #[br(count = header.remaining_size())] - header_data: Vec, - /// Unknown content data. - #[br(count = header.content_size())] - content_data: Vec, - }, + Unknown(#[br(args(header.clone()))] Unknown), +} + +/// All beats in the track. +#[derive(Debug, PartialEq)] +#[binread] +pub struct BeatGrid { + /// Unknown field. + unknown1: u32, + /// Unknown field. + /// + /// According to [@flesniak](https://github.com/flesniak), this is always `00800000`. + unknown2: u32, + /// Number of beats in this beatgrid. + len_beats: u32, + /// Beats in this beatgrid. + #[br(count = len_beats)] + pub beats: Vec, +} + +/// List of cue points or loops (either hot cues or memory cues). +#[derive(Debug, PartialEq)] +#[binread] +pub struct CueList { + /// The types of cues (memory or hot) that this list contains. + pub list_type: CueListType, + /// Unknown field + unknown: u16, + /// Number of cues. + len_cues: u16, + /// Unknown field. + memory_count: u32, + /// Cues + #[br(count = len_cues)] + pub cues: Vec, +} + +/// List of cue points or loops (either hot cues or memory cues, extended version). +/// +/// Variation of the original `CueList` that also adds support for more metadata such as +/// comments and colors. Introduces with the Nexus 2 series players. +#[derive(Debug, PartialEq)] +#[binread] +pub struct ExtendedCueList { + /// The types of cues (memory or hot) that this list contains. + pub list_type: CueListType, + /// Number of cues. + len_cues: u16, + /// Unknown field + #[br(assert(unknown == 0))] + unknown: u16, + /// Cues + #[br(count = len_cues)] + pub cues: Vec, +} + +/// Path of the audio file that this analysis belongs to. +#[derive(Debug, PartialEq)] +#[binread] +#[br(import(header: Header))] +pub struct Path { + /// Length of the path field in bytes. + len_path: u32, + #[br(assert(len_path == header.content_size()), count = len_path)] + /// Path of the audio file. + pub path: Vec, +} + +/// Seek information for variable bitrate files (probably). +#[derive(Debug, PartialEq)] +#[binread] +#[br(import(header: Header))] +pub struct VBR { + /// Unknown field. + unknown1: u32, + /// Unknown data. + #[br(count = header.content_size())] + unknown2: Vec, +} + +/// Fixed-width monochrome preview of the track waveform. +#[derive(Debug, PartialEq)] +#[binread] +#[br(import(header: Header))] +pub struct WaveformPreview { + /// Unknown field. + len_preview: u32, + /// Unknown field (apparently always `0x00100000`) + unknown: u32, + /// Waveform preview column data. + #[br(count = header.content_size())] + pub data: Vec, +} + +/// Smaller version of the fixed-width monochrome preview of the track waveform. +#[derive(Debug, PartialEq)] +#[binread] +#[br(import(header: Header))] +pub struct TinyWaveformPreview { + /// Unknown field. + len_preview: u32, + /// Unknown field (apparently always `0x00100000`) + unknown: u32, + /// Waveform preview column data. + #[br(count = header.content_size())] + pub data: Vec, +} + +/// Variable-width large monochrome version of the track waveform. +/// +/// Used in `.EXT` files. +#[derive(Debug, PartialEq)] +#[binread] +pub struct WaveformDetail { + /// Size of a single entry, always 1. + #[br(assert(len_entry_bytes == 1))] + len_entry_bytes: u32, + /// Number of entries in this section. + len_entries: u32, + /// Unknown field (apparently always `0x00960000`) + #[br(assert(unknown == 0x00960000))] + unknown: u32, + /// Waveform preview column data. + /// + /// Each entry represents one half-frame of audio data, and there are 75 frames per second, + /// so for each second of track audio there are 150 waveform detail entries. + #[br(count = len_entries)] + pub data: Vec, +} + +/// Variable-width large monochrome version of the track waveform. +/// +/// Used in `.EXT` files. +#[derive(Debug, PartialEq)] +#[binread] +pub struct WaveformColorPreview { + /// Size of a single entry, always 6. + #[br(assert(len_entry_bytes == 6))] + len_entry_bytes: u32, + /// Number of entries in this section. + len_entries: u32, + /// Unknown field. + unknown: u32, + /// Waveform preview column data. + /// + /// Each entry represents one half-frame of audio data, and there are 75 frames per second, + /// so for each second of track audio there are 150 waveform detail entries. + #[br(count = len_entries)] + pub data: Vec, +} + +/// Variable-width large colored version of the track waveform. +/// +/// Used in `.EXT` files. +#[derive(Debug, PartialEq)] +#[binread] +pub struct WaveformColorDetail { + /// Size of a single entry, always 2. + #[br(assert(len_entry_bytes == 2))] + len_entry_bytes: u32, + /// Number of entries in this section. + len_entries: u32, + /// Unknown field. + unknown: u32, + /// Waveform detail column data. + #[br(count = len_entries)] + pub data: Vec, +} + +/// Describes the structure of a sond (Intro, Chrous, Verse, etc.). +/// +/// Used in `.EXT` files. +#[derive(Debug, PartialEq)] +#[binread] +pub struct SongStructure { + /// Size of a single entry, always 24. + #[br(assert(len_entry_bytes == 24))] + len_entry_bytes: u32, + /// Number of entries in this section. + len_entries: u16, + /// Overall type of phrase structure. + pub mood: Mood, + /// Unknown field. + unknown1: u32, + /// Unknown field. + unknown2: u16, + /// Number of the beat at which the last recognized phrase ends. + pub end_beat: u16, + /// Unknown field. + unknown3: u16, + /// Stylistic bank assigned in Lightning Mode. + pub bank: Bank, + /// Unknown field. + unknown4: u8, + /// Phrase entry data. + #[br(count = len_entries)] + pub data: Vec, +} + +/// Unknown content. +#[derive(Debug, PartialEq)] +#[binread] +#[br(import(header: Header))] +pub struct Unknown { + /// Unknown header data. + #[br(count = header.remaining_size())] + header_data: Vec, + /// Unknown content data. + #[br(count = header.content_size())] + content_data: Vec, } /// ANLZ Section. From 30d7e4b703de98e651d7bf3e9391246afdefcc22 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Fri, 25 Feb 2022 10:30:26 +0100 Subject: [PATCH 3/5] anlz: Use `NullWideString` for `UTF16-BE` strings instead of `Vec` --- src/anlz.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/anlz.rs b/src/anlz.rs index 7879e4d..c6bc287 100644 --- a/src/anlz.rs +++ b/src/anlz.rs @@ -30,7 +30,7 @@ use crate::util::ColorIndex; use binrw::{ binread, io::{Read, Seek}, - BinRead, BinResult, ReadOptions, + BinRead, BinResult, NullWideString, ReadOptions, }; use modular_bitfield::prelude::*; @@ -264,8 +264,8 @@ pub struct ExtendedCue { /// Length of the comment string in bytes. pub len_comment: u32, /// An UTF-16BE encoded string, followed by a trailing `0x0000`. - #[br(count = len_comment)] - pub comment: Vec, + #[br(assert((comment.len() as u32 + 1) * 2 == len_comment))] + pub comment: NullWideString, /// Rekordbox hotcue color index. /// /// | Value | Color | @@ -642,9 +642,10 @@ pub struct ExtendedCueList { pub struct Path { /// Length of the path field in bytes. len_path: u32, - #[br(assert(len_path == header.content_size()), count = len_path)] + #[br(assert(len_path == header.content_size()))] /// Path of the audio file. - pub path: Vec, + #[br(assert((path.len() as u32 + 1) * 2 == len_path))] + pub path: NullWideString, } /// Seek information for variable bitrate files (probably). From f0d4ad0343e0ef0c57f36ba6f20f884d2403500e Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Fri, 25 Feb 2022 10:54:37 +0100 Subject: [PATCH 4/5] anlz: Add basic serialization support --- src/anlz.rs | 163 +++++++++++++++++++++-------------------- src/util.rs | 22 +++--- tests/tests_anlz.rs.in | 5 ++ 3 files changed, 99 insertions(+), 91 deletions(-) diff --git a/src/anlz.rs b/src/anlz.rs index c6bc287..7652527 100644 --- a/src/anlz.rs +++ b/src/anlz.rs @@ -28,70 +28,70 @@ use crate::util::ColorIndex; use binrw::{ - binread, + binrw, io::{Read, Seek}, - BinRead, BinResult, NullWideString, ReadOptions, + BinRead, BinResult, BinWrite, NullWideString, ReadOptions, }; use modular_bitfield::prelude::*; /// The kind of section. #[derive(Debug, PartialEq, Clone)] -#[binread] -#[br(big)] +#[binrw] +#[brw(big)] pub enum ContentKind { /// File section that contains all other sections. - #[br(magic = b"PMAI")] + #[brw(magic = b"PMAI")] File, /// All beats found in the track. - #[br(magic = b"PQTZ")] + #[brw(magic = b"PQTZ")] BeatGrid, /// Either memory points and loops or hotcues and hot loops of the track. /// /// *Note:* Since the release of the Nexus 2 series, there also exists the `ExtendedCueList` /// section which can carry additional information. - #[br(magic = b"PCOB")] + #[brw(magic = b"PCOB")] CueList, /// Extended version of the `CueList` section (since Nexus 2 series). - #[br(magic = b"PCO2")] + #[brw(magic = b"PCO2")] ExtendedCueList, /// Single cue entry inside a `ExtendedCueList` section. - #[br(magic = b"PCP2")] + #[brw(magic = b"PCP2")] ExtendedCue, /// Single cue entry inside a `CueList` section. - #[br(magic = b"PCPT")] + #[brw(magic = b"PCPT")] Cue, /// File path of the audio file. - #[br(magic = b"PPTH")] + #[brw(magic = b"PPTH")] Path, /// Seek information for variable bitrate files. - #[br(magic = b"PVBR")] + #[brw(magic = b"PVBR")] VBR, /// Fixed-width monochrome preview of the track waveform. - #[br(magic = b"PWAV")] + #[brw(magic = b"PWAV")] WaveformPreview, /// Smaller version of the fixed-width monochrome preview of the track waveform (for the /// CDJ-900). - #[br(magic = b"PWV2")] + #[brw(magic = b"PWV2")] TinyWaveformPreview, /// Variable-width large monochrome version of the track waveform. /// /// Used in `.EXT` files. - #[br(magic = b"PWV3")] + #[brw(magic = b"PWV3")] WaveformDetail, /// Fixed-width colored version of the track waveform. /// /// Used in `.EXT` files. - #[br(magic = b"PWV4")] + #[brw(magic = b"PWV4")] WaveformColorPreview, /// Variable-width large colored version of the track waveform. /// /// Used in `.EXT` files. - #[br(magic = b"PWV5")] + #[brw(magic = b"PWV5")] WaveformColorDetail, /// Describes the structure of a sond (Intro, Chrous, Verse, etc.). /// /// Used in `.EXT` files. - #[br(magic = b"PSSI")] + #[brw(magic = b"PSSI")] SongStructure, /// Unknown Kind. Unknown([u8; 4]), @@ -99,8 +99,8 @@ pub enum ContentKind { /// Header of a section that contains type and size information. #[derive(Debug, PartialEq, Clone)] -#[binread] -#[br(big)] +#[binrw] +#[brw(big)] pub struct Header { /// Kind of content in this item. pub kind: ContentKind, @@ -122,8 +122,8 @@ impl Header { /// A single beat inside the beat grid. #[derive(Debug, PartialEq)] -#[binread] -#[br(big)] +#[binrw] +#[brw(big)] pub struct Beat { /// Beat number inside the bar (1-4). pub beat_number: u16, @@ -135,8 +135,8 @@ pub struct Beat { /// Describes the types of entries found in a Cue List section. #[derive(Debug, PartialEq)] -#[binread] -#[br(big, repr = u32)] +#[binrw] +#[brw(big, repr = u32)] pub enum CueListType { /// Memory cues or loops. MemoryCues = 0, @@ -146,8 +146,8 @@ pub enum CueListType { /// Indicates if the cue is point or a loop. #[derive(Debug, PartialEq)] -#[binread] -#[br(repr = u8)] +#[binrw] +#[brw(repr = u8)] pub enum CueType { /// Cue is a single point. Point = 0, @@ -157,8 +157,8 @@ pub enum CueType { /// A memory or hot cue (or loop). #[derive(Debug, PartialEq)] -#[binread] -#[br(big)] +#[binrw] +#[brw(big)] pub struct Cue { /// Cue entry header. pub header: Header, @@ -223,8 +223,8 @@ pub struct Cue { /// A memory or hot cue (or loop). #[derive(Debug, PartialEq)] -#[binread] -#[br(big)] +#[binrw] +#[brw(big)] pub struct ExtendedCue { /// Cue entry header. pub header: Header, @@ -354,8 +354,9 @@ pub struct ExtendedCue { /// Single Column value in a Waveform Preview. #[bitfield] -#[derive(BinRead, Debug, PartialEq)] +#[derive(BinRead, BinWrite, Debug, PartialEq, Clone, Copy)] #[br(big, map = Self::from_bytes)] +#[bw(big, map = |x: &WaveformPreviewColumn| x.into_bytes())] pub struct WaveformPreviewColumn { /// Height of the Column in pixels. pub height: B5, @@ -365,8 +366,9 @@ pub struct WaveformPreviewColumn { /// Single Column value in a Tiny Waveform Preview. #[bitfield] -#[derive(BinRead, Debug, PartialEq)] +#[derive(BinRead, BinWrite, Debug, PartialEq, Clone, Copy)] #[br(big, map = Self::from_bytes)] +#[bw(big, map = |x: &TinyWaveformPreviewColumn| x.into_bytes())] pub struct TinyWaveformPreviewColumn { /// Height of the Column in pixels. pub unused: B4, @@ -378,8 +380,8 @@ pub struct TinyWaveformPreviewColumn { /// See these the documentation for details: /// #[derive(Debug, PartialEq)] -#[binread] -#[br(big)] +#[binrw] +#[brw(big)] pub struct WaveformColorPreviewColumn { /// Unknown field (somehow encodes the "whiteness"). pub unknown1: u8, @@ -397,8 +399,9 @@ pub struct WaveformColorPreviewColumn { /// Single Column value in a Waveform Color Detail section. #[bitfield] -#[derive(BinRead, Debug, PartialEq)] +#[derive(BinRead, BinWrite, Debug, PartialEq, Clone, Copy)] #[br(map = Self::from_bytes)] +#[bw(big, map = |x: &WaveformColorDetailColumn| x.into_bytes())] pub struct WaveformColorDetailColumn { /// Red color component. pub red: B3, @@ -415,23 +418,23 @@ pub struct WaveformColorDetailColumn { /// Music classification that is used for Lightnight mode and based on rhythm, tempo kick drum and /// sound density. #[derive(Debug, PartialEq)] -#[binread] -#[br(big)] +#[binrw] +#[brw(big)] pub enum Mood { /// Phrase types consist of "Intro", "Up", "Down", "Chorus", and "Outro". Other values in each /// phrase entry cause the intro, chorus, and outro phrases to have their labels subdivided /// into styes "1" or "2" (for example, "Intro 1"), and "up" is subdivided into style "Up 1", /// "Up 2", or "Up 3". - #[br(magic = 1u16)] + #[brw(magic = 1u16)] High, /// Phrase types are labeled "Intro", "Verse 1" through "Verse 6", "Chorus", "Bridge", and /// "Outro". - #[br(magic = 2u16)] + #[brw(magic = 2u16)] Mid, /// Phrase types are labeled "Intro", "Verse 1", "Verse 2", "Chorus", "Bridge", and "Outro". /// There are three different phrase type values for each of "Verse 1" and "Verse 2", but /// rekordbox makes no distinction between them. - #[br(magic = 3u16)] + #[brw(magic = 3u16)] Low, /// Unknown value. Unknown(u16), @@ -439,34 +442,34 @@ pub enum Mood { /// Stylistic track bank for Lightning mode. #[derive(Debug, PartialEq)] -#[binread] +#[binrw] pub enum Bank { /// Default bank variant, treated as `Cool`. - #[br(magic = 0u8)] + #[brw(magic = 0u8)] Default, /// "Cool" bank variant. - #[br(magic = 1u8)] + #[brw(magic = 1u8)] Cool, /// "Natural" bank variant. - #[br(magic = 2u8)] + #[brw(magic = 2u8)] Natural, /// "Hot" bank variant. - #[br(magic = 3u8)] + #[brw(magic = 3u8)] Hot, /// "Subtle" bank variant. - #[br(magic = 4u8)] + #[brw(magic = 4u8)] Subtle, /// "Warm" bank variant. - #[br(magic = 5u8)] + #[brw(magic = 5u8)] Warm, /// "Vivid" bank variant. - #[br(magic = 6u8)] + #[brw(magic = 6u8)] Vivid, /// "Club 1" bank variant. - #[br(magic = 7u8)] + #[brw(magic = 7u8)] Club1, /// "Club 2" bank variant. - #[br(magic = 8u8)] + #[brw(magic = 8u8)] Club2, /// Unknown value. Unknown(u8), @@ -474,8 +477,8 @@ pub enum Bank { /// A song structure entry that represents a phrase in the track. #[derive(Debug, PartialEq)] -#[binread] -#[br(big)] +#[binrw] +#[brw(big)] pub struct Phrase { /// Phrase number (starting at 1). pub index: u16, @@ -530,61 +533,61 @@ pub struct Phrase { /// Section content which differs depending on the section type. #[derive(Debug, PartialEq)] -#[binread] +#[binrw] #[br(import(header: Header))] pub enum Content { /// All beats in the track. - #[br(pre_assert(header.kind == ContentKind::BeatGrid))] + #[brw(pre_assert(header.kind == ContentKind::BeatGrid))] BeatGrid(BeatGrid), /// List of cue points or loops (either hot cues or memory cues). - #[br(pre_assert(header.kind == ContentKind::CueList))] + #[brw(pre_assert(header.kind == ContentKind::CueList))] CueList(CueList), /// List of cue points or loops (either hot cues or memory cues, extended version). /// /// Variation of the original `CueList` that also adds support for more metadata such as /// comments and colors. Introduces with the Nexus 2 series players. - #[br(pre_assert(header.kind == ContentKind::ExtendedCueList))] + #[brw(pre_assert(header.kind == ContentKind::ExtendedCueList))] ExtendedCueList(ExtendedCueList), /// Path of the audio file that this analysis belongs to. - #[br(pre_assert(header.kind == ContentKind::Path))] + #[brw(pre_assert(header.kind == ContentKind::Path))] Path(#[br(args(header.clone()))] Path), /// Seek information for variable bitrate files (probably). - #[br(pre_assert(header.kind == ContentKind::VBR))] + #[brw(pre_assert(header.kind == ContentKind::VBR))] VBR(#[br(args(header.clone()))] VBR), /// Fixed-width monochrome preview of the track waveform. - #[br(pre_assert(header.kind == ContentKind::WaveformPreview))] + #[brw(pre_assert(header.kind == ContentKind::WaveformPreview))] WaveformPreview(#[br(args(header.clone()))] WaveformPreview), /// Smaller version of the fixed-width monochrome preview of the track waveform. - #[br(pre_assert(header.kind == ContentKind::TinyWaveformPreview))] + #[brw(pre_assert(header.kind == ContentKind::TinyWaveformPreview))] TinyWaveformPreview(#[br(args(header.clone()))] TinyWaveformPreview), /// Variable-width large monochrome version of the track waveform. /// /// Used in `.EXT` files. - #[br(pre_assert(header.kind == ContentKind::WaveformDetail))] + #[brw(pre_assert(header.kind == ContentKind::WaveformDetail))] WaveformDetail(WaveformDetail), /// Variable-width large monochrome version of the track waveform. /// /// Used in `.EXT` files. - #[br(pre_assert(header.kind == ContentKind::WaveformColorPreview))] + #[brw(pre_assert(header.kind == ContentKind::WaveformColorPreview))] WaveformColorPreview(WaveformColorPreview), /// Variable-width large colored version of the track waveform. /// /// Used in `.EXT` files. - #[br(pre_assert(header.kind == ContentKind::WaveformColorDetail))] + #[brw(pre_assert(header.kind == ContentKind::WaveformColorDetail))] WaveformColorDetail(WaveformColorDetail), /// Describes the structure of a sond (Intro, Chrous, Verse, etc.). /// /// Used in `.EXT` files. - #[br(pre_assert(header.kind == ContentKind::SongStructure))] + #[brw(pre_assert(header.kind == ContentKind::SongStructure))] SongStructure(SongStructure), /// Unknown content. - #[br(pre_assert(matches!(header.kind, ContentKind::Unknown(_))))] + #[brw(pre_assert(matches!(header.kind, ContentKind::Unknown(_))))] Unknown(#[br(args(header.clone()))] Unknown), } /// All beats in the track. #[derive(Debug, PartialEq)] -#[binread] +#[binrw] pub struct BeatGrid { /// Unknown field. unknown1: u32, @@ -601,7 +604,7 @@ pub struct BeatGrid { /// List of cue points or loops (either hot cues or memory cues). #[derive(Debug, PartialEq)] -#[binread] +#[binrw] pub struct CueList { /// The types of cues (memory or hot) that this list contains. pub list_type: CueListType, @@ -621,7 +624,7 @@ pub struct CueList { /// Variation of the original `CueList` that also adds support for more metadata such as /// comments and colors. Introduces with the Nexus 2 series players. #[derive(Debug, PartialEq)] -#[binread] +#[binrw] pub struct ExtendedCueList { /// The types of cues (memory or hot) that this list contains. pub list_type: CueListType, @@ -637,7 +640,7 @@ pub struct ExtendedCueList { /// Path of the audio file that this analysis belongs to. #[derive(Debug, PartialEq)] -#[binread] +#[binrw] #[br(import(header: Header))] pub struct Path { /// Length of the path field in bytes. @@ -650,7 +653,7 @@ pub struct Path { /// Seek information for variable bitrate files (probably). #[derive(Debug, PartialEq)] -#[binread] +#[binrw] #[br(import(header: Header))] pub struct VBR { /// Unknown field. @@ -662,7 +665,7 @@ pub struct VBR { /// Fixed-width monochrome preview of the track waveform. #[derive(Debug, PartialEq)] -#[binread] +#[binrw] #[br(import(header: Header))] pub struct WaveformPreview { /// Unknown field. @@ -676,7 +679,7 @@ pub struct WaveformPreview { /// Smaller version of the fixed-width monochrome preview of the track waveform. #[derive(Debug, PartialEq)] -#[binread] +#[binrw] #[br(import(header: Header))] pub struct TinyWaveformPreview { /// Unknown field. @@ -692,7 +695,7 @@ pub struct TinyWaveformPreview { /// /// Used in `.EXT` files. #[derive(Debug, PartialEq)] -#[binread] +#[binrw] pub struct WaveformDetail { /// Size of a single entry, always 1. #[br(assert(len_entry_bytes == 1))] @@ -714,7 +717,7 @@ pub struct WaveformDetail { /// /// Used in `.EXT` files. #[derive(Debug, PartialEq)] -#[binread] +#[binrw] pub struct WaveformColorPreview { /// Size of a single entry, always 6. #[br(assert(len_entry_bytes == 6))] @@ -735,7 +738,7 @@ pub struct WaveformColorPreview { /// /// Used in `.EXT` files. #[derive(Debug, PartialEq)] -#[binread] +#[binrw] pub struct WaveformColorDetail { /// Size of a single entry, always 2. #[br(assert(len_entry_bytes == 2))] @@ -753,7 +756,7 @@ pub struct WaveformColorDetail { /// /// Used in `.EXT` files. #[derive(Debug, PartialEq)] -#[binread] +#[binrw] pub struct SongStructure { /// Size of a single entry, always 24. #[br(assert(len_entry_bytes == 24))] @@ -781,7 +784,7 @@ pub struct SongStructure { /// Unknown content. #[derive(Debug, PartialEq)] -#[binread] +#[binrw] #[br(import(header: Header))] pub struct Unknown { /// Unknown header data. @@ -794,7 +797,7 @@ pub struct Unknown { /// ANLZ Section. #[derive(Debug, PartialEq)] -#[binread] +#[binrw] pub struct Section { /// The header. pub header: Header, @@ -808,8 +811,8 @@ pub struct Section { /// The actual contents are not part of this struct and can parsed on-the-fly by iterating over the /// `ANLZ::sections()` method. #[derive(Debug, PartialEq)] -#[binread] -#[br(big)] +#[binrw] +#[brw(big)] pub struct ANLZ { /// The file header. #[br(assert(header.kind == ContentKind::File))] diff --git a/src/util.rs b/src/util.rs index cbb8622..5cdf750 100644 --- a/src/util.rs +++ b/src/util.rs @@ -8,7 +8,7 @@ //! Common types used in multiple modules. -use binrw::binread; +use binrw::binrw; use nom::error::{ErrorKind, ParseError}; use nom::Err; use nom::IResult; @@ -21,34 +21,34 @@ pub fn nom_input_error_with_kind(input: &[u8], kind: ErrorKind) -> Err Date: Fri, 25 Feb 2022 13:14:39 +0100 Subject: [PATCH 5/5] anlz: Explain why unknown values are allowed in some enums --- src/anlz.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/anlz.rs b/src/anlz.rs index 7652527..bdec912 100644 --- a/src/anlz.rs +++ b/src/anlz.rs @@ -94,6 +94,9 @@ pub enum ContentKind { #[brw(magic = b"PSSI")] SongStructure, /// Unknown Kind. + /// + /// This allows handling files that contain unknown section types and allows to access later + /// sections in the file that have a known type instead of failing to parse the whole file. Unknown([u8; 4]), } @@ -437,6 +440,8 @@ pub enum Mood { #[brw(magic = 3u16)] Low, /// Unknown value. + /// + /// TODO: Remove this once https://github.com/Holzhaus/rekordcrate/issues/13 has been fixed. Unknown(u16), } @@ -472,6 +477,8 @@ pub enum Bank { #[brw(magic = 8u8)] Club2, /// Unknown value. + /// + /// TODO: Remove this once https://github.com/Holzhaus/rekordcrate/issues/13 has been fixed. Unknown(u8), } @@ -581,6 +588,9 @@ pub enum Content { #[brw(pre_assert(header.kind == ContentKind::SongStructure))] SongStructure(SongStructure), /// Unknown content. + /// + /// This allows handling files that contain unknown section types and allows to access later + /// sections in the file that have a known type instead of failing to parse the whole file. #[brw(pre_assert(matches!(header.kind, ContentKind::Unknown(_))))] Unknown(#[br(args(header.clone()))] Unknown), }