ITunesDB/src/main.rs
alterwain b94a19b192 modified: Cargo.lock
modified:   Cargo.toml
	modified:   src/main.rs
2025-02-03 17:02:25 +03:00

295 lines
9.2 KiB
Rust

use std::{fs::File, io::Read};
use env_logger::Builder;
use log::{error, info, LevelFilter};
use rkyv::{deserialize, rancor::Error, Archive, Deserialize, Serialize};
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::AlbumList,
[0x6D, 0x68, 0x6C, 0x61] => ChunkType::AlbumItem,
[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
}
}
}
#[derive(Archive, Deserialize, Serialize, Debug, PartialEq)]
#[rkyv(compare(PartialEq), derive(Debug))]
struct Database {
header_length: u32,
total_length: u32,
unknown: u32,
version: u32,
children_count: u32,
id: u64,
unknown1: [u8; 32],
language: u16,
persistent_id: u64,
hash: [u8; 20]
}
#[derive(Archive, Deserialize, Serialize, Debug, PartialEq)]
#[rkyv(compare(PartialEq), derive(Debug))]
struct DataSet {
header_length: u32,
total_length: u32,
data_type: u32
}
#[derive(Archive, Deserialize, Serialize, Debug, PartialEq)]
#[rkyv(compare(PartialEq), derive(Debug))]
struct AlbumItem {
header_length: u32,
total_length: u32, // length of header + all child records
number_of_strings: u32,
unknown: u16,
album_id_for_track: u16,
timestamp: u64,
unknown1: u32
}
#[derive(Archive, Deserialize, Serialize, Debug, PartialEq)]
#[rkyv(compare(PartialEq), derive(Debug))]
struct TrackItem {
header_length: u32,
total_length: u32, // length of header + all child records
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(Archive, Deserialize, Serialize, Debug, PartialEq)]
#[rkyv(compare(PartialEq), derive(Debug))]
struct Entry { // mhod
header_length: u32,
total_length: u32,
entry_type: u32,
unk1: u32,
unk2: u32,
position: u32,
length: u32,
unknown: u32,
unk4: u32
}
#[derive(Archive, Deserialize, Serialize, Debug, PartialEq)]
#[rkyv(compare(PartialEq), derive(Debug))]
struct Playlist {
header_length: u32,
total_length: u32, // length of header + all child records
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
}
#[derive(Archive, Deserialize, Serialize, Debug, PartialEq)]
#[rkyv(compare(PartialEq), derive(Debug))]
struct Header {
header_length: u32,
total_length: u32
}
enum ChunkState {
Header,
Data
}
fn db(data: &[u8]) {
let mut state = ChunkState::Header;
let mut chunk_type = None;
let mut i = 0;
while i < data.len() {
state = match state {
ChunkState::Header => {
let a: [u8; 4] = data[i..i+4].try_into().unwrap();
chunk_type = Some(ChunkType::from(a));
i += 4;
ChunkState::Data
},
ChunkState::Data => {
let mut u = 0;
match chunk_type.unwrap() {
ChunkType::Database => {
let db = rkyv::access::<ArchivedDatabase, Error>(&data[i..i+std::mem::size_of::<Database>()]).unwrap();
info!("val: {:?}", db);
info!("i: {}", data[i]);
u = usize::try_from(db.header_length).unwrap() - 4;
},
ChunkType::DataSet => {
let ds = rkyv::access::<ArchivedDataSet, Error>(&data[i..i+std::mem::size_of::<DataSet>()]).unwrap();
info!("val: {:?}", ds);
u = usize::try_from(ds.total_length).unwrap() - 4;
},
ChunkType::AlbumList => {
info!("AlbumList");
let header = rkyv::access::<ArchivedHeader, Error>(&data[i..i+std::mem::size_of::<Header>()]).unwrap();
u = usize::try_from(header.total_length).unwrap() - 4;
},
ChunkType::AlbumItem => {
let ai = rkyv::access::<ArchivedAlbumItem, Error>(&data[i..i+std::mem::size_of::<AlbumItem>()]).unwrap();
info!("val: {:?}", ai);
u = usize::try_from(ai.total_length).unwrap() - 4;
},
ChunkType::TrackList => {
info!("TrackList");
let header = rkyv::access::<ArchivedHeader, Error>(&data[i..i+std::mem::size_of::<Header>()]).unwrap();
u = usize::try_from(header.total_length).unwrap() - 4;
},
ChunkType::TrackItem => {
let ti = rkyv::access::<ArchivedTrackItem, Error>(&data[i..i+std::mem::size_of::<TrackItem>()]).unwrap();
info!("val: {:?}", ti);
u = usize::try_from(ti.total_length).unwrap() - 4;
},
ChunkType::StringTypes => {
let header = rkyv::access::<ArchivedHeader, Error>(&data[i..i+std::mem::size_of::<Header>()]).unwrap();
u = usize::try_from(header.header_length).unwrap() - 4;
let header_offset: usize = (header.total_length + 4) as usize;
let str_end: usize = (header.header_length - 4) as usize;
let entry = rkyv::access::<ArchivedEntry, Error>(&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);
},
ChunkType::PlaylistList => {
let header = rkyv::access::<ArchivedHeader, Error>(&data[i..i+std::mem::size_of::<Header>()]).unwrap();
info!("Playlists count: {}", header.header_length);
u = usize::try_from(header.total_length).unwrap() - 4;
},
ChunkType::Playlist => {
let playlist = rkyv::access::<ArchivedPlaylist, Error>(&data[i..i+std::mem::size_of::<Playlist>()]).unwrap();
info!("playlist: {:?}", playlist);
u = usize::try_from(playlist.total_length).unwrap() - 4;
},
_ => return
}
i += u;
chunk_type = None;
ChunkState::Header
}
}
}
}
fn main() {
// Initialize the logger with 'info' as the default level
Builder::new()
.filter(None, LevelFilter::Info)
.init();
let mut f = File::open("D:\\Documents\\iTunes\\iTunesDB").unwrap();
let mut buf = Vec::new();
match f.read_to_end(&mut buf) {
Ok(n) => {
let data = &buf[..n];
db(data);
},
Err(e) => {
error!("Error: {}",e);
}
}
}