diff options
-rw-r--r-- | src/cubase_project.rs | 7 | ||||
-rw-r--r-- | src/main.rs | 204 |
2 files changed, 111 insertions, 100 deletions
diff --git a/src/cubase_project.rs b/src/cubase_project.rs index 498936d..b58bbc9 100644 --- a/src/cubase_project.rs +++ b/src/cubase_project.rs @@ -18,15 +18,16 @@ /** * A struct to hold the information about a Cubase project. */ -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct CubaseProject { pub app_version: PAppVersion, + pub arrangement: PArrangement, } /* * Version information about the app that created the file. */ -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct PAppVersion { pub appname: String, pub appversion: String, @@ -38,6 +39,6 @@ pub struct PAppVersion { pub applocale: String, } -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct PArrangement { } diff --git a/src/main.rs b/src/main.rs index a561af4..b4c6bbf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,17 +22,16 @@ use cubase_project::*; use nom::{ branch::alt, bytes::complete::*, - combinator::{map, rest, value}, - error::ParseError, - multi::{length_data, length_value, many1}, + combinator::{map}, + multi::{fold_many0, length_data, length_value}, number::complete::*, sequence::{preceded, terminated, tuple}, Finish, IResult, - Parser, }; use std::{ error::Error, + fmt, path::Path, }; @@ -48,18 +47,6 @@ fn cmstring(input: &[u8]) -> IResult<&[u8], &str> { map(strdata, |s| std::str::from_utf8(s).unwrap())(input) } -fn cmtag<'a>(t: &'a str) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], (NodeType, u16)> { - map( - tuple(( - node_type, - length_value( - be_u32, tuple((tag(t), tag(b"\0"))) - ), - be_u16) - ), - |(nt, (_, _), v)| (nt, v)) -} - /** * The data chunks in the file is split into what seems like a structure of containers and leaf * nodes. Where the containers don't contain any data of themselves, but leaf nodes may contain @@ -68,22 +55,13 @@ fn cmtag<'a>(t: &'a str) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], (NodeType, * Each node regardless of type has a name, and a 16 bit number which meaning I'm not sure about. * Leaf nodes also has a data payload prefixed by a 32 bit length (be). */ -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] enum NodeType { Container, Object, } -fn node_type<'a>(input: &'a [u8]) -> IResult<&'a [u8], NodeType> { - alt(( - value(NodeType::Container, tag(b"\xff\xff\xff\xfe")), - value(NodeType::Object, tag(b"\xff\xff\xff\xff")) - ))(input) -} - - -#[derive(Debug)] struct Node<'a> { node_type: NodeType, name: &'a str, @@ -91,6 +69,22 @@ struct Node<'a> { payload: Option<&'a [u8]>, } +impl<'a> fmt::Debug for Node<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut r = f.debug_struct("Node"); + + r.field("node_type", &self.node_type) + .field("name", &self.name) + .field("num", &self.num); + + if let Some(p) = self.payload { + r.field("payload_size", &p.len()); + } + + r.finish() + } +} + fn p_app_version<'a>(input: &'a [u8]) -> IResult<&'a [u8], PAppVersion> { let mut v = PAppVersion::default(); @@ -116,9 +110,16 @@ fn p_app_version<'a>(input: &'a [u8]) -> IResult<&'a [u8], PAppVersion> { } fn p_arrangement<'a>(input: &'a [u8]) -> IResult<&'a [u8], PArrangement> { - let v = PArrangement::default(); - Ok((input, v)) + fold_many0( + node, + PArrangement::default(), + |a: PArrangement, n: Node<'_>| { + println!(" {:?}", n); + a + } + )(input) + } /** @@ -133,16 +134,25 @@ fn root_chunk<'a>(input: &'a [u8]) -> IResult<&'a [u8], (&str, &str)> { length_value(be_u32, tuple((cmstring, cmstring))))(input) } -fn arch_chunk<'a, O, E, F>(subparser: F) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], O, E> -where - E: ParseError<&'a [u8]>, - F: Parser<&'a [u8], O, E>, -{ +// fn arch_chunk<'a, O, E, F>(subparser: F) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], O, E> +// where +// E: ParseError<&'a [u8]>, +// F: Parser<&'a [u8], O, E>, +// { +// preceded( +// tag(b"ARCH"), +// length_value(be_u32, subparser)) +// } + + +fn arch_chunk<'a>(input: &'a [u8]) -> IResult<&'a [u8], &'a [u8]> { preceded( tag(b"ARCH"), - length_value(be_u32, subparser)) + length_data(be_u32) + )(input) } + /** * A container node does not contain any data of it's own, but contains * one or more sub elements. These can be either other containers, or @@ -163,40 +173,50 @@ fn object_node<'a>(data: &'a [u8]) -> IResult<&'a [u8], Node> { Ok((r, Node { node_type: NodeType::Object, name, num, payload: Some(payload) } )) } +fn node<'a>(data: &'a [u8]) -> IResult<&'a [u8], Node> { + alt((container_node, object_node))(data) +} -fn cpr_file<'a>(input: &'a [u8]) -> IResult<&'a [u8], CubaseProject> { - let mut proj = CubaseProject::default(); - - let (mut payload, len) = cpr_file_header(input)?; - assert_eq!(payload.len(), len as usize); - - println!("[*] Reading Cubase CPR file, {} bytes of data...", payload.len()); - - while payload.len() > 0 { - // Expect a root chunk first: - let (r, (k, t)) = root_chunk(payload)?; - println!("[*] Found root: ({}, {})", k, t); - - match k { - "Version" => { - let (r2, v) = arch_chunk(version_chunk)(r)?; - println!("[*] {:?}", v); - proj.app_version = v; - payload = r2; - }, - "Arrangement1" => { - let (r2, a) = arch_chunk(arrangement_chunk)(r)?; - println!("[*] {:?}", a); - payload = r2; - }, - _ => { - let (r2, _) = arch_chunk(rest)(r)?; - payload = r2; + +fn version_chunk<'a>(input: &'a [u8]) -> IResult<&'a [u8], PAppVersion> { + fold_many0( + node, + PAppVersion::default(), + |mut version, n: Node<'_>| { + println!(" {:?}", n); + if n.node_type == NodeType::Object { + let (_, v) = p_app_version(n.payload.unwrap()).unwrap(); + version = v; } + version } - } + )(input) + // preceded( + // container_node, + // p_app_version_node + // )(input) +} - Ok((payload, proj)) +fn arrangement_chunk<'a>(input: &'a [u8]) -> IResult<&'a [u8], PArrangement> { + fold_many0( + node, + PArrangement::default(), + |a: PArrangement, n: Node<'_>| { + println!(" {:?}", n); + if n.node_type == NodeType::Object { + let _ = p_arrangement(n.payload.unwrap()); + } + a + } + )(input) +} + + +/** + * Main parser for a Cubase Project (.cpr) file. + */ +fn cpr_file<'a>(input: &'a [u8]) -> IResult<&'a [u8], CubaseProject> { + preceded(cpr_file_header, cpr_file_body)(input) } /** @@ -216,41 +236,31 @@ fn cpr_file<'a>(input: &'a [u8]) -> IResult<&'a [u8], CubaseProject> { */ fn cpr_file_header<'a>(input: &'a [u8]) -> IResult<&'a [u8], u32> { terminated( - preceded( - tag(b"RIFF"), be_u32), - tag(b"NUND"))(input) -} - - -fn version_chunk<'a>(input: &'a [u8]) -> IResult<&'a [u8], PAppVersion> { - preceded( - tuple((cmtag("CmObject"), cmtag("PAppVersion"))), - length_value(be_u32, p_app_version))(input) + preceded(tag(b"RIFF"), be_u32), + tag(b"NUND") + )(input) } - -fn arrangement_chunk<'a>(input: &'a [u8]) -> IResult<&'a [u8], PArrangement> { - // It varies a bit how many levels deep the actual PArrangement object is. - // - // File created with Cubase 4.5.x (file version 310?) seems to have this - // structure: - // - // GDocument -> GModel -> CmObject - // - // While Cubase version 5.x (file version 400?) seems to have the CmObject - // directly under the GDocument container. - let (odata, c) = many1(container_node)(input)?; - println!("[*] {:?}", c); - - // This is the actual PArrangeent object - let (r2, o) = object_node(odata)?; - println!("[*] {:?}: {} ({} bytes)", o.node_type, o.name, o.payload.unwrap().len()); - assert_eq!(r2.len(), 0); - - let (r2, _v) = p_arrangement(o.payload.unwrap())?; - // assert_eq!(r2.len(), 0); - - Ok((r2, PArrangement::default())) +fn cpr_file_body<'a>(input: &'a [u8]) -> IResult<&'a [u8], CubaseProject> { + fold_many0( + tuple((root_chunk, arch_chunk)), + CubaseProject::default(), + |mut prj, ((key, value), arch)| { + println!("Root: {} = {}", key, value); + match key { + "Version" => { + let (_, v) = version_chunk(arch).unwrap(); + prj.app_version = v; + }, + "Arrangement1" => { + let (_, a) = arrangement_chunk(arch).unwrap(); + prj.arrangement = a; + } + _ => {}, + } + prj + } + )(input) } pub fn parse_cubase_project<P>(filename: P) -> Result<CubaseProject, Box<dyn Error>> |