/******************************Module*Header*******************************\
* Module Name: cdoptimp.cpp
*
* Copyright (c) 1998 Microsoft Corporation.  All rights reserved.
\**************************************************************************/

#include "precomp.h"
#include "objbase.h"
#include "cdoptimp.h"
#include "cdopti.h"
#include "cddata.h"

extern HINSTANCE g_dllInst;

/////////////
// Typedefs
/////////////
typedef struct Sheet
{
    INT             iResID;
    DLGPROC         pfnDlgProc;

}SHEET,*LPSHEET;

typedef struct CDReserved
{
    LPCDOPTIONS     pCDOpts;
    BOOL            fChanged;

}CDRESERVED, *LPCDRESERVED;


/////////////
// Defines
/////////////
#define CDKEYSIZE   (20)
#define NUMKEYSRC   (2)
#define NUMPAGES    (3)


//////////										   
// Globals
//////////
TCHAR gszHelpFile[]               = TEXT("deluxcd.hlp");

const TCHAR szCDKeySet[]          = TEXT("MSDELXCD");
const TCHAR szCDPlayerPath[]      = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Applets\\DeluxeCD\\Settings");
const TCHAR szCDSysTrayPath[]     = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Run");
const TCHAR szCDSysTray[]         = TEXT("DeluxeCD");
const TCHAR szCDTrayOption[]      = TEXT("-tray");
const TCHAR szCDStartPlay[]       = TEXT("StartPlay");   
const TCHAR szCDExitStop[]        = TEXT("ExitStop");   
const TCHAR szCDDispMode[]        = TEXT("DispMode");   
const TCHAR szCDTopMost[]         = TEXT("TopMost");  
const TCHAR szCDTray[]            = TEXT("Tray");    
const TCHAR szCDPlayMode[]        = TEXT("PlayMode"); 
const TCHAR szCDIntroTime[]       = TEXT("IntroTime"); 
const TCHAR szCDDownloadEnabled[] = TEXT("DownloadEnabled");   
const TCHAR szCDDownloadPrompt[]  = TEXT("DownloadPrompt");   
const TCHAR szCDBatchEnabled[]    = TEXT("BatchEnabled");
const TCHAR szCDByArtist[]        = TEXT("ByArtist");
const TCHAR szCDConfirmUpload[]   = TEXT("ConfirmUpload");
const TCHAR szCDWindowX[]         = TEXT("WindowX");   
const TCHAR szCDWindowY[]         = TEXT("WindowY");   
const TCHAR szCDViewMode[]        = TEXT("ViewMode");   

const TCHAR szCDCurrentProvider[] = TEXT("CurrentProvider");
            
const TCHAR szCDProviderPath[]    = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Applets\\DeluxeCD\\Providers\\");
const TCHAR szCDProviderKey[]     = TEXT("Provider");
const TCHAR szCDProviderURL[]     = TEXT("ProviderURL");
const TCHAR szCDProviderName[]    = TEXT("ProviderName");
const TCHAR szCDProviderHome[]    = TEXT("ProviderHome");
const TCHAR szCDProviderLogo[]    = TEXT("ProviderLogo");
const TCHAR szCDProviderUpload[]  = TEXT("ProviderUpload");


////////////
// IUnknown Implementation for CDOpt
////////////


CCDOpt::CCDOpt()
{
    HRESULT hr;

    m_dwRef = 0;
    m_pCDData = NULL;
    m_pCDOpts = new(CDOPTIONS);
    m_hImageList = NULL;
    m_pCDTitle = NULL;
    m_hInst = NULL;
    m_hList = NULL;
    m_uDragListMsg = 0L;
    m_pfnSubProc = NULL;
    m_fEditReturn = FALSE;
    m_fVolChanged = FALSE;
    m_fAlbumsExpanded = FALSE;
    m_fDrivesExpanded = TRUE;
    m_pICDNet = FALSE;
    m_pCDUploadTitle = NULL;
    m_hTitleWnd = NULL;

    if (m_pCDOpts == NULL)
    {
        hr = E_OUTOFMEMORY;
    }
    else
    {
        memset(m_pCDOpts,0,sizeof(CDOPTIONS));

        hr = CreateCDList(&(m_pCDOpts->pCDUnitList));

        if (SUCCEEDED(hr))
        {
            m_pCDOpts->pCDData = new(CDOPTDATA);

            if (m_pCDOpts->pCDData == NULL)
            {
                hr = E_OUTOFMEMORY;
            }
            else
            {
                memset(m_pCDOpts->pCDData, 0, sizeof(CDOPTDATA));

                hr = GetCDData(m_pCDOpts->pCDData);

                if (SUCCEEDED(hr))
                {
                    hr = GetProviderData(m_pCDOpts);
                }
            }
        }
    }

    if (FAILED(hr))
    {
        DestroyCDOptions();
    }
}


CCDOpt::~CCDOpt()
{
    DestroyCDOptions();
}


STDMETHODIMP CCDOpt::QueryInterface(REFIID riid, void** ppv)
{
    *ppv = NULL;
    if (IID_IUnknown == riid || IID_ICDOpt == riid)
    {  
        *ppv = this;
    }

    if (NULL==*ppv)
    {
        return E_NOINTERFACE;
    }

    AddRef();

    return S_OK;
}

STDMETHODIMP_(ULONG) CCDOpt::AddRef(void)
{
    return ++m_dwRef;
}

STDMETHODIMP_(ULONG) CCDOpt::Release(void)
{
    if (0!=--m_dwRef)
        return m_dwRef;

    delete this;
    return 0;
}


////////////
// ICDOpt Public Method Implementation
////////////


STDMETHODIMP_(LPCDOPTIONS) CCDOpt::GetCDOpts(void)
{
    return(m_pCDOpts);
}


STDMETHODIMP_(void) CCDOpt::OrderProviders(LPCDPROVIDER *ppProviderList, LPCDPROVIDER pCurrentProvider)
{
    if (ppProviderList && *ppProviderList && pCurrentProvider)
    {
        LPCDPROVIDER pProvider;
        LPCDPROVIDER pLast = NULL;
        pProvider = *ppProviderList;

        while (pProvider)
        {
            if (pProvider == pCurrentProvider)
            {
                if (pLast != NULL)      // if current is not already head of list
                {
                    pLast->pNext = pProvider->pNext;                // Current is now removed from the list
                    pProvider->pNext = *ppProviderList;             // Have current point to head of list
                    *ppProviderList = pProvider;                    // Current is now head of list
                }

                break;
            }

            pLast = pProvider;
            pProvider = pProvider->pNext;
        }
    }
}

STDMETHODIMP CCDOpt::CreateProviderList(LPCDPROVIDER *ppProviderList)
{
    HRESULT hr = S_OK;

    if (ppProviderList)
    {
        LPCDPROVIDER pProvider = m_pCDOpts->pProviderList;
        LPCDPROVIDER pCurrentProvider = NULL;
        LPCDPROVIDER pNewProvider = NULL;
        LPCDPROVIDER pLast = NULL;

        while (pProvider)
        {
            pNewProvider = (LPCDPROVIDER) new(CDPROVIDER);
            
            if (pNewProvider == NULL)
            {
                hr = E_FAIL;
                break;
            }
            
            memcpy(pNewProvider, pProvider, sizeof(CDPROVIDER));
            pNewProvider->pNext = NULL;

            if (pLast)
            {
                pLast->pNext = pNewProvider;
            }
            else
            {
                *ppProviderList = pNewProvider;
            }
            
            if (pProvider == m_pCDOpts->pCurrentProvider)
            {
                pCurrentProvider = pNewProvider;
            }
                               
            pLast = pNewProvider;
            pProvider = pProvider->pNext;
        }

        if (SUCCEEDED(hr))
        {
            OrderProviders(ppProviderList, pCurrentProvider);
        }
        else
        {
            DestroyProviderList(ppProviderList);
        }
    }

    return(hr);
}

STDMETHODIMP_(void) CCDOpt::DestroyProviderList(LPCDPROVIDER *ppProviderList)
{
    if (ppProviderList)
    {
        while (*ppProviderList)
        {
            LPCDPROVIDER pTemp = *ppProviderList;
            *ppProviderList = (*ppProviderList)->pNext;
            delete pTemp;
        }
    }
}


STDMETHODIMP_(void) CCDOpt::UpdateRegistry(void)
{
    if (m_pCDOpts && m_pCDOpts->pCDData)
    {
        SetCDData(m_pCDOpts->pCDData);
    }
}


STDMETHODIMP CCDOpt::OptionsDialog(HWND hWnd, LPCDDATA pCDData, CDOPT_PAGE nStartPage)
{
    HRESULT hr = S_OK;
    PROPSHEETHEADER psh;
    PROPSHEETPAGE psp[NUMPAGES];
    int page;
    int pages;
    TCHAR str[MAX_PATH];

    if (m_pCDOpts == NULL)
    {
        hr = E_INVALIDARG;
    }
    else
    {
        LPCDOPTIONS pCDCopy = NULL;
        CDRESERVED  cdReserved;

        cdReserved.pCDOpts = m_pCDOpts;
        cdReserved.fChanged = FALSE;

        m_pCDOpts->pReserved = (UINT_PTR)&cdReserved;

        if (pCDData && SUCCEEDED(pCDData->CheckDatabase(hWnd)))
        {
            m_pCDData = pCDData;
            m_pCDData->AddRef();
        }


        hr = CopyOptions();

        if (SUCCEEDED(hr))
        {
            pages = NUMPAGES;

            if (m_pCDData == NULL)
            {
                nStartPage = CDOPT_PAGE_PLAY;
                pages = 1;
            }

            for (page = 0; page < pages; page++)
            {
                memset(&psp[page],0,sizeof(PROPSHEETPAGE));
                psp[page].dwSize = sizeof(PROPSHEETPAGE);
                psp[page].dwFlags = PSP_DEFAULT;
                psp[page].hInstance = g_dllInst;
                psp[page].lParam = (LPARAM) this;

                switch (page)
                {
                    case 0:
                        psp[page].pszTemplate = MAKEINTRESOURCE(IDD_CDPLAYEROPTIONS);
                        psp[page].pfnDlgProc = (DLGPROC) CCDOpt::PlayerOptionsProc;
                    break;

                    case 1:
                        psp[page].pszTemplate = MAKEINTRESOURCE(IDD_CDTITLEOPTIONS);
                        psp[page].pfnDlgProc = (DLGPROC) CCDOpt::TitleOptionsProc;
                    break;

                    case 2:
                        psp[page].pszTemplate = MAKEINTRESOURCE(IDD_CDPLAYLISTS);
                        psp[page].pfnDlgProc = (DLGPROC) CCDOpt::PlayListsProc;
                    break;
                }
            }

            LoadString( g_dllInst, IDS_CDOPTIONS, str, sizeof( str )/sizeof(TCHAR) );

            memset(&psh,0,sizeof(psh));
            psh.dwSize = sizeof(psh);
            psh.dwFlags = PSH_DEFAULT | PSH_PROPSHEETPAGE; 
            psh.hwndParent = hWnd;
            psh.hInstance = g_dllInst;
            psh.pszCaption = str;
            psh.nPages = pages;
            psh.nStartPage = nStartPage;
            psh.ppsp = psp;

            m_hInst = g_dllInst;

            if (PropertySheet(&psh) == -1)
            {
                hr = E_FAIL;            // Big problem.
            }

            DumpOptionsCopy();

            if (SUCCEEDED(hr))
            {
                if (!cdReserved.fChanged)
                {
                    hr = S_FALSE;       // No changes
                }
                else
                {
                    hr = S_OK;          // There have been changes
                }
            }
        }

        if (m_pCDData)
        {
            m_pCDData->Release();
            m_pCDData = NULL;
        }
    }

    return(hr);
}

STDMETHODIMP_(void) CCDOpt::DestroyCDOptions(void)
{
    if (m_pCDOpts)
    {
        if (m_pCDOpts->pCDData)
        {
            delete m_pCDOpts->pCDData;
        }

        if (m_pCDOpts->pProviderList)
        {
            DestroyProviderList(&(m_pCDOpts->pProviderList));
        }

        if (m_pCDOpts->pCDUnitList)
        {
            DestroyCDList(&(m_pCDOpts->pCDUnitList));
        }

        delete m_pCDOpts;
    }
}


STDMETHODIMP_(void) CCDOpt::RegGetByte(HKEY hKey, const TCHAR *szKey, LPBYTE pByte, BYTE bDefault)
{
    DWORD dwSize = sizeof(DWORD);
    DWORD dwByte;

    if (RegQueryValueEx(hKey, szKey, NULL, NULL, (LPBYTE) &dwByte, &dwSize) != NO_ERROR)
    {
        *pByte = bDefault;   
    }
    else
    {
        *pByte = (BYTE) dwByte;
    }
}


STDMETHODIMP_(void) CCDOpt::RegGetDWORD(HKEY hKey, const TCHAR *szKey, LPDWORD pdwData, DWORD dwDefault)
{
    DWORD dwSize = sizeof(DWORD);

    if (RegQueryValueEx(hKey, szKey, NULL, NULL, (LPBYTE) pdwData, &dwSize) != NO_ERROR)
    {
        *pdwData = dwDefault;   
    }
}


STDMETHODIMP_(void) CCDOpt::RegSetByte(HKEY hKey, const TCHAR *szKey, BYTE bData)
{
    DWORD dwData = (DWORD) bData;

    RegSetValueEx( hKey, (LPTSTR) szKey, 0, REG_DWORD, (LPBYTE) &dwData, sizeof(DWORD) );
}


STDMETHODIMP_(void) CCDOpt::RegSetDWORD(HKEY hKey, const TCHAR *szKey, DWORD dwData)
{
    RegSetValueEx( hKey, (LPTSTR) szKey, 0, REG_DWORD,(LPBYTE) &dwData, sizeof(DWORD) );
}


STDMETHODIMP_(BOOL) CCDOpt::GetUploadPrompt(void)
{
    HKEY        hKey;
    BOOL fConfirm = CDDEFAULT_CONFIRMUPLOAD;

    if (RegOpenKeyEx(HKEY_CURRENT_USER , szCDPlayerPath , 0 , KEY_READ , &hKey) == ERROR_SUCCESS)
    {
        RegGetByte(hKey,     szCDConfirmUpload,  (LPBYTE) &fConfirm,      CDDEFAULT_CONFIRMUPLOAD);
        RegCloseKey(hKey);
    }

    return fConfirm;
}


STDMETHODIMP_(void) CCDOpt::SetUploadPrompt(BOOL fConfirmUpload)
{
    HKEY        hKey;

    if (RegCreateKeyEx( HKEY_CURRENT_USER, (LPTSTR)szCDPlayerPath, 0, NULL, 0,KEY_WRITE | KEY_READ, NULL, &hKey, NULL ) == ERROR_SUCCESS)
    {            
        RegSetByte(hKey,     szCDConfirmUpload,  (BYTE) fConfirmUpload);
        RegCloseKey(hKey);
    }
}


STDMETHODIMP CCDOpt::GetCDData(LPCDOPTDATA pCDData)
{
    HRESULT     hr = S_OK;
    HKEY        hKey;

    if (pCDData)
    {
        if (RegOpenKeyEx(HKEY_CURRENT_USER , szCDPlayerPath , 0 , KEY_READ , &hKey) == ERROR_SUCCESS)
        {
            DWORD dwSize = sizeof(BOOL);
            
            RegGetByte(hKey,     szCDStartPlay,      (LPBYTE) &pCDData->fStartPlay,          CDDEFAULT_START);
            RegGetByte(hKey,     szCDExitStop,       (LPBYTE) &pCDData->fExitStop,           CDDEFAULT_EXIT);
            RegGetByte(hKey,     szCDDispMode,       (LPBYTE) &pCDData->fDispMode,           CDDEFAULT_DISP);
            RegGetByte(hKey,     szCDTopMost,        (LPBYTE) &pCDData->fTopMost,            CDDEFAULT_TOP);
            RegGetByte(hKey,     szCDTray,           (LPBYTE) &pCDData->fTrayEnabled,        CDDEFAULT_TRAY);
            RegGetDWORD(hKey,    szCDIntroTime,      (LPDWORD) &pCDData->dwIntroTime,        CDDEFAULT_INTRO);
            RegGetDWORD(hKey,    szCDPlayMode,       (LPDWORD) &pCDData->dwPlayMode,         CDDEFAULT_PLAY);
            RegGetByte(hKey,     szCDDownloadEnabled,(LPBYTE) &pCDData->fDownloadEnabled,    CDDEFAULT_DOWNLOADENABLED);
            RegGetByte(hKey,     szCDDownloadPrompt, (LPBYTE) &pCDData->fDownloadPrompt,     CDDEFAULT_DOWNLOADPROMPT);
            RegGetByte(hKey,     szCDBatchEnabled,   (LPBYTE) &pCDData->fBatchEnabled,       CDDEFAULT_BATCHENABLED);
            RegGetByte(hKey,     szCDByArtist,       (LPBYTE) &pCDData->fByArtist,           CDDEFAULT_BYARTIST);
            RegGetDWORD(hKey,    szCDWindowX,        (LPDWORD) &pCDData->dwWindowX,          CW_USEDEFAULT);
            RegGetDWORD(hKey,    szCDWindowY,        (LPDWORD) &pCDData->dwWindowY,          CW_USEDEFAULT);
            RegGetDWORD(hKey,    szCDViewMode,       (LPDWORD) &pCDData->dwViewMode,         0);

            RegCloseKey(hKey);
        }
        else     // Just use the defaults
        {
            pCDData->fStartPlay         = CDDEFAULT_START;   
            pCDData->fExitStop          = CDDEFAULT_EXIT;   
            pCDData->fDispMode          = CDDEFAULT_DISP;   
            pCDData->fTopMost           = CDDEFAULT_TOP;   
            pCDData->fTrayEnabled       = CDDEFAULT_TRAY;   
            pCDData->dwIntroTime        = CDDEFAULT_INTRO; 
            pCDData->dwPlayMode         = CDDEFAULT_PLAY; 
            pCDData->fDownloadEnabled   = CDDEFAULT_DOWNLOADENABLED;   
            pCDData->fDownloadPrompt    = CDDEFAULT_DOWNLOADPROMPT;   
            pCDData->fBatchEnabled      = CDDEFAULT_BATCHENABLED;            
            pCDData->fByArtist          = CDDEFAULT_BYARTIST;            
            pCDData->dwWindowX          = CW_USEDEFAULT ;            
            pCDData->dwWindowY          = CW_USEDEFAULT ;            
            pCDData->dwViewMode         = 0;            
        }
    }

    return(hr);
}


STDMETHODIMP CCDOpt::SetCDData(LPCDOPTDATA pCDData)
{
    HRESULT     hr = E_FAIL;
    HKEY        hKey;

    if (pCDData)
    {
        if (RegCreateKeyEx( HKEY_CURRENT_USER, (LPTSTR)szCDPlayerPath, 0, NULL, 0,KEY_WRITE | KEY_READ, NULL, &hKey, NULL ) == ERROR_SUCCESS)
        {
            DWORD dwSize = sizeof(BOOL);
            
            hr = S_OK;

            RegSetByte(hKey,     szCDStartPlay,      (BYTE) pCDData->fStartPlay);
            RegSetByte(hKey,     szCDExitStop,       (BYTE) pCDData->fExitStop);
            RegSetByte(hKey,     szCDDispMode,       (BYTE) pCDData->fDispMode);
            RegSetByte(hKey,     szCDTopMost,        (BYTE) pCDData->fTopMost);
            RegSetByte(hKey,     szCDTray,           (BYTE) pCDData->fTrayEnabled);
            RegSetDWORD(hKey,    szCDIntroTime,      (DWORD) pCDData->dwIntroTime);
            RegSetDWORD(hKey,    szCDPlayMode,       (DWORD) pCDData->dwPlayMode);
            RegSetByte(hKey,     szCDDownloadEnabled,(BYTE) pCDData->fDownloadEnabled);
            RegSetByte(hKey,     szCDDownloadPrompt, (BYTE) pCDData->fDownloadPrompt);
            RegSetByte(hKey,     szCDBatchEnabled,   (BYTE) pCDData->fBatchEnabled);
            RegSetByte(hKey,     szCDByArtist,       (BYTE) pCDData->fByArtist);
            RegSetDWORD(hKey,    szCDWindowX,        (DWORD) pCDData->dwWindowX);
            RegSetDWORD(hKey,    szCDWindowY,        (DWORD) pCDData->dwWindowY);
            RegSetDWORD(hKey,    szCDViewMode,       (DWORD) pCDData->dwViewMode);
           
            RegCloseKey(hKey);

            if (RegCreateKeyEx( HKEY_LOCAL_MACHINE, (LPTSTR)szCDSysTrayPath, 0, NULL, 0,KEY_WRITE | KEY_READ, NULL, &hKey, NULL ) == ERROR_SUCCESS)
            {
                TCHAR szPath[MAX_PATH];
                TCHAR szCommand[MAX_PATH + 5];

                if (pCDData->fTrayEnabled)
                {
                    GetModuleFileName(NULL, szPath, sizeof(szPath)/sizeof(TCHAR));
                    wsprintf(szCommand, TEXT("%s %s"), szPath, szCDTrayOption);
                    RegSetValueEx( hKey, (LPTSTR) szCDSysTray, 0, REG_SZ,(LPBYTE) szCommand, (wcslen(szCommand)*sizeof(TCHAR))+sizeof(TCHAR));
                }
                else
                {
                    RegDeleteValue( hKey, (LPTSTR) szCDSysTray);
                }

                RegCloseKey(hKey);
            }
        }
    }

    return(hr);
}



STDMETHODIMP_(void) CCDOpt::GetCurrentProviderURL(TCHAR *szProviderURL)
{
    HKEY hKey;

    if (szProviderURL)
    {
        if (RegOpenKeyEx(HKEY_CURRENT_USER , szCDPlayerPath , 0 , KEY_READ , &hKey) == ERROR_SUCCESS)
        {
            DWORD dwSize = MAX_PATH;
            
            RegQueryValueEx(hKey, szCDCurrentProvider, NULL, NULL, (LPBYTE) szProviderURL, &dwSize);

            RegCloseKey(hKey);
        }
    }
}



STDMETHODIMP CCDOpt::GetProviderData(LPCDOPTIONS pCDOpts)
{
    HRESULT         hr = S_OK;
    DWORD           dwCount = 0;
    TCHAR           szPath[MAX_PATH];
    BOOL            done = FALSE;
    LPCDPROVIDER    pProvider;
    LPCDPROVIDER    *ppLast;
    HKEY            hKey;
    TCHAR           szProviderURL[MAX_PATH];

    if (pCDOpts)
    {
        szProviderURL[0] = TEXT('\0');

        GetCurrentProviderURL(szProviderURL);

        ppLast = &(pCDOpts->pProviderList);

        while (!done)
        {
            wsprintf(szPath,TEXT("%s%s%04d"), szCDProviderPath, szCDProviderKey, dwCount);

            if (RegOpenKeyEx(HKEY_LOCAL_MACHINE , szPath , 0 , KEY_READ , &hKey) == ERROR_SUCCESS)
            {
                BOOL fGotIt = FALSE;

                pProvider = new (CDPROVIDER);
            
                if (pProvider == NULL)
                {
                    hr = E_OUTOFMEMORY;
                    done = TRUE;
                }   
                else
                {
                    memset(pProvider, 0, sizeof(CDPROVIDER));

                    DWORD cbSize = sizeof(pProvider->szProviderURL);
        
                    if (RegQueryValueEx(hKey, szCDProviderURL, NULL, NULL, (LPBYTE) pProvider->szProviderURL, &cbSize) == NO_ERROR)
                    {
                        cbSize = sizeof(pProvider->szProviderName);

                        if (RegQueryValueEx(hKey, szCDProviderName, NULL, NULL, (LPBYTE) pProvider->szProviderName, &cbSize) == NO_ERROR)
                        {
                            cbSize = sizeof(pProvider->szProviderHome);

                            if (RegQueryValueEx(hKey, szCDProviderHome, NULL, NULL, (LPBYTE) pProvider->szProviderHome, &cbSize) == NO_ERROR)
                            {
                                TCHAR szTempLogo[MAX_PATH];
                                cbSize = sizeof(szTempLogo);

                                if (RegQueryValueEx(hKey, szCDProviderLogo, NULL, NULL, (LPBYTE) szTempLogo, &cbSize) == NO_ERROR)
                                {
                                    ExpandEnvironmentStrings(szTempLogo,pProvider->szProviderLogo,sizeof(pProvider->szProviderLogo)/sizeof(TCHAR));

                                    cbSize = sizeof(pProvider->szProviderUpload);
                                    RegQueryValueEx(hKey, szCDProviderUpload, NULL, NULL, (LPBYTE) pProvider->szProviderUpload, &cbSize);

                                    *ppLast = pProvider;
                                    ppLast = &(pProvider)->pNext;
                                    fGotIt = TRUE;

                                    if (pCDOpts->pDefaultProvider == NULL)
                                    {
                                        pCDOpts->pDefaultProvider = pProvider;
                                        pCDOpts->pCurrentProvider = pProvider;
                                    }

                                    if (!lstrcmp(szProviderURL, pProvider->szProviderURL))
                                    {
                                        pCDOpts->pCurrentProvider = pProvider;
                                    }

                                    #ifdef DEBUG

                                    char szCert[255];
    
                                    CreateProviderKey(pProvider, szCert, sizeof(szCert));

                                    char szOut[255];

                                    wsprintf(szOut,"%s = %s, Verify = %d\n", pProvider->szProviderName, szCert, VerifyProvider(pProvider, szCert));
                                    OutputDebugString(szOut);

                                    #endif
                                }
                            }
                        }
                    }

                    if (!fGotIt)
                    {
                        delete pProvider;
                        pProvider = NULL;
                    }
                }

                RegCloseKey(hKey);

                dwCount++;
            }
            else
            {
                done = TRUE;
            }
        }
    }

    return(hr);
}


STDMETHODIMP CCDOpt::SetProviderData(LPCDOPTIONS pCDOpts)
{
    HRESULT         hr = S_OK;
    DWORD           dwCount = 0;
    BOOL            done = FALSE;
    LPCDPROVIDER    pProvider;
    HKEY            hKey;

    pProvider = pCDOpts->pCurrentProvider;

    if (pProvider)
    {
        if (RegCreateKeyEx( HKEY_CURRENT_USER, (LPTSTR)szCDPlayerPath, 0, NULL, 0,KEY_WRITE | KEY_READ, NULL, &hKey, NULL ) == ERROR_SUCCESS)
        {
            RegSetValueEx( hKey, (LPTSTR) szCDCurrentProvider, 0, REG_SZ, (LPBYTE) pProvider->szProviderURL, (lstrlen(pProvider->szProviderURL)*sizeof(TCHAR))+sizeof(TCHAR));
            
            RegCloseKey(hKey);
        }
    }

    return(hr);
}




STDMETHODIMP_(void) CCDOpt::DumpOptionsCopy(void)
{ 
    if (m_pCDCopy)
    {
        if (m_pCDCopy->pCDData)
        {
            delete m_pCDCopy->pCDData;
        }

        delete m_pCDCopy;

        m_pCDCopy = NULL;
    }
}



STDMETHODIMP CCDOpt::CopyOptions(void)
{
    HRESULT hr = S_OK;

    if (m_pCDOpts)
    {
        m_pCDCopy = (LPCDOPTIONS) new(CDOPTIONS);   
  
        if (m_pCDCopy == NULL)
        {
            hr = E_OUTOFMEMORY;
        }
        else
        {
            memcpy(m_pCDCopy, m_pCDOpts, sizeof(CDOPTIONS));

            m_pCDCopy->pCDData = new (CDOPTDATA);

            if (m_pCDCopy->pCDData == NULL)
            {
                hr = E_OUTOFMEMORY;
            }
            else
            {
                memcpy(m_pCDCopy->pCDData, m_pCDOpts->pCDData, sizeof(CDOPTDATA));
            }
        }
    }

    if (FAILED(hr))
    {
        DumpOptionsCopy();
    }

    return(hr);
}




STDMETHODIMP_(BOOL) CCDOpt::OptionsChanged(LPCDOPTIONS pCDOpts)
{
    BOOL fChanged = FALSE;

    if (pCDOpts)
    {
        LPCDRESERVED pCDReserved =  (LPCDRESERVED) pCDOpts->pReserved;
        LPCDOPTIONS pCDOriginal =   pCDReserved->pCDOpts;

        fChanged = m_fVolChanged;

        if (!fChanged)
        {
            if (memcmp(pCDOpts->pCDData,pCDOriginal->pCDData,sizeof(CDOPTDATA)))
            {
                fChanged = TRUE;
            }
            else if (pCDOpts->pCurrentProvider != pCDOriginal->pCurrentProvider)
            {
                fChanged = TRUE;
            }
            else if (m_pCDData)
            {
                LPCDTITLE pCDTitle = m_pCDData->GetTitleList();

                while (pCDTitle)
                {
                    if (pCDTitle->fChanged || pCDTitle->fRemove)
                    {
                        fChanged = TRUE;
                        break;
                    }

                    pCDTitle = pCDTitle->pNext;
                }
            }
        }
    }

    return(fChanged);
}


STDMETHODIMP_(void) CCDOpt::ApplyCurrentSettings(void)
{

    if (m_pCDCopy)
    {
        if (OptionsChanged(m_pCDCopy))
        {
            LPCDRESERVED pCDReserved =  (LPCDRESERVED) m_pCDCopy->pReserved;

            pCDReserved->fChanged = TRUE;

            memcpy(m_pCDOpts->pCDData, m_pCDCopy->pCDData, sizeof(CDOPTDATA));
            m_pCDOpts->pCurrentProvider = m_pCDCopy->pCurrentProvider;

            SetCDData(m_pCDCopy->pCDData);
            SetProviderData(m_pCDCopy);

            if (m_pCDData)
            {
                m_pCDData->PersistTitles();
            }

            if (m_pCDCopy->pfnOptionsCallback)
            {
                m_pCDCopy->pfnOptionsCallback(m_pCDCopy);
            }
        }
    }
}


STDMETHODIMP_(void) CCDOpt::ToggleApplyButton(HWND hDlg)
{
    HWND hwndSheet;

    if (m_pCDCopy)
    {
        hwndSheet = GetParent(hDlg);

        if (OptionsChanged(m_pCDCopy))
        {
            PropSheet_Changed(hwndSheet,hDlg);
        }
        else
        {
            PropSheet_UnChanged(hwndSheet,hDlg);
        }
    }
}



STDMETHODIMP CCDOpt::AcquireKey(LPCDKEY pCDKey, char *szName)
{
    HRESULT hr = E_FAIL;
    DWORD dwLength = strlen(szName);

    pCDKey->hProv = NULL;

    if(!CryptAcquireContext(&(pCDKey->hProv), szCDKeySet, NULL, PROV_RSA_FULL, 0 )) 
    {
        hr = GetLastError();

        if (hr == NTE_BAD_KEYSET)
        {
            if(CryptAcquireContext(&(pCDKey->hProv), szCDKeySet, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET )) 
            {
                hr = S_OK;
            }
            else
            {
                hr = GetLastError();
            }
        }
    }

    if(pCDKey->hProv) 
    {
        if(CryptCreateHash(pCDKey->hProv, CALG_MD5, 0, 0, &(pCDKey->hHash))) 
        {
            if(CryptHashData(pCDKey->hHash, (BYTE *)szName, dwLength, 0)) 
            {
                if(CryptDeriveKey(pCDKey->hProv, CALG_RC2, pCDKey->hHash, CRYPT_EXPORTABLE, &(pCDKey->hKey))) 
                {
                    hr = S_OK;
                }
            }
        }
    }

    if (FAILED(hr))
    {
        hr = GetLastError();
    }

    return(hr);
}

STDMETHODIMP_(void) CCDOpt::ReleaseKey(LPCDKEY pCDKey)
{
    if(pCDKey->hHash)
    { 
        CryptDestroyHash(pCDKey->hHash);
        pCDKey->hHash = 0;
    }

    if(pCDKey->hKey) 
    {
        CryptDestroyKey(pCDKey->hKey); 
        pCDKey->hKey = 0;
    }

    if(pCDKey->hProv)
    {   
        CryptReleaseContext(pCDKey->hProv, 0);
        pCDKey->hProv = 0;
    }
}


// This function takes a certification key, decrypts it and determines it's validity
//
// It first converts the returned data from it's re-able numeric text version, into
// it's raw encrypted data format.
//
// It then generates the data key using the current provider data, as was done when the key
// was generated
//
// It then decrypts the encrypted data and compares it to the data key, if they match, great
// if not, then this provider is not certified and was attempting to mess with us.

STDMETHODIMP_(BOOL) CCDOpt::VerifyProvider(LPCDPROVIDER pCDProvider, TCHAR *szCertKey)
{
    BOOL fCertified = FALSE;
    CDKEY cdKey;
    char  szKey[CDKEYSIZE * 2]; //note: crypto doesn't know unicode, we'll do the conversion later
    TCHAR szMatch[CDKEYSIZE];
    DWORD dwSize;
    TCHAR *szSrc;
    TCHAR szHex[3];
    LPBYTE pData;
    HRESULT hr;

    szSrc = szCertKey;
    dwSize = lstrlen(szCertKey);
    szHex[2] = TEXT('\0');
    pData = (LPBYTE) szKey;

    for (DWORD dwPos = 0; dwPos < dwSize; dwPos += 2)
    {
        szHex[0] = szSrc[0];
        szHex[1] = szSrc[1];
        szSrc += 2;
        _stscanf(szHex,TEXT("%xd"),pData);
        pData++;
    }

    dwSize = dwSize >> 1;

    memset(&cdKey,0,sizeof(cdKey));

    if (SUCCEEDED(CreateCertString(pCDProvider, szMatch)))
    {
        char chKeyName[MAX_PATH];
        #ifdef UNICODE
        WideCharToMultiByte(CP_ACP, 0, pCDProvider->szProviderName, 
									    -1, chKeyName, MAX_PATH, NULL, NULL); 
        #else
        strcpy(chKeyName,pCDProvider->szProviderName);
        #endif

        hr = AcquireKey(&cdKey, chKeyName);

        if (SUCCEEDED(hr))
        {         
            if (CryptDecrypt(cdKey.hKey, 0, TRUE, 0, (BYTE *) szKey, &dwSize))
            {
                szKey[dwSize] = TEXT('\0');

                //convert key back to unicode for string comparison
                #ifdef UNICODE
			    wchar_t wszKey[CDKEYSIZE*2];
			    MultiByteToWideChar( CP_ACP, 0, szKey, -1, wszKey, sizeof(wszKey) / sizeof(wchar_t) );
                #else
                char wszKey[CDKEYSIZE*2];
                strcpy(wszKey,szKey);
                #endif

                if (lstrcmp(szMatch, wszKey) == 0)
                {
                    fCertified = TRUE;
                }
            }
            else
            {
                hr = GetLastError();

                if (hr == NTE_PERM)
                {
                    //succeed in the case where crypto fails due to import restrictions (i.e. France)
                    fCertified = TRUE;
                }
            } 

            ReleaseKey(&cdKey);
        }
    }

    return fCertified;
}


// This function creates a string to be encrypted based on data in the provider header
//
// It takes the provider name and the provider URL and strips out any spaces and punctuation
// Of the data that remains, it only uses every other character. It fills out the key
// to exactly CDKEYSIZE characters, when it's full it stops, if it runs out of input
// characters from the header, it simply wraps back to the begining of the input data until full.
// Also, the output characters are stored in reverse order than they are found and every other
// character is capitalized, while all others are lowercased.
// This generates a key that is encrypted using crypto.  
//
// During runtime in the shipped product, the key passed down to us is decrypted, this key is
// re-generated from the provider and the strings must match, if not, well, then it's not certified.
//
STDMETHODIMP CCDOpt::CreateCertString(LPCDPROVIDER pCDProvider, TCHAR *szCertStr)
{
    HRESULT hr = S_OK;
    TCHAR *pDest = szCertStr + (CDKEYSIZE - 2);
    TCHAR *pSrc = NULL;
    TCHAR *pSrcPtrs[NUMKEYSRC];
    DWORD count = 0;
    DWORD dwSrc = 0;

    pSrcPtrs[0] = pCDProvider->szProviderName;
    pSrcPtrs[1] = pCDProvider->szProviderURL;

    pSrc = pSrcPtrs[dwSrc];

    while(count < (DWORD)(CDKEYSIZE - 1))
    {
        while(*pSrc && (_istspace(*pSrc) || _istpunct(*pSrc)))
        {
            pSrc++;
        }

        if (*pSrc == TEXT('\0'))
        {
            dwSrc = (dwSrc + 1) % NUMKEYSRC;

            if (dwSrc == 0 && count == 0)
            {
                hr = E_INVALIDARG;
                break;
            }

            pSrc = pSrcPtrs[dwSrc];
        }
        else
        {
            *pDest = *pSrc++;

            if (*pSrc != TEXT('\0'))
            {
                pSrc++;
            }

            if (count & 1)
            {
                *pDest = _totlower(*pDest);
            }
            else
            {
                *pDest = _totupper(*pDest);
            }

            *pDest--;
            count++;
        }
    }

    if (SUCCEEDED(hr))
    {
        szCertStr[CDKEYSIZE - 1] = TEXT('\0');
    }

    return(hr);
}


// This function will generate an Certification string using the provider information, 
//
// this function only operates in a build that has DEBUG defined, in the shipping version this
// function will return E_NOTIMPL.
//
// First, using the provider data, a data key is generated of a precise size.  This key is then 
// encrypted.  The raw encrypted data is then converted to a readable numeric text format and
// returned as the certification key for this provider
//
// This certification key will be provided to the provided who will download it upon request to
// allow us to verify that they are indeed a licensed cd data provider.
//
// This key does NOT expire, it's mainly here to prevent unlicensed data providers from hooking
// into this product.

STDMETHODIMP CCDOpt::CreateProviderKey(LPCDPROVIDER pCDProvider, TCHAR *szCertKey, UINT cBytes)
{
    HRESULT hr = E_NOTIMPL;

#ifdef DEBUG

    hr = S_OK;

    if (cBytes < 128 || pCDProvider == NULL || szCertKey == NULL)
    {
        hr = E_INVALIDARG;
    }
    else
    {
        TCHAR szKey[CDKEYSIZE * 2];

        if (SUCCEEDED(CreateCertString(pCDProvider, szKey)))
        {
            CDKEY cdKey;

            memset(&cdKey,0,sizeof(cdKey));

            hr = AcquireKey(&cdKey, pCDProvider->szProviderName);

            if (SUCCEEDED(hr))
            {
                DWORD dwSize = lstrlen(szKey); 

                if (CryptEncrypt(cdKey.hKey, 0, TRUE, 0, (BYTE *) szKey, &dwSize, CDKEYSIZE * 2))
                {
                    LPBYTE pData = (LPBYTE) szKey;
                    TCHAR *szDest = szCertKey;

                    for (DWORD dwPos = 0; dwPos < dwSize; dwPos++, szDest += 2, pData++)
                    {
                        wsprintf(szDest, TEXT("%02x"), (UINT) *pData);      
                    }

                    hr = S_OK;
                }
                else
                {
                    hr = GetLastError();
                } 

                ReleaseKey(&cdKey);
            }
        }
    }

#endif // DEBUG

    return hr;
}