diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/Plugins/Portable/pmp_ipod | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/Plugins/Portable/pmp_ipod')
49 files changed, 13608 insertions, 0 deletions
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 <windows.h> +#include <stddef.h> // for offsetof +#include <winioctl.h> +#include <strsafe.h> + + 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<numPages;i++) + { + if (!AddPagetoXML(dev, xml, xmllen, pages[i+4])) + return false; + } + } + } + return true; +} + +bool ParseSysInfoXML(wchar_t drive_letter, char * xml, int xmllen) +{ + wchar_t fn[MAX_PATH] = {0}; + StringCchPrintf(fn, MAX_PATH, L"\\\\.\\%c:", drive_letter); + HANDLE hfile = CreateFileW(fn, GENERIC_WRITE|GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0); + if (hfile != INVALID_HANDLE_VALUE) + { + bool ret = BuildXML(hfile, xml, xmllen); + CloseHandle(hfile); + return ret; + } + return false; +} diff --git a/Src/Plugins/Portable/pmp_ipod/api.h b/Src/Plugins/Portable/pmp_ipod/api.h new file mode 100644 index 00000000..3443671f --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/api.h @@ -0,0 +1,30 @@ +#ifndef NULLSOFT_PMP_IPOD_API_H +#define NULLSOFT_PMP_IPOD_API_H + +#include <api/memmgr/api_memmgr.h> +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 <api/application/api_application.h> +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<ifc_deviceprovider*>(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 <wtypes.h> +#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 <windows.h> +#include <stdio.h> +#include <winioctl.h> + +#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 <stdio.h> + +#include <windows.h> + +#include <Setupapi.h> +#include <winioctl.h> +#include <winioctl.h> +#include <cfgmgr32.h> +#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 <windows.h> +#include <stdio.h> +#include <wchar.h> +#include "resource.h" +#include <strsafe.h> + +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 <memory.h> +#include <algorithm> + +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 <algorithm> +#include <strsafe.h> + +//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; i<numchildren; i++) { + ArtDataSet * d = new ArtDataSet; + int p = d->parse(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; i<children; i++) { + ArtImage * f = new ArtImage; + int p = f->parse(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; i<numchildren; i++) { + ArtDataObject *d = new ArtDataObject; + int p = d->parse(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<class T> +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; i<children; i++) { + ArtFile * f = new ArtFile; + int p = f->parse(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 <stdio.h> +#include <windows.h> +#include <vector> +#include <map> +#include <bfc/platform/types.h> + +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<uint64_t ,ArtImage*> 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<ArtDataObject*> 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<ArtFile*> 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<ArtFileImage*> 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 <bfc/platform/types.h> +#include <assert.h> +#include <time.h> +#include <windows.h> +#include <tchar.h> +#include <math.h> +#include <vector> + +/* +#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 <stdlib.h> +#include <crtdbg.h> +#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; i<children; i++) + { + iPod_mhsd * mhsd = new iPod_mhsd(0); + long ret = mhsd->parse(&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;i<headsize;i++) + data[i]=0; + ptr=headsize; + + rev2(1, &data[0x30]); + + rev2(language, &data[0x46]); + put8(library_id, &data[0x48]); + rev4(unk80, &data[0x50]); + rev4(unk84, &data[0x54]); + rev4(timezone, &data[0x6c]); + rev2(2, &data[0x70]); + rev2(audio_language, &data[0xA0]); + rev2(subtitle_language, &data[0xA2]); + rev2(unk164, &data[0xA4]); + rev2(unk166, &data[0xA6]); + rev2(unk168, &data[0xA8]); + + long ret; + // write the mhla (album list) + iPod_mhsd mhsd_mhla(4); + iPod_mhla *album_list = mhsd_mhla.mhla; + + iPod_mhlt::mhit_map_t::const_iterator begin = mhsdsongs->mhlt->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<iPod_mhit*>((*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;i<headsize;i++) + data[i]=0; + ptr=headsize; + + // write out the songs or the playlists, depending on index + long ret; + if (index==1) // mhlt + ret=mhlt->write(&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;i<children;i++) + { + iPod_mhit *m = new iPod_mhit; + ret=m->parse(&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<headsize;i++) + data[i]=0; + ptr=headsize; + + long ret; + + // write children one by one + 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++) + { + iPod_mhit *m = static_cast<iPod_mhit*>(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<iPod_mhit*>(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<iPod_mhit*>(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<iPod_mhit*>(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<iPod_mhit*>(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<count;i++) + delete mhod[i]; +} + +long iPod_mhit::parse(const uint8_t *data) +{ + size_t ptr=0; + unsigned long temp=0; + + //check mhit header + if (_strnicmp((char *)&data[ptr],"mhit",4)) return -1; + ptr+=4; + + // get sizes + size_head=read_uint32_t(data, ptr); + size_total=read_uint32_t(data, ptr); + + ASSERT(size_head == 0x9c || size_head == 0xf4 || size_head == 0x148 || size_head == 0x184 || size_head == 0x248); // if this is false, this is a new mhit format + + // get rest of data + unsigned long mhodnum=read_uint32_t(data, ptr); // Only used locally + id=read_uint32_t(data, ptr); + visible=read_uint32_t(data, ptr); + filetype=read_uint32_t(data, ptr); + + vbr = data[ptr++]; + type = data[ptr++]; + compilation = data[ptr++]; + stars = data[ptr++]; + + lastmodifiedtime=read_uint32_t(data, ptr); + size=read_uint32_t(data, ptr); + length=read_uint32_t(data, ptr); + tracknum=read_uint32_t(data, ptr); + totaltracks=read_uint32_t(data, ptr); + year=read_uint32_t(data, ptr); + bitrate=read_uint32_t(data, ptr); + + temp=rev4(&data[ptr]); + ptr+=4; + samplerate = (uint16_t)(temp >> 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;i<mhodnum;i++) + { + iPod_mhod *m = new iPod_mhod; + ret=m->parse(&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;i<GetChildrenCount();i++) + { + iPod_mhod *m = mhod[i]; + ASSERT(m != NULL); + if(m == NULL) + continue; + + if(m->type < 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;i<mhodnum;i++) + { + ret=mhod[i]->write(&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;i<children;i++) + { + if (mhod[i]->type == 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 = '<space>' + 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; i<mhodSize; i++) + { + iPod_mhod *src_mhod = src->mhod[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<size;i++) + delete rule[i]; +} + +long iPod_mhod::parse(const uint8_t *data) +{ + size_t ptr=0; + unsigned long i = 0; + + //check mhod header + if (_strnicmp((char *)&data[ptr],"mhod",4)) return -1; + ptr+=4; + + // get sizes + size_head=read_uint32_t(data, ptr); + size_total=read_uint32_t(data, ptr); + + ASSERT(size_head == 0x18); + + // get type + type=read_uint32_t(data, ptr); + + // dunno what these are, but they are definitely common among all the types of mhods + // smartlist types prove that. + // they're usually zero though.. null padding for the MHOD header? + unk1=read_uint32_t(data, ptr); + unk2=read_uint32_t(data, ptr); + + if (iPod_mhod::IsSimpleStringType(type)) + { + // string types get parsed + // 1 == UTF-16, 2 == UTF-8 + // TODO: handle UTF-8 + encoding_type=read_uint32_t(data, ptr); + length=read_uint32_t(data, ptr); + unk3=read_uint32_t(data, ptr); + unk4=read_uint32_t(data, ptr); + + str=new wchar_t[length + 1]; + memcpy(str,&data[ptr],length); + str[length / 2] = '\0'; + ptr+=length; + } + else if (type == MHOD_ENCLOSUREURL || type == MHOD_RSSFEEDURL) + { + // Apple makes life hard again! These are almost like regular/simple MHOD string types, + // except the string is UTF-8 encoded and there is no length or position fields + length = size_total - size_head; + ASSERT(length > 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;i<numrules;i++) + { + SPLRule * r = new SPLRule; + ASSERT(r); + if(r == NULL) + continue; + + r->field=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; i<len; i+=2) + { + *(c + i) = data[ptr + i + 1]; + *(c + i + 1) = data[ptr + i]; + } + + ptr += r->length; + } + 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<len;i++) + { + data[ptr]=str[i] & 0xff; + ptr++; + data[ptr]=(str[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;i<ruleCount;i++) + { + SPLRule *r = rule[i]; + ASSERT(r); + if(r == NULL) + continue; + + put4(r->field,&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; i<r->length; 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;i<length;i++) + { + data[ptr]=binary[i]; + ptr++; + } + } + + // fix the total size + rev4(ptr,&data[8]); + + return ptr; +} + +void iPod_mhod::SetString(const wchar_t *string) +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iPod_mhod_SetString); +#endif + + if(!string) + return; + + delete [] str; + length = 0; + + unsigned int stringLen = min(wcslen(string), 512); + str = new wchar_t[stringLen + 1]; + memset(str, 0, sizeof(wchar_t) * (stringLen + 1)); + + if(str) + { + wcsncpy(str, string, stringLen); + length = stringLen * 2; + } +} + +bool iPod_mhod::IsSimpleStringType(const unsigned int type) +{ + switch(type) + { + case MHOD_TITLE: + case MHOD_LOCATION: + case MHOD_ALBUM: + case MHOD_ARTIST: + case MHOD_GENRE: + case MHOD_FILETYPE: + case MHOD_EQSETTING: + case MHOD_COMMENT: + case MHOD_CATEGORY: + case MHOD_COMPOSER: + case MHOD_GROUPING: + case MHOD_DESCRIPTION: + case MHOD_SUBTITLE: + case MHOD_ALBUMARTIST: + case MHOD_ARTIST_SORT: + case MHOD_TITLE_SORT: + case MHOD_ALBUM_SORT: + case MHOD_ALBUMARTIST_SORT: + case MHOD_COMPOSER_SORT: + case MHOD_SHOW_SORT: + case MHOD_ALBUMLIST_ALBUM: + case MHOD_ALBUMLIST_ARTIST: + return(true); + } + + return(false); +} + + +void iPod_mhod::Duplicate(iPod_mhod *src, iPod_mhod *dst) +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iPod_mhod_Duplicate); +#endif + + if(src == NULL || dst == NULL) + return; + + dst->type = 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; i<ruleLen; i++) + { + SPLRule *srcRule = src->rule[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;i<children; i++) + { + iPod_mhyp *m = new iPod_mhyp; + ret=m->parse(&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;i<headsize;i++) + data[i]=0; + ptr=headsize; + + long ret; + for (i=0;i<children; i++) + { + ret=mhyp[i]->write(&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; i<count; i++) + { + iPod_mhyp * m = GetPlaylist(i); + if (m->playlistID == 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; i<count; i++) + { + iPod_mhyp *m = GetPlaylist(i); + ASSERT(m); + if(m == NULL) + continue; + + if(m->playlistID == 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<count;i++) + { + iPod_mhyp *m=GetPlaylist(i); + delete m; + } + + if(!beingDeleted) + mhyp.clear(); + + // create the default playlist again, if it's gone + // XXX - Normally this is the right thing to do, but if we + // are about to parse the playlists from the iPod, we can't create + // the default playlist. Doing so will create two default/hidden + // playlists - this one and the one that will be created when + // the playlists are parsed! + if(createDefaultPlaylist) + GetDefaultPlaylist(); + + return true; +} + +void iPod_mhlp::RemoveDeadPlaylistEntries(iPod_mhlt *mhlt) +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iPod_mhlp_RemoveDeadPlaylistEntries); +#endif + + if(mhlt == NULL) + return; + + const unsigned int playlistCount = GetChildrenCount(); + for(unsigned int i=0; i<playlistCount; i++) + { + iPod_mhyp *m = mhyp[i]; + ASSERT(m); + if(m == NULL) + continue; + + std::vector<unsigned int> deleteList; + + const unsigned int songCount = m->GetMhipChildrenCount(); + for(unsigned int j=0; j<songCount; j++) + { + iPod_mhip *p = m->mhip.at(j); + ASSERT(p); + if(p == NULL) + continue; + + if(mhlt->GetTrackByID(p->songindex) != NULL) + continue; + + // Found a dead song + deleteList.push_back(p->songindex); + } + + const unsigned int deleteCount = deleteList.size(); + for(unsigned int k=0; k<deleteCount; k++) + { + const bool retval = m->DeletePlaylistEntryByID(deleteList[k]); + ASSERT(retval == true); + } + } +} + +void iPod_mhlp::SortPlaylists() { + std::sort(mhyp.begin(),mhyp.end(),iPod_mhyp()); +} + +////////////////////////////////////////////////////////////////////// +// iPod_mhyp - A Playlist +////////////////////////////////////////////////////////////////////// + +iPod_mhyp::iPod_mhyp() : +hidden(0), +timestamp(0), +unk3(0), +numStringMHODs(0), +podcastflag(0), +numLibraryMHODs(0), +mhod(), +mhip(), +mhit(NULL), +isSmartPlaylist(false), +isPopulated(true), // consider normal playlists to be populated always +writeLibraryMHODs(true) +{ + timestamp = wintime_to_mactime(time(NULL)); + + // Create a highly randomized 64 bit value for the playlistID + playlistID = Generate64BitID(); +} + +iPod_mhyp::~iPod_mhyp() +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iPod_mhyp_destructor); +#endif + + const unsigned long mhodCount = GetMhodChildrenCount(); + const unsigned long mhipCount = GetMhipChildrenCount(); + unsigned long i; + for (i=0;i<mhodCount;i++) + delete mhod[i]; + for (i=0;i<mhipCount;i++) + delete mhip[i]; +} + +long iPod_mhyp::parse(const uint8_t *data) +{ + size_t ptr=0; + + //check mhyp header + if (_strnicmp((char *)&data[ptr],"mhyp",4)) return -1; + ptr+=4; + + // get sizes + size_head=read_uint32_t(data, ptr); + size_total=read_uint32_t(data, ptr); + + ASSERT(size_head == 0x6c || size_head == 0x8c); + + const unsigned long mhodnum=read_uint32_t(data, ptr); // only used locally + const unsigned long numsongs=read_uint32_t(data, ptr); // only used locally + hidden=read_uint32_t(data, ptr); + timestamp=read_uint32_t(data, ptr); + playlistID = rev8(get8(&data[ptr])); + if(playlistID == 0) + { + // Force the playlistID 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... + playlistID = Generate64BitID(); + } + ptr+=8; + unk3=read_uint32_t(data, ptr); + unsigned long temp = rev4(&data[ptr]); + numStringMHODs = temp && 0xFFFF; + podcastflag = (uint16_t)(temp >> 16); + ptr+=4; + numLibraryMHODs=read_uint32_t(data, ptr); + + ptr=size_head; + + long ret; + unsigned long i; + + mhod.reserve(mhodnum); // pre allocate the space, for speed + mhip.reserve(numsongs); + + for (i=0;i<mhodnum;i++) + { + iPod_mhod *m = new iPod_mhod; + + // parseSmartPlaylists is an optimization for when dealing with smart playlists. + // Since the playlist has to be parsed in order to determine if it is a smart playlist, + // and if it is, parsed again as a smart playlist, this optimization prevents some duplicate parsing. + m->parseSmartPlaylists = isSmartPlaylist; + ret=m->parse(&data[ptr]); + ASSERT(ret >= 0); + if (ret<0) + { + delete m; + continue; + return ret; + } + + ptr+=ret; + + // Don't add Type 52 MHODs - if the file changes, the indexes are no longer valid + if(m->type == MHOD_LIBRARY || m->type == MHOD_LIBRARY_LETTER) + { + delete m; + continue; + } + + // Reset the Type 52 MHOD count to zero + numLibraryMHODs = 0; + + if(m->type == MHOD_PLAYLIST) + delete m; // fuck 'em! + else + mhod.push_back(m); + + } + + for (i=0;i<numsongs;i++) + { + iPod_mhip *m = new iPod_mhip; + ret=m->parse(&data[ptr]); + ASSERT(ret >= 0); + if (ret<0) + { + delete m; + return ret; + } + else + { + ptr+=ret; + } + mhip.push_back(m); + } + + return ptr; +} + +long iPod_mhyp::write(unsigned char * data, const unsigned long datasize, int index) +{ + + const unsigned int headsize=0x6c; + // check for header size + ASSERT(headsize <= datasize); + if (headsize>datasize) return -1; + + size_t ptr=0; + + // List of Type 52 MHODs to write + unsigned int indexType[] = { TYPE52_SONG_NAME, TYPE52_ARTIST, TYPE52_ALBUM, TYPE52_GENRE, TYPE52_COMPOSER }; + const unsigned int indexTypeCount = sizeof(indexType) / sizeof(unsigned int); + + //write mhyp header + data[0]='m';data[1]='h';data[2]='y';data[3]='p'; + 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) + + writeLibraryMHODs = true; + bool writeType52MHOD = writeLibraryMHODs && (hidden > 0 && mhit != NULL); + + // fill in stuff + const unsigned long mhodnum = GetMhodChildrenCount(); + + numStringMHODs = (uint16_t)mhodnum; + + if(writeType52MHOD) + { + write_uint32_t(data, ptr, mhodnum + indexTypeCount); // Include the extra MHOD Type 52s that aren't in the MHOD list + numLibraryMHODs = indexTypeCount; + } + else + { + write_uint32_t(data, ptr, mhodnum); + } + + const unsigned long numsongs = GetMhipChildrenCount(); + write_uint32_t(data, ptr, numsongs); + write_uint32_t(data, ptr, hidden); + write_uint32_t(data, ptr, timestamp); + + put8(rev8(playlistID),&data[ptr]); + ptr+=8; + write_uint32_t(data, ptr, unk3); + unsigned long temp = numStringMHODs | podcastflag << 16; + rev4(temp,&data[ptr]); + ptr+=4; + write_uint32_t(data, ptr, numLibraryMHODs); + + unsigned long i; + // fill up the rest of the header with nulls + for (i=ptr;i<headsize;i++) + data[i]=0; + ptr=headsize; + + long ret; + for (i=0;i<mhodnum; i++) + { + ret=mhod[i]->write(&data[ptr],datasize-ptr); + ASSERT(ret >= 0); + if (ret<0) return ret; + else ptr+=ret; + } + + // If this is the default hidden playlist, create the type 52 MHODs that are used to accelerate the browse menus + if(writeType52MHOD) + { + + /* + header identifier | 4 | mhod + header length | 4 | size of the mhod header + total length | 4 | size of the header and all the index entries + type | 4 | the type indicator ( 52 ) + unk1 | 4 | unknown (always zero) + unk2 | 4 | unknown (always zero) + index type | 4 | what this index is sorted on (see list above) + count | 4 | number of entries. Always the same as the number of entries in the playlist, which is the same as the number of songs on the iPod. + null padding | 40| lots of padding + ----- + 72 bytes (0x48) + index entries | 4 * count | The index entries themselves. This is an index into the mhit list, in order, starting from 0 for the first mhit. + */ + for(unsigned int i=0; i<indexTypeCount; i++) + { + unsigned int mhodType = 0; + const unsigned int mhod52Type = indexType[i]; + + // Map the index type to the MHOD type + switch(mhod52Type) + { + case TYPE52_SONG_NAME: + mhodType = MHOD_TITLE; + break; + case TYPE52_ALBUM: + mhodType = MHOD_ALBUM; + break; + case TYPE52_ARTIST: + mhodType = MHOD_ARTIST; + break; + case TYPE52_GENRE: + mhodType = MHOD_GENRE; + break; + case TYPE52_COMPOSER: + mhodType = MHOD_COMPOSER; + break; + default: + ASSERT(0); // unknown type + mhodType = 0; + } + + if(mhodType == 0) + continue; + + ASSERT(mhit->size() == numsongs); + if(mhit->size() != numsongs) + continue; + + + // Start writing the MHOD... + const unsigned long headsize = 0x18; + const unsigned long totalsize = 0x48 + (4 * numsongs); + + //write mhod header + data[ptr++]='m';data[ptr++]='h';data[ptr++]='o';data[ptr++]='d'; + + // write sizes + + + write_uint32_t(data, ptr, headsize); // header size + write_uint32_t(data, ptr, totalsize); // total size = 72 * (4 * number of songs) + + // This is type 52 MHOD + write_uint32_t(data, ptr, MHOD_LIBRARY); + + // Unknowns (always zero) + unsigned long unk1 = 0, unk2 = 0; + write_uint32_t(data, ptr, unk1); + write_uint32_t(data, ptr, unk2); + + // What kind of index is this? + write_uint32_t(data, ptr, mhod52Type); + + // Song Count + write_uint32_t(data, ptr, numsongs); + + // 40 NULLs + for(unsigned int j=0;j<40;j++) + data[ptr++]=0; + + // Build the sort by string vector list + std::vector<indexMhit*> strList; + strList.reserve(numsongs); + + unsigned int ki = 0; + /* + iPod_mhlt::mhit_map_t::const_iterator begin = mhit->begin(); + iPod_mhlt::mhit_map_t::const_iterator end = mhit->end(); + for(iPod_mhlt::mhit_map_t::const_iterator it = begin; it != end; it++) + { + iPod_mhit *m = static_cast<iPod_mhit*>((*it).second); + */ + int kl = mhip.size(); + for(int k=0; k < kl; k++) { + + iPod_mhit *m = mhit->find(mhip.at(k)->songindex)->second; + + ASSERT(m != NULL); + if(m == NULL) + continue; + + indexMhit *foo = new indexMhit; + foo->index = ki++; + + + iPod_mhod *d; + if(mhod52Type == TYPE52_COMPOSER) { + foo->track = 0; + foo->str[0] = L""; + foo->str[1] = L""; + d = m->FindString(MHOD_COMPOSER); + foo->str[2] = d?d->str:L""; + } else { + foo->track = (int)m->tracknum; + d = (mhod52Type >= TYPE52_GENRE)?m->FindString(MHOD_GENRE):NULL; + foo->str[0] = d?d->str:L""; + d = (mhod52Type >= TYPE52_ARTIST)?m->FindString(MHOD_ARTIST):NULL; + foo->str[1] = d?d->str:L""; + d = (mhod52Type >= TYPE52_ALBUM)?m->FindString(MHOD_ALBUM):NULL; + foo->str[2] = d?d->str:L""; + } + d = m->FindString(MHOD_TITLE); + foo->str[3] = d?d->str:L""; + + strList.push_back(foo); + } + + // Sort the list alphabetically + std::sort(strList.begin(), strList.end(), indexMhit()); + + // Write out the index entries + const unsigned indexCount = strList.size(); + ASSERT(indexCount == numsongs); + for(unsigned int mi =0; mi<indexCount; mi++) + { + const unsigned int index = strList[mi]->index; + write_uint32_t(data, ptr, index); + } + + // Free the list of indexMhits + for(unsigned int li=0;li<indexCount;li++) + { + delete strList[li]; + } + } + } + + int SongNum = 0; + for (i=0;i<numsongs;i++) + { + //FUCKO: use index here to write out podcast list differently + if(index == 3 || mhip[i]->podcastgroupflag == 0) + { + if(mhip[i]->podcastgroupflag == 0) + SongNum++; + unsigned long temp=0; + if(index == 2) + { + temp = mhip[i]->podcastgroupref; + mhip[i]->podcastgroupref = 0; + } + ret=mhip[i]->write(&data[ptr],datasize-ptr, SongNum); + if(index == 2) + mhip[i]->podcastgroupref=temp; + ASSERT(ret >= 0); + if (ret<0) + return ret; + else + ptr+=ret; + } + } + + // fix the total size + rev4(ptr,&data[8]); + // fix number of songs written + if(index == 2) rev4(SongNum,&data[16]); + + return ptr; +} + +long iPod_mhyp::AddPlaylistEntry(iPod_mhip * entry, const unsigned long id) +{ + entry = new iPod_mhip; + ASSERT(entry != NULL); + if (entry !=NULL) + { + entry->songindex=id; + entry->timestamp = wintime_to_mactime(time(NULL)); + + mhip.push_back(entry); + return GetMhipChildrenCount()-1; + } + else return -1; +} + +long iPod_mhyp::FindPlaylistEntry(const unsigned long id) const +{ + const unsigned long children = GetMhipChildrenCount(); + for (unsigned long i=0;i<children;i++) + { + if (mhip.at(i)->songindex==id) + return i; + } + return -1; +} + +bool iPod_mhyp::DeletePlaylistEntry(unsigned long pos) +{ + if (GetMhipChildrenCount() >= pos) + { + iPod_mhip * m = GetPlaylistEntry(pos); + if (pos < mhip.size()) + { + mhip.erase(mhip.begin() + pos); + } + delete m; + return true; + } + return false; +} + +bool iPod_mhyp::DeletePlaylistEntryByID(unsigned long songindex) +{ + // Search the list of mhips until the matching mhip(s) are found + bool retval = false; + unsigned int count = GetMhipChildrenCount(); + for(unsigned int i=0; i<count; i++) + { + iPod_mhip *m = GetPlaylistEntry(i); + if(m->songindex == songindex) + { + DeletePlaylistEntry(i); + count = GetMhipChildrenCount(); + i=0; + retval = true; + continue; + } + } + + return(retval); +} + + +bool iPod_mhyp::ClearPlaylist() +{ + const unsigned long count = GetMhipChildrenCount(); + for (unsigned long i=0;i<count;i++) + { + iPod_mhip * m = GetPlaylistEntry(i); + delete m; + } + mhip.clear(); + return true; +} + +long iPod_mhyp::PopulatePlaylist(iPod_mhlt * tracks, int hidden_field) +{ + ASSERT(tracks != NULL); + if(tracks == NULL) + return(-1); + + const unsigned long trackCount = tracks->GetChildrenCount(); + if(trackCount == 0) + return(0); + + ClearPlaylist(); + + // Speed up getting the id as follows: + // Iterate the whole tracks->mhit map, storing the ids in a local vector + std::vector<unsigned long> ids; + ids.reserve(trackCount); + + iPod_mhlt::mhit_map_t::const_iterator begin = tracks->mhit.begin(); + iPod_mhlt::mhit_map_t::const_iterator end = tracks->mhit.end(); + for(iPod_mhlt::mhit_map_t::const_iterator it = begin; it != end; it++) + ids.push_back(static_cast<unsigned long>((*it).first)); + + for (unsigned long i=0;i<trackCount;i++) + { + // Add the playlist entry locally rather than using + // AddPlaylistEntry() for speed optimization reasons + iPod_mhip *entry = new iPod_mhip; + ASSERT(entry != NULL); + if(entry) + { + entry->songindex = ids[i]; + mhip.push_back(entry); + } + } + + hidden=hidden_field; + return GetMhipChildrenCount(); +} + +iPod_mhod * iPod_mhyp::AddString(const int type) +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iPod_mhyp_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); + return m; +} + + +iPod_mhod * iPod_mhyp::FindString(const unsigned long type) +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iPod_mhyp_FindString); +#endif + + const unsigned long children = GetMhodChildrenCount(); + for (unsigned long i=0;i<children;i++) + { + if (mhod[i]->type == type) return mhod[i]; + } + + return NULL; +} + +unsigned long iPod_mhyp::DeleteString(const unsigned long type) +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iPod_mhyp_DeleteString); +#endif + + unsigned long count=0; + + for (unsigned long i=0; i != GetMhodChildrenCount(); 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; +} + +void iPod_mhyp::SetPlaylistTitle(const wchar_t *string) +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iPod_mhyp_SetPlaylistTitle); +#endif + + if(string == NULL) + return; + + iPod_mhod *mhod = AddString(MHOD_TITLE); + ASSERT(mhod); + if(mhod) + mhod->SetString(string); +} + +void iPod_mhyp::Duplicate(iPod_mhyp *src, iPod_mhyp *dst) +{ + if(src == NULL || dst == NULL) + return; + + dst->hidden = src->hidden; + dst->timestamp = src->timestamp; + dst->playlistID = src->playlistID; + dst->unk3 = src->unk3; + dst->numStringMHODs = src->numStringMHODs; + dst->podcastflag = src->podcastflag; + dst->numLibraryMHODs = src->numLibraryMHODs; + dst->isSmartPlaylist = src->isSmartPlaylist; + dst->isPopulated = src->isPopulated; + + const unsigned int mhodCount = src->mhod.size(); + const unsigned int mhipCount = src->mhip.size(); + + unsigned int i; + for(i=0; i<mhodCount; i++) + { + iPod_mhod *mhod = dst->AddString(); + ASSERT(mhod); + if(mhod) + { + iPod_mhod *srcMHOD = src->mhod[i]; + ASSERT(srcMHOD); + if(srcMHOD) + mhod->Duplicate(srcMHOD, mhod); + } + } + + for(i=0; i<mhipCount; i++) + { + iPod_mhip *mhip = NULL; + if(dst->AddPlaylistEntry(mhip) >= 0 && mhip != NULL) + { + iPod_mhip *srcMHIP = src->mhip[i]; + ASSERT(srcMHIP); + if(srcMHIP) + mhip->Duplicate(srcMHIP, mhip); + } + } +} + +bool iPod_mhyp::operator()(iPod_mhyp*& one, iPod_mhyp*& two) { + if(one->hidden & 0xff) return true; + if(two->hidden & 0xff) return false; + wchar_t * a = one->FindString(MHOD_TITLE)->str; + wchar_t * b = two->FindString(MHOD_TITLE)->str; + return _wcsicmp(a?a:L"",b?b:L"") < 0; +} + +////////////////////////////////////////////////////////////////////// +// iPod_mhip - Playlist Entry +////////////////////////////////////////////////////////////////////// +iPod_mhip::iPod_mhip() : +dataobjectcount(1), +podcastgroupflag(0), +groupid(0), +songindex(0), +timestamp(0), +podcastgroupref(0) +{ +} + +iPod_mhip::~iPod_mhip() +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iPod_mhip_destructor); +#endif +} + +long iPod_mhip::parse(const uint8_t *data) +{ + long ptr=0; + + //check mhyp header + if (_strnicmp((char *)&data[ptr],"mhip",4)) return -1; + ptr+=4; + + // get sizes + size_head=rev4(&data[ptr]); + ptr+=4; + size_total=rev4(&data[ptr]); + ptr+=4; + + ASSERT(size_head == 0x4c); + + // read in the useful info + dataobjectcount=rev4(&data[ptr]); //data object count + ptr+=4; + podcastgroupflag=rev4(&data[ptr]); //podcast group flag + ptr+=4; + groupid=rev4(&data[ptr]); // group id + ptr+=4; + songindex=rev4(&data[ptr]); //trackid + ptr+=4; + timestamp=rev4(&data[ptr]); // timestamp + ptr+=4; + podcastgroupref=rev4(&data[ptr]); // group ref + ptr+=4; + + ptr=size_head; + + // dump the mhod after the mhip, as it's just a position number in the playlist + // and useless to read in since we get them in order anyway + for(uint32_t i=0; i!=dataobjectcount; i++) { + iPod_mhod *m = new iPod_mhod; + long ret = m->parse(&data[ptr]); + ASSERT(ret >= 0); + if(ret<0) return ret; + ptr+=ret; + if(m->type == 100) delete m; //fuck 'em + else mhod.push_back(m); + } + + return ptr; +} + + +long iPod_mhip::write(unsigned char * data, const unsigned long datasize, int entrynum) +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iPod_mhip_write); +#endif + + long ptr=0; + + const unsigned int headsize=0x4c; + // check for header size and mhod size + if (headsize+0x2c>datasize) + { + ASSERT(0); + return -1; + } + + //write mhip header + data[0]='m';data[1]='h';data[2]='i';data[3]='p'; + ptr+=4; + + // write sizes + rev4(headsize,&data[ptr]); // header size + ptr+=4; + //rev4(headsize,&data[ptr]); // mhips have no children or subdata + ptr+=4; + + // fill in stuff + rev4(mhod.size() + ((podcastgroupflag!=0)?0:1),&data[ptr]); + ptr+=4; + rev4(podcastgroupflag,&data[ptr]); + ptr+=4; + rev4(groupid,&data[ptr]); + ptr+=4; + rev4(songindex,&data[ptr]); + ptr+=4; + rev4(timestamp,&data[ptr]); + ptr+=4; + rev4(podcastgroupref,&data[ptr]); + ptr+=4; + + // fill up the rest of the header with nulls + for (unsigned int i=ptr;i<headsize;i++) + data[i]=0; + ptr=headsize; + + if(!podcastgroupflag) { + // create an faked up mhod type 100 for position info + // (required at this point, albeit seemingly useless.. type 100 mhods are weird) + data[ptr]='m';data[ptr+1]='h';data[ptr+2]='o';data[ptr+3]='d'; + ptr+=4; + rev4(0x18,&data[ptr]); // header size + ptr+=4; + rev4(0x2c,&data[ptr]); // total size + ptr+=4; + rev4(100,&data[ptr]); // type + ptr+=4; + rev4(0,&data[ptr]); // two nulls + ptr+=4; + rev4(0,&data[ptr]); + ptr+=4; + rev4(entrynum,&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; + // the above code could be more optimized, but this is simpler to read, methinks + } + + for(unsigned long i = 0; i < mhod.size(); i++) { + long ret = mhod[i]->write(&data[ptr],datasize-ptr); + ASSERT(ret >= 0); + if(ret<0) return ret; + else ptr+=ret; + } + rev4(ptr,&data[8]); + return ptr; +} + +void iPod_mhip::Duplicate(iPod_mhip *src, iPod_mhip *dst) +{ + if(src == NULL || dst == NULL) + return; + + dst->dataobjectcount = src->dataobjectcount; + dst->podcastgroupflag = src->podcastgroupflag; + dst->groupid = src->groupid; + dst->songindex = src->songindex; + dst->timestamp = src->timestamp; + dst->podcastgroupref = src->podcastgroupref; +} + + + +////////////////////////////////////////////////////////////////////// +// iPod_slst - Smart Playlist +////////////////////////////////////////////////////////////////////// + +iPod_slst::iPod_slst() : +splPref(NULL), +splData(NULL) +{ + isSmartPlaylist = true; + isPopulated = false; // smart playlists are not considered populated by default + Reset(); +} + +iPod_slst::~iPod_slst() +{ + RemoveAllRules(); +} + + +iPod_slst::FieldType iPod_slst::GetFieldType(const unsigned long field) +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iPod_slst_GetFieldType); +#endif + + switch(field) + { + case SPLFIELD_SONG_NAME: + case SPLFIELD_ALBUM: + case SPLFIELD_ARTIST: + case SPLFIELD_GENRE: + case SPLFIELD_KIND: + case SPLFIELD_COMMENT: + case SPLFIELD_COMPOSER: + case SPLFIELD_GROUPING: + return(ftString); + + case SPLFIELD_BITRATE: + case SPLFIELD_SAMPLE_RATE: + case SPLFIELD_YEAR: + case SPLFIELD_TRACKNUMBER: + case SPLFIELD_SIZE: + case SPLFIELD_PLAYCOUNT: + case SPLFIELD_DISC_NUMBER: + case SPLFIELD_BPM: + case SPLFIELD_RATING: + case SPLFIELD_TIME: // time is the length of the track in milliseconds + case SPLFIELD_VIDEO_KIND: + return(ftInt); + + case SPLFIELD_COMPILATION: + return(ftBoolean); + + case SPLFIELD_DATE_MODIFIED: + case SPLFIELD_DATE_ADDED: + case SPLFIELD_LAST_PLAYED: + return(ftDate); + + case SPLFIELD_PLAYLIST: + return(ftPlaylist); + + default: + // Unknown field type + ASSERT(0); + } + + return(ftUnknown); +} + +iPod_slst::ActionType iPod_slst::GetActionType(const unsigned long field, const unsigned long action) +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iPod_slst_GetActionType); +#endif + + const FieldType fieldType = GetFieldType(field); + switch(fieldType) + { + case ftString: + switch(action) + { + case SPLACTION_IS_STRING: + case SPLACTION_IS_NOT: + case SPLACTION_CONTAINS: + case SPLACTION_DOES_NOT_CONTAIN: + case SPLACTION_STARTS_WITH: + case SPLACTION_DOES_NOT_START_WITH: + case SPLACTION_ENDS_WITH: + case SPLACTION_DOES_NOT_END_WITH: + return(atString); + + case SPLACTION_IS_NOT_IN_THE_RANGE: + case SPLACTION_IS_INT: + case SPLACTION_IS_NOT_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_IN_THE_LAST: + case SPLACTION_IS_NOT_IN_THE_LAST: + return(atInvalid); + + default: + // Unknown action type + ASSERT(0); + return(atUnknown); + } + break; + + case ftInt: + switch(action) + { + case SPLACTION_IS_INT: + case SPLACTION_IS_NOT_INT: + case SPLACTION_IS_GREATER_THAN: + case SPLACTION_IS_NOT_GREATER_THAN: + case SPLACTION_IS_LESS_THAN: + case SPLACTION_IS_NOT_LESS_THAN: + return(atInt); + + case SPLACTION_IS_NOT_IN_THE_RANGE: + case SPLACTION_IS_IN_THE_RANGE: + return(atRange); + + 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_IN_THE_LAST: + case SPLACTION_IS_NOT_IN_THE_LAST: + case SPLACTION_IS_NOT: + case SPLACTION_DOES_NOT_CONTAIN: + return(atInvalid); + + default: + // Unknown action type + ASSERT(0); + return(atUnknown); + } + break; + + case ftBoolean: + return(atNone); + + case ftDate: + switch(action) + { + case SPLACTION_IS_INT: + case SPLACTION_IS_NOT_INT: + case SPLACTION_IS_GREATER_THAN: + case SPLACTION_IS_NOT_GREATER_THAN: + case SPLACTION_IS_LESS_THAN: + case SPLACTION_IS_NOT_LESS_THAN: + return(atDate); + + case SPLACTION_IS_IN_THE_LAST: + case SPLACTION_IS_NOT_IN_THE_LAST: + return(atInTheLast); + + case SPLACTION_IS_IN_THE_RANGE: + case SPLACTION_IS_NOT_IN_THE_RANGE: + return(atRange); + + 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: + case SPLACTION_DOES_NOT_CONTAIN: + return(atInvalid); + + default: + // Unknown action type + ASSERT(0); + return(atUnknown); + } + break; + + case ftPlaylist: + switch(action) + { + case SPLACTION_IS_INT: + case SPLACTION_IS_NOT_INT: + return (atPlaylist); + + 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_LAST: + case SPLACTION_IS_NOT_IN_THE_LAST: + case SPLACTION_IS_IN_THE_RANGE: + case SPLACTION_IS_NOT_IN_THE_RANGE: + 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: + case SPLACTION_DOES_NOT_CONTAIN: + return (atInvalid); + + default: + ASSERT(0); + return(atUnknown); + } + + case ftUnknown: + // Unknown action type + ASSERT(0); + break; + } + + return(atUnknown); +} + + +void iPod_slst::UpdateMHODPointers() +{ + if(IsSmartPlaylist() == false) + return; + + splPref = AddString(MHOD_SPLPREF); + splData = AddString(MHOD_SPLDATA); +} + +void iPod_slst::SetPrefs(const bool liveupdate, const bool rules_enabled, const bool limits_enabled, + const unsigned long limitvalue, const unsigned long limittype, const unsigned long limitsort) +{ + UpdateMHODPointers(); + + ASSERT(splPref); + if(splPref) + { + splPref->liveupdate = liveupdate ? 1 : 0; + splPref->checkrules = rules_enabled ? 1 : 0; + splPref->checklimits = limits_enabled ? 1 : 0; + splPref->matchcheckedonly = 0; + splPref->limitsort_opposite = limitsort & 0x80000000 ? 1 : 0; + splPref->limittype = limittype; + splPref->limitsort = limitsort & 0x000000ff; + splPref->limitvalue = limitvalue; + } +} + +unsigned long iPod_slst::GetRuleCount() +{ + UpdateMHODPointers(); + + ASSERT(splData); + if(splData == NULL) + return(0); + + return(splData->rule.size()); +} + + +int iPod_slst::AddRule(const unsigned long field, + const unsigned long action, + const wchar_t * string, // use string for string based rules + const unsigned __int64 value, // use value for single variable rules + const unsigned __int64 from, // use from and to for range based rules + const unsigned __int64 to, + const unsigned __int64 units) // use units for "In The Last" based rules +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iPod_slst_AddRule); +#endif + + UpdateMHODPointers(); + + ASSERT(splPref != NULL && splData != NULL); + if (splPref == NULL || splData == NULL) return -1; + + // create a new rule + SPLRule *r = new SPLRule; + ASSERT(r); + if(r == NULL) + return(-1); + + const FieldType ft = GetFieldType(field); + const ActionType at = GetActionType(field, action); + + if(ft == ftUnknown || at == atUnknown) + { + ASSERT(0); + return(-1); + } + + + r->field = field; + r->action = action; + r->unk1 = 0; + r->unk2 = 0; + r->unk3 = 0; + r->unk4 = 0; + r->unk5 = 0; + + if(ft == ftString) + { + // it's a string type (SetString() sets the length) + r->SetString(string); + } + else + { + // All non-string rules currently have a length of 68 bytes + r->length = 0x44; + + /* Values based on ActionType: + * int: fromvalue = value, fromdate = 0, fromunits = 1, tovalue = value, todate = 0, tounits = 1 + * playlist: same as int + * boolean: same as int, except fromvalue/tovalue are 1 if set, 0 if not set + * date: same as int + * range: same as int, except use from and to, instead of value + * in the last: fromvalue = 0x2dae2dae2dae2dae (SPLDATE_IDENTIFIER), fromdate = 0xffffffffffffffff - value, fromunits = seconds in period, + * tovalue = 0x2dae2dae2dae2dae (SPLDATE_IDENTIFIER), todate = 0xffffffffffffffff - value, tounits = seconds in period + */ + switch(at) + { + case atInt: + case atPlaylist: + case atDate: + r->fromvalue = value; + r->fromdate = 0; + r->fromunits = 1; + r->tovalue = value; + r->todate = 0; + r->tounits = 1; + break; + case atBoolean: + r->fromvalue = value > 0 ? 1 : 0; + r->fromdate = 0; + r->fromunits = 1; + r->tovalue = r->fromvalue; + r->todate = 0; + r->tounits = 1; + break; + case atRange: + r->fromvalue = from; + r->fromdate = 0; + r->fromunits = 1; + r->tovalue = to; + r->todate = 0; + r->tounits = 1; + break; + case atInTheLast: + r->fromvalue = SPLDATE_IDENTIFIER; + r->fromdate = ConvertNumToDateValue(value); + r->fromunits = units; + r->tovalue = SPLDATE_IDENTIFIER; + r->todate = ConvertNumToDateValue(value); + r->tounits = units; + break; + case atNone: + break; + default: + ASSERT(0); + break; + } + } + + // set isPopulated to false, since we're modifying the rules + isPopulated = false; + + // push the rule into the mhod + splData->rule.push_back(r); + return(splData->rule.size() - 1); +} + +int iPod_slst::AddRule(const SPLRule &rule) +{ + UpdateMHODPointers(); + + ASSERT(splPref != NULL && splData != NULL); + if (splPref == NULL || splData == NULL) + return -1; + + SPLRule *r = new SPLRule; + ASSERT(r); + if(r == NULL) + return(-1); + + r->action = rule.action; + r->field = rule.field; + r->fromdate = rule.fromdate; + r->fromunits = rule.fromunits; + r->fromvalue = rule.fromvalue; + r->length = rule.length; + r->SetString(rule.string); + r->todate = rule.todate; + r->tounits = rule.tounits; + r->tovalue = rule.tovalue; + r->unk1 = rule.unk1; + r->unk2 = rule.unk2; + r->unk3 = rule.unk3; + r->unk4 = rule.unk4; + r->unk5 = rule.unk5; + + // set isPopulated to false, since we're modifying the rules + isPopulated = false; + + // push the rule into the mhod + splData->rule.push_back(r); + return(splData->rule.size() - 1); +} + +void iPod_slst::RemoveAllRules() +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iPod_slst_RemoveAllRules); +#endif + + // Note: Don't update MHOD pointers here...if they don't already exist, there is no point in creating them + if(splData == NULL) + return; + + //while(splData->rule.size() > 0) + //{ + // // set isPopulated to false, since we're modifying the rules + // isPopulated = false; + + // SPLRule *r = splData->rule[0]; + // splData->rule.eraseindex(0); + // delete r; + //} + auto it = splData->rule.begin(); + while ( it != splData->rule.end()) + { + // set isPopulated to false, since we're modifying the rules + isPopulated = false; + + SPLRule* r = *it; + it = splData->rule.erase(it); + delete r; + } + + splData->rule.clear(); +} + + +void iPod_slst::Reset() +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iPod_slst_Reset); +#endif + + // Note: Don't update MHOD pointers here...if they don't already exist, there is no point in creating them + RemoveAllRules(); + + isPopulated = false; + + if(splPref) + { + splPref->liveupdate = 1; + splPref->checkrules = 1; + splPref->checklimits = 0; + splPref->matchcheckedonly = 0; + splPref->limittype = LIMITTYPE_SONGS; + splPref->limitsort = LIMITSORT_RANDOM; + splPref->limitsort_opposite = 0; + splPref->limitvalue = 25; + } + + if(splData) + { + splData->rules_operator = SPLMATCH_AND; + splData->unk5 = 0; + } +} + +////////////////////////////////////////////////////////////////////// +// iPod_mhdp - Class for parsing the Play Counts file +////////////////////////////////////////////////////////////////////// + +iPod_mhdp::iPod_mhdp() +{ + children = 0; + entry = 0; +} + +iPod_mhdp::~iPod_mhdp() +{ + free(entry); +} + +long iPod_mhdp::parse(const uint8_t *data) +{ + long ptr=0; + + //check mhdp header + if (_strnicmp((char *)&data[ptr],"mhdp",4)) return -1; + ptr+=4; + + // get sizes + size_head=rev4(&data[ptr]); + ptr+=4; + entrysize=rev4(&data[ptr]); + ptr+=4; + if (entrysize != 0x10 && entrysize != 0x0c && entrysize != 0x14 && entrysize != 0x1c) + { + ASSERT(0); + return -1; // can't understand new versions of this file + } + + children=rev4(&data[ptr]); + ptr+=4; + + // skip dummy space + ptr=size_head; + + unsigned long i; + + entry = (PCEntry *)malloc(children * sizeof(PCEntry)); + + for (i=0;i<children;i++) + { + PCEntry &e = entry[i]; + + e.playcount=rev4(&data[ptr]); + ptr+=4; + e.lastplayedtime=rev4(&data[ptr]); + ptr+=4; + e.bookmarktime=rev4(&data[ptr]); + ptr+=4; + if (entrysize >= 0x10) + { + e.stars=rev4(&data[ptr]); + ptr+=4; + } + else + e.stars = 0; + + if (entrysize >= 0x14) + { + e.unk1 = rev4(&data[ptr]); + ptr+=4; + } + else + e.unk1 = 0; + + if (entrysize >= 0x1c) + { + e.skipcount = rev4(&data[ptr]); + ptr+=4; + e.skippedtime = rev4(&data[ptr]); + ptr+=4; + } + else + { + e.skipcount = 0; + e.skippedtime = 0; + } + } + + return children; +} + + +////////////////////////////////////////////////////////////////////// +// iPod_mhpo - Class for parsing the OTGPlaylist file +////////////////////////////////////////////////////////////////////// + +iPod_mhpo::iPod_mhpo() : +size_head(0), +unk1(0), +unk2(0) +{ + idList = 0; + children = 0; +} + +iPod_mhpo::~iPod_mhpo() +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iPod_mhpo_destructor); +#endif + free(idList); +} + +long iPod_mhpo::parse(const uint8_t *data) +{ + long ptr=0; + + //check mhdp header + if (_strnicmp((char *)&data[ptr],"mhpo",4)) return -1; + ptr+=4; + + // get sizes + size_head=rev4(&data[ptr]); + ptr+=4; + + unk1=rev4(&data[ptr]); + ptr+=4; + + children=rev4(&data[ptr]); // Only used locally + ptr+=4; + + unk2=rev4(&data[ptr]); + ptr+=4; + + if (ptr!=size_head) return -1; // if this isn't true, I screwed up somewhere + + unsigned long i; + + idList = (uint32_t *)malloc(children * sizeof(uint32_t)); + + for (i=0;i<children;i++) + { + unsigned long temp; + temp=rev4(&data[ptr]); + ptr+=4; + idList[i]=temp; + } + + return ptr; +} + +long iPod_mhpo::write(unsigned char * data, const unsigned long datasize) +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iPod_mhpo_write); +#endif + + long ptr=0; + + const unsigned int headsize=0x14; + // check for header size + child size + if (headsize+(GetChildrenCount()*4)>datasize) return -1; + + //write mhpo header + data[0]='m';data[1]='h';data[2]='p';data[3]='o'; + ptr+=4; + + // write size + rev4(headsize,&data[ptr]); // header size + ptr+=4; + + // fill in stuff + rev4(unk1,&data[ptr]); + ptr+=4; + const unsigned long children = GetChildrenCount(); + rev4(children,&data[ptr]); + ptr+=4; + rev4(unk2,&data[ptr]); + ptr+=4; + + for (unsigned long i=0;i<children;i++) + { + rev4(idList[i],&data[ptr]); + ptr+=4; + + } + return ptr; +} + + +iPod_mhyp * iPod_mhpo::CreatePlaylistFromOTG(iPod_mhbd * iPodDB, wchar_t * name) +{ + // create playlist + iPod_mhyp * myplaylist = iPodDB->mhsdplaylists->mhlp->AddPlaylist(); + if (myplaylist == NULL) + return NULL; + + // set name + iPod_mhod * playlistName = myplaylist->AddString(MHOD_TITLE); + playlistName->SetString(name); + + unsigned long count = GetChildrenCount(); + // for every track + for (unsigned long i=0;i<count;i++) + { + // find the track + iPod_mhit * track = iPodDB->mhsdsongs->mhlt->GetTrack(idList[i]); + + // myplaylist->AddPlaylistEntry(iPod_mhip * entry, track->id); + + // Add the playlist entry locally rather than using + // AddPlaylistEntry() for speed optimization reasons + iPod_mhip *entry = new iPod_mhip; + ASSERT(entry != NULL); + if(entry && track) + { + entry->songindex = track->id; + myplaylist->mhip.push_back(entry); + } + } + + return myplaylist; +} + + +////////////////////////////////////////////////////////////////////// +// iPod_mqed - Class for parsing the EQ Presets file +////////////////////////////////////////////////////////////////////// +iPod_mqed::iPod_mqed() : +unk1(1), +unk2(1), +eqList(), +size_head(0x68) +{ +} + +iPod_mqed::~iPod_mqed() +{ + const unsigned long count = GetChildrenCount(); + for (unsigned long i=0;i<count;i++) + { + iPod_pqed * m=eqList[i]; + delete m; + } + eqList.clear(); +} + +long iPod_mqed::parse(const uint8_t *data) +{ + unsigned long ptr=0; + uint32_t i; + + //check mqed header + if (_strnicmp((char *)&data[ptr],"mqed",4)) return -1; + ptr+=4; + + size_head=rev4(&data[ptr]); + ptr+=4; + + ASSERT(size_head == 0x68); + + unk1=rev4(&data[ptr]); + ptr+=4; + unk2=rev4(&data[ptr]); + ptr+=4; + unsigned long numchildren=rev4(&data[ptr]); + ptr+=4; + unsigned long childsize=rev4(&data[ptr]); + ptr+=4; + + ASSERT(childsize == 588); + if (childsize != 588) return -1; + + // skip the nulls + ptr=size_head; + + for (i=0;i<numchildren;i++) + { + iPod_pqed * e = new iPod_pqed; + long ret = e->parse(&data[ptr]); + if (ret < 0) + { + delete e; + return ret; + } + + eqList.push_back(e); + ptr+=ret; + } + + return ptr; +} + + +long iPod_mqed::write(unsigned char * data, const unsigned long datasize) +{ + unsigned long ptr=0; + uint32_t i; + + //write mqed header + data[0]='m';data[1]='q';data[2]='e';data[3]='d'; + ptr+=4; + + rev4(size_head,&data[ptr]); + ptr+=4; + rev4(unk1,&data[ptr]); + ptr+=4; + rev4(unk2,&data[ptr]); + ptr+=4; + + rev4(GetChildrenCount(),&data[ptr]); + ptr+=4; + + rev4(588,&data[ptr]); + ptr+=4; + + // fill with nulls + while (ptr<size_head) + { + data[ptr]=0; ptr++; + } + + // write the eq settings + for (i=0;i<GetChildrenCount();i++) + { + long ret=eqList[i]->write(&data[ptr],datasize-ptr); + if (ret <0) return -1; + ptr+=ret; + } + + return ptr; +} + + + +////////////////////////////////////////////////////////////////////// +// iPod_pqed - Class for parsing the EQ Entries +////////////////////////////////////////////////////////////////////// +iPod_pqed::iPod_pqed() : +name(NULL), +preamp(0) +{ + int i; + for (i=0;i<10;i++) + eq[i]=0; + for (i=0;i<5;i++) + short_eq[i]=0; +} + +iPod_pqed::~iPod_pqed() +{ + if (name) delete [] name; +} + +long iPod_pqed::parse(const uint8_t *data) +{ + unsigned long ptr=0; + uint32_t i; + + //check pqed header + if (_strnicmp((char *)&data[ptr],"pqed",4)) return -1; + ptr+=4; + + // get string length + length=data[ptr]; ptr++; + length+=data[ptr]*256; ptr++; + + name=new wchar_t[length + 1]; + memcpy(name,&data[ptr],length); + name[length / 2] = '\0'; + + // skip the nulls + ptr=516; + + preamp = rev4(&data[ptr]); + ptr+=4; + + unsigned long numbands; + numbands = rev4(&data[ptr]); + ptr+=4; + + ASSERT (numbands == 10); + if (numbands != 10) return -1; + + for (i=0;i!=numbands;i++) + { + eq[i]=rev4(&data[ptr]); + ptr+=4; + } + + numbands = rev4(&data[ptr]); + ptr+=4; + ASSERT (numbands == 5); + if (numbands != 5) return -1; + + for (i=0;i!=numbands;i++) + { + short_eq[i]=rev4(&data[ptr]); + ptr+=4; + } + + return ptr; +} + +long iPod_pqed::write(unsigned char * data, const unsigned long datasize) +{ + long ptr=0; + uint32_t i; + + //write pqed header + data[0]='p';data[1]='q';data[2]='e';data[3]='d'; + ptr+=4; + + // write 2 byte string length + data[ptr++]=(uint8_t)(length & 0xff); + data[ptr++]=(uint8_t)((length >> 8) & 0xff); + + for (i=0;i!=length;i++) + { + data[ptr++]=name[i] & 0xff; + data[ptr++]=(name[i] >> 8) & 0xff; + } + + // fill rest with nulls + while (ptr<516) + { + data[ptr++]=0; + } + + rev4(preamp,&data[ptr]); + ptr+=4; + + rev4(10,&data[ptr]); + ptr+=4; + for (i=0;i<10;i++) + { + rev4(eq[i],&data[ptr]); + ptr+=4; + } + + rev4(5,&data[ptr]); + ptr+=4; + for (i=0;i<5;i++) + { + rev4(short_eq[i],&data[ptr]); + ptr+=4; + } + + return ptr; +} + + + + +iTunesStats::iTunesStats() : +mhlt(NULL) +{ + entry = 0; + children = 0; +} + +iTunesStats::~iTunesStats() +{ + free(entry); +} + +long iTunesStats::parse(const uint8_t *data) +{ + long ptr=0; + + children = rev3(&data[ptr]); + ptr+=3; + unk1 = rev3(&data[ptr]); + ptr+=3; + + unsigned long i; + + entry = (iTunesStatsEntry *)malloc(children*sizeof(iTunesStatsEntry)); + + for (i=0;i<children;i++) + { + iTunesStatsEntry &e = entry[i]; + + e.entry_size = rev3(&data[ptr]); + ptr+=3; + e.bookmarktime = rev3(&data[ptr]); + ptr+=3; + e.unk1 = rev3(&data[ptr]); + ptr+=3; + e.unk2 = rev3(&data[ptr]); + ptr+=3; + e.playcount = rev3(&data[ptr]); + ptr+=3; + e.skippedcount = rev3(&data[ptr]); + ptr+=3; + +#ifdef _DEBUG + // If any of these trigger an assertion, something new is in the database format + ASSERT(e.entry_size == 0x12); + //ASSERT(e.unk1 == 0); + ASSERT(e.unk2 == 0); +#endif + } + + return children; +} + +// Unlike Play Counts, iTunesStats needs to be created by the application - apparently only for the bookmark time +long iTunesStats::write(unsigned char * data, const unsigned long datasize) +{ + ASSERT(mhlt != NULL); + if(mhlt == NULL) + return(-1); + + const unsigned int numentries = mhlt->GetChildrenCount(); + const unsigned int total_size = 6 + (numentries * sizeof(iTunesStatsEntry)); // 6 bytes for the header + ASSERT(datasize >= total_size); + if(datasize < total_size) + return(-1); + + long ptr=0; + + rev3(numentries, &data[ptr]); + ptr+=3; + + put3(unk1, &data[ptr]); + ptr+=3; + + // Create a new iTunesStatsEntry for each song, only preserving the bookmark time + long ret = 0; + for(unsigned int i=0; i<numentries; i++) + { + iPod_mhit *mhit = mhlt->GetTrack(i); + ASSERT(mhit); + if(mhit == NULL) + return(-1); + + rev3(0x12, &data[ptr]); // Entry size + ptr+=3; + rev3(mhit->bookmarktime / 256, &data[ptr]); + ptr+=3; + rev3(0, &data[ptr]); // iTunesStatsEntry.unk1 + ptr+=3; + rev3(0, &data[ptr]); // iTunesStatsEntry.unk2 + ptr+=3; + rev3(0, &data[ptr]); // Don't write out the playcount + ptr+=3; + rev3(0, &data[ptr]); // or the skipped count + ptr+=3; + } + + return(ptr); +} + +iTunesShuffle::iTunesShuffle() : +datasize(0) +{ + numentries=0; + entry = 0; +} + +iTunesShuffle::~iTunesShuffle() +{ + free(entry); +} + +long iTunesShuffle::parse(const uint8_t *data) +{ + ASSERT(datasize > 0); + + // iTunesShuffle is simply a list of reversed 3 byte indexes + ASSERT(datasize % 3 == 0); + if(datasize % 3 != 0) + return(-1); + + free(entry); + long ptr = 0; + numentries = datasize / 3; + entry = (uint32_t *)malloc(numentries*sizeof(uint32_t)); + for(unsigned int i=0; i<numentries; i++) + { + unsigned int value = rev3(&data[ptr]); + if(value == 0xffffff) // This can happen if iTunesSD::playflags doesn't have the shuffle bit set + value = 0; + + entry[i]=value; + ptr+=3; + } + + return(ptr); +} + +long iTunesShuffle::write(unsigned char *data, const unsigned long datasize) +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iTunesShuffle_write); +#endif + + const unsigned int total_size = numentries * 3; + ASSERT(datasize >= total_size); + if(datasize < total_size) + return(-1); + + long ptr = 0; + + for(unsigned int i=0; i<numentries; i++) + { + rev3(entry[i], &data[ptr]); + ptr+=3; + } + + return(ptr); +} + +void iTunesShuffle::Randomize() +{ + Randomize(numentries); +} + +void iTunesShuffle::Randomize(const unsigned int numsongs) +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iTunesShuffle_randomize); +#endif + + if(numsongs == 0) + return; + + if (numentries < numsongs) + { + free(entry); + entry = (uint32_t *)malloc(numsongs*sizeof(uint32_t)); + } + + numentries=numsongs; + + for (uint32_t i=0;i!=numsongs;i++) + { + entry[i] = i; + } + + std::random_shuffle(entry, entry+numsongs); +} + +////////////////////////////////////////////////////////////////////// +// MHIA - Album Item +////////////////////////////////////////////////////////////////////// + +iPod_mhia::iPod_mhia() +: unk1(0), albumid(0), type(2) +{ + dbid = Generate64BitID(); +} + +iPod_mhia::~iPod_mhia() +{ + for (auto obj : mhod) + { + delete obj; + } + mhod.clear(); +} + +long iPod_mhia::parse(const uint8_t *data) +{ + return 0; + /* + long ptr = 0; + if (_strnicmp((char *)&data[ptr],"mhia",4)) return -1; + ptr+=4; + + uint32_t size_head=rev4(&data[ptr]); + ptr+=4; + uint32_t size_total=rev4(&data[ptr]); + ptr+=4; + uint32_t num_children=rev4(&data[ptr]); + ptr+=4; + unk1 = rev2(&data[ptr]); + ptr+=2; + albumid = rev4(&data[ptr]); + ptr+=4; + dbid = rev8(get8(&data[ptr])); + ptr+=8; + type = rev4(&data[ptr]); + ptr+=4; + + ptr = size_head; // skip nulls + + for(uint32_t i = 0; i < num_children; i++) + { + iPod_mhod* m = new iPod_mhod(); + long ret = m->parse(data + ptr); + if(ret == -1) + { + delete m; + return -1; + } + mhod.push_back(m); + ptr += ret; + } + + return ptr; + */ +} + +long iPod_mhia::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 mhla header + data[0]='m';data[1]='h';data[2]='i';data[3]='a'; + ptr+=4; + + rev4(headsize,&data[ptr]); // header size + ptr+=4; + rev4(-1,&data[ptr]); // total size (put this in later) + ptr+=4; + rev4((uint32_t)mhod.size(),&data[ptr]); // number of strings + ptr+=4; + rev2(unk1, &data[ptr]); + ptr+=2; + rev2(albumid, &data[ptr]); + ptr+=2; + put8(rev8(dbid), &data[ptr]); + ptr+=8; + rev4(type, &data[ptr]); + ptr+=4; + + memset(&data[ptr], 0, 60); // a whole shitload of zeroes + ptr+=60; + + for (size_t i=0;i!=mhod.size();i++) + { + long ret = mhod[i]->write(data + ptr, datasize - ptr); + if (ret == -1) + return -1; + ptr += ret; + } + + // put in total size + rev4(ptr, &data[8]); + + return ptr; +} + +////////////////////////////////////////////////////////////////////// +// MHLA - Album List +////////////////////////////////////////////////////////////////////// + +iPod_mhla::iPod_mhla() : nextAlbumId(400) +{ +} +iPod_mhla::~iPod_mhla() +{ +} + +long iPod_mhla::parse(const uint8_t *data) +{ + return 0; + /* + long ptr=0; + + if (_strnicmp((char *)&data[ptr],"mhla",4)) return -1; + ptr+=4; + + uint32_t size_head=rev4(&data[ptr]); + ptr+=4; + uint32_t num_children=rev4(&data[ptr]); + ptr+=4; + + ptr = size_head; // skip nulls + + for(uint32_t i = 0; i < num_children; i++) + { + iPod_mhia* m = new iPod_mhia(); + long ret = m->parse(data + ptr); + if(ret == -1) + { + delete m; + return -1; + } + mhia.push_back(m); + ptr += ret; + } + + return ptr; + */ +} + +long iPod_mhla::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 mhla header + data[0]='m';data[1]='h';data[2]='l';data[3]='a'; + ptr+=4; + + rev4(headsize,&data[ptr]); // header size + ptr+=4; + rev4((uint32_t)albums.size(),&data[ptr]); // number of albums + ptr+=4; + memset(&data[ptr], 0, 80); // a whole shitload of zeroes + ptr+=80; + + /* + for (size_t i=0;i!=mhia.size();i++) + { + long ret = mhia[i]->write(data + ptr, datasize - ptr); + if (ret == -1) + return -1; + ptr += ret; + } + */ + + for(albums_map_t::iterator i = albums.begin(); i!=albums.end(); i++) + { + iPod_mhia mhia; + mhia.albumid = i->second; + iPod_mhod* artist = new iPod_mhod(); + artist->SetString(i->first.artist); + artist->type = MHOD_ALBUMLIST_ARTIST; + mhia.mhod.push_back(artist); + + iPod_mhod* album = new iPod_mhod(); + album->SetString(i->first.album); + album->type = MHOD_ALBUMLIST_ALBUM; + mhia.mhod.push_back(album); + + long ret = mhia.write(data + ptr, datasize - ptr); + if (ret == -1) + return -1; + ptr += ret; + } + + return ptr; +} + +uint16_t iPod_mhla::GetAlbumId(const wchar_t* artist, const wchar_t* album) +{ + ArtistAlbumPair key(artist, album); + if(albums.find(key) == albums.end()) + { + albums[key] = nextAlbumId; + return nextAlbumId++; + } + return albums[key]; +} + +void iPod_mhla::ClearAlbumsList() +{ + nextAlbumId = 0; + albums.clear(); +} + + diff --git a/Src/Plugins/Portable/pmp_ipod/iPodDB.h b/Src/Plugins/Portable/pmp_ipod/iPodDB.h new file mode 100644 index 00000000..dd4878fa --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/iPodDB.h @@ -0,0 +1,1256 @@ +/* + * + * + * 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.h: interface for the iPod classes. +// +////////////////////////////////////////////////////////////////////// + +#ifndef __IPODDB_H__ +#define __IPODDB_H__ + +#pragma once + +#pragma warning( disable : 4786) + +#include <algorithm> +#include <windows.h> +#include <bfc/platform/types.h> +#include <map> +#include <vector> + +#ifdef _DEBUG +#undef ASSERT +#define ASSERT(x) assert(x) +#else +#define ASSERT(x) {} +#endif + + + +// mhod types +#define MHOD_TITLE 1 +#define MHOD_LOCATION 2 +#define MHOD_ALBUM 3 +#define MHOD_ARTIST 4 +#define MHOD_GENRE 5 +#define MHOD_FILETYPE 6 +#define MHOD_EQSETTING 7 +#define MHOD_COMMENT 8 +#define MHOD_CATEGORY 9 // iTunes Music Store Podcast category +#define MHOD_COMPOSER 12 +#define MHOD_GROUPING 13 +#define MHOD_DESCRIPTION 14 // Podcast show notes text - accessible via the center iPod button +#define MHOD_ENCLOSUREURL 15 // Used by iTunes 4.9 for a Podcast's original enclosure URL +#define MHOD_RSSFEEDURL 16 // Used by iTunes 4.9 for a Podcast's RSS 2.0 feed URL +#define MHOD_CHAPTER 17 // M4A-style tagged data that is used to support subsongs/chapters +#define MHOD_SUBTITLE 18 +#define MHOD_SHOW 19 +#define MHOD_EPISODE 20 +#define MHOD_TVNETWORK 21 +#define MHOD_ALBUMARTIST 22 +#define MHOD_ARTIST_SORT 23 +#define MHOD_TITLE_SORT 27 +#define MHOD_ALBUM_SORT 28 +#define MHOD_ALBUMARTIST_SORT 29 +#define MHOD_COMPOSER_SORT 30 +#define MHOD_SHOW_SORT 31 +#define MHOD_SPLPREF 50 +#define MHOD_SPLDATA 51 +#define MHOD_LIBRARY 52 // Found in the default hidden playlist +#define MHOD_LIBRARY_LETTER 53 // letter jump table +#define MHOD_PLAYLIST 100 +#define MHOD_ALBUMLIST_ALBUM 200 +#define MHOD_ALBUMLIST_ARTIST 201 +#define MHOD_ALBUMLIST_ARTIST_SORT 202 +#define MHOD_ALBUMLIST_PODCASTURL 203 +#define MHOD_ALBUMLIST_SHOW 204 + + +// Equalizer defines +#define EQ_NONE -1 +#define EQ_ACOUSTIC 100 +#define EQ_BASSBOOSTER 101 +#define EQ_BASSREDUCER 102 +#define EQ_CLASSICAL 103 +#define EQ_DANCE 104 +#define EQ_DEEP 105 +#define EQ_ELECTRONIC 106 +#define EQ_FLAT 107 +#define EQ_HIPHOP 108 +#define EQ_JAZZ 109 +#define EQ_LATIN 110 +#define EQ_LOUDNESS 111 +#define EQ_LOUNGE 112 +#define EQ_PIANO 113 +#define EQ_POP 114 +#define EQ_RNB 115 +#define EQ_ROCK 116 +#define EQ_SMALLSPEAKERS 117 +#define EQ_SPOKENWORD 118 +#define EQ_TREBLEBOOSTER 119 +#define EQ_TREBLEREDUCER 120 +#define EQ_VOCALBOOSTER 121 + + + +// Smart Playlist stuff +#define SPLMATCH_AND 0 // AND rule - all of the rules must be true in order for the combined rule to be applied +#define SPLMATCH_OR 1 // OR rule + +// Limit Types.. like limit playlist to 100 minutes or to 100 songs +#define LIMITTYPE_MINUTES 0x01 +#define LIMITTYPE_MB 0x02 +#define LIMITTYPE_SONGS 0x03 +#define LIMITTYPE_HOURS 0x04 +#define LIMITTYPE_GB 0x05 + +// Limit Sorts.. Like which songs to pick when using a limit type +// Special note: the values for LIMITSORT_LEAST_RECENTLY_ADDED, LIMITSORT_LEAST_OFTEN_PLAYED, +// LIMITSORT_LEAST_RECENTLY_PLAYED, and LIMITSORT_LOWEST_RATING are really 0x10, 0x14, +// 0x15, 0x17, with the 'limitsort_opposite' flag set. This is the same value as the +// "positive" value (i.e. LIMITSORT_LEAST_RECENTLY_ADDED), and is really very terribly +// awfully weird, so we map the values to iPodDB specific values with the high bit set. +// +// On writing, we check the high bit and write the limitsort_opposite from that. That +// way, we don't have to deal with programs using the class needing to set the wrong +// limit and then make it into the "opposite", which would be frickin' annoying. +#define LIMITSORT_RANDOM 0x02 +#define LIMITSORT_SONG_NAME 0x03 +#define LIMITSORT_ALBUM 0x04 +#define LIMITSORT_ARTIST 0x05 +#define LIMITSORT_GENRE 0x07 +#define LIMITSORT_MOST_RECENTLY_ADDED 0x10 +#define LIMITSORT_COMPOSER 0x12 // Not used by iTunes, but inferred from the Type 52 MHOD's Composer type +#define LIMITSORT_LEAST_RECENTLY_ADDED 0x80000010 // See note above +#define LIMITSORT_MOST_OFTEN_PLAYED 0x14 +#define LIMITSORT_LEAST_OFTEN_PLAYED 0x80000014 // See note above +#define LIMITSORT_MOST_RECENTLY_PLAYED 0x15 +#define LIMITSORT_LEAST_RECENTLY_PLAYED 0x80000015 // See note above +#define LIMITSORT_HIGHEST_RATING 0x17 +#define LIMITSORT_LOWEST_RATING 0x80000017 // See note above + +// Smartlist Actions - Used in the rules. +/* + really this is a bitmapped field... + high byte + bit 0 = "string" values if set, "int" values if not set + bit 1 = "not", or to negate the check. + lower 2 bytes + bit 0 = simple "IS" query + bit 1 = contains + bit 2 = begins with + bit 3 = ends with + bit 4 = greater than + bit 5 = unknown, but probably greater than or equal to + bit 6 = less than + bit 7 = unknown, but probably less than or equal to + bit 8 = a range selection + bit 9 = "in the last" +*/ +#define SPLACTION_IS_INT 0x00000001 // Also called "Is Set" in iTunes +#define SPLACTION_IS_GREATER_THAN 0x00000010 // Also called "Is After" in iTunes +#define SPLACTION_IS_LESS_THAN 0x00000040 // Also called "Is Before" in iTunes +#define SPLACTION_IS_IN_THE_RANGE 0x00000100 +#define SPLACTION_IS_IN_THE_LAST 0x00000200 +#define SPLACTION_BINARY_AND 0x00000400 + +#define SPLACTION_IS_STRING 0x01000001 +#define SPLACTION_CONTAINS 0x01000002 +#define SPLACTION_STARTS_WITH 0x01000004 +#define SPLACTION_ENDS_WITH 0x01000008 + +#define SPLACTION_IS_NOT_INT 0x02000001 // Also called "Is Not Set" in iTunes +#define SPLACTION_IS_NOT_GREATER_THAN 0x02000010 // Note: Not available in iTunes +#define SPLACTION_IS_NOT_LESS_THAN 0x02000040 // Note: Not available in iTunes +#define SPLACTION_IS_NOT_IN_THE_RANGE 0x02000100 // Note: Not available in iTunes +#define SPLACTION_IS_NOT_IN_THE_LAST 0x02000200 +#define SPLACTION_UNKNOWN2 0x02000800 + +#define SPLACTION_IS_NOT 0x03000001 +#define SPLACTION_DOES_NOT_CONTAIN 0x03000002 +#define SPLACTION_DOES_NOT_START_WITH 0x03000004 // Note: Not available in iTunes +#define SPLACTION_DOES_NOT_END_WITH 0x03000008 // Note: Not available in iTunes + + +// these are to pass to AddRule() when you need a unit for the two "in the last" action types +// Or, in theory, you can use any time range... iTunes might not like it, but the iPod might. +#define SPLACTION_LAST_DAYS_VALUE 86400 // number of seconds in 24 hours +#define SPLACTION_LAST_WEEKS_VALUE 604800 // number of seconds in 7 days +#define SPLACTION_LAST_MONTHS_VALUE 2628000 // number of seconds in 30.4167 days ~= 1 month + +// Hey, why limit ourselves to what iTunes can do? If the iPod can deal with it, excellent! +#define SPLACTION_LAST_SECONDS_RULE 1 // one second +#define SPLACTION_LAST_HOURS_VALUE 3600 // number of seconds in 1 hour +#define SPLACTION_LAST_MINUTES_VALUE 60 // number of seconds in 1 minute +#define SPLACTION_LAST_YEARS_VALUE 31536000 // number of seconds in 365 days + +// fun ones.. Near as I can tell, all of these work. It's open like that. :) +#define SPLACTION_LAST_LUNARCYCLE_VALUE 2551443 // a "lunar cycle" is the time it takes the moon to circle the earth +#define SPLACTION_LAST_SIDEREAL_DAY 86164 // a "sidereal day" is time in one revolution of earth on its axis +#define SPLACTION_LAST_SWATCH_BEAT 86 // a "swatch beat" is 1/1000th of a day.. search for "internet time" on google +#define SPLACTION_LAST_MOMENT 90 // a "moment" is 1/40th of an hour, or 1.5 minutes +#define SPLACTION_LAST_OSTENT 600 // an "ostent" is 1/10th of an hour, or 6 minutes +#define SPLACTION_LAST_FORTNIGHT 1209600 // a "fortnight" is 14 days +#define SPLACTION_LAST_VINAL 1728000 // a "vinal" is 20 days +#define SPLACTION_LAST_QUARTER 7889231 // a "quarter" is a quarter year +#define SPLACTION_LAST_SOLAR_YEAR 31556926 // a "solar year" is the time it takes the earth to go around the sun +#define SPLACTION_LAST_SIDEREAL_YEAR 31558150 // a "sidereal year" is the time it takes the earth to reach the same point in space again, compared to the stars + + +// Smartlist fields - Used for rules. +#define SPLFIELD_SONG_NAME 0x02 // String +#define SPLFIELD_ALBUM 0x03 // String +#define SPLFIELD_ARTIST 0x04 // String +#define SPLFIELD_BITRATE 0x05 // Int (e.g. from/to = 128) +#define SPLFIELD_SAMPLE_RATE 0x06 // Int (e.g. from/to = 44100) +#define SPLFIELD_YEAR 0x07 // Int (e.g. from/to = 2004) +#define SPLFIELD_GENRE 0x08 // String +#define SPLFIELD_KIND 0x09 // String +#define SPLFIELD_DATE_MODIFIED 0x0a // Int/Mac Timestamp (e.g. from/to = bcf93280 == is before 6/19/2004) +#define SPLFIELD_TRACKNUMBER 0x0b // Int (e.g. from = 1, to = 2) +#define SPLFIELD_SIZE 0x0c // Int (e.g. from/to = 0x00600000 for 6MB) +#define SPLFIELD_TIME 0x0d // Int (e.g. from/to = 83999 for 1:23/83 seconds) +#define SPLFIELD_COMMENT 0x0e // String +#define SPLFIELD_DATE_ADDED 0x10 // Int/Mac Timestamp (e.g. from/to = bcfa83ff == is after 6/19/2004) +#define SPLFIELD_COMPOSER 0x12 // String +#define SPLFIELD_PLAYCOUNT 0x16 // Int (e.g. from/to = 1) +#define SPLFIELD_LAST_PLAYED 0x17 // Int/Mac Timestamp (e.g. from = bcfa83ff (6/19/2004), to = 0xbcfbd57f (6/20/2004)) +#define SPLFIELD_DISC_NUMBER 0x18 // Int (e.g. from/to = 1) +#define SPLFIELD_RATING 0x19 // Int/Stars Rating (e.g. from/to = 60 (3 stars)) +#define SPLFIELD_COMPILATION 0x1f // Int (e.g. is set -> SPLACTION_IS_INT/from=1, is not set -> SPLACTION_IS_NOT_INT/from=1) +#define SPLFIELD_BPM 0x23 // Int (e.g. from/to = 60) +#define SPLFIELD_GROUPING 0x27 // String +#define SPLFIELD_PLAYLIST 0x28 // XXX - Unknown...not parsed correctly...from/to = 0xb6fbad5f for "Purchased Music". Extra data after "to"... +#define SPLFIELD_VIDEO_KIND 0x3C // Logic Int (???) +#define SPLFIELD_TVSHOW 0x3E // Int +#define SPLFIELD_SEASON_NR 0x3F // Int +#define SPLFIELD_SKIPCOUNT 0x44 // Int +#define SPLFIELD_ALBUMARTIST 0x47 // string + +#define SPLDATE_IDENTIFIER 0x2dae2dae2dae2dae + + +// MHOD Type 52 types +#define TYPE52_SONG_NAME 0x03 +#define TYPE52_ARTIST 0x05 +#define TYPE52_ALBUM 0x04 +#define TYPE52_GENRE 0x07 +#define TYPE52_COMPOSER 0x12 + +static const uint32_t FILETYPE_M4A=0x4d344120; +static const uint32_t FILETYPE_MP3=0x4d503320; +static const uint32_t FILETYPE_WAV=0x57415620; + +// useful functions +time_t mactime_to_wintime (const unsigned long mactime); +unsigned long wintime_to_mactime (const __time64_t time); +char * UTF16_to_char(wchar_t * str, int length); + + +// Pre-declare iPod_* classes +class iPod_mhbd; +class iPod_mhsd; +class iPod_mhlt; +class iPod_mhit; +class iPod_mhlp; +class iPod_mhyp; +class iPod_slst; +class iPod_mhip; +class iPod_mhod; +class iPod_mqed; +class iPod_mhpo; +class iPod_pqed; +class iPod_mhla; + +// Maximum string length that iTunes writes to the database +#define SPL_MAXSTRINGLENGTH 255 + + +// a struct to hold smart playlist rules in mhods +struct SPLRule +{ + SPLRule() : + field(0), + action(0), + length(0), + fromvalue(0), + fromdate(0), + fromunits(0), + tovalue(0), + todate(0), + tounits(0), + unk1(0), + unk2(0), + unk3(0), + unk4(0), + unk5(0) + { + memset(string, 0, sizeof(string)); + } + + void SetString(const wchar_t *value) + { + if(value) + { + lstrcpynW(string, value, SPL_MAXSTRINGLENGTH); + length = lstrlenW(string) * 2; + } + else + { + memset(string, 0, sizeof(string)); + length = 0; + } + } + + unsigned long field; + unsigned long action; + unsigned long length; + wchar_t string[SPL_MAXSTRINGLENGTH + 1]; + + // from and to are pretty stupid.. if it's a date type of field, then + // value = 0x2dae2dae2dae2dae, + // date = some number, like 2 or -2 + // units = unit in seconds, like 604800 = a week + // but if this is actually some kind of integer comparison, like rating = 60 (3 stars) + // value = the value we care about + // date = 0 + // units = 1 + // So we leave these as they are, and will just deal with it in the rules functions. + uint64_t fromvalue; + int64_t fromdate; + uint64_t fromunits; + uint64_t tovalue; + int64_t todate; + uint64_t tounits; + unsigned long unk1; + unsigned long unk2; + unsigned long unk3; + unsigned long unk4; + unsigned long unk5; +}; + + +// PCEntry: Play Count struct for the entries in iPod_mhdp +struct PCEntry +{ + unsigned long playcount; + unsigned long lastplayedtime; + unsigned long bookmarktime; + unsigned long stars; + uint32_t unk1; + uint32_t skipcount; + uint32_t skippedtime; +}; + + +/************************************** + iTunesDB Database Layout + + MHBD (Database) + | + |-MHSD (Data Set) + | | + | |-MHLT (Track List) + | | | + | | |-MHIT (Track Item) + | | | | + | | | |-MHOD (Description Object) + | | | |-MHOD + | | | | ... + | | | + | | |-MHIT + | | | | + | | | |-MHOD + | | | |-MHOD + | | | | ... + | | | + | | |-... + | + | + |-MHSD + | | + | |-MHLP (Playlists List) + | | | + | | |-MHYP (Playlist) + | | | | + | | | |-MHOD + | | | |-MHIP (Playlist Item) + | | | | ... + | | | + | | |-MHYP + | | | | + | | | |-MHOD + | | | |-MHIP + | | | | ... + | | | + | | |-... + +**************************************/ + + +// base class, not used directly +class iPodObj +{ +public: + iPodObj(); + virtual ~iPodObj(); + + // parse function is required in all subclasses + // feed it a iTunesDB, it creates an object hierarchy + virtual long parse(const uint8_t *data) = 0; + + // write function is required too + // feed it a buffer and the size of the buffer, it fills it with an iTunesDB + // return value is size of the resulting iTunesDB + // return of -1 means the buffer was too small + virtual long write(uint8_t * data, const unsigned long datasize) = 0; + + unsigned long size_head; + unsigned long size_total; +}; + + +// MHBD: The database - parent of all items +class iPod_mhbd : public iPodObj +{ +public: + iPod_mhbd(); + virtual ~iPod_mhbd(); + + virtual long parse(const uint8_t *data); + virtual long write(uint8_t * data, const unsigned long datasize); + virtual long write(uint8_t * data, const unsigned long datasize, uint8_t * fwid); + + uint32_t unk1; + uint32_t dbversion; + uint32_t children; + uint64_t id; + uint16_t platform; + uint16_t language; + uint64_t library_id; + uint32_t unk80; + uint32_t unk84; + int32_t timezone; // in seconds + uint16_t audio_language; + uint16_t subtitle_language; + uint16_t unk164; + uint16_t unk166; + uint16_t unk168; + + iPod_mhsd *mhsdsongs; + iPod_mhsd *mhsdplaylists; + iPod_mhsd *mhsdsmartplaylists; +}; + + +// MHSD: List container - parent of MHLT or MHLP, child of MHBD +class iPod_mhsd : public iPodObj +{ +public: + iPod_mhsd(); + iPod_mhsd(int newindex); + virtual ~iPod_mhsd(); + + virtual long parse(const uint8_t *data); + virtual long write(unsigned char * data, const unsigned long datasize) {return write(data,datasize,1);} + virtual long write(unsigned char * data, const unsigned long datasize, int index); + + uint32_t index; // 1 = mhlt, 3 = mhlp, 2 = legacy mhlp, 4 = album list, 5 = mhlp_smart + iPod_mhlt * mhlt; + iPod_mhlp * mhlp; + iPod_mhlp * mhlp_smart; + iPod_mhla * mhla; +}; + +class iPod_mhia : public iPodObj +{ +public: + iPod_mhia(); + virtual ~iPod_mhia(); + + virtual long parse(const uint8_t *data); + virtual long write(unsigned char * data, const unsigned long datasize); + + uint16_t unk1; + uint16_t albumid; + uint64_t dbid; + uint32_t type; + + std::vector<iPod_mhod*> mhod; +}; + +class ArtistAlbumPair +{ +public: + const wchar_t* artist; + const wchar_t* album; + ArtistAlbumPair() : artist(0), album(0) {} + ArtistAlbumPair(const wchar_t* artist, const wchar_t* album) : artist(artist), album(album) {} + /*bool operator < (const ArtistAlbumPair& that) const + { + int yy = _wcsicmp(artist, that.artist); + if(yy) return yy < 0; + return _wcsicmp(album, that.album) < 0; + }*/ +}; + +struct ArtistAlbumPairComparer +{ + int operator ()(const ArtistAlbumPair &a, const ArtistAlbumPair &b) const + { + int yy = _wcsicmp(a.artist, b.artist); + if(yy) return yy; + return _wcsicmp(a.album, b.album); + } +}; + +class iPod_mhla : public iPodObj +{ +public: + iPod_mhla(); + virtual ~iPod_mhla(); + + virtual long parse(const uint8_t *data); + virtual long write(unsigned char * data, const unsigned long datasize); + uint16_t GetAlbumId(const wchar_t* artist, const wchar_t* album); + void ClearAlbumsList(); + + //typedef std::map<ArtistAlbumPair, uint16_t> albums_map_t; + typedef std::map<ArtistAlbumPair, uint16_t, ArtistAlbumPairComparer> albums_map_t; + albums_map_t albums; + uint16_t nextAlbumId; +}; + + +// MHLT: song list container - parent of MHIT, child of MHSD +class iPod_mhlt : public iPodObj +{ +public: + typedef std::map<uint32_t, iPod_mhit*> mhit_map_t; + //typedef std::map<unsigned long, iPod_mhit*> mhit_map_t; // Map the unique mhit.id to a mhit object + typedef mhit_map_t::value_type mhit_value_t; + + iPod_mhlt(); + virtual ~iPod_mhlt(); + + virtual long parse(const uint8_t *data); + virtual long write(unsigned char * data, const unsigned long datasize); + + const unsigned long GetChildrenCount() const { return mhit.size(); } + + // returns a pointer to the new iPod_mhit object in the track list, which you edit directly + iPod_mhit *NewTrack(); + void AddTrack(iPod_mhit *new_track); + + // takes a position index, returns a pointer to the track itself, or NULL if the index isn't found. + iPod_mhit *GetTrack(uint32_t index) const; + + // searches for a track based on the track's id number (mhit.id). returns mhit pointer, or NULL if the id isn't found. + iPod_mhit * GetTrackByID(const unsigned long id); + + // couple of ways to delete a track + bool DeleteTrack(const unsigned long index); + bool DeleteTrackByID(const unsigned long id); + + // clears out the tracklist + bool ClearTracks(const bool clearMap = true); + + // the map of the tracks themselves + mhit_map_t mhit; + std::vector<uint32_t> mhit_indexer; + + uint32_t GetNextID(); + +private: + volatile uint32_t next_mhit_id; +}; + + +// MHIT: song item - parent of MHOD, child of MHLT +class iPod_mhit : public iPodObj +{ +public: + iPod_mhit(); + virtual ~iPod_mhit(); + + virtual long parse(const uint8_t *data); + virtual long write(unsigned char * data, const unsigned long datasize); + + const unsigned long GetChildrenCount() const { return(mhod.size()); } + + // will add a new mhod string to the mhit + // optional: pass in a type to get an existing string, if there is one, + // or a new one with the type filled in already, if there is not one + iPod_mhod * AddString(const int type=0); + + // Find a string by type + iPod_mhod * FindString(const unsigned long type) const; + + // deletes a string from the track + // if more than one string of given type exists, all of that type will be deleted, + // to ensure consistency. Pointers to these strings will be invalid after this. + // return val is how many strings were deleted + unsigned long DeleteString(const unsigned long type); + + // Creates a copy of the mhit. The operator = is overloaded so you can + // more easily copy mhit's. + static void Duplicate(const iPod_mhit *src, iPod_mhit *dst); + iPod_mhit& operator=(const iPod_mhit& src); + + int GetRating() { return stars/20; } + void SetRating(int rating) { stars=rating*20; } + + int GetEQSetting(); + void SetEQSetting(int value); + + unsigned int GetFileTypeID(const wchar_t *filename); + + + uint32_t id; + uint32_t visible; // 0x01 means the song shows up on the iPod, all other values means it is hidden + uint32_t filetype; // MP3 = 0x4d503320, M4A = 0x4d344120, M4B = 0x4d344220, M4P = 0x4d345020, WAV = 0x57415620, AA = ??? + uint8_t vbr; + uint8_t type; + uint8_t compilation; + uint8_t stars; + uint32_t lastmodifiedtime; // iTunes sets this the UTC time value for the Windows Last Modified timestamp + uint32_t size; + uint32_t length; + uint32_t tracknum; + uint32_t totaltracks; + uint32_t year; + uint32_t bitrate; + uint16_t samplerate; + uint16_t samplerate_fixedpoint; + uint32_t volume; + uint32_t starttime; + uint32_t stoptime; + uint32_t soundcheck; + uint32_t playcount; + uint32_t playcount2; // Seems to always be the same as playcount(?!?) + uint32_t lastplayedtime; + uint32_t cdnum; + uint32_t totalcds; + uint32_t userID; // Apple Store User ID + uint32_t addedtime; // iTunes sets this to the UTC time value for when the file was added to the iTunes library + uint32_t bookmarktime; + uint64_t dbid; // 64 bit value that identifies this mhit across iPod databases. iTunes increments this by 1 for each additional song. (previously unk7 and unk8) + uint32_t BPM; + uint32_t app_rating; // The rating set by the application, as opposed to the rating set on the iPod itself + uint8_t checked; // a "checked" song has the value of 0, a non-checked song is 1 + uint16_t unk9; // Seems to always be 0xffff... + uint16_t artworkcount; // Number of artwork files attached to this song + uint32_t artworksize; // Size of all artwork files attached to this song, in bytes. (was unk10); + uint32_t unk11; + float samplerate2; + uint32_t releasedtime; + uint32_t unk14; + uint32_t unk15; + uint32_t unk16; + /* --- */ + uint32_t skipcount; + uint32_t skippedtime; + uint8_t hasArtwork; + uint8_t skipShuffle; + uint8_t rememberPosition; + uint8_t unk19; + uint64_t dbid2; // same as dbid? + uint8_t lyrics_flag; + uint8_t movie_flag; + uint8_t mark_unplayed; + uint8_t unk20; + uint32_t unk21; + uint32_t pregap; + uint64_t samplecount; + uint32_t unk25; + uint32_t postgap; + uint32_t unk27; + uint32_t mediatype; + uint32_t seasonNumber; + uint32_t episodeNumber; + uint32_t unk31; + uint32_t unk32; + uint32_t unk33; + uint32_t unk34; + uint32_t unk35; + uint32_t unk36; + /* --- */ + uint32_t unk37; + uint32_t gaplessData; + uint32_t unk39; + uint16_t albumgapless; + uint16_t trackgapless; + uint32_t unk40; + uint32_t unk41; + uint32_t unk42; + uint32_t unk43; + uint32_t unk44; + uint32_t unk45; + uint32_t unk46; + uint32_t album_id; + uint32_t unk48; + uint32_t unk49; + uint32_t unk50; + uint32_t unk51; + uint32_t unk52; + uint32_t unk53; + uint32_t unk54; + uint32_t unk55; + uint32_t unk56; + + /* --- */ + // 22 bytes of unknown (we'll just write back zeroes) + + uint32_t mhii_link; // TODO: benski> figure this thing out + // 32 more bytes of unknown (we'll just write back zeroes) + +/* benski> this is a hack. i'm putting this in here so we can retrieve album art from the transfer thread and add it in the main thread +it doesn't really belong as part of this object, though! */ + + // protect these members, so stuff doesn't fuck up my cache +protected: + std::vector<iPod_mhod*> mhod; + iPod_mhod * mhodcache[25]; +}; + + +// MHLP: playlist container - parent of MHYP, child of MHSD + +// Important note: Playlist zero must always be the default playlist, containing every +// track in the DB. To do this, always call "GetDefaultPlaylist()" before you create any +// other playlists, if you start from scratch. +// After you're done adding/deleting tracks in the database, and just before you call +// write(), do the following: GetDefaultPlaylist()->PopulatePlaylist(ptr_to_mhlt); + +class iPod_mhlp : public iPodObj +{ +public: + iPod_mhlp(); + virtual ~iPod_mhlp(); + + virtual long parse(const uint8_t *data); + virtual long write(unsigned char * data, const unsigned long datasize) {return write(data,datasize,3);} + virtual long write(unsigned char * data, const unsigned long datasize, int index); + + const unsigned long GetChildrenCount() const { return mhyp.size(); } + + // returns a new playlist for you + iPod_mhyp * AddPlaylist(); + + // gets a playlist + iPod_mhyp * GetPlaylist(const unsigned long pos) const { return mhyp.at(pos); } + + // finds a playlist by its ID + iPod_mhyp * FindPlaylist(const uint64_t playlistID); + + // deletes the playlist at a position + bool DeletePlaylist(const unsigned long pos); + + // deletes the playlist matching the ID + bool DeletePlaylistByID(const uint64_t playlistID); + + // gets the default playlist ( GetPlaylist(0); ) + // if there are no playlists yet (empty db), then it creates the default playlist + // and returns a pointer to it + iPod_mhyp * GetDefaultPlaylist(); + + // erases all playlists, including the default one, so be careful here. + // Set createDefaultPlaylist to create a new, empty default playlist + bool ClearPlaylists(const bool createDefaultPlaylist = false); + + // Goes through all playlists and removed any songs that are no longer in the MHLT + void RemoveDeadPlaylistEntries(iPod_mhlt *mhlt); + + std::vector<iPod_mhyp*> mhyp; + + void SortPlaylists(); + +private: + bool beingDeleted; +}; + +int STRCMP_NULLOK(const wchar_t *pa, const wchar_t *pb); + +// MHYP: playlist - parent of MHOD or MHIP, child of MHLP +class iPod_mhyp : public iPodObj +{ +public: + iPod_mhyp(); + virtual ~iPod_mhyp(); + + virtual long parse(const uint8_t *data); + virtual long write(unsigned char * data, const unsigned long datasize) {return write(data,datasize,3);} + virtual long write(unsigned char * data, const unsigned long datasize, int index); + + bool IsSmartPlaylist(void) const { return(isSmartPlaylist); } + + // add an entry to the playlist. Creates a new entry, returns the position in the vector + // optionally fills in the songindex for you, with the ID from a track you might have + long AddPlaylistEntry(iPod_mhip * entry, const unsigned long id=0); + + // give it a song id, it'll return a position in the playlist + // -1, as always, means not found + // if the same entry is in the playlist multiple times, this only gives back the first one + long FindPlaylistEntry(const unsigned long id) const; + + // get an mhip given its position + iPod_mhip * GetPlaylistEntry(const unsigned long pos) const { return mhip.at(pos); } + + // deletes an entry from the playlist. Pointers to that entry become invalid + bool DeletePlaylistEntry(const unsigned long pos); + + // Removes all playlist entries matching the songindex parameter + bool DeletePlaylistEntryByID(unsigned long songindex); + + // clears a playlist of all mhip entries + bool ClearPlaylist(); + + // populates a playlist to be the same as a track list you pass into it. + // Mainly only useful for building the default playlist after you add/delete tracks + // GetDefaultPlaylist()->PopulatePlaylist(ptr_to_mhlt); + // for example... + long PopulatePlaylist(iPod_mhlt * tracks, int hidden_field=1); + + // will add a new string to the playlist + // optional: pass in a type to get an existing string, if there is one, + // or a new one with the type filled in already, if there is not one + iPod_mhod * AddString(const int type=0); + + // get an mhod given it's type.. Only really useful with MHOD_TITLE here, until + // smartlists get worked out better + iPod_mhod * FindString(const unsigned long type); + + // deletes a string from the playlist + // if more than one string of given type exists, all of that type will be deleted, + // to ensure consistency. Pointers to these strings will be invalid after this. + // ret val is number of strings removed + unsigned long DeleteString(const unsigned long type); + + void SetPlaylistTitle(const wchar_t *string); + + const unsigned long GetMhodChildrenCount() const { return mhod.size(); } + const unsigned long GetMhipChildrenCount() const { return mhip.size(); } + + static void Duplicate(iPod_mhyp *src, iPod_mhyp *dst); + + unsigned long hidden; + unsigned long timestamp; + uint64_t playlistID; // ID of the playlist, used in smart playlist rules + unsigned long unk3; + unsigned short numStringMHODs; + unsigned short podcastflag; + unsigned long numLibraryMHODs; + + std::vector<iPod_mhod*> mhod; + std::vector<iPod_mhip*> mhip; + + struct indexMhit + { + __forceinline bool operator()(indexMhit*& one, indexMhit*& two) + { +#define RETIFNZ(x) { int yy = x; if(yy != 0) return yy < 0; } + //return(STRCMP_NULLOK(one->str.c_str(), two->str.c_str()) < 0 ? true : false); + RETIFNZ(STRCMP_NULLOK(one->str[0],two->str[0])); + RETIFNZ(STRCMP_NULLOK(one->str[1],two->str[1])); + RETIFNZ(STRCMP_NULLOK(one->str[2],two->str[2])); + RETIFNZ(one->track - two->track); + RETIFNZ(STRCMP_NULLOK(one->str[3],two->str[3])); + return true; +#undef RETIFNZ + } + + unsigned int index; + const wchar_t *str[4]; + int track; + }; + + iPod_mhlt::mhit_map_t *mhit; + std::vector<uint32_t> mhit_indexer; + bool writeLibraryMHODs; + + bool operator()(iPod_mhyp*& one, iPod_mhyp*& two); + +protected: + bool isSmartPlaylist; + bool isPopulated; +}; + + +// MHIP: playlist item - child of MHYP +class iPod_mhip : public iPodObj +{ +public: + iPod_mhip(); + virtual ~iPod_mhip(); + + virtual long parse(const uint8_t *data); + virtual long write(unsigned char * data, const unsigned long datasize) { return write(data,datasize,0); } + virtual long write(unsigned char * data, const unsigned long datasize, int entrynum); + + static void Duplicate(iPod_mhip *src, iPod_mhip *dst); + + unsigned long dataobjectcount; // was unk1 + unsigned long podcastgroupflag; // was corrid + unsigned long groupid; // was unk2 + unsigned long songindex; + unsigned long timestamp; + unsigned long podcastgroupref; + std::vector<iPod_mhod*> mhod; +}; + +// MHOD: string container item, child of MHIT or MHYP +// MHOD: string container item, child of MHIT or MHYP +class iPod_mhod : public iPodObj +{ +public: + iPod_mhod(); + virtual ~iPod_mhod(); + + virtual long parse(const uint8_t *data); + virtual long write(unsigned char * data, const unsigned long datasize); + + void SetString(const wchar_t *string); + + static void Duplicate(iPod_mhod *src, iPod_mhod *dst); + + static bool IsSimpleStringType(const unsigned int type); + + uint32_t type; + uint32_t unk1; + uint32_t unk2; + + // renamed this from corrid.. all it is is a position in the playlist + // for type 100 mhods that come immediately after mhips. + // for strings, this is the encoded type. 1 == UTF-16, 2 == UTF-8 + union + { + uint32_t position; + uint32_t encoding_type; + }; + + uint32_t length; + uint32_t unk3; + uint32_t unk4; + + // string mhods get the string put here, unaltered, still byte reversed + // Use unicode functions to work with this string. + wchar_t *str; + + // mhod types 50 and up get the whole thing put here. + // until I can figure out all of these, I won't bother to try to recreate them + // and i'll just copy them back as needed when rewriting the iTunesDB file. + uint8_t * binary; + + // stuff for type 50 mhod + uint8_t liveupdate; // "Live Updating" check box + uint8_t checkrules; // "Match X of the following conditions" check box + uint8_t checklimits; // "Limit To..." check box. 1 = checked, 0 = not checked + uint8_t matchcheckedonly; // "Match only checked songs" check box. + uint8_t limitsort_opposite; // Limit Sort rule is reversed (e.g. limitsort == LIMIT_HIGHEST_RATING really means LIMIT_LOWEST_RATING...quite weird...) + uint32_t limittype; // See Limit Types defines above + uint32_t limitsort; // See Limit Sort defines above + uint32_t limitvalue; // Whatever value you type next to "limit type". + + // stuff for type 51 mhod + uint32_t unk5; // not sure, probably junk data + uint32_t rules_operator; // "All" (logical AND / value = 0) or "Any" (logical OR / value = 1). + std::vector<SPLRule*> rule; + + bool parseSmartPlaylists; +}; + + +// Smart Playlist. A smart playlist doesn't act different from a regular playlist, +// except that it contains a type 50 and type 51 MHOD. But deriving the iPod_slst +// class makes sense, since there are a lot of functions that are only appropriate +// for smart playlists, and it can guarantee that a type 50 and 51 MHOD will always +// be available. +class iPod_slst : public iPod_mhyp +{ +public: + enum FieldType + { + ftString, + ftInt, + ftBoolean, + ftDate, + ftPlaylist, + ftUnknown, + ftBinaryAnd, + }; + + enum ActionType + { + atString, + atInt, + atBoolean, + atDate, + atRange, + atInTheLast, + atPlaylist, + atNone, + atInvalid, + atUnknown, + atBinaryAnd, + }; + + + iPod_slst(); + virtual ~iPod_slst(); + + iPod_mhod* GetPrefs(void) { UpdateMHODPointers(); return(splPref); } + void SetPrefs(const bool liveupdate = true, const bool rules_enabled = true, const bool limits_enabled = false, + const unsigned long limitvalue = 0, const unsigned long limittype = 0, const unsigned long limitsort = 0); + + static FieldType GetFieldType(const unsigned long field); + static ActionType GetActionType(const unsigned long field, const unsigned long action); + + static uint64_t ConvertDateValueToNum(const uint64_t val) { return(-(int64_t)val); } + static uint64_t ConvertNumToDateValue(const uint64_t val) { return(-(int64_t)val); } + + // returns a pointer to the SPLDATA mhod + iPod_mhod* GetRules() { UpdateMHODPointers(); return(splData); } + + // get the number of rules in the smart playlist + unsigned long GetRuleCount(); + + // Returns rule number (0 == first rule, -1 == error) + int AddRule(const unsigned long field, + const unsigned long action, + const wchar_t * string = NULL, // use string for string based rules + const uint64_t value = 0, // use value for single variable rules + const uint64_t from = 0, // use from and to for range based rules + const uint64_t to = 0, + const uint64_t units = 0); // use units for "in the last" based rules + + int AddRule(const SPLRule& rule); + + void RemoveAllRules(void); + + // populates a smart playlist + // Pass in the mhlt with all the songs on the iPod, and it populates the playlist + // given those songs and the current rules + // Return value is number of songs in the resulting playlist. + long PopulateSmartPlaylist(iPod_mhlt * tracks, iPod_mhlp * playlists); + + // used in PopulateSmartPlaylist + static bool EvalRule( + SPLRule * r, + iPod_mhit * track, + iPod_mhlt * tracks = NULL, // if you're going to allow playlist type rules + iPod_mhlp * playlists = NULL // these are required to be passed in + ); + + // Restore default prefs and remove all rules + void Reset(void); + +protected: + void UpdateMHODPointers(void); + + iPod_mhod *splPref; + iPod_mhod *splData; +}; + + + + +// MHDP: Play Count class +class iPod_mhdp +{ +public: + iPod_mhdp(); + ~iPod_mhdp(); + unsigned long size_head; + unsigned long entrysize; + + const unsigned long GetChildrenCount() const { return children; } + + // return value is number of songs or -1 if error. + // you should probably check to make sure the number of songs is the same + // as the number of songs you read in from parsing the iTunesDB + virtual long parse(const uint8_t *data); + + // there is no write() function because there is no conceivable need to ever write a + // play counts file. + + const PCEntry &GetPlayCount(const unsigned int pos) const { return entry[pos]; } + + // playcounts are stored in the Play Counts file, in the same order as the mhits are + // stored in the iTunesDB. So you should apply the changes from these entries to the + // mhits in order and then probably delete the Play Counts file entirely to prevent + // doing it more than once. + PCEntry *entry; + uint32_t children; +}; + + + + +// MHPO: On-The-Go Playlist class +class iPod_mhpo +{ +public: + iPod_mhpo(); + virtual ~iPod_mhpo(); + + unsigned long size_head; + unsigned long unk1; + unsigned long unk2; // this looks like a timestamp, sorta + + const unsigned long GetChildrenCount() const { return children; } + + virtual long parse(const uint8_t *data); + virtual long write(unsigned char * data, const unsigned long datasize); + + // This will create a new playlist from the OTGPlaylist.. + // Give it the DB to create the playlist in and from, and a name for the playlist. + // Return value is a pointer to the playlist itself, which will be inside the DB you + // give to it as well. + // Returns NULL on error (can't create the playlist) + iPod_mhyp * CreatePlaylistFromOTG(iPod_mhbd * iPodDB, wchar_t * name); + + // OTGPlaylists are stored in the OTGPlaylist file. When iTunes copies them into a + // new playlist, it deletes the file afterwards. I do not know if creating this file + // will make the iPod have an OTGPlaylist after you undock it. I added the write function + // anyway, in case somebody wants to try it. Not much use for it though, IMO. + uint32_t *idList; + uint32_t children; +}; + + +// MQED: EQ Presets holder +class iPod_mqed +{ +public: + iPod_mqed(); + virtual ~iPod_mqed(); + + unsigned long size_head; + unsigned long unk1; + unsigned long unk2; // this looks like a timestamp, sorta + + const unsigned long GetChildrenCount() const { return eqList.size(); } + + virtual long parse(const uint8_t *data); + virtual long write(unsigned char * data, const unsigned long datasize); + + std::vector<iPod_pqed*> eqList; +}; + +// PQED: A single EQ Preset +class iPod_pqed +{ +public: + iPod_pqed(); + virtual ~iPod_pqed(); + + unsigned long length; // length of name string + wchar_t * name; // name string + +/* + 10 band eq is not exactly what iTunes shows it to be.. It really is these: + 32Hz, 64Hz, 128Hz, 256Hz, 512Hz, 1024Hz, 2048Hz, 4096Hz, 8192Hz, 16384Hz + + Also note that although these are longs, The range is only -1200 to +1200. That's dB * 100. +*/ + + signed long preamp; // preamp setting + + signed long eq[10]; // iTunes shows 10 bands for EQ presets + signed long short_eq[5]; // This is a 5 band version of the same thing (possibly what the iPod actually uses?) + + virtual long parse(const uint8_t *data); + virtual long write(unsigned char * data, const unsigned long datasize); +}; + + + + +struct iTunesStatsEntry +{ + unsigned int GetBookmarkTimeInMilliseconds() { if(bookmarktime == 0xffffff) return(0); return(bookmarktime * 256); } + + // These are 3 byte values + unsigned int entry_size; + unsigned int bookmarktime; // In 0.256 seconds units + unsigned int unk1; // Somehow associated with bookmark time + unsigned int unk2; + unsigned int playcount; + unsigned int skippedcount; +}; + +class iTunesStats +{ +public: + iTunesStats(); + ~iTunesStats(); + + virtual long parse(const uint8_t *data); + virtual long write(unsigned char * data, const unsigned long datasize); + + const unsigned long GetChildrenCount() const { return children; } + const iTunesStatsEntry &GetEntry(const unsigned int pos) const { return entry[pos];} + + // This is a 3 byte value + unsigned int unk1; + + iTunesStatsEntry *entry; + uint32_t children; + iPod_mhlt *mhlt; +}; + + +class iTunesShuffle +{ +public: + iTunesShuffle(); + ~iTunesShuffle(); + + virtual long parse(const uint8_t *data); + virtual long write(unsigned char *data, const unsigned long datasize); + + unsigned int GetChildrenCount() const { return numentries; } + unsigned int GetEntry(const unsigned int pos) const { return entry[pos]; } + //void AddEntry(const unsigned int index) { entry.push_back(index); } + void Randomize(); + void Randomize(const unsigned int numsongs); + + uint32_t *entry; + uint32_t numentries; + unsigned int datasize; +}; +#endif diff --git a/Src/Plugins/Portable/pmp_ipod/iPodDevice.cpp b/Src/Plugins/Portable/pmp_ipod/iPodDevice.cpp new file mode 100644 index 00000000..60b5e2aa --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/iPodDevice.cpp @@ -0,0 +1,1795 @@ +#include "iPodDevice.h" +//#include <assert.h> +#include "..\..\General\gen_ml/itemlist.h" +#include "../nu/AutoWide.h" +#include "../nu/AutoChar.h" +#include "../Winamp/wa_ipc.h" +#include <math.h> +#include "../Agave/Language/api_language.h" +#include "api.h" +#include <tataki/bitmap/bitmap.h> +#include <tataki/canvas/bltcanvas.h> +#include "iPodSD.h" +#include <strsafe.h> +#include <shlwapi.h> +//#include "../nu/combobox.h" +// needed to query for replaygain stuff + +static const GUID playbackConfigGroupGUID = +{ 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } }; + +extern PMPDevicePlugin plugin; +extern time_t mactime_to_wintime (const unsigned long mactime); +extern unsigned long wintime_to_mactime (const __time64_t time); +extern wchar_t* UTF8_to_UTF16(char *str); +extern BOOL EjectVolume(TCHAR cDriveLetter); + +extern std::vector<iPodDevice*> iPods; + +static __int64 fileSize(const wchar_t * filename); + +iPodDevice::iPodDevice(char deviceDrive) +{ + fwid=0; + info=0; + artdb=0; + gapscanner=0; + transcoder=0; + image16 = 0; + image160 = 0; + drive = deviceDrive; + driveW = ((int)(drive - 'A')) + L'A'; + transferQueueLength=0; + db=NULL; + srand(GetTickCount()); + dirnum = rand() % 20; + + + { + wchar_t artwork[] = {driveW,L":\\iPod_Control\\Artwork"}; + _wmkdir(artwork); + wchar_t device[] = {driveW,L":\\iPod_Control\\Device"}; + _wmkdir(device); + } + + iPods.push_back(this); + + pmpDeviceLoading load; + load.dev = this; + load.UpdateCaption = NULL; + SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)&load,PMP_IPC_DEVICELOADING); + + if(load.UpdateCaption) + { + load.UpdateCaption(WASABI_API_LNGSTRINGW(IDS_IPOD_LOADING),load.context); + } + + info = GetiPodInfo(driveW); + + // Get the artwork formats that are supported + if(info && info->numberOfSupportedFormats >0) + { + // formats are already available, read from the sysinfo xml + // just use them + for (int i=0; i<info->numberOfSupportedFormats; i++) + { + thumbs.push_back(&info->supportedArtworkFormats[i]); + } + } + else + { + // revert to the static list of supported artwork formats + const ArtworkFormat* art = GetArtworkFormats(info); + if(art) for(int i=0; art[i].type != THUMB_INVALID; i++) + { + if(art[i].type >= THUMB_COVER_SMALL && art[i].type <= THUMB_COVER_LARGE) + thumbs.push_back(&art[i]); + } + } + + if(!info || parseiTunesDB(thumbs.size()!=0) < 0) + { + //iPods.eraseObject(this); + auto it = std::find(iPods.begin(), iPods.end(), this); + if (it != iPods.end()) + { + iPods.erase(it); + } + + SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICEDISCONNECTED); + delete this; + return; + } + + image16 = info->image16; + image160 = info->image160; + + db->mhsdplaylists->mhlp->SortPlaylists(); + + int n = db->mhsdplaylists->mhlp->GetChildrenCount(); + for(int i=0; i<n; i++) + playlists.Add(db->mhsdplaylists->mhlp->GetPlaylist(i)); + + + SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICECONNECTED); + + transcoder = (Transcoder*)SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(WPARAM)this,PMP_IPC_GET_TRANSCODER); + if(transcoder) + { + transcoder->AddAcceptableFormat(mmioFOURCC('M','4','A',' ')); + transcoder->AddAcceptableFormat(L"mp3"); + //transcoder->AddAcceptableFormat(L"wav"); + transcoder->AddAcceptableFormat(L"m4v"); + transcoder->AddAcceptableFormat(L"m4b"); + transcoder->AddAcceptableFormat(L"aa\0\0"); + transcoder->AddAcceptableFormat(L"mp4"); + } + if (info->fwid) + { + fwid = (uint8_t *)malloc(8); + memcpy(fwid, info->fwid, 8); + } +} + +iPodDevice::~iPodDevice() { + if(gapscanner) SendMessage(gapscanner,WM_CLOSE,0,0); + if(db) delete db; db=NULL; + + char lockPath[] = {drive, ":\\iPod_Control\\iTunes\\iTunesLock"}; + _unlink(lockPath); + if(transcoder) SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(WPARAM)transcoder,PMP_IPC_RELEASE_TRANSCODER); + delete info; + info=0; + free(fwid); +} + +static unsigned char * readFile(char * path, int &len) { + FILE * f = fopen(path,"rb"); + if(!f) return 0; + fseek(f,0,2); //seek to end + int l = ftell(f); //length of file + unsigned char * data = (unsigned char *)malloc(l); + if(!data) + { + fclose(f); + return 0; + } + fseek(f,0,0); + if(fread(data,1,l,f) != l) { fclose(f); free(data); return 0; } + fclose(f); + len = l; + return data; +} + +static unsigned char * readFile(char * path) { + int l=0; + return readFile(path,l); +} + +static HANDLE iTunesLock(char drive) { // returns false for unable to aquire lock. + char lockPath[] = {drive, ":\\iPod_Control\\iTunes\\iTunesLock"}; + HANDLE h=CreateFileA(lockPath,GENERIC_READ,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); + if(h == INVALID_HANDLE_VALUE) return h; + while(!LockFile(h,0,0,0,0)) Sleep(50); + return h; +} + +static void iTunesUnlock(HANDLE h) { + UnlockFile(h,0,0,0,0); + CloseHandle(h); +} + +int iPodDevice::parseiTunesDB(bool parseArt) { + HANDLE hLock = iTunesLock(drive); + if(hLock == INVALID_HANDLE_VALUE) return -1; + char dbPath[] = "x:\\iPod_Control\\iTunes\\iTunesDB"; + dbPath[0]=drive; + unsigned char * data = readFile(dbPath); + if(data==0) { + iTunesUnlock(hLock); + return -1; + } + db = new iPod_mhbd; + int ret = db->parse(data); + free(data); + bool changed=false; + char playcounts[] = "x:\\iPod_Control\\iTunes\\Play Counts"; + playcounts[0]=drive; + data = readFile(playcounts); + if(data) { + iPod_mhdp * mhdp = new iPod_mhdp; + int l = db->mhsdsongs->mhlt->GetChildrenCount(); + if(mhdp->parse(data) == l) { + changed=true; + for(int i=0; i<l; i++) { + PCEntry p = mhdp->GetPlayCount(i); + iPod_mhit * mhit = db->mhsdsongs->mhlt->GetTrack(i); + if(!mhit) continue; + mhit->bookmarktime = p.bookmarktime; + mhit->lastplayedtime = p.lastplayedtime; + mhit->stars = (unsigned char)p.stars; + mhit->playcount = p.playcount; + mhit->skipcount = p.skipcount; + mhit->skippedtime = p.skippedtime; + } + } + delete mhdp; + free(data); + } + _unlink(playcounts); + db->mhsdplaylists->mhlp->GetPlaylist(0)->mhit = &db->mhsdsongs->mhlt->mhit; + char otg[] = "x:\\iPod_Control\\iTunes\\OTGPlaylistInfo"; + otg[0]=drive; + data = readFile(otg); + if(data) { + iPod_mhpo * mhpo = new iPod_mhpo; + mhpo->parse(data); + mhpo->CreatePlaylistFromOTG(db,L"On The Go"); + changed=true; + delete mhpo; + free(data); + } + _unlink(otg); + iTunesUnlock(hLock); + if(changed) writeiTunesDB(); + + if(parseArt) { + char dbPath[] = "x:\\iPod_Control\\Artwork\\ArtworkDB"; + dbPath[0]=drive; + int l=0; + unsigned char * data = readFile(dbPath,l); + bool createNew=false; + if(data) { + artdb = new ArtDB(); + int r = artdb->parse(data,l,driveW); + if(r<0) { + delete artdb; + artdb=NULL; + } + free(data); + } else createNew=true; + + if(createNew) { + char dir[] = {drive,":\\iPod_Control\\Artwork"}; + CreateDirectoryA(dir,NULL); + artdb = new ArtDB(); + artdb->makeEmptyDB(driveW); + } + } + + return ret; +} + +extern bool ParseSysInfoXML(wchar_t drive_letter, char * xml, int xmllen); + +static unsigned char *GetFwId(wchar_t drive, unsigned char *fwid) +{ + char xml[65536] = {0}; + if(!ParseSysInfoXML(drive, xml, sizeof(xml)/sizeof(char))) return NULL; + char *p = strstr(xml,"<key>FireWireGUID</key>"); + if(!p) return 0; + p = strstr(p,"<string>"); + if(!p) return 0; + p += strlen("<string>"); + for(int i=0; i<8 && *p; i++) { + char num[3]={0,0,0}; + num[0] = *(p++); + num[1] = *(p++); + fwid[i] = (uint8_t)strtoul(num,NULL,16); + } + return fwid; +} + +int iPodDevice::writeiTunesDB() +{ + char dbPath[] = "x:\\iPod_Control\\iTunes\\iTunesDB"; dbPath[0]=drive; + char dbPathOld[] = "x:\\iPod_Control\\iTunes\\iTunesDB.old_mlpmp"; dbPathOld[0]=drive; + char dbPathNew[] = "x:\\iPod_Control\\iTunes\\iTunesDB.new_mlpmp"; dbPathNew[0]=drive; + if(!db) return -1; + HANDLE hLock = iTunesLock(drive); + if(hLock == INVALID_HANDLE_VALUE) return -1; + uint32_t allocate = (uint32_t)fileSize(AutoWide(dbPath)); + int incr = 10000000; + bool done=false; + int i=0; + int ret=0; + unsigned char * data; + + while(!done) + { + if(i++ > 10) + { + iTunesUnlock(hLock); + return -1; + } + allocate += incr; + data = (unsigned char*)malloc(allocate); + if(!data) return -1; //what else can we do? + + // TODO: i'd like to cut this but it seems to still be causing problems to parse it from XML + unsigned char fwid[8]={0}; + GetFwId(driveW, fwid); +#ifdef _DEBUG + if (memcmp(fwid, this->fwid, 8) || memcmp(fwid, info->fwid, 8)) + { + DebugBreak(); + } +#endif + + int len = db->write(data,allocate, fwid); + if(len > 0) { + _unlink(dbPathOld); + _unlink(dbPathNew); + FILE * f = fopen(dbPathNew,"wb"); + if(!f) { + iTunesUnlock(hLock); + return -1; + } + fwrite(data,1,len,f); + fclose(f); + rename(dbPath,dbPathOld); + _unlink(dbPath); + rename(dbPathNew,dbPath); + done=true; + ret=len; + } else free(data); + } + + + if(data) + { + if (info->shadow_db_version == 1) + { + iTunesSD1 sd; + int l = sd.write(&db->mhsdsongs->mhlt->mhit, data,allocate); + if(l>0) + { + char dbPath[] = "x:\\iPod_Control\\iTunes\\iTunesSD"; dbPath[0]=drive; + FILE * f = fopen(dbPath,"wb"); + if(f) { + fwrite(data,1,l,f); + fclose(f); + } + } + } + else if (info->shadow_db_version == 2) + { + iTunesSD2 sd; + int l = sd.write(db->mhsdsongs->mhlt, db->mhsdplaylists->mhlp, data,allocate); + if(l>0) + { + char dbPath[] = "x:\\iPod_Control\\iTunes\\iTunesSD"; dbPath[0]=drive; + FILE * f = fopen(dbPath,"wb"); + if(f) { + fwrite(data,1,l,f); + fclose(f); + } + } + } + } + + if(artdb && data) { + int l = artdb->write(data,allocate); + if(l>0) { + char dbPath[] = "x:\\iPod_Control\\Artwork\\ArtworkDB"; dbPath[0]=drive; + FILE * f = fopen(dbPath,"wb"); + if(f) { + fwrite(data,1,l,f); + fclose(f); + } + } + } + + iTunesUnlock(hLock); + if(data) free(data); + + return ret; +} + +__int64 iPodDevice::getDeviceCapacityAvailable() { + ULARGE_INTEGER tfree={0,}, total={0,}, freeb={0,}; + wchar_t path[4]=L"x:\\"; + path[0]=drive; + GetDiskFreeSpaceEx(path, &tfree, &total, &freeb); + return freeb.QuadPart; +} + +__int64 iPodDevice::getDeviceCapacityTotal() { + ULARGE_INTEGER tfree={0,}, total={0,}, freeb={0,}; + wchar_t path[4]=L"x:\\"; + path[0]=drive; + GetDiskFreeSpaceEx(path, &tfree, &total, &freeb); + return total.QuadPart; +} + +void iPodDevice::Close() +{ + SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICEDISCONNECTED); + //writeiTunesDB(); + + //iPods.eraseObject(this); + auto it = std::find(iPods.begin(), iPods.end(), this); + if (it != iPods.end()) + { + iPods.erase(it); + } + + delete this; +} + +void iPodDevice::Eject() +{ + //iPods.eraseObject(this); + auto it = std::find(iPods.begin(), iPods.end(), this); + if (it != iPods.end()) + { + iPods.erase(it); + } + + if(EjectVolume(drive)) + { + SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICEDISCONNECTED); + delete this; + } + else + { + wchar_t titleStr[32] = {0}; + iPods.push_back(this); + MessageBox(plugin.hwndLibraryParent,WASABI_API_LNGSTRINGW(IDS_FAILED_TO_EJECT_IPOD), + WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,titleStr,32),0); + } +} + +extern int CopyFile(const wchar_t * infile, const wchar_t * outfile, void * callbackContext, void (*callback)(void * callbackContext, wchar_t * status), int * killswitch); + +static __int64 fileSize(const wchar_t * filename) +{ + WIN32_FIND_DATA f={0}; + HANDLE h = FindFirstFileW(filename,&f); + if(h == INVALID_HANDLE_VALUE) return -1; + FindClose(h); + ULARGE_INTEGER i; + i.HighPart = f.nFileSizeHigh; + i.LowPart = f.nFileSizeLow; + return i.QuadPart; +} + +void GetFileInfo(const wchar_t * file, const wchar_t * metadata, wchar_t * buf, int len) { + buf[0]=0; + extendedFileInfoStructW m = {file,metadata,buf,(size_t)len}; + SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&m,IPC_GET_EXTENDED_FILE_INFOW); +} + +__int64 GetFileInfoInt64(wchar_t * file, wchar_t * metadata, BOOL *w=NULL) { + wchar_t buf[100]=L""; + GetFileInfo(file,metadata,buf,100); + if(w && buf[0]==0) {(*w) = 0; return 0;} + return _wtoi64(buf); +} + +int GetFileInfoInt(wchar_t * file, wchar_t * metadata, BOOL *w=NULL) { + wchar_t buf[100]=L""; + GetFileInfo(file,metadata,buf,100); + if(w && buf[0]==0) {(*w) = 0; return 0;} + return _wtoi(buf); +} + +int iPodDevice::transferTrackToDevice(const itemRecordW *track,void * callbackContext,void (*callback)(void * callbackContext, wchar_t * status),songid_t * songid,int * killswitch) +{ + bool transcodefile = false; + wchar_t outfile[2048] = {0}; + wchar_t infile[MAX_PATH] = {0}; + StringCchCopy(infile, MAX_PATH, track->filename); + bool nocopy=false; + + iPod_mhit * mhit = db->mhsdsongs->mhlt->NewTrack(); + dirnum = (dirnum + 1) % 20; + + // create the output filename and directory + wchar_t ext[10]=L""; + const wchar_t *e = wcsrchr(infile,L'.'); + if(e) + StringCbCopyW(ext, sizeof(ext), e); + + if(transcoder && transcoder->ShouldTranscode(infile)) { + int r = transcoder->CanTranscode(infile,ext, track->length); + if(r != 0 && r != -1) transcodefile = true; + } + + bool video = !_wcsicmp(ext,L".m4v"); + + if(!_wcsicmp(ext,L".mp4")) { + wchar_t buf[100]=L"0"; + extendedFileInfoStructW m = {infile,L"type",buf,100}; + SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&m,IPC_GET_EXTENDED_FILE_INFOW); + if(!wcscmp(buf,L"1")) { video=true; wcsncpy(ext,L".m4v",10); } + else wcsncpy(ext,L".m4a",10); + } + + // and the location in the ipod naming scheme + if (infile[0] == drive && infile[1] && infile[1] == L':') + { + // file already on the ipod? add it directly + StringCbCopy(outfile, sizeof(outfile), infile); + nocopy=true; + } + else + { + StringCbPrintf(outfile,sizeof(outfile),L"%c:\\iPod_Control\\Music\\F%02d\\",(wchar_t)drive,dirnum); + CreateDirectory(outfile,NULL); + StringCbPrintf(outfile,sizeof(outfile),L"%c:\\iPod_Control\\Music\\F%02d\\w%05d%s",(wchar_t)drive,dirnum,mhit->id,ext); + } + + wchar_t location[2048] = {0}; + StringCbCopy(location, sizeof(location), outfile+2); + int i=0; + while(location[i] != 0) { if(location[i]==L'\\') location[i]=L':'; i++; } + + { + wchar_t buf[100]=L""; + int which = AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_source", 0); + extendedFileInfoStructW m = {infile,which?L"replaygain_album_gain":L"replaygain_track_gain",buf,100}; + SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&m,IPC_GET_EXTENDED_FILE_INFOW); + if(buf[0]) { + double gain = _wtof(&buf[buf[0]==L'+'?1:0]); + mhit->soundcheck = (unsigned long)(1000.0 * pow(10.0,-0.1*gain)); + } + } + + // fill in the new MHIT (track item) with our metadata + mhit->AddString(MHOD_TITLE)->SetString(track->title); + mhit->AddString(MHOD_LOCATION)->SetString(location); + mhit->AddString(MHOD_ALBUM)->SetString(track->album); + mhit->AddString(MHOD_ARTIST)->SetString(track->artist); + mhit->AddString(MHOD_GENRE)->SetString(track->genre); + mhit->AddString(MHOD_COMMENT)->SetString(track->comment); + mhit->AddString(MHOD_ALBUMARTIST)->SetString(track->albumartist); + mhit->AddString(MHOD_COMPOSER)->SetString(track->composer); + mhit->length = (track->length>0)?track->length*1000:0; + mhit->year = (track->year>0)?track->year:0; + mhit->tracknum = (track->track>0)?track->track:0; + mhit->totaltracks = (track->tracks>0)?track->tracks:0; + mhit->stars = (unsigned char)(mhit->app_rating = track->rating); + mhit->playcount = mhit->playcount2 = track->playcount; + mhit->lastplayedtime = wintime_to_mactime(track->lastplay); + mhit->lastmodifiedtime = wintime_to_mactime(track->lastupd); + mhit->compilation = track->albumartist && !_wcsicmp(track->albumartist, L"various artists"); + mhit->samplerate = 44100; // TODO: benski> we could query this from the input plugin, but we'd have to be careful with HE-AAC + mhit->samplerate2 = 44100.0f; + mhit->mediatype = video?0x02:0x01; + mhit->movie_flag = video?1:0; + mhit->cdnum = (track->disc>0)?track->disc:0; + mhit->totalcds = (track->discs>0)?track->discs:0; + mhit->BPM=(track->bpm>0)?track->bpm:0; + + wchar_t *pubdate = getRecordExtendedItem(track,L"podcastpubdate"); + if(pubdate && *pubdate) mhit->releasedtime=wintime_to_mactime(_wtoi(pubdate)); + + // copy the file over + int r; + if(transcodefile) + r = transcoder->TranscodeFile(infile,outfile,killswitch,callback,callbackContext); + else if (!nocopy) + r = CopyFile(infile,outfile,callbackContext,callback,killswitch); + else + { + if (callback) + { + wchar_t langtemp[100] = {0}; + callback(callbackContext, WASABI_API_LNGSTRINGW_BUF(IDS_DONE, langtemp, 100)); + } + r=0; + } + if(r == 0) + { + StringCbCopyW(ext, sizeof(ext), wcsrchr(outfile,L'.')); + if (!_wcsicmp(ext, L".m4a") || !_wcsicmp(ext, L".mp4")) + { + mhit->vbr = 0; + mhit->type = 0; + mhit->unk14 = 51; + mhit->filetype = FILETYPE_M4A; + } + else if (!_wcsicmp(ext, L".mp3")) + { + mhit->type = 1; + mhit->unk27 = 1; + mhit->unk14 = 12; + mhit->AddString(MHOD_FILETYPE)->SetString(L"MPEG audio file"); + mhit->filetype = FILETYPE_MP3; + } + else if (!_wcsicmp(ext, L".wav")) + { + mhit->filetype = FILETYPE_WAV; + } + mhit->samplecount = GetFileInfoInt64(outfile,L"numsamples"); + mhit->pregap = (unsigned long)GetFileInfoInt64(outfile,L"pregap"); + mhit->postgap = (unsigned long)GetFileInfoInt64(outfile,L"postgap"); + mhit->gaplessData = (unsigned long)GetFileInfoInt64(outfile,L"endoffset"); + mhit->trackgapless = 1; + + mhit->size = (unsigned long)fileSize(outfile); + if (!transcodefile && track->bitrate > 0) + mhit->bitrate = track->bitrate; + else + { + mhit->bitrate = (unsigned long)GetFileInfoInt64(outfile,L"bitrate"); + if (!mhit->bitrate) + { + if (track->length > 0) + mhit->bitrate = (mhit->size / track->length)/125; + else + mhit->bitrate = 128; + } + } + + + *songid = (songid_t)mhit; + } + else + { + DeleteFileW(outfile); + delete mhit; + } + return r; +} + +int iPodDevice::trackAddedToTransferQueue(const itemRecordW *track) { + __int64 l; + if(transcoder && transcoder->ShouldTranscode(track->filename)) { + int k = transcoder->CanTranscode(track->filename, 0, track->length); + if(k == -1) return -2; + if(k == 0) l = (__int64)fileSize(track->filename); + else l = (__int64)k; + } else { + wchar_t * ext = wcsrchr(track->filename,L'.'); + if(!ext) return -2; + if(_wcsicmp(ext,L".mp3") && _wcsicmp(ext,L".wav") && _wcsicmp(ext,L".m4a") && + _wcsicmp(ext,L".m4b") && _wcsicmp(ext,L".aa") && _wcsicmp(ext,L".m4v") && + _wcsicmp(ext,L".mp4")) return -2; + + l = (__int64)fileSize(track->filename); + } + __int64 avail = getDeviceCapacityAvailable(); + __int64 cmp = transferQueueLength; + cmp += l; + cmp += (__int64)3000000; + + if(cmp > avail) + return -1; + else { + transferQueueLength += l; + return 0; + } +} + +void iPodDevice::trackRemovedFromTransferQueue(const itemRecordW *track) { + __int64 l = (__int64)fileSize(track->filename); + if(transcoder && transcoder->ShouldTranscode(track->filename)) { + int k = transcoder->CanTranscode(track->filename, 0, track->length); + if(k != -1 && k != 0) l = (__int64)k; + } + transferQueueLength -= l; +} + +__int64 iPodDevice::getTrackSizeOnDevice(const itemRecordW *track) { + if(transcoder && transcoder->ShouldTranscode(track->filename)) { + int k = transcoder->CanTranscode(track->filename, 0, track->length); + if(k != -1 && k != 0) return k; + } + wchar_t * ext = wcsrchr(track->filename,'.'); + if(!ext) return 0; + if(_wcsicmp(ext,L".mp3") && _wcsicmp(ext,L".wav") && _wcsicmp(ext,L".m4a") && + _wcsicmp(ext,L".m4b") && _wcsicmp(ext,L".aa") && _wcsicmp(ext,L".m4v") && + _wcsicmp(ext,L"mp4")) return 0; + return fileSize(track->filename); +} + +void iPodDevice::deleteTrack(songid_t songid) { + + iPod_mhit * mhit = (iPod_mhit *)songid; + iPod_mhod * mhod = mhit->FindString(MHOD_LOCATION); + if(!mhod) return; + wchar_t * t = mhod->str; + + // change ':' to '\\; + wchar_t * p = t; + int l = wcslen(t); + for(int j=0; j<l; j++) if(*(p++)==L':') *(p-1)=L'\\'; + + // add drive onto front + wchar_t file[2048] = L"x:\\"; + file[0] = driveW; + wcscat(file,t); + + //check this file isn't playing... + wchar_t* curPlaying = (wchar_t*)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GET_PLAYING_FILENAME); + if(curPlaying && !_wcsicmp(curPlaying,file)) SendMessage(plugin.hwndWinampParent,WM_COMMAND,40047,0); //stop + + //delete :) + // benski> we might have a file that has been deleted from the disk but not the DB + if(!DeleteFileW(file) // check for file failure + && GetFileAttributes(file) != INVALID_FILE_ATTRIBUTES) // but only fail if the file actually exists + return; + + setArt(songid,NULL,0,0); + + l = playlists.GetSize(); + for(int i=0; i<l; i++) { + iPod_mhyp * mhyp = ((iPod_mhyp*)playlists.Get(i)); //->DeletePlaylistEntryByID(mhit->id); + for(unsigned int j=0; j<mhyp->GetMhipChildrenCount(); j++) { + if(mhyp->GetPlaylistEntry(j)->songindex == mhit->id) mhyp->DeletePlaylistEntry(j); + } + } + + + db->mhsdsongs->mhlt->DeleteTrackByID(mhit->id); +} + +int iPodDevice::getPlaylistCount() { + return playlists.GetSize(); +} + +static void readStringMHOD(iPod_mhod * mhod, wchar_t * buf, int len) { + if(mhod) lstrcpyn(buf,mhod->str,len); + else buf[0]=0; +} + +static void setStringMHOD(iPod_mhod * mhod, const wchar_t *buf) { + mhod->SetString(buf); +} + +void iPodDevice::getPlaylistName(int playlistnumber, wchar_t * buf, int len) { + iPod_mhod * name = ((iPod_mhyp*)playlists.Get(playlistnumber))->FindString(MHOD_TITLE); + readStringMHOD(name,buf,len); +} + +int iPodDevice::getPlaylistLength(int playlistnumber) { + return ((iPod_mhyp*)playlists.Get(playlistnumber))->GetMhipChildrenCount(); +} + +static iPod_mhit blank; + +songid_t iPodDevice::getPlaylistTrack(int playlistnumber,int songnum) { + int idx = ((iPod_mhyp*)playlists.Get(playlistnumber))->GetPlaylistEntry(songnum)->songindex; + iPod_mhlt::mhit_map_t::const_iterator f = db->mhsdsongs->mhlt->mhit.find(idx); + if(f != db->mhsdsongs->mhlt->mhit.end() && idx) return (songid_t)f->second; + else { + iPod_mhip* m = ((iPod_mhyp*)playlists.Get(playlistnumber))->GetPlaylistEntry(songnum); + blank.DeleteString(4); + if(m->podcastgroupflag && m->mhod[0]) { + iPod_mhod * mh = blank.AddString(4); + mh->SetString(m->mhod[0]->str); + } + return (songid_t)␣ + } +} + +void iPodDevice::setPlaylistName(int playlistnumber, const wchar_t *buf) { + iPod_mhod * name = ((iPod_mhyp*)playlists.Get(playlistnumber))->FindString(MHOD_TITLE); + if(!name) name = ((iPod_mhyp*)playlists.Get(playlistnumber))->AddString(MHOD_TITLE); + setStringMHOD(name,buf); + if(playlistnumber == 0) { + wchar_t volumename[12] = {0}; + const wchar_t * p = buf; + for(int i=0; i<11;) { + if(*p!=L' ' && *p!=L'\t') volumename[i++]=*p; + if(*(p++)==0) break; + } + volumename[11]=0; + char root[] = {drive,":\\"}; + SetVolumeLabel(AutoWide(root),volumename); + } +} + +void iPodDevice::playlistSwapItems(int playlistnumber, int posA, int posB) { + iPod_mhyp * p = ((iPod_mhyp*)playlists.Get(playlistnumber)); + iPod_mhip * a = p->mhip.at(posA); + iPod_mhip * b = p->mhip.at(posB); + if(a && b) { + p->mhip[posA] = b; + p->mhip[posB] = a; + } +} + +static iPod_mhyp * sortpl; +static int sortby; +static iPod_mhbd * sortdb; + +#define CMPFIELDS(x) { int v=0; iPod_mhod * am = a->FindString(x); iPod_mhod * bm = b->FindString(x); if(am && bm) v = lstrcmpi(am->str,bm->str); else if(am != bm) v = am==NULL?-1:1; if(v!=0) return v<0; } +#define CMPINTFIELDS(x,y) { int v = x-y; if(v!=0) return v<0; } + +struct PlaylistItemSort { + bool operator()(iPod_mhip*& ap,iPod_mhip*& bp) { + int use_by = sortby; + iPod_mhit * a = sortdb->mhsdsongs->mhlt->mhit.find(ap->songindex)->second; + iPod_mhit * b = sortdb->mhsdsongs->mhlt->mhit.find(bp->songindex)->second; + + // this might be too slow, but it'd be nice + int x; + for (x = 0; x < 5; x ++) + { + if (use_by == SORTBY_TITLE) // title -> artist -> album -> disc -> track + { + CMPFIELDS(MHOD_TITLE); + use_by=SORTBY_ARTIST; + } + else if (use_by == SORTBY_ARTIST) // artist -> album -> disc -> track -> title + { + CMPFIELDS(MHOD_ARTIST); + use_by=SORTBY_ALBUM; + } + else if (use_by == SORTBY_ALBUM) // album -> disc -> track -> title -> artist + { + CMPFIELDS(MHOD_ALBUM); + use_by=SORTBY_DISCNUM; + } + else if (use_by == SORTBY_DISCNUM) // disc -> track -> title -> artist -> album + { + CMPINTFIELDS(a->cdnum,b->cdnum); + use_by=SORTBY_TRACKNUM; + } + else if (use_by == SORTBY_TRACKNUM) // track -> title -> artist -> album -> disc + { + CMPINTFIELDS(a->tracknum,b->tracknum); + use_by=SORTBY_TITLE; + } + else if (use_by == SORTBY_GENRE) // genre -> artist -> album -> disc -> track + { + CMPFIELDS(MHOD_GENRE); + use_by=SORTBY_ARTIST; + } + else if (use_by == SORTBY_PLAYCOUNT) // size -> artist -> album -> disc -> track + { + CMPINTFIELDS(a->playcount,b->playcount); + use_by=SORTBY_ARTIST; + } + else if (use_by == SORTBY_RATING) // size -> artist -> album -> disc -> track + { + CMPINTFIELDS(a->stars,b->stars); + use_by=SORTBY_ARTIST; + } + else if (use_by == SORTBY_LASTPLAYED) + { + double t = difftime(a->lastplayedtime,b->lastplayedtime); + if(t != 0) return t>0; + use_by=SORTBY_ARTIST; + } + else break; // no sort order? + } + return false; + } +}; + +#undef CMPFIELDS +#undef CMPINTFIELDS + +void iPodDevice::sortPlaylist(int playlistnumber, int sortby0) { + sortpl = ((iPod_mhyp*)playlists.Get(playlistnumber)); + sortby = sortby0; + sortdb = db; + std::sort(sortpl->mhip.begin(),sortpl->mhip.end(),PlaylistItemSort()); +} + +void iPodDevice::addTrackToPlaylist(int playlistnumber, songid_t songid) +{ + iPod_mhit *mhit = (iPod_mhit *)songid; + + if (playlistnumber == 0) + { + db->mhsdsongs->mhlt->AddTrack(mhit); + db->mhsdplaylists->mhlp->GetDefaultPlaylist()->AddPlaylistEntry(NULL, mhit->id); + } + else + { + ((iPod_mhyp*)playlists.Get(playlistnumber))->AddPlaylistEntry(NULL, mhit->id); + } +} + +void iPodDevice::removeTrackFromPlaylist(int playlistnumber, int songnum) { + ((iPod_mhyp*)playlists.Get(playlistnumber))->DeletePlaylistEntry(songnum); +} + +void iPodDevice::deletePlaylist(int playlistnumber) { + iPod_mhyp* p = ((iPod_mhyp*)playlists.Get(playlistnumber)); + playlists.Del(playlistnumber); + db->mhsdplaylists->mhlp->DeletePlaylistByID(p->playlistID); +} + +int iPodDevice::newPlaylist(const wchar_t *name) { + iPod_mhyp * p = db->mhsdplaylists->mhlp->AddPlaylist(); + playlists.Add(p); + int ret = db->mhsdplaylists->mhlp->GetChildrenCount() - 1; + setPlaylistName(ret,name); + db->mhsdplaylists->mhlp->SortPlaylists(); + return ret; +} + +void iPodDevice::getTrackArtist(songid_t songid, wchar_t * buf, int len) { + iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_ARTIST); + readStringMHOD(mhod,buf,len); +} + +void iPodDevice::getTrackAlbum(songid_t songid, wchar_t * buf, int len) { + iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_ALBUM); + readStringMHOD(mhod,buf,len); +} + +void iPodDevice::getTrackTitle(songid_t songid, wchar_t * buf, int len) { + iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_TITLE); + readStringMHOD(mhod,buf,len); +} + +void iPodDevice::getTrackGenre(songid_t songid, wchar_t * buf, int len) { + iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_GENRE); + readStringMHOD(mhod,buf,len); +} + +int iPodDevice::getTrackTrackNum(songid_t songid) { + return ((iPod_mhit *)songid)->tracknum; +} + +int iPodDevice::getTrackDiscNum(songid_t songid) { + return ((iPod_mhit *)songid)->cdnum; +} + +int iPodDevice::getTrackYear(songid_t songid) { + return (int)(((iPod_mhit *)songid)->year); +} + +__int64 iPodDevice::getTrackSize(songid_t songid) { + return ((iPod_mhit *)songid)->size; +} + +int iPodDevice::getTrackLength(songid_t songid) { + return ((iPod_mhit *)songid)->length; +} + +int iPodDevice::getTrackBitrate(songid_t songid) { + return ((iPod_mhit *)songid)->bitrate; +} + +int iPodDevice::getTrackPlayCount(songid_t songid) { + return ((iPod_mhit *)songid)->playcount; +} + +int iPodDevice::getTrackRating(songid_t songid) { + return ((iPod_mhit *)songid)->stars / 20; +} + +__time64_t iPodDevice::getTrackLastPlayed(songid_t songid) { + return mactime_to_wintime(((iPod_mhit *)songid)->lastplayedtime); +} + +__time64_t iPodDevice::getTrackLastUpdated(songid_t songid) { + return mactime_to_wintime(((iPod_mhit *)songid)->lastmodifiedtime); +} + +void iPodDevice::getTrackAlbumArtist(songid_t songid, wchar_t * buf, int len) { + iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_ALBUMARTIST); + readStringMHOD(mhod,buf,len); + if(!mhod) getTrackArtist(songid,buf,len); +} +void iPodDevice::getTrackComposer(songid_t songid, wchar_t * buf, int len) { + iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_COMPOSER); + readStringMHOD(mhod,buf,len); +} + +int iPodDevice::getTrackType(songid_t songid) { + iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_LOCATION); + if(!mhod) return 0; + wchar_t * ext = wcsrchr(mhod->str,L'.'); + if(!ext) return 0; + if(!_wcsicmp(ext,L".m4v")) return 1; + return 0; +} + +void iPodDevice::getTrackExtraInfo(songid_t songid, const wchar_t *field, wchar_t * buf, int len) { + if(!wcscmp(field,FIELD_EXTENSION)) { + wchar_t buf2[1024]=L""; + getFilename(buf2,1024,songid); + wchar_t * ext = wcsrchr(buf2,L'.'); + if(ext) { ext++; lstrcpyn(buf,ext,len); } + } +} + +void iPodDevice::setTrackArtist(songid_t songid, const wchar_t *value) { + iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_ARTIST); + if(!mhod) mhod = ((iPod_mhit *)songid)->AddString(MHOD_ARTIST); + setStringMHOD(mhod,value); +} + +void iPodDevice::setTrackAlbum(songid_t songid, const wchar_t *value) { + iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_ALBUM); + if(!mhod) mhod = ((iPod_mhit *)songid)->AddString(MHOD_ALBUM); + setStringMHOD(mhod,value); +} + +void iPodDevice::setTrackTitle(songid_t songid, const wchar_t *value) { + iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_TITLE); + if(!mhod) mhod = ((iPod_mhit *)songid)->AddString(MHOD_TITLE); + setStringMHOD(mhod,value); +} + +void iPodDevice::setTrackTrackNum(songid_t songid, int value) { + ((iPod_mhit *)songid)->tracknum = value; +} + +void iPodDevice::setTrackDiscNum(songid_t songid, int value) { + ((iPod_mhit *)songid)->cdnum = value; +} + +void iPodDevice::setTrackGenre(songid_t songid, const wchar_t *value) { + iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_GENRE); + if(!mhod) mhod = ((iPod_mhit *)songid)->AddString(MHOD_GENRE); + setStringMHOD(mhod,value); +} + +void iPodDevice::setTrackYear(songid_t songid, int value) { + ((iPod_mhit *)songid)->year = (unsigned long)value; +} + +void iPodDevice::setTrackPlayCount(songid_t songid, int value) { + ((iPod_mhit *)songid)->playcount = value; +} + +void iPodDevice::setTrackRating(songid_t songid, int value) { + ((iPod_mhit *)songid)->app_rating = ((iPod_mhit *)songid)->stars = value*20; +} + +void iPodDevice::setTrackLastPlayed(songid_t songid, __time64_t value) { + ((iPod_mhit *)songid)->lastplayedtime = wintime_to_mactime(value); +} + +void iPodDevice::setTrackLastUpdated(songid_t songid, __time64_t value) { + ((iPod_mhit *)songid)->lastmodifiedtime = wintime_to_mactime(value); +} + +void iPodDevice::setTrackAlbumArtist(songid_t songid, const wchar_t *value) { + iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_ALBUMARTIST); + if(!mhod) mhod = ((iPod_mhit *)songid)->AddString(MHOD_ALBUMARTIST); + setStringMHOD(mhod,value); +} + +void iPodDevice::setTrackComposer(songid_t songid, const wchar_t *value) { + iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_COMPOSER); + if(!mhod) mhod = ((iPod_mhit *)songid)->AddString(MHOD_COMPOSER); + setStringMHOD(mhod,value); +} + +void iPodDevice::getFilename(char * buf, int len, songid_t song) { + iPod_mhod * mhod = ((iPod_mhit *)song)->FindString(MHOD_LOCATION); + if(!mhod) {buf[0]=0; return;} + char * filename = UTF16_to_char(mhod->str,mhod->length); + char * p = filename; + buf[0] = drive; + buf[1] = ':'; + int j=2; + while(p && *p && j < len-1) { if(*p==':') buf[j]='\\'; else buf[j]=*p; p++; j++; } + buf[j]=0; + free(filename); +} + +void iPodDevice::getFilename(wchar_t * buf, int len, songid_t song) { + iPod_mhod * mhod = ((iPod_mhit *)song)->FindString(MHOD_LOCATION); + if(!mhod) {buf[0]=0; return;} + wchar_t * filename = mhod->str; + wchar_t * p = filename; + buf[0] = drive; + buf[1] = L':'; + int j=2; + while(p && *p && j < len-1) { if(*p==L':') buf[j]=L'\\'; else buf[j]=*p; p++; j++; } + buf[j]=0; +} + +typedef struct { songid_t song; Device * dev; const wchar_t * filename; } tagItem; + +static wchar_t * tagFunc(const wchar_t * tag, void * p) { //return 0 if not found, -1 for empty tag + tagItem * s = (tagItem *)p; + int len = 2048; + wchar_t * buf = (wchar_t *)malloc(sizeof(wchar_t)*len); + if (!_wcsicmp(tag, L"artist")) s->dev->getTrackArtist(s->song,buf,len); + else if (!_wcsicmp(tag, L"album")) s->dev->getTrackAlbum(s->song,buf,len); + else if (!_wcsicmp(tag, L"title")) s->dev->getTrackTitle(s->song,buf,len); + else if (!_wcsicmp(tag, L"genre")) s->dev->getTrackGenre(s->song,buf,len); + else if (!_wcsicmp(tag, L"year")) + { + int year = s->dev->getTrackYear(s->song); + if (year>0) + StringCchPrintf(buf,len,L"%d",year); + else + buf[0]=0; + } + else if (!_wcsicmp(tag, L"tracknumber") || !_wcsicmp(tag, L"track")) + { + int track = s->dev->getTrackTrackNum(s->song); + if (track>0) + StringCchPrintf(buf,len,L"%d",track); + else + buf[0]=0; + } + else if (!_wcsicmp(tag, L"discnumber")) + { + int disc = s->dev->getTrackDiscNum(s->song); + if (disc>0) + StringCchPrintf(buf,len,L"%d",disc); + else + buf[0]=0; + } + else if (!_wcsicmp(tag, L"bitrate")) + { + int bitrate = s->dev->getTrackBitrate(s->song); + if (bitrate>0) + StringCchPrintf(buf,len,L"%d",bitrate); + else + buf[0]=0; + } + else if (!_wcsicmp(tag, L"filename")) lstrcpyn(buf,s->filename,len); + else buf[0]=0; + return buf; +} + +static void tagFreeFunc(wchar_t *tag, void *p) { if(tag) free(tag); } + +void getTitle(Device * dev, songid_t song, const wchar_t * filename, wchar_t * buf, int len) { + buf[0]=0; buf[len-1]=0; + tagItem item = {song,dev,filename}; + waFormatTitleExtended fmt={filename,0,NULL,&item,buf,len,tagFunc,tagFreeFunc}; + SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&fmt, IPC_FORMAT_TITLE_EXTENDED); +} + +bool iPodDevice::playTracks(songid_t * songidList, int listLength, int startPlaybackAt, bool enqueue) { + //char buf[2048]=""; + wchar_t wbuf[2048]=L""; + + if(!enqueue) { //clear playlist + SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_DELETE); + /*int l=SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_PE_GETINDEXTOTAL); + while(l>=0) SendMessage(plugin.hwndWinampParent,WM_WA_IPC,--l,IPC_PE_DELETEINDEX);*/ + } + + for(int i=0; i<listLength; i++) { + getFilename(wbuf,2048,songidList[i]); + //strcpy(buf,AutoChar(wbuf)); + + wchar_t title[2048] = {0}; + getTitle(this,songidList[i],wbuf,title,2048); + + /*enqueueFileWithMetaStruct s={buf,strdup(AutoChar(title)),getTrackLength(songidList[i])/1000}; + SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILE); + free((void*)s.title);*/ + + enqueueFileWithMetaStructW s={wbuf,_wcsdup(title),PathFindExtensionW(wbuf),getTrackLength(songidList[i]) / 1000}; + SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW); + free((void*)s.title); + } + + if(!enqueue) { //play item startPlaybackAt + SendMessage(plugin.hwndWinampParent,WM_WA_IPC,startPlaybackAt,IPC_SETPLAYLISTPOS); + SendMessage(plugin.hwndWinampParent,WM_COMMAND,40047,0); //stop + SendMessage(plugin.hwndWinampParent,WM_COMMAND,40045,0); //play + } + return true; +} + +int iPodDevice::copyToHardDrive(songid_t song, // the song to copy + wchar_t * path, // path to copy to, in the form "c:\directory\song". The directory will already be created, you must append ".mp3" or whatever to this string! (there is space for at least 10 new characters). + void * callbackContext, //pass this to the callback + void (*callback)(void * callbackContext, wchar_t * status), // call this every so often so the GUI can be updated. Including when finished! + int * killswitch // if this gets set to anything other than zero, the transfer has been cancelled by the user + ) // -1 for failed/not supported. 0 for success. +{ + wchar_t fn[2048] = {0}; // song filename on ipod + getFilename(fn,2048,song); + wchar_t * ext = wcsrchr(fn,L'.'); + if(!ext) { callback(callbackContext,WASABI_API_LNGSTRINGW(IDS_INVALID_TRACK)); return -1; } + wcscat(path,ext); + return CopyFile(fn,path,callbackContext,callback,killswitch); +} + +// art functions +static void fileputinhole(const wchar_t* file, unsigned int pos, int len, void* newdata) { + __int64 fs = fileSize(file); + int open_flags = OPEN_EXISTING; + if(fs <= 0) open_flags = CREATE_NEW; + HANDLE hw = CreateFile(file,GENERIC_WRITE | GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,open_flags,0,NULL); + if(hw == INVALID_HANDLE_VALUE) return; + SetFilePointer(hw,pos,NULL,FILE_BEGIN); + DWORD written=0; + WriteFile(hw,newdata,len,&written,NULL); + CloseHandle(hw); +} + +ArtDataObject * makeThumbMetadata(const ArtworkFormat * thumb, wchar_t drive, Image * image, ArtDB *artdb) { + wchar_t file[MAX_PATH] = {0}; + wsprintfW(file,L"%c:\\iPod_Control\\Artwork\\F%04d_1.ithmb",drive,thumb->correlation_id); + + bool found=false; + ArtFile *f=NULL; + for(size_t i=0; i < artdb->fileListDS->fileList->files.size(); i++) { + f = artdb->fileListDS->fileList->files[i]; + if(f->corrid == thumb->correlation_id) { found=true; break; } + } + if(!found) { + f = new ArtFile; + f->corrid = thumb->correlation_id; + f->imagesize = image->get16BitSize(thumb->row_align, thumb->image_align); + artdb->fileListDS->fileList->files.push_back(f); + f->file = _wcsdup(file); + } + + ArtDataObject * ms = new ArtDataObject; + ms->type=2; + ms->image = new ArtImageName; + ms->image->corrid = thumb->correlation_id; + ms->image->imgw = thumb->width; + ms->image->imgh = thumb->height; + ms->image->imagesize = image->get16BitSize(thumb->row_align, thumb->image_align); + + //__int64 fs = fileSize(file); + //ms->image->ithmboffset = fs>0?fs:0; + ms->image->ithmboffset = f->getNextHole(ms->image->imagesize); + + wchar_t buf[100] = {0}; + StringCchPrintf(buf,100,L":F%04d_1.ithmb",thumb->correlation_id); + ms->image->filename = new ArtDataObject; + ms->image->filename->type=3; + ms->image->filename->SetString(buf); + unsigned short *data = (unsigned short *)calloc(ms->image->imagesize,1); + image->exportToRGB565((RGB565*)data, thumb->format, thumb->row_align, thumb->image_align); + fileputinhole(file,ms->image->ithmboffset,ms->image->imagesize,data); + //writeDataToThumb(file,data,thumb->width * thumb->height); + free(data); + + f->images.push_back(new ArtFileImage(ms->image->ithmboffset,ms->image->imagesize,1)); + f->sortImages(); + + return ms; +} + +void GetTempFilePath(wchar_t *path) { + wchar_t dir[MAX_PATH] = {0}; + GetTempPath(MAX_PATH,dir); + GetTempFileName(dir,L"ml_pmp",0,path); +} + +static void fileclosehole(const wchar_t* file, unsigned int pos, int len) { + HANDLE hw = CreateFile(file,GENERIC_WRITE | GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL); + if(hw == INVALID_HANDLE_VALUE) return; + HANDLE hr = CreateFile(file,GENERIC_WRITE | GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL); + if(hr == INVALID_HANDLE_VALUE) { CloseHandle(hw); return; } + SetFilePointer(hw,pos,NULL,FILE_BEGIN); + SetFilePointer(hr,pos+len,NULL,FILE_BEGIN); + DWORD fs = GetFileSize(hw,NULL); + if(pos == 0 && len == fs) { CloseHandle(hr); CloseHandle(hw); _wunlink(file); return; } + unsigned int p = pos; + while(1) { + BYTE buf[65536] = {0}; + DWORD read=0; + ReadFile(hr,buf,sizeof(buf),&read,NULL); + if(!read) break; + DWORD written=0; + WriteFile(hw,buf,read,&written,NULL); + if(!written) break; + p+=read; + if(p>=fs) break; + } + + SetFilePointer(hw,fs - len,NULL,FILE_BEGIN); + SetEndOfFile(hw); + CloseHandle(hr); + CloseHandle(hw); + +} + +static bool replaceart(songid_t songid, wchar_t driveW, ArtDB *artdb, std::vector<const ArtworkFormat*> * thumbs, std::vector<Image*> * images) { + //return false; + __int64 dbid = ((iPod_mhit*)songid)->dbid; + int done=0; + ArtImageList::ArtImageMapIterator art = artdb->imageListDS->imageList->images.find(dbid); + if(art != artdb->imageListDS->imageList->images.end() && art->second) { // replace old art + for(size_t i=0; i!=art->second->dataobjs.size(); i++) if(art->second->dataobjs[i]->image) { + ArtImageName * in = art->second->dataobjs[i]->image; + for(size_t j=0; j!=thumbs->size(); j++) + { + if(in->corrid == thumbs->at(j)->correlation_id) { + wchar_t file[MAX_PATH] = {0}; + wsprintfW(file,L"%c:\\iPod_Control\\Artwork\\F%04d_1.ithmb",driveW,in->corrid); + int size = images->at(j)->get16BitSize(thumbs->at(j)->row_align, thumbs->at(j)->image_align); + if(size == in->imagesize) { + unsigned short *data = (unsigned short *)malloc(size); + images->at(j)->exportToRGB565((RGB565*)data, thumbs->at(j)->format, thumbs->at(j)->row_align, thumbs->at(j)->image_align); + fileputinhole(file,in->ithmboffset,in->imagesize,data); + free(data); + done++; + } + } + } + } + } + return (done == thumbs->size()); +} + +void iPodDevice::setArt(songid_t songid, void *bits, int w, int h) { //buf is in format ARGB32* + + if(!artdb || !thumbs.size()) return; // art not supported + iPod_mhit * mhit = (iPod_mhit *)songid; + + if(bits == NULL || w == 0 || h == 0) { // remove art + ArtImageList::ArtImageMapIterator arti = artdb->imageListDS->imageList->images.find(mhit->dbid); + if(arti == artdb->imageListDS->imageList->images.end() || !arti->second) return; + ArtImage * art = arti->second; + for(auto j = art->dataobjs.begin(); j!=art->dataobjs.end(); j++) { + ArtImageName *n = (*j)->image; + if(n) + { + ArtFile * f = artdb->fileListDS->fileList->getFile(n->corrid); + if(f) + { + bool found=false; + for(size_t i=0; i!=f->images.size(); i++) + { + if(!found && f->images[i]->start == n->ithmboffset && --f->images[i]->refcount==0) + { + delete f->images[i]; + f->images.erase(f->images.begin() + i); + i--; + found=true; + } + } + } + } + } + mhit->mhii_link = 0; + mhit->artworkcount = 0; + mhit->hasArtwork = 0; + artdb->imageListDS->imageList->images.erase(arti); + delete art; + } else { + //setArt(songid,NULL,0,0); // clear old art first + + HQSkinBitmap albumart((ARGB32*)bits, w, h); // wrap image into a bitmap object (no copying done) + + std::vector<Image*> images; + for(size_t i=0; i!=thumbs.size(); i++) { + BltCanvas canvas(thumbs[i]->width,thumbs[i]->height); + albumart.stretch(&canvas, 0, 0, thumbs[i]->width,thumbs[i]->height); + images.push_back(new Image((ARGB32 *)canvas.getBits(), thumbs[i]->width,thumbs[i]->height)); + } + + if(!replaceart(songid,driveW,artdb,&thumbs,&images)) { + setArt(songid,NULL,0,0); + ArtImage * artimg = new ArtImage(); + artimg->songid = mhit->dbid; + artimg->id = artdb->nextid++; + artimg->srcImageSize = w*h*4;//0; //fileSize(infile); + //artimg->srcImageSize = mhit->unk45 = rand(); + mhit->artworksize = 0; + for(size_t i=0; i!=thumbs.size(); i++) + { + artimg->dataobjs.push_back(makeThumbMetadata(thumbs[i],driveW,images[i],artdb)); + mhit->artworksize += thumbs[i]->width * thumbs[i]->height * sizeof(short); + } + artdb->imageListDS->imageList->images.insert(ArtImageList::ArtImageMapPair(artimg->songid,artimg)); + mhit->artworkcount = 1;//thumbs.size(); + mhit->hasArtwork = 1;//thumbs.size(); + mhit->mhii_link = artimg->id; + } + + //images.deleteAll(); + for (auto image : images) + { + delete image; + } + images.clear(); + } +} + +class ipodart_t { +public: + ipodart_t(ArtImageName *in, wchar_t driveW,int w, int h, const ArtworkFormat* format): w(w),h(h),image(0),error(0),resized(0),format(format) { + wsprintf(fn,L"%c:\\iPod_Control\\Artwork",driveW); + wchar_t *p = fn+wcslen(fn); + in->filename->GetString(p,MAX_PATH - (p - fn)); + while(p && *p) {if(*p == L':') *p=L'\\'; p++;} + offset = in->ithmboffset; + } + ~ipodart_t() { if(image) delete image; } + Image * GetImage() { + if(image || error) return image; + int size = Image::get16BitSize(w,h,format->row_align, format->image_align); + RGB565 * r = (RGB565*)calloc(size,1); + if(!r) { return 0; error=1; } + FILE *f = _wfopen(fn,L"rb"); + if(!f) { free(r); error=1; return 0; } + fseek(f,offset,0); + if(fread(r,size,1,f) != 1) { free(r); fclose(f); error=1; return 0; } + fclose(f); + image = new Image(r,w,h,format->format,format->row_align, format->image_align); + free(r); + return image; + } + Image * RegetImage() { + if(image) delete image; image=0; + return GetImage(); + } + int GetError() {return error;} + int getHeight(){if(image) return image->getHeight(); else return h;} + int getWidth() {if(image) return image->getWidth(); else return w;} + int resized; + void Resize(int neww, int newh) + { + HQSkinBitmap temp(image->getData(), image->getWidth(), image->getHeight()); // wrap into a SkinBitmap (no copying involved) + BltCanvas newImage(neww,newh); + temp.stretch(&newImage, 0, 0, neww, newh); + delete image; + image = new Image((ARGB32 *)newImage.getBits(), neww, newh); + resized=1; + } +private: + wchar_t fn[MAX_PATH]; + int offset; + int w,h; + Image * image; + int error; + const ArtworkFormat* format; +}; + +pmpart_t iPodDevice::getArt(songid_t songid) { + if(!artdb) return 0; + __int64 dbid = ((iPod_mhit*)songid)->dbid; + ArtImageList::ArtImageMapIterator art = artdb->imageListDS->imageList->images.find(dbid); + if(art == artdb->imageListDS->imageList->images.end() || !art->second) return 0; + int l = art->second->dataobjs.size(); + + ArtImageName * in=0; + int w=0,h=0; + const ArtworkFormat * format=0; + + for(int i=0; i<l; i++) { + if(art->second->dataobjs[i]->image && (w < art->second->dataobjs[i]->image->imgw || h < art->second->dataobjs[i]->image->imgh)) { + in = art->second->dataobjs[i]->image; + w = in->imgw; + h = in->imgh; + for(size_t i=0; i < thumbs.size(); i++) + { + const ArtworkFormat *f = thumbs.at(i); + if(f->width == w && f->height == h) + format = f; + } + } + } + if(!in || !format) return 0; + + return (pmpart_t)new ipodart_t(in,driveW,w,h,format); +} + +void iPodDevice::releaseArt(pmpart_t art) { + if(!art) return; + ipodart_t *image = (ipodart_t *)art; + delete image; +} + +int iPodDevice::drawArt(pmpart_t art, HDC dc, int x, int y, int w, int h) { + Image *image = ((ipodart_t*)art)->GetImage(); + if(!image) return 0; + HQSkinBitmap temp(image->getData(), image->getWidth(), image->getHeight()); // wrap into a SkinBitmap (no copying involved) + DCCanvas canvas(dc); + temp.stretch(&canvas,x,y,w,h); + return 1; + +} + +void iPodDevice::getArtNaturalSize(pmpart_t art, int *w, int *h){ + ipodart_t *image = (ipodart_t*)art; + if(!image) return; + *h = image->getHeight(); + *w = image->getWidth(); +} + +void iPodDevice::setArtNaturalSize(pmpart_t art, int w, int h){ + Image *image = ((ipodart_t*)art)->GetImage(); + if(!image) return; + if(w == image->getWidth() && h == image->getHeight()) return; + if(((ipodart_t*)art)->resized) { + image = ((ipodart_t*)art)->RegetImage(); + if(!image) return; + } + ((ipodart_t*)art)->Resize(w, h); +} + +void iPodDevice::getArtData(pmpart_t art, void* data){ // data ARGB32* is at natural size + Image *image = ((ipodart_t*)art)->GetImage(); + if(!image) return; + image->exportToARGB32((ARGB32*)data); +} + +bool iPodDevice::artIsEqual(pmpart_t at, pmpart_t bt) { + if(at == bt) return true; + if(!at || !bt) return false; + if(((ipodart_t*)at)->getWidth() != ((ipodart_t*)bt)->getWidth()) return false; + if(((ipodart_t*)at)->getHeight() != ((ipodart_t*)bt)->getHeight()) return false; + Image *a = ((ipodart_t*)at)->RegetImage(); + Image *b = ((ipodart_t*)bt)->RegetImage(); + if(!a && !b) return true; + if(!a || !b) return false; + if(a->getWidth() != b->getWidth()) return false; + if(b->getHeight() != b->getHeight()) return false; + return memcmp(a->getData(),b->getData(),a->getWidth()*a->getHeight()*sizeof(ARGB32)) == 0; +} + +static INT_PTR CALLBACK gapscan_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) { + static iPodDevice * dev; + static int i; + switch(uMsg) { + case WM_INITDIALOG: + SetWindowPos(hwndDlg,HWND_TOPMOST,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE); + dev = (iPodDevice*)lParam; + i=0; + dev->gapscanner = hwndDlg; + SendDlgItemMessage(hwndDlg,IDC_PROGRESS1,PBM_SETRANGE32,0,dev->getPlaylistLength(0)); + SendDlgItemMessage(hwndDlg,IDC_PROGRESS1,PBM_SETPOS,0,0); + SetTimer(hwndDlg,1,100,NULL); + break; + case WM_TIMER: + if(wParam == 1) { + KillTimer(hwndDlg,1); + int l = dev->getPlaylistLength(0); + int j=0; + for(;;) { + if(i >= l) return gapscan_dialogProc(hwndDlg,WM_CLOSE,0,0); + iPod_mhit * mhit = (iPod_mhit *)dev->getPlaylistTrack(0,i++); + if(!mhit->trackgapless && !mhit->gaplessData) { + wchar_t artist[50] = {0}, title[50] = {0}, buf[200] = {0}; + dev->getTrackArtist((songid_t)mhit,artist,50); + dev->getTrackTitle((songid_t)mhit,title,50); + StringCchPrintf(buf,200,L"%d/%d: %s - %s",i+1,l,artist,title); + SetDlgItemText(hwndDlg,IDC_CAPTION,buf); + wchar_t infile[MAX_PATH]=L""; + dev->getFilename(infile,MAX_PATH,(songid_t)mhit); + BOOL worked=TRUE; + mhit->samplecount = GetFileInfoInt64(infile,L"numsamples",&worked); + mhit->pregap = (unsigned long)GetFileInfoInt64(infile,L"pregap",&worked); + mhit->postgap = (unsigned long)GetFileInfoInt64(infile,L"postgap",&worked); + mhit->gaplessData = (unsigned long)GetFileInfoInt64(infile,L"endoffset",&worked); + mhit->trackgapless = worked?1:0; + j++; + } else if(!(i%23)) SetDlgItemText(hwndDlg,IDC_CAPTION,WASABI_API_LNGSTRINGW(IDS_SCANNING)); + if(j > 3 || !(i % 50)) { + SendDlgItemMessage(hwndDlg,IDC_PROGRESS1,PBM_SETPOS,i,0); + SetTimer(hwndDlg,1,25,NULL); + return 0; + } + } + } + break; + case WM_CLOSE: + dev->writeiTunesDB(); + EndDialog(hwndDlg,0); + dev->gapscanner = NULL; + break; + case WM_DESTROY: + dev->gapscanner = NULL; + break; + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDCANCEL: + gapscan_dialogProc(hwndDlg,WM_CLOSE,0,0); + break; + case IDC_BG: + ShowWindow(hwndDlg,SW_HIDE); + break; + } + break; + } + return 0; +} + +static INT_PTR CALLBACK config_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) { + static iPodDevice * dev; + switch(uMsg) { + case WM_INITDIALOG: + { + prefsParam* p = (prefsParam*)lParam; + p->config_tab_init(hwndDlg,p->parent); + dev = (iPodDevice*)p->dev; + if(dev->artdb && dev->thumbs.size()) { + wchar_t inifile[] = {dev->driveW,L":\\iPod_Control\\iTunes\\ml_pmp.ini"}; + ShowWindow(GetDlgItem(hwndDlg,IDC_STATIC_ARTGROUP),SW_SHOWNA); + ShowWindow(GetDlgItem(hwndDlg,IDC_CHECK_USEART),SW_SHOWNA); + CheckDlgButton(hwndDlg,IDC_CHECK_USEART,GetPrivateProfileInt(L"ml_pmp",L"albumart",1,inifile)); + //ShowWindow(GetDlgItem(hwndDlg,IDC_COMBO_ARTMODE),SW_SHOWNA); + /*ComboBox combo(hwndDlg,IDC_COMBO_USEART); + combo.AddString(L"Add to all tracks"); + combo.AddString(L"Only add to the first track in an album"); + combo.AddString(L"Don't add to any tracks"); + */ + } + } + break; + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDC_SCAN: + if(dev->gapscanner) ShowWindow(dev->gapscanner,SW_SHOW); + else WASABI_API_DIALOGBOXPARAM(IDD_GAPSCAN,NULL,gapscan_dialogProc,(LPARAM)dev); + break; + case IDC_CHECK_USEART: + wchar_t inifile[] = {dev->driveW,L":\\iPod_Control\\iTunes\\ml_pmp.ini"}, s[32] = {0}; + StringCchPrintf(s, 32, L"%d", (IsDlgButtonChecked(hwndDlg, IDC_CHECK_USEART)==BST_CHECKED)); + WritePrivateProfileString(L"ml_pmp", L"albumart", s, inifile); + break; + } + break; + } + return 0; +} + +static const intptr_t encoder_blacklist[] = +{ + mmioFOURCC('W','M','A',' '), + mmioFOURCC('A','A','C','H'), + mmioFOURCC('A','A','C','P'), + mmioFOURCC('A','A','C','r'), + mmioFOURCC('F','L','A','C'), + mmioFOURCC('O','G','G',' '), + mmioFOURCC('M','P','2',' '), + mmioFOURCC('M','4','A','H'), + mmioFOURCC('M','4','A','+'), + mmioFOURCC('A','D','T','S'), +}; + +intptr_t iPodDevice::extraActions(intptr_t param1, intptr_t param2, intptr_t param3,intptr_t param4) { + switch(param1) { + case DEVICE_SET_ICON: // icons + { + MLTREEIMAGE * i = (MLTREEIMAGE*)param2; + i->hinst = plugin.hDllInstance; + i->resourceId = image16; + } + break; + case DEVICE_GET_ICON: + { + if (param2 <= 16 && param3 <= 16) + { + // TODO: get the name of the DLL at load time + StringCchPrintfW((wchar_t *)param4, 260, L"res://%s/PNG/#%u", L"pmp_ipod.dll", image16); + } + else + { + // TODO: get the name of the DLL at load time + StringCchPrintfW((wchar_t *)param4, 260, L"res://%s/PNG/#%u", L"pmp_ipod.dll", image160); + } + } + break; + case DEVICE_SUPPORTED_METADATA: + return 0xffff | (artdb?SUPPORTS_ALBUMART:0); + case DEVICE_CAN_RENAME_DEVICE: + return 1; + case DEVICE_GET_INI_FILE: + { + wchar_t inifile[] = {driveW,L":\\iPod_Control\\iTunes\\ml_pmp.ini"}; + wcsncpy((wchar_t*)param2,inifile,MAX_PATH); + } + break; + case DEVICE_GET_PREFS_DIALOG: + if(param3 == 0) { + pref_tab * p = (pref_tab *)param2; + p->hinst = WASABI_API_LNG_HINST; + p->dlg_proc = config_dialogProc; + p->res_id = IDD_CONFIG; + WASABI_API_LNGSTRINGW_BUF(IDS_ADVANCED,p->title,100); + } + break; + case DEVICE_REFRESH: + { + char drive = this->drive; + + //iPods.eraseObject(this); + auto it = std::find(iPods.begin(), iPods.end(), this); + if (it != iPods.end()) + { + iPods.erase(it); + } + + SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICEDISCONNECTED); + delete this; + new iPodDevice(drive); + } + break; + case DEVICE_ADDPODCASTGROUP: + { + int pos = param3; + wchar_t * name = (wchar_t *)param4; + iPod_mhyp* pl = (iPod_mhyp*)playlists.Get(param2); + pl->podcastflag=1; + iPod_mhip * mhip = new iPod_mhip(); + mhip->podcastgroupflag=256; + mhip->podcastgroupref=0; + iPod_mhod * d = new iPod_mhod(); + d->SetString(name); + d->type=1; + mhip->mhod.push_back(d); + pl->mhip.insert(pl->mhip.begin()+pos,mhip); + } + break; + case DEVICE_ADDPODCASTGROUP_FINISH: + { + iPod_mhyp* pl = (iPod_mhyp*)playlists.Get(param2); + pl->numLibraryMHODs=0x18; + int groupref=0; + for(size_t i=0; i < pl->mhip.size(); i++) { + iPod_mhip * m = pl->mhip[i]; + m->groupid = i+1000000; + if(m->podcastgroupflag & 256) groupref = m->groupid; + else m->podcastgroupref = groupref; + } + } + break; + case DEVICE_SUPPORTS_VIDEO: + return 1; + case DEVICE_VETO_ENCODER: + { + for (size_t i=0;i<sizeof(encoder_blacklist)/sizeof(*encoder_blacklist);i++) + { + // TODO: check device info XML for aacPlus support + if (param2 == encoder_blacklist[i]) + return 1; + } + } + return 0; + case DEVICE_GET_MODEL: + { + wchar_t *model_buffer = (wchar_t *)param2; + unsigned int cch = (unsigned int)param3; + if (!info) + { + StringCchCopyW(model_buffer, cch, L"Apple iPod"); + } + else switch(info->family_id) + { + default: + StringCchCopyW(model_buffer, cch, L"Apple iPod"); + break; + case 3: + StringCchCopyW(model_buffer, cch, L"Apple iPod Mini"); + break; + case 4: + StringCchCopyW(model_buffer, cch, L"Apple iPod 4G"); + break; + case 5: + StringCchCopyW(model_buffer, cch, L"Apple iPod Photo"); + break; + case 6: + StringCchCopyW(model_buffer, cch, L"Apple iPod 5G"); + break; + case 7: + StringCchCopyW(model_buffer, cch, L"Apple iPod Nano 1G"); + break; + case 9: + StringCchCopyW(model_buffer, cch, L"Apple iPod Nano 2G"); + break; + case 11: + StringCchCopyW(model_buffer, cch, L"Apple iPod Classic"); + break; + case 12: + StringCchCopyW(model_buffer, cch, L"Apple iPod Fat Nano"); + break; + case 15: + StringCchCopyW(model_buffer, cch, L"Apple iPod Nano4G"); + break; + case 128: + StringCchCopyW(model_buffer, cch, L"Apple iPod Shuffle"); + break; + case 130: + StringCchCopyW(model_buffer, cch, L"Apple iPod Shuffle 2G"); + break; + case 132: + StringCchCopyW(model_buffer, cch, L"Apple iPod Shuffle 3G"); + break; + } + + } + return 1; + } + return 0; +}
\ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_ipod/iPodDevice.h b/Src/Plugins/Portable/pmp_ipod/iPodDevice.h new file mode 100644 index 00000000..375dcf85 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/iPodDevice.h @@ -0,0 +1,142 @@ +#ifndef _IPODDEVICE_H_ +#define _IPODDEVICE_H_ + +#include <windows.h> +#include <windowsx.h> +#include <stdio.h> +#include <shlobj.h> +#include <time.h> +#include "..\..\General\gen_ml/ml.h" +#include "..\..\Library\ml_pmp/pmp.h" +#include "..\..\Library\ml_pmp/transcoder.h" +#include "../winamp/wa_ipc.h" +#include "../winamp/ipc_pe.h" +#include "iPodDB.h" +#include "resource.h" +#include "..\..\General\gen_ml/itemlist.h" +#include "yail.h" +#include "iPodArtworkDB.h" +#include "iPodInfo.h" +#include <vector> + +class iPodDevice : public Device { +public: + HWND gapscanner; + C_ItemList playlists; // list of iPod_mhyp* + Transcoder * transcoder; + iPod_mhbd * db; + char drive; + wchar_t driveW; + int dirnum; + __int64 transferQueueLength; + + int image16; + int image160; + + ArtDB * artdb; + std::vector<const ArtworkFormat*> thumbs; + uint8_t *fwid; + const iPodInfo *info; + iPodDevice(char drive); + virtual ~iPodDevice(); + virtual int parseiTunesDB(bool parseArt); + virtual int writeiTunesDB(); + virtual void getFilename(char * buf, int len, songid_t song); + virtual void getFilename(wchar_t *buf, int len, songid_t song); + virtual __int64 getDeviceCapacityAvailable(); // in bytes + virtual __int64 getDeviceCapacityTotal(); // in bytes + + virtual void Eject(); // if you ejected successfully, you MUST call plugin.deviceDisconnected(this) and delete this; + virtual void Close(); // save any changes, and call plugin.deviceDisconnected(this) AND delete this; + + // return 0 for success, -1 for failed or cancelled + virtual int transferTrackToDevice(const itemRecordW * track, // the track to transfer + void * callbackContext, //pass this to the callback + void (*callback)(void * callbackContext, wchar_t * status), // call this with stats every so often so the GUI can be updated + songid_t * songid, // fill in the songid when you are finished + int * killswitch); // if this gets set to 1, the transfer has been cancelled by the user + virtual int trackAddedToTransferQueue(const itemRecordW *track); // return 0 to accept, -1 for "not enough space", -2 for "incorrect format" + virtual void trackRemovedFromTransferQueue(const itemRecordW *track); + virtual __int64 getTrackSizeOnDevice(const itemRecordW *track); // return the amount of space taken up on the device by the track, or 0 for incompatable (usually the filesize, unless you are transcoding) + + virtual void deleteTrack(songid_t songid); // physically remove from device. Be sure to remove it from all the playlists! + + virtual void commitChanges(){writeiTunesDB();} // optional. Will be called at a good time to save changes + + virtual int getPlaylistCount(); // always at least 1. playlistnumber 0 is the Master Playlist containing all tracks. + // PlaylistName(0) should return the name of the device. + virtual void getPlaylistName(int playlistnumber, wchar_t *buf, int len); + virtual int getPlaylistLength(int playlistnumber); + virtual songid_t getPlaylistTrack(int playlistnumber,int songnum); // returns a songid + + virtual void setPlaylistName(int playlistnumber, const wchar_t *buf); // with playlistnumber==0, set the name of the device. + virtual void playlistSwapItems(int playlistnumber, int posA, int posB); // swap the songs at position posA and posB + virtual void sortPlaylist(int playlistnumber, int sortBy); + virtual void addTrackToPlaylist(int playlistnumber, songid_t songid); // adds songid to the end of the playlist + virtual void removeTrackFromPlaylist(int playlistnumber, int songnum); //where songnum is the position of the track in the playlist + + virtual void deletePlaylist(int playlistnumber); + virtual int newPlaylist(const wchar_t *name); // create empty playlist, returns playlistnumber + + virtual void getTrackArtist(songid_t songid, wchar_t *buf, int len); + virtual void getTrackAlbum(songid_t songid, wchar_t *buf, int len); + virtual void getTrackTitle(songid_t songid, wchar_t *buf, int len); + virtual int getTrackTrackNum(songid_t songid); + virtual int getTrackDiscNum(songid_t songid); + virtual void getTrackGenre(songid_t songid, wchar_t *buf, int len); + virtual int getTrackYear(songid_t songid); + virtual __int64 getTrackSize(songid_t songid); // in bytes + virtual int getTrackLength(songid_t songid); // in millisecs + virtual int getTrackBitrate(songid_t songid); // in kbps + virtual int getTrackPlayCount(songid_t songid); + virtual int getTrackRating(songid_t songid); //0-5 + virtual __time64_t getTrackLastPlayed(songid_t songid); // in unix time format + virtual __time64_t getTrackLastUpdated(songid_t songid); // in unix time format + virtual void getTrackAlbumArtist(songid_t songid, wchar_t * buf, int len); + virtual void getTrackComposer(songid_t songid, wchar_t * buf, int len); + virtual int getTrackType(songid_t songid); + virtual void getTrackExtraInfo(songid_t songid, const wchar_t *field, wchar_t * buf, int len); //optional + + // feel free to ignore any you don't support + virtual void setTrackArtist(intptr_t songid, const wchar_t *value); + virtual void setTrackAlbum(intptr_t songid, const wchar_t *value); + virtual void setTrackTitle(intptr_t songid, const wchar_t *value); + virtual void setTrackTrackNum(intptr_t songid, int value); + virtual void setTrackDiscNum(intptr_t songid, int value); + virtual void setTrackGenre(intptr_t songid, const wchar_t *value); + virtual void setTrackYear(intptr_t songid, int year); + virtual void setTrackPlayCount(intptr_t songid, int value); + virtual void setTrackRating(intptr_t songid, int value); + virtual void setTrackLastPlayed(intptr_t songid, __time64_t value); // in unix time format + virtual void setTrackLastUpdated(intptr_t songid, __time64_t value); // in unix time format + virtual void setTrackAlbumArtist(songid_t songid, const wchar_t *value); + virtual void setTrackComposer(songid_t songid, const wchar_t *value); + virtual void setTrackExtraInfo(intptr_t songid, const wchar_t *field, const wchar_t *value) {}; //optional + + virtual bool playTracks(songid_t * songidList, int listLength, int startPlaybackAt, bool enqueue); // return false if unsupported + + virtual intptr_t extraActions(intptr_t param1, intptr_t param2, intptr_t param3,intptr_t param4); // This does nothing yet. For future use. + + virtual bool copyToHardDriveSupported() {return true;} + + virtual __int64 songSizeOnHardDrive(songid_t song) {return getTrackSize(song);} // how big a song will be when copied back. Return -1 for not supported. + + virtual int copyToHardDrive(songid_t song, // the song to copy + wchar_t * path, // path to copy to, in the form "c:\directory\song". The directory will already be created, you must append ".mp3" or whatever to this string! (there is space for at least 10 new characters). + void * callbackContext, //pass this to the callback + void (*callback)(void * callbackContext, wchar_t * status), // call this every so often so the GUI can be updated. Including when finished! + int * killswitch // if this gets set to anything other than zero, the transfer has been cancelled by the user + ); // -1 for failed/not supported. 0 for success. + + // art functions + virtual void setArt(songid_t songid, void *buf, int w, int h); //buf is in format ARGB32* + virtual pmpart_t getArt(songid_t songid); + virtual void releaseArt(pmpart_t art); + virtual int drawArt(pmpart_t art, HDC dc, int x, int y, int w, int h); + virtual void getArtNaturalSize(pmpart_t art, int *w, int *h); + virtual void setArtNaturalSize(pmpart_t art, int w, int h); + virtual void getArtData(pmpart_t art, void* data); // data ARGB32* is at natural size + virtual bool artIsEqual(pmpart_t a, pmpart_t b); +}; + +#endif // _IPODDEVICE_H_
\ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_ipod/iPodInfo.cpp b/Src/Plugins/Portable/pmp_ipod/iPodInfo.cpp new file mode 100644 index 00000000..94870553 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/iPodInfo.cpp @@ -0,0 +1,1040 @@ +#include "api.h" +#include "iPodInfo.h" +#include "resource.h" +#include "../../General/gen_ml/ml.h" +#include "../../Library/ml_pmp/pmp.h" + +#include "../xml/obj_xml.h" +#include "../plist/loader.h" + +#include <api/service/waservicefactory.h> + +#include <stdio.h> +#include <windows.h> +#include <strsafe.h> + +extern PMPDevicePlugin plugin; + +static const ArtworkFormat ipod_color_artwork_info[] = +{ + {THUMB_COVER_SMALL, 56, 56, 1017, RGB_565, 4, 4}, + {THUMB_COVER_LARGE, 140, 140, 1016, RGB_565, 4, 4}, + {THUMB_PHOTO_TV_SCREEN, 720, 480, 1019, RGB_565, 4, 4}, + {THUMB_PHOTO_LARGE, 130, 88, 1015, RGB_565, 4, 4}, + {THUMB_PHOTO_FULL_SCREEN, 220, 176, 1013, RGB_565, 4, 4}, + {THUMB_PHOTO_SMALL, 42, 30, 1009, RGB_565, 4, 4}, + {THUMB_INVALID, -1, -1, -1, RGB_565, 4, 4} +}; + +static const ArtworkFormat ipod_nano_artwork_info[] = +{ + {THUMB_COVER_SMALL, 42, 42, 1031, RGB_565, 4, 4}, + {THUMB_COVER_LARGE, 100, 100, 1027, RGB_565, 4, 4}, + {THUMB_PHOTO_LARGE, 42, 37, 1032, RGB_565, 4, 4}, + {THUMB_PHOTO_FULL_SCREEN, 176, 132, 1023, RGB_565, 4, 4}, + {THUMB_INVALID, -1, -1, -1, RGB_565, 4, 4} +}; + +static const ArtworkFormat ipod_video_artwork_info[] = +{ + {THUMB_COVER_SMALL, 100, 100, 1028, RGB_565, 4, 4}, + {THUMB_COVER_LARGE, 200, 200, 1029, RGB_565, 4, 4}, + {THUMB_PHOTO_TV_SCREEN, 720, 480, 1019, RGB_565, 4, 4}, + {THUMB_PHOTO_LARGE, 130, 88, 1015, RGB_565, 4, 4}, + {THUMB_PHOTO_FULL_SCREEN, 320, 240, 1024, RGB_565, 4, 4}, + {THUMB_PHOTO_SMALL, 50, 41, 1036, RGB_565, 4, 4}, + {THUMB_INVALID, -1, -1, -1, RGB_565, 4, 4} +}; + +static const ArtworkFormat ipod_7g_artwork_info[] = +{ + {THUMB_COVER_SMALL, 55, 55, 1061, RGB_565, 4, 4}, + {THUMB_COVER_MEDIUM1, 128, 128, 1055, RGB_565, 4, 4}, + {THUMB_COVER_LARGE, 320, 320, 1060, RGB_565, 4, 4}, + {THUMB_INVALID, -1, -1, -1, RGB_565, 4, 4} +}; + +static const ArtworkFormat ipod_touch_artwork_info[] = +{ + {THUMB_COVER_SMALL, 55, 55, 3006, RGB_555, 16, 4096}, + {THUMB_COVER_MEDIUM1, 64, 64, 3003, RGB_555_REC, 16, 4096}, + {THUMB_COVER_MEDIUM2, 88, 88, 3007, RGB_555, 16, 4096}, + {THUMB_COVER_MEDIUM3, 128, 128, 3002, RGB_555_REC, 16, 4096}, + {THUMB_COVER_MEDIUM4, 256, 256, 3001, RGB_555_REC, 16, 4096}, + {THUMB_COVER_LARGE, 320, 320, 3005, RGB_555, 16, 4096}, + {THUMB_INVALID, -1, -1, -1, RGB_555, 4, 4} +}; + +/* +static const ArtworkFormat ipod_mobile_1_artwork_info[] = { + {THUMB_COVER_SMALL, 50, 50, 2002}, + {THUMB_COVER_LARGE, 150, 150, 2003}, + {THUMB_INVALID, -1, -1, -1} +}; +*/ + +//maps model to artwork format +static const ArtworkFormat *ipod_artwork_info_table[] = +{ + NULL, // invalid + ipod_color_artwork_info, // color + NULL, // regular + NULL, // mini + NULL, // shuffle + ipod_video_artwork_info, // video + ipod_nano_artwork_info, // nano + ipod_7g_artwork_info, // classic + ipod_7g_artwork_info, // fat nano + ipod_touch_artwork_info, // touch +}; + + +// this list compiled from http://www.thismuchiknow.co.uk/?page_id=27 and is kept in the same order as that table for easy updating +// when new ipods come out, let's keep this up to date. +// at the moment this is just used as a mapping from part number to model, for album art +static const iPodModelInfo ipod_info_table[] = +{ + //1st gen ipods + {L"8513", IPOD_MODEL_REGULAR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, //mac + {L"8541", IPOD_MODEL_REGULAR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, //mac + {L"8697", IPOD_MODEL_REGULAR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, //pc + {L"8709", IPOD_MODEL_REGULAR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, //mac + + //2nd gen ipods + {L"8737", IPOD_MODEL_REGULAR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, //mac + {L"8740", IPOD_MODEL_REGULAR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, //pc + {L"8738", IPOD_MODEL_REGULAR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, //mac + {L"8741", IPOD_MODEL_REGULAR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, //pc + + //3rd gen ipods + {L"8976", IPOD_MODEL_REGULAR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"8946", IPOD_MODEL_REGULAR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"8948", IPOD_MODEL_REGULAR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"9244", IPOD_MODEL_REGULAR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"9245", IPOD_MODEL_REGULAR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"9460", IPOD_MODEL_REGULAR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + + //1st gen mini + {L"9160", IPOD_MODEL_MINI, IPOD_COLOR_SILVER, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"9436", IPOD_MODEL_MINI, IPOD_COLOR_BLUE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"9435", IPOD_MODEL_MINI, IPOD_COLOR_PINK, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"9434", IPOD_MODEL_MINI, IPOD_COLOR_GREEN, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"9437", IPOD_MODEL_MINI, IPOD_COLOR_GOLD, IDB_CLASSIC_16, IDB_CLASSIC_160}, + + //4th gen ipods + {L"9282", IPOD_MODEL_REGULAR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"9268", IPOD_MODEL_REGULAR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"E435", IPOD_MODEL_REGULAR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, //HP branded + {L"E436", IPOD_MODEL_REGULAR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, //HP branded + {L"9787", IPOD_MODEL_REGULAR, IPOD_COLOR_U2, IDB_CLASSIC_16, IDB_CLASSIC_160}, + + //4th gen ipod photos + {L"9585", IPOD_MODEL_COLOR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"9586", IPOD_MODEL_COLOR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + + //shuffles + {L"A133", IPOD_MODEL_SHUFFLE, IPOD_COLOR_WHITE, IDB_SHUFFLE1G_16, IDB_SHUFFLE1G_160}, + {L"9724", IPOD_MODEL_SHUFFLE, IPOD_COLOR_WHITE, IDB_SHUFFLE1G_16, IDB_SHUFFLE1G_160}, + {L"9725", IPOD_MODEL_SHUFFLE, IPOD_COLOR_WHITE, IDB_SHUFFLE1G_16, IDB_SHUFFLE1G_160}, + + // more ipod photos + {L"9829", IPOD_MODEL_COLOR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"9830", IPOD_MODEL_COLOR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + + // ipod mini 2nd gen + {L"9959", IPOD_MODEL_MINI, IPOD_COLOR_SILVER, IDB_CLASSIC_16, IDB_CLASSIC_160}, // pepsi giveaway ipod + {L"9800", IPOD_MODEL_MINI, IPOD_COLOR_SILVER, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"9802", IPOD_MODEL_MINI, IPOD_COLOR_BLUE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"9804", IPOD_MODEL_MINI, IPOD_COLOR_PINK, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"9806", IPOD_MODEL_MINI, IPOD_COLOR_GREEN, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"9801", IPOD_MODEL_MINI, IPOD_COLOR_SILVER, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"9803", IPOD_MODEL_MINI, IPOD_COLOR_BLUE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"9805", IPOD_MODEL_MINI, IPOD_COLOR_PINK, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"9807", IPOD_MODEL_MINI, IPOD_COLOR_GREEN, IDB_CLASSIC_16, IDB_CLASSIC_160}, + + //HP colour ipods + {L"S492", IPOD_MODEL_COLOR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"S493", IPOD_MODEL_COLOR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + + //HP ipod mini 2nd gen + {L"W753", IPOD_MODEL_MINI, IPOD_COLOR_SILVER, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"X762", IPOD_MODEL_MINI, IPOD_COLOR_SILVER, IDB_CLASSIC_16, IDB_CLASSIC_160}, + + //more 4th gen ipod photos + {L"A079", IPOD_MODEL_COLOR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"A127", IPOD_MODEL_COLOR, IPOD_COLOR_U2, IDB_CLASSIC_16, IDB_CLASSIC_160}, + + //HP ipod shuffles + {L"X765", IPOD_MODEL_SHUFFLE, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"X766", IPOD_MODEL_SHUFFLE, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + + /* + //harry potter ipod 4G, don't know serial number. but that's ok because it was only on sale for a month, so fuck it. + {L"????", IPOD_MODEL_COLOR, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + */ + + //ipod nano 1st gen + {L"A004", IPOD_MODEL_NANO, IPOD_COLOR_WHITE, IDB_NANO1G_16, IDB_NANO1G_160}, + {L"A099", IPOD_MODEL_NANO, IPOD_COLOR_BLACK, IDB_NANO1G_16, IDB_NANO1G_160}, + {L"A005", IPOD_MODEL_NANO, IPOD_COLOR_WHITE, IDB_NANO1G_16, IDB_NANO1G_160}, + {L"A107", IPOD_MODEL_NANO, IPOD_COLOR_BLACK, IDB_NANO1G_16, IDB_NANO1G_160}, + + //ipod video + {L"A002", IPOD_MODEL_VIDEO, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"A146", IPOD_MODEL_VIDEO, IPOD_COLOR_BLACK, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"A003", IPOD_MODEL_VIDEO, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"A147", IPOD_MODEL_VIDEO, IPOD_COLOR_BLACK, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"A253", IPOD_MODEL_VIDEO, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, //harry potter ipod 5G + + //1gig nano + {L"A350", IPOD_MODEL_NANO, IPOD_COLOR_WHITE, IDB_NANO1G_16, IDB_NANO1G_160}, + {L"A352", IPOD_MODEL_NANO, IPOD_COLOR_BLACK, IDB_NANO1G_16, IDB_NANO1G_160}, + + // U2 ipod video + {L"A452", IPOD_MODEL_VIDEO, IPOD_COLOR_U2, IDB_CLASSIC_16, IDB_CLASSIC_160}, + + //2nd gen nano + {L"A477", IPOD_MODEL_NANO, IPOD_COLOR_SILVER, IDB_NANO2G_16, IDB_NANO2G_160}, + {L"A426", IPOD_MODEL_NANO, IPOD_COLOR_SILVER, IDB_NANO2G_16, IDB_NANO2G_160}, + {L"A428", IPOD_MODEL_NANO, IPOD_COLOR_BLUE, IDB_NANO2G_16, IDB_NANO2G_160}, + {L"A487", IPOD_MODEL_NANO, IPOD_COLOR_GREEN, IDB_NANO2G_16, IDB_NANO2G_160}, + {L"A489", IPOD_MODEL_NANO, IPOD_COLOR_PINK, IDB_NANO2G_16, IDB_NANO2G_160}, + {L"A497", IPOD_MODEL_NANO, IPOD_COLOR_BLACK, IDB_NANO2G_16, IDB_NANO2G_160}, + + // ipod video 6th gen + {L"A444", IPOD_MODEL_VIDEO, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"A446", IPOD_MODEL_VIDEO, IPOD_COLOR_BLACK, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"A448", IPOD_MODEL_VIDEO, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"A450", IPOD_MODEL_VIDEO, IPOD_COLOR_BLACK, IDB_CLASSIC_16, IDB_CLASSIC_160}, + + //2nd gen shuffle + {L"A564", IPOD_MODEL_SHUFFLE, IPOD_COLOR_SILVER, IDB_SHUFFLE2G_16, IDB_SHUFFLE2G_160}, + + // ipod video u2 6th gen + {L"A664", IPOD_MODEL_VIDEO, IPOD_COLOR_U2, IDB_CLASSIC_16, IDB_CLASSIC_160}, + + //product red ipod nano + {L"A725", IPOD_MODEL_NANO, IPOD_COLOR_RED, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"A899", IPOD_MODEL_NANO, IPOD_COLOR_RED, IDB_CLASSIC_16, IDB_CLASSIC_160}, + + // coloured versions of ipod shuffle 2nd gen + {L"A947", IPOD_MODEL_SHUFFLE, IPOD_COLOR_PINK, IDB_SHUFFLE2G_16, IDB_SHUFFLE2G_160}, + {L"A949", IPOD_MODEL_SHUFFLE, IPOD_COLOR_BLUE, IDB_SHUFFLE2G_16, IDB_SHUFFLE2G_160}, + {L"A951", IPOD_MODEL_SHUFFLE, IPOD_COLOR_GREEN, IDB_SHUFFLE2G_16, IDB_SHUFFLE2G_160}, + {L"A953", IPOD_MODEL_SHUFFLE, IPOD_COLOR_ORANGE, IDB_SHUFFLE2G_16, IDB_SHUFFLE2G_160}, + + // fat nanos + {L"A978", IPOD_MODEL_FATNANO, IPOD_COLOR_SILVER, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"A980", IPOD_MODEL_FATNANO, IPOD_COLOR_SILVER, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"B249", IPOD_MODEL_FATNANO, IPOD_COLOR_BLUE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"B253", IPOD_MODEL_FATNANO, IPOD_COLOR_GREEN, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"B261", IPOD_MODEL_FATNANO, IPOD_COLOR_BLACK, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"B257", IPOD_MODEL_FATNANO, IPOD_COLOR_RED, IDB_CLASSIC_16, IDB_CLASSIC_160}, + + // ipod classic + {L"B147", IPOD_MODEL_CLASSIC, IPOD_COLOR_BLACK, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"B029", IPOD_MODEL_CLASSIC, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"B150", IPOD_MODEL_CLASSIC, IPOD_COLOR_BLACK, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"B145", IPOD_MODEL_CLASSIC, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, + + // ipod touch + {L"A623", IPOD_MODEL_TOUCH, IPOD_COLOR_BLACK, IDB_CLASSIC_16, IDB_CLASSIC_160}, + {L"A627", IPOD_MODEL_TOUCH, IPOD_COLOR_BLACK, IDB_CLASSIC_16, IDB_CLASSIC_160}, + + //insert info about new models here (be sure to take first char off the product code)... +}; + +static const iPodModelInfo +shuffle1g_info = {L"XXXX", IPOD_MODEL_SHUFFLE, IPOD_COLOR_PINK, IDB_SHUFFLE1G_16, IDB_SHUFFLE1G_160}, +shuffle2g_info = {L"XXXX", IPOD_MODEL_SHUFFLE, IPOD_COLOR_PINK, IDB_SHUFFLE2G_16, IDB_SHUFFLE2G_160}, +shuffle3g_info = {L"XXXX", IPOD_MODEL_SHUFFLE, IPOD_COLOR_SILVER, IDB_SHUFFLE3G_16, IDB_SHUFFLE3G_160}, +shuffle4g_info = {L"XXXX", IPOD_MODEL_SHUFFLE, IPOD_COLOR_SILVER, IDB_SHUFFLE4G_16, IDB_SHUFFLE4G_160}, +classic_info = {L"XXXX", IPOD_MODEL_CLASSIC, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, +video_info = {L"XXXX", IPOD_MODEL_VIDEO, IPOD_COLOR_WHITE, IDB_CLASSIC_16, IDB_CLASSIC_160}, +nano1g_info = {L"XXXX", IPOD_MODEL_NANO, IPOD_COLOR_WHITE, IDB_NANO1G_16, IDB_NANO1G_160}, +nano2g_info = {L"XXXX", IPOD_MODEL_NANO, IPOD_COLOR_WHITE, IDB_NANO2G_16, IDB_NANO2G_160}, +nano3g_info = {L"XXXX", IPOD_MODEL_NANO, IPOD_COLOR_WHITE, IDB_NANO3G_16, IDB_NANO3G_160}, +nano4g_info = {L"XXXX", IPOD_MODEL_NANO, IPOD_COLOR_WHITE, IDB_NANO4G_16, IDB_NANO4G_160}; + +static INT_PTR CALLBACK selectipodtype_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + BringWindowToTop(hwndDlg); + wchar_t * sysinfo = (wchar_t*)lParam; + SetWindowLongPtr(hwndDlg,GWLP_USERDATA,lParam); + wchar_t path[] = {sysinfo[0],L":\\"}; + wchar_t name[32] = {0}; + GetVolumeInformation(path,name,32,NULL,NULL,NULL,NULL,0); + wchar_t buf[100] = {0}; + wchar_t s[32] = {0}; + GetDlgItemText(hwndDlg,IDC_IPODINFO,s,32); + StringCchPrintf(buf,100,L"%s (%s) %s",path,name,s); + SetDlgItemText(hwndDlg,IDC_IPODINFO,buf); + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_RADIO1: + case IDC_RADIO2: + case IDC_RADIO3: + case IDC_RADIO4: + case IDC_RADIO5: + case IDC_RADIO6: + case IDC_RADIO7: + case IDC_RADIO8: + EnableWindow(GetDlgItem(hwndDlg,IDOK),TRUE); + break; + case IDCANCEL: + EndDialog(hwndDlg,1); + break; + case IDOK: + { + char *m; + if (IsDlgButtonChecked(hwndDlg,IDC_RADIO1)) m = "A133"; //shuffle + else if (IsDlgButtonChecked(hwndDlg,IDC_RADIO2)) m = "9586"; //photo + else if (IsDlgButtonChecked(hwndDlg,IDC_RADIO3)) m = "A002"; //video + else if (IsDlgButtonChecked(hwndDlg,IDC_RADIO4)) m = "A005"; //nano + else if (IsDlgButtonChecked(hwndDlg,IDC_RADIO6)) m = "A623"; //touch + else if (IsDlgButtonChecked(hwndDlg,IDC_RADIO7)) m = "B145"; //classic + else if (IsDlgButtonChecked(hwndDlg,IDC_RADIO8)) m = "B257"; //fatnano + else m = "8976"; //other + wchar_t * sysinfo = (wchar_t*)GetWindowLongPtr(hwndDlg,GWLP_USERDATA); + FILE *f = _wfopen(sysinfo,L"a+b"); + if (f) + { + fprintf(f,"ModelNumStr: M%s\n",m); + fclose(f); + } + } + EndDialog(hwndDlg,0); + break; + } + break; + } + return 0; +} + +/* This table was extracted from ipod-model-table from podsleuth svn trunk + * on 2008-06-14 (which seems to match podsleuth 0.6.2) +*/ +static const iPodSerialToModel serial_to_model_mapping[] = +{ + { L"LG6", L"8541" }, + { L"NAM", L"8541" }, + { L"MJ2", L"8541" }, + { L"ML1", L"8709" }, + { L"MME", L"8709" }, + { L"MMB", L"8737" }, + { L"MMC", L"8738" }, + { L"NGE", L"8740" }, + { L"NGH", L"8740" }, + { L"MMF", L"8741" }, + { L"NLW", L"8946" }, + { L"NRH", L"8976" }, + { L"QQF", L"9460" }, + { L"PQ5", L"9244" }, + { L"PNT", L"9244" }, + { L"NLY", L"8948" }, + { L"NM7", L"8948" }, + { L"PNU", L"9245" }, + { L"PS9", L"9282" }, + { L"Q8U", L"9282" }, + { L"V9V", L"9787" }, + { L"S2X", L"9787" }, + { L"PQ7", L"9268" }, + { L"TDU", L"A079" }, + { L"TDS", L"A079" }, + { L"TM2", L"A127" }, + { L"SAZ", L"9830" }, + { L"SB1", L"9830" }, + { L"SAY", L"9829" }, + { L"R5Q", L"9585" }, + { L"R5R", L"9586" }, + { L"R5T", L"9586" }, + { L"PFW", L"9160" }, + { L"PRC", L"9160" }, + { L"QKL", L"9436" }, + { L"QKQ", L"9436" }, + { L"QKK", L"9435" }, + { L"QKP", L"9435" }, + { L"QKJ", L"9434" }, + { L"QKN", L"9434" }, + { L"QKM", L"9437" }, + { L"QKR", L"9437" }, + { L"S41", L"9800" }, + { L"S4C", L"9800" }, + { L"S43", L"9802" }, + { L"S45", L"9804" }, + { L"S47", L"9806" }, + { L"S4J", L"9806" }, + { L"S42", L"9801" }, + { L"S44", L"9803" }, + { L"S48", L"9807" }, + { L"RS9", L"9724" }, + { L"QGV", L"9724" }, + { L"TSX", L"9724" }, + { L"PFV", L"9724" }, + { L"R80", L"9724" }, + { L"RSA", L"9725" }, + { L"TSY", L"9725" }, + { L"C60", L"9725" }, + { L"VTE", L"A546" }, + { L"VTF", L"A546" }, + { L"XQ5", L"A947" }, + { L"XQS", L"A947" }, + { L"XQV", L"A949" }, + { L"XQX", L"A949" }, + { L"YX7", L"A949" }, + { L"XQY", L"A951" }, + { L"YX8", L"A951" }, + { L"XR1", L"A953" }, + { L"YXA", L"B233" }, + { L"YX6", L"B225" }, + { L"YX7", L"B228" }, + { L"YX9", L"B225" }, + { L"UNA", L"A350" }, + { L"UNB", L"A350" }, + { L"UPR", L"A352" }, + { L"UPS", L"A352" }, + { L"SZB", L"A004" }, + { L"SZV", L"A004" }, + { L"SZW", L"A004" }, + { L"SZC", L"A005" }, + { L"SZT", L"A005" }, + { L"TJT", L"A099" }, + { L"TJU", L"A099" }, + { L"TK2", L"A107" }, + { L"TK3", L"A107" }, + { L"VQ5", L"A477" }, + { L"VQ6", L"A477" }, + { L"V8T", L"A426" }, + { L"V8U", L"A426" }, + { L"V8W", L"A428" }, + { L"V8X", L"A428" }, + { L"VQH", L"A487" }, + { L"VQJ", L"A487" }, + { L"VQK", L"A489" }, + { L"VKL", L"A489" }, + { L"WL2", L"A725" }, + { L"WL3", L"A725" }, + { L"X9A", L"A726" }, + { L"X9B", L"A726" }, + { L"VQT", L"A497" }, + { L"VQU", L"A497" }, + { L"Y0P", L"A978" }, + { L"Y0R", L"A980" }, + { L"YXR", L"B249" }, + { L"YXV", L"B257" }, + { L"YXT", L"B253" }, + { L"YXX", L"B261" }, + { L"SZ9", L"A002" }, + { L"WEC", L"A002" }, + { L"WED", L"A002" }, + { L"WEG", L"A002" }, + { L"WEH", L"A002" }, + { L"WEL", L"A002" }, + { L"TXK", L"A146" }, + { L"TXM", L"A146" }, + { L"WEE", L"A146" }, + { L"WEF", L"A146" }, + { L"WEJ", L"A146" }, + { L"WEK", L"A146" }, + { L"SZA", L"A003" }, + { L"SZU", L"A003" }, + { L"TXL", L"A147" }, + { L"TXN", L"A147" }, + { L"V9K", L"A444" }, + { L"V9L", L"A444" }, + { L"WU9", L"A444" }, + { L"VQM", L"A446" }, + { L"V9M", L"A446" }, + { L"V9N", L"A446" }, + { L"WEE", L"A446" }, + { L"V9P", L"A448" }, + { L"V9Q", L"A448" }, + { L"V9R", L"A450" }, + { L"V9S", L"A450" }, + { L"V95", L"A450" }, + { L"V96", L"A450" }, + { L"WUC", L"A450" }, + { L"W9G", L"A664" }, /* 30GB iPod Video U2 5.5g */ + { L"Y5N", L"B029" }, /* Silver Classic 80GB */ + { L"YMV", L"B147" }, /* Black Classic 80GB */ + { L"YMU", L"B145" }, /* Silver Classic 160GB */ + { L"YMX", L"B150" }, /* Black Classic 160GB */ + { L"2C5", L"B562" }, /* Silver Classic 120GB */ + { L"2C7", L"B565" }, /* Black Classic 120GB */ + { L"9ZS", L"C293" }, /* Silver Classic 160GB (2009) */ + { L"9ZU", L"C297" }, /* Black Classic 160GB (2009) */ + + { L"37P", L"B663" }, /* 4GB Green Nano 4g */ + { L"37Q", L"B666" }, /* 4GB Yellow Nano 4g */ + { L"37H", L"B654" }, /* 4GB Pink Nano 4g */ + { L"1P1", L"B480" }, /* 4GB Silver Nano 4g */ + { L"37K", L"B657" }, /* 4GB Purple Nano 4g */ + { L"37L", L"B660" }, /* 4GB Orange Nano 4g */ + { L"2ME", L"B598" }, /* 8GB Silver Nano 4g */ + { L"3QS", L"B732" }, /* 8GB Blue Nano 4g */ + { L"3QT", L"B735" }, /* 8GB Pink Nano 4g */ + { L"3QU", L"B739" }, /* 8GB Purple Nano 4g */ + { L"3QW", L"B742" }, /* 8GB Orange Nano 4g */ + { L"3QX", L"B745" }, /* 8GB Green Nano 4g */ + { L"3QY", L"B748" }, /* 8GB Yellow Nano 4g */ + { L"3R0", L"B754" }, /* 8GB Black Nano 4g */ + { L"3QZ", L"B751" }, /* 8GB Red Nano 4g */ + { L"5B7", L"B903" }, /* 16GB Silver Nano 4g */ + { L"5B8", L"B905" }, /* 16GB Blue Nano 4g */ + { L"5B9", L"B907" }, /* 16GB Pink Nano 4g */ + { L"5BA", L"B909" }, /* 16GB Purple Nano 4g */ + { L"5BB", L"B911" }, /* 16GB Orange Nano 4g */ + { L"5BC", L"B913" }, /* 16GB Green Nano 4g */ + { L"5BD", L"B915" }, /* 16GB Yellow Nano 4g */ + { L"5BE", L"B917" }, /* 16GB Red Nano 4g */ + { L"5BF", L"B918" }, /* 16GB Black Nano 4g */ + + { L"71V", L"C027" }, /* 8GB Silver Nano 5g */ + { L"71Y", L"C031" }, /* 8GB Black Nano 5g */ + { L"721", L"C034" }, /* 8GB Purple Nano 5g */ + { L"726", L"C037" }, /* 8GB Blue Nano 5g */ + { L"72A", L"C040" }, /* 8GB Green Nano 5g */ + { L"72F", L"C046" }, /* 8GB Orange Nano 5g */ + { L"72L", L"C050" }, /* 8GB Pink Nano 5g */ + + { L"72Q", L"C060" }, /* 16GB Silver Nano 5g */ + { L"72R", L"C062" }, /* 16GB Black Nano 5g */ + { L"72S", L"C064" }, /* 16GB Purple Nano 5g */ + { L"72X", L"C066" }, /* 16GB Blue Nano 5g */ + { L"734", L"C068" }, /* 16GB Green Nano 5g */ + { L"738", L"C070" }, /* 16GB Yellow Nano 5g */ + { L"739", L"C072" }, /* 16GB Orange Nano 5g */ + { L"73A", L"C074" }, /* 16GB Red Nano 5g */ + { L"73B", L"C075" }, /* 16GB Pink Nano 5g */ + + { L"4NZ", L"B867" }, /* 4GB Silver Shuffle 4g */ + { L"891", L"C164" }, /* 4GB Black Shuffle 4g */ + + { L"W4T", L"A627" }, /* 16GB Silver iPod Touch (1st gen) */ + { L"0JW", L"B376" }, /* 32GB Silver iPod Touch (1st gen) */ + { L"201", L"B528" }, /* 8GB Silver iPod Touch (2nd gen) */ + { L"203", L"B531" }, /* 16GB Silver iPod Touch (2nd gen) */ + { L"75J", L"C086" }, /* 8GB Silver iPod Touch (3rd gen) */ + { L"6K2", L"C008" }, /* 32GB Silver iPod Touch (3rd gen) */ + { L"6K4", L"C011" }, /* 64GB Silver iPod Touch (3rd gen) */ + + { L"VR0", L"A501" }, /* 4GB Silver iPhone 1st gen */ + { L"WH8", L"A712" }, /* 8GB Silver iPhone */ + { L"0KH", L"B384" }, /* 16GB Silver iPhone */ + { L"Y7H", L"B046" }, /* 8GB Black iPhone 3G */ + { L"Y7K", L"B496" }, /* 16GB Black iPhone 3G */ + { L"3NP", L"C131" }, /* 16GB Black iPhone 3GS */ + { L"3NR", L"C133" } /* 32GB Black iPhone 3GS */ +}; + +static const wchar_t *GetModelStrForFamilyID(unsigned int familyID) +{ + switch (familyID) + { + case 4: // iPod 4 + return L"9282"; + case 5: // iPod 4 (photo) + return L"9830"; + case 6: // iPod 5 + return L"A002"; + case 7: // nano 1 + return L"A004"; + case 9: // nano 2 + return L"A477"; + case 11: // classic + return L"B147"; + case 12: // fat nano + return L"A978"; + case 128: // shuffle + return L"A133"; + case 130: // shuffle 2 + return L"A947"; + default: + return 0; + } +} + +static const iPodModelInfo *GetiPodInfoForModelStr(const wchar_t *modelstr) +{ + // now locate this ipod in our table + int l = sizeof(ipod_info_table)/sizeof(ipod_info_table[0]); + for (int i=0; i<l; i++) + { + if (_wcsnicmp(ipod_info_table[i].model_number,modelstr,wcslen(ipod_info_table[i].model_number))==0) + return &ipod_info_table[i]; // success! + } + return 0; +} + +static const iPodModelInfo *GetiPodInfoForFamilyID(unsigned int familyID) +{ + switch(familyID) + { + case 6: + return &video_info; + case 7: + return &nano1g_info; + case 9: + return &nano2g_info; + case 11: + return &classic_info; + case 12: + return &nano3g_info; + case 15: + return &nano4g_info; + case 128: + return &shuffle1g_info; + case 130: // shuffle 2G + return &shuffle2g_info; + case 132: // shuffle 3G + return &shuffle3g_info; + case 133: // shuffle 4G + return &shuffle4g_info; + } + return 0; +} + +const wchar_t* GetModelStrForSerialNumber(const wchar_t *serialNumber) +{ + // now locate this ipod in our table + int l = sizeof(serial_to_model_mapping)/sizeof(iPodSerialToModel); + + INT serialNumberLen = lstrlen(serialNumber); + + if (serialNumberLen < 3) + { + return NULL; + } + + const wchar_t *last3OfSerialNumber = &serialNumber[serialNumberLen-3]; + + for (int i=0; i<l; i++) + { + int compareRet = CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, last3OfSerialNumber, -1, serial_to_model_mapping[i].serial, -1)-2; + if (compareRet==0) + return serial_to_model_mapping[i].model_number; // success! + } + return 0; +} + +extern bool ParseSysInfoXML(wchar_t drive_letter, char * xml, int xmllen); + +iPodInfo::iPodInfo(const iPodModelInfo *_model) +{ + family_id = 0; + color = _model->color; + model = _model->model; + model_number = _wcsdup(_model->model_number); + image16 = _model->image16; + image160 = _model->image160; + fwid=0; + supportedArtworkFormats=0; + numberOfSupportedFormats=0; + shadow_db_version=0; +} + +iPodInfo::~iPodInfo() +{ + free(fwid); + free(model_number); + delete supportedArtworkFormats; +} + +void iPodInfo::SetFWID(const uint8_t *new_fwid) +{ + fwid = (uint8_t *)malloc(8); + memcpy(fwid, new_fwid, 8); +} + +iPodInfo *GetiPodInfo(wchar_t drive) +{ + static const iPodModelInfo unknown = {NULL, IPOD_MODEL_INVALID, IPOD_COLOR_WHITE}; + + unsigned char fwid[8]={0}; + bool have_fwid=false; + char xml[65536] = {0}; + if (ParseSysInfoXML(drive, xml, sizeof(xml)/sizeof(char))) + { + // go fetch the FamilyID so we can construct a model string + DWORD bytesRead = strlen(xml);//sizeof(xml)/sizeof(char); + + // use the plist handler here instead of fishing for the familyid string + // in the xml + + // instantiate the plist loader + plistLoader it; + + obj_xml *parser=0; + waServiceFactory *factory = plugin.service->service_getServiceByGuid(obj_xmlGUID); + if (factory) + { + parser = (obj_xml *)factory->getInterface(); + } + + if (parser) + { + // load the XML, this creates an iTunes DB in memory, and returns the root key + parser->xmlreader_open(); + parser->xmlreader_registerCallback(L"plist\f*", &it); + parser->xmlreader_feed(xml, bytesRead); + parser->xmlreader_feed(0, 0); + parser->xmlreader_unregisterCallback(&it); + parser->xmlreader_close(); + plistKey *root_key = ⁢ + plistData *root_dict = root_key->getData(); + if (root_dict) + { + // get Firewire ID + plistKey *fwidKey = ((plistDict*)root_dict)->getKey(L"FireWireGUID"); + if (fwidKey) + { + plistData *fwidData = fwidKey->getData(); + if (fwidData) + { + const wchar_t* p = fwidData->getString(); + for (int i=0; i<8 && *p; i++) + { + char num[3]={0,0,0}; + num[0] = *(p++); + num[1] = *(p++); + fwid[i] = (uint8_t)strtoul(num,NULL,16); + } + have_fwid=true; + } + } + + + // check for the existance of sqlite + plistKey *sqliteKey = ((plistDict*)root_dict)->getKey(L"SQLiteDB"); + if (sqliteKey) + { + plistData *sqliteData = sqliteKey->getData(); + if (sqliteData) + { + const wchar_t* sqliteString = sqliteData->getString(); + if (sqliteString) + { + int compareRet = CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, sqliteString, -1, L"1", -1)-2; + + // At this point we dont want to support the sqlite family of ipods + // so, return unknown if sqlite found + if (compareRet == 0) + { + return 0; + } + } + } + } // end sqlite check + + // check for FamilyID + plistKey *familyKey = ((plistDict*)root_dict)->getKey(L"FamilyID"); + if (familyKey) + { + plistData *familyData = familyKey->getData(); + if (familyData) + { + const wchar_t* familyIDString = familyData->getString(); + if (familyIDString) + { + const wchar_t *modelStr = NULL; + unsigned int familyID = _wtoi(familyIDString); + // first, try to look up the iPod by family ID + const iPodModelInfo *info = GetiPodInfoForFamilyID(familyID); + if (!info) + { + modelStr = GetModelStrForFamilyID(familyID); + + // if modelString not apparent, as the case is in most + // 5th gen nanos and classics + if (!info && !modelStr) + { + plistKey *serialNumberKey = ((plistDict*)root_dict)->getKey(L"SerialNumber"); + if (serialNumberKey) + { + plistData *serialNumberData = serialNumberKey->getData(); + if (serialNumberData) + { + const wchar_t* serialNumberString = serialNumberData->getString(); + + if (serialNumberString) + { + modelStr = GetModelStrForSerialNumber(serialNumberString); + } + } + } + } + } + + if (modelStr || info) + { + if (!info) + info = GetiPodInfoForModelStr(modelStr); + + if (info) + { + iPodInfo* retInfo = new iPodInfo(info); + + if (have_fwid) + retInfo->SetFWID(fwid); + + plistKey *shadow_db_key = ((plistDict*)root_dict)->getKey(L"ShadowDB"); + if (shadow_db_key) + { + plistData *shadow_db_data = shadow_db_key->getData(); + if (shadow_db_data && shadow_db_data->getType() == PLISTDATA_BOOLEAN) + { + plistBoolean *shadow_db_boolean = (plistBoolean *)shadow_db_data; + if (shadow_db_boolean->getValue()) + retInfo->shadow_db_version = 1; + + plistKey *shadow_db_version_key = ((plistDict*)root_dict)->getKey(L"ShadowDBVersion"); + if (shadow_db_version_key) + { + plistData *shadow_db_version_data = shadow_db_version_key->getData(); + if (shadow_db_version_data && shadow_db_version_data->getType() == PLISTDATA_INTEGER) + { + plistInteger *shadow_db_version_integer= (plistInteger *)shadow_db_version_data; + retInfo->shadow_db_version = shadow_db_version_integer->getValue(); + } + } + } + } + // now try and populate the ArtworkFormats from the plist + // looks something like this + /***************************************************** + <key>AlbumArt</key> + <array> + <key>1069</key> + <dict> + <key>FormatId</key> + <integer>1069</integer> + <key>RenderWidth</key> + <integer>142</integer> + <key>RenderHeight</key> + <integer>142</integer> + <key>PixelFormat</key> + <string>4C353635</string> + <key>Interlaced</key> + <false/> + <key>ColorAdjustment</key> + <integer>0</integer> + <key>GammaAdjustment</key> + <real>2.2</real> + <key>Crop</key> + <false/> + <key>AlignRowBytes</key> + <true/> + <key>BackColor</key> + <string>00000000</string> + <key>AssociatedFormat</key> + <integer>131072</integer> + <key>ExcludedFormats</key> + <integer>-1</integer> + </dict> + <key>1055</key> + <dict> + <key>FormatId</key> + <integer>1055</integer> + <key>RenderWidth</key> + <integer>128</integer> + <key>RenderHeight</key> + <integer>128</integer> + <key>PixelFormat</key> + <string>4C353635</string> + <key>Interlaced</key> + <false/> + <key>ColorAdjustment</key> + <integer>0</integer> + <key>GammaAdjustment</key> + <real>2.2</real> + <key>Crop</key> + <true/> + <key>AlignRowBytes</key> + <true/> + <key>BackColor</key> + <string>00000000</string> + <key>AssociatedFormat</key> + <integer>0</integer> + </dict> + </array> + *******************************************************************/ + + // look for the AlbumArt dict + plistKey *albumArtKey = ((plistDict*)root_dict)->getKey(L"AlbumArt"); + + if (albumArtKey) + { + plistArray* albumArtArray = (plistArray *) albumArtKey->getData(); + + if (albumArtArray) + { + int numFormats = albumArtArray->getNumItems(); + ArtworkFormat* artworkFormats = new ArtworkFormat[numFormats]; + + + retInfo->supportedArtworkFormats = &artworkFormats[0]; + retInfo->numberOfSupportedFormats = numFormats; + + for (int i=0;i<numFormats;i++) + { + // we need to populate this structure + /** + static const ArtworkFormat ipod_color_artwork_info[] = { + {THUMB_COVER_SMALL, 56, 56, 1017, RGB_565, 4, 4}, + {THUMB_COVER_LARGE, 140, 140, 1016, RGB_565, 4, 4}, + {THUMB_PHOTO_TV_SCREEN, 720, 480, 1019, RGB_565, 4, 4}, + {THUMB_PHOTO_LARGE, 130, 88, 1015, RGB_565, 4, 4}, + {THUMB_PHOTO_FULL_SCREEN, 220, 176, 1013, RGB_565, 4, 4}, + {THUMB_PHOTO_SMALL, 42, 30, 1009, RGB_565, 4, 4}, + {THUMB_INVALID, -1, -1, -1, RGB_565, 4, 4} + }; + */ + plistDict *albumArtFormatDict = 0; + plistData *albumArtFormatKey = (plistKey *)albumArtArray->enumItem(i); + if (albumArtFormatKey->getType() == PLISTDATA_KEY) + { + albumArtFormatDict = (plistDict *)((plistKey *)albumArtFormatKey)->getData(); + } + else + { // Nano 4G doesn't store keys in the AlbumArt array + albumArtFormatDict = (plistDict *)albumArtFormatKey; + } + + int numKeys = albumArtFormatDict->getNumKeys(); + + if (numKeys) + { + for (int j=0; j<numKeys; j++) + { + plistKey *albumArtFormatItemKey = albumArtFormatDict->enumKey(j); + const wchar_t* albumArtFormatKeyName = albumArtFormatItemKey->getName(); + + // we need all the arwork formats under AlbumArt, just use + // a thumb type that we know is accepted + artworkFormats[i].type = THUMB_COVER_SMALL; + + // these are 4, they just are + artworkFormats[i].row_align = 4; + artworkFormats[i].image_align = 4; + + // gather the FormatId + if (CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, albumArtFormatKeyName, -1, L"FormatId", -1)-2 == 0) + { + const wchar_t* albumArtFormatValue = albumArtFormatItemKey->getData()->getString(); + if (albumArtFormatValue != NULL) + { + artworkFormats[i].correlation_id = _wtoi(albumArtFormatValue); + } + } + // gather the RenderWidth + if (CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, albumArtFormatKeyName, -1, L"RenderWidth", -1)-2 == 0) + { + const wchar_t* albumArtFormatValue = albumArtFormatItemKey->getData()->getString(); + if (albumArtFormatValue != NULL) + { + artworkFormats[i].width = _wtoi(albumArtFormatValue); + } + } + // gather the RenderHeight + if (CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, albumArtFormatKeyName, -1, L"RenderHeight", -1)-2 == 0) + { + const wchar_t* albumArtFormatValue = albumArtFormatItemKey->getData()->getString(); + if (albumArtFormatValue != NULL) + { + artworkFormats[i].height = _wtoi(albumArtFormatValue); + } + } + // gather the PixelFormat + if (CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, albumArtFormatKeyName, -1, L"PixelFormat", -1)-2 == 0) + { + const wchar_t* albumArtFormatValue = albumArtFormatItemKey->getData()->getString(); + if (albumArtFormatValue != NULL) + { + artworkFormats[i].format = RGB_565; + } + } + } + } + } + } + } + retInfo->family_id = familyID; + return retInfo; + } + } + + } + } + } // end familyid + } + } // end plist parser + } + + for (int yy=0; yy<2; yy++) + { + wchar_t sysinfo[] = {drive,L":\\iPod_Control\\Device\\SysInfo"}; + + FILE *f = _wfopen(sysinfo,L"rt"); + if (f) + { + wchar_t *modelnr=NULL; + wchar_t buf[1024] = {0}; + while (fgetws(buf,1024,f)) + { + int len = wcslen(buf); + //snip off trailing newline + if (len>0 && buf[len-1]==10) + { + buf[len-1]=0; len--; + } + wchar_t *colon = wcschr(buf,L':'); + if (colon) + { + *colon=0; + if (!wcscmp(L"ModelNumStr",buf)) // found ModelNumStr line.. + { + modelnr = colon+1; + while (modelnr && *modelnr == L' ') modelnr++; + if (!(*modelnr >= L'0' && *modelnr <= L'9')) modelnr++; + break; // modelnr found, so we're done + } + } + } + + fclose(f); + + if (modelnr && *modelnr) + { + const iPodModelInfo *info = GetiPodInfoForModelStr(modelnr); + if (info) + { + iPodInfo* retInfo = new iPodInfo(info); + if (have_fwid) + retInfo->SetFWID(fwid); + + return retInfo; + } + } + } + if (!yy) + { + int d = WASABI_API_DIALOGBOXPARAM(IDD_SELECTIPODTYPE,plugin.hwndWinampParent,selectipodtype_dlgproc,(LPARAM)sysinfo); + if (d) return NULL; + } + } + return new iPodInfo(&unknown); +} + +const ArtworkFormat* GetArtworkFormats(const iPodInfo* info) +{ + if (!info) return NULL; + return ipod_artwork_info_table[info->model]; +} + diff --git a/Src/Plugins/Portable/pmp_ipod/iPodInfo.h b/Src/Plugins/Portable/pmp_ipod/iPodInfo.h new file mode 100644 index 00000000..7414e004 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/iPodInfo.h @@ -0,0 +1,97 @@ +#ifndef _IPOD_INFO_H_ +#define _IPOD_INFO_H_ + +#define RGB_565 0 +#define RGB_555 1 +#define RGB_555_REC 2 +typedef enum { + THUMB_INVALID = -1, + THUMB_COVER_SMALL, + THUMB_COVER_MEDIUM1, + THUMB_COVER_MEDIUM2, + THUMB_COVER_MEDIUM3, + THUMB_COVER_MEDIUM4, + THUMB_COVER_LARGE, + THUMB_PHOTO_SMALL, + THUMB_PHOTO_LARGE, + THUMB_PHOTO_FULL_SCREEN, + THUMB_PHOTO_TV_SCREEN, +} ThumbType; + +typedef enum { + IPOD_COLOR_WHITE, + IPOD_COLOR_BLACK, + IPOD_COLOR_SILVER, + IPOD_COLOR_BLUE, + IPOD_COLOR_PINK, + IPOD_COLOR_GREEN, + IPOD_COLOR_ORANGE, + IPOD_COLOR_GOLD, + IPOD_COLOR_RED, + IPOD_COLOR_U2, +} iPodColor; + +typedef enum { + IPOD_MODEL_INVALID=0, + IPOD_MODEL_COLOR=1, + IPOD_MODEL_REGULAR=2, + IPOD_MODEL_MINI=3, + IPOD_MODEL_SHUFFLE=4, + IPOD_MODEL_VIDEO=5, + IPOD_MODEL_NANO=6, + IPOD_MODEL_CLASSIC=7, + IPOD_MODEL_FATNANO=8, + IPOD_MODEL_TOUCH=9, +} iPodModel; + +typedef struct { + ThumbType type; + int width; + int height; + int correlation_id; + int format; + int row_align; + int image_align; +} ArtworkFormat; + +struct iPodModelInfo +{ + // model_number is abbreviated: if the first character is not numeric, it is ommited. e.g. "MA350 -> A350", "M9829 -> 9829" + const wchar_t *model_number; + iPodModel model; + iPodColor color; + int image16; + int image160; +}; + +class iPodInfo +{ +public: + iPodInfo(const iPodModelInfo *model); + ~iPodInfo(); + void SetFWID(const uint8_t *new_fwid); + int family_id; + wchar_t *model_number; + iPodModel model; + iPodColor color; + int image16; + int image160; + // Store the supported artwork formats if we + // can dynamically read it from the extended sysinfo xml + ArtworkFormat* supportedArtworkFormats; + size_t numberOfSupportedFormats; + unsigned char *fwid; + unsigned int shadow_db_version; +}; + +struct _iPodSerialToModel { + const wchar_t *serial; + const wchar_t *model_number; +}; +typedef struct _iPodSerialToModel iPodSerialToModel; + +iPodInfo *GetiPodInfo(wchar_t drive); + +const ArtworkFormat* GetArtworkFormats(const iPodInfo* info); + +#endif //_IPOD_INFO_H_ diff --git a/Src/Plugins/Portable/pmp_ipod/iPodSD.cpp b/Src/Plugins/Portable/pmp_ipod/iPodSD.cpp new file mode 100644 index 00000000..098473cf --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/iPodSD.cpp @@ -0,0 +1,523 @@ +#include "iPodSD.h" +#include <math.h> +#include <assert.h> +// get 3 bytes from data (used in iTunesSD1) +static __forceinline unsigned long get3(const uint8_t * data) +{ + unsigned long ret = 0; + ret += ((unsigned long) data[0]) << 16; + ret += ((unsigned long) data[1]) << 8; + ret += ((unsigned long) data[2]); + return ret; +} +//write 3 bytes normal (used in iTunesSD1) +static __forceinline void put3(const unsigned long number, uint8_t * data) +{ + data[0] = (uint8_t)(number >> 16) & 0xff; + data[1] = (uint8_t)(number >> 8) & 0xff; + data[2] = (uint8_t)number & 0xff; +} + +// pass data and ptr, updates ptr automatically (by reference) +static __forceinline void write_uint64_t(uint8_t *data, size_t &offset, uint64_t value) +{ + memcpy(&data[offset], &value, 8); + offset+=8; +} + +// pass data and ptr, updates ptr automatically (by reference) +static __forceinline void write_uint32_t(uint8_t *data, size_t &offset, uint32_t value) +{ + memcpy(&data[offset], &value, 4); + offset+=4; +} + +// pass data and ptr, updates ptr automatically (by reference) +static __forceinline void write_uint16_t(uint8_t *data, size_t &offset, uint16_t value) +{ + memcpy(&data[offset], &value, 2); + offset+=2; +} + + +// pass data and ptr, updates ptr automatically (by reference) +static __forceinline void write_uint8_t(uint8_t *data, size_t &offset, uint8_t value) +{ + data[offset++] = value; +} + +// pass data and ptr, updates ptr automatically (by reference) +static __forceinline void write_header(uint8_t *data, size_t &offset, const char *header) +{ + data[offset++] = header[0]; + data[offset++] = header[1]; + data[offset++] = header[2]; + data[offset++] = header[3]; +} + + +// Case insensitive version of wcsstr +static wchar_t *wcsistr (const wchar_t *s1, const wchar_t *s2) +{ + wchar_t *cp = (wchar_t*) s1; + wchar_t *s, *t, *endp; + wchar_t l, r; + + endp = (wchar_t*)s1 + ( lstrlen(s1) - lstrlen(s2)) ; + while (cp && *cp && (cp <= endp)) + { + s = cp; + t = (wchar_t*)s2; + while (s && *s && t && *t) + { + l = towupper(*s); + r = towupper(*t); + if (l != r) + break; + s++, t++; + } + + if (*t == 0) + return cp; + + cp = CharNext(cp); + } + + return NULL; +} +////////////////////////////////////////////////////////////////////// +// iTunesSD1 - Classes for dealing with the iPodShuffle +////////////////////////////////////////////////////////////////////// + +iTunesSD1::iTunesSD1() +{ +} + +iTunesSD1::~iTunesSD1() +{ +} + + +long iTunesSD1::write(const iPod_mhlt::mhit_map_t *songs, unsigned char * data, const unsigned long datasize) +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iTunesSD_write); +#endif + + const unsigned int numsongs = songs->size(); + const unsigned int total_size = 18 + (numsongs * 558); + ASSERT(datasize >= total_size); + if(datasize < total_size) + return(-1); + + long ptr=0; + + put3(numsongs, &data[ptr]); + ptr+=3; + + put3(0x010600, &data[ptr]); + ptr+=3; + + put3(0x12, &data[ptr]); + ptr+=3; + + put3(0, &data[ptr]); + ptr+=3; + put3(0, &data[ptr]); + ptr+=3; + put3(0, &data[ptr]); + ptr+=3; + + iPod_mhlt::mhit_map_t::const_iterator begin = songs->begin(); + iPod_mhlt::mhit_map_t::const_iterator end = songs->end(); + for(iPod_mhlt::mhit_map_t::const_iterator it = begin; it != end; it++) + { + iPod_mhit *m = ((*it).second); + iTunesSD_Song song(m); + long ret = song.write(&data[ptr], datasize - ptr); + if (ret < 0) + return ret; + ptr += ret; + } + + return(ptr); +} + + +iTunesSD_Song::iTunesSD_Song(const iPod_mhit *m) : size_total(0x22e), starttime(0), stoptime(0), volume(0x64), filetype(0), playflags(iTunesSD_Song::SHUFFLE) +{ + memset(filename, 0, (SDSONG_FILENAME_LEN + 1) * sizeof(wchar_t)); + + + iPod_mhod *mhod = m->FindString(MHOD_LOCATION); + ASSERT(mhod); + if(mhod) + { + // Convert from HFS format (:iPod_Control:Music:F00:filename) to quasi-FAT format (/iPod_Control/Music/F00/filename) filepaths + SetFilename(mhod->str); + wchar_t *w = filename; + while(w && *w != '\0') + { + if(*w == ':') + *w = '/'; + + w = CharNext(w); + } + + + SetStartTime(m->starttime); + SetStopTime(m->stoptime); + + int volume = (int)m->volume; + + // If Sound Check information is present, use that instead of volume + if(m->soundcheck != 0) + { + // This code converts SoundCheck back into a gain value, then into a -255 to 255 mhit::volume value + const double gain = -10.0 * log10(m->soundcheck / 1000.0); + volume = (int)(gain * 12.75); // XXX - this might not be the best way to convert the gain value... + } + + if(volume < -255) + volume = -255; + else if(volume > 255) + volume = 255; + + // Convert the volume value into a percentage for SetVolume + SetVolume((int)((double)volume / 2.55)); + + + // To determine the filetype, first check the MHOD_FILETYPE type. If that isn't available, fallback to file extension + iPod_mhod *mtype = m->FindString(MHOD_FILETYPE); + if(mtype != NULL) + { + if(wcsistr(mtype->str, L"MPEG") != NULL || wcsistr(mtype->str, L"MP3") != NULL) + filetype = iTunesSD_Song::MP3; + else if(wcsistr(mtype->str, L"AAC") != NULL) + filetype = iTunesSD_Song::AAC; + else if(wcsistr(mtype->str, L"WAV") != NULL) + filetype = iTunesSD_Song::WAV; + } + if(filetype == 0) + { + if(wcsistr(mhod->str, L".mp3") != NULL) + filetype = iTunesSD_Song::MP3; + else if(wcsistr(mhod->str, L".m4a") != NULL || wcsistr(mhod->str, L".m4b") != NULL || wcsistr(mhod->str, L".m4p") != NULL) + filetype = iTunesSD_Song::AAC; + else if(wcsistr(mhod->str, L".wav") != NULL) + filetype = iTunesSD_Song::WAV; + } + ASSERT(filetype != 0); + if(filename == 0) + filetype = iTunesSD_Song::MP3; // Default to mp3 + + + if(wcsistr(mhod->str, L".m4b") != NULL) + playflags = iTunesSD_Song::BOOKMARKABLE; // Only playback in normal mode + else + playflags = iTunesSD_Song::SHUFFLE; // Playable in normal/shuffle modes, but not bookmarkable + } +} + + +long iTunesSD_Song::write(unsigned char * data, const unsigned long datasize) +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iTunesSD_Song_write); +#endif + long ptr=0; + + ASSERT(size_total == 0x22e); + ASSERT(filetype != 0); + + put3(size_total, &data[ptr]); + ptr+=3; + + put3(0x005aa501, &data[ptr]); + + ptr+=3; + put3(starttime, &data[ptr]); + ptr+=3; + put3(0, &data[ptr]); + ptr+=3; + put3(0, &data[ptr]); + ptr+=3; + put3(stoptime, &data[ptr]); + ptr+=3; + put3(0, &data[ptr]); + ptr+=3; + put3(0, &data[ptr]); + ptr+=3; + put3(volume, &data[ptr]); + ptr+=3; + put3(filetype, &data[ptr]); + ptr+=3; + put3(0x200, &data[ptr]); + ptr+=3; + + const unsigned int bufSize = (SDSONG_FILENAME_LEN + 1) * sizeof(wchar_t); + memcpy(&data[ptr], filename, bufSize); + ptr+=bufSize; + + put3(playflags, &data[ptr]); + ptr+=3; + + ASSERT(size_total == ptr); + return(ptr); +} + +void iTunesSD_Song::SetFilename(const wchar_t *filename) +{ +#ifdef IPODDB_PROFILER + profiler(iPodDB__iTunesStats_SetFilename); +#endif + + ASSERT(filename != NULL); + if(filename == NULL) + return; + + if(filename) + { + lstrcpyn(this->filename, filename, SDSONG_FILENAME_LEN); + } + else + { + memset(this->filename, 0, SDSONG_FILENAME_LEN * sizeof(wchar_t)); + } +} + +// Accepts values from -100 to 100, with 0 meaning no volume change +void iTunesSD_Song::SetVolume(const int percent) +{ + int p = percent; + if(p > 100) + p = 100; + else if(p < -100) + p = -100; + + // Volume ranges from 0 (-100%) to 100 (0%) to 200 (100%) + volume = (unsigned int)(percent + 100); +} + + +/* Shadow DB version 2 */ +long iTunesSD2::write(const iPod_mhlt *songs, const iPod_mhlp *playlists, unsigned char * data, const unsigned long datasize) +{ + uint32_t numsongs = songs->GetChildrenCount(); + uint32_t numplaylists = playlists->GetChildrenCount(); + size_t offset=0; + size_t ptr=0; + + if (datasize < 64) + return -1; + + write_header(data, ptr, "bdhs"); + write_uint32_t(data, ptr, 0x02000003); /* also have seen 0x02010001, perhaps a DB version number? */ + write_uint32_t(data, ptr, 64); /* length of header */ + write_uint32_t(data, ptr, numsongs); + write_uint32_t(data, ptr, numplaylists); /* number of playlists */ + write_uint32_t(data, ptr, 0); + write_uint32_t(data, ptr, 0); + write_uint8_t(data, ptr, 0); /* volume limit */ + write_uint8_t(data, ptr, 1); /* voiceover */ + write_uint16_t(data, ptr, 0); + write_uint32_t(data, ptr, numsongs); /* TODO number of tracks w/o podcasts and audiobooks*/ + write_uint32_t(data, ptr, 64); /* track header offset */ + write_uint32_t(data, ptr, 64+20 + numsongs*4+iTunesSD2_Song::header_size*numsongs); /* playlist header offset */ + + write_uint32_t(data, ptr, 0); + write_uint32_t(data, ptr, 0); + write_uint32_t(data, ptr, 0); + write_uint32_t(data, ptr, 0); + write_uint32_t(data, ptr, 0); + offset = 64; + + uint32_t hths_header_size = 20+numsongs*4; + if (datasize - ptr < hths_header_size) + return -1; + write_header(data, ptr, "hths"); + write_uint32_t(data, ptr, hths_header_size); /* header length */ + write_uint32_t(data, ptr, numsongs); + write_uint32_t(data, ptr, 0); + write_uint32_t(data, ptr, 0); + offset += hths_header_size; + /* positions for each track */ + for (size_t i=0;i<numsongs;i++) + { + write_uint32_t(data, ptr, offset + iTunesSD2_Song::header_size * i); + } + + /* write tracks */ + for (uint32_t i=0;i<numsongs;i++) + { + iPod_mhit *m = songs->GetTrack(i); + long ret = iTunesSD2_Song::write(m, &data[ptr], datasize - ptr); + if (ret < 0) + return ret; + ptr += ret; + offset += ret; + } + + uint32_t podcast_playlist_count=0; + for (size_t i=0;i<numplaylists;i++) + { + iPod_mhyp *p = playlists->GetPlaylist(i); + if (p->podcastflag) + podcast_playlist_count++; + } + + uint32_t hphs_header_size = 20 + numplaylists*4; + if (datasize - ptr < hphs_header_size) + return -1; + + write_header(data, ptr, "hphs"); + write_uint32_t(data, ptr, hphs_header_size); /* header length */ + write_uint32_t(data, ptr, numplaylists); + write_uint16_t(data, ptr, 0); + write_uint16_t(data, ptr, numplaylists-podcast_playlist_count); /* non-podcast playlists */ + write_uint16_t(data, ptr, 1); /* master playlists */ + write_uint16_t(data, ptr, numplaylists); /* non-audiobook playlists */ + offset += hphs_header_size; + + /* write offsets for each track */ + for (size_t i=0;i<numplaylists;i++) + { + iPod_mhyp *p = playlists->GetPlaylist(i); + write_uint32_t(data, ptr, offset); + offset += p->GetMhipChildrenCount()*4 + 44; + } + + iPod_mhyp *master_playlist = playlists->GetPlaylist(0); + /* write playlists */ + for (size_t i=0;i<numplaylists;i++) + { + iPod_mhyp *p = playlists->GetPlaylist(i); + long ret = iTunesSD2_Playlist::write(master_playlist, p, &data[ptr], datasize - ptr); + if (ret < 0) + return ret; + ptr += ret; + } + return ptr; +} +uint32_t iTunesSD2_Song::header_size = 372; + +long iTunesSD2_Song::write(const iPod_mhit *mhit, unsigned char *data, const unsigned long datasize) +{ + if (datasize < header_size) + return -1; + + size_t ptr=0; + write_header(data, ptr, "rths"); + write_uint32_t(data, ptr, header_size); /* length of header */ + write_uint32_t(data, ptr, mhit->starttime); /* start time, in milliseconds */ + write_uint32_t(data, ptr, mhit->stoptime); /* stop time, in milliseconds */ + write_uint32_t(data, ptr, mhit->volume); /* volume */ + switch(mhit->filetype) + { + case FILETYPE_WAV: + write_uint32_t(data, ptr, iTunesSD_Song::WAV); /* file type */ + break; + case FILETYPE_M4A: + case 0x4d344220: + case 0x4d345020: + write_uint32_t(data, ptr, iTunesSD_Song::AAC); /* file type */ + break; + case FILETYPE_MP3: + default: + write_uint32_t(data, ptr, iTunesSD_Song::MP3); /* file type */ + break; + } + + iPod_mhod *mhod = mhit->FindString(MHOD_LOCATION); + + char filename[256] = {0}; + int converted = WideCharToMultiByte(CP_UTF8, 0, mhod->str, -1, filename, 256, 0, 0); + for (int i=0;i<converted;i++) + { + if (filename[i] == ':') + filename[i] = '/'; + } + memcpy(&data[ptr], filename, converted); + ptr+=converted; + memset(&data[ptr], 0, 256-converted); + ptr+=256-converted; + + write_uint32_t(data, ptr, mhit->bookmarktime); /* bookmark time */ + write_uint8_t(data, ptr, 0); /* skip flag */ + write_uint8_t(data, ptr, mhit->rememberPosition); /* remember playback position */ + write_uint8_t(data, ptr, 0); /* part of gapless album */ + write_uint8_t(data, ptr, 0); + write_uint32_t(data, ptr, mhit->pregap); /* pre-gap */ + write_uint32_t(data, ptr, mhit->postgap); /* post-gap */ + write_uint64_t(data, ptr, mhit->samplecount); /* number of samples */ + write_uint32_t(data, ptr, mhit->gaplessData); /* gapless data */ + write_uint32_t(data, ptr, 0); + write_uint32_t(data, ptr, mhit->album_id); /* album ID */ + write_uint16_t(data, ptr, mhit->tracknum); /* track number */ + write_uint16_t(data, ptr, mhit->cdnum); /* disc number */ + write_uint32_t(data, ptr, 0); + write_uint32_t(data, ptr, 0); + write_uint64_t(data, ptr, mhit->dbid); /* dbid */ + write_uint32_t(data, ptr, 0); /* artist ID */ + write_uint32_t(data, ptr, 0); + write_uint32_t(data, ptr, 0); + write_uint32_t(data, ptr, 0); + write_uint32_t(data, ptr, 0); + write_uint32_t(data, ptr, 0); + write_uint32_t(data, ptr, 0); + write_uint32_t(data, ptr, 0); + write_uint32_t(data, ptr, 0); + + return ptr; +} + +long iTunesSD2_Playlist::write(const iPod_mhyp *master_playlist, const iPod_mhyp *playlist, unsigned char * data, const unsigned long datasize) +{ + size_t ptr=0; + uint32_t tracks = playlist->GetMhipChildrenCount(); + uint32_t header_size = 44 + tracks*4; + if (datasize < header_size) + return -1; + + write_header(data, ptr, "lphs"); + write_uint32_t(data, ptr, header_size); /* header length */ + write_uint32_t(data, ptr, tracks); /* number of tracks */ + + /* number of music tracks TODO: special handling for master playlist */ + if (playlist->podcastflag) + write_uint32_t(data, ptr, 0); + else + write_uint32_t(data, ptr, tracks); + + write_uint64_t(data, ptr, playlist->playlistID); /* playlist ID */ + + /* playlist type */ + if (playlist->podcastflag) + write_uint32_t(data, ptr, 3); /* podcast */ + else if (playlist->hidden) + write_uint32_t(data, ptr, 1); /* master playlist */ + else + write_uint32_t(data, ptr, 2); /* normal */ + write_uint32_t(data, ptr, 0); + write_uint32_t(data, ptr, 0); + write_uint32_t(data, ptr, 0); + write_uint32_t(data, ptr, 0); + if (master_playlist == playlist) + { + for (uint32_t i=0;i<tracks;i++) + { + write_uint32_t(data, ptr, i); + } + } + else + { + for (uint32_t i=0;i<tracks;i++) + { + iPod_mhip *item = playlist->GetPlaylistEntry(i); + uint32_t master_index = master_playlist->FindPlaylistEntry(item->songindex); + assert(master_index != -1); + write_uint32_t(data, ptr, master_index); + } + } + return ptr; +} diff --git a/Src/Plugins/Portable/pmp_ipod/iPodSD.h b/Src/Plugins/Portable/pmp_ipod/iPodSD.h new file mode 100644 index 00000000..8d2fc6f1 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/iPodSD.h @@ -0,0 +1,78 @@ +#pragma once +#include <bfc/platform/types.h> +#include "iPodDB.h" +/* iPod shuffle Shadow Database code */ + +// iTunesSD (iPod Shuffle) Database Classes +class iTunesSD_Song; +class iTunesSD2_Song; + + +class iTunesSD1 +{ +public: + iTunesSD1(); + ~iTunesSD1(); + + long write(const iPod_mhlt::mhit_map_t *mhit, unsigned char * data, const unsigned long datasize); +}; + +class iTunesSD2 +{ +public: + long write(const iPod_mhlt *mhit, const iPod_mhlp *playlists, unsigned char * data, const unsigned long datasize); +}; + +#define SDSONG_FILENAME_LEN 260 + + +class iTunesSD_Song +{ +public: + iTunesSD_Song(const iPod_mhit *mhit); + enum FileType + { + MP3 = 0x01, + AAC = 0x02, + WAV = 0x04 + }; + + enum PlayFlags + { + UNKNOWN = 0x000001, // Might do something special, but nothing has been observed so far + BOOKMARKABLE = 0x000100, // Any song that has flag is bookmarked + SHUFFLE = 0x010000 // Only songs that have this flag are available in shuffle playback mode + }; + + + + + long write(unsigned char * data, const unsigned long datasize); + + void SetFilename(const wchar_t *filename); + void SetStartTime(const double milliseconds) { starttime = (unsigned int)(milliseconds / 256.0); } + void SetStopTime(const double milliseconds) { stoptime = (unsigned int)(milliseconds / 256.0); } + void SetVolume(const int percent); + + // These are also only 3 byte values + uint32_t size_total; + uint32_t starttime; + uint32_t stoptime; + uint32_t volume; // -100% = 0x0, 0% = 0x64 (100), 100% = 0xc8 (200) + uint32_t filetype; // 0x01 = MP3, 0x02 = AAC, 0x04 = WAV + wchar_t filename[SDSONG_FILENAME_LEN + 1]; // Equal to Windows' MAX_PATH, plus the trailing NULL (261 wide chars = 522 bytes) + unsigned int playflags; +}; + +class iTunesSD2_Song +{ +public: + static long write(const iPod_mhit *mhit, unsigned char * data, const unsigned long datasize); + static uint32_t header_size; +}; + +class iTunesSD2_Playlist +{ +public: + static long write(const iPod_mhyp *master_playlist, const iPod_mhyp *playlist, unsigned char * data, const unsigned long datasize); +}; diff --git a/Src/Plugins/Portable/pmp_ipod/main.cpp b/Src/Plugins/Portable/pmp_ipod/main.cpp new file mode 100644 index 00000000..c1df71fe --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/main.cpp @@ -0,0 +1,336 @@ +//#define PLUGIN_NAME "Nullsoft iPod Plug-in" +#define PLUGIN_VERSION L"0.91" + +#include "iPodDevice.h" +#include <Dbt.h> +#include "..\..\General\gen_ml/itemlist.h" +#include <api/service/waservicefactory.h> +#include "api.h" +#include "../nu/autoLock.h" +#include "deviceprovider.h" +#include <tataki/export.h> +#include <shlwapi.h> + +int init(); +void quit(); +intptr_t MessageProc(int msg, intptr_t param1, intptr_t param2, intptr_t param3); + +extern PMPDevicePlugin plugin = {PMPHDR_VER,0,init,quit,MessageProc}; + +static DWORD WINAPI ThreadFunc_DeviceChange(LPVOID lpParam); + +bool g_detectAll=false; + +std::vector<iPodDevice*> iPods; + +api_config *AGAVE_API_CONFIG=0; +api_memmgr *WASABI_API_MEMMGR=0; +api_albumart *AGAVE_API_ALBUMART=0; +api_threadpool *WASABI_API_THREADPOOL=0; +api_application *WASABI_API_APP=0; +api_devicemanager *AGAVE_API_DEVICEMANAGER = 0; + +// wasabi based services for localisation support +api_language *WASABI_API_LNG = 0; + +HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; + +static Nullsoft::Utility::LockGuard connect_guard; +static DeviceProvider *deviceProvider = NULL; +static bool loading_devices[26] = {0,}; + +template <class api_T> +void ServiceBuild(api_T *&api_t, GUID factoryGUID_t) +{ + if (plugin.service) + { + waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t); + if (factory) + api_t = reinterpret_cast<api_T *>( factory->getInterface() ); + } +} + +template <class api_T> +void ServiceRelease(api_T *api_t, GUID factoryGUID_t) +{ + if (plugin.service && api_t) + { + waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t); + if (factory) + factory->releaseInterface(api_t); + } + api_t = NULL; +} + +static bool createBlankDatabase(wchar_t drive) { + wchar_t ipodtest[]={drive,L":\\iPod_Control\\iTunes\\iTunesDB"}; + iPod_mhbd db; + BYTE data[4096] = {0}; + long l = db.write(data,sizeof(data)); + if(l <= 0) return false; + FILE* fh=_wfopen(ipodtest,L"wb"); + if(!fh) return false; + if(fwrite(data,l,1,fh)) + { + wchar_t ipodtest2[]={drive,L":\\iPod_Control\\iTunes\\firsttime"}; + _wunlink(ipodtest2); + } + fclose(fh); + wchar_t music[] = {drive,L":\\iPod_Control\\Music"}; + CreateDirectoryW(music,NULL); + return true; +} + + +bool ConnectDrive(wchar_t drive, bool connect) +{ + bool result; + size_t index; + iPodDevice *device; + + Nullsoft::Utility::AutoLock connect_lock(connect_guard); + + // capitalize + if (drive >= L'a' && drive <= L'z') + drive = drive - 32; + + // reject invalid drive letters + if (drive < L'A' || drive > L'Z') + return false; + + if (false != loading_devices[drive-'A']) + return false; + + + loading_devices[drive-'A'] = true; + + if (false != connect) + { + result = true; + + char ipodtest[]= {(char)drive,":\\iPod_Control\\iTunes\\iTunesDB"}; + if (GetFileAttributes(ipodtest) == INVALID_FILE_ATTRIBUTES) + { + char ipodtest2[]={(char)drive,":\\iPod_Control\\iTunes\\firsttime"}; + if (GetFileAttributes(ipodtest2) == INVALID_FILE_ATTRIBUTES || + false == createBlankDatabase(drive)) + { + result = false; + } + } + + if (false != result) + { + index = iPods.size(); + while(index--) + { + device = iPods[index]; + if(device->drive == drive) + break; + } + + if((size_t)-1 == index) + { + iPodDevice *d = new iPodDevice((char)drive); + } + else + result = false; + } + } + else + { + result = false; + + index = iPods.size(); + while(index--) + { + device = iPods.at(index); + if (device->drive == drive) + { + SendNotifyMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)device,PMP_IPC_DEVICEDISCONNECTED); + iPods.erase(iPods.begin() + index); + delete device; + result = true; + break; + } + } + } + + loading_devices[drive-'A'] = false; + + return result; +} + + +static void autoDetectCallback(wchar_t driveW, UINT type) +{ + if(false == g_detectAll && DRIVE_REMOVABLE != type) + return; + + ConnectDrive(driveW, true); +} + +int init() +{ + wchar_t mlipod[MAX_PATH] = {0}; + PathCombineW(mlipod, (LPCWSTR)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GETPLUGINDIRECTORYW), L"ml_ipod.dll"); + FILE * f = _wfopen(mlipod, L"rb"); + if (f) { + fclose(f); + return -1; + } + + Tataki::Init(plugin.service); + + ServiceBuild(AGAVE_API_CONFIG, AgaveConfigGUID); + ServiceBuild(WASABI_API_LNG, languageApiGUID); + ServiceBuild(WASABI_API_MEMMGR, memMgrApiServiceGuid); + ServiceBuild(AGAVE_API_ALBUMART, albumArtGUID); + ServiceBuild(WASABI_API_THREADPOOL, ThreadPoolGUID); + ServiceBuild(WASABI_API_APP, applicationApiServiceGuid); + ServiceBuild(AGAVE_API_DEVICEMANAGER, DeviceManagerGUID); + + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG(plugin.hDllInstance,PmpIPODLangGUID); + + static wchar_t szDescription[256]; + swprintf(szDescription, ARRAYSIZE(szDescription), + WASABI_API_LNGSTRINGW(IDS_NULLSOFT_IPOD_PLUGIN), PLUGIN_VERSION); + plugin.description = szDescription; + + if (NULL != AGAVE_API_DEVICEMANAGER && + NULL == deviceProvider) + { + if (SUCCEEDED(DeviceProvider::CreateInstance(&deviceProvider)) && + FAILED(deviceProvider->Register(AGAVE_API_DEVICEMANAGER))) + { + deviceProvider->Release(); + deviceProvider = NULL; + } + } + + /* Our device shows up as a normal drive */ + if (NULL == deviceProvider || + FAILED(deviceProvider->BeginDiscovery(AGAVE_API_DEVICEMANAGER))) + { + SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(WPARAM)autoDetectCallback,PMP_IPC_ENUM_ACTIVE_DRIVES); + } + + return 0; +} + +void quit() +{ + ServiceRelease(AGAVE_API_CONFIG, AgaveConfigGUID); + ServiceRelease(WASABI_API_LNG, languageApiGUID); + ServiceRelease(WASABI_API_MEMMGR, memMgrApiServiceGuid); + ServiceRelease(AGAVE_API_ALBUMART, albumArtGUID); + ServiceRelease(WASABI_API_THREADPOOL, ThreadPoolGUID); + ServiceRelease(WASABI_API_APP, applicationApiServiceGuid); + ServiceRelease(AGAVE_API_DEVICEMANAGER, DeviceManagerGUID); + + Tataki::Quit(); +} + +static char FirstDriveFromMask(ULONG unitmask) { + char i; + for(i=0; i<26; ++i) { + if(unitmask & 0x1) break; + unitmask = unitmask >> 1; + } + return (i+'A'); +} + + +int wmDeviceChange(WPARAM wParam, LPARAM lParam) +{ + if(wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE) + { + PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam; + if(lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) + { // its a volume + PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb; + if(0 == ((DBTF_MEDIA | DBTF_NET) & lpdbv->dbcv_flags) || g_detectAll) + { // its not a network drive or a CD/floppy, game on! + unsigned long unitmask = lpdbv->dbcv_unitmask; + for (int i = 0; i < 26; i++) + { + if (0 != (0x1 & unitmask)) + { + int p = (int)('A' + i); + if (DBT_DEVICEARRIVAL == wParam) + p += 0x10000; + + ThreadFunc_DeviceChange((void*)(intptr_t)p); + + unitmask = unitmask >> 1; + if (0 == unitmask) + break; + } + else + unitmask = unitmask >> 1; + } + } + } + } + return 0; +} + + +static DWORD WINAPI ThreadFunc_DeviceChange(LPVOID lpParam) +{ + int p = (int)lpParam; + bool connect = p > 0x10000; + + if(connect) + p -= 0x10000; + + char drive = (char)p; + if(drive == 0) + return 0; + + if(connect) + { // something plugged in + ConnectDrive(drive, connect); + } + else + { //something removed + size_t index; + + index = iPods.size(); + while(index--) + { + iPodDevice *device = iPods.at(index); + if (device->drive == drive) + { + SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)device,PMP_IPC_DEVICEDISCONNECTED); + iPods.erase(iPods.begin() + index); + delete device; + break; + } + } + } + return 0; +} + + +intptr_t MessageProc(int msg, intptr_t param1, intptr_t param2, intptr_t param3) { + switch(msg) { + case PMP_DEVICECHANGE: + return wmDeviceChange(param1,param2); + case PMP_NO_CONFIG: + return TRUE; + case PMP_CONFIG: + return 0; + } + return 0; +} + +extern "C" { + __declspec( dllexport ) PMPDevicePlugin * winampGetPMPDevicePlugin(){return &plugin;} + __declspec( dllexport ) int winampUninstallPlugin(HINSTANCE hDllInst, HWND hwndDlg, int param) { + int i = iPods.size(); + while(i-- > 0) iPods[i]->Close(); + return PMP_PLUGIN_UNINSTALL_NOW; + } +};
\ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_ipod/pmp_ipod.rc b/Src/Plugins/Portable/pmp_ipod/pmp_ipod.rc new file mode 100644 index 00000000..6bcfbacb --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/pmp_ipod.rc @@ -0,0 +1,194 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "#include ""version.rc2""\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// PNG +// + +IDB_CLASSIC_16 PNG "resources\\apple_ipod_classic_16.png" +IDB_NANO1G_16 PNG "resources\\apple_ipod_nano_1g_16.png" +IDB_NANO2G_16 PNG "resources\\apple_ipod_nano_2g_16.png" +IDB_NANO3G_16 PNG "resources\\apple_ipod_nano_3g_16.png" +IDB_NANO4G_16 PNG "resources\\apple_ipod_nano_4g_16.png" +IDB_NANO5G_16 PNG "resources\\apple_ipod_nano_5g_16.png" +IDB_SHUFFLE1G_16 PNG "resources\\apple_ipod_shuffle_1g_16.png" +IDB_SHUFFLE2G_16 PNG "resources\\apple_ipod_shuffle_2g_16.png" +IDB_SHUFFLE3G_16 PNG "resources\\apple_ipod_shuffle_3g_16.png" +IDB_CLASSIC_160 PNG "resources\\apple_ipod_classic.png" +IDB_NANO1G_160 PNG "resources\\apple_ipod_nano_1g.png" +IDB_NANO2G_160 PNG "resources\\apple_ipod_nano_2g.png" +IDB_NANO3G_160 PNG "resources\\apple_ipod_nano_3g.png" +IDB_NANO4G_160 PNG "resources\\apple_ipod_nano_4g.png" +IDB_NANO5G_160 PNG "resources\\apple_ipod_nano_5g.png" +IDB_SHUFFLE1G_160 PNG "resources\\apple_ipod_shuffle_1g.png" +IDB_SHUFFLE2G_160 PNG "resources\\apple_ipod_shuffle_2g.png" +IDB_SHUFFLE3G_160 PNG "resources\\apple_ipod_shuffle_3g.png" +IDB_SHUFFLE4G_160 PNG "resources\\apple_ipod_shuffle_4g.png" +IDB_SHUFFLE4G_16 PNG "resources\\apple_ipod_shuffle_4g_16.png" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_CONFIG DIALOGEX 0, 0, 264, 226 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_VISIBLE | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Gapless Playback",IDC_STATIC,4,3,255,44 + LTEXT "Some iPods support the gapless playback of files.\nTo enable this, Winamp needs to scan the files on your iPod. Click ""Scan"" to begin this process.",IDC_STATIC,12,14,179,24 + PUSHBUTTON "Scan",IDC_SCAN,201,19,50,14 + GROUPBOX "Album Art",IDC_STATIC_ARTGROUP,4,54,255,30,NOT WS_VISIBLE + CONTROL "Add album art to tracks during transfer",IDC_CHECK_USEART, + "Button",BS_AUTOCHECKBOX | NOT WS_VISIBLE | WS_TABSTOP,9,67,141,10 +END + +IDD_GAPSCAN DIALOGEX 0, 0, 186, 55 +STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Scanning for gapless playback information" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Scanning...",IDC_CAPTION,7,7,172,8 + CONTROL "",IDC_PROGRESS1,"msctls_progress32",WS_BORDER,7,17,172,14 + PUSHBUTTON "Scan in Background",IDC_BG,21,34,69,14 + PUSHBUTTON "Stop Scan",IDCANCEL,95,34,50,14 +END + +IDD_SELECTIPODTYPE DIALOGEX 0, 0, 186, 140 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_TOPMOST | WS_EX_APPWINDOW +CAPTION "Select iPod Type" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "In order to support some new features, we need to know which iPod model you have.",IDC_STATIC,7,7,172,17 + LTEXT "My iPod model is:",IDC_IPODINFO,7,27,172,8 + CONTROL "iPod Touch",IDC_RADIO6,"Button",BS_AUTORADIOBUTTON,7,40,51,10 + CONTROL "iPod Classic",IDC_RADIO7,"Button",BS_AUTORADIOBUTTON,7,50,53,10 + CONTROL "iPod Nano (widescreen)",IDC_RADIO8,"Button",BS_AUTORADIOBUTTON,7,59,91,10 + CONTROL "iPod Shuffle",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON,7,69,54,10 + CONTROL "iPod Photo",IDC_RADIO2,"Button",BS_AUTORADIOBUTTON,7,79,50,10 + CONTROL "iPod Video",IDC_RADIO3,"Button",BS_AUTORADIOBUTTON,7,89,49,10 + CONTROL "iPod Nano",IDC_RADIO4,"Button",BS_AUTORADIOBUTTON,7,99,48,10 + CONTROL "Other",IDC_RADIO5,"Button",BS_AUTORADIOBUTTON,7,109,35,10 + DEFPUSHBUTTON "OK",IDOK,67,119,50,14,WS_DISABLED + PUSHBUTTON "Cancel",IDCANCEL,127,119,52,14 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_GAPSCAN, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 48 + END + + IDD_SELECTIPODTYPE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 133 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_IPOD_PLUGIN "Nullsoft iPod Device Plug-in v%s" + 65535 "{C2EE3DA5-B29B-42a0-AB5E-B202393435D6}" +END + +STRINGTABLE +BEGIN + IDS_CANNOT_OPEN_FILE "Cannot open file" + IDS_CANNOT_CREATE_FILE "Cannot create file" + IDS_TRANSFERRING_PERCENT "Transferring %d%%" + IDS_CANCELLED "Cancelled" + IDS_DONE "Done" + IDS_TRANSFER_FAILED "Transfer failed" + IDS_IPOD_LOADING "iPod Loading..." + IDS_FAILED_TO_EJECT_IPOD + "Failed to Eject iPod. Is something else using it?" + IDS_ERROR "Error" + IDS_INVALID_TRACK "Invalid Track" + IDS_SCANNING "Scanning..." + IDS_ADVANCED "Advanced" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#include "version.rc2" + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/Src/Plugins/Portable/pmp_ipod/pmp_ipod.sln b/Src/Plugins/Portable/pmp_ipod/pmp_ipod.sln new file mode 100644 index 00000000..3e6362f4 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/pmp_ipod.sln @@ -0,0 +1,75 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29424.173 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pmp_ipod", "pmp_ipod.vcxproj", "{54C393C2-5A0A-497F-930B-DA5316351B00}" + ProjectSection(ProjectDependencies) = postProject + {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F} + {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1} = {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1} + {5ED1729B-EA41-4163-9506-741A8B76F625} = {5ED1729B-EA41-4163-9506-741A8B76F625} + {255B68B5-7EF8-45EF-A675-2D6B88147909} = {255B68B5-7EF8-45EF-A675-2D6B88147909} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bfc", "..\Wasabi\bfc\bfc.vcxproj", "{D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plist", "..\plist\plist.vcxproj", "{5ED1729B-EA41-4163-9506-741A8B76F625}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tataki", "..\tataki\tataki.vcxproj", "{255B68B5-7EF8-45EF-A675-2D6B88147909}" + ProjectSection(ProjectDependencies) = postProject + {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nsutil", "..\nsutil\nsutil.vcxproj", "{DABE6307-F8DD-416D-9DAC-673E2DECB73F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {54C393C2-5A0A-497F-930B-DA5316351B00}.Debug|Win32.ActiveCfg = Debug|Win32 + {54C393C2-5A0A-497F-930B-DA5316351B00}.Debug|Win32.Build.0 = Debug|Win32 + {54C393C2-5A0A-497F-930B-DA5316351B00}.Debug|x64.ActiveCfg = Debug|x64 + {54C393C2-5A0A-497F-930B-DA5316351B00}.Debug|x64.Build.0 = Debug|x64 + {54C393C2-5A0A-497F-930B-DA5316351B00}.Release|Win32.ActiveCfg = Release|Win32 + {54C393C2-5A0A-497F-930B-DA5316351B00}.Release|Win32.Build.0 = Release|Win32 + {54C393C2-5A0A-497F-930B-DA5316351B00}.Release|x64.ActiveCfg = Release|x64 + {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|Win32.ActiveCfg = Debug|Win32 + {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|Win32.Build.0 = Debug|Win32 + {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|x64.ActiveCfg = Debug|x64 + {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|Win32.ActiveCfg = Release|Win32 + {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|Win32.Build.0 = Release|Win32 + {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|x64.ActiveCfg = Release|x64 + {5ED1729B-EA41-4163-9506-741A8B76F625}.Debug|Win32.ActiveCfg = Debug|Win32 + {5ED1729B-EA41-4163-9506-741A8B76F625}.Debug|Win32.Build.0 = Debug|Win32 + {5ED1729B-EA41-4163-9506-741A8B76F625}.Debug|x64.ActiveCfg = Debug|x64 + {5ED1729B-EA41-4163-9506-741A8B76F625}.Release|Win32.ActiveCfg = Release|Win32 + {5ED1729B-EA41-4163-9506-741A8B76F625}.Release|Win32.Build.0 = Release|Win32 + {5ED1729B-EA41-4163-9506-741A8B76F625}.Release|x64.ActiveCfg = Release|x64 + {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|Win32.ActiveCfg = Debug|Win32 + {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|Win32.Build.0 = Debug|Win32 + {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|x64.ActiveCfg = Debug|x64 + {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|x64.Build.0 = Debug|x64 + {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|Win32.ActiveCfg = Release|Win32 + {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|Win32.Build.0 = Release|Win32 + {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|x64.ActiveCfg = Release|x64 + {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|x64.Build.0 = Release|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.ActiveCfg = Debug|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.Build.0 = Debug|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.ActiveCfg = Debug|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.Build.0 = Debug|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.ActiveCfg = Release|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.Build.0 = Release|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.ActiveCfg = Release|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FC646532-2050-40A5-A2AB-F699F1C071C4} + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Portable/pmp_ipod/pmp_ipod.vcxproj b/Src/Plugins/Portable/pmp_ipod/pmp_ipod.vcxproj new file mode 100644 index 00000000..0e0444f7 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/pmp_ipod.vcxproj @@ -0,0 +1,355 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{54C393C2-5A0A-497F-930B-DA5316351B00}</ProjectGuid> + <RootNamespace>pmp_ipod</RootNamespace> + <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir> + <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir> + <IncludePath>$(IncludePath)</IncludePath> + <LibraryPath>$(LibraryPath)</LibraryPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir> + <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir> + <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir> + <IncludePath>$(IncludePath)</IncludePath> + <LibraryPath>$(LibraryPath)</LibraryPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir> + <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir> + </PropertyGroup> + <PropertyGroup Label="Vcpkg"> + <VcpkgEnableManifest>false</VcpkgEnableManifest> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>..\..\..\Wasabi;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;ML_ex_EXPORTS;_UNICODE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <FunctionLevelLinking>true</FunctionLevelLinking> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4018;4244;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x040c</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <GenerateMapFile>false</GenerateMapFile> + <MapExports>false</MapExports> + <SubSystem>Windows</SubSystem> + <RandomizedBaseAddress>true</RandomizedBaseAddress> + <DataExecutionPrevention>true</DataExecutionPrevention> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <MapFileName>$(IntDir)$(TargetName).map</MapFileName> + </Link> + <PostBuildEvent> + <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ +xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command> + <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message> + </PostBuildEvent> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>..\..\..\Wasabi;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN64;_DEBUG;_WINDOWS;_USRDLL;ML_ex_EXPORTS;_UNICODE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <FunctionLevelLinking>true</FunctionLevelLinking> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4018;4244;4302;4311;4267;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x040c</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <GenerateMapFile>false</GenerateMapFile> + <MapExports>false</MapExports> + <SubSystem>Windows</SubSystem> + <RandomizedBaseAddress>true</RandomizedBaseAddress> + <DataExecutionPrevention>true</DataExecutionPrevention> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <MapFileName>$(IntDir)$(TargetName).map</MapFileName> + </Link> + <PostBuildEvent> + <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ +xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command> + <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message> + </PostBuildEvent> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <Optimization>MinSpace</Optimization> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>..\..\..\Wasabi;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;ML_ex_EXPORTS;_UNICODE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4018;4244;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x040c</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <MapFileName>$(IntDir)$(TargetName).map</MapFileName> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </Link> + <PostBuildEvent> + <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command> + <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message> + </PostBuildEvent> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ClCompile> + <Optimization>MinSpace</Optimization> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>..\..\..\Wasabi;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;ML_ex_EXPORTS;_UNICODE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4018;4244;4302;4311;4267;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x040c</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <MapFileName>$(IntDir)$(TargetName).map</MapFileName> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </Link> + <PostBuildEvent> + <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command> + <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message> + </PostBuildEvent> + </ItemDefinitionGroup> + <ItemGroup> + <ProjectReference Include="..\..\..\plist\plist.vcxproj"> + <Project>{5ed1729b-ea41-4163-9506-741a8b76f625}</Project> + <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies> + <ReferenceOutputAssembly>true</ReferenceOutputAssembly> + </ProjectReference> + <ProjectReference Include="..\..\..\tataki\tataki.vcxproj"> + <Project>{255b68b5-7ef8-45ef-a675-2d6b88147909}</Project> + </ProjectReference> + <ProjectReference Include="..\..\..\Wasabi\bfc\bfc.vcxproj"> + <Project>{d0ec862e-dddd-4f4f-934f-b75dc9062dc1}</Project> + <CopyLocalSatelliteAssemblies>true</CopyLocalSatelliteAssemblies> + <ReferenceOutputAssembly>true</ReferenceOutputAssembly> + </ProjectReference> + <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj"> + <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <ClCompile Include="..\..\General\gen_ml\itemlist.cpp" /> + <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp" /> + <ClCompile Include="deviceprovider.cpp" /> + <ClCompile Include="eject.cpp" /> + <ClCompile Include="filecopy.cpp" /> + <ClCompile Include="hash58.cpp" /> + <ClCompile Include="iPodArtworkDB.cpp" /> + <ClCompile Include="iPodDB.cpp" /> + <ClCompile Include="iPodDevice.cpp" /> + <ClCompile Include="iPodInfo.cpp" /> + <ClCompile Include="iPodSD.cpp" /> + <ClCompile Include="main.cpp"> + <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">WIN32;_DEBUG;_WINDOWS;_MBCS;_USRDLL;ML_ex_EXPORTS</PreprocessorDefinitions> + <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">WIN64;_DEBUG;_WINDOWS;_MBCS;_USRDLL;ML_ex_EXPORTS</PreprocessorDefinitions> + <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">WIN32;NDEBUG;_WINDOWS;_MBCS;_USRDLL;ML_ex_EXPORTS</PreprocessorDefinitions> + <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">WIN64;NDEBUG;_WINDOWS;_MBCS;_USRDLL;ML_ex_EXPORTS</PreprocessorDefinitions> + </ClCompile> + <ClCompile Include="sha1.c" /> + <ClCompile Include="SysInfoXML.cpp" /> + <ClCompile Include="yail.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\..\General\gen_ml\itemlist.h" /> + <ClInclude Include="..\..\General\gen_ml\ml.h" /> + <ClInclude Include="..\..\Library\ml_pmp\pmp.h" /> + <ClInclude Include="..\..\..\nu\Map.h" /> + <ClInclude Include="api.h" /> + <ClInclude Include="deviceprovider.h" /> + <ClInclude Include="hash58.h" /> + <ClInclude Include="iPodArtworkDB.h" /> + <ClInclude Include="iPodDB.h" /> + <ClInclude Include="iPodDevice.h" /> + <ClInclude Include="iPodInfo.h" /> + <ClInclude Include="iPodSD.h" /> + <ClInclude Include="resource.h" /> + <ClInclude Include="sha1.h" /> + <ClInclude Include="yail.h" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="pmp_ipod.rc" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_ipod/pmp_ipod.vcxproj.filters b/Src/Plugins/Portable/pmp_ipod/pmp_ipod.vcxproj.filters new file mode 100644 index 00000000..84bca7be --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/pmp_ipod.vcxproj.filters @@ -0,0 +1,113 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClCompile Include="deviceprovider.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="eject.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="filecopy.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="hash58.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="iPodArtworkDB.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="iPodDB.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="iPodDevice.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="iPodInfo.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="iPodSD.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="sha1.c"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="SysInfoXML.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="yail.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\General\gen_ml\itemlist.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="api.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="deviceprovider.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="hash58.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="iPodArtworkDB.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="iPodDB.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="iPodDevice.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="iPodInfo.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="iPodSD.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="sha1.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="yail.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\General\gen_ml\itemlist.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\nu\Map.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\General\gen_ml\ml.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\Library\ml_pmp\pmp.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <Filter Include="Header Files"> + <UniqueIdentifier>{8a27dfc8-8c21-4608-a4b0-2de0009d3411}</UniqueIdentifier> + </Filter> + <Filter Include="Ressource Files"> + <UniqueIdentifier>{72f38cc1-6101-41e6-a08e-3583fa56c3ba}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{b8889dfc-5b23-471a-821f-74e6540a3df2}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="pmp_ipod.rc"> + <Filter>Ressource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_ipod/resource.h b/Src/Plugins/Portable/pmp_ipod/resource.h new file mode 100644 index 00000000..61f771f3 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/resource.h @@ -0,0 +1,71 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by pmp_ipod1.rc +// +#define IDS_CANNOT_OPEN_FILE 1 +#define IDS_CANNOT_CREATE_FILE 2 +#define IDS_TRANSFERRING_PERCENT 3 +#define IDS_CANCELLED 4 +#define IDS_DONE 5 +#define IDS_TRANSFER_FAILED 6 +#define IDS_IPOD_LOADING 7 +#define IDS_FAILED_TO_EJECT_IPOD 8 +#define IDS_ERROR 9 +#define IDS_INVALID_TRACK 10 +#define IDS_SCANNING 11 +#define IDS_STRING12 12 +#define IDS_ADVANCED 12 +#define IDD_CONFIG 102 +#define IDD_GAPSCAN 103 +#define IDD_DIALOG1 106 +#define IDD_SELECTIPODTYPE 106 +#define IDB_CLASSIC_16 110 +#define IDB_NANO1G_16 111 +#define IDB_NANO2G_16 112 +#define IDB_NANO3G_16 113 +#define IDB_NANO4G_16 114 +#define IDB_NANO5G_16 115 +#define IDB_SHUFFLE1G_16 116 +#define IDB_SHUFFLE2G_16 117 +#define IDB_SHUFFLE3G_16 118 +#define IDB_CLASSIC_160 119 +#define IDB_NANO1G_160 120 +#define IDB_NANO2G_160 121 +#define IDB_NANO3G_160 122 +#define IDB_NANO4G_160 123 +#define IDB_NANO5G_160 124 +#define IDB_SHUFFLE1G_160 125 +#define IDB_SHUFFLE2G_160 126 +#define IDB_PNG9 127 +#define IDB_SHUFFLE3G_160 127 +#define IDB_SHUFFLE4G_160 128 +#define IDB_PNG1 129 +#define IDB_SHUFFLE4G_16 129 +#define IDC_SCAN 1001 +#define IDC_PROGRESS1 1002 +#define IDC_CAPTION 1003 +#define IDC_BUTTON1 1004 +#define IDC_BG 1004 +#define IDC_RADIO1 1005 +#define IDC_RADIO2 1006 +#define IDC_RADIO3 1007 +#define IDC_RADIO4 1008 +#define IDC_IPODINFO 1009 +#define IDC_RADIO5 1010 +#define IDC_CHECK_USEART 1011 +#define IDC_RADIO6 1011 +#define IDC_RADIO7 1012 +#define IDC_STATIC_ARTGROUP 1013 +#define IDC_RADIO8 1013 +#define IDS_NULLSOFT_IPOD_PLUGIN 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 131 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1014 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_classic.png b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_classic.png Binary files differnew file mode 100644 index 00000000..3e1ab968 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_classic.png diff --git a/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_classic_16.png b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_classic_16.png Binary files differnew file mode 100644 index 00000000..3dc4defd --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_classic_16.png diff --git a/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_1g.png b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_1g.png Binary files differnew file mode 100644 index 00000000..466100c5 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_1g.png diff --git a/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_1g_16.png b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_1g_16.png Binary files differnew file mode 100644 index 00000000..a7d27890 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_1g_16.png diff --git a/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_2g.png b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_2g.png Binary files differnew file mode 100644 index 00000000..5b59d389 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_2g.png diff --git a/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_2g_16.png b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_2g_16.png Binary files differnew file mode 100644 index 00000000..5a4fa632 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_2g_16.png diff --git a/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_3g.png b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_3g.png Binary files differnew file mode 100644 index 00000000..59b7c098 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_3g.png diff --git a/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_3g_16.png b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_3g_16.png Binary files differnew file mode 100644 index 00000000..c7358d0a --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_3g_16.png diff --git a/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_4g.png b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_4g.png Binary files differnew file mode 100644 index 00000000..b455b90e --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_4g.png diff --git a/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_4g_16.png b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_4g_16.png Binary files differnew file mode 100644 index 00000000..7fac0d7a --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_4g_16.png diff --git a/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_5g.png b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_5g.png Binary files differnew file mode 100644 index 00000000..dbca3467 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_5g.png diff --git a/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_5g_16.png b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_5g_16.png Binary files differnew file mode 100644 index 00000000..d29a4ef5 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_nano_5g_16.png diff --git a/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_1g.png b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_1g.png Binary files differnew file mode 100644 index 00000000..6ebebb72 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_1g.png diff --git a/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_1g_16.png b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_1g_16.png Binary files differnew file mode 100644 index 00000000..14ed7814 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_1g_16.png diff --git a/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_2g.png b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_2g.png Binary files differnew file mode 100644 index 00000000..9f1ca1db --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_2g.png diff --git a/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_2g_16.png b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_2g_16.png Binary files differnew file mode 100644 index 00000000..39f45806 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_2g_16.png diff --git a/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_3g.png b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_3g.png Binary files differnew file mode 100644 index 00000000..5c00b6f9 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_3g.png diff --git a/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_3g_16.png b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_3g_16.png Binary files differnew file mode 100644 index 00000000..570d9c16 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_3g_16.png diff --git a/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_4g.png b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_4g.png Binary files differnew file mode 100644 index 00000000..65a5147c --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_4g.png diff --git a/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_4g_16.png b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_4g_16.png Binary files differnew file mode 100644 index 00000000..e6ea3a13 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/resources/apple_ipod_shuffle_4g_16.png diff --git a/Src/Plugins/Portable/pmp_ipod/sha1.c b/Src/Plugins/Portable/pmp_ipod/sha1.c new file mode 100644 index 00000000..09b3dd9d --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/sha1.c @@ -0,0 +1,240 @@ +/* +SHA-1 in C +By Steve Reid <steve@edmweb.com> +100% Public Domain + +Test Vectors (from FIPS PUB 180-1) +"abc" +A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" +84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" +34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +#define LITTLE_ENDIAN /* This should be #define'd if true. */ +/* #define SHA1HANDSOFF * Copies data before messing with it. */ + +#include <stdio.h> +#include <string.h> + +typedef struct { + unsigned long state[5]; + unsigned long count[2]; + unsigned char buffer[64]; +} SHA1_CTX; + +void SHA1Transform(unsigned long state[5], unsigned char buffer[64]); +void SHA1Init(SHA1_CTX* context); +void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int len); +void SHA1Final(unsigned char digest[20], SHA1_CTX* context); + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#ifdef LITTLE_ENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#else +#define blk0(i) block->l[i] +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +void SHA1Transform(unsigned long state[5], unsigned char buffer[64]) +{ + unsigned long a, b, c, d, e; + typedef union { + unsigned char c[64]; + unsigned long l[16]; + } CHAR64LONG16; + CHAR64LONG16* block; + + /* + printf("SHA1Transform:\n"); + for (k = 0; k < 4; k++) { + for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { + printf("%02X", buffer[k*16+i*4+j]); + } + putchar(' '); + } + putchar('\n'); + } + printf("SHA1Transform (translated):\n"); + for (k = 0; k < 4; k++) { + for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { + long x = buffer[k*16+i*4+j] & 0xFF; + printf("%02X", cryptTable[x] & 0xFF); + } + putchar(' '); + } + putchar('\n'); + } + */ + +#ifdef SHA1HANDSOFF + static unsigned char workspace[64]; + block = (CHAR64LONG16*)workspace; + memcpy(block, buffer, 64); +#else + block = (CHAR64LONG16*)buffer; +#endif + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +} + + +/* SHA1Init - Initialize new context */ + +void SHA1Init(SHA1_CTX* context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* Run your data through this. */ + +void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int len) +{ + unsigned int i, j; + + j = (context->count[0] >> 3) & 63; + if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++; + context->count[1] += (len >> 29); + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + SHA1Transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ + +void SHA1Final(unsigned char digest[20], SHA1_CTX* context) +{ + unsigned long i, j; + unsigned char finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } + SHA1Update(context, (unsigned char *)"\200", 1); + while ((context->count[0] & 504) != 448) { + SHA1Update(context, (unsigned char *)"\0", 1); + } + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + /* Wipe variables */ + i = j = 0; + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); + memset(&finalcount, 0, 8); +#ifdef SHA1HANDSOFF /* make SHA1Transform overwrite it's own static vars */ + SHA1Transform(context->state, context->buffer); +#endif +} + + +/*************************************************************/ + +/* +int main(int argc, char** argv) +{ + int i, j; + SHA1_CTX context; + unsigned char digest[20], buffer[16384]; + FILE* file; + + if (argc > 2) { + puts("Public domain SHA-1 implementation - by Steve Reid <steve@edmweb.com>"); + puts("Produces the SHA-1 hash of a file, or stdin if no file is specified."); + exit(0); + } + if (argc < 2) { + file = stdin; + } + else { + if (!(file = fopen(argv[1], "rb"))) { + fputs("Unable to open file.", stderr); + exit(-1); + } + } + SHA1Init(&context); + while (!feof(file)) { /* note: what if ferror(file) + i = fread(buffer, 1, 16384, file); + SHA1Update(&context, buffer, i); + } + SHA1Final(digest, &context); + fclose(file); + for (i = 0; i < 5; i++) { + for (j = 0; j < 4; j++) { + printf("%02X", digest[i*4+j]); + } + putchar(' '); + } + putchar('\n'); + exit(0); +} +*/ diff --git a/Src/Plugins/Portable/pmp_ipod/sha1.h b/Src/Plugins/Portable/pmp_ipod/sha1.h new file mode 100644 index 00000000..7601f7ee --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/sha1.h @@ -0,0 +1,9 @@ +typedef struct { + unsigned long state[5]; + unsigned long count[2]; + unsigned char buffer[64]; +} SHA1_CTX; + +extern "C" void SHA1Init(SHA1_CTX* context); +extern "C" void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int len); +extern "C" void SHA1Final(unsigned char digest[20], SHA1_CTX* context); diff --git a/Src/Plugins/Portable/pmp_ipod/version.rc2 b/Src/Plugins/Portable/pmp_ipod/version.rc2 new file mode 100644 index 00000000..802c1f4d --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 0,91,0,0 + PRODUCTVERSION WINAMP_PRODUCTVER + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Winamp SA" + VALUE "FileDescription", "Winamp Portable Device Plug-in" + VALUE "FileVersion", "0,91,0,0" + VALUE "InternalName", "Nullsoft iPod Device" + VALUE "LegalCopyright", "Copyright © 2006-2023 Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "pmp_ipod.dll" + VALUE "ProductName", "Winamp" + VALUE "ProductVersion", STR_WINAMP_PRODUCTVER + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/Src/Plugins/Portable/pmp_ipod/yail.cpp b/Src/Plugins/Portable/pmp_ipod/yail.cpp new file mode 100644 index 00000000..1be65450 --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/yail.cpp @@ -0,0 +1,114 @@ +#include "iPodDevice.h" +#include <stdlib.h> +#include <memory.h> +#include <api/service/waservicefactory.h> +#include <api/service/svcs/svc_imgload.h> +#include "api.h" +#include <tataki/export.h> + +#include "yail.h" + +extern PMPDevicePlugin plugin; +static void recTransform (RGB565 *destination, RGB565 *source, int width, int height, int row_stride); + +static __forceinline ARGB32 pixto32bit(RGB565 pix0, int format) { + unsigned long pix = pix0; + if(format == RGB_565) + return (ARGB32)(((pix & 0x001F) << 3) | ((pix & 0x07E0) << 5) | ((pix & 0xF800) << 8) | 0xff000000); + else // format == RGB_555. Ignore alpha channel. + return (ARGB32)(((pix & 0x001F) << 3) | ((pix & 0x03E0) << 6) | ((pix & 0x7C00) << 9) | 0xff000000); +} + +static __forceinline RGB565 pixto16bit(ARGB32 pix, int format) { + // 10987654321098765432109876543210 + // ARGB32 is AAAAAAAARRRRRRRRGGGGGGGGBBBBBBBB + // RGB565 is RRRRRGGGGGGBBBBB + // RGB555 is ARRRRRGGGGGBBBBB + if(format == RGB_565) + return (RGB565)( ((pix >> 8) & 0xF800) | ((pix >> 5) & 0x07E0) | ((pix >> 3) & 0x001F) ); + else // format == RGB_555. set A to 1 for now. + return (RGB565)( 0x8000 | ((pix >> 9) & 0x7C00) | ((pix >> 6) & 0x03E0) | ((pix >> 3) & 0x001F) ); +} + +Image::Image(const ARGB32 * d, int w, int h) : width(w), height(h) { + int size = sizeof(ARGB32)*w*h; + data = (ARGB32*)calloc(size,1); + memcpy(data,d,size); +} + +#define ALIGN(size, boundary) ((((boundary) - ((size) % (boundary))) % (boundary)) + (size)) + +Image::Image(const RGB565 * d, int w, int h, int format, int alignRowBytes, int alignImageBytes) : width(w), height(h) { + data = (ARGB32*)calloc(w*h*sizeof(ARGB32),1); + int rowgap = (ALIGN(sizeof(RGB565) * width, alignRowBytes) / sizeof(RGB565)) - width; + int p=0, q=0; + for(int j=0; j<height; j++) + { + for(int i=0; i<width; i++) + { + data[p++] = pixto32bit(d[q++], format); + } + q += rowgap; + } +} + +Image::~Image() { + if(data) free(data); data=0; +} + +void Image::exportToRGB565(RGB565* d_, int format, int alignRowBytes, int alignImageBytes) const { + int p=0, q=0; + int rowgap = (ALIGN(sizeof(RGB565) * width, alignRowBytes) / sizeof(RGB565)) - width; + + RGB565* d; + if(format == RGB_555_REC) + { + int l = get16BitSize(width, height, alignRowBytes, alignImageBytes); + d = (RGB565*)calloc(l, 1); + } + else + d = d_; + + for(int j=0; j<height; j++) + { + for(int i=0; i<width; i++) + { + d[p++] = pixto16bit(data[q++], format); + } + p += rowgap; + } + + if(format == RGB_555_REC) + { // do wierd transform + recTransform(d_, d, width, height, width + rowgap); + free(d); + } +} + +void Image::exportToARGB32(ARGB32* d) const { + memcpy(d,data,sizeof(ARGB32)*width*height); +} + +int Image::get16BitSize(int width, int height, int alignRowBytes, int alignImageBytes) { + int rowSize = ALIGN(sizeof(RGB565) * width, alignRowBytes); + return ALIGN(rowSize * height, alignImageBytes); +} + + +static void recTransform (RGB565 *destination, RGB565 *source, int width, int height, int row_stride) +{ + if (width == 1) + { + *destination = *source; + } + else + { + recTransform(destination, source, width/2, height/2, row_stride); + + recTransform(destination + (width/2)*(height/2), source + (height/2)*row_stride, width/2, height/2, row_stride); + + recTransform(destination + 2*(width/2)*(height/2), source + width/2, width/2, height/2, row_stride); + + recTransform(destination + 3*(width/2)*(height/2), source + (height/2)*row_stride + width/2, width/2, height/2, row_stride); + } +}
\ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_ipod/yail.h b/Src/Plugins/Portable/pmp_ipod/yail.h new file mode 100644 index 00000000..374b376f --- /dev/null +++ b/Src/Plugins/Portable/pmp_ipod/yail.h @@ -0,0 +1,25 @@ +#ifndef _YAIL_H_ +#define _YAIL_H_ + +// yet another image library. Because everything else SUCKS. Fact. + +typedef unsigned short RGB565; + +class Image { +public: + Image(const ARGB32 * data, int w, int h); + Image(const RGB565 * data, int w, int h, int format, int alignRowBytes, int alignImageBytes); + ~Image(); + void exportToRGB565(RGB565* data, int format, int alignRowBytes, int alignImageBytes) const; + void exportToARGB32(ARGB32* data) const; + ARGB32 * getData() {return data;} + int getWidth() const {return width;} + int getHeight() const {return height;} + int get16BitSize(int alignRowBytes, int alignImageBytes) { return get16BitSize(width,height,alignRowBytes, alignImageBytes); } + static int get16BitSize(int width, int height, int alignRowBytes, int alignImageBytes); +protected: + ARGB32 *data; + int width,height; +}; + +#endif //_YAIL_H_
\ No newline at end of file |