/* * * * Copyright (c) 2004 Samuel Wood (sam.wood@gmail.com) * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * * */ // For more information on how all this stuff works, see: // http://www.ipodlinux.org/ITunesDB // iPodDB.cpp: implementation of the iPod classes. // ////////////////////////////////////////////////////////////////////// #pragma warning( disable : 4786) #include "iPodDB.h" #include #include #include #include #include #include #include /* #ifdef ASSERT #undef ASSERT #define ASSERT(x) {} #endif */ //#define IPODDB_PROFILER // Uncomment to enable profiler measurments #ifdef IPODDB_PROFILER /* profiler code from Foobar2000's PFC library: * * Copyright (c) 2001-2003, Peter Pawlowski * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ class profiler_static { private: const char * name; __int64 total_time,num_called; public: profiler_static(const char * p_name) { name = p_name; total_time = 0; num_called = 0; } ~profiler_static() { char blah[512] = {0}; char total_time_text[128] = {0}; char num_text[128] = {0}; _i64toa(total_time,total_time_text,10); _i64toa(num_called,num_text,10); _snprintf(blah, sizeof(blah), "profiler: %s - %s cycles (executed %s times)\n",name,total_time_text,num_text); OutputDebugStringA(blah); } void add_time(__int64 delta) {total_time+=delta;num_called++;} }; class profiler_local { private: static __int64 get_timestamp(); __int64 start; profiler_static * owner; public: profiler_local(profiler_static * p_owner) { owner = p_owner; start = get_timestamp(); } ~profiler_local() { __int64 end = get_timestamp(); owner->add_time(end-start); } }; __declspec(naked) __int64 profiler_local::get_timestamp() { __asm { rdtsc ret } } #define profiler(name) \ static profiler_static profiler_static_##name(#name); \ profiler_local profiler_local_##name(&profiler_static_##name); #endif #ifdef _DEBUG #define MYDEBUG_NEW new( _NORMAL_BLOCK, __FILE__, __LINE__) // Replace _NORMAL_BLOCK with _CLIENT_BLOCK if you want the //allocations to be of _CLIENT_BLOCK type #define _CRTDBG_MAP_ALLOC #include #include #undef THIS_FILE static char THIS_FILE[] = __FILE__; #define new MYDEBUG_NEW #endif // // useful functions ////////////////////////////////////////////////////////////////////// inline BOOL WINAPI IsCharSpaceW(wchar_t c) { return (c == L' ' || c == L'\t'); } inline bool IsTheW(const wchar_t *str) { if (str && (str[0] == L't' || str[0] == L'T') && (str[1] == L'h' || str[1] == L'H') && (str[2] == L'e' || str[2] == L'E') && (str[3] == L' ')) return true; else return false; } #define SKIP_THE_AND_WHITESPACE(x) { wchar_t *save##x=(wchar_t*)x; while (IsCharSpaceW(*x) && *x) x++; if (IsTheW(x)) x+=4; while (IsCharSpaceW(*x)) x++; if (!*x) x=save##x; } ///#define SKIP_THE_AND_WHITESPACE(x) { while (!iswalnum(*x) && *x) x++; if (!_wcsnicmp(x,L"the ",4)) x+=4; while (*x == L' ') x++; } int STRCMP_NULLOK(const wchar_t *pa, const wchar_t *pb) { if (!pa) pa=L""; else SKIP_THE_AND_WHITESPACE(pa) if (!pb) pb=L""; else SKIP_THE_AND_WHITESPACE(pb) return lstrcmpi(pa,pb); } #undef SKIP_THE_AND_WHITESPACE // convert Macintosh timestamp to windows timestamp time_t mactime_to_wintime (const unsigned long mactime) { if (mactime != 0) return (time_t)(mactime - 2082844800); else return (time_t)mactime; } // convert windows timestamp to Macintosh timestamp unsigned long wintime_to_mactime (const __time64_t time) { return (unsigned long)(time + 2082844800); } char * UTF16_to_UTF8(wchar_t * str) { const unsigned int tempstrLen = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL); char * tempstr=(char *)malloc(tempstrLen + 1); int ret=WideCharToMultiByte( CP_UTF8, 0, str, -1, tempstr, tempstrLen, NULL, NULL ); tempstr[tempstrLen]='\0'; if (!ret) DWORD bob=GetLastError(); return tempstr; } wchar_t* UTF8_to_UTF16(char *str) { const unsigned int tempstrLen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); wchar_t *tempstr = (wchar_t*)malloc((tempstrLen * 2) + 2); MultiByteToWideChar(CP_UTF8, 0, str, -1, tempstr, tempstrLen); tempstr[tempstrLen] = '\0'; return tempstr; } // get 2 bytes from data, reversed static __forceinline uint16_t rev2(const uint8_t * data) { uint16_t ret; ret = ((uint16_t) data[1]) << 8; ret += ((uint16_t) data[0]); return ret; } static __forceinline void rev2(const unsigned short number, unsigned char * data) { data[1] = (unsigned char)(number >> 8) & 0xff; data[0] = (unsigned char)number & 0xff; } // get 4 bytes from data, reversed static __forceinline uint32_t rev4(const uint8_t * data) { unsigned long ret; ret = ((unsigned long) data[3]) << 24; ret += ((unsigned long) data[2]) << 16; ret += ((unsigned long) data[1]) << 8; ret += ((unsigned long) data[0]); return ret; } // get 4 bytes from data static __forceinline uint32_t get4(const uint8_t * data) { unsigned long ret; ret = ((unsigned long) data[0]) << 24; ret += ((unsigned long) data[1]) << 16; ret += ((unsigned long) data[2]) << 8; ret += ((unsigned long) data[3]); return ret; } // get 8 bytes from data static __forceinline unsigned __int64 get8(const uint8_t * data) { unsigned __int64 ret; ret = get4(data); ret = ret << 32; ret += get4(&data[4]); return ret; } // reverse 8 bytes in place static __forceinline unsigned __int64 rev8(uint64_t number) { unsigned __int64 ret; ret = (number&0x00000000000000FF) << 56; ret+= (number&0x000000000000FF00) << 40; ret+= (number&0x0000000000FF0000) << 24; ret+= (number&0x00000000FF000000) << 8; ret+= (number&0x000000FF00000000) >> 8; ret+= (number&0x0000FF0000000000) >> 24; ret+= (number&0x00FF000000000000) >> 40; ret+= (number&0xFF00000000000000) >> 56; return ret; } //write 4 bytes reversed static __forceinline void rev4(const unsigned long number, uint8_t * data) { data[3] = (uint8_t)(number >> 24) & 0xff; data[2] = (uint8_t)(number >> 16) & 0xff; data[1] = (uint8_t)(number >> 8) & 0xff; data[0] = (uint8_t)number & 0xff; } //write 4 bytes normal static __forceinline void put4(const unsigned long number, uint8_t * data) { data[0] = (uint8_t)(number >> 24) & 0xff; data[1] = (uint8_t)(number >> 16) & 0xff; data[2] = (uint8_t)(number >> 8) & 0xff; data[3] = (uint8_t)number & 0xff; } // write 8 bytes normal static __forceinline void put8(const unsigned __int64 number, uint8_t * data) { data[0] = (uint8_t)(number >> 56) & 0xff; data[1] = (uint8_t)(number >> 48) & 0xff; data[2] = (uint8_t)(number >> 40) & 0xff; data[3] = (uint8_t)(number >> 32) & 0xff; data[4] = (uint8_t)(number >> 24) & 0xff; data[5] = (uint8_t)(number >> 16) & 0xff; data[6] = (uint8_t)(number >> 8) & 0xff; data[7] = (uint8_t)number & 0xff; } // get 3 bytes from data, reversed static __forceinline unsigned long rev3(const uint8_t * data) { unsigned long ret = 0; ret += ((unsigned long) data[2]) << 16; ret += ((unsigned long) data[1]) << 8; ret += ((unsigned long) data[0]); return ret; } //write 3 bytes normal (used in iTunesSD) 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; } //write 3 bytes reversed static __forceinline void rev3(const unsigned long number, uint8_t * data) { data[2] = (uint8_t)(number >> 16) & 0xff; data[1] = (uint8_t)(number >> 8) & 0xff; data[0] = (uint8_t)number & 0xff; } // pass data and ptr, updates ptr automatically (by reference) static __forceinline void write_uint32_t(uint8_t *data, size_t &offset, uint32_t value) { rev4(value, &data[offset]); offset+=4; } static __forceinline uint32_t read_uint32_t(const uint8_t *data, size_t &offset) { const uint8_t *ptr = &data[offset]; offset+=4; return rev4(ptr); } static __forceinline uint32_t read_uint16_t(const uint8_t *data, size_t &offset) { const uint8_t *ptr = &data[offset]; offset+=2; return rev2(ptr); } static unsigned __int64 Generate64BitID() { GUID tmp; CoCreateGuid(&tmp); unsigned __int64 one = tmp.Data1; unsigned __int64 two = tmp.Data2; unsigned __int64 three = tmp.Data3; unsigned __int64 four = rand(); return(one << 32 | two << 16 | three | four); } // useful function to convert from UTF16 to chars char * UTF16_to_char(wchar_t * str, int length) { char * tempstr=(char *)malloc(length/2+1); int ret=WideCharToMultiByte( CP_MACCP, 0, str, length/2, tempstr, length/2, "x", NULL ); tempstr[length/2]='\0'; if (!ret) DWORD bob=GetLastError(); return tempstr; } // Case insensitive version of wcsstr 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; } ////////////////////////////////////////////////////////////////////// // iPodObj - Base for all iPod classes ////////////////////////////////////////////////////////////////////// iPodObj::iPodObj() : size_head(0), size_total(0) { } iPodObj::~iPodObj() { } ////////////////////////////////////////////////////////////////////// // iPod_mhbd - iTunes database class ////////////////////////////////////////////////////////////////////// iPod_mhbd::iPod_mhbd() : unk1(1), dbversion(0x0c), // iTunes 4.2 = 0x09, 4.5 = 0x0a, 4.7 = 0x0b, 4.7.1 = 0x0c, 0x0d, 0x13 children(2), id(0), platform(2), language('ne'), // byte-swapped 'en' library_id(0), timezone(0), audio_language(0), unk80(1), unk84(15), subtitle_language(0), unk164(0), unk166(0), unk168(0) { // get timezone info _tzset(); // this function call ensures that _timezone global var is valid timezone = -_timezone; id = Generate64BitID(); mhsdsongs = new iPod_mhsd(1); mhsdplaylists = new iPod_mhsd(3); mhsdsmartplaylists = new iPod_mhsd(5); } iPod_mhbd::~iPod_mhbd() { delete mhsdsongs; delete mhsdplaylists; } long iPod_mhbd::parse(const uint8_t *data) { size_t ptr=0; //check mhbd header if (_strnicmp((char *)&data[ptr],"mhbd",4)) return -1; ptr+=4; // get sizes size_head=read_uint32_t(data, ptr); size_total=read_uint32_t(data, ptr); //ASSERT(size_head == 0xbc); // get unk's and numchildren unk1=read_uint32_t(data, ptr); dbversion=read_uint32_t(data, ptr); children=read_uint32_t(data, ptr); id=rev8(get8(&data[ptr])); ptr+=8; if(id == 0) { // Force the id to be a valid value. // This may not always be the right thing to do, but I can't think of any reason why it wouldn't be ok... id = Generate64BitID(); } platform=read_uint16_t(data, ptr); ptr = 0x46; language = read_uint16_t(data, ptr); library_id = rev8(get8(&data[ptr])); ptr+=8; unk80 = read_uint32_t(data, ptr); unk84 = read_uint32_t(data, ptr); ptr = 0xA0; audio_language = read_uint16_t(data, ptr); subtitle_language =read_uint16_t(data, ptr); unk164 = read_uint16_t(data, ptr); unk166 = read_uint16_t(data, ptr); unk168 = read_uint16_t(data, ptr); // timezone is at 0x6c, but we want to calculate this based on the computer timezone // TODO: 4 byte field at 0xA0 that contains FFFFFFFF for the ipod shuffle I'm playing with //if (children != 2) return -1; //skip over nulls ptr=size_head; // get the mhsd's bool parsedPlaylists = false; for(unsigned int i=0; iparse(&data[ptr]); if(ret<0) return ret; else ptr+=ret; if(mhsd->index == 1) { delete mhsdsongs; mhsdsongs = mhsd; } else if(mhsd->index == 3 && !parsedPlaylists) { delete mhsdplaylists; mhsdplaylists = mhsd; parsedPlaylists = true; } else if(mhsd->index == 2 && !parsedPlaylists) { delete mhsdplaylists; mhsdplaylists = mhsd; } else if(mhsd->index == 5) { delete mhsdsmartplaylists; mhsdsmartplaylists = mhsd; } else { delete mhsd; } } return size_total; } long iPod_mhbd::write(unsigned char * data, const unsigned long datasize) { return write(data,datasize,NULL); } extern void GenerateHash(unsigned char *pFWID, unsigned char *pDataBase, long lSize, unsigned char *pHash); long iPod_mhbd::write(unsigned char * data, const unsigned long datasize, unsigned char *fwid) { //const unsigned int headsize=0xbc; // for db version 0x19 const unsigned int headsize=188; // for db version 0x2A // check for header size if (headsize>datasize) return -1; long ptr=0; //write mhbd header data[0]='m';data[1]='h';data[2]='b';data[3]='d'; ptr+=4; // write sizes rev4(headsize,&data[ptr]); // header size ptr+=4; rev4(0x00,&data[ptr]); // placeholder for total size (fill in later) ptr+=4; //write unks rev4(unk1,&data[ptr]); ptr+=4; rev4(0x2a/*dbversion*/,&data[ptr]); ptr+=4; //write numchildren //ASSERT (children == 2); // seen no other case in an iTunesDB yet children = 4; rev4(children,&data[ptr]); ptr+=4; // fill this in later (it's the db id, it has to be 0 for the hash generation) put8(0,&data[ptr]); ptr+=8; rev2(2, &data[ptr]); // platform (2 == Windows) ptr+=2; // fill up the rest of the header with nulls for (unsigned int i=ptr;imhlt->mhit.begin(); iPod_mhlt::mhit_map_t::const_iterator end = mhsdsongs->mhlt->mhit.end(); for(iPod_mhlt::mhit_map_t::const_iterator it = begin; it != end; it++) { wchar_t * artist = L""; wchar_t * album = L""; iPod_mhit *m = static_cast((*it).second); iPod_mhod *mhartist = m->FindString(MHOD_ARTIST); iPod_mhod *mhalbum = m->FindString(MHOD_ALBUM); if(mhartist && mhartist->str) artist = mhartist->str; if(mhalbum && mhalbum->str) album = mhalbum->str; m->album_id = mhsd_mhla.mhla->GetAlbumId(artist, album); } ret=mhsd_mhla.write(&data[ptr], datasize-ptr, 4); ASSERT(ret >= 0); if (ret<0) return ret; else ptr+=ret; // write the mhsd's ret=mhsdsongs->write(&data[ptr], datasize-ptr); ASSERT(ret >= 0); if (ret<0) return ret; else ptr+=ret; ret=mhsdplaylists->write(&data[ptr], datasize-ptr, 3); ASSERT(ret >= 0); if (ret<0) return ret; else ptr+=ret; ret=mhsdplaylists->write(&data[ptr], datasize-ptr, 2); ASSERT(ret >= 0); if (ret<0) return ret; else ptr+=ret; if(mhsdsmartplaylists->mhlp_smart) { ret=mhsdsmartplaylists->write(&data[ptr], datasize-ptr, 5); ASSERT(ret >= 0); if (ret<0) return ret; else ptr+=ret; } else children--; // fix the total size rev4(ptr,&data[8]); rev4(children,&data[20]); if(fwid) GenerateHash(fwid,data,ptr,data+0x58); // fuck you, gaydickian put8(rev8(id),&data[0x18]); // put this back in -- it has to be 0 for the hash generation. return ptr; } ////////////////////////////////////////////////////////////////////// // iPod_mhsd - Holds tracklists and playlists ////////////////////////////////////////////////////////////////////// iPod_mhsd::iPod_mhsd() : index(0), mhlt(NULL), mhlp(NULL), mhlp_smart(NULL), mhla(NULL) { } iPod_mhsd::iPod_mhsd(int newindex) : index(newindex), mhlt(NULL), mhlp(NULL), mhlp_smart(NULL), mhla(NULL) { switch(newindex) { case 1: mhlt=new iPod_mhlt(); break; case 2: case 3: case 5: mhlp=new iPod_mhlp(); break; case 4: mhla=new iPod_mhla(); break; default: index=0; } } iPod_mhsd::~iPod_mhsd() { delete mhlt; delete mhlp; delete mhla; } long iPod_mhsd::parse(const uint8_t *data) { unsigned long ptr=0; //check mhsd header if (_strnicmp((char *)&data[ptr],"mhsd",4)) return -1; ptr+=4; // get sizes size_head=rev4(&data[ptr]); ptr+=4; size_total=rev4(&data[ptr]); ptr+=4; ASSERT(size_head == 0x60); // get index number index=rev4(&data[ptr]); ptr+=4; // skip null padding ptr=size_head; long ret; // check to see if this is a holder for an mhlt or an mhlp if (!_strnicmp((char *)&data[ptr],"mhlt",4)) { if (mhlt==NULL) { mhlt=new iPod_mhlt(); //index=1; } ret=mhlt->parse(&data[ptr]); ASSERT(ret >= 0); if (ret<0) return ret; else ptr+=ret; } else if (!_strnicmp((char *)&data[ptr],"mhlp",4) && (index == 2 || index == 3)) { if (mhlp==NULL) { mhlp=new iPod_mhlp(); if(index != 2) index=3; } ret=mhlp->parse(&data[ptr]); ASSERT(ret >= 0); if (ret<0) return ret; else ptr+=ret; } else if (!_strnicmp((char *)&data[ptr],"mhlp",4) && index == 5) // smart playlists { if (mhlp_smart==NULL) mhlp_smart=new iPod_mhlp(); ret=mhlp_smart->parse(&data[ptr]); ASSERT(ret >= 0); if (ret<0) return ret; else ptr+=ret; } else { } //return -1; //if (ptr != size_total) return -1; return size_total; } long iPod_mhsd::write(unsigned char * data, const unsigned long datasize, int index) { const unsigned int headsize=0x60; // check for header size if (headsize>datasize) return -1; long ptr=0; //write mhsd header data[0]='m';data[1]='h';data[2]='s';data[3]='d'; ptr+=4; // write sizes rev4(headsize,&data[ptr]); // header size ptr+=4; rev4(0x00,&data[ptr]); // placeholder for total size (fill in later) ptr+=4; // write index number rev4(index,&data[ptr]); ptr+=4; // fill up the rest of the header with nulls for (unsigned int i=ptr;iwrite(&data[ptr],datasize-ptr); else if (index==2 || index==3) // mhlp ret=mhlp->write(&data[ptr],datasize-ptr,index); else if (index == 4) // mhla ret=mhla->write(&data[ptr],datasize-ptr); else if (index==5) // mhlp_smart ret=mhlp_smart->write(&data[ptr],datasize-ptr,3); else return -1; ASSERT(ret>=0); if (ret<0) return ret; else ptr+=ret; // fix the total size rev4(ptr,&data[8]); return ptr; } ////////////////////////////////////////////////////////////////////// // iPod_mhlt - TrackList class ////////////////////////////////////////////////////////////////////// iPod_mhlt::iPod_mhlt() : mhit(), next_mhit_id(100) { } iPod_mhlt::~iPod_mhlt() { // It is unnecessary (and slow) to clear the map, since the object is being destroyed anyway ClearTracks(false); } long iPod_mhlt::parse(const uint8_t *data) { long ptr=0; //check mhlt header if (_strnicmp((char *)&data[ptr],"mhlt",4)) return -1; ptr+=4; // get size size_head=rev4(&data[ptr]); ptr+=4; ASSERT(size_head == 0x5c); // get num children (num songs on iPod) const unsigned long children=rev4(&data[ptr]); // Only used locally - child count is obtained from the mhit list ptr+=4; //skip nulls ptr=size_head; long ret; // get children one by one for (unsigned long i=0;iparse(&data[ptr]); ASSERT(ret >= 0); if (ret<0) { delete m; return ret; } ptr+=ret; mhit.insert(mhit_value_t(m->id, m)); mhit_indexer.push_back(m->id); } if (!mhit.empty()) { //InterlockedExchange(&next_mhit_id, mhit.back().first); uint32_t id = mhit_indexer[mhit_indexer.size() - 1]; InterlockedExchange(&next_mhit_id, id); } return ptr; } uint32_t iPod_mhlt::GetNextID() { return (uint32_t)InterlockedIncrement(&next_mhit_id); } long iPod_mhlt::write(unsigned char * data, const unsigned long datasize) { const unsigned int headsize=0x5c; // check for header size if (headsize>datasize) return -1; long ptr=0; //write mhlt header data[0]='m';data[1]='h';data[2]='l';data[3]='t'; ptr+=4; // write size rev4(headsize,&data[ptr]); // header size ptr+=4; // write numchildren (numsongs) const unsigned long children = GetChildrenCount(); rev4(children,&data[ptr]); ptr+=4; // fill up the rest of the header with nulls for (unsigned long i=ptr;i(it->second); #ifdef _DEBUG const unsigned int mapID = (*it).first; ASSERT(mapID == m->id); #endif ret=m->write(&data[ptr],datasize-ptr); ASSERT(ret >= 0); if (ret<0) return ret; else ptr+=ret; } return ptr; } iPod_mhit * iPod_mhlt::NewTrack() { iPod_mhit *track = new iPod_mhit; if (track != NULL) { track->addedtime = wintime_to_mactime(time(0)); track->id = GetNextID(); } return track; } void iPod_mhlt::AddTrack(iPod_mhit *new_track) { mhit_indexer.push_back(new_track->id); mhit.insert(mhit_value_t(new_track->id, new_track)); } bool iPod_mhlt::DeleteTrack(const unsigned long index) { //unsigned int i=0; //for(mhit_map_t::const_iterator it = mhit.begin(); it != mhit.end(); it++, i++) //{ // if(i == index) // { // iPod_mhit *m = static_cast(it->second); // return(DeleteTrackByID(m->id)); // } //} //return false; if (index > mhit_indexer.size()) { return false; } auto key = mhit_indexer[index]; auto it = mhit.find(key); if (mhit.end() == it) { return false; } return DeleteTrackByID(it->first); } bool iPod_mhlt::DeleteTrackByID(const unsigned long id) { mhit_map_t::iterator it = mhit.find(id); if(it != mhit.end()) { iPod_mhit *m = static_cast(it->second); mhit.erase(it); // remove also from indexer!! for (size_t n = 0; n < mhit_indexer.size(); ++n) { if (id == mhit_indexer[n]) { mhit_indexer.erase(mhit_indexer.begin() + n); break; } } delete m; return true; } return false; } iPod_mhit * iPod_mhlt::GetTrack(uint32_t index) const { //mhit_map_t::value_type value = mhit.at(index); //return value.second; if (index > mhit_indexer.size()) { return nullptr; } uint32_t key = mhit_indexer[index]; auto it = mhit.find(key); if (mhit.end() == it) { return nullptr; } return it->second; } iPod_mhit * iPod_mhlt::GetTrackByID(const unsigned long id) { mhit_map_t::const_iterator it = mhit.find(id); if(it == mhit.end()) return NULL; return static_cast(it->second); } bool iPod_mhlt::ClearTracks(const bool clearMap) { #ifdef IPODDB_PROFILER profiler(iPodDB__iPod_mhlt_ClearTracks); #endif mhit_map_t::const_iterator begin = mhit.begin(); mhit_map_t::const_iterator end = mhit.end(); for(mhit_map_t::const_iterator it = begin; it != end; it++) { delete static_cast(it->second); } if (clearMap) { mhit.clear(); mhit_indexer.clear(); } return true; } ////////////////////////////////////////////////////////////////////// // iPod_mhit - Holds info about a song ////////////////////////////////////////////////////////////////////// iPod_mhit::iPod_mhit() : id(0), visible(1), filetype(0), vbr(0), type(0), compilation(0), stars(0), lastmodifiedtime(0), size(0), length(0), tracknum(0), totaltracks(0), year(0), bitrate(0), samplerate(0), samplerate_fixedpoint(0), volume(0), starttime(0), stoptime(0), soundcheck(0), playcount(0), playcount2(0), lastplayedtime(0), cdnum(0), totalcds(0), userID(0), addedtime(0), bookmarktime(0), dbid(0), BPM(0), app_rating(0), checked(0), unk9(0), artworkcount(0), artworksize(0), unk11(0), samplerate2(0), releasedtime(0), unk14(0), unk15(0), unk16(0), skipcount(0), skippedtime(0), hasArtwork(2), // iTunes 4.7.1 always seems to write 2 for unk19 skipShuffle(0), rememberPosition(0), unk19(0), dbid2(0), lyrics_flag(0), movie_flag(0), mark_unplayed(0), unk20(0), unk21(0), pregap(0), samplecount(0), unk25(0), postgap(0), unk27(0), mediatype(0), seasonNumber(0), episodeNumber(0), unk31(0), unk32(0), unk33(0), unk34(0), unk35(0), unk36(0), unk37(0), gaplessData(0), unk39(0), albumgapless(0), trackgapless(0), unk40(0), unk41(0), unk42(0), unk43(0), unk44(0), unk45(0), unk46(0), album_id(0), unk48(0), unk49(0), unk50(0), unk51(0), unk52(0), unk53(0), unk54(0), unk55(0), unk56(0), mhii_link(0), mhod() { // Create a highly randomized 64 bit value for the dbID dbid = Generate64BitID(); dbid2 = dbid; for(int i=0; i<25; i++) mhodcache[i]=NULL; mhod.reserve(8); } iPod_mhit::~iPod_mhit() { #ifdef IPODDB_PROFILER profiler(iPodDB__iPod_mhit_destructor); #endif const unsigned long count = GetChildrenCount(); for (unsigned long i=0;i> 16); samplerate_fixedpoint = (uint16_t)(temp & 0x0000ffff); volume=read_uint32_t(data, ptr); starttime=read_uint32_t(data, ptr); stoptime=read_uint32_t(data, ptr); soundcheck=read_uint32_t(data, ptr); playcount=read_uint32_t(data, ptr); playcount2=read_uint32_t(data, ptr); lastplayedtime=read_uint32_t(data, ptr); cdnum=read_uint32_t(data, ptr);; totalcds=read_uint32_t(data, ptr); userID=read_uint32_t(data, ptr); addedtime=read_uint32_t(data, ptr); bookmarktime=read_uint32_t(data, ptr); dbid=rev8(get8(&data[ptr])); ptr+=8; if(dbid == 0) { // Force the dbid to be a valid value. // This may not always be the right thing to do, but I can't think of any reason why it wouldn't be ok... dbid = Generate64BitID(); } temp=rev4(&data[ptr]); BPM=temp>>16; app_rating=(temp&0xff00) >> 8; checked = (uint8_t)(temp&0xff); ptr+=4; artworkcount=rev2(&data[ptr]); ptr+=2; unk9=rev2(&data[ptr]); ptr+=2; artworksize=read_uint32_t(data, ptr); unk11=read_uint32_t(data, ptr); memcpy(&samplerate2, &data[ptr], sizeof(float)); ptr+=4; releasedtime=read_uint32_t(data, ptr); unk14=read_uint32_t(data, ptr); unk15=read_uint32_t(data, ptr); unk16=read_uint32_t(data, ptr); // Newly added as of dbversion 0x0c if(size_head >= 0xf4) { skipcount=read_uint32_t(data, ptr); skippedtime=read_uint32_t(data, ptr); hasArtwork=data[ptr++]; skipShuffle=data[ptr++]; rememberPosition=data[ptr++]; unk19=data[ptr++]; dbid2=rev8(get8(&data[ptr])); ptr+=8; if(dbid2 == 0) dbid2 = dbid; lyrics_flag=data[ptr++]; movie_flag=data[ptr++]; mark_unplayed=data[ptr++]; unk20=data[ptr++]; unk21=read_uint32_t(data, ptr); // 180 pregap=read_uint32_t(data, ptr); samplecount=rev8(get8(&data[ptr])); //sample count ptr+=8; unk25=read_uint32_t(data, ptr); // 196 postgap=read_uint32_t(data, ptr); unk27=read_uint32_t(data, ptr); mediatype=read_uint32_t(data, ptr); seasonNumber=read_uint32_t(data, ptr); episodeNumber=read_uint32_t(data, ptr); unk31=read_uint32_t(data, ptr); unk32=read_uint32_t(data, ptr); unk33=read_uint32_t(data, ptr); unk34=read_uint32_t(data, ptr); unk35=read_uint32_t(data, ptr); unk36=read_uint32_t(data, ptr); } if(size_head >= 0x148) { // dbversion 0x13 unk37=read_uint32_t(data, ptr); gaplessData=read_uint32_t(data, ptr); unk39=read_uint32_t(data, ptr); trackgapless = read_uint16_t(data, ptr); albumgapless = read_uint16_t(data, ptr); unk40=read_uint32_t(data, ptr); // 260 unk41=read_uint32_t(data, ptr); // 264 unk42=read_uint32_t(data, ptr); // 268 unk43=read_uint32_t(data, ptr); // 272 unk44=read_uint32_t(data, ptr); // 276 unk45=read_uint32_t(data, ptr); // 280 unk46=read_uint32_t(data, ptr); // 284 album_id=read_uint32_t(data, ptr); // 288 - libgpod lists "album_id" unk48=read_uint32_t(data, ptr); // 292 - libgpod lists first half of an id unk49=read_uint32_t(data, ptr); // 296 - libgpod lists second half of an id unk50=read_uint32_t(data, ptr); // 300 - libgpod lists file size unk51=read_uint32_t(data, ptr); // 304 unk52=read_uint32_t(data, ptr); // 308 - libgpod mentions 8 bytes of 0x80 unk53=read_uint32_t(data, ptr); // 312 - libgpod mentions 8 bytes of 0x80 unk54=read_uint32_t(data, ptr); // 316 unk55=read_uint32_t(data, ptr); // 320 unk56=read_uint32_t(data, ptr); // 324 } if(size_head >= 0x184) { ptr = 0x148; // line it up, just in case ptr += 22; // dunno what the first 22 bytes are album_id = read_uint16_t(data, ptr); mhii_link = read_uint32_t(data, ptr); } #ifdef _DEBUG // If these trigger an assertion, something in the database format has changed/been added ASSERT(visible == 1); ASSERT(unk11 == 0); ASSERT(unk16 == 0); // ASSERT(unk19 == 0); // ASSERT(hasArtwork == 2); // iTunes always sets unk19 to 2, but older programs won't have set it ASSERT(unk20 == 0); ASSERT(unk21 == 0); ASSERT(unk25 == 0); // ASSERT(unk27 == 0); //ASSERT(unk31 == 0); ASSERT(unk32 == 0); ASSERT(unk33 == 0); ASSERT(unk34 == 0); ASSERT(unk35 == 0); ASSERT(unk36 == 0); ASSERT(unk37 == 0); ASSERT(unk39 == 0); ASSERT(unk40 == 0); ASSERT(unk41 == 0); ASSERT(unk42 == 0); ASSERT(unk43 == 0); ASSERT(unk44 == 0); ASSERT(unk45 == 0); ASSERT(unk46 == 0); // ASSERT(unk47 == 0); //ASSERT(unk48 == 0); // ASSERT(unk49 == 0); ASSERT(unk50 == 0 || unk50 == size); ASSERT(unk51 == 0); // ASSERT(unk52 == 0); ASSERT(unk53 == 0 || unk53 == 0x8080 || unk53 == 0x8081); ASSERT(unk54 == 0); ASSERT(unk55 == 0); ASSERT(unk56 == 0); #endif // skip nulls ptr=size_head; long ret; for (unsigned long i=0;iparse(&data[ptr]); ASSERT(ret >= 0); if (ret<0) { delete m; return ret; } ptr+=ret; mhod.push_back(m); if(m->type <= 25 && m->type >= 1) mhodcache[m->type-1] = m; } return size_total; } long iPod_mhit::write(unsigned char * data, const unsigned long datasize) { #ifdef IPODDB_PROFILER profiler(iPodDB__iPod_mhit_write); #endif //const unsigned int headsize=0x148; // was 0x9c in db version <= 0x0b const unsigned int headsize=0x184; // db version 0x19 // check for header size if (headsize>datasize) return -1; size_t ptr=0; //write mhlt header data[0]='m';data[1]='h';data[2]='i';data[3]='t'; ptr+=4; // write sizes write_uint32_t(data, ptr, headsize); // header size write_uint32_t(data, ptr, 0); // placeholder for total size (fill in later) unsigned long temp, i; // Remove all empty MHOD strings before continuing for(i=0;itype < 50 && m->length == 0) { //DeleteString(m->type); //i = 0; } } // write stuff out unsigned long mhodnum = GetChildrenCount(); write_uint32_t(data, ptr, mhodnum); write_uint32_t(data, ptr, id); write_uint32_t(data, ptr, visible); if(filetype == 0) { iPod_mhod *mhod = FindString(MHOD_LOCATION); if(mhod) { filetype = GetFileTypeID(mhod->str); } } write_uint32_t(data, ptr, filetype); vbr = data[ptr++] = vbr; type = data[ptr++] = type; compilation = data[ptr++] = compilation; stars = data[ptr++] = stars; write_uint32_t(data, ptr, lastmodifiedtime); write_uint32_t(data, ptr, size); write_uint32_t(data, ptr, length); write_uint32_t(data, ptr, tracknum); write_uint32_t(data, ptr, totaltracks); write_uint32_t(data, ptr, year); write_uint32_t(data, ptr, bitrate); temp = samplerate << 16 | samplerate_fixedpoint & 0x0000ffff; rev4(temp,&data[ptr]); ptr+=4; write_uint32_t(data, ptr, volume); write_uint32_t(data, ptr, starttime); write_uint32_t(data, ptr, stoptime); write_uint32_t(data, ptr, soundcheck); write_uint32_t(data, ptr, playcount); write_uint32_t(data, ptr, playcount2); write_uint32_t(data, ptr, lastplayedtime); write_uint32_t(data, ptr, cdnum); write_uint32_t(data, ptr, totalcds); write_uint32_t(data, ptr, userID); write_uint32_t(data, ptr, addedtime); write_uint32_t(data, ptr, bookmarktime); put8(rev8(dbid),&data[ptr]); ptr+=8; temp = BPM << 16 | (app_rating & 0xff) << 8 | (checked & 0xff); write_uint32_t(data, ptr, temp); rev2(artworkcount, &data[ptr]); ptr+=2; rev2(unk9, &data[ptr]); ptr+=2; write_uint32_t(data, ptr, artworksize); write_uint32_t(data, ptr, unk11); // If samplerate2 is not set, base it off of samplerate if(samplerate2 == 0) { // samplerate2 is the binary representation of the samplerate, as a 32 bit float const float foo = (float)samplerate; memcpy(&data[ptr], &foo, 4); } else { memcpy(&data[ptr], &samplerate2, 4); } ptr+=4; rev4(releasedtime,&data[ptr]); ptr+=4; rev4(unk14,&data[ptr]); ptr+=4; rev4(unk15,&data[ptr]); ptr+=4; rev4(unk16,&data[ptr]); ptr+=4; // New data as of dbversion 0x0c if(headsize >= 0xf4) { rev4(skipcount,&data[ptr]); ptr+=4; rev4(skippedtime,&data[ptr]); ptr+=4; data[ptr++]=hasArtwork; data[ptr++]=skipShuffle; data[ptr++]=rememberPosition; data[ptr++]=unk19; put8(rev8(dbid2),&data[ptr]); ptr+=8; data[ptr++]=lyrics_flag; data[ptr++]=movie_flag; data[ptr++]=mark_unplayed; data[ptr++]=unk20; rev4(unk21,&data[ptr]); ptr+=4; rev4(pregap,&data[ptr]); ptr+=4; put8(rev8(samplecount),&data[ptr]); ptr+=8; rev4(unk25,&data[ptr]); ptr+=4; rev4(postgap,&data[ptr]); ptr+=4; rev4(unk27,&data[ptr]); ptr+=4; rev4(mediatype,&data[ptr]); ptr+=4; rev4(seasonNumber,&data[ptr]); ptr+=4; rev4(episodeNumber,&data[ptr]); ptr+=4; rev4(unk31,&data[ptr]); ptr+=4; rev4(unk32,&data[ptr]); ptr+=4; rev4(unk33,&data[ptr]); ptr+=4; rev4(unk34,&data[ptr]); ptr+=4; rev4(unk35,&data[ptr]); ptr+=4; rev4(unk36,&data[ptr]); ptr+=4; } if(headsize >= 0x148) { rev4(unk37,&data[ptr]); ptr+=4; rev4(gaplessData,&data[ptr]); ptr+=4; rev4(unk39,&data[ptr]); ptr+=4; temp = albumgapless << 16 | (trackgapless & 0xffff); rev4(temp, &data[ptr]); ptr+=4; rev4(unk40,&data[ptr]); ptr+=4; rev4(unk41,&data[ptr]); ptr+=4; rev4(unk42,&data[ptr]); ptr+=4; rev4(unk43,&data[ptr]); ptr+=4; rev4(unk44,&data[ptr]); ptr+=4; rev4(unk45,&data[ptr]); ptr+=4; rev4(unk46,&data[ptr]); ptr+=4; rev4(album_id,&data[ptr]); ptr+=4; rev4(unk48,&data[ptr]); ptr+=4; rev4(unk49,&data[ptr]); ptr+=4; rev4(size,&data[ptr]); ptr+=4; rev4(unk51,&data[ptr]); ptr+=4; rev4(unk52,&data[ptr]); ptr+=4; rev4(unk53,&data[ptr]); ptr+=4; rev4(unk54,&data[ptr]); ptr+=4; rev4(unk55,&data[ptr]); ptr+=4; rev4(unk56,&data[ptr]); ptr+=4; } if (headsize >= 0x184) { ptr = 0x148; // line it up, just in case memset(&data[ptr], 0, 22); // write a bunch of zeroes ptr+=22; rev2(album_id, &data[ptr]); ptr+=2; rev4(mhii_link,&data[ptr]); ptr+=4; memset(&data[ptr], 0, 32); // write a bunch of zeroes ptr+=32; } ASSERT(ptr==headsize); // if this ain't true, I screwed up badly somewhere above long ret; for (i=0;iwrite(&data[ptr],datasize-ptr); ASSERT(ret >= 0); if (ret<0) return ret; else ptr+=ret; } // fix the total size rev4(ptr,&data[8]); return ptr; } iPod_mhod * iPod_mhit::AddString(const int type) { #ifdef IPODDB_PROFILER profiler(iPodDB__iPod_mhit_AddString); #endif iPod_mhod * m; if (type) { m = FindString(type); if (m != NULL) { return m; } } m=new iPod_mhod; if (m!=NULL && type) m->type=type; mhod.push_back(m); if(m->type <= 25 && m->type >= 1) mhodcache[m->type-1] = m; return m; } iPod_mhod * iPod_mhit::FindString(const unsigned long type) const { #ifdef IPODDB_PROFILER profiler(iPodDB__iPod_mhit_FindString); #endif if(type <= 25 && type >= 1) return mhodcache[type-1]; const unsigned long children = GetChildrenCount(); for (unsigned long i=0;itype == type) return mhod[i]; } return NULL; } unsigned long iPod_mhit::DeleteString(const unsigned long type) { #ifdef IPODDB_PROFILER profiler(iPodDB__iPod_mhit_DeleteString); #endif if(type <= 25 && type >= 1) mhodcache[type-1] = NULL; unsigned long count=0; for (unsigned long i=0; i != GetChildrenCount(); i++) { if (mhod[i]->type == type) { iPod_mhod * m = mhod.at(i); mhod.erase(mhod.begin() + i); delete m; i = i > 0 ? i - 1 : 0; // do this to ensure that it checks the new entry in position i next count++; } } return count; } unsigned int iPod_mhit::GetFileTypeID(const wchar_t *filename) { ASSERT(filename); if(filename == NULL) return(0); // Incredibly, this is really the file extension as ASCII characters // e.g. 0x4d = 'M', 0x50 = 'P', 0x33 = '3', 0x20 = '' if(wcsistr(filename, L".mp3") != NULL) return FILETYPE_MP3; else if(wcsistr(filename, L".m4a") != NULL) return FILETYPE_M4A; else if(wcsistr(filename, L".m4b") != NULL) return(0x4d344220); else if(wcsistr(filename, L".m4p") != NULL) return(0x4d345020); else if(wcsistr(filename, L".wav") != NULL) return FILETYPE_WAV; return(0); } iPod_mhit& iPod_mhit::operator=(const iPod_mhit& src) { Duplicate(&src,this); return *this; } void iPod_mhit::Duplicate(const iPod_mhit *src, iPod_mhit *dst) { #ifdef IPODDB_PROFILER profiler(iPodDB__iPod_mhit_Duplicate); #endif if(src == NULL || dst == NULL) return; dst->id = src->id; dst->visible = src->visible; dst->filetype = src->filetype; dst->vbr = src->vbr; dst->type = src->type; dst->compilation = src->compilation; dst->stars = src->stars; dst->lastmodifiedtime = src->lastmodifiedtime; dst->size = src->size; dst->length = src->length; dst->tracknum = src->tracknum; dst->totaltracks = src->totaltracks; dst->year = src->year; dst->bitrate = src->bitrate; dst->samplerate = src->samplerate; dst->samplerate_fixedpoint = src->samplerate_fixedpoint; dst->volume = src->volume; dst->starttime = src->starttime; dst->stoptime = src->stoptime; dst->soundcheck = src->soundcheck; dst->playcount = src->playcount; dst->playcount2 = src->playcount2; dst->lastplayedtime = src->lastplayedtime; dst->cdnum = src->cdnum; dst->totalcds = src->totalcds; dst->userID = src->userID; dst->addedtime = src->addedtime; dst->bookmarktime = src->bookmarktime; dst->dbid = src->dbid; dst->BPM = src->BPM; dst->app_rating = src->app_rating; dst->checked = src->checked; dst->unk9 = src->unk9; dst->artworksize = src->artworksize; dst->unk11 = src->unk11; dst->samplerate2 = src->samplerate2; dst->releasedtime = src->releasedtime; dst->unk14 = src->unk14; dst->unk15 = src->unk15; dst->unk16 = src->unk16; dst->skipcount = src->skipcount; dst->skippedtime = src->skippedtime; dst->hasArtwork = src->hasArtwork; dst->skipShuffle = src->skipShuffle; dst->rememberPosition = src->rememberPosition; dst->unk19 = src->unk19; dst->dbid2 = src->dbid2; dst->lyrics_flag = src->lyrics_flag; dst->movie_flag = src->movie_flag; dst->mark_unplayed = src->mark_unplayed; dst->unk20 = src->unk20; dst->unk21 = src->unk21; dst->pregap = src->pregap; dst->samplecount = src->samplecount; dst->unk25 = src->unk25; dst->postgap = src->postgap; dst->unk27 = src->unk27; dst->mediatype = src->mediatype; dst->seasonNumber = src->seasonNumber; dst->episodeNumber = src->episodeNumber; dst->unk31 = src->unk31; dst->unk32 = src->unk32; dst->unk33 = src->unk33; dst->unk34 = src->unk34; dst->unk35 = src->unk35; dst->unk36 = src->unk36; dst->unk37 = src->unk37; dst->gaplessData = src->gaplessData; dst->unk39 = src->unk39; dst->albumgapless = src->albumgapless; dst->trackgapless = src->trackgapless; dst->unk40 = src->unk40; dst->unk41 = src->unk41; dst->unk42 = src->unk42; dst->unk43 = src->unk43; dst->unk44 = src->unk44; dst->unk45 = src->unk45; dst->unk46 = src->unk46; dst->album_id = src->album_id; dst->unk48 = src->unk48; dst->unk49 = src->unk49; dst->unk50 = src->unk50; dst->unk51 = src->unk51; dst->unk52 = src->unk52; dst->unk53 = src->unk53; dst->unk54 = src->unk54; dst->unk55 = src->unk55; dst->unk56 = src->unk56; dst->mhii_link = src->mhii_link; const unsigned int mhodSize = src->mhod.size(); for(unsigned int i=0; imhod[i]; if(src_mhod == NULL) continue; iPod_mhod *dst_mhod = dst->AddString(src_mhod->type); if(dst_mhod) dst_mhod->Duplicate(src_mhod, dst_mhod); } } int iPod_mhit::GetEQSetting() { iPod_mhod *mhod = FindString(MHOD_EQSETTING); if(mhod == NULL) return(EQ_NONE); ASSERT(lstrlen(mhod->str) == 9); if(lstrlen(mhod->str) != 9) return(EQ_NONE); wchar_t strval[4] = {0}; lstrcpyn(strval, mhod->str + 3, 3); int val = _wtoi(strval); ASSERT(val >= EQ_ACOUSTIC && val <= EQ_VOCALBOOSTER); return(val); } void iPod_mhit::SetEQSetting(int value) { DeleteString(MHOD_EQSETTING); if(value < 0) return; ASSERT(value >= EQ_ACOUSTIC && value <= EQ_VOCALBOOSTER); wchar_t strval[10] = {0}; _snwprintf(strval, 9, L"#!#%d#!#", value); strval[9] = '\0'; iPod_mhod *mhod = AddString(MHOD_EQSETTING); ASSERT(mhod); if(mhod == NULL) return; mhod->SetString(strval); } ////////////////////////////////////////////////////////////////////// // iPod_mhod - Holds strings for a song or playlist, among other things ////////////////////////////////////////////////////////////////////// iPod_mhod::iPod_mhod() : type(0), unk1(0), unk2(0), position(1), length(0), unk3(1), unk4(0), str(NULL), binary(NULL), liveupdate(1), checkrules(0), matchcheckedonly(0), limitsort_opposite(0), limittype(0), limitsort(0), limitvalue(0), unk5(0), rules_operator(SPLMATCH_AND), parseSmartPlaylists(true) { } iPod_mhod::~iPod_mhod() { #ifdef IPODDB_PROFILER profiler(iPodDB__iPod_mhod_destructor); #endif if (str) delete [] str; if (binary) delete [] binary; const unsigned int size = rule.size(); for (unsigned int i=0;i 0); if(length > 0) { char *tmp = new char[length + 1]; ASSERT(tmp); if(tmp != NULL) { memcpy(tmp, &data[ptr], length); tmp[length] = '\0'; wchar_t *tmpUTF = UTF8_to_UTF16(tmp); unsigned int len = wcslen(tmpUTF); str=new wchar_t[len + 1]; wcsncpy(str, tmpUTF, len); str[len] = '\0'; free(tmpUTF); } ptr+=length; } } else if (type==MHOD_SPLPREF) { if(parseSmartPlaylists) { liveupdate=data[ptr]; ptr++; checkrules=data[ptr]; ptr++; checklimits=data[ptr]; ptr++; limittype=data[ptr]; ptr++; limitsort=data[ptr]; ptr++; ptr+=3; limitvalue=read_uint32_t(data, ptr); matchcheckedonly=data[ptr]; ptr++; // if the opposite flag is on, set limitsort's high bit limitsort_opposite=data[ptr]; ptr++; if(limitsort_opposite) limitsort += 0x80000000; } } else if (type==MHOD_SPLDATA) { if(parseSmartPlaylists) { // strangely, SPL Data is the only thing in the file that *isn't* byte reversed. // check for SLst header if (_strnicmp((char *)&data[ptr],"SLst",4)) return -1; ptr+=4; unk5=get4(&data[ptr]); ptr+=4; const unsigned int numrules=get4(&data[ptr]); ptr+=4; rules_operator=get4(&data[ptr]); ptr+=4; ptr+=120; rule.reserve(numrules); for (i=0;ifield=get4(&data[ptr]); ptr+=4; r->action=get4(&data[ptr]); ptr+=4; ptr+=44; r->length=get4(&data[ptr]); ptr+=4; #ifdef _DEBUG switch(r->action) { case SPLACTION_IS_INT: case SPLACTION_IS_GREATER_THAN: case SPLACTION_IS_NOT_GREATER_THAN: case SPLACTION_IS_LESS_THAN: case SPLACTION_IS_NOT_LESS_THAN: case SPLACTION_IS_IN_THE_RANGE: case SPLACTION_IS_NOT_IN_THE_RANGE: case SPLACTION_IS_IN_THE_LAST: case SPLACTION_IS_STRING: case SPLACTION_CONTAINS: case SPLACTION_STARTS_WITH: case SPLACTION_DOES_NOT_START_WITH: case SPLACTION_ENDS_WITH: case SPLACTION_DOES_NOT_END_WITH: case SPLACTION_IS_NOT_INT: case SPLACTION_IS_NOT_IN_THE_LAST: case SPLACTION_IS_NOT: case SPLACTION_DOES_NOT_CONTAIN: case SPLACTION_BINARY_AND: case SPLACTION_UNKNOWN2: break; default: // New action! //printf("New Action Discovered = %x\n",r->action); ASSERT(0); break; } #endif const bool hasString = iPod_slst::GetFieldType(r->field) == iPod_slst::ftString; if(hasString) { // For some unknown reason, smart playlist strings have UTF-16 characters that are byte swapped unsigned char *c = (unsigned char*)r->string; const unsigned len = min(r->length, SPL_MAXSTRINGLENGTH); for(unsigned int i=0; ilength; } else { // from/to combos always seem to be 0x44 in length in all cases... // fix this to be smarter if it turns out not to be the case ASSERT(r->length == 0x44); r->fromvalue=get8(&data[ptr]); ptr+=8; r->fromdate=get8(&data[ptr]); ptr+=8; r->fromunits=get8(&data[ptr]); ptr+=8; r->tovalue=get8(&data[ptr]); ptr+=8; r->todate=get8(&data[ptr]); ptr+=8; r->tounits=get8(&data[ptr]); ptr+=8; // SPLFIELD_PLAYLIST seems to use the unks here... r->unk1=get4(&data[ptr]); ptr+=4; r->unk2=get4(&data[ptr]); ptr+=4; r->unk3=get4(&data[ptr]); ptr+=4; r->unk4=get4(&data[ptr]); ptr+=4; r->unk5=get4(&data[ptr]); ptr+=4; } rule.push_back(r); } } } else if(type == MHOD_PLAYLIST) { position=read_uint32_t(data, ptr); // Skip to the end ptr+=16; } else { // non string/smart playlist types get copied in.. with the header and such being ignored binary=new unsigned char[size_total-size_head]; memcpy(binary,&data[ptr],size_total-size_head); // in this case, we'll use the length field to store the length of the binary stuffs, // since it's not being used for anything else in these entries. // this helps in the writing phase of the process. // note that if, for some reason, you decide to create a mhod for type 50+ from scratch, // you need to set the length = the size of your binary space length=size_total-size_head; } return size_total; } long iPod_mhod::write(unsigned char * data, const unsigned long datasize) { const unsigned long headsize=0x18; // check for header size if (headsize>datasize) return -1; long ptr=0; //write mhod header data[0]='m';data[1]='h';data[2]='o';data[3]='d'; ptr+=4; // write sizes rev4(headsize,&data[ptr]); // header size ptr+=4; rev4(0x00,&data[ptr]); // placeholder for total size (fill in later) ptr+=4; // write stuff out rev4(type,&data[ptr]); ptr+=4; rev4(unk1,&data[ptr]); ptr+=4; rev4(unk2,&data[ptr]); ptr+=4; if (iPod_mhod::IsSimpleStringType(type)) { // check for string size if (16+length+headsize>datasize) return -1; rev4(position,&data[ptr]); ptr+=4; rev4(length,&data[ptr]); ptr+=4; rev4(unk3,&data[ptr]); ptr+=4; rev4(unk4,&data[ptr]); ptr+=4; const unsigned int len = length / 2; for (unsigned int i=0;i> 8) & 0xff; ptr++; } } else if (type == MHOD_ENCLOSUREURL || type == MHOD_RSSFEEDURL) { // Convert the UTF-16 string back to UTF-8 char *utf8Str = UTF16_to_UTF8(str); const unsigned int len = strlen(utf8Str); if (16+len+headsize>datasize) { free(utf8Str); return -1; } memcpy(data + ptr, utf8Str, len); free(utf8Str); ptr += len; } else if (type==MHOD_SPLPREF) { if (16+74 > datasize) return -1; // write the type 50 mhod data[ptr]=liveupdate; ptr++; data[ptr]=checkrules; ptr++; data[ptr]=checklimits; ptr++; data[ptr]=(unsigned char)(limittype); ptr++; data[ptr]=(unsigned char)((limitsort & 0x000000ff)); ptr++; data[ptr]=0; ptr++; data[ptr]=0; ptr++; data[ptr]=0; ptr++; rev4(limitvalue,&data[ptr]); ptr+=4; data[ptr]=matchcheckedonly; ptr++; // set the limitsort_opposite flag by checking the high bit of limitsort data[ptr] = limitsort & 0x80000000 ? 1 : 0; ptr++; // insert 58 nulls memset(data + ptr, 0, 58); ptr += 58; } else if (type==MHOD_SPLDATA) { const unsigned int ruleCount = rule.size(); if (16+136+ (ruleCount*(124+515)) > datasize) return -1; // put "SLst" header data[ptr]='S';data[ptr+1]='L';data[ptr+2]='s';data[ptr+3]='t'; ptr+=4; put4(unk5,&data[ptr]); ptr+=4; put4(ruleCount,&data[ptr]); ptr+=4; put4(rules_operator,&data[ptr]); ptr+=4; memset(data + ptr, 0, 120); ptr+=120; for (unsigned int i=0;ifield,&data[ptr]); ptr+=4; put4(r->action,&data[ptr]); ptr+=4; memset(data + ptr, 0, 44); ptr+=44; put4(r->length,&data[ptr]); ptr+=4; const bool hasString = iPod_slst::GetFieldType(r->field) == iPod_slst::ftString; if(hasString) { // Byte swap the characters unsigned char *c = (unsigned char*)r->string; for(unsigned int i=0; ilength; i+=2) { data[ptr + i] = *(c + i + 1); data[ptr + i + 1] = *(c + i); } ptr += r->length; } else { put8(r->fromvalue,&data[ptr]); ptr+=8; put8(r->fromdate,&data[ptr]); ptr+=8; put8(r->fromunits,&data[ptr]); ptr+=8; put8(r->tovalue,&data[ptr]); ptr+=8; put8(r->todate,&data[ptr]); ptr+=8; put8(r->tounits,&data[ptr]); ptr+=8; put4(r->unk1,&data[ptr]); ptr+=4; put4(r->unk2,&data[ptr]); ptr+=4; put4(r->unk3,&data[ptr]); ptr+=4; put4(r->unk4,&data[ptr]); ptr+=4; put4(r->unk5,&data[ptr]); ptr+=4; } } // end for } else if(type == MHOD_PLAYLIST) { if (16+20 > datasize) return -1; rev4(position,&data[ptr]); // position in playlist ptr+=4; rev4(0,&data[ptr]); // four nulls ptr+=4; rev4(0,&data[ptr]); ptr+=4; rev4(0,&data[ptr]); ptr+=4; rev4(0,&data[ptr]); ptr+=4; } else // not a known type, use the binary { // check for binary size if (length+headsize>datasize) return -1; for (unsigned int i=0;itype = src->type; dst->unk1 = src->unk1; dst->unk2 = src->unk2; dst->position = src->position; dst->length = src->length; dst->unk3 = src->unk3; dst->unk4 = src->unk4; dst->liveupdate = src->liveupdate; dst->checkrules = src->checkrules; dst->checklimits = src->checklimits; dst->limittype = src->limittype; dst->limitsort = src->limitsort; dst->limitvalue = src->limitvalue; dst->matchcheckedonly = src->matchcheckedonly; dst->limitsort_opposite = src->limitsort_opposite; dst->unk5 = src->unk5; dst->rules_operator = src->rules_operator; if(src->str) { dst->SetString(src->str); } else if(src->binary) { dst->binary = new unsigned char[src->length]; if(dst->binary) memcpy(dst->binary, src->binary, src->length); } const unsigned int ruleLen = src->rule.size(); for(unsigned int i=0; irule[i]; if(srcRule) { SPLRule *dstRule = new SPLRule; if(dstRule) memcpy(dstRule, srcRule, sizeof(SPLRule)); dst->rule.push_back(dstRule); } } } ////////////////////////////////////////////////////////////////////// // iPod_mhlp - Holds playlists ////////////////////////////////////////////////////////////////////// iPod_mhlp::iPod_mhlp() : mhyp(), beingDeleted(false) { // Always start off with an empty, hidden, default playlist ASSERT(GetChildrenCount() == 0); GetDefaultPlaylist(); } iPod_mhlp::~iPod_mhlp() { // This is unnecessary (and slow) to clear the vector list, // since the object is being destroyed anyway... beingDeleted = true; ClearPlaylists(); } long iPod_mhlp::parse(const uint8_t *data) { long ptr=0; //check mhlp header if (_strnicmp((char *)&data[ptr],"mhlp",4)) return -1; ptr+=4; // get sizes size_head=rev4(&data[ptr]); ptr+=4; const unsigned long children=rev4(&data[ptr]); // Only used locally - child count is obtained from the mhyp vector list ptr+=4; ASSERT(size_head == 0x5c); // skip nulls ptr=size_head; mhyp.reserve(children); // pre allocate the space, for speed ClearPlaylists(); long ret; for (unsigned long i=0;iparse(&data[ptr]); ASSERT(ret >= 0); if (ret<0) { delete m; return ret; } // If this is really a smart playlist, we need to parse it again as a smart playlist if(m->FindString(MHOD_SPLPREF) != NULL) { delete m; ptr+=ret; continue; m = new iPod_slst; ASSERT(m); ret = m->parse(&data[ptr]); ASSERT(ret >= 0); if(ret < 0) { delete m; return ret; } } ptr+=ret; mhyp.push_back(m); } return ptr; } long iPod_mhlp::write(unsigned char * data, const unsigned long datasize, int index) { #ifdef IPODDB_PROFILER profiler(iPodDB__iPod_mhlp_write); #endif const unsigned int headsize=0x5c; // check for header size if (headsize>datasize) return -1; long ptr=0; //write mhlp header data[0]='m';data[1]='h';data[2]='l';data[3]='p'; ptr+=4; // write sizes rev4(headsize,&data[ptr]); // header size ptr+=4; // write num of children const unsigned long children = GetChildrenCount(); rev4(children,&data[ptr]); ptr+=4; // fill up the rest of the header with nulls unsigned int i; for (i=ptr;iwrite(&data[ptr],datasize-ptr,index); ASSERT(ret >= 0); if (ret<0) return ret; ptr+=ret; } return ptr; } iPod_mhyp * iPod_mhlp::AddPlaylist() { #ifdef IPODDB_PROFILER profiler(iPodDB__iPod_mhlp_AddPlaylist); #endif iPod_mhyp * m = new iPod_mhyp; ASSERT(m); if (m != NULL) { mhyp.push_back(m); return m; } else return NULL; } iPod_mhyp * iPod_mhlp::FindPlaylist(const unsigned __int64 playlistID) { const unsigned long count = GetChildrenCount(); for (unsigned long i=0; iplaylistID == playlistID) return m; } return NULL; } // deletes the playlist at a position bool iPod_mhlp::DeletePlaylist(const unsigned long pos) { if (GetChildrenCount() > pos) { iPod_mhyp *m = GetPlaylist(pos); mhyp.erase(mhyp.begin() + pos); delete m; return true; } else return false; } bool iPod_mhlp::DeletePlaylistByID(const unsigned __int64 playlistID) { if(playlistID == 0) return(false); const unsigned int count = GetChildrenCount(); for(unsigned int i=0; iplaylistID == playlistID) return(DeletePlaylist(i)); } return(false); } iPod_mhyp * iPod_mhlp::GetDefaultPlaylist() { #ifdef IPODDB_PROFILER profiler(iPodDB__iPod_mhlp_GetDefaultPlaylist); #endif if (!mhyp.empty()) return GetPlaylist(0); else { // Create a new hidden playlist, and set a default title iPod_mhyp * playlist = AddPlaylist(); ASSERT(playlist); if(playlist) { playlist->hidden = 1; iPod_mhod *mhod = playlist->AddString(MHOD_TITLE); if(mhod) mhod->SetString(L"iPod"); } return playlist; } } bool iPod_mhlp::ClearPlaylists(const bool createDefaultPlaylist) { #ifdef IPODDB_PROFILER profiler(iPodDB__iPod_mhlp_ClearPlaylists); #endif const unsigned long count = GetChildrenCount(); for (unsigned long i=0;i