refactor
new file: src/deserializer.rs modified: src/main.rs new file: src/objects.rs new file: src/serializer.rs modified: src/xobjects.rs
This commit is contained in:
parent
fdecbcc093
commit
05d07e0415
176
src/deserializer.rs
Normal file
176
src/deserializer.rs
Normal file
@ -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<ChunkHeader> = 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<JumpTable> = 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::<XDatabase>(&xdb).unwrap().as_bytes());
|
||||||
|
//info!("Result: {:?}", r);
|
||||||
|
xdb
|
||||||
|
}
|
561
src/main.rs
561
src/main.rs
@ -1,564 +1,17 @@
|
|||||||
use std::{fs::File, io::{Read, Write}};
|
use std::{fs::File, io::{Read, Write}};
|
||||||
use env_logger::Builder;
|
use env_logger::Builder;
|
||||||
use log::{error, info, LevelFilter};
|
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;
|
mod xobjects;
|
||||||
|
mod deserializer;
|
||||||
enum ChunkType {
|
mod serializer;
|
||||||
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<ChunkType> 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<ChunkHeader> = 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<JumpTable> = 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::<XDatabase>(&xdb).unwrap().as_bytes());
|
|
||||||
//info!("Result: {:?}", r);
|
|
||||||
xdb
|
|
||||||
}
|
|
||||||
|
|
||||||
fn string_to_ipod16(str: &String) -> Vec<u8> {
|
|
||||||
str.as_bytes().iter().map(|b| [*b, 0x0]).flatten().collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn x_args_to_bytes(args: &Vec<XArgument>) -> Vec<u8> {
|
|
||||||
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<u8> {
|
|
||||||
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<u8> {
|
|
||||||
let mut v: Vec<u8> = Vec::new();
|
|
||||||
for i in 0..cnt-12 {
|
|
||||||
v.push(0x00);
|
|
||||||
}
|
|
||||||
v
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["staticlib", "cdylib", "lib"]
|
crate-type = ["staticlib", "cdylib", "lib"]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
fn to_bytes(xdb: XDatabase) -> Vec<u8> {
|
|
||||||
let mut bytes: Vec<u8> = 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<u8> = 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<u8> = 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<u8> = 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() {
|
fn main() {
|
||||||
|
|
||||||
// Initialize the logger with 'info' as the default level
|
// Initialize the logger with 'info' as the default level
|
||||||
@ -567,15 +20,15 @@ fn main() {
|
|||||||
.init();
|
.init();
|
||||||
|
|
||||||
// /Users/michael/Documents/ipod/iTunes/iTunesDB
|
// /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();
|
let mut buf = Vec::new();
|
||||||
match f.read_to_end(&mut buf) {
|
match f.read_to_end(&mut buf) {
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
let data = &buf[..n];
|
let data = &buf[..n];
|
||||||
let xdb = parse_bytes(data);
|
let xdb = deserializer::parse_bytes(data);
|
||||||
info!("XDB: {:?}", xdb);
|
info!("XDB: {:?}", xdb);
|
||||||
//let mut op = File::create("outdb").unwrap();
|
let mut op = File::create("outdb").unwrap();
|
||||||
//info!("Write res: {:?}", op.write(&to_bytes(xdb)));
|
info!("Write res: {:?}", op.write(&serializer::to_bytes(xdb)));
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error: {}",e);
|
error!("Error: {}",e);
|
||||||
|
211
src/objects.rs
Normal file
211
src/objects.rs
Normal file
@ -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<ChunkType> 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
|
||||||
|
}
|
174
src/serializer.rs
Normal file
174
src/serializer.rs
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
use crate::{objects::{ChunkHeader, ChunkType, StringEntry}, xobjects::{XArgument, XDatabase, XPlArgument, XSomeList}};
|
||||||
|
|
||||||
|
|
||||||
|
fn string_to_ipod16(str: &String) -> Vec<u8> {
|
||||||
|
str.as_bytes().iter().map(|b| [*b, 0x0]).flatten().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn x_args_to_bytes(args: &Vec<XArgument>) -> Vec<u8> {
|
||||||
|
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<u8> {
|
||||||
|
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<u8> {
|
||||||
|
let mut v: Vec<u8> = Vec::new();
|
||||||
|
for i in 0..cnt-12 {
|
||||||
|
v.push(0x00);
|
||||||
|
}
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(xdb: XDatabase) -> Vec<u8> {
|
||||||
|
let mut bytes: Vec<u8> = 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<u8> = 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<u8> = 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<u8> = 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
|
||||||
|
}
|
@ -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)]
|
#[derive(Debug, serde::Serialize)]
|
||||||
pub struct XDatabase {
|
pub struct XDatabase {
|
||||||
pub header: Option<crate::ChunkHeader>,
|
pub header: Option<ChunkHeader>,
|
||||||
pub data: Option<crate::Database>,
|
pub data: Option<Database>,
|
||||||
pub children: Vec<XDataSet>
|
pub children: Vec<XDataSet>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize)]
|
#[derive(Debug, serde::Serialize)]
|
||||||
pub struct XDataSet {
|
pub struct XDataSet {
|
||||||
pub header: crate::ChunkHeader,
|
pub header: ChunkHeader,
|
||||||
pub data: crate::DataSet,
|
pub data: DataSet,
|
||||||
pub child: XSomeList
|
pub child: XSomeList
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize)]
|
#[derive(Debug, serde::Serialize)]
|
||||||
pub struct XTrackItem {
|
pub struct XTrackItem {
|
||||||
pub header: crate::ChunkHeader,
|
pub header: ChunkHeader,
|
||||||
pub data: crate::TrackItem,
|
pub data: TrackItem,
|
||||||
pub args: Vec<XArgument>
|
pub args: Vec<XArgument>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize)]
|
#[derive(Debug, serde::Serialize)]
|
||||||
pub struct XAlbumItem {
|
pub struct XAlbumItem {
|
||||||
pub header: crate::ChunkHeader,
|
pub header: ChunkHeader,
|
||||||
pub data: crate::AlbumItem,
|
pub data: AlbumItem,
|
||||||
pub args: Vec<XArgument>
|
pub args: Vec<XArgument>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize)]
|
#[derive(Debug, serde::Serialize)]
|
||||||
pub struct XPlaylist {
|
pub struct XPlaylist {
|
||||||
pub header: crate::ChunkHeader,
|
pub header: ChunkHeader,
|
||||||
pub data: crate::Playlist,
|
pub data: Playlist,
|
||||||
pub args: Vec<XPlArgument>
|
pub args: Vec<XPlArgument>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +40,8 @@ pub struct XPlaylist {
|
|||||||
pub enum XPlArgument {
|
pub enum XPlArgument {
|
||||||
String(XArgument),
|
String(XArgument),
|
||||||
IndexEntry(XPlaylistIndexEntry),
|
IndexEntry(XPlaylistIndexEntry),
|
||||||
LetterJumpEntry(XLetterJump)
|
LetterJumpEntry(XLetterJump),
|
||||||
|
RawArgument(Vec<u8>)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize)]
|
#[derive(Debug, serde::Serialize)]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user