aboutsummaryrefslogtreecommitdiff
path: root/Src/Plugins/Portable/pmp_usb
diff options
context:
space:
mode:
authorJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
committerJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
commit20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch)
tree12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/Plugins/Portable/pmp_usb
parent537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff)
downloadwinamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz
Initial community commit
Diffstat (limited to 'Src/Plugins/Portable/pmp_usb')
-rw-r--r--Src/Plugins/Portable/pmp_usb/albumart.cpp58
-rw-r--r--Src/Plugins/Portable/pmp_usb/api.cpp64
-rw-r--r--Src/Plugins/Portable/pmp_usb/api.h34
-rw-r--r--Src/Plugins/Portable/pmp_usb/deviceprovider.cpp343
-rw-r--r--Src/Plugins/Portable/pmp_usb/deviceprovider.h51
-rw-r--r--Src/Plugins/Portable/pmp_usb/eject.cpp135
-rw-r--r--Src/Plugins/Portable/pmp_usb/filecopy.cpp67
-rw-r--r--Src/Plugins/Portable/pmp_usb/main.cpp547
-rw-r--r--Src/Plugins/Portable/pmp_usb/pmp_usb2.rc209
-rw-r--r--Src/Plugins/Portable/pmp_usb/pmp_usb2.sln129
-rw-r--r--Src/Plugins/Portable/pmp_usb/pmp_usb2.vcxproj340
-rw-r--r--Src/Plugins/Portable/pmp_usb/pmp_usb2.vcxproj.filters89
-rw-r--r--Src/Plugins/Portable/pmp_usb/resource.h75
-rw-r--r--Src/Plugins/Portable/pmp_usb/resources/pspIcon.pngbin0 -> 216 bytes
-rw-r--r--Src/Plugins/Portable/pmp_usb/resources/usbIcon.pngbin0 -> 200 bytes
-rw-r--r--Src/Plugins/Portable/pmp_usb/usbdevice.cpp1913
-rw-r--r--Src/Plugins/Portable/pmp_usb/usbdevice.h242
-rw-r--r--Src/Plugins/Portable/pmp_usb/usbplaylist.cpp33
-rw-r--r--Src/Plugins/Portable/pmp_usb/usbplaylist.h35
-rw-r--r--Src/Plugins/Portable/pmp_usb/usbplaylistsaver.cpp74
-rw-r--r--Src/Plugins/Portable/pmp_usb/usbplaylistsaver.h31
-rw-r--r--Src/Plugins/Portable/pmp_usb/utils.cpp355
-rw-r--r--Src/Plugins/Portable/pmp_usb/version.rc239
23 files changed, 4863 insertions, 0 deletions
diff --git a/Src/Plugins/Portable/pmp_usb/albumart.cpp b/Src/Plugins/Portable/pmp_usb/albumart.cpp
new file mode 100644
index 00000000..7d3a26a7
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/albumart.cpp
@@ -0,0 +1,58 @@
+#include "../../Library/ml_pmp/pmp.h"
+#include "api.h"
+#include "../agave/albumart/svc_albumartprovider.h"
+#include <api/service/waservicefactory.h>
+
+extern PMPDevicePlugin plugin;
+
+static svc_albumArtProvider *FindProvider(const wchar_t *filename, int providerType, waServiceFactory **factory)
+{
+ FOURCC albumartprovider = svc_albumArtProvider::getServiceType();
+ int n = (int)plugin.service->service_getNumServices(albumartprovider);
+ for (int i=0; i<n; i++)
+ {
+ waServiceFactory *sf = plugin.service->service_enumService(albumartprovider,i);
+ if (sf)
+ {
+ svc_albumArtProvider * provider = (svc_albumArtProvider*)sf->getInterface();
+ if (provider)
+ {
+ if (provider->ProviderType() == providerType && provider->IsMine(filename))
+ {
+ *factory = sf;
+ return provider;
+ }
+ sf->releaseInterface(provider);
+ }
+ }
+ }
+ return NULL;
+}
+
+void CopyAlbumArt(const wchar_t *source, const wchar_t *destination)
+{
+ size_t datalen = 0;
+ void *data = 0;
+ wchar_t *mimeType = 0;
+ waServiceFactory *destinationFactory = 0;
+ svc_albumArtProvider *destinationProvider = FindProvider(destination, ALBUMARTPROVIDER_TYPE_EMBEDDED, &destinationFactory);
+ if (destinationFactory)
+ {
+ /* First, look to see if there's already embedded album art */
+ if (destinationProvider->GetAlbumArtData(destination, L"cover", &data, &datalen, &mimeType) == ALBUMARTPROVIDER_SUCCESS && data && datalen)
+ {
+ destinationFactory->releaseInterface(destinationProvider);
+ WASABI_API_MEMMGR->sysFree(data);
+ WASABI_API_MEMMGR->sysFree(mimeType);
+ return;
+ }
+ else if (AGAVE_API_ALBUMART->GetAlbumArtData(source, L"cover", &data, &datalen, &mimeType) == ALBUMART_SUCCESS && data && datalen)
+ {
+ destinationProvider->SetAlbumArtData(destination, L"cover", data, datalen, mimeType);
+ WASABI_API_MEMMGR->sysFree(data);
+ WASABI_API_MEMMGR->sysFree(mimeType);
+
+ destinationFactory->releaseInterface(destinationProvider);
+ }
+ }
+} \ No newline at end of file
diff --git a/Src/Plugins/Portable/pmp_usb/api.cpp b/Src/Plugins/Portable/pmp_usb/api.cpp
new file mode 100644
index 00000000..8e5dcee4
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/api.cpp
@@ -0,0 +1,64 @@
+#include "../../Library/ml_pmp/pmp.h"
+#include "api.h"
+#include <api/service/waservicefactory.h>
+
+api_language *WASABI_API_LNG = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
+extern PMPDevicePlugin plugin;
+
+// Metadata service
+api_metadata *AGAVE_API_METADATA=0;
+api_playlistmanager *WASABI_API_PLAYLISTMNGR=0;
+api_albumart *AGAVE_API_ALBUMART=0;
+api_memmgr *WASABI_API_MEMMGR=0;
+api_application *WASABI_API_APP=0;
+api_threadpool *WASABI_API_THREADPOOL=0;
+api_devicemanager *AGAVE_API_DEVICEMANAGER = 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;
+}
+
+void WasabiInit()
+{
+ ServiceBuild(WASABI_API_LNG, languageApiGUID);
+ ServiceBuild(AGAVE_API_METADATA, api_metadataGUID);
+ ServiceBuild(WASABI_API_PLAYLISTMNGR, api_playlistmanagerGUID);
+ ServiceBuild(WASABI_API_APP, applicationApiServiceGuid);
+ ServiceBuild(AGAVE_API_ALBUMART, albumArtGUID);
+ ServiceBuild(WASABI_API_MEMMGR, memMgrApiServiceGuid);
+ ServiceBuild(WASABI_API_THREADPOOL, ThreadPoolGUID);
+ ServiceBuild(AGAVE_API_DEVICEMANAGER, DeviceManagerGUID);
+}
+
+void WasabiQuit()
+{
+ ServiceRelease(WASABI_API_LNG, languageApiGUID);
+ ServiceRelease(WASABI_API_PLAYLISTMNGR, api_playlistmanagerGUID);
+ ServiceRelease(WASABI_API_APP, applicationApiServiceGuid);
+ ServiceRelease(AGAVE_API_METADATA, api_metadataGUID);
+ ServiceRelease(AGAVE_API_ALBUMART, albumArtGUID);
+ ServiceRelease(WASABI_API_MEMMGR, memMgrApiServiceGuid);
+ ServiceRelease(WASABI_API_THREADPOOL, ThreadPoolGUID);
+ ServiceRelease(AGAVE_API_DEVICEMANAGER, DeviceManagerGUID);
+} \ No newline at end of file
diff --git a/Src/Plugins/Portable/pmp_usb/api.h b/Src/Plugins/Portable/pmp_usb/api.h
new file mode 100644
index 00000000..f7cfd662
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/api.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include "../Agave/Language/api_language.h"
+
+#include "../Agave/Metadata/api_metadata.h"
+extern api_metadata *metadataApi;
+#define AGAVE_API_METADATA metadataApi
+
+#include "../playlist/api_playlistmanager.h"
+extern api_playlistmanager *playlistManagerApi;
+#define WASABI_API_PLAYLISTMNGR playlistManagerApi
+
+#include "../nu/threadpool/api_threadpool.h"
+extern api_threadpool *threadPoolApi;
+#define WASABI_API_THREADPOOL threadPoolApi
+
+#include "../Agave/AlbumArt/api_albumart.h"
+extern api_albumart *albumArtApi;
+#define AGAVE_API_ALBUMART albumArtApi
+
+#include <api/memmgr/api_memmgr.h>
+extern api_memmgr *memmgr;
+#define WASABI_API_MEMMGR memmgr
+
+#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
+
+void WasabiInit();
+void WasabiQuit();
diff --git a/Src/Plugins/Portable/pmp_usb/deviceprovider.cpp b/Src/Plugins/Portable/pmp_usb/deviceprovider.cpp
new file mode 100644
index 00000000..05e73d21
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/deviceprovider.cpp
@@ -0,0 +1,343 @@
+#include "api.h"
+#include "./deviceprovider.h"
+#include "../devices/api_devicemanager.h"
+
+extern PMPDevicePlugin plugin;
+void connectDrive(wchar_t drive, bool checkSize, bool checkBlacklist);
+
+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,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_usb/deviceprovider.h b/Src/Plugins/Portable/pmp_usb/deviceprovider.h
new file mode 100644
index 00000000..1f7e83ad
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/deviceprovider.h
@@ -0,0 +1,51 @@
+#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_usb/eject.cpp b/Src/Plugins/Portable/pmp_usb/eject.cpp
new file mode 100644
index 00000000..36e12240
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/eject.cpp
@@ -0,0 +1,135 @@
+// 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
+
+static HANDLE OpenVolume(TCHAR cDriveLetter)
+{
+ HANDLE hVolume;
+ UINT uDriveType;
+ wchar_t szVolumeName[8] = {0};
+ wchar_t szRootName[5] = {0};
+ DWORD dwAccessFlags;
+
+ 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)
+{
+ BOOL fAutoEject = FALSE;
+ HANDLE 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;
+} \ No newline at end of file
diff --git a/Src/Plugins/Portable/pmp_usb/filecopy.cpp b/Src/Plugins/Portable/pmp_usb/filecopy.cpp
new file mode 100644
index 00000000..aecf9422
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/filecopy.cpp
@@ -0,0 +1,67 @@
+#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_TRANSFERING_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_usb/main.cpp b/Src/Plugins/Portable/pmp_usb/main.cpp
new file mode 100644
index 00000000..19fb06d4
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/main.cpp
@@ -0,0 +1,547 @@
+#include "../../Library/ml_pmp/pmp.h"
+#include "../Winamp/wa_ipc.h"
+#include <vector>
+#include "../nu/AutoWide.h"
+#include "../nu/AutoChar.h"
+#include "../nu/AutoLock.h"
+#include "api.h"
+#include "resource.h"
+#include "usbdevice.h"
+#include "deviceprovider.h"
+
+#include <devguid.h>
+#include <shlobj.h>
+#include <shlwapi.h>
+#include <strsafe.h>
+
+#define PLUGIN_VERSION L"1.62"
+static int Init();
+static void Quit();
+static bool doRegisterForDevNotification(void);
+static intptr_t MessageProc(int msg, intptr_t param1, intptr_t param2, intptr_t param3);
+
+extern PMPDevicePlugin plugin = {PMPHDR_VER,0,Init,Quit,MessageProc};
+
+bool loading_devices[26] = {0,};
+
+// start-usb
+static const wchar_t *winampini;
+static std::vector<HANDLE> loadingThreads;
+static std::vector<wchar_t*> blacklist;
+
+static HDEVNOTIFY hDevNotify;
+std::vector<USBDevice*> devices;
+HWND config;
+
+static DeviceProvider *deviceProvider = NULL;
+static UINT_PTR rescanTimer = 0;
+
+static void blacklistLoad() {
+ wchar_t keyname[64] = {0};
+ int l = GetPrivateProfileIntW(L"pmp_usb", L"blacklistnum", 0, winampini);
+ for(int i=l>100?l-100:0; i<l; i++) {
+ wchar_t buf[100] = {0};
+ StringCchPrintfW(keyname, 64, L"blacklist-%d", i);
+ GetPrivateProfileStringW(L"pmp_usb", keyname, L"", buf, 100, winampini);
+ if(buf[0])
+ {
+ blacklist.push_back(_wcsdup(buf));
+ }
+ }
+}
+
+static void blacklistSave() {
+ wchar_t buf[64] = {0};
+ StringCchPrintfW(buf, 64, L"%u", blacklist.size());
+ WritePrivateProfileStringW(L"pmp_usb", L"blacklistnum", buf, winampini);
+ for(size_t i=0; i<blacklist.size(); i++)
+ {
+ StringCchPrintfW(buf, 64, L"blacklist-%u", i);
+ WritePrivateProfileStringW(L"pmp_usb", buf, (const wchar_t*)blacklist.at(i), winampini);
+ }
+}
+
+static wchar_t *makeBlacklistString(wchar_t drive) {
+ wchar_t path[4]={drive,L":\\"};
+ wchar_t name[100]=L"";
+ wchar_t buf[FIELD_LENGTH]=L"";
+ DWORD serial=0;
+ UINT olderrmode=SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
+ GetVolumeInformation(path,name,100,&serial,NULL,NULL,NULL,0);
+
+ if(serial)
+ {
+ StringCchPrintf(buf, FIELD_LENGTH, L"s:%d",serial);
+ SetErrorMode(olderrmode);
+ return _wcsdup(buf);
+ }
+
+ {
+ ULARGE_INTEGER tfree={0,}, total={0,}, freeb={0,};
+ GetDiskFreeSpaceEx(path, &tfree, &total, &freeb);
+ StringCchPrintf(buf, FIELD_LENGTH, L"n:%s,%d,%d", name, total.HighPart, total.LowPart);
+ SetErrorMode(olderrmode);
+ return _wcsdup(buf);
+ }
+}
+
+static bool blacklistCheck(wchar_t drive) {
+ wchar_t *s = makeBlacklistString(drive);
+ if (s)
+ {
+ for(size_t i=0; i<blacklist.size(); i++)
+ {
+ if(!wcscmp(s,(wchar_t*)blacklist.at(i)))
+ {
+ free(s);
+ return true;
+ }
+ }
+ free(s);
+ }
+ return false;
+}
+
+// helpers
+static DWORD WINAPI ThreadFunc_Load(LPVOID lpParam) {
+ wchar_t drive = (wchar_t)(intptr_t)lpParam;
+ pmpDeviceLoading load;
+ Device * d = new USBDevice(drive,&load);
+ return 0;
+}
+
+static Nullsoft::Utility::LockGuard connect_guard;
+void connectDrive(wchar_t drive, bool checkSize=true, bool checkBlacklist=true)
+{
+ Nullsoft::Utility::AutoLock connect_lock(connect_guard);
+ // capitalize
+ if (drive >= 'a' && drive <= 'z')
+ drive = drive - 32;
+
+ // reject invalid drive letters
+ if (drive < 'A' || drive > 'Z')
+ return;
+
+ if(checkBlacklist && blacklistCheck(drive)) return;
+
+ // if device is taken already ignore
+ for (std::vector<USBDevice*>::const_iterator e = devices.begin(); e != devices.end(); e++)
+ {
+ if ((*e)->drive == drive)
+ return;
+ }
+
+ if (loading_devices[drive-'A'])
+ return;
+
+ loading_devices[drive-'A'] = true;
+
+ wchar_t path[4]=L"x:\\";
+ path[0]=drive;
+
+ if(checkSize)
+ {
+ ULARGE_INTEGER total;
+
+ if (0 == GetDiskFreeSpaceExW(path, NULL, &total, NULL) ||
+ total.HighPart == 0 && total.LowPart == 0)
+ {
+ loading_devices[drive-'A'] = false;
+ return;
+ }
+ }
+
+ // Ignore iPods...
+ // Check for a "iPod_Control" folder,
+ // if "iPod_Control" is present just bail and
+ // let the iPod plugin handle it
+ const wchar_t iPodDb[] = {drive,L":\\iPod_Control"};
+ WIN32_FIND_DATA ffd={0};
+ HANDLE h = FindFirstFile(iPodDb,&ffd);
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ FindClose(h);
+
+ if (0 != (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+ {
+ loading_devices[drive-'A'] = false;
+ return;
+ }
+ }
+
+ // Ignore androids too, have a specific pulgin to take care of android...
+ // Check for a "Android" folder,
+ // if "Android" is present just bail and
+ // let the pmp_android plugin handle it
+
+ const wchar_t androidTopLevelDir[] = {drive,L":\\Android"};
+
+ h = FindFirstFile(androidTopLevelDir, &ffd);
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ FindClose(h);
+
+ if (0 != (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+ {
+ loading_devices[drive-'A'] = false;
+ return;
+ }
+ }
+
+ //not an ipod, not an android
+ //do we know about the device already? is there a metadata.dat file ?
+ wchar_t cacheFile[FIELD_LENGTH] = {0};
+ StringCchPrintf(cacheFile, FIELD_LENGTH, L"%c:\\%s", drive, NDE_CACHE_DAT);
+
+ if (!PathFileExists(cacheFile))
+ {
+ // new device, never plugged in before
+ wchar_t drvname[100] = {0}, titleStr[128] = {0};
+ DWORD serial=0;
+ GetVolumeInformation(path,drvname,100,&serial,NULL,NULL,NULL,0);
+
+ wchar_t buf[1024] = {0};
+ StringCchPrintf(buf, ARRAYSIZE(buf), WASABI_API_LNGSTRINGW(IDS_REMOVEABLE_DRIVE_DETECTED),
+ drvname, towupper(drive));
+
+ if(MessageBox(plugin.hwndLibraryParent,buf,
+ WASABI_API_LNGSTRINGW_BUF(IDS_WINAMP_PMP_SUPPORT,titleStr,128),
+ MB_YESNO|MB_SETFOREGROUND|MB_SYSTEMMODAL|MB_TOPMOST) == IDNO)
+ {
+ wchar_t * bstr = makeBlacklistString(drive);
+ loading_devices[drive-'A'] = false;
+ if (bstr)
+ {
+ blacklist.push_back(bstr);
+ blacklistSave();
+ }
+ return;
+ }
+ }
+
+ DWORD dwThreadId;
+ HANDLE loadingThread = CreateThread(NULL, 0, ThreadFunc_Load, (LPVOID)(intptr_t)drive, 0, &dwThreadId);
+ if(NULL != loadingThread)
+ loadingThreads.push_back(loadingThread);
+ else
+ loading_devices[drive-'A'] = false;
+
+}
+
+static void autoDetectCallback(wchar_t drive,UINT type) {
+ if(type == DRIVE_REMOVABLE)
+ {
+ connectDrive(drive,true,true);
+ }
+}
+
+
+// end-usb
+static int Init()
+{
+ WasabiInit();
+
+ // start-usb
+ winampini = (const wchar_t*)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GETINIFILEW);
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG(plugin.hDllInstance,PmpUSBLangGUID);
+ // end-usb
+
+ static wchar_t szDescription[256];
+ StringCchPrintfW(szDescription, ARRAYSIZE(szDescription),
+ WASABI_API_LNGSTRINGW(IDS_NULLSOFT_USB_DEVICE_PLUGIN), PLUGIN_VERSION);
+ plugin.description = szDescription;
+
+ /** load up the backlist */
+ blacklistLoad();
+
+ 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;
+}
+
+static void Quit()
+{
+ if (NULL != deviceProvider)
+ {
+ deviceProvider->Unregister();
+ deviceProvider->Release();
+ deviceProvider = NULL;
+ }
+
+ WasabiQuit();
+ UnregisterDeviceNotification(hDevNotify);
+ USBDevice::CloseDatabase();
+}
+
+static wchar_t FirstDriveFromMask(ULONG *unitmask) {
+ char i;
+ ULONG adj = 0x1, mask = *unitmask;
+ for(i=0; i<26; ++i) {
+ if(mask & 0x1) {
+ *unitmask -= adj;
+ break;
+ }
+ adj = adj << 1;
+ mask = mask >> 1;
+ }
+ return (i+L'A');
+}
+
+static int GetNumberOfDrivesFromMask(ULONG unitmask) {
+ int count = 0;
+ for(int i=0; i<26; ++i)
+ {
+ if(unitmask & 0x1)
+ count++;
+
+ unitmask = unitmask >> 1;
+ }
+ return count;
+}
+
+
+static void CALLBACK RescanOnTimer(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
+{
+ KillTimer(hwnd, idEvent);
+ if (idEvent == rescanTimer)
+ rescanTimer = 0;
+
+ if (NULL == deviceProvider ||
+ FAILED(deviceProvider->BeginDiscovery(AGAVE_API_DEVICEMANAGER)))
+ {
+ PostMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(WPARAM)autoDetectCallback,PMP_IPC_ENUM_ACTIVE_DRIVES);
+ }
+}
+
+int wmDeviceChange(WPARAM wParam, LPARAM lParam)
+{
+ UINT olderrmode=SetErrorMode(SEM_NOOPENFILEERRORBOX|SEM_FAILCRITICALERRORS);
+ if(wParam==DBT_DEVICEARRIVAL || wParam==DBT_DEVICEREMOVECOMPLETE)
+ { // something has been inserted or removed
+ 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((!(lpdbv->dbcv_flags & DBTF_MEDIA) && !(lpdbv->dbcv_flags & DBTF_NET)))
+ { // its not a network drive or a CD/floppy, game on!
+ ULONG dbcv_unitmask = lpdbv->dbcv_unitmask;
+
+ // see just how many drives have been flagged on the action
+ // eg one usb drive could have multiple partitions that we handle
+ int count = GetNumberOfDrivesFromMask(dbcv_unitmask);
+ for(int j = 0; j < count; j++)
+ {
+ wchar_t drive = FirstDriveFromMask(&dbcv_unitmask);
+ if((wParam == DBT_DEVICEARRIVAL) && !blacklistCheck(drive))
+ { // connected
+ connectDrive(drive);
+ //send a message as if the user just selected a drive from the combo box, this way the fields are refreshed to the correct device's settings
+ SendMessage(config, WM_COMMAND,MAKEWPARAM(IDC_DRIVESELECT,CBN_SELCHANGE),0);
+ }
+ else
+ { // removal
+ for(size_t i=0; i < devices.size(); i++) {
+ USBDevice * d = (USBDevice*)devices.at(i);
+ if(d->drive == drive)
+ {
+ devices.erase(devices.begin() + i);
+ if(config) SendMessage(config,WM_USER,0,0); //refresh fields
+ if(config) SendMessage(config,WM_COMMAND, MAKEWPARAM(IDC_DRIVESELECT,CBN_SELCHANGE),0); //update to correct device change as if the user had clicked on the combo box themself
+ SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)d,PMP_IPC_DEVICEDISCONNECTED);
+ delete d;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ rescanTimer = SetTimer(NULL, rescanTimer, 10000, RescanOnTimer);
+ }
+ SetErrorMode(olderrmode);
+ return 0;
+}
+
+static int IsDriveConnectedToPMP(wchar_t drive) {
+ for(size_t i = 0; i < devices.size(); i++)
+ {
+ if(((USBDevice*)devices.at(i))->drive == drive)
+ {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+static INT_PTR CALLBACK config_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ switch(uMsg) {
+ case WM_INITDIALOG:
+ {
+ for(wchar_t d=L'A'; d<='Z'; d++) {
+ wchar_t drive[3] = {d,L':',0}, drv[4] = {d,L':','\\',0};
+ UINT uDriveType = GetDriveType(drv);
+ if(uDriveType == DRIVE_REMOVABLE || uDriveType == DRIVE_CDROM || uDriveType == DRIVE_FIXED) {
+ int position = (int)SendDlgItemMessageW(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_ADDSTRING,0,(LPARAM)drive);
+ SendDlgItemMessage(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_SETITEMDATA,position,d);
+ }
+ }
+ SendDlgItemMessage(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_SETCURSEL,0,0);
+ SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_COMBO_MANUALCONNECT,CBN_SELCHANGE),0);
+ }
+ break;
+ case WM_CLOSE:
+ EndDialog(hwndDlg,0);
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDC_COMBO_MANUALCONNECT:
+ {
+ if(HIWORD(wParam)==CBN_SELCHANGE) {
+ int indx = (int)SendDlgItemMessageW(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_GETCURSEL,0,0);
+ wchar_t drive = (wchar_t)SendDlgItemMessage(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_GETITEMDATA,indx,0);
+ if(indx >= 0)
+ {
+ int connected = IsDriveConnectedToPMP(drive), isblacklisted = blacklistCheck(drive);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_MANUALCONNECT), !connected && !isblacklisted);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_MANUALDISCONNECT), connected);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_MANUALBLACKLIST), TRUE);
+ SetDlgItemText(hwndDlg, IDC_MANUALBLACKLIST, WASABI_API_LNGSTRINGW(isblacklisted ? IDS_UNBLACKLIST_DRIVE : IDS_BLACKLIST_DRIVE));
+ }
+ }
+ }
+ break;
+ case IDC_MANUALCONNECT:
+ {
+ char titleStr[32] = {0};
+ if(MessageBoxA(hwndDlg, WASABI_API_LNGSTRING(IDS_MANUAL_CONNECT_PROMPT),
+ WASABI_API_LNGSTRING_BUF(IDS_WARNING,titleStr,32), MB_YESNO) == IDYES)
+ {
+ int indx = (int)SendDlgItemMessageW(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_GETCURSEL,0,0);
+ wchar_t drive = (wchar_t)SendDlgItemMessage(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_GETITEMDATA,indx,0);
+ if(drive >= L'A' && drive <= L'Z') {
+ wchar_t *bl = makeBlacklistString(drive);
+ if (bl)
+ {
+ for(size_t i=0; i<blacklist.size(); i++)
+ {
+ if(!wcscmp(bl,(wchar_t*)blacklist.at(i)))
+ {
+ free(blacklist.at(i));
+ blacklist.erase(blacklist.begin() + i);
+ break;
+ }
+ }
+ free(bl);
+ }
+ connectDrive(drive,false);
+ // should do a better check here incase of failure, etc
+ EnableWindow(GetDlgItem(hwndDlg, IDC_MANUALCONNECT), FALSE);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_MANUALDISCONNECT), TRUE);
+ }
+ }
+ }
+ break;
+ case IDC_MANUALDISCONNECT:
+ {
+ int indx = (int)SendDlgItemMessageW(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_GETCURSEL,0,0);
+ wchar_t drive = (wchar_t)SendDlgItemMessage(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_GETITEMDATA,indx,0);
+ if(drive >= L'A' && drive <= L'Z') {
+ for(size_t i=0; i < devices.size(); i++) {
+ USBDevice * d = (USBDevice*)devices.at(i);
+ if(d->drive == drive)
+ {
+ devices.erase(devices.begin() + i);
+ if(config) SendMessage(config,WM_USER,0,0); //refresh fields
+ if(config) SendMessage(config,WM_COMMAND, MAKEWPARAM(IDC_DRIVESELECT,CBN_SELCHANGE),0); //update to correct device change as if the user had clicked on the combo box themself
+ SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)d,PMP_IPC_DEVICEDISCONNECTED);
+ SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_COMBO_MANUALCONNECT,CBN_SELCHANGE),0);
+ delete d;
+ }
+ }
+ }
+ }
+ break;
+ case IDC_MANUALBLACKLIST:
+ {
+ int indx = (int)SendDlgItemMessageW(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_GETCURSEL,0,0);
+ wchar_t drive = (wchar_t)SendDlgItemMessage(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_GETITEMDATA,indx,0);
+ if(drive >= L'A' && drive <= L'Z') {
+ wchar_t *bl = makeBlacklistString(drive);
+ if (bl)
+ {
+ if(!blacklistCheck(drive)) {
+ blacklist.push_back(bl);
+ // see if we've got a connected drive and prompt to remove it or wait till restart
+ if(IsDriveConnectedToPMP(drive)) {
+ wchar_t title[96] = {0};
+ GetWindowText(hwndDlg, title, 96);
+ if(MessageBox(hwndDlg,WASABI_API_LNGSTRINGW(IDS_DRIVE_CONNECTED_DISCONNECT_Q),title,MB_YESNO)==IDYES){
+ SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_MANUALDISCONNECT,0),0);
+ }
+ }
+ }
+ else {
+ for(size_t i=0; i < blacklist.size(); i++)
+ {
+ if(!wcscmp(bl,(wchar_t*)blacklist.at(i)))
+ {
+ free(blacklist.at(i));
+ blacklist.erase(blacklist.begin() + i);
+ break;
+ }
+ }
+ free(bl);
+ }
+ }
+ SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_COMBO_MANUALCONNECT,CBN_SELCHANGE),0);
+ }
+ }
+ break;
+ case IDOK:
+ case IDCANCEL:
+ blacklistSave();
+ EndDialog(hwndDlg,0);
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+
+static 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_CONFIG:
+ WASABI_API_DIALOGBOXW(IDD_CONFIG_GLOBAL,(HWND)param1,config_dialogProc);
+ return 1;
+ }
+ return 0;
+}
+
+extern "C" __declspec(dllexport) PMPDevicePlugin *winampGetPMPDevicePlugin()
+{
+ return &plugin;
+}
+
diff --git a/Src/Plugins/Portable/pmp_usb/pmp_usb2.rc b/Src/Plugins/Portable/pmp_usb/pmp_usb2.rc
new file mode 100644
index 00000000..2761cce4
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/pmp_usb2.rc
@@ -0,0 +1,209 @@
+// 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
+//
+
+IDR_PSP_ICON PNG "resources\\pspIcon.png"
+IDR_USB_ICON PNG "resources\\usbIcon.png"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_CONFIG DIALOGEX 0, 0, 264, 226
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_VISIBLE | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "File Name Format",IDC_STATIC,7,10,56,8
+ EDITTEXT IDC_NAMEFORMAT,80,7,127,14,ES_AUTOHSCROLL
+ PUSHBUTTON "Format Help",IDC_FILENAMEHELP,211,7,46,14
+ LTEXT "Playlist Directory",IDC_STATIC,7,26,54,8
+ EDITTEXT IDC_PLDIR,80,24,128,14,ES_AUTOHSCROLL
+ PUSHBUTTON "Browse...",IDC_PLBROWSE,211,23,46,14
+ LTEXT "Supported Formats",IDC_STATIC,7,42,62,8
+ EDITTEXT IDC_SUPPORTEDFORMATS,80,40,128,14,ES_AUTOHSCROLL
+ PUSHBUTTON "Syntax Help",IDC_FORMATSHELP,211,40,46,14
+ LTEXT "Delete Empty Folders",IDC_STATIC,7,55,69,8
+ CONTROL "",IDC_PURGEFOLDERS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,80,56,8,8
+ PUSHBUTTON "Save and Rescan",IDC_RESCAN,7,71,66,14
+ PUSHBUTTON "Refresh Cache",IDC_REFRESHCACHE,79,71,66,14
+ GROUPBOX "Playlist Writing Options",IDC_STATIC,7,95,250,49
+ COMBOBOX IDC_PL_WRITE_COMBO,12,108,120,35,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Changing how Winamp saves its playlists may improve compatability with other portable devices.",IDC_STATIC,137,104,113,34
+ LTEXT "",IDC_PL_WRITE_EG,12,126,120,10
+END
+
+IDD_CONFIG_GLOBAL DIALOGEX 0, 0, 196, 90
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "USB Device Support Configuration"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ GROUPBOX "Select the Drive to use from the available list below",IDC_STATIC,5,3,186,67
+ COMBOBOX IDC_COMBO_MANUALCONNECT,31,17,40,242,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
+ PUSHBUTTON "Connect Drive",IDC_MANUALCONNECT,75,17,90,13
+ PUSHBUTTON "Disconnect Drive",IDC_MANUALDISCONNECT,75,34,90,13
+ PUSHBUTTON "Blacklist Drive",IDC_MANUALBLACKLIST,75,51,90,13
+ DEFPUSHBUTTON "Close",IDOK,75,73,45,13
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_CONFIG, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 257
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 218
+ END
+
+ IDD_CONFIG_GLOBAL, DIALOG
+ BEGIN
+ LEFTMARGIN, 5
+ RIGHTMARGIN, 191
+ TOPMARGIN, 3
+ BOTTOMMARGIN, 86
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_NULLSOFT_USB_DEVICE_PLUGIN "Nullsoft USB Device Plug-in v%s"
+ 65535 "{E553C1A4-5DE2-4838-8000-FDF8DC377DD4}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_CANNOT_OPEN_FILE "Cannot open file"
+ IDS_CANNOT_CREATE_FILE "Cannot create file"
+ IDS_TRANSFERING_PERCENT "Transferring %d%%"
+ IDS_CANCELLED "Cancelled"
+ IDS_DONE "Done"
+ IDS_TRANSFER_FAILED "Transfer failed"
+ IDS_REMOVEABLE_DRIVE_DETECTED
+ "Winamp has detected a removable drive, ""%s (%c:)"".\nIs this a portable music player that you want to manage with Winamp?"
+ IDS_WINAMP_PMP_SUPPORT "Winamp Portable Music Player Support"
+ IDS_MANUAL_CONNECT_PROMPT
+ "Manually connecting the wrong device could cause problems.\nPlease be sure that you have entered a valid USB device letter.\n\nAre you sure you wish to continue?"
+ IDS_WARNING "Warning"
+ IDS_LOADING_DRIVE_X "Loading Drive X:"
+ IDS_FAILED_TO_EJECT_DRIVE
+ "Failed to eject device. Is something else using it?"
+ IDS_ERROR "Error"
+ IDS_USB_DRIVE_X "USB Drive X:"
+ IDS_TRACK_IN_USE "Track is in use - Could not delete!"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_CACHE_UPDATED "Cache updated."
+ IDS_SUCCESS "Success"
+ IDS_FILENAME_FORMATTING_INFO
+ "You may enter a filename format string for your files.\nIt can contain \\ or / to delimit a path, and the following keywords:\n\n <Artist> - inserts the artist with the default capitalization\n <ARTIST> - inserts the artist in all uppercase\n <artist> - inserts the artist in all lowercase\n <Albumartist>/<ALBUMARTIST>/<albumartist> - inserts the album artist\n <Album>/<ALBUM>/<album> - inserts the album\n <year> - inserts the album year\n <Genre>/<GENRE>/<genre> - inserts the album genre\n <Title>/<TITLE>/<title> - inserts the track title\n <filename> - inserts the original filename (extension safe)\n <disc> - inserts the disc number\n #, ##, or ### - inserts the track number, with leading 0s if ## or ###\n\n For Example: E:\\Music\\<Artist>\\<Album>\\## - <Title>\n"
+ IDS_FILENAME_FORMAT_HELP "Filename Format Help"
+ IDS_SELECT_FOLDER_TO_LOAD_PLAYLISTS
+ "Please select a folder to load playlists from the device."
+ IDS_ERR_SELECTED_PATH_NOT_ON_DEVICE
+ "Error: selected path is not on device!"
+ IDS_SUPPORTED_FORMAT_INFO
+ "You may enter supported extensions in the form of\nextensions separated by semicolons.\n\nFor Example: mp3;wav;wma"
+ IDS_SUPPORTED_FORMAT_HELP "Supported Formats Help"
+ IDS_RESCAN_COMPLETE_SAVED "Rescan complete, device settings saved."
+ IDS_RESCAN_COMPLETE "Rescan complete"
+ IDS_ADVANCED "Advanced"
+ IDS_UNBLACKLIST_DRIVE "Un-blacklist Drive"
+ IDS_BLACKLIST_DRIVE "Blacklist Drive"
+ IDS_DRIVE_CONNECTED_DISCONNECT_Q
+ "The Drive you have selected to blacklist is currently connected and being managed.\n\nWould you like to disconnect the Drive now or you can wait until you restart Winamp."
+ IDS_SLASH_AT_START "Slash at start of paths (default)"
+ IDS_DOT_AT_START "Dot at start of paths"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_NO_SLASH_OR_DOT "No slash or dot at start"
+ IDS_EG_SLASH "e.g. \\path\\to\\file.mp3"
+ IDS_EG_DOT "e.g. .\\path\\to\\file.mp3"
+ IDS_EG_NEITHER "e.g. path\\to\\file.mp3"
+ IDS_DELAYLOAD_FAILURE "USB plug-in cannot load the database to write the cache file"
+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_usb/pmp_usb2.sln b/Src/Plugins/Portable/pmp_usb/pmp_usb2.sln
new file mode 100644
index 00000000..c3806bbf
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/pmp_usb2.sln
@@ -0,0 +1,129 @@
+
+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_usb", "pmp_usb2.vcxproj", "{9AEE8DBD-D63A-41EC-8846-41208C23932B}"
+ ProjectSection(ProjectDependencies) = postProject
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27} = {57C90706-B25D-4ACA-9B33-95CDB2427C27}
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F}
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A} = {4D25C321-7F8B-424E-9899-D80A364BAF1A}
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1} = {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}
+ {E105A0A2-7391-47C5-86AC-718003524C3D} = {E105A0A2-7391-47C5-86AC-718003524C3D}
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nde", "..\nde\nde.vcxproj", "{4D25C321-7F8B-424E-9899-D80A364BAF1A}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nx", "..\replicant\nx\nx.vcxproj", "{57C90706-B25D-4ACA-9B33-95CDB2427C27}"
+ ProjectSection(ProjectDependencies) = postProject
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nu", "..\replicant\nu\nu.vcxproj", "{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnetlib", "..\replicant\jnetlib\jnetlib.vcxproj", "{E105A0A2-7391-47C5-86AC-718003524C3D}"
+ ProjectSection(ProjectDependencies) = postProject
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\replicant\zlib\zlib.vcxproj", "{0F9730E4-45DA-4BD2-A50A-403A4BC9751A}"
+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}") = "bfc", "..\Wasabi\bfc\bfc.vcxproj", "{D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}"
+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
+ {9AEE8DBD-D63A-41EC-8846-41208C23932B}.Debug|Win32.ActiveCfg = Debug|Win32
+ {9AEE8DBD-D63A-41EC-8846-41208C23932B}.Debug|Win32.Build.0 = Debug|Win32
+ {9AEE8DBD-D63A-41EC-8846-41208C23932B}.Debug|x64.ActiveCfg = Debug|x64
+ {9AEE8DBD-D63A-41EC-8846-41208C23932B}.Debug|x64.Build.0 = Debug|x64
+ {9AEE8DBD-D63A-41EC-8846-41208C23932B}.Release|Win32.ActiveCfg = Release|Win32
+ {9AEE8DBD-D63A-41EC-8846-41208C23932B}.Release|Win32.Build.0 = Release|Win32
+ {9AEE8DBD-D63A-41EC-8846-41208C23932B}.Release|x64.ActiveCfg = Release|x64
+ {9AEE8DBD-D63A-41EC-8846-41208C23932B}.Release|x64.Build.0 = Release|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.ActiveCfg = Debug|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.Build.0 = Debug|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.ActiveCfg = Debug|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.Build.0 = Debug|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.ActiveCfg = Release|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.Build.0 = Release|Win32
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.ActiveCfg = Release|x64
+ {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.Build.0 = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.ActiveCfg = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.Build.0 = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.ActiveCfg = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.Build.0 = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.ActiveCfg = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.Build.0 = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.ActiveCfg = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.Build.0 = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.ActiveCfg = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.Build.0 = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.ActiveCfg = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.Build.0 = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.ActiveCfg = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.Build.0 = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.ActiveCfg = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.Build.0 = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.Build.0 = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.ActiveCfg = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.Build.0 = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.ActiveCfg = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.Build.0 = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.ActiveCfg = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.Build.0 = Release|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.ActiveCfg = Debug|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.Build.0 = Debug|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.ActiveCfg = Debug|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.Build.0 = Debug|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.ActiveCfg = Release|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.Build.0 = Release|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.ActiveCfg = Release|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.Build.0 = 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
+ {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}.Debug|x64.Build.0 = 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
+ {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.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_usb/pmp_usb2.vcxproj b/Src/Plugins/Portable/pmp_usb/pmp_usb2.vcxproj
new file mode 100644
index 00000000..96c905e2
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/pmp_usb2.vcxproj
@@ -0,0 +1,340 @@
+<?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">
+ <ProjectName>pmp_usb</ProjectName>
+ <ProjectGuid>{9AEE8DBD-D63A-41EC-8846-41208C23932B}</ProjectGuid>
+ <RootNamespace>pmp_usb2</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;..;..\..\..\;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;ML_ex_EXPORTS;_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>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <Culture>0x040c</Culture>
+ </ResourceCompile>
+ <Link>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <GenerateMapFile>false</GenerateMapFile>
+ <MapExports>false</MapExports>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <SupportUnloadOfDelayLoadedDLL>true</SupportUnloadOfDelayLoadedDLL>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <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;..;..\..\..\;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN64;_DEBUG;_WINDOWS;_USRDLL;ML_ex_EXPORTS;_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>EditAndContinue</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <Culture>0x040c</Culture>
+ </ResourceCompile>
+ <Link>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <DelayLoadDLLs>nde.dll;%(DelayLoadDLLs)</DelayLoadDLLs>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <GenerateMapFile>false</GenerateMapFile>
+ <MapExports>false</MapExports>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <SupportUnloadOfDelayLoadedDLL>true</SupportUnloadOfDelayLoadedDLL>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <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;..;..\..\..\;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;ML_ex_EXPORTS;_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>
+ <ObjectFileName>$(IntDir)</ObjectFileName>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4995;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <Culture>0x040c</Culture>
+ </ResourceCompile>
+ <Link>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <DelayLoadDLLs>nde.dll;%(DelayLoadDLLs)</DelayLoadDLLs>
+ <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>
+ <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;..;..\..\..\;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;ML_ex_EXPORTS;_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>
+ <ObjectFileName>$(IntDir)</ObjectFileName>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4244;4995;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ </ClCompile>
+ <ResourceCompile>
+ <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <Culture>0x040c</Culture>
+ </ResourceCompile>
+ <Link>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <DelayLoadDLLs>nde.dll;%(DelayLoadDLLs)</DelayLoadDLLs>
+ <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>
+ <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="..\..\..\nde\nde.vcxproj">
+ <Project>{4d25c321-7f8b-424e-9899-d80a364baf1a}</Project>
+ </ProjectReference>
+ <ProjectReference Include="..\..\..\tataki\tataki.vcxproj">
+ <Project>{255b68b5-7ef8-45ef-a675-2d6b88147909}</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\ml_lib.cpp" />
+ <ClCompile Include="albumart.cpp" />
+ <ClCompile Include="api.cpp" />
+ <ClCompile Include="deviceprovider.cpp" />
+ <ClCompile Include="eject.cpp" />
+ <ClCompile Include="filecopy.cpp" />
+ <ClCompile Include="main.cpp">
+ <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">WIN32;_DEBUG;_WINDOWS;_UNICODE;UNICODE;_USRDLL;ML_ex_EXPORTS</PreprocessorDefinitions>
+ <PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">WIN64;_DEBUG;_WINDOWS;_UNICODE;UNICODE;_USRDLL;ML_ex_EXPORTS</PreprocessorDefinitions>
+ </ClCompile>
+ <ClCompile Include="usbdevice.cpp" />
+ <ClCompile Include="usbplaylist.cpp" />
+ <ClCompile Include="usbplaylistsaver.cpp" />
+ <ClCompile Include="utils.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\General\gen_ml\ml.h" />
+ <ClInclude Include="..\..\Library\ml_pmp\pmp.h" />
+ <ClInclude Include="api.h" />
+ <ClInclude Include="deviceprovider.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="usbdevice.h" />
+ <ClInclude Include="usbplaylist.h" />
+ <ClInclude Include="usbplaylistsaver.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="pmp_usb2.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\pspIcon.png" />
+ <Image Include="resources\usbIcon.png" />
+ </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_usb/pmp_usb2.vcxproj.filters b/Src/Plugins/Portable/pmp_usb/pmp_usb2.vcxproj.filters
new file mode 100644
index 00000000..c387fa9a
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/pmp_usb2.vcxproj.filters
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="utils.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="usbplaylistsaver.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="usbplaylist.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="usbdevice.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="filecopy.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="eject.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="deviceprovider.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="api.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="albumart.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="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="usbplaylistsaver.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="usbplaylist.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="usbdevice.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\Library\ml_pmp\pmp.h" />
+ <ClInclude Include="..\..\General\gen_ml\ml.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="resources\usbIcon.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ <Image Include="resources\pspIcon.png">
+ <Filter>Image Files</Filter>
+ </Image>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{df4d6295-0341-413a-a69b-289485e8ac9b}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{dd0483a1-4f48-4781-9440-bf945617ff32}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{f7ad4b77-838f-46fa-a4d5-e72746ad3a68}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Image Files">
+ <UniqueIdentifier>{4536af52-cfdf-4156-a936-f89abc7ee71a}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="pmp_usb2.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Portable/pmp_usb/resource.h b/Src/Plugins/Portable/pmp_usb/resource.h
new file mode 100644
index 00000000..5574f6f2
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/resource.h
@@ -0,0 +1,75 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by pmp_usb2.rc
+//
+#define IDS_CANNOT_OPEN_FILE 1
+#define IDS_CANNOT_CREATE_FILE 2
+#define IDS_TRANSFERING_PERCENT 3
+#define IDS_CANCELLED 4
+#define IDS_DONE 5
+#define IDS_TRANSFER_FAILED 6
+#define IDS_REMOVEABLE_DRIVE_DETECTED 7
+#define IDS_WINAMP_PMP_SUPPORT 8
+#define IDS_MANUAL_CONNECT_PROMPT 9
+#define IDS_WARNING 10
+#define IDS_LOADING_DRIVE_X 11
+#define IDS_FAILED_TO_EJECT_DRIVE 12
+#define IDS_ERROR 13
+#define IDS_STRING14 14
+#define IDS_USB_DRIVE_X 14
+#define IDS_TRACK_IN_USE 15
+#define IDS_CACHE_UPDATED 16
+#define IDS_SUCCESS 17
+#define IDS_FILENAME_FORMATTING_INFO 18
+#define IDS_FILENAME_FORMAT_HELP 19
+#define IDS_SELECT_FOLDER_TO_LOAD_PLAYLISTS 20
+#define IDS_ERR_SELECTED_PATH_NOT_ON_DEVICE 21
+#define IDS_SUPPORTED_FORMAT_INFO 22
+#define IDS_SUPPORTED_FORMAT_HELP 23
+#define IDS_RESCAN_COMPLETE_SAVED 24
+#define IDS_RESCAN_COMPLETE 25
+#define IDS_ADVANCED 26
+#define IDS_UNBLACKLIST_DRIVE 27
+#define IDS_BLACKLIST_DRIVE 28
+#define IDS_DRIVE_CONNECTED_DISCONNECT_Q 29
+#define IDS_SLASH_AT_START 30
+#define IDS_DOT_AT_START 31
+#define IDS_NO_SLASH_OR_DOT 32
+#define IDS_EG_SLASH 33
+#define IDS_EG_DOT 34
+#define IDS_STRING35 35
+#define IDS_EG_NEITHER 35
+#define IDS_DELAYLOAD_FAILURE 36
+#define IDD_CONFIG 102
+#define IDD_CONFIG_GLOBAL 105
+#define IDR_PSP_ICON 110
+#define IDB_PNG2 111
+#define IDR_USB_ICON 111
+#define IDC_PLDIR 1001
+#define IDC_NAMEFORMAT 1002
+#define IDC_SUPPORTEDFORMATS 1003
+#define IDC_DRIVESELECT 1004
+#define IDC_COMBO_MANUALCONNECT 1005
+#define IDC_MANUALCONNECT 1006
+#define IDC_MANUALDISCONNECT 1007
+#define IDC_MANUALBLACKLIST 1008
+#define IDC_REFRESHCACHE 1011
+#define IDC_FILENAMEHELP 1012
+#define IDC_PLBROWSE 1013
+#define IDC_FORMATSHELP 1014
+#define IDC_RESCAN 1015
+#define IDC_PURGEFOLDERS 1016
+#define IDC_PL_WRITE_COMBO 1017
+#define IDC_PL_WRITE_EG 1018
+#define IDS_NULLSOFT_USB_DEVICE_PLUGIN 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 113
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1019
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Portable/pmp_usb/resources/pspIcon.png b/Src/Plugins/Portable/pmp_usb/resources/pspIcon.png
new file mode 100644
index 00000000..46ff4f30
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/resources/pspIcon.png
Binary files differ
diff --git a/Src/Plugins/Portable/pmp_usb/resources/usbIcon.png b/Src/Plugins/Portable/pmp_usb/resources/usbIcon.png
new file mode 100644
index 00000000..763a3c2c
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/resources/usbIcon.png
Binary files differ
diff --git a/Src/Plugins/Portable/pmp_usb/usbdevice.cpp b/Src/Plugins/Portable/pmp_usb/usbdevice.cpp
new file mode 100644
index 00000000..dae8057a
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/usbdevice.cpp
@@ -0,0 +1,1913 @@
+#include "usbdevice.h"
+#include "resource.h"
+#include "usbplaylist.h"
+#include "usbplaylistsaver.h"
+#include "api.h"
+#include "../winamp/wa_ipc.h"
+#include <tataki/bitmap/bitmap.h>
+#include <tataki/canvas/bltcanvas.h>
+#include <shlobj.h>
+#include <strsafe.h>
+#include <shlwapi.h>
+
+// from main.cpp
+extern PMPDevicePlugin plugin;
+extern std::vector<USBDevice*> devices;
+extern bool loading_devices[26];
+// from utils.cpp
+extern BOOL RecursiveCreateDirectory(wchar_t* buf);
+extern bool supportedFormat(wchar_t * file, wchar_t * supportedFormats);
+extern DeviceType detectDeviceType(wchar_t drive);
+extern __int64 fileSize(wchar_t * filename);
+extern void removebadchars(wchar_t *s);
+extern wchar_t * fixReplacementVars(wchar_t *str, int str_size, Device * dev, songid_t song);
+static INT_PTR CALLBACK prefs_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
+int CopyFile(const wchar_t *infile, const wchar_t *outfile, void * callbackContext, void (*callback)(void * callbackContext, wchar_t * status), int * killswitch);
+// called from ml_pmp
+extern BOOL EjectVolume(TCHAR cDriveLetter);
+void CopyAlbumArt(const wchar_t *source, const wchar_t *destination);
+
+__int64 USBDevice::getDeviceCapacityAvailable() // in bytes
+{
+ 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;
+}
+
+// called from ml_pmp
+__int64 USBDevice::getDeviceCapacityTotal()
+{
+ // in bytes
+ 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;
+}
+
+// called from ml_pmp
+void USBDevice::Eject()
+{
+ // if you ejected successfully, you MUST call PMP_IPC_DEVICEDISCONNECTED and delete this
+ for(size_t i=0; i < devices.size(); i++)
+ {
+ USBDevice *device = devices.at(i);
+ if (device == this)
+ {
+ if (EjectVolume(drive))
+ {
+ devices.erase(devices.begin() + i);
+ SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICEDISCONNECTED);
+ delete this;
+ break;
+ }
+ else
+ {
+ wchar_t titleStr[128] = {0};
+ MessageBox(plugin.hwndLibraryParent,WASABI_API_LNGSTRINGW(IDS_FAILED_TO_EJECT_DRIVE), WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,titleStr,128),0);
+ break;
+ }
+ }
+ }
+}
+
+// called from ml_pmp
+void USBDevice::Close()
+{
+ // save any changes, and call PMP_IPC_DEVICEDISCONNECTED AND delete this
+ for(size_t i=0; i < devices.size(); i++)
+ {
+ if(((USBDevice*)devices.at(i)) == this)
+ {
+ devices.erase(devices.begin() + i);
+ SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICEDISCONNECTED);
+ delete this;
+ break;
+ }
+ }
+}
+
+// called from ml_pmp
+// return 0 for success, -1 for failed or cancelled
+int USBDevice::transferTrackToDevice(const itemRecordW * track, // the track to transfer
+ 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!
+ songid_t * songid, // fill in the songid when you are finished
+ int * killswitch) // if this gets set to anything other than zero, the transfer has been cancelled by the user
+{
+ wchar_t fn[MAX_PATH] = L"X:\\";
+ lstrcpyn(fn, songFormat, MAX_PATH);
+ fn[0] = drive;
+ wchar_t * src = track->filename;
+ wchar_t ext[10] = {0};
+ wchar_t *e = wcsrchr(src,L'.');
+ if (e) lstrcpyn(ext, e, 10);
+
+ bool transcodefile = false;
+ if (transcoder && transcoder->ShouldTranscode(src))
+ {
+ int r = transcoder->CanTranscode(src, ext);
+ if (r != 0 && r != -1) transcodefile = true;
+ }
+
+ UsbSong *s = new UsbSong();
+ lstrcpyn(s->filename, src, MAX_PATH); //this will get written over, but for now we have this so that the user can keep the old filename
+
+ fillMetaData(s); // TODO: benski> used cached info inside track (itemRecordW) if available
+ fixReplacementVars(fn, MAX_PATH, this, (songid_t)s);
+
+ StringCchCat(fn, MAX_PATH, ext); //place extension
+ StringCchCopy(s->filename, MAX_PATH, fn);
+
+ wchar_t * dir = wcsrchr(fn,L'\\');
+ wchar_t * dir2 = wcsrchr(fn,L'/');
+ wchar_t slash;
+ if (dir2 > dir)
+ {
+ dir = dir2;
+ slash=L'/';
+ }
+ else slash = L'\\';
+ if (dir) *dir = 0;
+ RecursiveCreateDirectory(fn);
+ if (dir) *dir = slash;
+ int r;
+ if (transcodefile)
+ {
+ r = transcoder->TranscodeFile(src, fn, killswitch, callback, callbackContext);
+ }
+ else
+ {
+ r = CopyFile(src, fn, callbackContext, callback, killswitch);
+ }
+
+ if (r == 0)
+ {
+ // TODO: benski> do we need to update any fields from the transcoded filed?
+ CopyAlbumArt(src, fn);
+ writeRecordToDB(s);
+ callback(callbackContext, WASABI_API_LNGSTRINGW(IDS_DONE));
+ *songid = (songid_t)s;
+ }
+ else
+ {
+ callback(callbackContext, WASABI_API_LNGSTRINGW(IDS_TRANSFER_FAILED));
+ delete s;
+ }
+ return r;
+}
+
+// called from ml_pmp
+int USBDevice::trackAddedToTransferQueue(const itemRecordW *track)
+{
+ // return 0 to accept, -1 for "not enough space", -2 for "incorrect format"
+ __int64 k = getTrackSizeOnDevice(track);
+ if(!k) return -2;
+ __int64 l = (__int64)k;
+ __int64 avail = getDeviceCapacityAvailable();
+ __int64 cmp = transferQueueLength;
+ cmp += l;
+ if(cmp > avail) return -1;
+ else {
+ transferQueueLength += l;
+ return 0;
+ }
+}
+
+// called from ml_pmp
+void USBDevice::trackRemovedFromTransferQueue(const itemRecordW *track)
+{
+ transferQueueLength -= (__int64)getTrackSizeOnDevice(track);
+}
+
+// called from ml_pmp
+// return the amount of space that will be taken up on the device by the track (once it has been tranferred)
+// or 0 for incompatable. This is usually the filesize, unless you are transcoding. An estimate is acceptable.
+__int64 USBDevice::getTrackSizeOnDevice(const itemRecordW *track)
+{
+ if(transcoder) {
+ if(transcoder->ShouldTranscode(track->filename)) {
+ int k = transcoder->CanTranscode(track->filename);
+ if(k != -1 && k != 0) return k;
+ return 0;
+ } else return fileSize(track->filename);
+ } else {
+ if(!supportedFormat(track->filename,supportedFormats)) return 0;
+ return fileSize(track->filename);
+ }
+}
+
+// called from ml_pmp
+void USBDevice::deleteTrack(songid_t songid)
+{
+ // physically remove from device. Be sure to remove it from all the playlists!
+ UsbSong * s = (UsbSong*)songid;
+
+ //errno == 2 is ENOENT
+ if(!_wunlink(s->filename) || errno == 2) { //will continue delete if file was deleted successfully or file path does not exist in the first place (errno==2)
+ for(size_t i=0; i < usbPlaylists.size(); i++) {
+
+ USBPlaylist * pl = usbPlaylists.at(i);
+ size_t l = pl->songs.size();
+ while(l--)
+ {
+ if(((UsbSong*)pl->songs.at(l)) == s)
+ {
+ // remove the track and rewrite the playlist
+ removeTrackFromPlaylist((int)i, (int)l);
+ }
+ }
+
+ if(purgeFolders[0]=='1')
+ {
+ RemoveDirectory(s->filename);
+ }
+ }
+ delete (UsbSong*)songid;
+ } else {
+ char titleStr[32] = {0};
+ MessageBoxA(plugin.hwndLibraryParent,WASABI_API_LNGSTRING(IDS_TRACK_IN_USE),
+ WASABI_API_LNGSTRING_BUF(IDS_ERROR,titleStr,32),0);
+ }
+}
+
+// called from ml_pmp
+// optional. Will be called at a good time to save changes
+void USBDevice::commitChanges()
+{
+ //update cache
+ tag();
+ cacheUpToDate = true;
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)0,IPC_WRITE_EXTENDED_FILE_INFO);
+}
+
+// called from ml_pmp
+int USBDevice::getPlaylistCount()
+{
+ // always at least 1. playlistnumber 0 is the Master Playlist containing all tracks.
+ return (int)usbPlaylists.size();
+}
+
+// called from ml_pmp
+// PlaylistName(0) should return the name of the device.
+void USBDevice::getPlaylistName(int playlistnumber, wchar_t *buf, int len)
+{
+ wchar_t * pathName = usbPlaylists.at(playlistnumber)->filename;
+ if(playlistnumber != 0)
+ {
+ if(pathName[0])
+ {
+ wchar_t * playlistName = PathFindFileNameW(pathName);
+ lstrcpyn(buf,playlistName,len);
+ PathRemoveExtension(buf);
+ }
+ }
+ else //playlist number = 0 -> this is the device
+ {
+ if(pathName[0])
+ { //if we have a custom device name
+ lstrcpyn(buf,pathName,len);
+ }
+ else
+ {
+ WASABI_API_LNGSTRINGW_BUF(IDS_USB_DRIVE_X,buf,len);
+ wchar_t * x = wcsrchr(buf,L'X');
+ if(x) *x = drive;
+ }
+ }
+}
+
+// called from ml_pmp
+int USBDevice::getPlaylistLength(int playlistnumber)
+{
+ return (int)usbPlaylists.at(playlistnumber)->songs.size();
+}
+
+// called from ml_pmp
+songid_t USBDevice::getPlaylistTrack(int playlistnumber,int songnum)
+{
+ // returns a songid
+ return (songid_t) usbPlaylists.at(playlistnumber)->songs.at(songnum);
+}
+
+// called from ml_pmp
+void USBDevice::setPlaylistName(int playlistnumber, const wchar_t *buf)
+{
+ // with playlistnumber==0, set the name of the device.
+ USBPlaylist * pl = usbPlaylists.at(playlistnumber);
+ if(playlistnumber==0)
+ {
+ WritePrivateProfileString(L"pmp_usb",L"customName",buf,iniFile);
+ lstrcpyn(pl->filename,buf,sizeof(pl->filename)/sizeof(wchar_t));
+ }
+ else
+ {
+ wchar_t currentFilename[MAX_PATH] = {0};
+ lstrcpynW(currentFilename, pl->filename, MAX_PATH);
+
+ wchar_t * newFilename = const_cast<wchar_t *>(buf);
+ if(wcslen(buf) >= MAX_PATH-1) newFilename[MAX_PATH-1]=0;
+ while(newFilename && *newFilename && *newFilename == L'.') newFilename++;
+ removebadchars(newFilename);
+ StringCchPrintf(pl->filename,MAX_PATH,L"%s\\%s.m3u",pldir,newFilename);
+ pl->filename[0]=drive;
+ MoveFile(currentFilename, pl->filename);
+
+ pl->dirty=true;
+ }
+}
+
+// called from ml_pmp
+void USBDevice::playlistSwapItems(int playlistnumber, int posA, int posB)
+{
+ // swap the songs at position posA and posB
+ USBPlaylist * pl = (USBPlaylist*)usbPlaylists.at(playlistnumber);
+ UsbSong* a = pl->songs.at(posA);
+ UsbSong* b = pl->songs.at(posB);
+ pl->songs.insert(pl->songs.begin() + posA, b);
+ pl->songs.erase(pl->songs.begin() + posA + 1);
+ pl->songs.insert(pl->songs.begin() + posB, a);
+ pl->songs.erase(pl->songs.begin() + posB + 1);
+ pl->dirty = true;
+}
+
+// called from ml_pmp
+void USBDevice::sortPlaylist(int playlistnumber, int sortBy)
+{
+// TODO: implement
+}
+
+// called from ml_pmp
+void USBDevice::addTrackToPlaylist(int playlistnumber, songid_t songid)
+{
+ // adds songid to the end of the playlist
+ UsbSong* song = (UsbSong *) songid;
+ USBPlaylist * pl = (USBPlaylist*)usbPlaylists.at(playlistnumber);
+ pl->songs.push_back(song);
+
+ pl->dirty=true;
+}
+
+// called from ml_pmp
+void USBDevice::removeTrackFromPlaylist(int playlistnumber, int songnum)
+{
+ //where songnum is the position of the track in the playlist
+ USBPlaylist * pl = (USBPlaylist*)usbPlaylists.at(playlistnumber);
+ pl->songs.erase(pl->songs.begin() + songnum);
+
+ pl->dirty=true;
+}
+
+// called from ml_pmp
+void USBDevice::deletePlaylist(int playlistnumber)
+{
+ USBPlaylist * pl = (USBPlaylist*)usbPlaylists.at(playlistnumber);
+ _wunlink(pl->filename);
+ usbPlaylists.erase(usbPlaylists.begin() + playlistnumber);
+ delete pl;
+}
+
+// called from ml_pmp
+int USBDevice::newPlaylist(const wchar_t *name)
+{
+ // create empty playlist, returns playlistnumber. -1 for failed.
+ wchar_t plname[MAX_PATH] = {0};
+ StringCchCopy(plname, MAX_PATH, name);
+ removebadchars(plname);
+ wchar_t buff[MAX_PATH] = {0};
+ StringCchPrintf(buff, MAX_PATH, L"%s\\%s.m3u",pldir,plname);
+
+ USBPlaylist * pl = new USBPlaylist(*this, buff, false);
+ pl->filename[0]=drive;
+
+ //Lets save the playlist right away
+ USBPlaylistSaver playlistSaver(pl->filename, L"autosaved", pl);
+ playlistSaver.Save();
+
+ usbPlaylists.push_back(pl);
+ return (int)usbPlaylists.size()-1;
+}
+
+// called from ml_pmp
+void USBDevice::getTrackArtist(songid_t songid, wchar_t *buf, int len)
+{
+ UsbSong* song = (UsbSong*)songid;
+ if (!song) return;
+
+ buf[0] = L'\0';
+ if(!loadedUpToDate)
+ {
+ this->fillMetaData(song);
+ }
+ StringCchCopy(buf, len, song->artist);
+}
+
+// called from ml_pmp
+void USBDevice::getTrackAlbum(songid_t songid, wchar_t *buf, int len)
+{
+ UsbSong* song = (UsbSong*)songid;
+ if (!song) return;
+
+ buf[0] = L'\0';
+ if(!loadedUpToDate)
+ {
+ this->fillMetaData(song);
+ }
+ StringCchCopy(buf, len, song->album);
+}
+
+// called from ml_pmp
+void USBDevice::getTrackTitle(songid_t songid, wchar_t *buf, int len)
+{
+ UsbSong* song = (UsbSong*)songid;
+ if (!song) return;
+
+ if(!loadedUpToDate)
+ {
+ this->fillMetaData(song);
+ }
+ StringCchCopy(buf, len, song->title);
+}
+
+// called from ml_pmp
+int USBDevice::getTrackTrackNum(songid_t songid)
+{
+ UsbSong* song = (UsbSong*)songid;
+ if (!song) return 0;
+
+ if(!loadedUpToDate)
+ {
+ this->fillMetaData(song);
+ }
+ return song->track;
+}
+
+// called from ml_pmp
+int USBDevice::getTrackDiscNum(songid_t songid)
+{
+ UsbSong* song = (UsbSong*)songid;
+ if (!song) return 0;
+
+ if(!loadedUpToDate)
+ {
+ this->fillMetaData(song);
+ }
+ return song->discnum;
+}
+
+// called from ml_pmp
+void USBDevice::getTrackGenre(songid_t songid, wchar_t * buf, int len)
+{
+ UsbSong* song = (UsbSong*)songid;
+ if (!song) return;
+
+ if(!loadedUpToDate)
+ {
+ this->fillMetaData(song);
+ }
+ StringCchCopy(buf, len, song->genre);
+}
+
+// called from ml_pmp
+int USBDevice::getTrackYear(songid_t songid)
+{
+ UsbSong* song = (UsbSong*)songid;
+ if (!song) return 0;
+
+ if(!loadedUpToDate)
+ {
+ this->fillMetaData(song);
+ }
+ return song->year;
+}
+
+// called from ml_pmp
+__int64 USBDevice::getTrackSize(songid_t songid)
+{
+ // in bytes
+ UsbSong* song = (UsbSong*)songid;
+ if (!song) return 0;
+
+ if(!loadedUpToDate)
+ {
+ this->fillMetaData(song);
+ }
+ return song->size;
+}
+
+// called from ml_pmp
+int USBDevice::getTrackLength(songid_t songid)
+{
+ // in millisecs
+ UsbSong* song = (UsbSong*)songid;
+ if (!song) return 0;
+
+ if(!loadedUpToDate)
+ {
+ this->fillMetaData(song);
+ }
+ return song->length;
+}
+
+// called from ml_pmp
+int USBDevice::getTrackBitrate(songid_t songid)
+{
+ // in kbps
+ UsbSong* song = (UsbSong*)songid;
+ if (!song) return 0;
+
+ if(!loadedUpToDate)
+ {
+ this->fillMetaData(song);
+ }
+ return song->bitrate;
+}
+
+// called from ml_pmp
+int USBDevice::getTrackPlayCount(songid_t songid)
+{
+ UsbSong* song = (UsbSong*)songid;
+ if (!song) return 0;
+
+ if(!loadedUpToDate)
+ {
+ this->fillMetaData(song);
+ }
+ return song->playcount;
+}
+
+// called from ml_pmp
+int USBDevice::getTrackRating(songid_t songid)
+{
+ //0-5
+// TODO: implement
+ return 0;
+}
+
+// called from ml_pmp
+__time64_t USBDevice::getTrackLastPlayed(songid_t songid)
+{
+ // in unix time format
+// TODO: implement
+ return 0;
+
+}
+
+// called from ml_pmp
+__time64_t USBDevice::getTrackLastUpdated(songid_t songid)
+{
+ // in unix time format
+// TODO: implement
+ return 0;
+}
+
+// called from ml_pmp
+void USBDevice::getTrackAlbumArtist(songid_t songid, wchar_t *buf, int len)
+{
+ UsbSong* song = (UsbSong*)songid;
+ if (!song) return;
+
+ if(!loadedUpToDate)
+ {
+ this->fillMetaData(song);
+ }
+ StringCchCopy(buf, len, song->albumartist);
+}
+
+// called from ml_pmp
+void USBDevice::getTrackPublisher(songid_t songid, wchar_t *buf, int len)
+{
+ UsbSong* song = (UsbSong*)songid;
+ if (!song) return;
+
+ if(!loadedUpToDate)
+ {
+ this->fillMetaData(song);
+ }
+ StringCchCopy(buf, len, song->publisher);
+}
+
+// called from ml_pmp
+void USBDevice::getTrackComposer(songid_t songid, wchar_t *buf, int len)
+{
+ UsbSong* song = (UsbSong*)songid;
+ if (!song) return;
+
+ if(!loadedUpToDate)
+ {
+ this->fillMetaData(song);
+ }
+ StringCchCopy(buf, len, song->composer);
+}
+
+// called from ml_pmp
+int USBDevice::getTrackType(songid_t songid)
+{
+// TODO: implement
+ return 0;
+}
+
+// called from ml_pmp
+void USBDevice::getTrackExtraInfo(songid_t songid, const wchar_t *field, wchar_t *buf, int len)
+{
+// TODO: implement
+//optional
+}
+
+// called from ml_pmp
+// feel free to ignore any you don't support
+void USBDevice::setTrackArtist(songid_t songid, const wchar_t *value)
+{
+ UsbSong *song = (UsbSong *) songid;
+ if (song)
+ {
+ updateTrackField(song, DEVICEVIEW_COL_ARTIST, value, FIELD_STRING);
+ StringCchCopy(song->artist, FIELD_LENGTH, value);
+ AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"artist", value);
+ }
+}
+
+// called from ml_pmp
+void USBDevice::setTrackAlbum(songid_t songid, const wchar_t *value)
+{
+ UsbSong *song = (UsbSong *) songid;
+ if (song)
+ {
+ updateTrackField(song, DEVICEVIEW_COL_ALBUM, value, FIELD_STRING);
+ StringCchCopy(song->album, FIELD_LENGTH, value);
+ AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"album", value);
+ }
+}
+
+// called from ml_pmp
+void USBDevice::setTrackTitle(songid_t songid, const wchar_t *value)
+{
+ UsbSong *song = (UsbSong *) songid;
+ if (song)
+ {
+ updateTrackField(song, DEVICEVIEW_COL_TITLE, value, FIELD_STRING);
+ StringCchCopy(song->title, FIELD_LENGTH, value);
+ AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"title", value);
+ }
+}
+
+// called from ml_pmp
+void USBDevice::setTrackTrackNum(songid_t songid, int value)
+{
+ UsbSong *song = (UsbSong *) songid;
+
+ if (song)
+ {
+ wchar_t track[FIELD_LENGTH] = {0};
+ updateTrackField(song, DEVICEVIEW_COL_TRACK, &value, FIELD_INTEGER);
+ song->track = value;
+ StringCchPrintf(track, FIELD_LENGTH, L"%d", value);
+ AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"track", track);
+ }
+}
+
+// called from ml_pmp
+void USBDevice::setTrackDiscNum(songid_t songid, int value)
+{
+ UsbSong *song = (UsbSong *) songid;
+
+ if (song)
+ {
+ wchar_t discNum[FIELD_LENGTH] = {0};
+ updateTrackField(song, DEVICEVIEW_COL_DISC_NUMBER, &value, FIELD_INTEGER);
+ song->discnum = value;
+ StringCchPrintf(discNum, FIELD_LENGTH, L"%d", value);
+ AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"disc", discNum);
+ }
+}
+
+// called from ml_pmp
+void USBDevice::setTrackGenre(songid_t songid, const wchar_t *value)
+{
+ UsbSong *song = (UsbSong *) songid;
+ if (song)
+ {
+ updateTrackField(song, DEVICEVIEW_COL_GENRE, value, FIELD_STRING);
+ StringCchCopy(song->genre, FIELD_LENGTH, value);
+ AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"genre", value);
+ }
+}
+
+// called from ml_pmp
+void USBDevice::setTrackYear(songid_t songid, int year)
+{
+ UsbSong *song = (UsbSong *) songid;
+
+ if (song)
+ {
+ wchar_t yearStr[FIELD_LENGTH] = {0};
+ updateTrackField(song, DEVICEVIEW_COL_YEAR, &year, FIELD_INTEGER);
+ song->year = year;
+ StringCchPrintf(yearStr, FIELD_LENGTH, L"%d", year);
+ AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"year", yearStr);
+ }
+}
+
+// called from ml_pmp
+void USBDevice::setTrackPlayCount(songid_t songid, int value)
+{
+ UsbSong *song = (UsbSong *) songid;
+
+ if (song)
+ {
+ wchar_t playCount[FIELD_LENGTH] = {0};
+ updateTrackField(song, DEVICEVIEW_COL_PLAY_COUNT, &value, FIELD_INTEGER);
+ song->playcount = value;
+ StringCchPrintf(playCount, FIELD_LENGTH, L"%d", value);
+ AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"playcount", playCount);
+ }
+}
+
+// called from ml_pmp
+void USBDevice::setTrackRating(songid_t songid, int value)
+{
+ UsbSong *song = (UsbSong *) songid;
+
+ if (song)
+ {
+ wchar_t rating[FIELD_LENGTH] = {0};
+ updateTrackField(song, DEVICEVIEW_COL_PLAY_COUNT, &value, FIELD_INTEGER);
+ song->playcount = value;
+ StringCchPrintf(rating, FIELD_LENGTH, L"%d", value);
+ AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"rating", rating);
+ }
+}
+
+// called from ml_pmp
+void USBDevice::setTrackLastPlayed(songid_t songid, __time64_t value)
+{
+// TODO: implement
+
+} // in unix time format
+
+// called from ml_pmp
+void USBDevice::setTrackLastUpdated(songid_t songid, __time64_t value)
+{
+// TODO: implement
+
+} // in unix time format
+
+// called from ml_pmp
+void USBDevice::setTrackAlbumArtist(songid_t songid, const wchar_t *value)
+{
+ UsbSong *song = (UsbSong *) songid;
+ if (song)
+ {
+ updateTrackField(song, DEVICEVIEW_COL_ALBUM_ARTIST, value, FIELD_STRING);
+ StringCchCopy(song->albumartist, FIELD_LENGTH, value);
+ AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"albumartist", value);
+ }
+}
+
+// called from ml_pmp
+void USBDevice::setTrackPublisher(songid_t songid, const wchar_t *value)
+{
+ UsbSong *song = (UsbSong *) songid;
+ if (song)
+ {
+ updateTrackField(song, DEVICEVIEW_COL_PUBLISHER, value, FIELD_STRING);
+ StringCchCopy(song->publisher, FIELD_LENGTH, value);
+ AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"publisher", value);
+ }
+}
+
+// called from ml_pmp
+void USBDevice::setTrackComposer(songid_t songid, const wchar_t *value)
+{
+ UsbSong *song = (UsbSong *) songid;
+ if (song)
+ {
+ updateTrackField(song, DEVICEVIEW_COL_COMPOSER, value, FIELD_STRING);
+ StringCchCopy(song->composer, FIELD_LENGTH, value);
+ AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"composer", value);
+ }
+}
+
+// called from ml_pmp
+void USBDevice::setTrackExtraInfo(songid_t songid, const wchar_t *field, const wchar_t *value)
+{
+// TODO: implement
+
+} //optional
+
+// called from ml_pmp
+bool USBDevice::playTracks(songid_t * songidList, int listLength, int startPlaybackAt, bool enqueue)
+{
+ // return false if unsupported
+ if(!enqueue) //clear playlist
+ {
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_DELETE);
+ }
+
+ for(int i=0; i<listLength; i++)
+ {
+ UsbSong *curSong = (UsbSong*)songidList[i];
+
+ if (curSong)
+ {
+ enqueueFileWithMetaStructW s={0};
+ s.filename = _wcsdup(curSong->filename);
+ s.title = _wcsdup(curSong->title);
+ s.ext = NULL;
+ s.length = curSong->length/1000;
+
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW);
+ }
+ else
+ {
+ char titleStr[32] = {0};
+ MessageBoxA(plugin.hwndWinampParent,WASABI_API_LNGSTRING(IDS_CANNOT_OPEN_FILE),
+ WASABI_API_LNGSTRING_BUF(IDS_ERROR,titleStr,32),0);
+ }
+ }
+
+ 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;
+}
+
+// called from ml_pmp
+intptr_t USBDevice::extraActions(intptr_t param1, intptr_t param2, intptr_t param3,intptr_t param4)
+{
+ switch(param1)
+ {
+ case DEVICE_SET_ICON:
+ {
+ MLTREEIMAGE * i = (MLTREEIMAGE*)param2;
+ i->hinst = plugin.hDllInstance;
+ switch(devType)
+ {
+ case TYPE_PSP:
+ i->resourceId = IDR_PSP_ICON;
+ break;
+ default:
+ i->resourceId = IDR_USB_ICON;
+ break;
+ }
+ }
+ 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_usb.dll", (devType==TYPE_PSP)?IDR_PSP_ICON:IDR_USB_ICON);
+ }
+
+ }
+ break;
+
+ case DEVICE_SUPPORTED_METADATA:
+ {
+ intptr_t supported = SUPPORTS_ARTIST | SUPPORTS_ALBUM | SUPPORTS_TITLE | SUPPORTS_TRACKNUM | SUPPORTS_DISCNUM | SUPPORTS_GENRE |
+ SUPPORTS_YEAR | SUPPORTS_SIZE | SUPPORTS_LENGTH | SUPPORTS_BITRATE | SUPPORTS_LASTUPDATED | SUPPORTS_ALBUMARTIST |
+ SUPPORTS_COMPOSER | SUPPORTS_PUBLISHER | SUPPORTS_ALBUMART;
+ return supported;
+ }
+ break;
+
+ case DEVICE_CAN_RENAME_DEVICE:
+ return 1;
+
+ case DEVICE_GET_INI_FILE:
+ StringCchCopy((wchar_t*)param2, MAX_PATH, iniFile);
+ break;
+ case DEVICE_GET_PREFS_DIALOG:
+ if(param3 == 0)
+ {
+ pref_tab * p = (pref_tab *)param2;
+ p->hinst = WASABI_API_LNG_HINST;
+ p->dlg_proc = prefs_dialogProc;
+ p->res_id = IDD_CONFIG;
+ WASABI_API_LNGSTRINGW_BUF(IDS_ADVANCED,p->title,100);
+ }
+ break;
+ case DEVICE_DONE_SETTING:
+ UsbSong * song = (UsbSong *) param2;
+ AGAVE_API_METADATA->WriteExtendedFileInfo(song->filename);
+ return true;
+ break;
+ }
+ return false;
+}
+
+// called from ml_pmp
+bool USBDevice::copyToHardDriveSupported()
+{
+ return true;
+}
+
+// called from ml_pmp
+__int64 USBDevice::songSizeOnHardDrive(songid_t song)
+{
+ // how big a song will be when copied back. Return -1 for not supported.
+// TODO: implement
+ return 0;
+
+}
+
+// called from ml_pmp
+int USBDevice::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.
+ UsbSong* track = (UsbSong*)song;
+ wchar_t * ext = PathFindExtensionW(track->filename);
+ if(ext && (lstrlen(ext)<10)) StringCchCat(path,MAX_PATH, ext); // append correct extention
+ return CopyFile(track->filename,path,callbackContext, callback, killswitch);
+}
+
+// called from ml_pmp
+// art functions
+void USBDevice::setArt(songid_t songid, void *buf, int w, int h)
+{
+ //buf is in format ARGB32*
+ // TODO: implement
+}
+
+// called from ml_pmp
+pmpart_t USBDevice::getArt(songid_t songid)
+{
+ UsbSong *song = (UsbSong *)songid;
+ ARGB32 *bits;
+ int w, h;
+ if (AGAVE_API_ALBUMART && AGAVE_API_ALBUMART->GetAlbumArt(song->filename, L"cover", &w, &h, &bits) == ALBUMART_SUCCESS && bits)
+ {
+ return (pmpart_t) new USBArt(bits, w, h);
+ }
+ return 0;
+}
+
+// called from ml_pmp
+void USBDevice::releaseArt(pmpart_t art)
+{
+ USBArt *image = (USBArt *)art;
+ delete image;
+}
+
+// called from ml_pmp
+int USBDevice::drawArt(pmpart_t art, HDC dc, int x, int y, int w, int h)
+{
+ USBArt *image = (USBArt *)art;
+ if (image)
+ {
+ HQSkinBitmap temp(image->bits, image->w, image->h); // wrap into a SkinBitmap (no copying involved)
+ DCCanvas canvas(dc);
+ temp.stretch(&canvas,x,y,w,h);
+ return 1;
+ }
+ return 0;
+}
+
+// called from ml_pmp
+void USBDevice::getArtNaturalSize(pmpart_t art, int *w, int *h)
+{
+ // TODO: implement
+ USBArt *image = (USBArt *)art;
+ if (image)
+ {
+ *w = image->w;
+ *h = image->h;
+ }
+}
+
+// called from ml_pmp
+void USBDevice::setArtNaturalSize(pmpart_t art, int w, int h)
+{
+ // TODO: implement
+}
+
+// called from ml_pmp
+void USBDevice::getArtData(pmpart_t art, void* data)
+{
+ USBArt *image = (USBArt *)art;
+ if (image)
+ memcpy(data, image->bits, image->w*image->h*sizeof(ARGB32));
+ // data ARGB32* is at natural size
+}
+
+// called from ml_pmp
+bool USBDevice::artIsEqual(pmpart_t a, pmpart_t b)
+{
+ if (a == b)
+ return true;
+ // TODO: implement
+ return false;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+// Initialize class statics
+nde_database_t USBDevice::discDB = 0;
+
+// The getter that returns the master playlist
+// the playlist vector always carries a master playlist
+USBPlaylist* USBDevice::getMasterPlaylist()
+{
+ for (std::vector<USBPlaylist*>::const_iterator e = usbPlaylists.begin(); e != usbPlaylists.end(); e++)
+ {
+ USBPlaylist* playlist = (*e);
+ if (playlist->isMaster()) return playlist;
+ }
+ return NULL;
+}
+
+// constructor
+USBDevice::USBDevice(wchar_t drive, pmpDeviceLoading * load): transcoder(NULL) {
+ deviceTable = 0;
+ StringCchPrintf(ndeDataFile, 100, L"%c:\\winamp_metadata.dat", drive);
+ StringCchPrintf(ndeIndexFile, 100, L"%c:\\winamp_metadata.idx", drive);
+ load->dev = this;
+ load->UpdateCaption = NULL;
+ //pass load to ml_pmp, ml updates load->UpdateCaption and context
+ SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)load,PMP_IPC_DEVICELOADING);
+ if(load->UpdateCaption) {
+ wchar_t buf[100] = L"";
+ WASABI_API_LNGSTRINGW_BUF(IDS_LOADING_DRIVE_X,buf,100);
+ wchar_t * x = wcsrchr(buf,L'X');
+ if(x) *x = drive;
+ load->UpdateCaption(buf,load->context);
+ }
+
+ devType = detectDeviceType(drive);
+ // load settings
+ StringCchCopy(iniFile,MAX_PATH,L"x:\\pmp_usb.ini");
+ iniFile[0]=drive;
+
+ wchar_t customName[FIELD_LENGTH] = {0};
+ GetPrivateProfileString(L"pmp_usb",L"pldir",devType==TYPE_PSP?L"X:\\PSP\\MUSIC":L"X:",pldir,sizeof(pldir)/sizeof(wchar_t),iniFile);
+ GetPrivateProfileString(L"pmp_usb",L"songFormat",devType==TYPE_PSP?L"X:\\PSP\\MUSIC\\<Artist> - <Album>\\## - <Title>":L"X:\\<Artist>\\<Album>\\## - <Title>",songFormat,sizeof(songFormat)/sizeof(wchar_t),iniFile);
+ GetPrivateProfileString(L"pmp_usb",L"supportedFormats",L"mp3;wav;wma;m4a;aac;ogg;flac",supportedFormats,sizeof(supportedFormats)/sizeof(wchar_t),iniFile);
+ GetPrivateProfileString(L"pmp_usb",L"purgeFolders",L"1",purgeFolders,sizeof(purgeFolders)/sizeof(wchar_t),iniFile);
+ GetPrivateProfileString(L"pmp_usb",L"customName",devType==TYPE_PSP?L"Sony PSP":L"",customName,sizeof(customName)/sizeof(wchar_t),iniFile);
+ pl_write_mode = GetPrivateProfileInt(L"pmp_usb",L"pl_write_mode",0,iniFile);
+ pldir[0] = drive;
+ songFormat[0] = drive;
+
+ transferQueueLength = 0;
+ this->drive = drive;
+ USBPlaylist * mpl = new USBPlaylist(*this, customName, true);
+ usbPlaylists.push_back(mpl);
+ wchar_t * pl = _wcsdup(pldir);
+ pl[0] = drive;
+ RecursiveCreateDirectory(pl);
+ wchar_t root[3] = L"X:";
+ root[0] = drive;
+
+ openDeviceTable();
+
+ fileProbe(root);
+
+ // sort out and read playlists....
+ if (WASABI_API_PLAYLISTMNGR != NULL && WASABI_API_PLAYLISTMNGR != (api_playlistmanager *)1)
+ {
+ for (std::vector<USBPlaylist*>::const_iterator e = usbPlaylists.begin(); e != usbPlaylists.end(); e++)
+ {
+ USBPlaylist* playlist = (*e);
+ if (playlist && playlist->isMaster() == false)
+ {
+ WASABI_API_PLAYLISTMNGR->Load(playlist->getFilename(), playlist);
+ }
+ }
+ }
+
+ tag();
+ devices.push_back(this);
+ extern HWND config;
+ if(config) PostMessage(config,WM_USER,0,0);
+
+ SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICECONNECTED);
+ setupTranscoder();
+ loading_devices[drive-'A']=false;
+}
+
+USBDevice::USBDevice() : transcoder(NULL), drive(0), devType(TYPE_OTHER), pl_write_mode(0), transferQueueLength(0), cacheUpToDate(false), loadedUpToDate(false)
+{
+ deviceTable = 0;
+ ZeroMemory(ndeDataFile, sizeof(ndeDataFile));
+ ZeroMemory(ndeIndexFile, sizeof(ndeIndexFile));
+
+ ZeroMemory(iniFile, sizeof(iniFile));
+ ZeroMemory(pldir, sizeof(pldir));
+ ZeroMemory(songFormat, sizeof(songFormat));
+ ZeroMemory(supportedFormats, sizeof(supportedFormats));
+ ZeroMemory(purgeFolders, sizeof(purgeFolders));
+}
+
+USBDevice::~USBDevice()
+{
+ closeDeviceTable();
+ if (transcoder) SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(WPARAM)transcoder,PMP_IPC_RELEASE_TRANSCODER);
+}
+
+//read files from device's folder 'indir'
+void USBDevice::fileProbe(wchar_t * indir) {
+ wchar_t dir[MAX_PATH] = {0};
+ WIN32_FIND_DATA FindFileData = {0};
+ StringCchPrintf(dir,MAX_PATH,L"%s\\*",indir);
+ HANDLE hFind = FindFirstFile(dir, &FindFileData);
+ if (hFind == INVALID_HANDLE_VALUE) return;
+ do {
+ if(wcscmp(FindFileData.cFileName,L".") && wcscmp(FindFileData.cFileName,L"..")) {
+ wchar_t fullfile[MAX_PATH] = {0};
+ StringCchPrintf(fullfile,MAX_PATH,L"%s\\%s",indir,FindFileData.cFileName);
+
+ if(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //file is directory
+ {
+ fileProbe(fullfile); //call until we have found a file
+ }
+ else // found a file!
+ {
+ wchar_t * ext = wcsrchr(FindFileData.cFileName,'.');
+ if(!ext) continue; //no files with extensions in the directory
+ ext++;
+ if(!_wcsicmp(ext,L"m3u")) // its a playlist
+ {
+ USBPlaylist *playlist = new USBPlaylist(*this, fullfile, false);
+ usbPlaylists.push_back(playlist);
+ continue;
+ } //its a file
+ if(supportedFormat(fullfile,supportedFormats)) //check extension
+ {
+ UsbSong *s = new UsbSong();
+ lstrcpynW(s->filename, fullfile, MAX_PATH);
+ this->getMasterPlaylist()->songs.push_back(s); //add track to alltrack list (playlist 0)
+ }
+ }
+ }
+ } while(FindNextFile(hFind, &FindFileData) != 0);
+ FindClose(hFind);
+}
+
+int USBDevice::getFileInfoW(const wchar_t *filename, const wchar_t *metadata, wchar_t *dest, size_t len)
+{
+ dest[0]=0;
+ return AGAVE_API_METADATA->GetExtendedFileInfo(filename, metadata, dest, len);
+}
+
+// read all metadata from the metadata wasabi service
+void USBDevice::fillMetaData(UsbSong *t)
+{
+ if (!t->filled)
+ {
+ wchar_t tmp[1024] = {0};
+ if (getFileInfoW(t->filename,L"artist",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
+ {
+ StringCchCopyW(t->artist, FIELD_LENGTH, tmp);
+ t->filled = true;
+ }
+
+ if (getFileInfoW(t->filename,L"title",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
+ {
+ StringCchCopyW(t->title, FIELD_LENGTH, tmp);
+ t->filled = true;
+ }
+
+ if (getFileInfoW(t->filename,L"album",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
+ {
+ StringCchCopyW(t->album, FIELD_LENGTH, tmp);
+ t->filled = true;
+ }
+
+ if (getFileInfoW(t->filename,L"composer",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
+ {
+ StringCchCopyW(t->composer, FIELD_LENGTH, tmp);
+ t->filled = true;
+ }
+
+ if (getFileInfoW(t->filename,L"publisher",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
+ {
+ StringCchCopyW(t->publisher, FIELD_LENGTH, tmp);
+ t->filled = true;
+ }
+
+ if (getFileInfoW(t->filename,L"albumartist",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
+ {
+ StringCchCopyW(t->albumartist, FIELD_LENGTH, tmp);
+ t->filled = true;
+ }
+
+ if (getFileInfoW(t->filename, L"length",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
+ {
+ t->length = _wtoi(tmp);
+ t->filled = true;
+ }
+
+ if (getFileInfoW(t->filename, L"track",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
+ {
+ t->track = _wtoi(tmp);
+ t->filled = true;
+ }
+
+ if (getFileInfoW(t->filename, L"disc",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
+ {
+ t->discnum = _wtoi(tmp);
+ t->filled = true;
+ }
+
+ if (getFileInfoW(t->filename, L"genre",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
+ {
+ StringCchCopyW(t->genre, FIELD_LENGTH, tmp);
+ t->filled = true;
+ }
+
+ if (getFileInfoW(t->filename, L"year",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
+ {
+ if (!wcsstr(tmp,L"__") && !wcsstr(tmp,L"/") && !wcsstr(tmp,L"\\") && !wcsstr(tmp,L"."))
+ {
+ wchar_t *p = tmp;
+ while (p && *p)
+ {
+ if (*p == L'_') *p=L'0';
+ p++;
+ }
+ t->year = _wtoi(tmp);
+ t->filled = true;
+ }
+ }
+
+ if (getFileInfoW(t->filename, L"bitrate",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
+ {
+ t->bitrate = _wtoi(tmp);
+ t->filled = true;
+ }
+
+ if (getFileInfoW(t->filename, L"size",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
+ {
+ t->size = _wtoi(tmp);
+ t->filled = true;
+ }
+ else
+ {
+ t->size = fileSize(t->filename);
+ t->filled = true;
+ }
+
+ if (getFileInfoW(t->filename, L"playcount",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
+ {
+ t->playcount = _wtoi(tmp);
+ t->filled = true;
+ }
+ }
+}
+
+int USBDevice::openDeviceDatabase()
+{
+ Nullsoft::Utility::AutoLock lock(dbcs);
+ if (!discDB)
+ {
+ discDB = NDE_CreateDatabase(plugin.hDllInstance);
+ }
+ return NDE_USB_SUCCESS;
+}
+
+void USBDevice::createDeviceFields()
+{
+ // create defaults
+ NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_FILENAME, L"filename", FIELD_FILENAME);
+ NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_ARTIST, L"artist", FIELD_STRING);
+ NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_ALBUM, L"album", FIELD_STRING);
+ NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_TITLE, L"title", FIELD_STRING);
+ NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_GENRE, L"genre", FIELD_STRING);
+ NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_ALBUM_ARTIST, L"albumartist", FIELD_STRING);
+ NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_PUBLISHER, L"publisher", FIELD_STRING);
+ NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_COMPOSER, L"composer", FIELD_STRING);
+ NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_YEAR, L"year", FIELD_INTEGER);
+ NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_TRACK, L"track", FIELD_INTEGER);
+ NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_BITRATE, L"bitrate", FIELD_INTEGER);
+ NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_DISC_NUMBER, L"discnumber", FIELD_INTEGER);
+ NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_LENGTH, L"length", FIELD_INTEGER);
+ NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_SIZE, L"size", FIELD_INTEGER);
+ NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_PLAY_COUNT, L"playcount", FIELD_INTEGER);
+ NDE_Table_PostColumns(deviceTable);
+ NDE_Table_AddIndexByIDW(deviceTable, 0, L"filename");
+}
+
+int USBDevice::openDeviceTable() {
+ Nullsoft::Utility::AutoLock lock(dbcs);
+ int ret = openDeviceDatabase();
+ if (ret != NDE_USB_SUCCESS)
+ return ret;
+
+ if (!deviceTable)
+ {
+ deviceTable = NDE_Database_OpenTable(discDB, ndeDataFile, ndeIndexFile,NDE_OPEN_ALWAYS,NDE_CACHE);
+ if (deviceTable) {
+ createDeviceFields();
+ }
+ }
+ return deviceTable?NDE_USB_SUCCESS:NDE_USB_FAILURE;
+}
+
+void USBDevice::closeDeviceTable()
+{
+ if (deviceTable)
+ {
+ NDE_Table_Sync(deviceTable);
+ NDE_Database_CloseTable(discDB, deviceTable);
+ deviceTable=0;
+ }
+
+}
+
+void USBDevice::CloseDatabase()
+{
+ if (discDB)
+ NDE_DestroyDatabase(discDB);
+ discDB=0;
+}
+
+static void db_setFieldInt(nde_scanner_t s, unsigned char id, int data)
+{
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, id);
+ if (!f) f = NDE_Scanner_NewFieldByID(s, id);
+ NDE_IntegerField_SetValue(f, data);
+}
+
+static void db_setFieldString(nde_scanner_t s, unsigned char id, const wchar_t *data)
+{
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, id);
+ if (!f) f = NDE_Scanner_NewFieldByID(s, id);
+ NDE_StringField_SetString(f, data);
+}
+
+static void db_removeField(nde_scanner_t s, unsigned char id)
+{
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, id);
+ if (f)
+ {
+ NDE_Scanner_DeleteField(s, f);
+ }
+}
+
+static int db_getFieldInt(nde_scanner_t s, unsigned char id, int defaultVal)
+{
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, id);
+ if (f)
+ {
+ return NDE_IntegerField_GetValue(f);
+ }
+ else
+ {
+ return defaultVal;
+ }
+}
+
+static wchar_t* db_getFieldString(nde_scanner_t s, unsigned char id)
+{
+ nde_field_t f = NDE_Scanner_GetFieldByID(s, id);
+ if (f)
+ {
+ return NDE_StringField_GetString(f);
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+void USBDevice::refreshNDECache(void) {
+ tag();
+ SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)0,IPC_WRITE_EXTENDED_FILE_INFO);
+}
+
+int filenamecmp(const wchar_t *f1, const wchar_t *f2)
+{
+ for (;;)
+ {
+ wchar_t c1 = *f1++;
+ wchar_t c2 = *f2++;
+ if (!c1 && !c2)
+ return 0;
+ if (!c1)
+ return -1;
+ if (!c2)
+ return 1;
+ c1 = towupper(c1);
+ c2 = towupper(c2);
+ if (c1 == '\\')
+ c1 = '/';
+ if (c2 == '\\')
+ c2 = '/';
+ if (c1 < c2)
+ return -1;
+ else if (c1 > c2)
+ return 1;
+ }
+}
+//
+UsbSong *USBDevice::findSongInMasterPlaylist(const wchar_t *songfn) {
+ USBPlaylist* mpl = this->getMasterPlaylist();
+ for (std::vector<UsbSong*>::const_iterator e = mpl->songs.begin(); e != mpl->songs.end(); e++)
+ {
+ if (filenamecmp(songfn, (*e)->filename) == 0) {
+ return (*e);
+ }
+ }
+ return NULL;
+}
+
+void USBDevice::tag(void)
+{
+ /**
+ loop thru the newly probed disk
+ check for updates on each of the songs
+ if there is an update or if metadata does not exist for the file, re-read the metadata
+ if there is no update and the song is found in the master playlist, just read from the db
+ */
+ USBPlaylist *mpl = this->getMasterPlaylist();
+ int top = (int)mpl->songs.size();
+
+ //first load in all songs data from ID3 - this is what we were trying to avoid
+ for(int i = 0; i < top; i++)
+ {
+ UsbSong *t = (UsbSong *)mpl->songs.at(i);
+
+ // now check if this song has changed
+ // check if the nde cache exists in the first place
+ if (songChanged(t))
+ {
+ this->fillMetaData(t);
+ // now since we've refreshed the metadata write to NDE
+ this->writeRecordToDB(t);
+ }
+ else
+ {
+ // read the record from NDE
+ if (this->readRecordFromDB(t) == false)
+ {
+ this->fillMetaData(t);
+ this->writeRecordToDB(t);
+ }
+ }
+ }
+
+ for (size_t i=0;i<usbPlaylists.size();i++)
+ {
+ USBPlaylist *pl = usbPlaylists[i];
+ if (pl->dirty)
+ {
+ // Lets delete the current playlist file
+ _wunlink(pl->filename);
+
+ USBPlaylistSaver playlistSaver(pl->filename, L"autosaved", pl);
+ playlistSaver.Save();
+ pl->dirty = false;
+ }
+ }
+
+ cacheUpToDate = true;
+ loadedUpToDate = true;
+}
+
+// check change in filetimes for the song
+bool USBDevice::songChanged(UsbSong* song)
+{
+ if (!song) return true;
+ if (!PathFileExists(ndeDataFile)) return true;
+
+ //For fLastAccess/LastWrite information, use GetFileAttributesEx
+ WIN32_FILE_ATTRIBUTE_DATA cacheFileInfo, tempInfo;
+ GetFileAttributesExW(ndeDataFile, GetFileExInfoStandard, (LPVOID)&cacheFileInfo);
+
+ if(song->filename)
+ {
+ GetFileAttributesExW(song->filename, GetFileExInfoStandard, (LPVOID)&tempInfo);
+ }
+ else
+ {
+ return true;
+ }
+
+ //cachetime - song time
+ if(CompareFileTime(&cacheFileInfo.ftLastWriteTime, &tempInfo.ftLastWriteTime) < 0)
+ {
+ return true;
+ }
+ return false;
+}
+
+// read metadata for a specific song from the NDE cache
+bool USBDevice::readRecordFromDB(UsbSong* song)
+{
+ if (!song) return false;
+
+ Nullsoft::Utility::AutoLock lock(dbcs);
+ openDeviceTable();
+ nde_scanner_t scanner = NDE_Table_CreateScanner(deviceTable);
+
+ if (NDE_Scanner_LocateFilename(scanner, DEVICEVIEW_COL_FILENAME, FIRST_RECORD, song->filename))
+ {
+ nde_field_t artist = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_ARTIST);
+ wchar_t* artistString = NDE_StringField_GetString(artist);
+ lstrcpyn(song->artist, artistString, FIELD_LENGTH);
+
+ nde_field_t album = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_ALBUM);
+ wchar_t* albumString = NDE_StringField_GetString(album);
+ lstrcpyn(song->album, albumString, FIELD_LENGTH);
+
+ nde_field_t albumArtist = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_ALBUM_ARTIST);
+ wchar_t* albumArtistString = NDE_StringField_GetString(albumArtist);
+ lstrcpyn(song->albumartist, albumArtistString, FIELD_LENGTH);
+
+ nde_field_t publisher = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_PUBLISHER);
+ wchar_t* publisherString = NDE_StringField_GetString(publisher);
+ lstrcpyn(song->publisher, publisherString, FIELD_LENGTH);
+
+ nde_field_t composer = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_COMPOSER);
+ wchar_t* composerString = NDE_StringField_GetString(composer);
+ lstrcpyn(song->composer, composerString, FIELD_LENGTH);
+
+ nde_field_t title = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_TITLE);
+ wchar_t* titleString = NDE_StringField_GetString(title);
+ lstrcpyn(song->title, titleString, FIELD_LENGTH);
+
+ nde_field_t genre = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_GENRE);
+ wchar_t* genreString = NDE_StringField_GetString(genre);
+ lstrcpyn(song->genre, genreString, FIELD_LENGTH);
+
+ nde_field_t track = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_TRACK);
+ int trackInt= NDE_IntegerField_GetValue(track);
+ song->track = trackInt;
+
+ nde_field_t year = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_YEAR);
+ int yearInt= NDE_IntegerField_GetValue(year);
+ song->year = yearInt;
+
+ nde_field_t discNumber = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_DISC_NUMBER);
+ int discNumberInt= NDE_IntegerField_GetValue(discNumber);
+ song->discnum = discNumberInt;
+
+ nde_field_t length = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_LENGTH);
+ int lengthInt= NDE_IntegerField_GetValue(length);
+ song->length = lengthInt;
+
+ nde_field_t bitrate = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_BITRATE);
+ int bitrateInt= NDE_IntegerField_GetValue(bitrate);
+ song->bitrate = bitrateInt;
+
+ nde_field_t size = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_SIZE);
+ int sizeInt= NDE_IntegerField_GetValue(size);
+ song->size = sizeInt;
+
+ nde_field_t playcount = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_PLAY_COUNT);
+ int playcountInt = NDE_IntegerField_GetValue(playcount);
+ song->playcount = playcountInt;
+ }
+ else
+ {
+ return false;
+ }
+
+ NDE_Table_DestroyScanner(deviceTable, scanner);
+ //closeDeviceTable();
+ return true;
+}
+
+
+
+// write a single record to the nde database
+void USBDevice::writeRecordToDB(UsbSong* songToPrint) {
+ Nullsoft::Utility::AutoLock lock(dbcs);
+ openDeviceTable();
+ nde_scanner_t s = NDE_Table_CreateScanner(deviceTable);
+
+ if (! NDE_Scanner_LocateFilename(s, DEVICEVIEW_COL_FILENAME, FIRST_RECORD, songToPrint->filename))
+ {
+ NDE_Scanner_New(s);
+ }
+
+ if (songToPrint->filename)
+ {
+ db_setFieldString(s, DEVICEVIEW_COL_FILENAME, songToPrint->filename);
+ }
+
+ if (songToPrint->artist)
+ {
+ db_setFieldString(s, DEVICEVIEW_COL_ARTIST, songToPrint->artist);
+ }
+
+ if (songToPrint->albumartist)
+ {
+ db_setFieldString(s, DEVICEVIEW_COL_ALBUM_ARTIST, songToPrint->albumartist);
+ }
+
+ if (songToPrint->publisher)
+ {
+ db_setFieldString(s, DEVICEVIEW_COL_PUBLISHER, songToPrint->publisher);
+ }
+
+ if (songToPrint->composer)
+ {
+ db_setFieldString(s, DEVICEVIEW_COL_COMPOSER, songToPrint->composer);
+ }
+
+ if (songToPrint->album)
+ {
+ db_setFieldString(s, DEVICEVIEW_COL_ALBUM, songToPrint->album);
+ }
+
+ if (songToPrint->title)
+ {
+ db_setFieldString(s, DEVICEVIEW_COL_TITLE, songToPrint->title);
+ }
+
+ if (songToPrint->genre)
+ {
+ db_setFieldString(s, DEVICEVIEW_COL_GENRE, songToPrint->genre);
+ }
+
+ if (songToPrint->year)
+ {
+ db_setFieldInt(s, DEVICEVIEW_COL_YEAR, songToPrint->year);
+ }
+
+ if (songToPrint->track)
+ {
+ db_setFieldInt(s, DEVICEVIEW_COL_TRACK, songToPrint->track);
+ }
+
+ if (songToPrint->bitrate)
+ {
+ db_setFieldInt(s, DEVICEVIEW_COL_BITRATE, songToPrint->bitrate);
+ }
+
+ if (songToPrint->discnum)
+ {
+ db_setFieldInt(s, DEVICEVIEW_COL_DISC_NUMBER, songToPrint->discnum);
+ }
+
+ if (songToPrint->length)
+ {
+ db_setFieldInt(s, DEVICEVIEW_COL_LENGTH, songToPrint->length);
+ }
+
+ if (songToPrint->size)
+ {
+ db_setFieldInt(s, DEVICEVIEW_COL_SIZE, (int)songToPrint->size);
+ }
+
+ if (songToPrint->playcount)
+ {
+ db_setFieldInt(s, DEVICEVIEW_COL_PLAY_COUNT, songToPrint->playcount);
+ }
+ NDE_Scanner_Post(s);
+ NDE_Table_DestroyScanner(deviceTable, s);
+ //NDE_Table_Sync(deviceTable);
+ //closeDeviceTable();
+}
+
+void USBDevice::setupTranscoder() {
+ if(transcoder) SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(WPARAM)transcoder,PMP_IPC_RELEASE_TRANSCODER);
+ transcoder = (Transcoder*)SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(WPARAM)this,PMP_IPC_GET_TRANSCODER);
+ if(!transcoder) return;
+
+ wchar_t * p = supportedFormats;
+ while(p && *p) {
+ wchar_t * np = wcschr(p,L';');
+ if(np) *np = 0;
+ transcoder->AddAcceptableFormat(p);
+ if(np) { *np = L';'; p=np+1; }
+ else return;
+ }
+}
+
+BOOL CALLBACK browseEnumProc(HWND hwnd, LPARAM lParam)
+{
+ wchar_t cl[32] = {0};
+ GetClassNameW(hwnd, cl, ARRAYSIZE(cl));
+ if (!lstrcmpiW(cl, WC_TREEVIEW))
+ {
+ PostMessage(hwnd, TVM_ENSUREVISIBLE, 0, (LPARAM)TreeView_GetSelection(hwnd));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+wchar_t pldir[MAX_PATH] = {0};
+int CALLBACK WINAPI BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
+{
+ if(uMsg == BFFM_INITIALIZED)
+ {
+ SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, (LPARAM)pldir);
+
+ // this is not nice but it fixes the selection not working correctly on all OSes
+ EnumChildWindows(hwnd, browseEnumProc, 0);
+ }
+ return 0;
+}
+
+static INT_PTR CALLBACK prefs_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
+ static USBDevice * dev;
+ switch(uMsg) {
+ case WM_INITDIALOG:
+ {
+ prefsParam* p = (prefsParam*)lParam;
+ dev = (USBDevice*)p->dev;
+ p->config_tab_init(hwndDlg,p->parent);
+ SetDlgItemTextW(hwndDlg,IDC_NAMEFORMAT,dev->songFormat);
+ SetDlgItemTextW(hwndDlg,IDC_PLDIR,dev->pldir);
+ SetDlgItemTextW(hwndDlg,IDC_SUPPORTEDFORMATS,dev->supportedFormats);
+ if(dev->purgeFolders[0]=='1') CheckDlgButton(hwndDlg,IDC_PURGEFOLDERS,BST_CHECKED);
+ else CheckDlgButton(hwndDlg,IDC_PURGEFOLDERS,BST_UNCHECKED);
+
+ SendDlgItemMessageW(hwndDlg,IDC_PL_WRITE_COMBO,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_SLASH_AT_START));
+ SendDlgItemMessageW(hwndDlg,IDC_PL_WRITE_COMBO,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_DOT_AT_START));
+ SendDlgItemMessageW(hwndDlg,IDC_PL_WRITE_COMBO,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_NO_SLASH_OR_DOT));
+ SendDlgItemMessage(hwndDlg,IDC_PL_WRITE_COMBO,CB_SETCURSEL,dev->pl_write_mode,0);
+ SetDlgItemTextW(hwndDlg,IDC_PL_WRITE_EG,WASABI_API_LNGSTRINGW(IDS_EG_SLASH+dev->pl_write_mode));
+ }
+ break;
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case IDC_NAMEFORMAT:
+ if(HIWORD(wParam)==EN_CHANGE)
+ {
+ GetDlgItemTextW(hwndDlg,IDC_NAMEFORMAT,dev->songFormat,sizeof(dev->songFormat)/sizeof(wchar_t));
+ WritePrivateProfileStringW(L"pmp_usb",L"songFormat",dev->songFormat,dev->iniFile);
+ }
+ break;
+
+ case IDC_PLDIR:
+ if(HIWORD(wParam)==EN_CHANGE)
+ {
+ GetDlgItemTextW(hwndDlg,IDC_PLDIR,dev->pldir,sizeof(dev->pldir)/sizeof(wchar_t));
+ WritePrivateProfileStringW(L"pmp_usb",L"pldir",dev->pldir,dev->iniFile);
+ }
+ break;
+
+ case IDC_SUPPORTEDFORMATS:
+ if(HIWORD(wParam)==EN_CHANGE)
+ {
+ GetDlgItemTextW(hwndDlg,IDC_SUPPORTEDFORMATS,dev->supportedFormats,sizeof(dev->supportedFormats)/sizeof(wchar_t));
+ WritePrivateProfileStringW(L"pmp_usb",L"supportedFormats",dev->supportedFormats,dev->iniFile);
+ }
+ break;
+
+ case IDC_REFRESHCACHE:
+ {
+ char titleStr[32] = {0};
+ dev->refreshNDECache();
+ MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_CACHE_UPDATED),
+ WASABI_API_LNGSTRING_BUF(IDS_SUCCESS,titleStr,32),MB_OK);
+ break;
+ }
+
+ case IDC_PL_WRITE_COMBO:
+ {
+ dev->pl_write_mode = (int)SendMessage((HWND)lParam,CB_GETCURSEL,0,0);
+ SetDlgItemTextW(hwndDlg,IDC_PL_WRITE_EG,WASABI_API_LNGSTRINGW(IDS_EG_SLASH+dev->pl_write_mode));
+
+ wchar_t tmp[16] = {0};
+ StringCchPrintf(tmp, 16, L"%d", dev->pl_write_mode);
+ WritePrivateProfileStringW(L"pmp_usb",L"pl_write_mode",tmp,dev->iniFile);
+ break;
+ }
+
+ case IDC_FILENAMEHELP:
+ {
+ char titleStr[64] = {0};
+ MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_FILENAME_FORMATTING_INFO),
+ WASABI_API_LNGSTRING_BUF(IDS_FILENAME_FORMAT_HELP,titleStr,64),MB_OK);
+ }
+ break;
+
+ case IDC_PLBROWSE:
+ {
+ wchar_t *tempWS;
+ BROWSEINFO bi = {0};
+ LPMALLOC lpm = 0;
+ wchar_t bffFileName[MAX_PATH] = {0};
+
+ bi.hwndOwner = hwndDlg;
+ bi.pszDisplayName = bffFileName;
+ bi.lpszTitle = WASABI_API_LNGSTRINGW(IDS_SELECT_FOLDER_TO_LOAD_PLAYLISTS);
+ bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_EDITBOX | BIF_NEWDIALOGSTYLE;
+ bi.lpfn = BrowseCallbackProc;
+ lstrcpynW(pldir, dev->pldir, MAX_PATH);
+ LPITEMIDLIST iil = SHBrowseForFolder(&bi);
+ if(iil)
+ {
+ SHGetPathFromIDListW(iil,bffFileName);
+ SHGetMalloc(&lpm);
+ // path is now in bffFileName
+ }
+
+ tempWS = _wcsdup(bffFileName);
+ if(tempWS[0] == dev->drive)
+ {
+ lstrcpynW(dev->pldir, tempWS, MAX_PATH);
+ SetDlgItemText(hwndDlg,IDC_PLDIR,tempWS);
+ }
+ else
+ {
+ if(bffFileName[0] != 0) //dont print error if the user selected 'cancel'
+ {
+ char titleStr[32] = {0};
+ MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_ERR_SELECTED_PATH_NOT_ON_DEVICE),
+ WASABI_API_LNGSTRING_BUF(IDS_ERROR,titleStr,32), MB_OK);
+ }
+ }
+ free(tempWS);
+ }
+ break;
+
+ case IDC_FORMATSHELP:
+ {
+ char titleStr[64] = {0};
+ MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_SUPPORTED_FORMAT_INFO),
+ WASABI_API_LNGSTRING_BUF(IDS_SUPPORTED_FORMAT_HELP,titleStr,64),MB_OK);
+ }
+ break;
+
+ case IDC_PURGEFOLDERS:
+ {
+ StringCchCopy(dev->purgeFolders, ARRAYSIZE(dev->purgeFolders),
+ ((IsDlgButtonChecked(hwndDlg,IDC_PURGEFOLDERS) == BST_CHECKED) ? L"1" : L"0"));
+ WritePrivateProfileStringW(L"pmp_usb",L"purgeFolders",dev->purgeFolders,dev->iniFile);
+ }
+ break;
+
+ case IDC_RESCAN:
+ {
+ //update changes
+ SetFileAttributesW(dev->iniFile,FILE_ATTRIBUTE_HIDDEN);
+
+ wchar_t driveletter = dev->drive; //hold on to driveletter before it goes away
+ //disconnect
+ dev->Close();
+
+ //connect
+ pmpDeviceLoading load;
+ dev = new USBDevice(driveletter,&load);
+ char titleStr[64] = {0};
+ MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_RESCAN_COMPLETE_SAVED),
+ WASABI_API_LNGSTRING_BUF(IDS_RESCAN_COMPLETE,titleStr,64),MB_OK);
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+// update a track with new metadata (string)
+void USBDevice::updateTrackField(UsbSong* song, unsigned int col, const void* newValue, int fieldType)
+{
+ if (!song) return;
+
+ Nullsoft::Utility::AutoLock lock(dbcs);
+ openDeviceTable();
+ nde_scanner_t s = NDE_Table_CreateScanner(deviceTable);
+
+ if (NDE_Scanner_LocateFilename(s, DEVICEVIEW_COL_FILENAME, FIRST_RECORD, song->filename))
+ {
+ switch (fieldType)
+ {
+ case FIELD_STRING:
+ db_setFieldString(s, col, (wchar_t *)(newValue));
+ break;
+ case FIELD_INTEGER:
+ db_setFieldInt(s, col, *((int *)newValue));
+ default:
+ break;
+ }
+ }
+
+ NDE_Scanner_Post(s);
+ NDE_Table_DestroyScanner(deviceTable, s);
+ //NDE_Table_Sync(deviceTable);
+ //closeDeviceTable();
+}
+
+UsbSong::UsbSong() {
+ filename[0]=artist[0]=album[0]=title[0]=genre[0]=albumartist[0]=publisher[0]=composer[0]=0;
+ filled=year=track=length=discnum=bitrate=playcount=(int)(size=0);
+}
+
+USBArt::USBArt(ARGB32 *bits, int w, int h) :bits(bits), w(w), h(h)
+{
+}
+
+USBArt::~USBArt()
+{
+ if (bits)
+ WASABI_API_MEMMGR->sysFree(bits);
+} \ No newline at end of file
diff --git a/Src/Plugins/Portable/pmp_usb/usbdevice.h b/Src/Plugins/Portable/pmp_usb/usbdevice.h
new file mode 100644
index 00000000..adeb887e
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/usbdevice.h
@@ -0,0 +1,242 @@
+#pragma once
+#include <windows.h>
+#include <Dbt.h>
+
+#include "../../Library/ml_pmp/transcoder.h"
+#include "../../Library/ml_pmp/pmp.h"
+
+#include "../nde/nde_c.h"
+#include "../nu/AutoLock.h"
+
+#include <vector>
+
+#define NDE_CACHE_DAT L"winamp_metadata.dat"
+#define NDE_CACHE_IDX L"winamp_metadata.idx"
+
+//Filename="E:\Howling Bells - Into The Chaos.MP3"
+//Artist="Howling Bells"
+//Album="E:"
+//Title="Into The Chaos"
+//Genre=""
+//AlbumArtist=""
+//Publisher=""
+//Composer=""
+//Year="0"
+//Track="0"
+//Bitrate="0"
+//Playcount="0"
+//Discnum="0"
+//Length="0"
+//Size="7767879"
+enum
+{
+ NDE_USB_FAILURE=0,
+ NDE_USB_SUCCESS=1,
+};
+
+enum
+{
+ DEVICEVIEW_COL_FILENAME = 0,
+ DEVICEVIEW_COL_ARTIST=1,
+ DEVICEVIEW_COL_ALBUM=2,
+ DEVICEVIEW_COL_TITLE=3,
+ DEVICEVIEW_COL_GENRE=4,
+ DEVICEVIEW_COL_ALBUM_ARTIST=5,
+ DEVICEVIEW_COL_PUBLISHER=6,
+ DEVICEVIEW_COL_COMPOSER=7,
+ DEVICEVIEW_COL_YEAR=8,
+ DEVICEVIEW_COL_TRACK=9,
+ DEVICEVIEW_COL_BITRATE=10,
+ DEVICEVIEW_COL_DISC_NUMBER=11,
+ DEVICEVIEW_COL_LENGTH=12,
+ DEVICEVIEW_COL_SIZE=13,
+ DEVICEVIEW_COL_PLAY_COUNT=14,
+};
+
+#define TAG_CACHE L"winamp_metadata.dat"
+#define FIELD_LENGTH 1024
+
+class UsbSong {
+public:
+ UsbSong();
+ wchar_t filename[MAX_PATH];
+ wchar_t artist[FIELD_LENGTH];
+ wchar_t album[FIELD_LENGTH];
+ wchar_t title[FIELD_LENGTH];
+ wchar_t genre[FIELD_LENGTH];
+ wchar_t albumartist[FIELD_LENGTH];
+ wchar_t publisher[FIELD_LENGTH];
+ wchar_t composer[FIELD_LENGTH];
+ int year,track,length,discnum,bitrate,playcount;
+ __int64 size;
+ BOOL filled;
+ wchar_t ext[ 6 ];
+};
+
+enum DeviceType {
+ TYPE_OTHER,
+ TYPE_PSP,
+};
+
+class USBPlaylist;
+
+class USBDevice : public Device
+{
+public:
+ USBDevice(wchar_t drive, pmpDeviceLoading * load);
+ ~USBDevice();
+ USBDevice();
+ void fileProbe(wchar_t * indir);
+ void tag(void); //load ID3 tags from cache or mp3 file
+ void createDeviceFields();
+ int openDeviceDatabase();
+ int openDeviceTable();
+ void closeDeviceTable();
+ static void CloseDatabase();
+
+ void refreshNDECache(void);
+ void fillMetaData(UsbSong *s);
+ static int getFileInfoW(const wchar_t *filename, const wchar_t *metadata, wchar_t *dest, size_t len);
+ void setupTranscoder();
+ USBPlaylist* getMasterPlaylist();
+ UsbSong* findSongInMasterPlaylist(const wchar_t *songfn);
+ void writeRecordToDB(UsbSong* songToPrint);
+ //////////////////////////////////////////
+
+ virtual __int64 getDeviceCapacityAvailable(); // in bytes
+ virtual __int64 getDeviceCapacityTotal(); // in bytes
+
+ virtual void Eject(); // if you ejected successfully, you MUST call PMP_IPC_DEVICEDISCONNECTED and delete this;
+ virtual void Close(); // save any changes, and call PMP_IPC_DEVICEDISCONNECTED 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 every so often so the GUI can be updated. Including when finished!
+ songid_t * songid, // fill in the songid when you are finished
+ int * killswitch // if this gets set to anything other than zero, 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);
+
+ // return the amount of space that will be taken up on the device by the track (once it has been tranferred)
+ // or 0 for incompatable. This is usually the filesize, unless you are transcoding. An estimate is acceptable.
+ virtual __int64 getTrackSizeOnDevice(const itemRecordW *track);
+
+ virtual void deleteTrack(songid_t songid); // physically remove from device. Be sure to remove it from all the playlists!
+
+ virtual void commitChanges(); // 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. -1 for failed.
+
+ 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 getTrackPublisher(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(songid_t songid, const wchar_t *value);
+ virtual void setTrackAlbum(songid_t songid, const wchar_t *value);
+ virtual void setTrackTitle(songid_t songid, const wchar_t *value);
+ virtual void setTrackTrackNum(songid_t songid, int value);
+ virtual void setTrackDiscNum(songid_t songid, int value);
+ virtual void setTrackGenre(songid_t songid, const wchar_t *value);
+ virtual void setTrackYear(songid_t songid, int year);
+ virtual void setTrackPlayCount(songid_t songid, int value);
+ virtual void setTrackRating(songid_t songid, int value);
+ virtual void setTrackLastPlayed(songid_t songid, __time64_t value); // in unix time format
+ virtual void setTrackLastUpdated(songid_t songid, __time64_t value); // in unix time format
+ virtual void setTrackAlbumArtist(songid_t songid, const wchar_t *value);
+ virtual void setTrackPublisher(songid_t songid, const wchar_t *value);
+ virtual void setTrackComposer(songid_t songid, const wchar_t *value);
+ virtual void setTrackExtraInfo(songid_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);
+
+ virtual bool copyToHardDriveSupported();
+
+ virtual __int64 songSizeOnHardDrive(songid_t 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);
+
+ // Additional attributes
+ Transcoder *transcoder;
+ wchar_t drive;
+ DeviceType devType;
+ wchar_t iniFile[MAX_PATH];
+ wchar_t pldir[MAX_PATH];
+ wchar_t songFormat[MAX_PATH];
+ wchar_t supportedFormats[MAX_PATH];
+ wchar_t purgeFolders[2];
+ int pl_write_mode; // used to determine how the playlists are stored
+ __int64 transferQueueLength;
+ std::vector<USBPlaylist*> usbPlaylists;
+ bool cacheUpToDate; //whether or not to output on device disconnect
+ bool loadedUpToDate; //whether or not songs in memory are tagged and correct
+
+ static nde_database_t discDB;
+ nde_table_t deviceTable;
+ Nullsoft::Utility::LockGuard dbcs;
+ wchar_t ndeDataFile[100];
+ wchar_t ndeIndexFile[100];
+
+private:
+ // update a track with new metadata (string)
+ void updateTrackField(UsbSong* song, unsigned int col, const void* newValue, int fieldType);
+ bool readRecordFromDB(UsbSong* song);
+ bool songChanged(UsbSong* song);
+};
+
+class USBArt
+{
+public:
+ USBArt(ARGB32 *bits, int w, int h);
+ ~USBArt();
+ ARGB32 *bits;
+ int w,h;
+}; \ No newline at end of file
diff --git a/Src/Plugins/Portable/pmp_usb/usbplaylist.cpp b/Src/Plugins/Portable/pmp_usb/usbplaylist.cpp
new file mode 100644
index 00000000..5e3ffbe5
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/usbplaylist.cpp
@@ -0,0 +1,33 @@
+#include "./usbdevice.h"
+#include "./USBPlaylist.h"
+
+#include <shlwapi.h>
+#include <strsafe.h>
+
+// dtor
+// cleanup the memory allocated for the vector of songs
+USBPlaylist::~USBPlaylist()
+{
+}
+
+// this is the constructor that gets called
+USBPlaylist::USBPlaylist(USBDevice& d, LPCTSTR fileName, BOOL m)
+ : device(d), master(m), dirty(false)
+{
+ StringCbCopyW(filename, sizeof(filename), fileName);
+ StringCbCopyW(playlistName, sizeof(playlistName), PathFindFileName(fileName));
+ StringCbCopyW(playlistPath, sizeof(playlistName), fileName);
+ PathRemoveFileSpec(playlistPath);
+}
+
+
+int USBPlaylist::OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info)
+{
+ if (NULL != filename)
+ {
+ UsbSong* song = NULL;
+ song = device.findSongInMasterPlaylist(filename);
+ songs.push_back(song);
+ }
+ return ifc_playlistloadercallback::LOAD_CONTINUE;
+} \ No newline at end of file
diff --git a/Src/Plugins/Portable/pmp_usb/usbplaylist.h b/Src/Plugins/Portable/pmp_usb/usbplaylist.h
new file mode 100644
index 00000000..15ffec6e
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/usbplaylist.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <vector>
+#include "../playlist/ifc_playlistloadercallbackT.h"
+
+class USBDevice;
+class UsbSong;
+
+class USBPlaylist: public ifc_playlistloadercallbackT<USBPlaylist>
+{
+public:
+ USBPlaylist(USBDevice& d, LPCTSTR pszPlaylist, BOOL master);
+ ~USBPlaylist();
+
+public:
+ /*** ifc_playlistloadercallback ***/
+ int OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info);
+
+public:
+ // utility
+ BOOL isMaster() { return master; }
+ wchar_t* getFilename() { return filename; }
+
+public:
+ USBDevice &device;
+ wchar_t playlistName[MAX_PATH];
+ wchar_t playlistPath[MAX_PATH];
+ typedef std::vector<UsbSong*> SongList;
+ SongList songs;
+ wchar_t filename[MAX_PATH];
+ BOOL master;
+ BOOL dirty;
+};
+
+
diff --git a/Src/Plugins/Portable/pmp_usb/usbplaylistsaver.cpp b/Src/Plugins/Portable/pmp_usb/usbplaylistsaver.cpp
new file mode 100644
index 00000000..360e09e3
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/usbplaylistsaver.cpp
@@ -0,0 +1,74 @@
+#include "./usbplaylistsaver.h"
+#include "./usbplaylist.h"
+#include "./usbdevice.h"
+#include "./api.h"
+
+#include <strsafe.h>
+
+
+USBPlaylistSaver::USBPlaylistSaver(LPCTSTR iFilename, LPCTSTR iPlaylistName, USBPlaylist *iPlaylist)
+ : title((LPTSTR)iPlaylistName), filename((LPTSTR)iFilename), playlist(iPlaylist)
+{
+}
+
+USBPlaylistSaver::~USBPlaylistSaver()
+{
+}
+
+HRESULT USBPlaylistSaver::Save()
+{
+ INT result = WASABI_API_PLAYLISTMNGR->Save(filename, this);
+
+ return (PLAYLISTMANAGER_SUCCESS == result) ? S_OK : E_FAIL;
+}
+
+size_t USBPlaylistSaver::GetNumItems()
+{
+ return (size_t) playlist->songs.size();
+}
+
+size_t USBPlaylistSaver::GetItem(size_t item, wchar_t *filename, size_t filenameCch)
+{
+ UsbSong* song = (UsbSong *) playlist->songs.at(item);
+ if (!song) return 0;
+
+ HRESULT hr = StringCchCopyEx(filename, filenameCch, song->filename, NULL, NULL, STRSAFE_IGNORE_NULLS);
+ if (FAILED(hr))
+ *filename = L'\0';
+
+ return SUCCEEDED(hr);
+}
+
+size_t USBPlaylistSaver::GetItemTitle(size_t item, wchar_t *title, size_t titleCch)
+{
+ UsbSong* song = (UsbSong *) playlist->songs.at(item);
+ if (!song) return 0;
+
+ HRESULT hr = StringCchCopyEx(title, titleCch, song->title, NULL, NULL, STRSAFE_IGNORE_NULLS);
+ if (FAILED(hr))
+ *title = L'\0';
+
+ return SUCCEEDED(hr);
+}
+
+int USBPlaylistSaver::GetItemLengthMs(size_t item)
+{
+ UsbSong* song = (UsbSong *) playlist->songs.at(item);
+ if (!song) return 0;
+
+ return song->length ? song->length: -1;
+}
+
+size_t USBPlaylistSaver::GetItemExtendedInfo(size_t item, const wchar_t *metadata, wchar_t *info, size_t infoCch)
+{
+ return 0;
+}
+
+#define CBCLASS USBPlaylistSaver
+START_DISPATCH;
+CB(IFC_PLAYLIST_GETNUMITEMS, GetNumItems)
+CB(IFC_PLAYLIST_GETITEM, GetItem)
+CB(IFC_PLAYLIST_GETITEMTITLE, GetItemTitle)
+CB(IFC_PLAYLIST_GETITEMLENGTHMILLISECONDS, GetItemLengthMs)
+CB(IFC_PLAYLIST_GETITEMEXTENDEDINFO, GetItemExtendedInfo)
+END_DISPATCH; \ No newline at end of file
diff --git a/Src/Plugins/Portable/pmp_usb/usbplaylistsaver.h b/Src/Plugins/Portable/pmp_usb/usbplaylistsaver.h
new file mode 100644
index 00000000..a7a2e098
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/usbplaylistsaver.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <wtypes.h>
+#include "../playlist/ifc_playlist.h"
+
+class USBPlaylist;
+
+class USBPlaylistSaver : public ifc_playlist
+{
+
+public:
+ USBPlaylistSaver(LPCTSTR iFilename, LPCTSTR iTitle, USBPlaylist * iPlaylist);
+ virtual ~USBPlaylistSaver();
+
+public:
+ /*** ifc_playlist ***/
+ size_t GetNumItems();
+ size_t GetItem(size_t item, wchar_t *filename, size_t filenameCch);
+ size_t GetItemTitle(size_t item, wchar_t *title, size_t titleCch);
+ int GetItemLengthMs(size_t item); // TODO: maybe microsecond for better resolution?
+ size_t GetItemExtendedInfo(size_t item, const wchar_t *metadata, wchar_t *info, size_t infoCch);
+
+ HRESULT Save();
+protected:
+ RECVS_DISPATCH;
+
+protected:
+ LPTSTR title;
+ LPTSTR filename;
+ USBPlaylist *playlist;
+}; \ No newline at end of file
diff --git a/Src/Plugins/Portable/pmp_usb/utils.cpp b/Src/Plugins/Portable/pmp_usb/utils.cpp
new file mode 100644
index 00000000..c2f582b6
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/utils.cpp
@@ -0,0 +1,355 @@
+#include <windows.h>
+#include "../../General/gen_ml/ml.h"
+#include "../../Library/ml_pmp/pmp.h"
+#include "USBDevice.h"
+#include <strsafe.h>
+#include <shlwapi.h>
+
+BOOL RecursiveCreateDirectory(wchar_t* buf1);
+__int64 fileSize(wchar_t * filename);
+bool supportedFormat(wchar_t * file, wchar_t * supportedFormats);
+DeviceType detectDeviceType(wchar_t drive);
+void removebadchars(wchar_t *s);
+wchar_t * FixReplacementVars(wchar_t *str, int str_size, Device * dev, songid_t song);
+
+void removebadchars(wchar_t *s) {
+ while (s && *s)
+ {
+ if (*s == L'?' || *s == L'/' || *s == L'\\' || *s == L':' || *s == L'*' || *s == L'\"' || *s == L'<' || *s == L'>' || *s == L'|')
+ *s = L'_';
+ s = CharNextW(s);
+ }
+}
+
+DeviceType detectDeviceType(wchar_t drive) {
+ // Check for device being a PSP
+ {
+ wchar_t memstickind[] = {drive, L":\\MEMSTICK.IND"};
+ FILE * f = _wfopen(memstickind,L"rb");
+ if(f) {
+ fclose(f);
+ WIN32_FIND_DATA FindFileData = {0};
+ wchar_t pspdir[] = {drive,L":\\PSP"};
+ HANDLE hFind = FindFirstFile(pspdir, &FindFileData);
+ if(hFind != INVALID_HANDLE_VALUE) {
+ FindClose(hFind);
+ if(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ return TYPE_PSP;
+ }
+ }
+ }
+ // Cannot recognise device type
+ return TYPE_OTHER;
+}
+
+__int64 fileSize(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;
+}
+
+// RecursiveCreateDirectory: creates all non-existent folders in a path
+BOOL RecursiveCreateDirectory(wchar_t* buf1) {
+ wchar_t *p=buf1;
+ int errors = 0;
+ if (*p) {
+ p = PathSkipRoot(buf1);
+ if (!p) return true ;
+
+ wchar_t ch='c';
+ while (ch) {
+ while (p && *p != '\\' && *p) p = CharNext(p);
+ ch = (p ? *p : 0);
+ if (p) *p = 0;
+ int pp = (int)wcslen(buf1)-1;
+
+ while(buf1[pp] == '.' || buf1[pp] == ' ' ||
+ (buf1[pp] == '\\' && (buf1[pp-1] == '.' || buf1[pp-1] == ' ' || buf1[pp-1] == '/')) ||
+ buf1[pp] == '/' && buf1)
+ {
+ if(buf1[pp] == '\\')
+ {
+ buf1[pp-1] = '_';
+ pp -= 2;
+ } else {
+ buf1[pp] = '_';
+ pp--;
+ }
+ }
+
+ WIN32_FIND_DATA fd = {0};
+ // Avoid a "There is no disk in the drive" error box on empty removable drives
+ UINT prevErrorMode = SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
+ HANDLE h = FindFirstFile(buf1,&fd);
+ SetErrorMode(prevErrorMode);
+ if (h == INVALID_HANDLE_VALUE)
+ {
+ if (!CreateDirectory(buf1,NULL)) errors++;
+ } else {
+ FindClose(h);
+ if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) errors++;
+ }
+ if (p) *p++ = ch;
+ }
+ }
+
+ return errors != 0;
+}
+
+bool supportedFormat(wchar_t * file, wchar_t * supportedFormats) {
+ wchar_t * ext = wcsrchr(file,'.');
+ if(!ext) return false;
+ ext++;
+ wchar_t * p = supportedFormats;
+ while(p && *p) {
+ bool ret=false;
+ wchar_t * np = wcschr(p,L';');
+ if(np) *np = 0;
+ if(!_wcsicmp(ext,p)) ret=true;
+ if(np) { *np = L';'; p=np+1; }
+ else return ret;
+ if(ret) return true;
+ }
+ return false;
+}
+
+// FixReplacementVars: replaces <Artist>, <Title>, <Album>, and #, ##, ##, with appropriate data
+// DOES NOT add a file extention!!
+wchar_t * fixReplacementVars(wchar_t *str, int str_size, Device * dev, songid_t song)
+{
+ #define ADD_STR(x) { x(song,outp,str_size-1-(outp-str)); removebadchars(outp); while (outp && *outp) outp++; }
+ #define ADD_STR_U(x) { x(song,outp,str_size-1-(outp-str)); removebadchars(outp); while (outp && *outp) { *outp=towupper(*outp); outp++; } }
+ #define ADD_STR_L(x) { x(song,outp,str_size-1-(outp-str)); removebadchars(outp); while (outp && *outp) { *outp=towlower(*outp); outp++; } }
+
+ wchar_t tmpsrc[4096] = {0};
+ lstrcpyn(tmpsrc,str,sizeof(tmpsrc)/sizeof(wchar_t)); //lstrcpyn is nice enough to make sure it's null terminated.
+
+ wchar_t *inp = tmpsrc;
+ wchar_t *outp = str;
+ int slash = 0;
+
+ while (inp && *inp && outp-str < str_size-2)
+ {
+ if (*inp == L'<')
+ {
+ if (!wcsncmp(inp,L"<TITLE>",7))
+ {
+ ADD_STR_U(dev->getTrackTitle);
+ inp+=7;
+ }
+ else if (!wcsncmp(inp,L"<title>",7))
+ {
+ ADD_STR_L(dev->getTrackTitle);
+ inp+=7;
+ }
+ else if (!_wcsnicmp(inp,L"<Title>",7))
+ {
+ ADD_STR(dev->getTrackTitle);
+ inp+=7;
+ }
+ else if (!wcsncmp(inp,L"<ALBUM>",7))
+ {
+ ADD_STR_U(dev->getTrackAlbum);
+ inp+=7;
+ }
+ else if (!wcsncmp(inp,L"<album>",7))
+ {
+ ADD_STR_L(dev->getTrackAlbum);
+ inp+=7;
+ }
+ else if (!_wcsnicmp(inp,L"<Album>",7))
+ {
+ ADD_STR(dev->getTrackAlbum);
+ inp+=7;
+ }
+ else if (!wcsncmp(inp,L"<GENRE>",7))
+ {
+ ADD_STR_U(dev->getTrackGenre);
+ inp+=7;
+ }
+ else if (!wcsncmp(inp,L"<genre>",7))
+ {
+ ADD_STR_L(dev->getTrackGenre);
+ inp+=7;
+ }
+ else if (!_wcsnicmp(inp,L"<Genre>",7))
+ {
+ ADD_STR(dev->getTrackGenre);
+ inp+=7;
+ }
+ else if (!wcsncmp(inp,L"<ARTIST>",8))
+ {
+ ADD_STR_U(dev->getTrackArtist);
+ inp+=8;
+ }
+ else if (!wcsncmp(inp,L"<artist>",8))
+ {
+ ADD_STR_L(dev->getTrackArtist);
+ inp+=8;
+ }
+ else if (!_wcsnicmp(inp,L"<Artist>",8))
+ {
+ ADD_STR(dev->getTrackArtist);
+ inp+=8;
+ }
+ else if (!wcsncmp(inp,L"<ALBUMARTIST>",13))
+ {
+ wchar_t temp[128] = {0};
+
+ dev->getTrackAlbumArtist(song, temp, 128);
+ if (temp[0] == 0)
+ dev->getTrackArtist(song, temp, 128);
+
+ lstrcpyn(outp,temp,str_size-1-(int)(outp-str));
+ removebadchars(outp);
+ while (outp && *outp) { *outp=towupper(*outp); outp++; }
+
+ inp+=13;
+ }
+ else if (!wcsncmp(inp,L"<albumartist>",13))
+ {
+ wchar_t temp[128] = {0};
+
+ dev->getTrackAlbumArtist(song, temp, 128);
+ if (temp[0] == 0)
+ dev->getTrackArtist(song, temp, 128);
+
+ lstrcpyn(outp,temp,str_size-1-(int)(outp-str));
+ removebadchars(outp);
+ while (outp && *outp) { *outp=towlower(*outp); outp++; }
+ inp+=13;
+ }
+ else if (!_wcsnicmp(inp,L"<Albumartist>",13))
+ {
+ wchar_t temp[128] = {0};
+
+ dev->getTrackAlbumArtist(song, temp, 128);
+ if (temp[0] == 0)
+ dev->getTrackArtist(song, temp, 128);
+
+ lstrcpyn(outp,temp,str_size-1-(int)(outp-str));
+ removebadchars(outp);
+ while (outp && *outp) outp++;
+ inp+=13;
+ }
+ else if (!_wcsnicmp(inp,L"<year>",6))
+ {
+ wchar_t year[64] = {0};
+ int y = dev->getTrackYear(song);
+ if (y) StringCchPrintf(year, ARRAYSIZE(year), L"%d", y);
+ lstrcpyn(outp,year,str_size-1-(int)(outp-str)); while (outp && *outp) outp++;
+ inp+=6;
+ }
+ else if (!_wcsnicmp(inp,L"<disc>",6))
+ {
+ wchar_t disc[16] = {0};
+ int d = dev->getTrackDiscNum(song);
+ if (d) StringCchPrintf(disc, ARRAYSIZE(disc), L"%d", d);
+ lstrcpyn(outp,disc,str_size-1-(int)(outp-str)); while (outp && *outp) outp++;
+ inp+=6;
+ }
+ else if (!_wcsnicmp(inp,L"<filename>",10))
+ {
+ wchar_t tfn[MAX_PATH], *ext, *fn;
+ StringCchCopy(tfn,MAX_PATH,((UsbSong*)song)->filename);
+ ext = wcsrchr(tfn, L'.');
+ *ext = 0; //kill extension since its added later
+ fn = wcsrchr(tfn, L'\\');
+ fn++;
+ lstrcpyn(outp,fn,str_size-1-(int)(outp-str));
+ while (outp && *outp) outp++;
+ inp+=10;
+ }
+ else
+ {
+ // use this to skip over unknown tags
+ while (inp && *inp && *inp != L'>') inp++;
+ if (inp) inp++;
+ }
+ }
+ else if (*inp == L'#')
+ {
+ int nd=0;
+ wchar_t tmp[64] = {0};
+ while (inp && *inp == L'#') nd++,inp++;
+ int track = dev->getTrackTrackNum(song);
+ if (!track)
+ {
+ while (inp && *inp == L' ') inp++;
+ while (inp && *inp == L'\\') inp++;
+ if (inp && (*inp == L'-' || *inp == L'.' || *inp == L'_')) // separator
+ {
+ inp++;
+ while (inp && *inp == L' ') inp++;
+ }
+ }
+ else
+ {
+ if (nd > 1)
+ {
+ wchar_t tmp2[32] = {0};
+ if (nd > 5) nd=5;
+ StringCchPrintf(tmp2, ARRAYSIZE(tmp2), L"%%%02dd",nd);
+ StringCchPrintf(tmp, ARRAYSIZE(tmp), tmp2,track);
+ }
+ else StringCchPrintf(tmp, ARRAYSIZE(tmp), L"%d",track);
+ }
+ lstrcpyn(outp,tmp,str_size-1-(int)(outp-str)); while (outp && *outp) outp++;
+ }
+ else
+ {
+ if (*inp == L'\\') slash += 1;
+ *outp++=*inp++;
+ }
+ }
+
+ if (outp) *outp = 0;
+
+ // if we end up with something like U:\\\ then this
+ // will set it to be just <filename> so it'll not
+ // end up making a load of bad files e.g. U:\.mp3
+ int out_len = lstrlen(str);
+ if (out_len)
+ {
+ if (out_len - 2 == slash)
+ {
+ outp = str + 3;
+ wchar_t tfn[MAX_PATH], *ext, *fn;
+ StringCchCopy(tfn,MAX_PATH,((UsbSong*)song)->filename);
+ ext = wcsrchr(tfn, L'.');
+ *ext = 0; //kill extension since its added later
+ fn = wcsrchr(tfn, L'\\');
+ fn++;
+ lstrcpyn(outp,fn,str_size-1-(int)(outp-str));
+ while (outp && *outp) outp++;
+ if (outp) *outp = 0;
+ }
+ }
+
+ inp=str;
+ outp=str;
+ wchar_t lastc=0;
+ while (inp && *inp)
+ {
+ wchar_t ch=*inp++;
+ if (ch == L'\t') ch=L' ';
+
+ if (ch == L' ' && (lastc == L' ' || lastc == L'\\' || lastc == L'/')) continue; // ignore space after slash, or another space
+
+ if ((ch == L'\\' || ch == L'/') && lastc == L' ') outp--; // if we have a space then slash, back up to write the slash where the space was
+ *outp++=ch;
+ lastc=ch;
+ }
+ if (outp) *outp=0;
+ #undef ADD_STR
+ #undef ADD_STR_L
+ #undef ADD_STR_U
+
+ return str;
+} \ No newline at end of file
diff --git a/Src/Plugins/Portable/pmp_usb/version.rc2 b/Src/Plugins/Portable/pmp_usb/version.rc2
new file mode 100644
index 00000000..e18d96dc
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_usb/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,62,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", "1,62,0,0"
+ VALUE "InternalName", "Nullsoft USB Device"
+ VALUE "LegalCopyright", "Copyright © 2006-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "pmp_usb.dll"
+ VALUE "ProductName", "Winamp"
+ VALUE "ProductVersion", STR_WINAMP_PRODUCTVER
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END