aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/main.rs319
1 files changed, 95 insertions, 224 deletions
diff --git a/src/main.rs b/src/main.rs
index 6a1c37d..58b6a86 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -20,9 +20,8 @@ mod cubase_project;
use cubase_project::*;
use nom::{
- branch::alt,
bytes::complete::*,
- multi::{count, length_data, length_value, many0},
+ multi::length_data,
number::complete::*,
sequence::tuple,
Finish,
@@ -53,184 +52,42 @@ fn cmstring(data: &[u8]) -> IResult<&[u8], &str> {
* Leaf nodes also has a data payload prefixed by a 32 bit length (be).
*/
#[derive(Debug)]
-enum Node<'a> {
- Container(&'a str, u16),
- Leaf(&'a str, u16, NodeValue<'a>),
+enum NodeType {
+ Container,
+ Object,
}
-#[derive(Debug)]
-enum NodeValue<'a> {
- Unknown,
- //List(Vec<Node<'a>>),
- Raw(Vec<u32>),
- Arrangement(PArrangement),
- InstrumentTrackEvent(MInstrumentTrackEvent<'a>),
-
- MidiAfterTouch(u32),
- MidiController(u32),
- MidiNote(u32),
- MidiPart(MMidiPart<'a>),
- MidiPartEvent(Vec<u8>),
- MidiPitchBend(u32),
- MidiPolyPressure(u32),
- MidiProgramChange(u32),
- MidiSysex(Vec<u8>),
-
- SignatureTrackEvent(MSignatureTrackEvent<'a>),
- TempoTrackEvent(&'a [u8]),
- Version(PAppVersion),
-}
-
-/// A vector of 32bit words preceeded by a 16 bit count.
-fn counted_vec(data: &[u8]) -> IResult<&[u8], Vec<u32>> {
- let (r, len) = be_u16(data)?;
- count(be_u32, len as usize)(&r)
-}
-
-/// A byte vector prefixed with a 32 bit count.
-fn bytevec<'a>(data: &'a[u8]) -> IResult<&'a [u8], &'a [u8]> {
- length_data(be_u32)(&data)
-}
-
-fn p_appversion<'a>(data: &'a [u8]) -> IResult<&'a [u8], PAppVersion> {
- let (r, (appname, appversion, appdate, num2, apparch, num3, appencoding, applocale)) =
- tuple((cmstring, cmstring, cmstring, be_u32, cmstring, be_u32, cmstring, cmstring))(&data)?;
-
- Ok((r, PAppVersion {
- appname: String::from(appname),
- appversion: String::from(appversion),
- appdate: String::from(appdate),
- num2,
- apparch: String::from(apparch),
- num3,
- appencoding: String::from(appencoding),
- applocale: String::from(applocale),
- }))
-}
#[derive(Debug)]
-struct PArrangement {
- num1: u16,
- num2: u16,
-}
-
-fn p_arrangement(data: &[u8]) -> IResult<&[u8], PArrangement> {
- let (r, (num1, num2)) = tuple((be_u16, be_u16))(data)?;
- Ok((r, PArrangement { num1, num2 }))
-}
-
-#[derive(Debug)]
-struct MInstrumentTrackEvent<'a> {
- num16_3: Vec<u16>,
- num32_8: Vec<u32>,
+struct Node<'a> {
+ node_type: NodeType,
name: &'a str,
- num32: u32,
+ num: u16,
+ payload: Option<&'a [u8]>,
}
-fn m_instrument_track_event<'a>(data: &'a [u8]) -> IResult<&'a [u8], MInstrumentTrackEvent<'a>> {
- let (r, (num16_3, num32_8, name, num32)) =
- tuple((count(be_u16, 3), count(be_u32, 8), cmstring, be_u32))(&data)?;
- Ok((r, MInstrumentTrackEvent {
- num16_3,
- num32_8,
- name,
- num32,
- }))
-}
+fn p_app_version<'a>(data: &'a [u8]) -> IResult<&'a [u8], PAppVersion> {
+ let mut v = PAppVersion::default();
-fn m_tempo_track_event<'a>(data: &'a [u8]) -> IResult<&'a [u8], &'a [u8]> {
- bytevec(&data)
-}
+ let (mut r, (appname, appversion, appdate, num2)) = tuple((cmstring, cmstring, cmstring, be_u32))(&data)?;
-#[derive(Debug)]
-struct MSignatureTrackEvent<'a> {
- bv: &'a [u8],
- num32: u32,
-}
-
-fn m_signature_track_event<'a>(data: &'a [u8]) -> IResult<&'a [u8], MSignatureTrackEvent<'a>> {
- let (r, (bv, num32)) = tuple((bytevec, be_u32))(&data)?;
- Ok((r, MSignatureTrackEvent { bv, num32 }))
-}
+ v.appname = String::from(appname);
+ v.appversion = String::from(appversion);
+ v.appdate = String::from(appdate);
+ v.num2 = num2;
-#[derive(Debug)]
-struct MMidiPart<'a> {
- num32: u32,
- name: &'a str,
- data: Vec<u8>,
-}
+ if r.len() > 0 {
+ let (r2, (apparch, num3, appencoding, applocale)) = tuple((cmstring, be_u32, cmstring, cmstring))(&r)?;
-fn m_midi_part<'a>(data: &'a [u8]) -> IResult<&'a [u8], MMidiPart<'a>> {
- let (r, (num32, name, data)) = tuple((be_u32, cmstring, count(be_u8, 17)))(&data)?;
- Ok((r, MMidiPart { num32, name, data }))
-}
+ v.apparch = String::from(apparch);
+ v.num3 = num3;
+ v.appencoding = String::from(appencoding);
+ v.applocale = String::from(applocale);
-fn node_value<'a>(class: &str, data: &'a [u8]) -> IResult<&'a [u8], NodeValue<'a>> {
- match class {
- "PAppVersion" => {
- let (r, appver) = p_appversion(&data)?;
- Ok((r, NodeValue::Version(appver)))
- },
- "PArrangement" => {
- let (r, arr) = p_arrangement(&data)?;
- Ok((r, NodeValue::Arrangement(arr)))
- },
- "MRoot" |
- "MTrackList" => {
- let (r, v) = counted_vec(&data)?;
- Ok((r, NodeValue::Raw(v)))
- },
- "MInstrumentTrackEvent" => {
- let (r, e) = m_instrument_track_event(&data)?;
- Ok((r, NodeValue::InstrumentTrackEvent(e)))
- },
- "MTempoTrackEvent" => {
- let (r, e) = m_tempo_track_event(&data)?;
- Ok((r, NodeValue::TempoTrackEvent(e)))
- },
- "MSignatureTrackEvent" => {
- let (r, e) = m_signature_track_event(&data)?;
- Ok((r, NodeValue::SignatureTrackEvent(e)))
- },
- "MMidiPartEvent" => {
- let (r, v) = count(be_u8, 30)(data)?;
- Ok((r, NodeValue::MidiPartEvent(v)))
- },
- "MMidiPart" => {
- let (r, v) = m_midi_part(&data)?;
- Ok((r, NodeValue::MidiPart(v)))
- },
- "MMidiNote" => {
- let (r, v) = be_u32(data)?;
- Ok((r, NodeValue::MidiNote(v)))
- },
- "MMidiPolyPressure" => {
- let (r, v) = be_u32(data)?;
- Ok((r, NodeValue::MidiPolyPressure(v)))
- },
- "MMidiAfterTouch" => {
- let (r, v) = be_u32(data)?;
- Ok((r, NodeValue::MidiAfterTouch(v)))
- },
- "MMidiProgramChange" => {
- let (r, v) = be_u32(data)?;
- Ok((r, NodeValue::MidiProgramChange(v)))
- },
- "MMidiController" => {
- let (r, v) = be_u32(data)?;
- Ok((r, NodeValue::MidiController(v)))
- },
- "MMidiPitchBend" => {
- let (r, v) = be_u32(data)?;
- Ok((r, NodeValue::MidiPitchBend(v)))
- },
- "MMidiSysex" => {
- let sep = vec![0xff, 0xff, 0xff, 0xff];
- let (r, v) = take_until(sep.as_slice())(data)?;
- Ok((r, NodeValue::MidiSysex(v.to_vec())))
- },
- &_ => Ok((&[], NodeValue::Unknown)) //"Unknown node class \"{}\".", class)
+ r = r2;
}
+
+ Ok((r, v))
}
fn fourcc<'a>(data: &'a [u8]) -> IResult<&'a [u8], &'a str> {
@@ -245,7 +102,11 @@ fn fourcc<'a>(data: &'a [u8]) -> IResult<&'a [u8], &'a str> {
* The actual data follows in the ARCH chunk following this ROOT chunk.
*/
fn root_chunk<'a>(data: &'a [u8]) -> IResult<&'a [u8], (&str, &str)> {
- tuple((cmstring, cmstring))(data)
+ let (r, chunk) = riff_chunk(data)?;
+ assert_eq!(chunk.fourcc, "ROOT");
+ //assert_eq!(r.len(), 0);
+ let (_, t) = tuple((cmstring, cmstring))(chunk.payload)?;
+ Ok((r, t))
}
@@ -256,7 +117,7 @@ fn root_chunk<'a>(data: &'a [u8]) -> IResult<&'a [u8], (&str, &str)> {
*/
fn container_node<'a>(data: &'a [u8]) -> IResult<&'a [u8], Node> {
let (r, (_, name, num)) = tuple((tag(b"\xff\xff\xff\xfe"), cmstring, be_u16))(data)?;
- Ok((r, Node::Container(name, num)))
+ Ok((r, Node { node_type: NodeType::Container, name, num, payload: None} ))
}
/**
@@ -266,17 +127,7 @@ fn container_node<'a>(data: &'a [u8]) -> IResult<&'a [u8], Node> {
*/
fn object_node<'a>(data: &'a [u8]) -> IResult<&'a [u8], Node> {
let (r, (_, name, num, payload)) = tuple((tag(b"\xff\xff\xff\xff"), cmstring, be_u16, length_data(be_u32)))(data)?;
- let (_, nv) = node_value(name, payload)?;
- Ok((r, Node::Leaf(name, num, nv)))
-}
-
-/**
- * ARCH chunks contains one or more structured data elements that can be
- * either container nodes or object/leaf nodes..
- */
-fn arch_chunk<'a>(data: &'a [u8]) -> IResult<&'a [u8], Vec<Node>> {
- let (rest, nodes) = many0(alt((container_node, object_node)))(data)?;
- Ok((rest, nodes))
+ Ok((r, Node { node_type: NodeType::Object, name, num, payload: Some(payload) } ))
}
struct RiffChunk<'a> {
@@ -290,8 +141,38 @@ fn riff_chunk<'a>(i: &'a [u8]) -> IResult<&'a [u8], RiffChunk> {
}
+fn cpr_file<'a>(data: &'a [u8]) -> IResult<&'a [u8], CubaseProject> {
+ let mut proj = CubaseProject::default();
+
+ let (mut payload, _) = cpr_file_header(data)?;
+
+ 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) = version_chunk(r)?;
+ println!("[*] {:?}", v);
+ //assert_eq!(r2.len(), 0);
+ proj.app_version = v;
+ payload = r2;
+ },
+ _ => {
+ let (r2, _) = riff_chunk(r)?;
+ payload = r2;
+ }
+ }
+ }
+
+ Ok((payload, proj))
+}
+
/**
- * Split file into individual chunks.
+ * Parse the Cubase Project File Header.
*
* A Cubase project file is a RIFF file.
*
@@ -302,19 +183,15 @@ fn riff_chunk<'a>(i: &'a [u8]) -> IResult<&'a [u8], RiffChunk> {
*
* To make it even worse, the chunk size of the root chunk is four bytes short, so this makes
* the file parser itself a bit more complex than what it needs to be.
+ *
+ * We just swallow the extra "NUND" tag here, so it won't bother us later.
*/
-fn split_chunks<'a>(data: &'a [u8]) -> IResult<&'a [u8], Vec<RiffChunk<'a>>> {
- let (r, (_, (_, chunks))) =
- tuple((
- tag(b"RIFF"),
- length_value(
- payload_len,
- tuple((tag(b"NUND"), many0(riff_chunk)))
- )
- ))(&data)?;
- Ok((r, chunks))
+fn cpr_file_header<'a>(data: &'a [u8]) -> IResult<&'a [u8], ()> {
+ let (r, (_, _, _)) = tuple((tag(b"RIFF"), payload_len, tag(b"NUND")))(data)?;
+ Ok((r, ()))
}
+
/**
* Get the correct payload length from the root chunk of the file.
*
@@ -327,6 +204,25 @@ fn payload_len<'a>(data: &'a [u8]) -> IResult<&'a [u8], u32> {
Ok((r, len + 4))
}
+
+fn version_chunk<'a>(data: &'a [u8]) -> IResult<&'a [u8], PAppVersion> {
+ let (r, chunk) = riff_chunk(data)?;
+ assert_eq!(chunk.fourcc, "ARCH");
+ //assert_eq!(r.len(), 0);
+
+ let (odata, c) = container_node(chunk.payload)?;
+ println!("[*] {:?}", c);
+
+ let (r2, o) = object_node(odata)?;
+ println!("[*] {:?}", o);
+ assert_eq!(r2.len(), 0);
+
+ let (r2, v) = p_app_version(o.payload.unwrap())?;
+ assert_eq!(r2.len(), 0);
+
+ Ok((r, v))
+}
+
pub fn parse_cubase_project<P>(filename: P) -> Result<CubaseProject, Box<dyn Error>>
where
P: AsRef<Path>
@@ -334,41 +230,11 @@ where
println!("Reading {}...", filename.as_ref().to_str().ok_or("Invalid file name")?);
let data = std::fs::read(filename)?;
- let (rest, chunks) = split_chunks(&data)
+ let (_, proj) = cpr_file(&data)
.finish()
.map_err(|e| format!("{:?}", e))?;
- println!("Rest: {}", rest.len());
-
- let proj = CubaseProject::default();
-
- for chunk in chunks {
- println!("{}: {}", chunk.fourcc, chunk.payload.len());
-
- match chunk.fourcc {
- "ROOT" => {
- let (r, (k, v)) = root_chunk(chunk.payload).finish().map_err(|e| format!("{:?}", e))?;
- assert_eq!(r.len(), 0);
- println!(" {}: {}", k, v);
- },
- "ARCH" => {
- let (r, nodes) = arch_chunk(chunk.payload).finish().map_err(|e| format!("{:?}", e))?;
- assert_eq!(r.len(), 0);
- let mut indent = 2;
- for node in nodes {
- println!("{1:0$}{2:?}", indent, " ", node);
- if let Node::Container(_, _) = node {
- indent += 2;
- }
- }
- },
- _ => {
- eprintln!("[-] Warning: ignoring unknown chunk \"{}\" of length {} bytes.",
- chunk.fourcc,
- chunk.payload.len());
- }
- };
- }
+ println!("[*] Done!");
Ok(proj)
}
@@ -379,7 +245,12 @@ fn main() -> Result<(), Box<dyn Error>> {
.next()
.expect("You must give me a file to analyze");
- parse_cubase_project(filename)?;
+ let proj = parse_cubase_project(filename)?;
+
+ println!("File generated by {} {}", proj.app_version.appname, proj.app_version.appversion);
+ println!("Architecture: {}", proj.app_version.apparch);
+ println!("Encoding: {}", proj.app_version.appencoding);
+ println!("Locale: {}", proj.app_version.applocale);
Ok(())
}