diff --git a/outdb b/outdb index ac85d77..0e1d7eb 100644 Binary files a/outdb and b/outdb differ diff --git a/src/deserializer.rs b/src/deserializer.rs new file mode 100644 index 0000000..c20b4a1 --- /dev/null +++ b/src/deserializer.rs @@ -0,0 +1,176 @@ +use log::{info, warn}; + +use crate::{objects::{AlbumItem, ChunkHeader, ChunkType, DataSet, Database, JumpTable, LetterJumpEntry, Playlist, PlaylistIndexEntry, StringEntry, TrackItem}, xobjects::{XAlbumItem, XArgument, XDataSet, XDatabase, XLetterJump, XPlArgument, XPlaylist, XPlaylistIndexEntry, XSomeList, XTrackItem}}; + +enum ChunkState { + Header, + Data +} + +pub fn parse_bytes(data: &[u8]) -> XDatabase { + let mut xdb = XDatabase{data: None, header: None, children: Vec::new()}; + let mut state = ChunkState::Header; + let mut chunk_header: Option = None; + let mut last_type: u32 = 0; + let mut i = 0; + while i < data.len() { + state = match state { + ChunkState::Header => { + if i + 12 >= data.len() { break; } + chunk_header = Some(bincode::deserialize(&data[i..i+12]).unwrap()); + i += 12; + ChunkState::Data + }, + ChunkState::Data => { + let mut u = 0; + let header = chunk_header.unwrap(); + match ChunkType::from(header.chunk_type) { + ChunkType::Database => { + info!("Db header: {:?}", header); + u = usize::try_from(header.end_of_chunk).unwrap() - 12; + let db: Database = bincode::deserialize(&data[i..i+u]).unwrap(); + info!("val: {:?}", db); + xdb.data = Some(db); + xdb.header = Some(header); + }, + ChunkType::DataSet => { + u = usize::try_from(header.end_of_chunk).unwrap() - 12; + let ds: DataSet = bincode::deserialize(&data[i..i+u]).unwrap(); + info!("DataSet: {:?}", ds); + xdb.children.push(XDataSet { header: header, data: ds.clone(), child: match ds.data_type { + 3 => XSomeList::Playlists(Vec::new()), // Playlist List + 4 => XSomeList::AlbumList(Vec::new()), // Album List + _ => XSomeList::TrackList(Vec::new()) // Track List (1) + }}); + }, + ChunkType::AlbumList => { + info!("AlbumList"); + u = usize::try_from(header.end_of_chunk).unwrap() - 12; + last_type = 4; + }, + ChunkType::AlbumItem => { + u = usize::try_from(header.end_of_chunk).unwrap() - 12; + let ai: AlbumItem = bincode::deserialize(&data[i..i+u]).unwrap(); + info!("val: {:?}", ai); + info!("AlbumItem: {}", u); + if let XSomeList::AlbumList(albums) = &mut xdb.find_dataset(4).child { + albums.push(XAlbumItem {header: header, data: ai,args: Vec::new()}); + } + }, + ChunkType::TrackList => { + info!("TrackList"); + u = usize::try_from(header.end_of_chunk).unwrap() - 12; + last_type = 1; + }, + ChunkType::TrackItem => { + u = usize::try_from(header.end_of_chunk).unwrap() - 12; + let ti: TrackItem = bincode::deserialize(&data[i..i+u]).unwrap(); + info!("val: {:?}", ti); + if let XSomeList::TrackList(tracks) = &mut xdb.find_dataset(1).child { + tracks.push(XTrackItem {header: header, data: ti,args: Vec::new()}); + } + }, + ChunkType::StringTypes => { + u = usize::try_from(header.children_count).unwrap() - 12; + let header_offset: usize = (header.end_of_chunk + 4) as usize; + let entry_type = u32::from_le_bytes(data[i..i+4].try_into().unwrap()); + match entry_type { + 0..=15 => { + let str_end: usize = (header.children_count - 12) as usize; + let entry: StringEntry = bincode::deserialize(&data[i..i+28]).unwrap(); + info!("val: {:?}", &entry); + let mut bytes = Vec::new(); + + let mut h = i+header_offset; + while h < i+str_end { + if data[h] != 0 { + bytes.push(data[h]); + } + h+=1; + } + let g = String::from_utf8(bytes).unwrap(); + info!("str: {}", g); + match &mut xdb.find_dataset(last_type).child { + XSomeList::AlbumList(albums) => { + albums.last_mut().unwrap().args.push(XArgument{ arg_type: entry_type, val: g}); + }, + XSomeList::Playlists(playlists) => { + playlists.last_mut().unwrap().args.push(XPlArgument::String(XArgument{ arg_type: entry_type, val: g})); + }, + XSomeList::TrackList(tracks) => { + tracks.last_mut().unwrap().args.push(XArgument{ arg_type: entry_type, val: g}); + } + } + }, + 52 => { + let entry: PlaylistIndexEntry = bincode::deserialize(&data[i..i+60]).unwrap(); + info!("valPl: {:?}", &entry); + let mut h = i+60; + let mut v = Vec::new(); + while h < i+60+((4*entry.count) as usize) { + v.push(u32::from_le_bytes(data[h..h+4].try_into().unwrap())); + h += 4; + } + info!("Indexes: {:?}", v); + match &mut xdb.find_dataset(last_type).child { + XSomeList::Playlists(playlists) => { + playlists.last_mut().unwrap().args.push(XPlArgument::IndexEntry(XPlaylistIndexEntry{data: entry, v})); + } + _ => {} + } + }, + 53 => { + let entry: LetterJumpEntry = bincode::deserialize(&data[i..i+28]).unwrap(); + info!("valJT: {:?}", &entry); + let mut h = i+28; + let mut v: Vec = Vec::new(); + while h < i+28+((12*entry.count) as usize) { + v.push(bincode::deserialize(&data[h..h+12]).unwrap()); + h += 12; + } + info!("Indexes: {:?}", v); + match &mut xdb.find_dataset(last_type).child { + XSomeList::Playlists(playlists) => { + playlists.last_mut().unwrap().args.push(XPlArgument::LetterJumpEntry(XLetterJump{ data: entry, v })); + } + _ => {} + } + }, + 100 | 102 => { + info!("Entry #100,102 fetched"); + match &mut xdb.find_dataset(last_type).child { + XSomeList::Playlists(playlists) => { + playlists.last_mut().unwrap().args.push(XPlArgument::RawArgument(data[i-12..i+(header.children_count as usize)-12].to_vec())); + } + _ => {} + } + }, + _ => warn!("Unknown entry: {}", entry_type) + } + }, + ChunkType::PlaylistList => { + info!("Playlists count: {}", header.children_count); + u = usize::try_from(header.end_of_chunk).unwrap() - 12; + last_type = 3; + }, + ChunkType::Playlist => { + u = usize::try_from(header.end_of_chunk).unwrap() - 12; + let playlist: Playlist = bincode::deserialize(&data[i..i+u]).unwrap(); + info!("playlist: {:?}", playlist); + if let XSomeList::Playlists(playlists) = &mut xdb.find_dataset(3).child { + playlists.push(XPlaylist {header: header, data: playlist,args: Vec::new()}); + } + }, + _ => { u = 1; } + } + i += u; + chunk_header = None; + ChunkState::Header + } + } + } + //let mut f = File::create("output.json").unwrap(); + //let r = f.write(serde_json::to_string::(&xdb).unwrap().as_bytes()); + //info!("Result: {:?}", r); + xdb +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 519ebb9..cbdd187 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,564 +1,17 @@ use std::{fs::File, io::{Read, Write}}; use env_logger::Builder; use log::{error, info, LevelFilter}; -use serde::{Deserialize, Serialize}; -use xobjects::{XAlbumItem, XArgument, XDataSet, XDatabase, XLetterJump, XPlArgument, XPlaylist, XPlaylistIndexEntry, XSomeList, XTrackItem}; +mod objects; mod xobjects; - -enum ChunkType { - Database, - DataSet, - AlbumList, - AlbumItem, - TrackList, - TrackItem, - StringTypes, - PlaylistList, - Playlist, - Unknown -} - -impl From<[u8; 4]> for ChunkType { - fn from(value: [u8; 4]) -> Self { - match value { - [0x6D, 0x68, 0x62, 0x64] => ChunkType::Database, - [0x6D, 0x68, 0x73, 0x64] => ChunkType::DataSet, - [0x6D, 0x68, 0x69, 0x61] => ChunkType::AlbumItem, - [0x6D, 0x68, 0x6C, 0x61] => ChunkType::AlbumList, - [0x6D, 0x68, 0x6C, 0x74] => ChunkType::TrackList, - [0x6D, 0x68, 0x69, 0x74] => ChunkType::TrackItem, - [0x6D, 0x68, 0x6F, 0x64] => ChunkType::StringTypes, - [0x6D, 0x68, 0x6C, 0x70] => ChunkType::PlaylistList, - [0x6D, 0x68, 0x79, 0x70] => ChunkType::Playlist, - _ => ChunkType::Unknown - } - } -} - -impl From for [u8; 4] { - fn from(value: ChunkType) -> Self { - match value { - ChunkType::Database => [0x6D, 0x68, 0x62, 0x64], - ChunkType::DataSet => [0x6D, 0x68, 0x73, 0x64], - ChunkType::AlbumItem => [0x6D, 0x68, 0x69, 0x61], - ChunkType::AlbumList => [0x6D, 0x68, 0x6C, 0x61], - ChunkType::TrackList => [0x6D, 0x68, 0x6C, 0x74], - ChunkType::TrackItem => [0x6D, 0x68, 0x69, 0x74], - ChunkType::StringTypes => [0x6D, 0x68, 0x6F, 0x64], - ChunkType::PlaylistList => [0x6D, 0x68, 0x6C, 0x70], - ChunkType::Playlist => [0x6D, 0x68, 0x79, 0x70], - ChunkType::Unknown => [0x00, 0x00, 0x00, 0x00] - } - } -} - -#[derive(Serialize, Deserialize, PartialEq, Debug)] -struct ChunkHeader { - chunk_type: [u8; 4], - end_of_chunk: u32, - children_count: u32 -} - -#[derive(Serialize, Deserialize, PartialEq, Debug)] -struct Database { - unknown: u32, - version: u32, - children_count: u32, - id: u64, - unknown1: [u8; 32], - language: u16, - persistent_id: u64, - hash: [u8; 20] -} - -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] -struct DataSet { - data_type: u32 -} - -#[derive(Serialize, Deserialize, PartialEq, Debug)] -struct AlbumItem { - number_of_strings: u32, - unknown: u16, - album_id_for_track: u16, - timestamp: u64, - unknown1: u32 -} - -#[derive(Serialize, Deserialize, PartialEq, Debug)] -struct TrackItem { - number_of_strings: u32, // number of mhod's count - unique_id: u32, - visible: u32, - filetype: u32, - type1: u8, - type2: u8, - compilation_flag: u8, - stars: u8, - last_modified_time: u32, - size: u32, - length: u32, - track_number: u32, - total_tracks: u32, - year: u32, - bitrate: u32, - sample_rate: u32, - volume: u32, - start_time: u32, - stop_time: u32, - soundcheck: u32, - play_count: u32, - play_count2: u32, - last_played_time: u32, - disc_number: u32, - total_discs: u32, - userid: u32, - date_added: u32, - bookmark_time: u32, - dbid: u64, - checked: u8, - application_rating: u8, - bpm: u16, - artwork_count: u16, - unk9: u16, - artwork_size: u32, - unk11: u32, - sample_rate2: u32, - date_released: u32, - unk14: u32, - unk15: u32, - unk16: u32, - skip_count: u32, - last_skipped: u32, - has_artwork: u8, - skip_when_shuffling: u8, - remember_playback_position: u8, - flag4: u8, - dbid2: u64, - lyrics_flag: u8, - movie_file_flag: u8, - played_mark: u8, - unk17: u8, - unk21: u32, - pregap: u32, - sample_count: u64, - unk25: u32, - postgap: u32, - unk27: u32, - media_type: u32, - season_number: u32, - episode_number: u32, - unk31: [u8; 28], - gapless_data: u32, - unk38: u32, - gapless_track_flag: u16, - gapless_album_flag: u16, - unk39_hash: [u8; 20], - unk40: [u8; 18], - album_id: u16, - mhii_link: u32 -} - -#[derive(Serialize, Deserialize, PartialEq, Debug)] -struct StringEntry { // mhod - entry_type: u32, - unk1: u32, - unk2: u32, - position: u32, - length: u32, - unknown: u32, - unk4: u32 -} - -#[derive(Serialize, Deserialize, PartialEq, Debug)] -struct PlaylistIndexEntry { // mhod - entry_type: u32, - unk1: u32, - unk2: u32, - index_type: u32, - count: u32, - null_padding: u64, - null_padding1: u64, - null_padding2: u64, - null_padding3: u64, - null_padding4: u64 -} - -#[derive(Serialize, Deserialize, PartialEq, Debug)] -struct LetterJumpEntry { - entry_type: u32, - unk1: u32, - unk2: u32, - index_type: u32, - count: u32, - null_padding: u64 -} - -#[derive(Serialize, Deserialize, PartialEq, Debug)] -struct JumpTable { - letter: u32, // UTF-16 LE Uppercase with two padding null bytes - entry_num: u32, // the number of the first entry in the corresponding MHOD52 index starting with this letter. Zero-based and incremented by one for each entry, not 4. - count: u32 // the count of entries starting with this letter in the corresponding MHOD52. -} - -#[derive(Serialize, Deserialize, PartialEq, Debug)] -struct Playlist { - data_object_child_count: u32, - playlist_item_count: u32, - is_master_playlist_flag: u8, - unk: [u8; 3], - timestamp: u32, - persistent_playlist_id: u64, - unk3: u32, - string_mhod_count: u16, - podcast_flag: u16, - list_sort_order: u32 -} - -enum ChunkState { - Header, - Data -} - -pub fn parse_bytes(data: &[u8]) -> XDatabase { - let mut xdb = XDatabase{data: None, header: None, children: Vec::new()}; - let mut state = ChunkState::Header; - let mut chunk_header: Option = None; - let mut last_type: u32 = 0; - let mut i = 0; - while i < data.len() { - state = match state { - ChunkState::Header => { - if i + 12 >= data.len() { break; } - chunk_header = Some(bincode::deserialize(&data[i..i+12]).unwrap()); - i += 12; - ChunkState::Data - }, - ChunkState::Data => { - let mut u = 0; - let header = chunk_header.unwrap(); - match ChunkType::from(header.chunk_type) { - ChunkType::Database => { - info!("Db header: {:?}", header); - u = usize::try_from(header.end_of_chunk).unwrap() - 12; - let db: Database = bincode::deserialize(&data[i..i+u]).unwrap(); - info!("val: {:?}", db); - xdb.data = Some(db); - xdb.header = Some(header); - }, - ChunkType::DataSet => { - u = usize::try_from(header.end_of_chunk).unwrap() - 12; - let ds: DataSet = bincode::deserialize(&data[i..i+u]).unwrap(); - info!("DataSet: {:?}", ds); - xdb.children.push(XDataSet { header: header, data: ds.clone(), child: match ds.data_type { - 3 => XSomeList::Playlists(Vec::new()), // Playlist List - 4 => XSomeList::AlbumList(Vec::new()), // Album List - _ => XSomeList::TrackList(Vec::new()) // Track List (1) - }}); - }, - ChunkType::AlbumList => { - info!("AlbumList"); - u = usize::try_from(header.end_of_chunk).unwrap() - 12; - last_type = 4; - }, - ChunkType::AlbumItem => { - u = usize::try_from(header.end_of_chunk).unwrap() - 12; - let ai: AlbumItem = bincode::deserialize(&data[i..i+u]).unwrap(); - info!("val: {:?}", ai); - info!("AlbumItem: {}", u); - if let XSomeList::AlbumList(albums) = &mut xdb.find_dataset(4).child { - albums.push(XAlbumItem {header: header, data: ai,args: Vec::new()}); - } - }, - ChunkType::TrackList => { - info!("TrackList"); - u = usize::try_from(header.end_of_chunk).unwrap() - 12; - last_type = 1; - }, - ChunkType::TrackItem => { - u = usize::try_from(header.end_of_chunk).unwrap() - 12; - let ti: TrackItem = bincode::deserialize(&data[i..i+u]).unwrap(); - info!("val: {:?}", ti); - if let XSomeList::TrackList(tracks) = &mut xdb.find_dataset(1).child { - tracks.push(XTrackItem {header: header, data: ti,args: Vec::new()}); - } - }, - ChunkType::StringTypes => { - u = usize::try_from(header.children_count).unwrap() - 12; - let header_offset: usize = (header.end_of_chunk + 4) as usize; - let entry_type = u32::from_le_bytes(data[i..i+4].try_into().unwrap()); - match entry_type { - 0..=15 => { - let str_end: usize = (header.children_count - 12) as usize; - let entry: StringEntry = bincode::deserialize(&data[i..i+28]).unwrap(); - info!("val: {:?}", &entry); - let mut bytes = Vec::new(); - - let mut h = i+header_offset; - while h < i+str_end { - if data[h] != 0 { - bytes.push(data[h]); - } - h+=1; - } - let g = String::from_utf8(bytes).unwrap(); - info!("str: {}", g); - match &mut xdb.find_dataset(last_type).child { - XSomeList::AlbumList(albums) => { - albums.last_mut().unwrap().args.push(XArgument{ arg_type: entry_type, val: g}); - }, - XSomeList::Playlists(playlists) => { - playlists.last_mut().unwrap().args.push(XPlArgument::String(XArgument{ arg_type: entry_type, val: g})); - }, - XSomeList::TrackList(tracks) => { - tracks.last_mut().unwrap().args.push(XArgument{ arg_type: entry_type, val: g}); - } - } - }, - 52 => { - let entry: PlaylistIndexEntry = bincode::deserialize(&data[i..i+60]).unwrap(); - info!("valPl: {:?}", &entry); - let mut h = i+60; - let mut v = Vec::new(); - while h < i+60+((4*entry.count) as usize) { - v.push(u32::from_le_bytes(data[h..h+4].try_into().unwrap())); - h += 4; - } - info!("Indexes: {:?}", v); - match &mut xdb.find_dataset(last_type).child { - XSomeList::Playlists(playlists) => { - playlists.last_mut().unwrap().args.push(XPlArgument::IndexEntry(XPlaylistIndexEntry{data: entry, v})); - } - _ => {} - } - }, - 53 => { - let entry: LetterJumpEntry = bincode::deserialize(&data[i..i+28]).unwrap(); - info!("valJT: {:?}", &entry); - let mut h = i+28; - let mut v: Vec = Vec::new(); - while h < i+28+((12*entry.count) as usize) { - v.push(bincode::deserialize(&data[h..h+12]).unwrap()); - h += 12; - } - info!("Indexes: {:?}", v); - match &mut xdb.find_dataset(last_type).child { - XSomeList::Playlists(playlists) => { - playlists.last_mut().unwrap().args.push(XPlArgument::LetterJumpEntry(XLetterJump{ data: entry, v })); - } - _ => {} - } - }, - 100 => { - - }, - 102 => { - - }, - _ => {} - } - }, - ChunkType::PlaylistList => { - info!("Playlists count: {}", header.children_count); - u = usize::try_from(header.end_of_chunk).unwrap() - 12; - last_type = 3; - }, - ChunkType::Playlist => { - u = usize::try_from(header.end_of_chunk).unwrap() - 12; - let playlist: Playlist = bincode::deserialize(&data[i..i+u]).unwrap(); - info!("playlist: {:?}", playlist); - if let XSomeList::Playlists(playlists) = &mut xdb.find_dataset(3).child { - playlists.push(XPlaylist {header: header, data: playlist,args: Vec::new()}); - } - }, - _ => { u = 1; } - } - i += u; - chunk_header = None; - ChunkState::Header - } - } - } - //let mut f = File::create("output.json").unwrap(); - //let r = f.write(serde_json::to_string::(&xdb).unwrap().as_bytes()); - //info!("Result: {:?}", r); - xdb -} - -fn string_to_ipod16(str: &String) -> Vec { - str.as_bytes().iter().map(|b| [*b, 0x0]).flatten().collect() -} - -fn x_args_to_bytes(args: &Vec) -> Vec { - args.iter() - .filter(|arg| arg.arg_type <= 15) - .map(|arg| { - let s = string_to_ipod16(&arg.val); - let mut b = bincode::serialize(&StringEntry { - entry_type: arg.arg_type, - unk1: 0, - unk2: 0, - position: 1, - length: s.len() as u32, - unknown: 0, - unk4: 0 - }).unwrap(); - let h = bincode::serialize(&ChunkHeader { - chunk_type: ChunkType::StringTypes.into(), - end_of_chunk: 0x18, - children_count: 16 + 0x18 + s.len() as u32 - }).unwrap(); - b = [h, b, s].concat(); - return b; - }) - .flatten() - .collect() -} - -fn generate_header(ct: ChunkType, header_size: usize, data_len: usize) -> Vec { - let header_size = 12 + header_size as u32; - let header = ChunkHeader{ chunk_type: ct.into(), end_of_chunk: header_size, children_count: header_size + data_len as u32}; - bincode::serialize(&header).unwrap() -} - -fn generate_zeroes(cnt: u32) -> Vec { - let mut v: Vec = Vec::new(); - for i in 0..cnt-12 { - v.push(0x00); - } - v -} +mod deserializer; +mod serializer; /* [lib] crate-type = ["staticlib", "cdylib", "lib"] */ -fn to_bytes(xdb: XDatabase) -> Vec { - let mut bytes: Vec = Vec::new(); - for i in 0..xdb.children.len() { - let data_set = xdb.children.get(i).unwrap(); - match &data_set.child { - XSomeList::Playlists(playlists) => { - let mut pl_bytes = Vec::new(); - for u in 0..playlists.len() { - let playlist = playlists.get(u).unwrap(); - - let mut args: Vec = playlist.args.iter() - .map(|arg| match arg { - XPlArgument::String(xarg) => { - let s = string_to_ipod16(&xarg.val); - let mut b = bincode::serialize(&StringEntry { - entry_type: xarg.arg_type, - unk1: 0, - unk2: 0, - position: 1, - length: s.len() as u32, - unknown: 0, - unk4: 0 - }).unwrap(); - let h = bincode::serialize(&ChunkHeader { - chunk_type: ChunkType::StringTypes.into(), - end_of_chunk: 0x18, - children_count: 16 + 0x18 + s.len() as u32 - }).unwrap(); - b = [h, b, s].concat(); - return b; - }, - XPlArgument::IndexEntry(xpl) => { - let mut b = bincode::serialize(&xpl.data).unwrap(); - let mut v: Vec = Vec::new(); - for i in xpl.v.iter() { - v = [v, i.to_le_bytes().to_vec()].concat(); - } - let h = bincode::serialize(&ChunkHeader { - chunk_type: ChunkType::StringTypes.into(), - end_of_chunk: 72, - children_count: 72 + (4 * xpl.v.len() as u32) - }).unwrap(); - b = [h, b, v].concat(); - return b; - }, - XPlArgument::LetterJumpEntry(xjump) => { - let mut b = bincode::serialize(&xjump.data).unwrap(); - let mut v: Vec = Vec::new(); - for i in xjump.v.iter() { - v.append(&mut bincode::serialize(i).unwrap()); - } - let h = bincode::serialize(&ChunkHeader { - chunk_type: ChunkType::StringTypes.into(), - end_of_chunk: 40, - children_count: 40 + (12 * xjump.v.len() as u32) - }).unwrap(); - b = [h, b, v].concat(); - return b; - }, - }) - .flatten() - .collect(); - - pl_bytes.append(&mut generate_header(ChunkType::Playlist, 36,args.len())); - pl_bytes.append(&mut bincode::serialize(&playlist.data).unwrap()); - pl_bytes.append(&mut args); - } - let mhlp = ChunkHeader { chunk_type: ChunkType::PlaylistList.into(), end_of_chunk: 92, children_count: playlists.len() as u32 }; - let mut mhlp = bincode::serialize(&mhlp).unwrap(); - bytes.append(&mut generate_header(ChunkType::DataSet, 84, mhlp.len())); - bytes.append(&mut bincode::serialize(&data_set.data).unwrap()); - bytes.append(&mut generate_zeroes(92)); - bytes.append(&mut mhlp); - bytes.append(&mut generate_zeroes(92)); - bytes.append(&mut pl_bytes); - }, - XSomeList::AlbumList(albums) => { - let mut al_bytes = Vec::new(); - for u in 0..albums.len() { - let album = albums.get(u).unwrap(); - let mut args = x_args_to_bytes(&album.args); - al_bytes.append(&mut generate_header(ChunkType::AlbumItem, 76,args.len())); - al_bytes.append(&mut bincode::serialize(&album.data).unwrap()); - al_bytes.append(&mut generate_zeroes(68)); - al_bytes.append(&mut args); - } - let mhla = ChunkHeader { chunk_type: ChunkType::AlbumList.into(), end_of_chunk: 92, children_count: albums.len() as u32 }; - let mut mhla = bincode::serialize(&mhla).unwrap(); - bytes.append(&mut generate_header(ChunkType::DataSet, 84, mhla.len())); - bytes.append(&mut bincode::serialize(&data_set.data).unwrap()); - bytes.append(&mut generate_zeroes(92)); - bytes.append(&mut mhla); - bytes.append(&mut generate_zeroes(92)); - bytes.append(&mut al_bytes); - }, - XSomeList::TrackList(tracks) => { - let mut tr_bytes = Vec::new(); - for u in 0..tracks.len() { - let track = tracks.get(u).unwrap(); - let mut args = x_args_to_bytes(&track.args); - tr_bytes.append(&mut generate_header(ChunkType::TrackItem, 612,args.len())); - tr_bytes.append(&mut bincode::serialize(&track.data).unwrap()); - tr_bytes.append(&mut generate_zeroes(332)); - tr_bytes.append(&mut args); - } - let mhlt = ChunkHeader { chunk_type: ChunkType::TrackList.into(), end_of_chunk: 92, children_count: tracks.len() as u32 }; - let mut mhlt = bincode::serialize(&mhlt).unwrap(); - bytes.append(&mut generate_header(ChunkType::DataSet, 84, mhlt.len())); - bytes.append(&mut bincode::serialize(&data_set.data).unwrap()); - bytes.append(&mut generate_zeroes(92)); - bytes.append(&mut mhlt); - bytes.append(&mut generate_zeroes(92)); - bytes.append(&mut tr_bytes); - } - } - } - let sdb = bincode::serialize(&xdb.data.unwrap()).unwrap(); - let sdb_len = sdb.len(); - let h = xdb.header.unwrap(); - bytes = [sdb, generate_zeroes(h.end_of_chunk - sdb_len as u32), bytes].concat(); - bytes = [generate_header(ChunkType::Database, (h.end_of_chunk - 12) as usize, bytes.len()), bytes].concat(); - bytes -} - fn main() { // Initialize the logger with 'info' as the default level @@ -567,15 +20,15 @@ fn main() { .init(); // /Users/michael/Documents/ipod/iTunes/iTunesDB - let mut f = File::open("outdb").unwrap(); // D:\\Documents\\iTunes\\iTunesDB + let mut f = File::open("D:\\Documents\\iTunes\\iTunesDB").unwrap(); // D:\\Documents\\iTunes\\iTunesDB let mut buf = Vec::new(); match f.read_to_end(&mut buf) { Ok(n) => { let data = &buf[..n]; - let xdb = parse_bytes(data); + let xdb = deserializer::parse_bytes(data); info!("XDB: {:?}", xdb); - //let mut op = File::create("outdb").unwrap(); - //info!("Write res: {:?}", op.write(&to_bytes(xdb))); + let mut op = File::create("outdb").unwrap(); + info!("Write res: {:?}", op.write(&serializer::to_bytes(xdb))); }, Err(e) => { error!("Error: {}",e); diff --git a/src/objects.rs b/src/objects.rs new file mode 100644 index 0000000..46c3d86 --- /dev/null +++ b/src/objects.rs @@ -0,0 +1,211 @@ +use serde::{Deserialize, Serialize}; + +pub enum ChunkType { + Database, + DataSet, + AlbumList, + AlbumItem, + TrackList, + TrackItem, + StringTypes, + PlaylistList, + Playlist, + Unknown +} + +impl From<[u8; 4]> for ChunkType { + fn from(value: [u8; 4]) -> Self { + match value { + [0x6D, 0x68, 0x62, 0x64] => ChunkType::Database, + [0x6D, 0x68, 0x73, 0x64] => ChunkType::DataSet, + [0x6D, 0x68, 0x69, 0x61] => ChunkType::AlbumItem, + [0x6D, 0x68, 0x6C, 0x61] => ChunkType::AlbumList, + [0x6D, 0x68, 0x6C, 0x74] => ChunkType::TrackList, + [0x6D, 0x68, 0x69, 0x74] => ChunkType::TrackItem, + [0x6D, 0x68, 0x6F, 0x64] => ChunkType::StringTypes, + [0x6D, 0x68, 0x6C, 0x70] => ChunkType::PlaylistList, + [0x6D, 0x68, 0x79, 0x70] => ChunkType::Playlist, + _ => ChunkType::Unknown + } + } +} + +impl From for [u8; 4] { + fn from(value: ChunkType) -> Self { + match value { + ChunkType::Database => [0x6D, 0x68, 0x62, 0x64], + ChunkType::DataSet => [0x6D, 0x68, 0x73, 0x64], + ChunkType::AlbumItem => [0x6D, 0x68, 0x69, 0x61], + ChunkType::AlbumList => [0x6D, 0x68, 0x6C, 0x61], + ChunkType::TrackList => [0x6D, 0x68, 0x6C, 0x74], + ChunkType::TrackItem => [0x6D, 0x68, 0x69, 0x74], + ChunkType::StringTypes => [0x6D, 0x68, 0x6F, 0x64], + ChunkType::PlaylistList => [0x6D, 0x68, 0x6C, 0x70], + ChunkType::Playlist => [0x6D, 0x68, 0x79, 0x70], + ChunkType::Unknown => [0x00, 0x00, 0x00, 0x00] + } + } +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct ChunkHeader { + pub chunk_type: [u8; 4], + pub end_of_chunk: u32, + pub children_count: u32 +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct Database { + unknown: u32, + version: u32, + children_count: u32, + id: u64, + unknown1: [u8; 32], + language: u16, + persistent_id: u64, + hash: [u8; 20] +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +pub struct DataSet { + pub data_type: u32 +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct AlbumItem { + number_of_strings: u32, + unknown: u16, + album_id_for_track: u16, + timestamp: u64, + unknown1: u32 +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct TrackItem { + number_of_strings: u32, // number of mhod's count + unique_id: u32, + visible: u32, + filetype: u32, + type1: u8, + type2: u8, + compilation_flag: u8, + stars: u8, + last_modified_time: u32, + size: u32, + length: u32, + track_number: u32, + total_tracks: u32, + year: u32, + bitrate: u32, + sample_rate: u32, + volume: u32, + start_time: u32, + stop_time: u32, + soundcheck: u32, + play_count: u32, + play_count2: u32, + last_played_time: u32, + disc_number: u32, + total_discs: u32, + userid: u32, + date_added: u32, + bookmark_time: u32, + dbid: u64, + checked: u8, + application_rating: u8, + bpm: u16, + artwork_count: u16, + unk9: u16, + artwork_size: u32, + unk11: u32, + sample_rate2: u32, + date_released: u32, + unk14: u32, + unk15: u32, + unk16: u32, + skip_count: u32, + last_skipped: u32, + has_artwork: u8, + skip_when_shuffling: u8, + remember_playback_position: u8, + flag4: u8, + dbid2: u64, + lyrics_flag: u8, + movie_file_flag: u8, + played_mark: u8, + unk17: u8, + unk21: u32, + pregap: u32, + sample_count: u64, + unk25: u32, + postgap: u32, + unk27: u32, + media_type: u32, + season_number: u32, + episode_number: u32, + unk31: [u8; 28], + gapless_data: u32, + unk38: u32, + gapless_track_flag: u16, + gapless_album_flag: u16, + unk39_hash: [u8; 20], + unk40: [u8; 18], + album_id: u16, + mhii_link: u32 +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct StringEntry { // mhod + pub entry_type: u32, + pub unk1: u32, + pub unk2: u32, + pub position: u32, + pub length: u32, + pub unknown: u32, + pub unk4: u32 +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct PlaylistIndexEntry { // mhod + entry_type: u32, + unk1: u32, + unk2: u32, + index_type: u32, + pub count: u32, + null_padding: u64, + null_padding1: u64, + null_padding2: u64, + null_padding3: u64, + null_padding4: u64 +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct LetterJumpEntry { + entry_type: u32, + unk1: u32, + unk2: u32, + index_type: u32, + pub count: u32, + null_padding: u64 +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct JumpTable { + letter: u32, // UTF-16 LE Uppercase with two padding null bytes + entry_num: u32, // the number of the first entry in the corresponding MHOD52 index starting with this letter. Zero-based and incremented by one for each entry, not 4. + count: u32 // the count of entries starting with this letter in the corresponding MHOD52. +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct Playlist { + data_object_child_count: u32, + playlist_item_count: u32, + is_master_playlist_flag: u8, + unk: [u8; 3], + timestamp: u32, + persistent_playlist_id: u64, + unk3: u32, + string_mhod_count: u16, + podcast_flag: u16, + list_sort_order: u32 +} \ No newline at end of file diff --git a/src/serializer.rs b/src/serializer.rs new file mode 100644 index 0000000..6458637 --- /dev/null +++ b/src/serializer.rs @@ -0,0 +1,174 @@ +use crate::{objects::{ChunkHeader, ChunkType, StringEntry}, xobjects::{XArgument, XDatabase, XPlArgument, XSomeList}}; + + +fn string_to_ipod16(str: &String) -> Vec { + str.as_bytes().iter().map(|b| [*b, 0x0]).flatten().collect() +} + +fn x_args_to_bytes(args: &Vec) -> Vec { + args.iter() + .filter(|arg| arg.arg_type <= 15) + .map(|arg| { + let s = string_to_ipod16(&arg.val); + let mut b = bincode::serialize(&StringEntry { + entry_type: arg.arg_type, + unk1: 0, + unk2: 0, + position: 1, + length: s.len() as u32, + unknown: 0, + unk4: 0 + }).unwrap(); + let h = bincode::serialize(&ChunkHeader { + chunk_type: ChunkType::StringTypes.into(), + end_of_chunk: 0x18, + children_count: 16 + 0x18 + s.len() as u32 + }).unwrap(); + b = [h, b, s].concat(); + return b; + }) + .flatten() + .collect() +} + +fn generate_header(ct: ChunkType, header_size: usize, data_len: usize) -> Vec { + let header_size = 12 + header_size as u32; + let header = ChunkHeader{ chunk_type: ct.into(), end_of_chunk: header_size, children_count: header_size + data_len as u32}; + bincode::serialize(&header).unwrap() +} + +fn generate_zeroes(cnt: u32) -> Vec { + let mut v: Vec = Vec::new(); + for i in 0..cnt-12 { + v.push(0x00); + } + v +} + +pub fn to_bytes(xdb: XDatabase) -> Vec { + let mut bytes: Vec = Vec::new(); + for i in 0..xdb.children.len() { + let data_set = xdb.children.get(i).unwrap(); + match &data_set.child { + XSomeList::Playlists(playlists) => { + let mut pl_bytes = Vec::new(); + for u in 0..playlists.len() { + let playlist = playlists.get(u).unwrap(); + + let mut args: Vec = playlist.args.iter() + .map(|arg| match arg { + XPlArgument::String(xarg) => { + let s = string_to_ipod16(&xarg.val); + let mut b = bincode::serialize(&StringEntry { + entry_type: xarg.arg_type, + unk1: 0, + unk2: 0, + position: 1, + length: s.len() as u32, + unknown: 0, + unk4: 0 + }).unwrap(); + let h = bincode::serialize(&ChunkHeader { + chunk_type: ChunkType::StringTypes.into(), + end_of_chunk: 0x18, + children_count: 16 + 0x18 + s.len() as u32 + }).unwrap(); + b = [h, b, s].concat(); + return b; + }, + XPlArgument::IndexEntry(xpl) => { + let mut b = bincode::serialize(&xpl.data).unwrap(); + let mut v: Vec = Vec::new(); + for i in xpl.v.iter() { + v = [v, i.to_le_bytes().to_vec()].concat(); + } + let h = bincode::serialize(&ChunkHeader { + chunk_type: ChunkType::StringTypes.into(), + end_of_chunk: 72, + children_count: 72 + (4 * xpl.v.len() as u32) + }).unwrap(); + b = [h, b, v].concat(); + return b; + }, + XPlArgument::LetterJumpEntry(xjump) => { + let mut b = bincode::serialize(&xjump.data).unwrap(); + let mut v: Vec = Vec::new(); + for i in xjump.v.iter() { + v.append(&mut bincode::serialize(i).unwrap()); + } + let h = bincode::serialize(&ChunkHeader { + chunk_type: ChunkType::StringTypes.into(), + end_of_chunk: 40, + children_count: 40 + (12 * xjump.v.len() as u32) + }).unwrap(); + b = [h, b, v].concat(); + return b; + }, + XPlArgument::RawArgument(raw_arg) => { + raw_arg.to_vec() + } + }) + .flatten() + .collect(); + + pl_bytes.append(&mut generate_header(ChunkType::Playlist, 36,args.len())); + pl_bytes.append(&mut bincode::serialize(&playlist.data).unwrap()); + pl_bytes.append(&mut generate_zeroes(148)); + pl_bytes.append(&mut args); + } + let mhlp = ChunkHeader { chunk_type: ChunkType::PlaylistList.into(), end_of_chunk: 92, children_count: playlists.len() as u32 }; + let mut mhlp = bincode::serialize(&mhlp).unwrap(); + bytes.append(&mut generate_header(ChunkType::DataSet, 84, mhlp.len() + pl_bytes.len() + 808)); + bytes.append(&mut bincode::serialize(&data_set.data).unwrap()); + bytes.append(&mut generate_zeroes(92)); + bytes.append(&mut mhlp); + bytes.append(&mut generate_zeroes(92)); + bytes.append(&mut pl_bytes); + }, + XSomeList::AlbumList(albums) => { + let mut al_bytes = Vec::new(); + for u in 0..albums.len() { + let album = albums.get(u).unwrap(); + let mut args = x_args_to_bytes(&album.args); + al_bytes.append(&mut generate_header(ChunkType::AlbumItem, 76,args.len())); + al_bytes.append(&mut bincode::serialize(&album.data).unwrap()); + al_bytes.append(&mut generate_zeroes(68)); + al_bytes.append(&mut args); + } + let mhla = ChunkHeader { chunk_type: ChunkType::AlbumList.into(), end_of_chunk: 92, children_count: albums.len() as u32 }; + let mut mhla = bincode::serialize(&mhla).unwrap(); + bytes.append(&mut generate_header(ChunkType::DataSet, 84, mhla.len() + 168)); + bytes.append(&mut bincode::serialize(&data_set.data).unwrap()); + bytes.append(&mut generate_zeroes(92)); + bytes.append(&mut mhla); + bytes.append(&mut generate_zeroes(92)); + bytes.append(&mut al_bytes); + }, + XSomeList::TrackList(tracks) => { + let mut tr_bytes = Vec::new(); + for u in 0..tracks.len() { + let track = tracks.get(u).unwrap(); + let mut args = x_args_to_bytes(&track.args); + tr_bytes.append(&mut generate_header(ChunkType::TrackItem, 612,args.len())); + tr_bytes.append(&mut bincode::serialize(&track.data).unwrap()); + tr_bytes.append(&mut generate_zeroes(332)); + tr_bytes.append(&mut args); + } + let mhlt = ChunkHeader { chunk_type: ChunkType::TrackList.into(), end_of_chunk: 92, children_count: tracks.len() as u32 }; + let mut mhlt = bincode::serialize(&mhlt).unwrap(); + bytes.append(&mut generate_header(ChunkType::DataSet, 84, mhlt.len() + tr_bytes.len() + 80)); + bytes.append(&mut bincode::serialize(&data_set.data).unwrap()); + bytes.append(&mut generate_zeroes(92)); + bytes.append(&mut mhlt); + bytes.append(&mut generate_zeroes(92)); + bytes.append(&mut tr_bytes); + } + } + } + let sdb = bincode::serialize(&xdb.data.unwrap()).unwrap(); + let sdb_len = sdb.len(); + let h = xdb.header.unwrap(); + bytes = [sdb, generate_zeroes(h.end_of_chunk - sdb_len as u32), bytes].concat(); + bytes = [generate_header(ChunkType::Database, (h.end_of_chunk - 12) as usize, bytes.len()), bytes].concat(); + bytes +} \ No newline at end of file diff --git a/src/xobjects.rs b/src/xobjects.rs index 1515ee3..a509425 100644 --- a/src/xobjects.rs +++ b/src/xobjects.rs @@ -1,38 +1,38 @@ -use crate::{JumpTable, LetterJumpEntry, PlaylistIndexEntry}; +use crate::objects::{JumpTable, LetterJumpEntry, PlaylistIndexEntry, ChunkHeader, Database, DataSet, TrackItem, AlbumItem, Playlist}; #[derive(Debug, serde::Serialize)] pub struct XDatabase { - pub header: Option, - pub data: Option, + pub header: Option, + pub data: Option, pub children: Vec } #[derive(Debug, serde::Serialize)] pub struct XDataSet { - pub header: crate::ChunkHeader, - pub data: crate::DataSet, + pub header: ChunkHeader, + pub data: DataSet, pub child: XSomeList } #[derive(Debug, serde::Serialize)] pub struct XTrackItem { - pub header: crate::ChunkHeader, - pub data: crate::TrackItem, + pub header: ChunkHeader, + pub data: TrackItem, pub args: Vec } #[derive(Debug, serde::Serialize)] pub struct XAlbumItem { - pub header: crate::ChunkHeader, - pub data: crate::AlbumItem, + pub header: ChunkHeader, + pub data: AlbumItem, pub args: Vec } #[derive(Debug, serde::Serialize)] pub struct XPlaylist { - pub header: crate::ChunkHeader, - pub data: crate::Playlist, + pub header: ChunkHeader, + pub data: Playlist, pub args: Vec } @@ -40,7 +40,8 @@ pub struct XPlaylist { pub enum XPlArgument { String(XArgument), IndexEntry(XPlaylistIndexEntry), - LetterJumpEntry(XLetterJump) + LetterJumpEntry(XLetterJump), + RawArgument(Vec) } #[derive(Debug, serde::Serialize)]