#include "iPodSD.h" #include #include // get 3 bytes from data (used in iTunesSD1) static __forceinline unsigned long get3(const uint8_t * data) { unsigned long ret = 0; ret += ((unsigned long) data[0]) << 16; ret += ((unsigned long) data[1]) << 8; ret += ((unsigned long) data[2]); return ret; } //write 3 bytes normal (used in iTunesSD1) static __forceinline void put3(const unsigned long number, uint8_t * data) { data[0] = (uint8_t)(number >> 16) & 0xff; data[1] = (uint8_t)(number >> 8) & 0xff; data[2] = (uint8_t)number & 0xff; } // pass data and ptr, updates ptr automatically (by reference) static __forceinline void write_uint64_t(uint8_t *data, size_t &offset, uint64_t value) { memcpy(&data[offset], &value, 8); offset+=8; } // pass data and ptr, updates ptr automatically (by reference) static __forceinline void write_uint32_t(uint8_t *data, size_t &offset, uint32_t value) { memcpy(&data[offset], &value, 4); offset+=4; } // pass data and ptr, updates ptr automatically (by reference) static __forceinline void write_uint16_t(uint8_t *data, size_t &offset, uint16_t value) { memcpy(&data[offset], &value, 2); offset+=2; } // pass data and ptr, updates ptr automatically (by reference) static __forceinline void write_uint8_t(uint8_t *data, size_t &offset, uint8_t value) { data[offset++] = value; } // pass data and ptr, updates ptr automatically (by reference) static __forceinline void write_header(uint8_t *data, size_t &offset, const char *header) { data[offset++] = header[0]; data[offset++] = header[1]; data[offset++] = header[2]; data[offset++] = header[3]; } // Case insensitive version of wcsstr static wchar_t *wcsistr (const wchar_t *s1, const wchar_t *s2) { wchar_t *cp = (wchar_t*) s1; wchar_t *s, *t, *endp; wchar_t l, r; endp = (wchar_t*)s1 + ( lstrlen(s1) - lstrlen(s2)) ; while (cp && *cp && (cp <= endp)) { s = cp; t = (wchar_t*)s2; while (s && *s && t && *t) { l = towupper(*s); r = towupper(*t); if (l != r) break; s++, t++; } if (*t == 0) return cp; cp = CharNext(cp); } return NULL; } ////////////////////////////////////////////////////////////////////// // iTunesSD1 - Classes for dealing with the iPodShuffle ////////////////////////////////////////////////////////////////////// iTunesSD1::iTunesSD1() { } iTunesSD1::~iTunesSD1() { } long iTunesSD1::write(const iPod_mhlt::mhit_map_t *songs, unsigned char * data, const unsigned long datasize) { #ifdef IPODDB_PROFILER profiler(iPodDB__iTunesSD_write); #endif const unsigned int numsongs = songs->size(); const unsigned int total_size = 18 + (numsongs * 558); ASSERT(datasize >= total_size); if(datasize < total_size) return(-1); long ptr=0; put3(numsongs, &data[ptr]); ptr+=3; put3(0x010600, &data[ptr]); ptr+=3; put3(0x12, &data[ptr]); ptr+=3; put3(0, &data[ptr]); ptr+=3; put3(0, &data[ptr]); ptr+=3; put3(0, &data[ptr]); ptr+=3; iPod_mhlt::mhit_map_t::const_iterator begin = songs->begin(); iPod_mhlt::mhit_map_t::const_iterator end = songs->end(); for(iPod_mhlt::mhit_map_t::const_iterator it = begin; it != end; it++) { iPod_mhit *m = ((*it).second); iTunesSD_Song song(m); long ret = song.write(&data[ptr], datasize - ptr); if (ret < 0) return ret; ptr += ret; } return(ptr); } iTunesSD_Song::iTunesSD_Song(const iPod_mhit *m) : size_total(0x22e), starttime(0), stoptime(0), volume(0x64), filetype(0), playflags(iTunesSD_Song::SHUFFLE) { memset(filename, 0, (SDSONG_FILENAME_LEN + 1) * sizeof(wchar_t)); iPod_mhod *mhod = m->FindString(MHOD_LOCATION); ASSERT(mhod); if(mhod) { // Convert from HFS format (:iPod_Control:Music:F00:filename) to quasi-FAT format (/iPod_Control/Music/F00/filename) filepaths SetFilename(mhod->str); wchar_t *w = filename; while(w && *w != '\0') { if(*w == ':') *w = '/'; w = CharNext(w); } SetStartTime(m->starttime); SetStopTime(m->stoptime); int volume = (int)m->volume; // If Sound Check information is present, use that instead of volume if(m->soundcheck != 0) { // This code converts SoundCheck back into a gain value, then into a -255 to 255 mhit::volume value const double gain = -10.0 * log10(m->soundcheck / 1000.0); volume = (int)(gain * 12.75); // XXX - this might not be the best way to convert the gain value... } if(volume < -255) volume = -255; else if(volume > 255) volume = 255; // Convert the volume value into a percentage for SetVolume SetVolume((int)((double)volume / 2.55)); // To determine the filetype, first check the MHOD_FILETYPE type. If that isn't available, fallback to file extension iPod_mhod *mtype = m->FindString(MHOD_FILETYPE); if(mtype != NULL) { if(wcsistr(mtype->str, L"MPEG") != NULL || wcsistr(mtype->str, L"MP3") != NULL) filetype = iTunesSD_Song::MP3; else if(wcsistr(mtype->str, L"AAC") != NULL) filetype = iTunesSD_Song::AAC; else if(wcsistr(mtype->str, L"WAV") != NULL) filetype = iTunesSD_Song::WAV; } if(filetype == 0) { if(wcsistr(mhod->str, L".mp3") != NULL) filetype = iTunesSD_Song::MP3; else if(wcsistr(mhod->str, L".m4a") != NULL || wcsistr(mhod->str, L".m4b") != NULL || wcsistr(mhod->str, L".m4p") != NULL) filetype = iTunesSD_Song::AAC; else if(wcsistr(mhod->str, L".wav") != NULL) filetype = iTunesSD_Song::WAV; } ASSERT(filetype != 0); if(filename == 0) filetype = iTunesSD_Song::MP3; // Default to mp3 if(wcsistr(mhod->str, L".m4b") != NULL) playflags = iTunesSD_Song::BOOKMARKABLE; // Only playback in normal mode else playflags = iTunesSD_Song::SHUFFLE; // Playable in normal/shuffle modes, but not bookmarkable } } long iTunesSD_Song::write(unsigned char * data, const unsigned long datasize) { #ifdef IPODDB_PROFILER profiler(iPodDB__iTunesSD_Song_write); #endif long ptr=0; ASSERT(size_total == 0x22e); ASSERT(filetype != 0); put3(size_total, &data[ptr]); ptr+=3; put3(0x005aa501, &data[ptr]); ptr+=3; put3(starttime, &data[ptr]); ptr+=3; put3(0, &data[ptr]); ptr+=3; put3(0, &data[ptr]); ptr+=3; put3(stoptime, &data[ptr]); ptr+=3; put3(0, &data[ptr]); ptr+=3; put3(0, &data[ptr]); ptr+=3; put3(volume, &data[ptr]); ptr+=3; put3(filetype, &data[ptr]); ptr+=3; put3(0x200, &data[ptr]); ptr+=3; const unsigned int bufSize = (SDSONG_FILENAME_LEN + 1) * sizeof(wchar_t); memcpy(&data[ptr], filename, bufSize); ptr+=bufSize; put3(playflags, &data[ptr]); ptr+=3; ASSERT(size_total == ptr); return(ptr); } void iTunesSD_Song::SetFilename(const wchar_t *filename) { #ifdef IPODDB_PROFILER profiler(iPodDB__iTunesStats_SetFilename); #endif ASSERT(filename != NULL); if(filename == NULL) return; if(filename) { lstrcpyn(this->filename, filename, SDSONG_FILENAME_LEN); } else { memset(this->filename, 0, SDSONG_FILENAME_LEN * sizeof(wchar_t)); } } // Accepts values from -100 to 100, with 0 meaning no volume change void iTunesSD_Song::SetVolume(const int percent) { int p = percent; if(p > 100) p = 100; else if(p < -100) p = -100; // Volume ranges from 0 (-100%) to 100 (0%) to 200 (100%) volume = (unsigned int)(percent + 100); } /* Shadow DB version 2 */ long iTunesSD2::write(const iPod_mhlt *songs, const iPod_mhlp *playlists, unsigned char * data, const unsigned long datasize) { uint32_t numsongs = songs->GetChildrenCount(); uint32_t numplaylists = playlists->GetChildrenCount(); size_t offset=0; size_t ptr=0; if (datasize < 64) return -1; write_header(data, ptr, "bdhs"); write_uint32_t(data, ptr, 0x02000003); /* also have seen 0x02010001, perhaps a DB version number? */ write_uint32_t(data, ptr, 64); /* length of header */ write_uint32_t(data, ptr, numsongs); write_uint32_t(data, ptr, numplaylists); /* number of playlists */ write_uint32_t(data, ptr, 0); write_uint32_t(data, ptr, 0); write_uint8_t(data, ptr, 0); /* volume limit */ write_uint8_t(data, ptr, 1); /* voiceover */ write_uint16_t(data, ptr, 0); write_uint32_t(data, ptr, numsongs); /* TODO number of tracks w/o podcasts and audiobooks*/ write_uint32_t(data, ptr, 64); /* track header offset */ write_uint32_t(data, ptr, 64+20 + numsongs*4+iTunesSD2_Song::header_size*numsongs); /* playlist header offset */ write_uint32_t(data, ptr, 0); write_uint32_t(data, ptr, 0); write_uint32_t(data, ptr, 0); write_uint32_t(data, ptr, 0); write_uint32_t(data, ptr, 0); offset = 64; uint32_t hths_header_size = 20+numsongs*4; if (datasize - ptr < hths_header_size) return -1; write_header(data, ptr, "hths"); write_uint32_t(data, ptr, hths_header_size); /* header length */ write_uint32_t(data, ptr, numsongs); write_uint32_t(data, ptr, 0); write_uint32_t(data, ptr, 0); offset += hths_header_size; /* positions for each track */ for (size_t i=0;iGetTrack(i); long ret = iTunesSD2_Song::write(m, &data[ptr], datasize - ptr); if (ret < 0) return ret; ptr += ret; offset += ret; } uint32_t podcast_playlist_count=0; for (size_t i=0;iGetPlaylist(i); if (p->podcastflag) podcast_playlist_count++; } uint32_t hphs_header_size = 20 + numplaylists*4; if (datasize - ptr < hphs_header_size) return -1; write_header(data, ptr, "hphs"); write_uint32_t(data, ptr, hphs_header_size); /* header length */ write_uint32_t(data, ptr, numplaylists); write_uint16_t(data, ptr, 0); write_uint16_t(data, ptr, numplaylists-podcast_playlist_count); /* non-podcast playlists */ write_uint16_t(data, ptr, 1); /* master playlists */ write_uint16_t(data, ptr, numplaylists); /* non-audiobook playlists */ offset += hphs_header_size; /* write offsets for each track */ for (size_t i=0;iGetPlaylist(i); write_uint32_t(data, ptr, offset); offset += p->GetMhipChildrenCount()*4 + 44; } iPod_mhyp *master_playlist = playlists->GetPlaylist(0); /* write playlists */ for (size_t i=0;iGetPlaylist(i); long ret = iTunesSD2_Playlist::write(master_playlist, p, &data[ptr], datasize - ptr); if (ret < 0) return ret; ptr += ret; } return ptr; } uint32_t iTunesSD2_Song::header_size = 372; long iTunesSD2_Song::write(const iPod_mhit *mhit, unsigned char *data, const unsigned long datasize) { if (datasize < header_size) return -1; size_t ptr=0; write_header(data, ptr, "rths"); write_uint32_t(data, ptr, header_size); /* length of header */ write_uint32_t(data, ptr, mhit->starttime); /* start time, in milliseconds */ write_uint32_t(data, ptr, mhit->stoptime); /* stop time, in milliseconds */ write_uint32_t(data, ptr, mhit->volume); /* volume */ switch(mhit->filetype) { case FILETYPE_WAV: write_uint32_t(data, ptr, iTunesSD_Song::WAV); /* file type */ break; case FILETYPE_M4A: case 0x4d344220: case 0x4d345020: write_uint32_t(data, ptr, iTunesSD_Song::AAC); /* file type */ break; case FILETYPE_MP3: default: write_uint32_t(data, ptr, iTunesSD_Song::MP3); /* file type */ break; } iPod_mhod *mhod = mhit->FindString(MHOD_LOCATION); char filename[256] = {0}; int converted = WideCharToMultiByte(CP_UTF8, 0, mhod->str, -1, filename, 256, 0, 0); for (int i=0;ibookmarktime); /* bookmark time */ write_uint8_t(data, ptr, 0); /* skip flag */ write_uint8_t(data, ptr, mhit->rememberPosition); /* remember playback position */ write_uint8_t(data, ptr, 0); /* part of gapless album */ write_uint8_t(data, ptr, 0); write_uint32_t(data, ptr, mhit->pregap); /* pre-gap */ write_uint32_t(data, ptr, mhit->postgap); /* post-gap */ write_uint64_t(data, ptr, mhit->samplecount); /* number of samples */ write_uint32_t(data, ptr, mhit->gaplessData); /* gapless data */ write_uint32_t(data, ptr, 0); write_uint32_t(data, ptr, mhit->album_id); /* album ID */ write_uint16_t(data, ptr, mhit->tracknum); /* track number */ write_uint16_t(data, ptr, mhit->cdnum); /* disc number */ write_uint32_t(data, ptr, 0); write_uint32_t(data, ptr, 0); write_uint64_t(data, ptr, mhit->dbid); /* dbid */ write_uint32_t(data, ptr, 0); /* artist ID */ write_uint32_t(data, ptr, 0); write_uint32_t(data, ptr, 0); write_uint32_t(data, ptr, 0); write_uint32_t(data, ptr, 0); write_uint32_t(data, ptr, 0); write_uint32_t(data, ptr, 0); write_uint32_t(data, ptr, 0); write_uint32_t(data, ptr, 0); return ptr; } long iTunesSD2_Playlist::write(const iPod_mhyp *master_playlist, const iPod_mhyp *playlist, unsigned char * data, const unsigned long datasize) { size_t ptr=0; uint32_t tracks = playlist->GetMhipChildrenCount(); uint32_t header_size = 44 + tracks*4; if (datasize < header_size) return -1; write_header(data, ptr, "lphs"); write_uint32_t(data, ptr, header_size); /* header length */ write_uint32_t(data, ptr, tracks); /* number of tracks */ /* number of music tracks TODO: special handling for master playlist */ if (playlist->podcastflag) write_uint32_t(data, ptr, 0); else write_uint32_t(data, ptr, tracks); write_uint64_t(data, ptr, playlist->playlistID); /* playlist ID */ /* playlist type */ if (playlist->podcastflag) write_uint32_t(data, ptr, 3); /* podcast */ else if (playlist->hidden) write_uint32_t(data, ptr, 1); /* master playlist */ else write_uint32_t(data, ptr, 2); /* normal */ write_uint32_t(data, ptr, 0); write_uint32_t(data, ptr, 0); write_uint32_t(data, ptr, 0); write_uint32_t(data, ptr, 0); if (master_playlist == playlist) { for (uint32_t i=0;iGetPlaylistEntry(i); uint32_t master_index = master_playlist->FindPlaylistEntry(item->songindex); assert(master_index != -1); write_uint32_t(data, ptr, master_index); } } return ptr; }