/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * Windows Registry operations.
 */
#include "stdafx.h"
#ifdef CAN_UPDATE_FILE_ASSOC
#include "Registry.h"
#include "Main.h"
#include "MyApp.h"

#define kCompanyName    L"faddenSoft"

#if 0
#define kRegAppName     L"CiderPress"
#define kRegExeName     L"CiderPress.exe"

static const WCHAR kRegKeyCPKVersions[] = L"vrs";
static const WCHAR kRegKeyCPKExpire[] = L"epr";

/*
 * Application path.  Add two keys:
 *
 *  (default) = FullPathName
 *      Full pathname of the executable file.
 *  Path = Path
 *      The $PATH that will be in effect when the program starts (but only if
 *      launched from the Windows explorer).
 */
static const WCHAR kAppKeyBase[] =
    L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" kRegExeName;

/*
 * Local settings.  App stuff goes in the per-user key, registration info is
 * in the per-machine key.
 */
static const WCHAR kMachineSettingsBaseKey[] =
    L"HKEY_LOCAL_MACHINE\\SOFTWARE\\" kCompanyName L"\\" kRegAppName;
static const WCHAR kUserSettingsBaseKey[] =
    L"HKEY_CURRENT_USER\\Software\\" kCompanyName L"\\" kRegAppName;

static const WCHAR kRegKeyCPKStr[] = L"CPK";
#endif

/*
 * ProgID fields.
 *
 * See http://msdn.microsoft.com/en-us/library/windows/desktop/cc144152%28v=vs.85%29.aspx
 */

static const WCHAR kDefaultIcon[] = L"DefaultIcon";
static const WCHAR kFriendlyTypeName[] = L"FriendlyTypeName";
static const WCHAR kInfoTip[] = L"InfoTip";

static const WCHAR kShellOpenCommand[] = L"\\shell\\open\\command";


/*
 * ProgID key names.
 *
 * We used to open HKEY_CLASSES_ROOT, which provides a "merged" view of
 * HKEY_LOCAL_MACHINE\Software\Classes and HKEY_CURRENT_USER\Software\Classes.
 * The HKLM entries provided defaults for all users on the machine, while the
 * HKCU entries were specific to the current user.
 *
 * It appears that Windows no longer likes it when executables other than the
 * app installer (which can run privileged) mess with HKLM, so we just work
 * with HKCU directly now.
 */
static const WCHAR kProgIdKeyNuFX[] =       L"CiderPress.NuFX.4";
static const WCHAR kProgIdKeyDiskImage[] =  L"CiderPress.DiskImage.4";
static const WCHAR kProgIdKeyBinaryII[] =   L"CiderPress.BinaryII.4";

/* file associations go here under HKCU */
static const WCHAR kFileAssocBase[] = L"Software\\Classes";

/*
 * Table of file type associations.  They will appear in the UI in the same
 * order that they appear here, so try to maintain alphabetical order.
 */
const MyRegistry::FileTypeAssoc MyRegistry::kFileTypeAssoc[] = {
    { L".2MG",  kProgIdKeyDiskImage },
    { L".APP",  kProgIdKeyDiskImage },
    { L".BNY",  kProgIdKeyBinaryII },
    { L".BQY",  kProgIdKeyBinaryII },
    { L".BSE",  kProgIdKeyNuFX },
    { L".BXY",  kProgIdKeyNuFX },
    { L".D13",  kProgIdKeyDiskImage },
    { L".DDD",  kProgIdKeyDiskImage },
    { L".DO",   kProgIdKeyDiskImage },
    { L".DSK",  kProgIdKeyDiskImage },
    { L".FDI",  kProgIdKeyDiskImage },
    { L".HDV",  kProgIdKeyDiskImage },
    { L".IMG",  kProgIdKeyDiskImage },
    { L".NIB",  kProgIdKeyDiskImage },
    { L".PO",   kProgIdKeyDiskImage },
    { L".SDK",  kProgIdKeyDiskImage },
    { L".SEA",  kProgIdKeyNuFX },
    { L".SHK",  kProgIdKeyNuFX },
//  { L".DC",   kProgIdKeyDiskImage },
//  { L".DC6",  kProgIdKeyDiskImage },
//  { L".GZ",   kProgIdKeyDiskImage },
//  { L".NB2",  kProgIdKeyDiskImage },
//  { L".RAW",  kProgIdKeyDiskImage },
//  { L".ZIP",  kProgIdKeyDiskImage },
};

#if 0
static const struct {
    const char* user;
    const char* reg;
} gBadKeys[] = {
    { "Nimrod Bonehead", "CP1-68C069-62CC9444" },
    { "Connie Tan", "CP1-877B2C-A428FFD6" },
};
#endif


/*
 * ==========================================================================
 *      One-time install/uninstall
 * ==========================================================================
 */

void MyRegistry::OneTimeInstall(void) const
{
    /* start by stomping on our ProgIDs */
    LOGI(" Removing ProgIDs");
    RegDeleteKeyHKCU(kProgIdKeyNuFX);
    RegDeleteKeyHKCU(kProgIdKeyDiskImage);
    RegDeleteKeyHKCU(kProgIdKeyBinaryII);

    /* configure the ProgIDs */
    FixBasicSettings();

    HKEY hClassesKey = NULL;
    if (OpenHKCUSoftwareClasses(&hClassesKey) != ERROR_SUCCESS) {
        return;
    }

    /* configure extensions */
    for (int i = 0; i < NELEM(kFileTypeAssoc); i++) {
        HKEY hExtKey;
        LSTATUS res = RegOpenKeyEx(hClassesKey, kFileTypeAssoc[i].ext, 0,
                KEY_READ, &hExtKey);
        if (res == ERROR_SUCCESS) {
            LOGI(" Found existing HKCU\\%ls\\'%ls', leaving alone",
                kFileAssocBase, kFileTypeAssoc[i].ext);
            RegCloseKey(hExtKey);
        } else if (res == ERROR_FILE_NOT_FOUND) {
            OwnExtension(kFileTypeAssoc[i].ext, kFileTypeAssoc[i].progId);
        } else {
            LOGW(" Got error %lu opening HKCU\\%ls\\'%ls', leaving alone",
                res, kFileAssocBase, kFileTypeAssoc[i].ext);
        }
    }

    RegCloseKey(hClassesKey);
}

void MyRegistry::OneTimeUninstall(void) const
{
    /* drop any associations we hold */
    int i;
    for (i = 0; i < NELEM(kFileTypeAssoc); i++) {
        CString ext, handler;
        bool ours;

        GetFileAssoc(i, &ext, &handler, &ours);
        if (ours) {
            DisownExtension(ext);
        }
    }

    /* remove our ProgIDs */
    LOGI(" Removing ProgIDs");
    RegDeleteKeyHKCU(kProgIdKeyNuFX);
    RegDeleteKeyHKCU(kProgIdKeyDiskImage);
    RegDeleteKeyHKCU(kProgIdKeyBinaryII);
}


/*
 * ==========================================================================
 *      Shareware registration logic
 * ==========================================================================
 */

/* [removed] */

/*
 * ==========================================================================
 *      Windows shell game
 * ==========================================================================
 */

const WCHAR* MyRegistry::GetAppRegistryKey(void) const
{
    return kCompanyName;
}

bool MyRegistry::IsOurProgId(const WCHAR* progIdKeyName) const
{
    return (wcsicmp(progIdKeyName, kProgIdKeyNuFX) == 0 ||
            wcsicmp(progIdKeyName, kProgIdKeyDiskImage) == 0 ||
            wcsicmp(progIdKeyName, kProgIdKeyBinaryII) == 0);
}

void MyRegistry::FixBasicSettings(void) const
{
    /*
     * Fix the basic registry settings, e.g. our ProgID classes.
     *
     * We don't overwrite values that already exist.  We want to hold on to the
     * installer's settings, which should get whacked if the program is
     * uninstalled or reinstalled.  This is here for "installer-less" environments
     * and to cope with registry damage.
     */

    const WCHAR* exeName = gMyApp.GetExeFileName();
    ASSERT(exeName != NULL && wcslen(exeName) > 0);

    LOGI("Fixing any missing file type ProgID entries in registry");

    ConfigureProgId(kProgIdKeyNuFX, L"NuFX Archive (CiderPress)", exeName, 1);
    ConfigureProgId(kProgIdKeyBinaryII, L"Binary II (CiderPress)", exeName, 2);
    ConfigureProgId(kProgIdKeyDiskImage, L"Disk Image (CiderPress)", exeName, 3);
}

void MyRegistry::ConfigureProgId(const WCHAR* progIdKeyName, const WCHAR* descr,
    const WCHAR* exeName, int iconIdx) const
{
    LOGI(" ConfigureProgId '%ls' for '%ls'", progIdKeyName, exeName);

    HKEY hClassesKey = NULL;
    HKEY hAppKey = NULL;

    if (OpenHKCUSoftwareClasses(&hClassesKey) != ERROR_SUCCESS) {
        LOGW(" ConfigureProgId failed to open HKCU");
        return;
    }

    DWORD disp;
    LONG result;
    if ((result = RegCreateKeyEx(hClassesKey, progIdKeyName, 0, REG_NONE,
        REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL,
        &hAppKey, &disp)) == ERROR_SUCCESS)
    {
        if (disp == REG_CREATED_NEW_KEY) {
            LOGD(" Created new key for %ls", progIdKeyName);
        } else if (disp == REG_OPENED_EXISTING_KEY) {
            LOGD(" Opened existing key for %ls", progIdKeyName);
        } else {
            LOGD(" Odd RegCreateKeyEx result 0x%lx", disp);
        }

        ConfigureProgIdCommand(hAppKey, descr, exeName);

        // Configure default entry and "friendly" type name.  The friendly
        // name takes precedence (as tested on Win7), but the default entry
        // is set for backward compatibility.
        if (RegSetValueEx(hAppKey, L"", 0, REG_SZ, (const BYTE*) descr,
            (wcslen(descr)+1) * sizeof(WCHAR)) != ERROR_SUCCESS)
        {
            LOGW("  WARNING: unable to set ProgID default to '%ls'", descr);
        }
        if (RegSetValueEx(hAppKey, kFriendlyTypeName, 0, REG_SZ,
            (const BYTE*) descr,
            (wcslen(descr)+1) * sizeof(WCHAR)) != ERROR_SUCCESS)
        {
            LOGW("  WARNING: unable to set ProgID friendly name to '%ls'", descr);
        }
        //if (RegSetValueEx(hAppKey, kInfoTip, 0, REG_SZ,
        //    (const BYTE*) infoTip,
        //    (wcslen(infoTip)+1) * sizeof(WCHAR)) != ERROR_SUCCESS)
        //{
        //    LOGW("  WARNING: unable to set ProgID info tip to '%ls'", infoTip);
        //}

        // Configure the default icon field.  This is a DefaultIcon entry
        // with a (Default) name.
        HKEY hIconKey = NULL;
        if (RegCreateKeyEx(hAppKey, kDefaultIcon, 0, REG_NONE,
            REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL,
            &hIconKey, NULL) == ERROR_SUCCESS)
        {
            DWORD type, size;
            unsigned char buf[512];
            long res;

            size = sizeof(buf);     // size in bytes
            res = RegQueryValueEx(hIconKey, L"", NULL, &type, buf, &size);
            if (res == ERROR_SUCCESS && size > 1) {
                LOGI("  Icon for '%ls' already exists, not altering",
                    progIdKeyName);
            } else {
                CString iconStr;
                iconStr.Format(L"%ls,%d", exeName, iconIdx);

                if (RegSetValueEx(hIconKey, L"", 0, REG_SZ,
                    (const BYTE*)(LPCWSTR) iconStr,
                    wcslen(iconStr) * sizeof(WCHAR)) == ERROR_SUCCESS)
                {
                    LOGI("  Set icon for '%ls' to '%ls'", progIdKeyName,
                        (LPCWSTR) iconStr);
                } else {
                    LOGW("  WARNING: unable to set DefaultIcon  for '%ls' to '%ls'",
                        progIdKeyName, (LPCWSTR) iconStr);
                }
            }

            RegCloseKey(hIconKey);
        } else {
            LOGW(" WARNING: couldn't set up DefaultIcon for '%ls'", progIdKeyName);
        }
    } else {
        LOGW(" WARNING: couldn't create ProgId='%ls' (err=%ld)",
            progIdKeyName, result);
    }

    RegCloseKey(hAppKey);
    RegCloseKey(hClassesKey);
}

void MyRegistry::ConfigureProgIdCommand(HKEY hAppKey, const WCHAR* descr,
    const WCHAR* exeName) const
{
    HKEY hShellKey, hOpenKey, hCommandKey;
    DWORD dw;

    ASSERT(hAppKey != NULL);
    ASSERT(descr != NULL);
    ASSERT(exeName != NULL);
    hShellKey = hOpenKey = hCommandKey = NULL;
    
    // TODO: I believe we can do this with a single call -- it should create
    //  the intermediate path elements for us.
    if (RegCreateKeyEx(hAppKey, L"shell", 0, REG_NONE,
        REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL,
        &hShellKey, &dw) == ERROR_SUCCESS)
    {
        if (RegCreateKeyEx(hShellKey, L"open", 0, REG_NONE,
            REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL,
            &hOpenKey, &dw) == ERROR_SUCCESS)
        {
            if (RegCreateKeyEx(hOpenKey, L"command", 0, REG_NONE,
                REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL,
                &hCommandKey, &dw) == ERROR_SUCCESS)
            {
                DWORD type, size;
                WCHAR buf[MAX_PATH+8];
                long res;

                size = sizeof(buf);     // size in bytes
                res = RegQueryValueEx(hCommandKey, L"", NULL, &type, (LPBYTE) buf,
                    &size);
                if (res == ERROR_SUCCESS && size > 1) {
                    LOGI("  Command already exists, not altering ('%ls')", buf);
                } else {
                    CString openCmd;

                    openCmd.Format(L"\"%ls\" \"%%1\"", exeName);
                    if (RegSetValueEx(hCommandKey, L"", 0, REG_SZ,
                        (LPBYTE)(LPCWSTR) openCmd,
                        wcslen(openCmd) * sizeof(WCHAR)) == ERROR_SUCCESS)
                    {
                        LOGI("  Set command to '%ls'", (LPCWSTR) openCmd);
                    } else {
                        LOGW("  WARNING: unable to set open cmd '%ls'",
                            (LPCWSTR) openCmd);
                    }
                }
                RegCloseKey(hCommandKey);
            }
            RegCloseKey(hOpenKey);
        }
        RegCloseKey(hShellKey);
    }
}


int MyRegistry::GetNumFileAssocs(void) const
{
    return NELEM(kFileTypeAssoc);
}

void MyRegistry::GetFileAssoc(int idx, CString* pExt, CString* pHandler,
    bool* pOurs) const
{
    /*
     * We check to see if the file extension is associated with one of our
     * ProgID keys.  We don't bother to check whether the ProgID keys
     * are still associated with CiderPress, since nobody should be
     * messing with those.
     *
     * BUG: we should be checking to see what the shell actually does to
     * take into account the overrides that users can set.
     */

    ASSERT(idx >= 0 && idx < NELEM(kFileTypeAssoc));

    *pExt = kFileTypeAssoc[idx].ext;
    *pHandler = L"";
    *pOurs = false;

    HKEY hClassesKey = NULL;
    if (OpenHKCUSoftwareClasses(&hClassesKey) != ERROR_SUCCESS) {
        LOGW("GetFileAssoc failed to open HKCU");
        return;
    }

    CString progIdKeyName;
    HKEY hExtKey = NULL;

    long res = RegOpenKeyEx(hClassesKey, *pExt, 0, KEY_READ, &hExtKey);
    if (res == ERROR_SUCCESS) {
        WCHAR buf[MAX_PATH];        // somewhat arbitrary
        DWORD size = sizeof(buf);   // size in bytes
        DWORD type;

        res = RegQueryValueEx(hExtKey, L"", NULL, &type, (LPBYTE)buf, &size);
        if (res == ERROR_SUCCESS) {
            LOGD("  GetFileAssoc %d got '%ls'", idx, buf);
            progIdKeyName = buf;

            if (GetAssocAppName(progIdKeyName, pHandler) != 0)
                *pHandler = progIdKeyName;
        } else {
            LOGW("RegQueryValueEx failed on '%ls'", (LPCWSTR) *pExt);
        }
    } else {
        LOGW("  RegOpenKeyEx failed on '%ls'", (LPCWSTR) *pExt);
    }

    if (!pHandler->IsEmpty()) {
        *pOurs = IsOurProgId(progIdKeyName);
    }

    RegCloseKey(hExtKey);
    RegCloseKey(hClassesKey);
}

int MyRegistry::GetAssocAppName(const CString& progIdKeyName, CString* pCmd) const
{
    HKEY hAppKey = NULL;
    int result = -1;

    HKEY hClassesKey = NULL;
    if (OpenHKCUSoftwareClasses(&hClassesKey) != ERROR_SUCCESS) {
        LOGW("GetAssocAppName failed to open HKCU");
        return result;
    }

    CString keyName = progIdKeyName + kShellOpenCommand;

    long res = RegOpenKeyEx(hClassesKey, keyName, 0, KEY_READ, &hAppKey);
    if (res == ERROR_SUCCESS) {
        WCHAR buf[260];
        DWORD type;
        DWORD size = sizeof(buf);       // size in bytes

        res = RegQueryValueEx(hAppKey, L"", NULL, &type, (LPBYTE) buf, &size);
        if (res == ERROR_SUCCESS) {
            CString cmd(buf);
            int pos;

            /* cut it down to just the EXE name */
            ReduceToToken(&cmd);

            pos = cmd.ReverseFind('\\');
            if (pos != -1 && pos != cmd.GetLength()-1) {
                cmd = cmd.Right(cmd.GetLength() - pos -1);
            }

            *pCmd = cmd;
            result = 0;
        } else {
            LOGW("Unable to open %ls for '%ls'", (LPCWSTR) kShellOpenCommand,
                (LPCWSTR) progIdKeyName);
        }
    } else {
        CString errBuf;
        GetWin32ErrorString(res, &errBuf);

        LOGW("Unable to open ProgId key '%ls' (%ls)",
            (LPCWSTR) keyName, (LPCWSTR) errBuf);
    }

    RegCloseKey(hAppKey);
    RegCloseKey(hClassesKey);
    return result;
}

void MyRegistry::ReduceToToken(CString* pStr) const
{
    WCHAR* argv[1];
    int argc = 1;
    WCHAR* mangle = wcsdup(*pStr);

    VectorizeString(mangle, argv, &argc);

    if (argc == 1)
        *pStr = argv[0];

    free(mangle);
}

int MyRegistry::SetFileAssoc(int idx, bool wantIt) const
{
    /*
     * Set the state of a file association.  There are four possibilities:
     *
     *  - We own it, we want to keep owning it: do nothing.
     *  - We don't own it, we want to keep not owning it: do nothing.
     *  - We own it, we don't want it anymore: remove ".xxx" entry.
     *  - We don't own it, we want to own it: remove ".xxx" entry and replace it.
     *
     * Returns 0 on success, nonzero on failure.
     */

    const WCHAR* ext;
    bool weOwnIt;
    int result = 0;

    ASSERT(idx >= 0 && idx < NELEM(kFileTypeAssoc));

    ext = kFileTypeAssoc[idx].ext;
    weOwnIt = GetAssocState(ext);
    LOGI("SetFileAssoc: ext='%ls' own=%d want=%d", ext, weOwnIt, wantIt);

    if (weOwnIt && !wantIt) {
        /* reset it */
        LOGI(" SetFileAssoc: clearing '%ls'", ext);
        result = DisownExtension(ext);
    } else if (!weOwnIt && wantIt) {
        /* take it */
        LOGI(" SetFileAssoc: taking '%ls'", ext);
        result = OwnExtension(ext, kFileTypeAssoc[idx].progId);
    } else {
        LOGI(" SetFileAssoc: do nothing with '%ls'", ext);
        /* do nothing */
    }

    return 0;
}

bool MyRegistry::GetAssocState(const WCHAR* ext) const
{
    WCHAR buf[260];
    HKEY hExtKey = NULL;
    int res;
    bool result = false;

    HKEY hClassesKey = NULL;
    if (OpenHKCUSoftwareClasses(&hClassesKey) != ERROR_SUCCESS) {
        LOGW("GetAssocState failed to open HKCU");
        return result;
    }

    res = RegOpenKeyEx(hClassesKey, ext, 0, KEY_READ, &hExtKey);
    if (res == ERROR_SUCCESS) {
        DWORD type;
        DWORD size = sizeof(buf);       // size in bytes
        res = RegQueryValueEx(hExtKey, L"", NULL, &type, (LPBYTE) buf, &size);
        if (res == ERROR_SUCCESS && type == REG_SZ) {
            /* compare it to known ProgID values */
            LOGD("  Found '%ls', testing '%ls'", ext, buf);
            if (IsOurProgId((WCHAR*)buf))
                result = true;
        }
        RegCloseKey(hExtKey);
    }
    RegCloseKey(hClassesKey);

    return result;
}

int MyRegistry::DisownExtension(const WCHAR* ext) const
{
    ASSERT(ext != NULL);
    ASSERT(ext[0] == '.');
    if (ext == NULL || wcslen(ext) < 2)
        return -1;

    if (RegDeleteKeyHKCU(ext) == ERROR_SUCCESS) {
        LOGI("    HKCU\\%ls\\%ls subtree deleted", kFileAssocBase, ext);
    } else {
        LOGW("    Failed deleting HKCU\\%ls\\'%ls'", kFileAssocBase, ext);
        return -1;
    }

    return 0;
}

int MyRegistry::OwnExtension(const WCHAR* ext, const WCHAR* progIdKeyName) const
{
    ASSERT(ext != NULL);
    ASSERT(ext[0] == '.');
    if (ext == NULL || wcslen(ext) < 2)
        return -1;

    HKEY hClassesKey = NULL;
    HKEY hExtKey = NULL;
    int result = -1;

    if (OpenHKCUSoftwareClasses(&hClassesKey) != ERROR_SUCCESS) {
        goto bail;
    }

    // delete the old key (which might be a hierarchy)
    DWORD res = RegDeleteKeyNT(hClassesKey, ext);
    if (res == ERROR_SUCCESS) {
        LOGI("    HKCU\\%ls\\%ls subtree deleted", kFileAssocBase, ext);
    } else if (res == ERROR_FILE_NOT_FOUND) {
        LOGI("    No HKCU\\%ls\\%ls subtree to delete", kFileAssocBase, ext);
    } else {
        LOGW("    Failed deleting HKCU\\%ls\\'%ls'", kFileAssocBase, ext);
        goto bail;
    }

    // set the new key
    DWORD dw;
    if (RegCreateKeyEx(hClassesKey, ext, 0, REG_NONE,
        REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL,
        &hExtKey, &dw) == ERROR_SUCCESS)
    {
        // default entry gets the ProgID key name
        res = RegSetValueEx(hExtKey, L"", 0, REG_SZ, (LPBYTE) progIdKeyName,
            (wcslen(progIdKeyName)+1) * sizeof(WCHAR));
        if (res == ERROR_SUCCESS) {
            LOGI("    Set '%ls' to '%ls'", ext, progIdKeyName);
            result = 0;
        } else {
            LOGW("Failed setting '%ls' to '%ls' (res=%d)",
                ext, progIdKeyName, res);
            goto bail;
        }
    }

bail:
    RegCloseKey(hExtKey);
    RegCloseKey(hClassesKey);
    return result;
}

DWORD MyRegistry::OpenHKCUSoftwareClasses(HKEY* phKey) const
{
    DWORD result = RegOpenKeyEx(HKEY_CURRENT_USER, kFileAssocBase, 0,
        KEY_READ, phKey);
    if (result != ERROR_SUCCESS) {
        LOGW("Unable to open HKEY_CURRENT_USER \\ '%ls' for reading",
            (LPCWSTR) kFileAssocBase);
    }

    return result;
}

DWORD MyRegistry::RegDeleteKeyHKCU(const WCHAR* partialKeyName) const
{
    HKEY hClassesKey;
    DWORD result;

    result = OpenHKCUSoftwareClasses(&hClassesKey);
    if (result != ERROR_SUCCESS) { return result;  }

    result = RegDeleteKeyNT(hClassesKey, partialKeyName);
    RegCloseKey(hClassesKey);
    if (result != ERROR_SUCCESS) {
        LOGW("RegDeleteKeyNT failed (err=%lu)", result);
    }
    return result;
}

// (This comes from the MSDN sample sources.)
//
// Recursively delete a key and any sub-keys.
//
// The sample code makes no attempt to check or recover from partial
// deletions.
//
// A registry key that is opened by an application can be deleted
// without error by another application in both Windows 95 and
// Windows NT. This is by design.
//
#define MAX_KEY_LENGTH  256     // not in any header I can find ++ATM
DWORD MyRegistry::RegDeleteKeyNT(HKEY hStartKey, LPCTSTR pKeyName) const
{
    DWORD   dwRtn, dwSubKeyLength;
    LPTSTR  pSubKey = NULL;
    TCHAR   szSubKey[MAX_KEY_LENGTH]; // (256) this should be dynamic.
    HKEY    hKey;
    
    LOGD("RegDeleteKeyNT %p '%ls'", hStartKey, (LPCWSTR) pKeyName);

    // Do not allow NULL or empty key name
    if ( pKeyName &&  lstrlen(pKeyName))
    {
        if( (dwRtn=RegOpenKeyEx(hStartKey,pKeyName,
            0, KEY_ENUMERATE_SUB_KEYS | DELETE, &hKey )) == ERROR_SUCCESS)
        {
            while (dwRtn == ERROR_SUCCESS )
            {
                dwSubKeyLength = MAX_KEY_LENGTH;
                dwRtn=RegEnumKeyEx(
                    hKey,
                    0,       // always index zero, because we're deleting it
                    szSubKey,
                    &dwSubKeyLength,
                    NULL,
                    NULL,
                    NULL,
                    NULL
                    );
                
                if(dwRtn == ERROR_NO_MORE_ITEMS)
                {
                    dwRtn = RegDeleteKey(hStartKey, pKeyName);
                    break;
                }
                else if(dwRtn == ERROR_SUCCESS)
                    dwRtn=RegDeleteKeyNT(hKey, szSubKey);
            }
            RegCloseKey(hKey);
            // Do not save return code because error
            // has already occurred
        }
    }
    else
        dwRtn = ERROR_BADKEY;
    
    return dwRtn;
}
#endif
