From 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d Mon Sep 17 00:00:00 2001 From: Jef Date: Tue, 24 Sep 2024 14:54:57 +0200 Subject: Initial community commit --- Src/Plugins/Portable/pmp_ipod/SysInfoXML.cpp | 149 + Src/Plugins/Portable/pmp_ipod/api.h | 30 + Src/Plugins/Portable/pmp_ipod/deviceprovider.cpp | 343 ++ Src/Plugins/Portable/pmp_ipod/deviceprovider.h | 50 + Src/Plugins/Portable/pmp_ipod/eject.cpp | 357 ++ Src/Plugins/Portable/pmp_ipod/filecopy.cpp | 69 + Src/Plugins/Portable/pmp_ipod/hash58.cpp | 154 + Src/Plugins/Portable/pmp_ipod/hash58.h | 1 + Src/Plugins/Portable/pmp_ipod/iPodArtworkDB.cpp | 914 ++++ Src/Plugins/Portable/pmp_ipod/iPodArtworkDB.h | 232 + Src/Plugins/Portable/pmp_ipod/iPodDB.cpp | 4807 ++++++++++++++++++++ Src/Plugins/Portable/pmp_ipod/iPodDB.h | 1256 +++++ Src/Plugins/Portable/pmp_ipod/iPodDevice.cpp | 1795 ++++++++ Src/Plugins/Portable/pmp_ipod/iPodDevice.h | 142 + Src/Plugins/Portable/pmp_ipod/iPodInfo.cpp | 1040 +++++ Src/Plugins/Portable/pmp_ipod/iPodInfo.h | 97 + Src/Plugins/Portable/pmp_ipod/iPodSD.cpp | 523 +++ Src/Plugins/Portable/pmp_ipod/iPodSD.h | 78 + Src/Plugins/Portable/pmp_ipod/main.cpp | 336 ++ Src/Plugins/Portable/pmp_ipod/pmp_ipod.rc | 194 + Src/Plugins/Portable/pmp_ipod/pmp_ipod.sln | 75 + Src/Plugins/Portable/pmp_ipod/pmp_ipod.vcxproj | 355 ++ .../Portable/pmp_ipod/pmp_ipod.vcxproj.filters | 113 + Src/Plugins/Portable/pmp_ipod/resource.h | 71 + .../pmp_ipod/resources/apple_ipod_classic.png | Bin 0 -> 3464 bytes .../pmp_ipod/resources/apple_ipod_classic_16.png | Bin 0 -> 199 bytes .../pmp_ipod/resources/apple_ipod_nano_1g.png | Bin 0 -> 1991 bytes .../pmp_ipod/resources/apple_ipod_nano_1g_16.png | Bin 0 -> 192 bytes .../pmp_ipod/resources/apple_ipod_nano_2g.png | Bin 0 -> 1861 bytes .../pmp_ipod/resources/apple_ipod_nano_2g_16.png | Bin 0 -> 195 bytes .../pmp_ipod/resources/apple_ipod_nano_3g.png | Bin 0 -> 2128 bytes .../pmp_ipod/resources/apple_ipod_nano_3g_16.png | Bin 0 -> 186 bytes .../pmp_ipod/resources/apple_ipod_nano_4g.png | Bin 0 -> 2513 bytes .../pmp_ipod/resources/apple_ipod_nano_4g_16.png | Bin 0 -> 193 bytes .../pmp_ipod/resources/apple_ipod_nano_5g.png | Bin 0 -> 1939 bytes .../pmp_ipod/resources/apple_ipod_nano_5g_16.png | Bin 0 -> 187 bytes .../pmp_ipod/resources/apple_ipod_shuffle_1g.png | Bin 0 -> 1781 bytes .../resources/apple_ipod_shuffle_1g_16.png | Bin 0 -> 182 bytes .../pmp_ipod/resources/apple_ipod_shuffle_2g.png | Bin 0 -> 1117 bytes .../resources/apple_ipod_shuffle_2g_16.png | Bin 0 -> 230 bytes .../pmp_ipod/resources/apple_ipod_shuffle_3g.png | Bin 0 -> 1345 bytes .../resources/apple_ipod_shuffle_3g_16.png | Bin 0 -> 119 bytes .../pmp_ipod/resources/apple_ipod_shuffle_4g.png | Bin 0 -> 1964 bytes .../resources/apple_ipod_shuffle_4g_16.png | Bin 0 -> 233 bytes Src/Plugins/Portable/pmp_ipod/sha1.c | 240 + Src/Plugins/Portable/pmp_ipod/sha1.h | 9 + Src/Plugins/Portable/pmp_ipod/version.rc2 | 39 + Src/Plugins/Portable/pmp_ipod/yail.cpp | 114 + Src/Plugins/Portable/pmp_ipod/yail.h | 25 + 49 files changed, 13608 insertions(+) create mode 100644 Src/Plugins/Portable/pmp_ipod/SysInfoXML.cpp create mode 100644 Src/Plugins/Portable/pmp_ipod/api.h create mode 100644 Src/Plugins/Portable/pmp_ipod/deviceprovider.cpp create mode 100644 Src/Plugins/Portable/pmp_ipod/deviceprovider.h create mode 100644 Src/Plugins/Portable/pmp_ipod/eject.cpp create mode 100644 Src/Plugins/Portable/pmp_ipod/filecopy.cpp create mode 100644 Src/Plugins/Portable/pmp_ipod/hash58.cpp create mode 100644 Src/Plugins/Portable/pmp_ipod/hash58.h create mode 100644 Src/Plugins/Portable/pmp_ipod/iPodArtworkDB.cpp create mode 100644 Src/Plugins/Portable/pmp_ipod/iPodArtworkDB.h create mode 100644 Src/Plugins/Portable/pmp_ipod/iPodDB.cpp create mode 100644 Src/Plugins/Portable/pmp_ipod/iPodDB.h create mode 100644 Src/Plugins/Portable/pmp_ipod/iPodDevice.cpp create mode 100644 Src/Plugins/Portable/pmp_ipod/iPodDevice.h create mode 100644 Src/Plugins/Portable/pmp_ipod/iPodInfo.cpp create mode 100644 Src/Plugins/Portable/pmp_ipod/iPodInfo.h create mode 100644 Src/Plugins/Portable/pmp_ipod/iPodSD.cpp create mode 100644 Src/Plugins/Portable/pmp_ipod/iPodSD.h create mode 100644 Src/Plugins/Portable/pmp_ipod/main.cpp create mode 100644 Src/Plugins/Portable/pmp_ipod/pmp_ipod.rc create mode 100644 Src/Plugins/Portable/pmp_ipod/pmp_ipod.sln create mode 100644 Src/Plugins/Portable/pmp_ipod/pmp_ipod.vcxproj create mode 100644 Src/Plugins/Portable/pmp_ipod/pmp_ipod.vcxproj.filters create mode 100644 Src/Plugins/Portable/pmp_ipod/resource.h create mode 100644 Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_classic.png create mode 100644 Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_classic_16.png create mode 100644 Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_1g.png create mode 100644 Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_1g_16.png create mode 100644 Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_2g.png create mode 100644 Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_2g_16.png create mode 100644 Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_3g.png create mode 100644 Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_3g_16.png create mode 100644 Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_4g.png create mode 100644 Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_4g_16.png create mode 100644 Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_5g.png create mode 100644 Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_5g_16.png create mode 100644 Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_1g.png create mode 100644 Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_1g_16.png create mode 100644 Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_2g.png create mode 100644 Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_2g_16.png create mode 100644 Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_3g.png create mode 100644 Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_3g_16.png create mode 100644 Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_4g.png create mode 100644 Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_4g_16.png create mode 100644 Src/Plugins/Portable/pmp_ipod/sha1.c create mode 100644 Src/Plugins/Portable/pmp_ipod/sha1.h create mode 100644 Src/Plugins/Portable/pmp_ipod/version.rc2 create mode 100644 Src/Plugins/Portable/pmp_ipod/yail.cpp create mode 100644 Src/Plugins/Portable/pmp_ipod/yail.h (limited to 'Src/Plugins/Portable/pmp_ipod') diff --git a/Src/Plugins/Portable/pmp_ipod/SysInfoXML.cpp b/Src/Plugins/Portable/pmp_ipod/SysInfoXML.cpp new file mode 100644 index 00000000..61246c8f --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/SysInfoXML.cpp @@ -0,0 +1,149 @@ +#include +#include // for offsetof +#include +#include + + typedef struct { + USHORT Length; + UCHAR ScsiStatus; + UCHAR PathId; + UCHAR TargetId; + UCHAR Lun; + UCHAR CdbLength; + UCHAR SenseInfoLength; + UCHAR DataIn; + ULONG DataTransferLength; + ULONG TimeOutValue; + PVOID DataBuffer; + ULONG SenseInfoOffset; + UCHAR Cdb[16]; +} SCSI_PASS_THROUGH_DIRECT, *PSCSI_PASS_THROUGH_DIRECT; + + + typedef struct { + SCSI_PASS_THROUGH_DIRECT spt; + ULONG Filler; + UCHAR ucSenseBuf[32]; +} SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, *PSCSI_PASS_THROUGH_DIRECT_WITH_BUFFER; + + + + #define IOCTL_SCSI_BASE 0x00000004 + +/* + * constants for DataIn member of SCSI_PASS_THROUGH* structures + */ +#define SCSI_IOCTL_DATA_OUT 0 +#define SCSI_IOCTL_DATA_IN 1 +#define SCSI_IOCTL_DATA_UNSPECIFIED 2 + +/* + * Standard IOCTL define + */ +#define CTL_CODE( DevType, Function, Method, Access ) ( \ + ((DevType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \ +) + +#define IOCTL_SCSI_PASS_THROUGH CTL_CODE( IOCTL_SCSI_BASE, 0x0401, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS ) +#define IOCTL_SCSI_MINIPORT CTL_CODE( IOCTL_SCSI_BASE, 0x0402, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS ) +#define IOCTL_SCSI_GET_INQUIRY_DATA CTL_CODE( IOCTL_SCSI_BASE, 0x0403, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_SCSI_GET_CAPABILITIES CTL_CODE( IOCTL_SCSI_BASE, 0x0404, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_SCSI_PASS_THROUGH_DIRECT CTL_CODE( IOCTL_SCSI_BASE, 0x0405, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS ) +#define IOCTL_SCSI_GET_ADDRESS CTL_CODE( IOCTL_SCSI_BASE, 0x0406, METHOD_BUFFERED, FILE_ANY_ACCESS + +static bool scsi_inquiry(HANDLE deviceHandle, UCHAR page, UCHAR *inqbuf, size_t &buf_len) +{ + char buf[2048] = {0}; + BOOL status; + PSCSI_PASS_THROUGH_DIRECT_WITH_BUFFER pswb; + + DWORD length=0, returned=0; + + /* + * Get the drive inquiry data + */ + ZeroMemory( &buf, 2048 ); + ZeroMemory( inqbuf, buf_len ); + pswb = (PSCSI_PASS_THROUGH_DIRECT_WITH_BUFFER)buf; + pswb->spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + pswb->spt.CdbLength = 6; + pswb->spt.SenseInfoLength = 32; + pswb->spt.DataIn = SCSI_IOCTL_DATA_IN; + pswb->spt.DataTransferLength = buf_len; + pswb->spt.TimeOutValue = 2; + pswb->spt.DataBuffer = inqbuf; + pswb->spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER,ucSenseBuf); + pswb->spt.Cdb[0] = 0x12; + pswb->spt.Cdb[1] = 0x1; + pswb->spt.Cdb[2] = page; + pswb->spt.Cdb[4] = (UCHAR)buf_len; + + length = sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER); + status = DeviceIoControl( deviceHandle, + IOCTL_SCSI_PASS_THROUGH_DIRECT, + pswb, + length, + pswb, + length, + &returned, + NULL ); + + + if (status && returned >3) + { + buf_len=returned; + return true; + } + else + { + buf_len=0; + return false; + } +} + +static bool AddPagetoXML(HANDLE dev, char *&dest, size_t &destlen, UCHAR page) +{ + UCHAR buf[256] = {0}; + size_t buflen=255; + if (scsi_inquiry(dev, page, buf, buflen)) + { + size_t len = buf[3]; + StringCchCopyNExA(dest, destlen, (char *)buf+4, len, &dest, &destlen, 0); + return true; + } + return false; +} + +static bool BuildXML(HANDLE dev, char *xml, size_t xmllen) +{ + *xml=0; + UCHAR pages[255] = {0}; + size_t pageslen=255; + if (scsi_inquiry(dev, 0xc0, pages, pageslen) && pageslen>3) + { + unsigned char numPages=pages[3]; + if (numPages+4 <= 255) + { + for (int i=0;i +extern api_memmgr *memmgr; +#define WASABI_API_MEMMGR memmgr + +#include "../Agave/AlbumArt/api_albumart.h" +extern api_albumart *albumArtApi; +#define AGAVE_API_ALBUMART albumArtApi + +#include "../Agave/Config/api_config.h" +extern api_config *agaveConfigApi; +#define AGAVE_API_CONFIG agaveConfigApi + +#include "../Agave/Language/api_language.h" + +#include "../nu/threadpool/api_threadpool.h" +extern api_threadpool *threadPoolApi; +#define WASABI_API_THREADPOOL threadPoolApi + +#include +extern api_application *applicationApi; +#define WASABI_API_APP applicationApi + +#include "../devices/api_devicemanager.h" +extern api_devicemanager *deviceManagerApi; +#define AGAVE_API_DEVICEMANAGER deviceManagerApi + +#endif \ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_ipod/deviceprovider.cpp b/Src/Plugins/Portable/pmp_ipod/deviceprovider.cpp new file mode 100644 index 00000000..8b68922a --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/deviceprovider.cpp @@ -0,0 +1,343 @@ +#include "api.h" +#include "./deviceprovider.h" +#include "../devices/api_devicemanager.h" + +extern PMPDevicePlugin plugin; +bool ConnectDrive(wchar_t drive, bool connect); + +static size_t tlsIndex = (size_t)-1; + +static BOOL +DiscoveryProvider_RegisterCancelSwitch(BOOL *cancelSwitch) +{ + if ((size_t)-1 != tlsIndex && + NULL != WASABI_API_APP) + { + WASABI_API_APP->SetThreadStorage(tlsIndex, cancelSwitch); + return TRUE; + } + + return FALSE; +} + +static BOOL +DiscoveryProvider_GetCancelSwitchOn() +{ + if ((size_t)-1 != tlsIndex && + NULL != WASABI_API_APP) + { + BOOL *cancelSwitch = (BOOL*)WASABI_API_APP->GetThreadStorage(tlsIndex); + if (NULL != cancelSwitch && + FALSE != *cancelSwitch) + { + return TRUE; + } + } + return FALSE; +} + +static void +DeviceProvider_DriverEnumCb(wchar_t drive, unsigned int type) +{ + if (DRIVE_REMOVABLE == type && + FALSE == DiscoveryProvider_GetCancelSwitchOn()) + { + ConnectDrive(drive,true); + } +} + +DeviceProvider::DeviceProvider() + : ref(1), activity(0), manager(NULL), readyEvent(NULL), cancelDiscovery(FALSE) +{ + InitializeCriticalSection(&lock); + enumerator = (ENUMDRIVES)SendMessageW(plugin.hwndPortablesParent, + WM_PMP_IPC, 0, PMP_IPC_ENUM_ACTIVE_DRIVES); +} + +DeviceProvider::~DeviceProvider() +{ + CancelDiscovery(); + + if (NULL != readyEvent) + CloseHandle(readyEvent); + + DeleteCriticalSection(&lock); +} + +HRESULT DeviceProvider::CreateInstance(DeviceProvider **instance) +{ + if (NULL == instance) + return E_POINTER; + + *instance = new DeviceProvider(); + + if (NULL == *instance) + return E_OUTOFMEMORY; + + return S_OK; +} + +size_t DeviceProvider::AddRef() +{ + return InterlockedIncrement((LONG*)&ref); +} + +size_t DeviceProvider::Release() +{ + if (0 == ref) + return ref; + + LONG r = InterlockedDecrement((LONG*)&ref); + if (0 == r) + delete(this); + + return r; +} + +int DeviceProvider::QueryInterface(GUID interface_guid, void **object) +{ + if (NULL == object) + return E_POINTER; + + if (IsEqualIID(interface_guid, IFC_DeviceProvider)) + *object = static_cast(this); + else + { + *object = NULL; + return E_NOINTERFACE; + } + + if (NULL == *object) + return E_UNEXPECTED; + + AddRef(); + return S_OK; +} + +void DeviceProvider::Lock() +{ + EnterCriticalSection(&lock); +} + +void DeviceProvider::Unlock() +{ + LeaveCriticalSection(&lock); +} + +DWORD DeviceProvider::DiscoveryThread() +{ + IncrementActivity(); + + if (NULL != enumerator && + FALSE == cancelDiscovery) + { + DiscoveryProvider_RegisterCancelSwitch(&cancelDiscovery); + + enumerator(DeviceProvider_DriverEnumCb); + + DiscoveryProvider_RegisterCancelSwitch(NULL); + } + + DecrementActivity(); + + Lock(); + + if (NULL != readyEvent) + SetEvent(readyEvent); + + Unlock(); + + + return 0; +} + +static int DeviceProvider_DiscoveryThreadStarter(HANDLE handle, void *user, intptr_t id) +{ + DeviceProvider *self; + DWORD result; + + self = (DeviceProvider*)user; + + if (NULL != self) + result = self->DiscoveryThread(); + else + result = -2; + + return result; +} + +HRESULT DeviceProvider::BeginDiscovery(api_devicemanager *manager) +{ + HRESULT hr; + + if (NULL == enumerator) + return E_UNEXPECTED; + + Lock(); + + if (NULL != readyEvent && + WAIT_TIMEOUT == WaitForSingleObject(readyEvent, 0)) + { + hr = E_PENDING; + } + else + { + hr = S_OK; + + if (NULL == readyEvent) + { + readyEvent = CreateEvent(NULL, TRUE, TRUE, NULL); + if (NULL == readyEvent) + hr = E_FAIL; + } + + if ((size_t)-1 == tlsIndex && + NULL != WASABI_API_APP) + { + tlsIndex = WASABI_API_APP->AllocateThreadStorage(); + } + + if (SUCCEEDED(hr)) + { + + cancelDiscovery = FALSE; + ResetEvent(readyEvent); + + if (0 != WASABI_API_THREADPOOL->RunFunction(0, DeviceProvider_DiscoveryThreadStarter, + this, 0, api_threadpool::FLAG_LONG_EXECUTION)) + { + + SetEvent(readyEvent); + hr = E_FAIL; + } + } + } + + Unlock(); + + return hr; +} + +HRESULT DeviceProvider::CancelDiscovery() +{ + HRESULT hr; + + hr = S_FALSE; + + Lock(); + + if (NULL != readyEvent) + { + cancelDiscovery = TRUE; + if (WAIT_OBJECT_0 == WaitForSingleObject(readyEvent, 0)) + hr = S_OK; + + cancelDiscovery = FALSE; + } + + Unlock(); + + return hr; +} + +HRESULT DeviceProvider::GetActive() +{ + HRESULT hr; + + Lock(); + + if (0 != activity) + hr = S_OK; + else + hr = S_FALSE; + + Unlock(); + + return hr; +} + +HRESULT DeviceProvider::Register(api_devicemanager *manager) +{ + HRESULT hr; + + if (NULL != this->manager) + return E_UNEXPECTED; + + if (NULL == manager) + return E_POINTER; + + hr = manager->RegisterProvider(this); + if (SUCCEEDED(hr)) + { + this->manager = manager; + manager->AddRef(); + } + return hr; +} + +HRESULT DeviceProvider::Unregister() +{ + HRESULT hr; + + if (NULL == manager) + return E_UNEXPECTED; + + hr = manager->UnregisterProvider(this); + manager->Release(); + manager = NULL; + return hr; +} + +size_t DeviceProvider::IncrementActivity() +{ + size_t a; + + Lock(); + + activity++; + if (1 == activity && + NULL != manager) + { + manager->SetProviderActive(this, TRUE); + } + + a = activity; + + Unlock(); + + return a; +} + +size_t DeviceProvider::DecrementActivity() +{ + size_t a; + + Lock(); + + if (0 != activity) + { + activity--; + if (0 == activity && + NULL != manager) + { + manager->SetProviderActive(this, FALSE); + } + } + + a = activity; + + Unlock(); + + return a; +} + +#define CBCLASS DeviceProvider +START_DISPATCH; +CB(ADDREF, AddRef) +CB(RELEASE, Release) +CB(QUERYINTERFACE, QueryInterface) +CB(API_BEGINDISCOVERY, BeginDiscovery) +CB(API_CANCELDISCOVERY, CancelDiscovery) +CB(API_GETACTIVE, GetActive) +END_DISPATCH; +#undef CBCLASS \ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_ipod/deviceprovider.h b/Src/Plugins/Portable/pmp_ipod/deviceprovider.h new file mode 100644 index 00000000..d2a6b3a6 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/deviceprovider.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include "../devices/ifc_deviceprovider.h" +#include "..\..\Library\ml_pmp/pmp.h" + +class DeviceProvider : public ifc_deviceprovider +{ +protected: + DeviceProvider(); + ~DeviceProvider(); + +public: + static HRESULT CreateInstance(DeviceProvider **instance); + +public: + /* Dispatchable */ + size_t AddRef(); + size_t Release(); + int QueryInterface(GUID interface_guid, void **object); + + /* ifc_deviceprovider */ + HRESULT BeginDiscovery(api_devicemanager *manager); + HRESULT CancelDiscovery(); + HRESULT GetActive(); + +public: + HRESULT Register(api_devicemanager *manager); + HRESULT Unregister(); + size_t IncrementActivity(); + size_t DecrementActivity(); + +private: + void Lock(); + void Unlock(); + DWORD DiscoveryThread(); + friend static int DeviceProvider_DiscoveryThreadStarter(HANDLE handle, void *user_data, intptr_t id); + +protected: + size_t ref; + size_t activity; + CRITICAL_SECTION lock; + api_devicemanager *manager; + ENUMDRIVES enumerator; + HANDLE readyEvent; + BOOL cancelDiscovery; + +protected: + RECVS_DISPATCH; +}; \ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_ipod/eject.cpp b/Src/Plugins/Portable/pmp_ipod/eject.cpp new file mode 100644 index 00000000..0786f141 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/eject.cpp @@ -0,0 +1,357 @@ +// this file almost totally copied from MSDN + +#include +#include +#include + +#define LOCK_TIMEOUT 3000 // 10 Seconds +#define LOCK_RETRIES 20 + +#if 1 // old way +static HANDLE OpenVolume(TCHAR cDriveLetter) +{ + HANDLE hVolume; + UINT uDriveType; + wchar_t szVolumeName[8] = {0}; + wchar_t szRootName[5] = {0}; + DWORD dwAccessFlags = 0; + cDriveLetter &= ~0x20; // capitalize + wsprintf(szRootName, L"%c:\\", cDriveLetter); + + uDriveType = GetDriveType(szRootName); + switch(uDriveType) { + case DRIVE_REMOVABLE: + dwAccessFlags = GENERIC_READ | GENERIC_WRITE; + break; + case DRIVE_CDROM: + dwAccessFlags = GENERIC_READ; + break; + default: + printf("Cannot eject. Drive type is incorrect.\n"); + return INVALID_HANDLE_VALUE; + } + + wsprintf(szVolumeName, L"\\\\.\\%c:", cDriveLetter); + + hVolume = CreateFile( szVolumeName, + dwAccessFlags, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + 0, + NULL ); + + if (hVolume == INVALID_HANDLE_VALUE) + printf("CreateFile error %d\n", GetLastError()); + return hVolume; +} + +static BOOL CloseVolume(HANDLE hVolume) +{ + return CloseHandle(hVolume); +} + +static BOOL LockVolume(HANDLE hVolume) +{ + DWORD dwBytesReturned; + DWORD dwSleepAmount; + int nTryCount; + + dwSleepAmount = LOCK_TIMEOUT / LOCK_RETRIES; + + // Do this in a loop until a timeout period has expired + for (nTryCount = 0; nTryCount < LOCK_RETRIES; nTryCount++) { + if (DeviceIoControl(hVolume, + FSCTL_LOCK_VOLUME, + NULL, 0, + NULL, 0, + &dwBytesReturned, + NULL)) + return TRUE; + + Sleep(dwSleepAmount); + } + return FALSE; +} + +static BOOL DismountVolume(HANDLE hVolume) +{ + DWORD dwBytesReturned; + + return DeviceIoControl( hVolume, + FSCTL_DISMOUNT_VOLUME, + NULL, 0, + NULL, 0, + &dwBytesReturned, + NULL); +} + +static BOOL PreventRemovalOfVolume(HANDLE hVolume, BOOL fPreventRemoval) +{ + DWORD dwBytesReturned; + PREVENT_MEDIA_REMOVAL PMRBuffer; + + PMRBuffer.PreventMediaRemoval = fPreventRemoval; + + return DeviceIoControl( hVolume, + IOCTL_STORAGE_MEDIA_REMOVAL, + &PMRBuffer, sizeof(PREVENT_MEDIA_REMOVAL), + NULL, 0, + &dwBytesReturned, + NULL); +} + +static int AutoEjectVolume(HANDLE hVolume) +{ + DWORD dwBytesReturned; + + return DeviceIoControl( hVolume, + IOCTL_STORAGE_EJECT_MEDIA, + NULL, 0, + NULL, 0, + &dwBytesReturned, + NULL); +} + +BOOL EjectVolume(TCHAR cDriveLetter) +{ + HANDLE hVolume; + + BOOL fAutoEject = FALSE; + + hVolume = OpenVolume(cDriveLetter); + if (hVolume == INVALID_HANDLE_VALUE) + return FALSE; + + // Lock and dismount the volume. + if (LockVolume(hVolume) && DismountVolume(hVolume)) { + // Set prevent removal to false and eject the volume. + if (PreventRemovalOfVolume(hVolume, FALSE) && AutoEjectVolume(hVolume)) + fAutoEject = TRUE; + } + + // Close the volume so other processes can use the drive. + if (!CloseVolume(hVolume)) + return FALSE; + + if (fAutoEject) return TRUE; + else return FALSE; +} +#else +#include + +#include + +#include +#include +#include +#include +#pragma comment(lib, "setupapi.lib") +//------------------------------------------------- +//---------------------------------------------------------------------- +// returns the device instance handle of a storage volume or 0 on error +//---------------------------------------------------------------------- +static DEVINST GetDrivesDevInstByDeviceNumber(long DeviceNumber, UINT DriveType, const wchar_t* szDosDeviceName) +{ + bool IsFloppy = (wcsstr(szDosDeviceName, L"\\Floppy") != NULL); // who knows a better way? + + GUID* guid; + + switch (DriveType) { + case DRIVE_REMOVABLE: + if ( IsFloppy ) { + guid = (GUID*)&GUID_DEVINTERFACE_FLOPPY; + } else { + guid = (GUID*)&GUID_DEVINTERFACE_DISK; + } + break; + case DRIVE_FIXED: + guid = (GUID*)&GUID_DEVINTERFACE_DISK; + break; + case DRIVE_CDROM: + guid = (GUID*)&GUID_DEVINTERFACE_CDROM; + break; + default: + return 0; + } + + // Get device interface info set handle for all devices attached to system + HDEVINFO hDevInfo = SetupDiGetClassDevs(guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + + if (hDevInfo == INVALID_HANDLE_VALUE) { + return 0; + } + + // Retrieve a context structure for a device interface of a device information set + DWORD dwIndex = 0; + long res; + + BYTE Buf[1024] = {0}; + PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)Buf; + SP_DEVICE_INTERFACE_DATA spdid; + SP_DEVINFO_DATA spdd; + DWORD dwSize; + + spdid.cbSize = sizeof(spdid); + + while ( true ) { + res = SetupDiEnumDeviceInterfaces(hDevInfo, NULL, guid, dwIndex, &spdid); + if ( !res ) { + break; + } + + dwSize = 0; + SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, NULL, 0, &dwSize, NULL); // check the buffer size + + if ( dwSize!=0 && dwSize<=sizeof(Buf) ) { + + pspdidd->cbSize = sizeof(*pspdidd); // 5 Bytes! + + ZeroMemory(&spdd, sizeof(spdd)); + spdd.cbSize = sizeof(spdd); + + long res = SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, pspdidd, dwSize, &dwSize, &spdd); + if ( res ) { + + // in case you are interested in the USB serial number: + // the device id string contains the serial number if the device has one, + // otherwise a generated id that contains the '&' char... + /* + DEVINST DevInstParent = 0; + CM_Get_Parent(&DevInstParent, spdd.DevInst, 0); + char szDeviceIdString[MAX_PATH] = {0}; + CM_Get_Device_ID(DevInstParent, szDeviceIdString, MAX_PATH, 0); + printf("DeviceId=%s\n", szDeviceIdString); + */ + + // open the disk or cdrom or floppy + HANDLE hDrive = CreateFile(pspdidd->DevicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if ( hDrive != INVALID_HANDLE_VALUE ) { + // get its device number + STORAGE_DEVICE_NUMBER sdn; + DWORD dwBytesReturned = 0; + res = DeviceIoControl(hDrive, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &dwBytesReturned, NULL); + if ( res ) { + if ( DeviceNumber == (long)sdn.DeviceNumber ) { // match the given device number with the one of the current device + CloseHandle(hDrive); + SetupDiDestroyDeviceInfoList(hDevInfo); + return spdd.DevInst; + } + } + CloseHandle(hDrive); + } + } + } + dwIndex++; + } + + SetupDiDestroyDeviceInfoList(hDevInfo); + + return 0; +} +//------------------------------------------------- + + + +//------------------------------------------------- +BOOL EjectVolume(TCHAR DriveLetter) +{ + DriveLetter &= ~0x20; // uppercase + + if ( DriveLetter < 'A' || DriveLetter > 'Z' ) { + return FALSE; + } + + wchar_t szRootPath[] = L"X:\\"; // "X:\" -> for GetDriveType + szRootPath[0] = DriveLetter; + + wchar_t szDevicePath[] = L"X:"; // "X:" -> for QueryDosDevice + szDevicePath[0] = DriveLetter; + + wchar_t szVolumeAccessPath[] = L"\\\\.\\X:"; // "\\.\X:" -> to open the volume + szVolumeAccessPath[4] = DriveLetter; + + long DeviceNumber = -1; + + // open the storage volume + HANDLE hVolume = CreateFileW(szVolumeAccessPath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL); + if (hVolume == INVALID_HANDLE_VALUE) { + return FALSE; + } + + // get the volume's device number + STORAGE_DEVICE_NUMBER sdn; + DWORD dwBytesReturned = 0; + long res = DeviceIoControl(hVolume, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &dwBytesReturned, NULL); + if ( res ) { + DeviceNumber = sdn.DeviceNumber; + } + CloseHandle(hVolume); + + if ( DeviceNumber == -1 ) { + return FALSE; + } + + // get the drive type which is required to match the device numbers correctely + UINT DriveType = GetDriveType(szRootPath); + + // get the dos device name (like \device\floppy0) to decide if it's a floppy or not - who knows a better way? + wchar_t szDosDeviceName[MAX_PATH] = {0}; + res = QueryDosDevice(szDevicePath, szDosDeviceName, MAX_PATH); + if ( !res ) { + return FALSE; + } + + // get the device instance handle of the storage volume by means of a SetupDi enum and matching the device number + DEVINST DevInst = GetDrivesDevInstByDeviceNumber(DeviceNumber, DriveType, szDosDeviceName); + + if ( DevInst == 0 ) { + return FALSE; + } + + PNP_VETO_TYPE VetoType = PNP_VetoTypeUnknown; + wchar_t VetoNameW[MAX_PATH] = {0}; + bool bSuccess = false; + + // get drives's parent, e.g. the USB bridge, the SATA port, an IDE channel with two drives! + DEVINST DevInstParent = 0; + res = CM_Get_Parent(&DevInstParent, DevInst, 0); + + for ( long tries=1; tries<=3; tries++ ) { // sometimes we need some tries... + + VetoNameW[0] = 0; + + // CM_Query_And_Remove_SubTree doesn't work for restricted users + //res = CM_Query_And_Remove_SubTreeW(DevInstParent, &VetoType, VetoNameW, MAX_PATH, CM_REMOVE_NO_RESTART); // CM_Query_And_Remove_SubTreeA is not implemented under W2K! + //res = CM_Query_And_Remove_SubTreeW(DevInstParent, NULL, NULL, 0, CM_REMOVE_NO_RESTART); // with messagebox (W2K, Vista) or balloon (XP) + + res = CM_Request_Device_EjectW(DevInstParent, &VetoType, VetoNameW, MAX_PATH, 0); + //res = CM_Request_Device_EjectW(DevInstParent, NULL, NULL, 0, 0); // with messagebox (W2K, Vista) or balloon (XP) + + bSuccess = (res==CR_SUCCESS && VetoType==PNP_VetoTypeUnknown); + if ( bSuccess ) { + break; + } + + Sleep(500); // required to give the next tries a chance! + } + + if ( bSuccess ) { + printf("Success\n\n"); + return TRUE; + } + + printf("failed\n"); + + printf("Result=0x%2X\n", res); + + if ( VetoNameW[0] ) { + printf("VetoName=%ws)\n\n", VetoNameW); + } + return FALSE; +} +//----------------------------------------------------------- + + + +#endif \ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_ipod/filecopy.cpp b/Src/Plugins/Portable/pmp_ipod/filecopy.cpp new file mode 100644 index 00000000..e229d795 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/filecopy.cpp @@ -0,0 +1,69 @@ +#include "api.h" +#include +#include +#include +#include "resource.h" +#include + +typedef struct CopyData +{ + void * callbackContext; + void (*callback)(void * callbackContext, wchar_t * status); +} CopyData; + +DWORD CALLBACK CopyToIpodProgressRoutine(LARGE_INTEGER TotalFileSize, LARGE_INTEGER TotalBytesTransferred, + LARGE_INTEGER StreamSize, LARGE_INTEGER StreamBytesTransferred, + DWORD dwStreamNumber, + DWORD dwCallbackReason, + HANDLE hSourceFile, HANDLE hDestinationFile, + LPVOID lpData) +{ + CopyData *inst = (CopyData *)lpData; + if (inst && inst->callback) + { + wchar_t status[100] = {0}; + wchar_t langtemp[100] = {0}; + StringCbPrintf(status, sizeof(status), WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFERRING_PERCENT, langtemp, 100), (int)(100ULL * TotalBytesTransferred.QuadPart / (TotalFileSize.QuadPart))); + inst->callback(inst->callbackContext,status); + } + return PROGRESS_CONTINUE; +} + +int CopyFile(const wchar_t *infile, const wchar_t *outfile, void * callbackContext, void (*callback)(void * callbackContext, wchar_t * status), int * killswitch) +{ + wchar_t langtemp[100] = {0}; + + CopyData c; + c.callback = callback; + c.callbackContext = callbackContext; + + if (CopyFileEx(infile, outfile, CopyToIpodProgressRoutine, &c, killswitch,0)) + { + if (callback) + { + callback(callbackContext, WASABI_API_LNGSTRINGW_BUF(IDS_DONE, langtemp, 100)); + } + return 0; + } + else + { + switch(GetLastError()) + { + case ERROR_REQUEST_ABORTED: + DeleteFile(outfile); + if (callback) + { + callback(callbackContext, WASABI_API_LNGSTRINGW_BUF(IDS_CANCELLED, langtemp, 100)); + } + + default: + if (callback) + { + callback(callbackContext, WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFER_FAILED, langtemp, 100)); + } + + } + return -1; + } + +} \ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_ipod/hash58.cpp b/Src/Plugins/Portable/pmp_ipod/hash58.cpp new file mode 100644 index 00000000..ca10af92 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/hash58.cpp @@ -0,0 +1,154 @@ +#include "sha1.h" +#include +#include + +unsigned char invTable[256] = { + 0x74, 0x85, 0x96, 0xA7, 0xB8, 0xC9, 0xDA, 0xEB, 0xFC, 0x0D, 0x1E, 0x2F, 0x40, 0x51, 0x62, 0x73, + 0x84, 0x95, 0xA6, 0xB7, 0xC8, 0xD9, 0xEA, 0xFB, 0x0C, 0x1D, 0x2E, 0x3F, 0x50, 0x61, 0x72, 0x83, + 0x94, 0xA5, 0xB6, 0xC7, 0xD8, 0xE9, 0xFA, 0x0B, 0x1C, 0x2D, 0x3E, 0x4F, 0x60, 0x71, 0x82, 0x93, + 0xA4, 0xB5, 0xC6, 0xD7, 0xE8, 0xF9, 0x0A, 0x1B, 0x2C, 0x3D, 0x4E, 0x5F, 0x70, 0x81, 0x92, 0xA3, + 0xB4, 0xC5, 0xD6, 0xE7, 0xF8, 0x09, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E, 0x6F, 0x80, 0x91, 0xA2, 0xB3, + 0xC4, 0xD5, 0xE6, 0xF7, 0x08, 0x19, 0x2A, 0x3B, 0x4C, 0x5D, 0x6E, 0x7F, 0x90, 0xA1, 0xB2, 0xC3, + 0xD4, 0xE5, 0xF6, 0x07, 0x18, 0x29, 0x3A, 0x4B, 0x5C, 0x6D, 0x7E, 0x8F, 0xA0, 0xB1, 0xC2, 0xD3, + 0xE4, 0xF5, 0x06, 0x17, 0x28, 0x39, 0x4A, 0x5B, 0x6C, 0x7D, 0x8E, 0x9F, 0xB0, 0xC1, 0xD2, 0xE3, + 0xF4, 0x05, 0x16, 0x27, 0x38, 0x49, 0x5A, 0x6B, 0x7C, 0x8D, 0x9E, 0xAF, 0xC0, 0xD1, 0xE2, 0xF3, + 0x04, 0x15, 0x26, 0x37, 0x48, 0x59, 0x6A, 0x7B, 0x8C, 0x9D, 0xAE, 0xBF, 0xD0, 0xE1, 0xF2, 0x03, + 0x14, 0x25, 0x36, 0x47, 0x58, 0x69, 0x7A, 0x8B, 0x9C, 0xAD, 0xBE, 0xCF, 0xE0, 0xF1, 0x02, 0x13, + 0x24, 0x35, 0x46, 0x57, 0x68, 0x79, 0x8A, 0x9B, 0xAC, 0xBD, 0xCE, 0xDF, 0xF0, 0x01, 0x12, 0x23, + 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0x9A, 0xAB, 0xBC, 0xCD, 0xDE, 0xEF, 0x00, 0x11, 0x22, 0x33, + 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x10, 0x21, 0x32, 0x43, + 0x54, 0x65, 0x76, 0x87, 0x98, 0xA9, 0xBA, 0xCB, 0xDC, 0xED, 0xFE, 0x0F, 0x20, 0x31, 0x42, 0x53, + 0x64, 0x75, 0x86, 0x97, 0xA8, 0xB9, 0xCA, 0xDB, 0xEC, 0xFD, 0x0E, 0x1F, 0x30, 0x41, 0x52, 0x63 +}; + +unsigned char table1[256] = { + 0x3A, 0x3F, 0x3E, 0x72, 0xBD, 0xA2, 0xD6, 0xB4, 0x63, 0xC0, 0x6E, 0x62, 0x59, 0x1E, 0xE2, 0x71, + 0xB5, 0x0D, 0xE8, 0x0C, 0x25, 0x38, 0xCE, 0x23, 0x7C, 0xB7, 0xAD, 0x16, 0xDF, 0x47, 0x3D, 0xB3, + 0x7E, 0x8C, 0xAA, 0x61, 0x31, 0x66, 0xBE, 0x4F, 0x97, 0x14, 0x54, 0xF0, 0x70, 0xEB, 0x30, 0xC4, + 0x27, 0x4E, 0xFA, 0x1A, 0x2B, 0x11, 0xF4, 0x45, 0x8E, 0x5D, 0x73, 0xED, 0x22, 0x2E, 0x7D, 0xA4, + 0x28, 0xDA, 0x2F, 0xC5, 0x92, 0x09, 0x05, 0x13, 0x9D, 0x32, 0x51, 0x4A, 0xC8, 0xBA, 0x96, 0xA7, + 0x6A, 0x50, 0xF3, 0xBC, 0x93, 0xBF, 0xB0, 0xD2, 0xD5, 0x82, 0x19, 0x98, 0x35, 0xCF, 0x6B, 0xB6, + 0x83, 0x56, 0x15, 0xF2, 0x9A, 0x9C, 0xCA, 0x74, 0x34, 0x58, 0x8D, 0xA6, 0x03, 0xFF, 0x46, 0x7B, + 0xD0, 0x7A, 0x33, 0x76, 0xDD, 0xAC, 0xCB, 0x24, 0x7F, 0xB1, 0x85, 0x60, 0xC3, 0x26, 0x8A, 0x1D, + 0x1C, 0x8F, 0x2A, 0xEF, 0x06, 0xDE, 0x67, 0x5E, 0xE7, 0xAE, 0xD9, 0xCC, 0x07, 0x6C, 0xF8, 0x0A, + 0xD3, 0x40, 0x36, 0x1F, 0x2D, 0x95, 0x43, 0xDB, 0x01, 0x89, 0x4B, 0xF7, 0xB9, 0x39, 0xC2, 0x52, + 0x53, 0xFD, 0x65, 0xF5, 0x68, 0xC1, 0xC7, 0x9F, 0x4D, 0xEA, 0xAF, 0x6D, 0x10, 0x44, 0x87, 0xD8, + 0xEE, 0x1B, 0xFE, 0x3C, 0xDC, 0x84, 0x69, 0x48, 0x6F, 0xD1, 0x57, 0x55, 0xD4, 0xA5, 0x49, 0x5B, + 0xE5, 0x0B, 0x94, 0xC9, 0x5F, 0xE1, 0x17, 0x81, 0xBB, 0xEC, 0xD7, 0xC6, 0x02, 0x4C, 0x42, 0x75, + 0xA3, 0x99, 0xE4, 0xA1, 0x9B, 0x5A, 0xF1, 0x29, 0xA0, 0x64, 0x9E, 0x18, 0x41, 0x80, 0x2C, 0x79, + 0x20, 0x8B, 0xAB, 0x90, 0x08, 0xB8, 0xA9, 0x77, 0x12, 0xF9, 0x0E, 0x88, 0xE9, 0x04, 0xFB, 0x86, + 0x0F, 0xE0, 0xA8, 0x5C, 0xE6, 0x21, 0xCD, 0x3B, 0x00, 0x78, 0xFC, 0xF6, 0xE3, 0x37, 0xB2, 0x91 +}; + +unsigned char table2[256] = { + 0xF3, 0xE4, 0x1B, 0x38, 0xE5, 0x6F, 0xE8, 0x9D, 0x3E, 0x55, 0xBA, 0xC7, 0xAC, 0xEA, 0x66, 0xA2, + 0xB9, 0x7A, 0x34, 0x43, 0x02, 0x4E, 0xFE, 0x36, 0x41, 0x57, 0x1A, 0xB1, 0x31, 0x87, 0x04, 0x52, + 0x21, 0x22, 0xE1, 0x13, 0x7F, 0x03, 0x3A, 0x90, 0xF7, 0x69, 0x78, 0x12, 0x83, 0x0B, 0x9A, 0x97, + 0x4D, 0xB7, 0x8C, 0xBF, 0x2D, 0x94, 0xD1, 0x93, 0x2F, 0x42, 0x23, 0xA4, 0xE0, 0x92, 0xDC, 0x68, + 0xD3, 0xDD, 0xAF, 0x91, 0x9F, 0xED, 0x3D, 0x8F, 0xA1, 0x51, 0xD9, 0xE9, 0x70, 0x28, 0xEF, 0xB3, + 0x49, 0xA5, 0x0D, 0xC5, 0xD0, 0x60, 0xB4, 0x2B, 0x07, 0xF8, 0xDF, 0xE6, 0x16, 0xC0, 0x30, 0x71, + 0x85, 0xFD, 0x72, 0x95, 0x29, 0x79, 0x0A, 0x7B, 0x46, 0x11, 0x7D, 0x88, 0x1D, 0x2A, 0x48, 0x1F, + 0x45, 0x89, 0x47, 0xEE, 0xBB, 0xBE, 0x6E, 0xC3, 0x6C, 0xCE, 0x10, 0x5A, 0x2C, 0xCA, 0xFB, 0xB2, + 0xCB, 0x1C, 0x9C, 0xEC, 0x2E, 0x56, 0x59, 0x9B, 0xA6, 0x53, 0xAE, 0x17, 0x25, 0xC1, 0x3F, 0x6A, + 0x0F, 0x09, 0x01, 0xA3, 0xD6, 0xA0, 0xD8, 0x08, 0xE3, 0x74, 0x06, 0x6D, 0x19, 0x98, 0x1E, 0x77, + 0x76, 0xBC, 0xEB, 0x3C, 0xB0, 0xC4, 0xC8, 0x64, 0x0E, 0x86, 0x63, 0xD7, 0xDB, 0xBD, 0xA7, 0x82, + 0x39, 0x4F, 0x27, 0xD2, 0x5F, 0x73, 0xF4, 0x75, 0x6B, 0xC2, 0xD5, 0x67, 0x5D, 0x80, 0xAB, 0x81, + 0xDE, 0xF0, 0xAD, 0xAA, 0xCD, 0xB6, 0xF6, 0x7C, 0xFC, 0x33, 0x05, 0x14, 0x96, 0x15, 0xC9, 0x9E, + 0x35, 0x5C, 0x7E, 0x44, 0x54, 0x58, 0x3B, 0x40, 0x20, 0xA8, 0x8B, 0x5E, 0x4A, 0x24, 0x99, 0x8E, + 0xF5, 0xB5, 0x62, 0x00, 0x37, 0x5B, 0x18, 0x65, 0x8D, 0x32, 0xE2, 0xF9, 0xDA, 0x8A, 0xD4, 0xCC, + 0x26, 0xF2, 0xF1, 0xE7, 0x4B, 0xC6, 0xCF, 0xFF, 0x4C, 0x84, 0x61, 0xFA, 0xB8, 0x0C, 0xA9, 0x50 +}; + +unsigned char fixed[18] = { + 0x67, 0x23, 0xFE, 0x30, 0x45, 0x33, 0xF8, 0x90, 0x99, 0x21, 0x07, 0xC1, 0xD0, 0x12, 0xB2, 0xA1, 0x07, 0x81 +}; + +int GCD(int a, int b){ + while( 1 ) + { + a = a % b; + if( a == 0 ) + return b; + b = b % a; + if( b == 0 ) + return a; + } +} + +int LCM(int a, int b) +{ + if(a==0 || b==0) + return 1; + + return (a*b)/GCD(a,b); +} + + +//pFWID -> 8 bytes +//pKey -> 64 byte buffer +void GenerateKey(unsigned char *pFWID, unsigned char *pKey){ + memset(pKey,0, 64); + + int i; + unsigned char y[16] = {0}; + //take LCM of each two bytes in the FWID in turn + for(i=0;i<4;i++){ + int a=pFWID[i*2]; + int b=pFWID[i*2+1]; + int lcm = LCM(a,b); + + unsigned char hi = (lcm & 0xFF00) >> 8; + unsigned char lo = lcm & 0xFF; + + y[i*4] = ((table1[hi] * 0xB5) - 3); + y[i*4 + 1] = ((table2[hi] * 0xB7) + 0x49); + y[i*4 + 2] = ((table1[lo] * 0xB5) - 3); + y[i*4 + 3] = ((table2[lo] * 0xB7) + 0x49); + } + + //convert y + for(i=0;i<16;i++){ + y[i] = invTable[y[i]]; + } + + //hash + SHA1_CTX context; + SHA1Init(&context); + SHA1Update(&context, fixed, 18); + SHA1Update(&context, y, 16); + SHA1Final(pKey, &context); +} + +//pDataBase -> iTunesDB +//pFWID -> 8 bytes +//pHash -> 20 byte buffer +void GenerateHash(unsigned char *pFWID, unsigned char *pDataBase0, long lSize, unsigned char *pHash) +{ + unsigned char *pDataBase = (unsigned char*)malloc(lSize); + memcpy(pDataBase,pDataBase0,lSize); + //generate invtable + unsigned char key[64] = {0}; + GenerateKey(pFWID, key); + + //hmac sha1 + int i; + for (i=0; i < 64; i++) + key[i] ^= 0x36; + + SHA1_CTX context; + + SHA1Init(&context); + SHA1Update(&context, key, 64); + SHA1Update(&context, pDataBase, lSize); + SHA1Final(pHash, &context); + + for (i=0; i < 64; i++) + key[i] ^= 0x36 ^ 0x5c; + + SHA1Init(&context); + SHA1Update(&context, key, 64); + SHA1Update(&context, pHash, 20); + SHA1Final(pHash, &context); + + free(pDataBase); +} diff --git a/Src/Plugins/Portable/pmp_ipod/hash58.h b/Src/Plugins/Portable/pmp_ipod/hash58.h new file mode 100644 index 00000000..ec9153bb --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/hash58.h @@ -0,0 +1 @@ +void GenerateHash(unsigned char *pFWID, unsigned char *pDataBase, long lSize, unsigned char *pHash); diff --git a/Src/Plugins/Portable/pmp_ipod/iPodArtworkDB.cpp b/Src/Plugins/Portable/pmp_ipod/iPodArtworkDB.cpp new file mode 100644 index 00000000..a3eca956 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/iPodArtworkDB.cpp @@ -0,0 +1,914 @@ +/* + * + * + * Copyright (c) 2007 Will Fisher (will.fisher@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. + * + * + * + */ + +#include "iPodArtworkDB.h" +#include +#include + +//utilities +#define SAFEDELETE(x) {if(x) delete (x); (x)=0;} +#define SAFEFREE(x) {if(x) free(x); (x)=0;} + +static __forceinline unsigned short rev1(const BYTE *data) +{ + return ((unsigned short) data[0]); +} + +static __forceinline unsigned short rev1i(const BYTE *data, int &ptr) +{ + unsigned short ret = rev1(data+ptr); + ptr+=1; + return ret; +} + +static __forceinline unsigned short rev2(const BYTE *data) +{ + unsigned short ret; + ret = ((unsigned short) data[1]) << 8; + ret += ((unsigned short) data[0]); + return ret; +} + +static __forceinline unsigned short rev2i(const BYTE *data, int &ptr) +{ + unsigned short ret = rev2(data+ptr); + ptr+=2; + return ret; +} + +// get 4 bytes from data, reversed +static __forceinline unsigned int rev4(const BYTE * data) +{ + unsigned int 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; +} + +static __forceinline unsigned int rev4i(const BYTE * data, int &ptr) +{ + unsigned int ret = rev4(data+ptr); + ptr+=4; + return ret; +} + +// get 4 bytes from data +static __forceinline unsigned long get4(const unsigned char * 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; +} + +static __forceinline unsigned long get4i(const unsigned char * data, int &ptr) +{ + unsigned long ret = get4(data+ptr); + ptr+=4; + return ret; +} + +// get 8 bytes from data +static __forceinline unsigned __int64 get8(const unsigned char * data) +{ + unsigned __int64 ret; + ret = get4(data); + ret = ret << 32; + ret+= get4(data+4); + return ret; +} + +// get 8 bytes from data +static __forceinline unsigned __int64 get8i(const unsigned char * data, int &ptr) +{ + unsigned __int64 ret = get8(data+ptr); + ptr+=8; + return ret; +} + +// reverse 8 bytes in place +static __forceinline unsigned __int64 rev8(unsigned __int64 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; +} + +static __forceinline void putmh(const char* x, BYTE *data, int &ptr) { + data[0+ptr]=x[0]; + data[1+ptr]=x[1]; + data[2+ptr]=x[2]; + data[3+ptr]=x[3]; + ptr+=4; +} + + +//write 4 bytes reversed +static __forceinline void rev4(const unsigned long number, unsigned char * data) +{ + data[3] = (unsigned char)(number >> 24) & 0xff; + data[2] = (unsigned char)(number >> 16) & 0xff; + data[1] = (unsigned char)(number >> 8) & 0xff; + data[0] = (unsigned char)number & 0xff; +} + +static __forceinline void rev4i(const unsigned int number, BYTE* data, int &ptr) +{ + rev4(number,data+ptr); + ptr+=4; +} + +static __forceinline void rev2(const unsigned short number, unsigned char * data) +{ + data[1] = (unsigned char)(number >> 8) & 0xff; + data[0] = (unsigned char)number & 0xff; +} + +static __forceinline void rev2i(const unsigned short number, BYTE* data, int &ptr) +{ + rev2(number,data+ptr); + ptr+=2; +} + +static __forceinline void rev1(const unsigned char number, unsigned char * data) +{ + data[0] = number; +} + +static __forceinline void rev1i(const unsigned char number, BYTE* data, int &ptr) +{ + rev1(number,data+ptr); + ptr+=1; +} + +// write 8 bytes normal +static __forceinline void put8(unsigned __int64 number, unsigned char * data) +{ + data[0] = (unsigned char)(number >> 56) & 0xff; + data[1] = (unsigned char)(number >> 48) & 0xff; + data[2] = (unsigned char)(number >> 40) & 0xff; + data[3] = (unsigned char)(number >> 32) & 0xff; + data[4] = (unsigned char)(number >> 24) & 0xff; + data[5] = (unsigned char)(number >> 16) & 0xff; + data[6] = (unsigned char)(number >> 8) & 0xff; + data[7] = (unsigned char)number & 0xff; +} + +static __forceinline void put8i(unsigned __int64 number, unsigned char * data, int &ptr) { + put8(number,data+ptr); + ptr+=8; +} + +static __forceinline void pad(BYTE * data, int endpoint, int& startpoint) { + if(endpoint == startpoint) return; + ZeroMemory(data+startpoint, endpoint - startpoint); + startpoint = endpoint; +} + +/////////////////////////////////////////////////////////////////////////////// +// ArtDB + +ArtDB::ArtDB() : + headerlen(0x84), + totallen(0), + unk1(0), + unk2(2), + unk3(0), + nextid(0x40), + unk5(0), + unk6(0), + unk7(0), + unk8(0), + unk9(0), + unk10(0), + unk11(0), + imageListDS(0), + albumListDS(0), + fileListDS(0) +{ +} + +ArtDB::~ArtDB() { + SAFEDELETE(imageListDS); + SAFEDELETE(albumListDS); + SAFEDELETE(fileListDS); +} + +int ArtDB::parse(BYTE * data, int len, wchar_t drive) { + int ptr=4; + if(len < headerlen) return -1; + if (_strnicmp((char *)data,"mhfd",4)) return -1; + headerlen = rev4i(data,ptr); + if(headerlen < 0x84) return -1; + totallen = rev4i(data,ptr); + unk1 = rev4i(data,ptr); + unk2 = rev4i(data,ptr); + int numchildren = rev4i(data,ptr); + unk3 = rev4i(data,ptr); + nextid = rev4i(data,ptr); + unk5 = rev8(get8i(data,ptr)); + unk6 = rev8(get8i(data,ptr)); + unk7 = rev4i(data,ptr); + unk8 = rev4i(data,ptr); + unk9 = rev4i(data,ptr); + unk10 = rev4i(data,ptr); + unk11 = rev4i(data,ptr); + + ptr=headerlen; + + for(int i=0; iparse(data+ptr,len-ptr); + if(p == -1) return -1; + switch(d->index) { + case 1: imageListDS = d; break; + case 2: albumListDS = d; break; + case 3: fileListDS = d; break; + default: delete d; + } + ptr+=p; + } + if(!imageListDS) imageListDS = new ArtDataSet(1); + if(!albumListDS) albumListDS = new ArtDataSet(2); + if(!fileListDS) fileListDS = new ArtDataSet(3); + + for(ArtImageList::ArtImageMapIterator i = imageListDS->imageList->images.begin(); i!=imageListDS->imageList->images.end(); i++) { + if(i->second) { + for(auto j = i->second->dataobjs.begin(); j != i->second->dataobjs.end(); j++) { + if((*j)->image) { + ArtFile *f = fileListDS->fileList->getFile((*j)->image->corrid); + if(!f) { + f = new ArtFile(); + f->corrid = (*j)->image->corrid; + fileListDS->fileList->files.push_back(f); + } + f->images.push_back(new ArtFileImage((*j)->image->ithmboffset,(*j)->image->imagesize,1)); + } + } + } + } + + for(auto i = fileListDS->fileList->files.begin(); i!=fileListDS->fileList->files.end(); i++) { + wchar_t file[MAX_PATH] = {0}; + StringCchPrintfW(file, MAX_PATH, L"%c:\\iPod_Control\\Artwork\\F%04d_1.ithmb",drive,(*i)->corrid); + (*i)->file = _wcsdup(file); + (*i)->sortImages(); + } + + return totallen; +} + +int ArtDB::write(BYTE *data, int len) { + int ptr=0; + if(headerlen > len) return -1; + putmh("mhfd",data,ptr); + rev4i(headerlen,data,ptr); + rev4i(0,data,ptr); // fill total len here later + rev4i(unk1,data,ptr); + rev4i(unk2,data,ptr); // always seems to be "2" when iTunes writes it + rev4i(3,data,ptr); // num children + rev4i(unk3,data,ptr); + rev4i(nextid,data,ptr); + put8i(rev8(unk5),data,ptr); + put8i(rev8(unk6),data,ptr); + rev4i(unk7,data,ptr); + rev4i(unk8,data,ptr); + rev4i(unk9,data,ptr); + rev4i(unk10,data,ptr); + rev4i(unk11,data,ptr); + + pad(data,headerlen,ptr); + + // write out children + int p; + p = imageListDS->write(data+ptr,len-ptr); + if(p<0) return -1; + ptr+=p; + + p = albumListDS->write(data+ptr,len-ptr); + if(p<0) return -1; + ptr+=p; + + p = fileListDS->write(data+ptr,len-ptr); + if(p<0) return -1; + ptr+=p; + + rev4(ptr,&data[8]); // fill in total length + + return ptr; +} + +void ArtDB::makeEmptyDB(wchar_t drive) { + imageListDS = new ArtDataSet(1); + albumListDS = new ArtDataSet(2); + fileListDS = new ArtDataSet(3); + + for(auto i = fileListDS->fileList->files.begin(); i!=fileListDS->fileList->files.end(); i++) { + wchar_t file[MAX_PATH] = {0}; + StringCchPrintfW(file, MAX_PATH, L"%c:\\iPod_Control\\Artwork\\F%04d_1.ithmb",drive,(*i)->corrid); + (*i)->file = _wcsdup(file); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// ArtDatSet + +ArtDataSet::ArtDataSet() : + headerlen(0x60), + totallen(0), + index(0), + imageList(0), + albumList(0), + fileList(0) +{ +} + +ArtDataSet::ArtDataSet(int idx) : + headerlen(0x60), + totallen(0), + index(idx), + imageList(0), + albumList(0), + fileList(0) +{ + switch(idx) { + case 1: imageList = new ArtImageList; break; + case 2: albumList = new ArtAlbumList; break; + case 3: fileList = new ArtFileList; break; + default: index=0; + } +} + +ArtDataSet::~ArtDataSet() { + SAFEDELETE(imageList); + SAFEDELETE(albumList); + SAFEDELETE(fileList); +} + +int ArtDataSet::parse(BYTE *data, int len) { + int ptr=4; + if(len < headerlen) return -1; + if (_strnicmp((char *)data,"mhsd",4)) return -1; + headerlen = rev4i(data,ptr); + if(headerlen < 0x60) return -1; + totallen = rev4i(data,ptr); + index = rev4i(data,ptr); + + ptr=headerlen; + + int p=0; + switch(index) { + case 1: imageList = new ArtImageList; p = imageList->parse(data+ptr, len-ptr); break; + case 2: albumList = new ArtAlbumList; p = albumList->parse(data+ptr, len-ptr); break; + case 3: fileList = new ArtFileList; p = fileList->parse(data+ptr, len-ptr); break; + } + + if(p < 0) return -1; + return totallen; +} + +int ArtDataSet::write(BYTE *data, int len) { + int ptr=0; + if(headerlen > len) return -1; + putmh("mhsd",data,ptr); + rev4i(headerlen,data,ptr); + rev4i(0,data,ptr); // fill total len here later + rev4i(index,data,ptr); + pad(data,headerlen,ptr); + int p=0; + switch(index) { + case 1: p=imageList->write(data+ptr, len-ptr); break; + case 2: p=albumList->write(data+ptr, len-ptr); break; + case 3: p=fileList->write(data+ptr, len-ptr); break; + } + if(p<0) return -1; + ptr+=p; + + rev4(ptr,&data[8]); // fill in total length + return ptr; +} + +/////////////////////////////////////////////////////////////////////////////// +// ArtImageList +ArtImageList::ArtImageList() : + headerlen(0x5c) +{ +} + +ArtImageList::~ArtImageList() { + for(ArtImageMapIterator f = images.begin(); f != images.end(); f++) + delete f->second; + images.clear(); +} + +int ArtImageList::parse(BYTE *data, int len) { + int ptr=4; + if(len < headerlen) return -1; + if (_strnicmp((char *)data,"mhli",4)) return -1; + headerlen = rev4i(data,ptr); + if(headerlen < 0x5c) return -1; + int children = rev4i(data,ptr); + + ptr=headerlen; + + for(int i=0; iparse(data+ptr,len-ptr); + if(p<0) {delete f; return -1;} + ptr+=p; + images.insert(ArtImageMapPair(f->songid,f)); + } + return ptr; +} + +int ArtImageList::write(BYTE *data, int len) { + int ptr=0; + if(headerlen > len) return -1; + putmh("mhli",data,ptr); + rev4i(headerlen,data,ptr); + rev4i(images.size(),data,ptr); + pad(data,headerlen,ptr); + + for(ArtImageMapIterator f = images.begin(); f != images.end(); f++) { + int p = f->second->write(data+ptr,len-ptr); + if(p<0) return -1; + ptr+=p; + } + return ptr; +} + +/////////////////////////////////////////////////////////////////////////////// +// ArtImage +ArtImage::ArtImage() : + headerlen(0x98), + totallen(0), + id(0), + songid(0), + unk4(0), + rating(0), + unk6(0), + originalDate(0), + digitizedDate(0), + srcImageSize(0) +{ +} + +ArtImage::~ArtImage() +{ + for (auto obj : dataobjs) + { + delete obj; + } + dataobjs.clear(); +} + +int ArtImage::parse(BYTE *data, int len) { + int ptr=4; + if(len < headerlen) return -1; + if (_strnicmp((char *)data,"mhii",4)) return -1; + headerlen = rev4i(data,ptr); + if(headerlen < 0x98) return -1; + totallen = rev4i(data,ptr); + int numchildren = rev4i(data,ptr); + id = rev4i(data,ptr); + songid = rev8(get8i(data,ptr)); + unk4 = rev4i(data,ptr); + rating = rev4i(data,ptr); + unk6 = rev4i(data,ptr); + originalDate = rev4i(data,ptr); + digitizedDate = rev4i(data,ptr); + srcImageSize = rev4i(data,ptr); + + ptr = headerlen; + for(int i=0; iparse(data+ptr,len-ptr); + if(p<0) { delete d; return -1; } + ptr+=p; + // fuck with d. ugh. + if((d->type == 2 || d->type == 5) && d->data) { // this is a container mhod + d->image = new ArtImageName; + int p2 = d->image->parse(d->data,d->datalen); + if(p2>0) { + SAFEFREE(d->data); + d->datalen=0; + } else SAFEDELETE(d->image); + } + dataobjs.push_back(d); + } + return totallen; +} + +template +BYTE *expandMemWrite(T * x, int &len, int maxsize=1024000) { + int s = 1024; + for(;;) { + BYTE *r = (BYTE*)malloc(s); + int p = x->write(r,s); + if(p>0) { + len=p; + return r; + } + free(r); + s = s+s; + if(s > maxsize) break; + } + return NULL; +} + +int ArtImage::write(BYTE *data, int len) { + int ptr=0; + if(headerlen > len) return -1; + putmh("mhii",data,ptr); + rev4i(headerlen,data,ptr); + rev4i(0,data,ptr); // fill in total length later + rev4i(dataobjs.size(),data,ptr); + rev4i(id,data,ptr); + put8i(rev8(songid),data,ptr); + rev4i(unk4,data,ptr); + rev4i(rating,data,ptr); + rev4i(unk6,data,ptr); + rev4i(originalDate,data,ptr); + rev4i(digitizedDate,data,ptr); + rev4i(srcImageSize,data,ptr); + pad(data,headerlen,ptr); + + for(auto f = dataobjs.begin(); f != dataobjs.end(); f++) { + if((*f)->image) { + int len=0; + BYTE *b = expandMemWrite((*f)->image,len); + if(!b) return -1; + (*f)->data = b; + (*f)->datalen = len; + } + int p = (*f)->write(data+ptr,len-ptr); + if((*f)->image) { + SAFEFREE((*f)->data); + (*f)->datalen=0; + } + if(p<0) return -1; + ptr+=p; + } + rev4(ptr,&data[8]); // fill in total length + return ptr; +} + +/////////////////////////////////////////////////////////////////////////////// +// ArtDataObj + +ArtDataObject::ArtDataObject() : + headerlen(0x18), + type(0), + data(0), + datalen(0), + image(0), + unk1(0) +{ +} + +ArtDataObject::~ArtDataObject() { + SAFEDELETE(image); + SAFEFREE(data); +} + +int ArtDataObject::parse(BYTE *data, int len) { + int ptr=4; + if(len < headerlen) return -1; + if (_strnicmp((char *)data,"mhod",4)) return -1; + headerlen = rev4i(data,ptr); + if(headerlen < 0x18) return -1; + int totallen = rev4i(data,ptr); + if(len < totallen) return -1; + type = rev2i(data,ptr); + unk1 = (unsigned char)rev1i(data,ptr); + short padding = rev1i(data,ptr); + ptr = headerlen; + if(type == 3 && rev2(&data[totallen-2]) == 0) + datalen = wcslen((wchar_t*)(data+ptr+12))*sizeof(wchar_t) + 12; + else + datalen = totallen - headerlen - padding; + if(datalen > 0x400 || datalen < 0) return -1; + this->data = (BYTE*)malloc(datalen); + memcpy(this->data,data+ptr,datalen); + + return totallen; +} + +int ArtDataObject::write(BYTE *data, int len) { + int ptr=0; + if(headerlen > len) return -1; + putmh("mhod",data,ptr); + rev4i(headerlen,data,ptr); + short padding = (4 - ((headerlen + datalen) % 4));// % 4; + if(padding == 4) padding = 0; + rev4i(headerlen+datalen+padding,data,ptr); + rev2i(type,data,ptr); + rev1i(unk1,data,ptr); + rev1i((unsigned char)padding,data,ptr); + pad(data,headerlen,ptr); + //write data + memcpy(data+ptr,this->data,datalen); + ptr+=datalen; + //add padding... + pad(data,ptr+padding,ptr); + return ptr; +} + +void ArtDataObject::GetString(wchar_t * str, int len) { + if(rev4(data+4) != 2) { str[0]=0; return; }//not utf-16! + int l = (rev4(data)/sizeof(wchar_t)); + StringCchCopyN(str, len, (wchar_t*)&data[12], l); + //lstrcpyn(str,(wchar_t*)&data[12],min(l,len)); +} + +void ArtDataObject::SetString(wchar_t * str) { + SAFEFREE(data); + datalen = wcslen(str)*sizeof(wchar_t) + 12; + data = (BYTE*)malloc(datalen); + rev4(wcslen(str)*sizeof(wchar_t),data); + rev4(2,data+4); //type 2 means utf-16 + rev4(0,data+8); //unk + memcpy(data+12,str,wcslen(str)*sizeof(wchar_t)); +} + +/////////////////////////////////////////////////////////////////////////////// +// ArtImageName +ArtImageName::ArtImageName() : + headerlen(0x4c), + totallen(0), + corrid(0), + ithmboffset(0), + imagesize(0), + vpad(0), + hpad(0), + imgh(0), + imgw(0), + filename(0) +{ +} + +ArtImageName::~ArtImageName() { + SAFEDELETE(filename); +} + +int ArtImageName::parse(BYTE *data, int len) { + int ptr=4; + if(len < headerlen) return -1; + if (_strnicmp((char *)data,"mhni",4)) return -1; + headerlen = rev4i(data,ptr); + if(headerlen < 0x4c) return -1; + totallen = rev4i(data,ptr); + int children = rev4i(data,ptr); + corrid = rev4i(data,ptr); + ithmboffset = rev4i(data,ptr); + imagesize = rev4i(data,ptr); + vpad = (short)rev2i(data,ptr); + hpad = (short)rev2i(data,ptr); + imgw = rev2i(data,ptr); + imgh = rev2i(data,ptr); + + ptr = headerlen; + + if(children) { + filename = new ArtDataObject(); + int p = filename->parse(data+ptr,len-ptr); + if(p<0) SAFEDELETE(filename); + } + return totallen; +} + +int ArtImageName::write(BYTE *data, int len) { + int ptr=0; + if(headerlen > len) return -1; + putmh("mhni",data,ptr); + rev4i(headerlen,data,ptr); + rev4i(0,data,ptr); // fill in totallen later + rev4i(filename?1:0,data,ptr); //num children + rev4i(corrid,data,ptr); + rev4i(ithmboffset,data,ptr); + rev4i(imagesize,data,ptr); + rev2i(vpad,data,ptr); + rev2i(hpad,data,ptr); + rev2i(imgw,data,ptr); + rev2i(imgh,data,ptr); + pad(data,headerlen,ptr); + if(filename) { + int p = filename->write(data+ptr,len-ptr); + if(p<0) return -1; + ptr+=p; + } + rev4(ptr,&data[8]); // fill in totallen + return ptr; +} + +/////////////////////////////////////////////////////////////////////////////// +// ArtAlbumList +ArtAlbumList::ArtAlbumList() : + headerlen(0x5c) +{ +} + +ArtAlbumList::~ArtAlbumList() {} + +int ArtAlbumList::parse(BYTE *data, int len) { + int ptr=4; + if(len < headerlen) return -1; + if (_strnicmp((char *)data,"mhla",4)) return -1; + headerlen = rev4i(data,ptr); + if(headerlen < 0x5c) return -1; + int children = rev4i(data,ptr); + + if(children != 0) return -1; + + return headerlen; +} + +int ArtAlbumList::write(BYTE *data, int len) { + int ptr=0; + if(headerlen > len) return -1; + putmh("mhla",data,ptr); + rev4i(headerlen,data,ptr); + rev4i(0,data,ptr); // num children + pad(data,headerlen,ptr); + + return ptr; +} + +/////////////////////////////////////////////////////////////////////////////// +// ArtFileList +ArtFileList::ArtFileList() : + headerlen(0x5c) +{ +} + +ArtFileList::~ArtFileList() +{ + for (auto file : files) + { + delete file; + } + files.clear(); +} + +int ArtFileList::parse(BYTE *data, int len) { + int ptr=4; + if(len < headerlen) return -1; + if (_strnicmp((char *)data,"mhlf",4)) return -1; + headerlen = rev4i(data,ptr); + if(headerlen < 0x5c) return -1; + int children = rev4i(data,ptr); + + ptr = headerlen; + + for(int i=0; iparse(data+ptr,len-ptr); + if(p<0) { delete f; return -1; } + ptr+=p; + files.push_back(f); + } + return ptr; +} + +int ArtFileList::write(BYTE *data, int len) { + int ptr=0; + if(headerlen > len) return -1; + putmh("mhlf",data,ptr); + rev4i(headerlen,data,ptr); + rev4i(files.size(),data,ptr); // num children + pad(data,headerlen,ptr); + + for(auto f = files.begin(); f != files.end(); f++) { + int p = (*f)->write(data+ptr,len-ptr); + if(p<0) return -1; + ptr+=p; + } + + return ptr; +} + +ArtFile * ArtFileList::getFile(int corrid) { + for(auto i = files.begin(); i!=files.end(); i++) { + if((*i)->corrid == corrid) return *i; + } + return NULL; +} + +/////////////////////////////////////////////////////////////////////////////// +// ArtFile +ArtFile::ArtFile() : + headerlen(0x7c), + corrid(0), + imagesize(0), + file(0) +{ +} + +ArtFile::~ArtFile() { + SAFEFREE(file); +} + +int ArtFile::parse(BYTE *data, int len) { + int ptr=4; + if(len < headerlen) return -1; + if (_strnicmp((char *)data,"mhif",4)) return -1; + headerlen = rev4i(data,ptr); + if(headerlen < 0x7c) return -1; + int totallen = rev4i(data,ptr); + rev4i(data,ptr); // might not be numchildren, it's really unk1 + corrid = rev4i(data,ptr); + imagesize = rev4i(data,ptr); + + return totallen; +} + +int ArtFile::write(BYTE *data, int len) { + int ptr=0; + if(headerlen > len) return -1; + putmh("mhif",data,ptr); + rev4i(headerlen,data,ptr); + rev4i(0,data,ptr); // total len, fill in later + rev4i(0,data,ptr); // numchildren/unk1 + rev4i(corrid,data,ptr); + rev4i(imagesize,data,ptr); + pad(data,headerlen,ptr); + // write children, if we had any... + rev4(ptr,&data[8]); // fill in total len + return ptr; +} + +struct ArtFileImageSort { + bool operator()(ArtFileImage*& ap,ArtFileImage*& bp) { + return ap->start < bp->start; + } +}; + +void ArtFile::sortImages() { + std::sort(images.begin(),images.end(),ArtFileImageSort()); + for(size_t i = 1; i != images.size(); i++) + { + if(images[i]->start == images[i-1]->start) + { + images.erase(images.begin() + i); + i--; + images[i]->refcount++; + } + } +} + +size_t ArtFile::getNextHole(size_t size) { + size_t s=0; + for(auto i = images.begin(); i!=images.end(); i++) { + if((*i)->start - s >= size) return s; + s = (*i)->start + (*i)->len; + } + return s; +} + +bool writeDataToThumb(wchar_t *file, unsigned short * data, int len) { + FILE * f = _wfopen(file,L"ab"); + if(!f) return false; + fwrite(data,len,sizeof(short),f); + fclose(f); + return true; +} diff --git a/Src/Plugins/Portable/pmp_ipod/iPodArtworkDB.h b/Src/Plugins/Portable/pmp_ipod/iPodArtworkDB.h new file mode 100644 index 00000000..8e74e3e0 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/iPodArtworkDB.h @@ -0,0 +1,232 @@ +/* + * + * + * Copyright (c) 2007 Will Fisher (will.fisher@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. + * + * + * + */ + +#ifndef __IPODARTDB_H__ +#define __IPODARTDB_H__ + +#pragma once + +//#include +#include +#include +#include +#include + +class ArtDB; +class ArtDataSet; +class ArtImageList; +class ArtImage; +class ArtImageName; +class ArtDataObject; +class ArtAlbumList; +class ArtFileList; +class ArtFile; +class ArtFileImage; + +// this contains our whole art database +class ArtDB { //mhfd +public: + int headerlen; //0x84 + int totallen; + int unk1; + int unk2; // must be 2 + // numchildren // should be 3 + int unk3; + uint32_t nextid; // for ArtImage ids, starts at 0x40 + __int64 unk5; + __int64 unk6; + int unk7; // 2 + int unk8; // 0 + int unk9; // 0 + int unk10; + int unk11; + ArtDataSet * imageListDS; + ArtDataSet * albumListDS; + ArtDataSet * fileListDS; + + ArtDB(); + ~ArtDB(); + int parse(BYTE * data, int len, wchar_t drive); + int write(BYTE * data, int len); + + void makeEmptyDB(wchar_t drive); +}; + +class ArtDataSet { //mhsd +public: + int headerlen; //0x60 + int totallen; + int index; // 1=image list, 2=album list, 3=file list + ArtImageList * imageList; + ArtAlbumList * albumList; + ArtFileList * fileList; + + ArtDataSet(); + ArtDataSet(int idx); + ~ArtDataSet(); + int parse(BYTE *data, int len); + int write(BYTE *data, int len); +}; + +// contains a list of images +class ArtImageList { //mhli +public: + typedef std::map ArtImageMap; + typedef ArtImageMap::iterator ArtImageMapIterator; + typedef ArtImageMap::value_type ArtImageMapPair; + + int headerlen; //0x5c + //int numchildren; + ArtImageMap images; + + ArtImageList(); + ~ArtImageList(); + int parse(BYTE *data, int len); + int write(BYTE *data, int len); +}; + +// contains a reference to an image within an .ithmb file +class ArtImage { //mhii +public: + int headerlen; //0x98 + int totallen; + //int numchildren; + uint32_t id; + uint64_t songid; + int32_t unk4; + int32_t rating; + int32_t unk6; + uint32_t originalDate; //0 + uint32_t digitizedDate; //0 + uint32_t srcImageSize; // in bytes + std::vector dataobjs; + + ArtImage(); + ~ArtImage(); + int parse(BYTE *data, int len); + int write(BYTE *data, int len); +}; + +class ArtDataObject { //mhod +public: + int headerlen; //0x18 + // total length + short type; + unsigned char unk1; + // unsigned char padding; // must pad to a multiple of 4 bytes! this is usually 2, but can be 0,1,2 or 3 + BYTE * data; + int datalen; + ArtImageName * image; + + ArtDataObject(); + ~ArtDataObject(); + int parse(BYTE *data, int len); + int write(BYTE *data, int len); + + void GetString(wchar_t * str, int len); + void SetString(wchar_t * str); +}; + +class ArtImageName { //mhni +public: + int headerlen; //0x4c + int totallen; + //num children = 1 + unsigned int corrid; + unsigned int ithmboffset; + unsigned int imagesize; // in bytes + short vpad; + short hpad; + unsigned short imgh; + unsigned short imgw; + ArtDataObject* filename; + + ArtImageName(); + ~ArtImageName(); + int parse(BYTE *data, int len); + int write(BYTE *data, int len); +}; + +// this is only used in photo databases (which we don't care about) so it's only a stub +class ArtAlbumList { //mhla +public: + int headerlen; //0x5c + //num children, should be 0 for artwork db + ArtAlbumList(); + ~ArtAlbumList(); + int parse(BYTE *data, int len); + int write(BYTE *data, int len); +}; + +// this contains the list of .ithmb files +class ArtFileList { //mhlf +public: + int headerlen; //0x5c + // num children + std::vector files; + + ArtFileList(); + ~ArtFileList(); + int parse(BYTE *data, int len); + int write(BYTE *data, int len); + ArtFile * getFile(int corrid); +}; + +// this talks about a .ithmb file +class ArtFile { //mhif +public: + int headerlen; //0x7c + unsigned int corrid; + unsigned int imagesize; // bytes + + ArtFile(); + ~ArtFile(); + int parse(BYTE *data, int len); + int write(BYTE *data, int len); + + std::vector images; + wchar_t * file; + void sortImages(); + size_t getNextHole(size_t size); +}; + +class ArtFileImage { +public: + size_t start; + size_t len; + int refcount; + ArtFileImage(size_t start, size_t len, int refcount) : start(start), len(len), refcount(refcount) {} +}; + +bool writeDataToThumb(wchar_t *file, unsigned short * data, int len); + +#endif //__IPODARTDB_H__ \ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_ipod/iPodDB.cpp b/Src/Plugins/Portable/pmp_ipod/iPodDB.cpp new file mode 100644 index 00000000..1fe94190 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/iPodDB.cpp @@ -0,0 +1,4807 @@ +/* +* +* +* 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