/* * CiderPress * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. * See the file LICENSE for distribution terms. */ /* * Windows Registry operations. */ #include "stdafx.h" #include "Registry.h" #include "Main.h" #include "MyApp.h" #define kRegAppName L"CiderPress" #define kRegExeName L"CiderPress.exe" #define kCompanyName L"faddenSoft" 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; /* * Set this key + ".XXX" to (Default)=AppID. This associates the file * type with kRegAppID. */ //static const char* kFileExtensionBase = L"HKEY_CLASSES_ROOT"; /* * Description of data files. Set this key + AppID to 40-char string, e.g. * (Default)=CompanyName AppName Version DataType * * Can also set DefaultIcon = Pathname [,Index] */ //static const char* kAppIDBase = L"HKEY_CLASSES_ROOT"; /* * Put one of these under the AppID to specify the icon for a file type. */ static const WCHAR* kDefaultIcon = L"DefaultIcon"; static const WCHAR* kRegKeyCPKStr = L"CPK"; /* * 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. */ #define kAppIDNuFX L"CiderPress.NuFX" #define kAppIDDiskImage L"CiderPress.DiskImage" #define kAppIDBinaryII L"CiderPress.BinaryII" #define kNoAssociation L"(no association)" const MyRegistry::FileTypeAssoc MyRegistry::kFileTypeAssoc[] = { { L".2MG", kAppIDDiskImage }, { L".APP", kAppIDDiskImage }, { L".BNY", kAppIDBinaryII }, { L".BQY", kAppIDBinaryII }, { L".BSE", kAppIDNuFX }, { L".BXY", kAppIDNuFX }, { L".D13", kAppIDDiskImage }, { L".DDD", kAppIDDiskImage }, { L".DO", kAppIDDiskImage }, { L".DSK", kAppIDDiskImage }, { L".FDI", kAppIDDiskImage }, { L".HDV", kAppIDDiskImage }, { L".IMG", kAppIDDiskImage }, { L".NIB", kAppIDDiskImage }, { L".PO", kAppIDDiskImage }, { L".SDK", kAppIDDiskImage }, { L".SEA", kAppIDNuFX }, { L".SHK", kAppIDNuFX }, // { L".DC", kAppIDDiskImage }, // { L".DC6", kAppIDDiskImage }, // { L".GZ", kAppIDDiskImage }, // { L".NB2", kAppIDDiskImage }, // { L".RAW", kAppIDDiskImage }, // { L".ZIP", kAppIDDiskImage }, }; static const struct { const char* user; const char* reg; } gBadKeys[] = { { "Nimrod Bonehead", "CP1-68C069-62CC9444" }, { "Connie Tan", "CP1-877B2C-A428FFD6" }, }; /* * ========================================================================== * One-time install/uninstall * ========================================================================== */ /* * This is called immediately after installation finishes. * * We want to snatch up any unused file type associations. We define them * as "unused" if the entry does not exist in the registry at all. A more * thorough installer would also verify that the appID actually existed * and "steal" any apparent orphans, but we can let the user do that manually. */ void MyRegistry::OneTimeInstall(void) const { /* start by stomping on our appIDs */ WMSG0(" Removing appIDs\n"); RegDeleteKeyNT(HKEY_CLASSES_ROOT, kAppIDNuFX); RegDeleteKeyNT(HKEY_CLASSES_ROOT, kAppIDDiskImage); RegDeleteKeyNT(HKEY_CLASSES_ROOT, kAppIDBinaryII); /* configure the appIDs */ FixBasicSettings(); /* configure extensions */ int i, res; for (i = 0; i < NELEM(kFileTypeAssoc); i++) { HKEY hExtKey; res = RegOpenKeyEx(HKEY_CLASSES_ROOT, kFileTypeAssoc[i].ext, 0, KEY_READ, &hExtKey); if (res == ERROR_SUCCESS) { WMSG1(" Found existing HKCR\\'%ls', leaving alone\n", kFileTypeAssoc[i].ext); RegCloseKey(hExtKey); } else if (res == ERROR_FILE_NOT_FOUND) { OwnExtension(kFileTypeAssoc[i].ext, kFileTypeAssoc[i].appID); } else { WMSG2(" Got error %ld opening HKCR\\'%ls', leaving alone\n", res, kFileTypeAssoc[i].ext); } } } /* * Remove things that the standard uninstall script won't. * * We want to un-set any of our file associations. We don't really need to * clean up the ".xxx" entries, because removing their appID entries is enough * to fry their little brains, but it's probably the right thing to do. * * We definitely want to strip out our appIDs. */ 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 appIDs */ WMSG0(" Removing appIDs\n"); RegDeleteKeyNT(HKEY_CLASSES_ROOT, kAppIDNuFX); RegDeleteKeyNT(HKEY_CLASSES_ROOT, kAppIDDiskImage); RegDeleteKeyNT(HKEY_CLASSES_ROOT, kAppIDBinaryII); } /* * ========================================================================== * Shareware registration logic * ========================================================================== */ /* [removed] */ /* * ========================================================================== * Windows shell game * ========================================================================== */ /* * Return the application's registry key. This is used as the argument to * CWinApp::SetRegistryKey(). The GetProfile{Int,String} calls combine this * (in m_pszRegistryKey) with the app name (in m_pszProfileName) and prepend * "HKEY_CURRENT_USER\Software\". */ const WCHAR* MyRegistry::GetAppRegistryKey(void) const { return kCompanyName; } /* * See if an AppID is one we recognize. */ bool MyRegistry::IsOurAppID(const WCHAR* id) const { return (wcsicmp(id, kAppIDNuFX) == 0 || wcsicmp(id, kAppIDDiskImage) == 0 || wcsicmp(id, kAppIDBinaryII) == 0); } /* * Fix the basic registry settings, e.g. our AppID 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. */ void MyRegistry::FixBasicSettings(void) const { const WCHAR* exeName = gMyApp.GetExeFileName(); ASSERT(exeName != nil && wcslen(exeName) > 0); WMSG0("Fixing any missing file type AppID entries in registry\n"); ConfigureAppID(kAppIDNuFX, L"NuFX Archive (CiderPress)", exeName, 1); ConfigureAppID(kAppIDBinaryII, L"Binary II (CiderPress)", exeName, 2); ConfigureAppID(kAppIDDiskImage, L"Disk Image (CiderPress)", exeName, 3); } /* * Set up the registry goodies for one appID. */ void MyRegistry::ConfigureAppID(const WCHAR* appID, const WCHAR* descr, const WCHAR* exeName, int iconIdx) const { WMSG2(" Configuring '%ls' for '%ls'\n", appID, exeName); HKEY hAppKey = nil; HKEY hIconKey = nil; DWORD dw; if (RegCreateKeyEx(HKEY_CLASSES_ROOT, appID, 0, REG_NONE, REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL, &hAppKey, &dw) == ERROR_SUCCESS) { ConfigureAppIDSubFields(hAppKey, descr, exeName); if (RegCreateKeyEx(hAppKey, kDefaultIcon, 0, REG_NONE, REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL, &hIconKey, &dw) == ERROR_SUCCESS) { DWORD type, size; unsigned char buf[512]; long res; size = sizeof(buf); // size in bytes res = RegQueryValueEx(hIconKey, L"", nil, &type, buf, &size); if (res == ERROR_SUCCESS && size > 1) { WMSG1(" Icon for '%ls' already exists, not altering\n", appID); } else { CString iconStr; iconStr.Format(L"%ls,%d", exeName, iconIdx); if (RegSetValueEx(hIconKey, L"", 0, REG_SZ, (const BYTE*)(LPCTSTR) iconStr, wcslen(iconStr) * sizeof(WCHAR)) == ERROR_SUCCESS) { WMSG2(" Set icon for '%ls' to '%ls'\n", appID, (LPCWSTR) iconStr); } else { WMSG2(" WARNING: unable to set DefaultIcon for '%ls' to '%ls'\n", appID, (LPCWSTR) iconStr); } } } else { WMSG1("WARNING: couldn't set up DefaultIcon for '%ls'\n", appID); } } else { WMSG1("WARNING: couldn't create AppID='%ls'\n", appID); } RegCloseKey(hIconKey); RegCloseKey(hAppKey); } /* * Set up the current key's default (which is used as the explorer * description) and put the "Open" command in "...\shell\open\command". */ void MyRegistry::ConfigureAppIDSubFields(HKEY hAppKey, const WCHAR* descr, const WCHAR* exeName) const { HKEY hShellKey, hOpenKey, hCommandKey; DWORD dw; ASSERT(hAppKey != nil); ASSERT(descr != nil); ASSERT(exeName != nil); hShellKey = hOpenKey = hCommandKey = nil; if (RegSetValueEx(hAppKey, L"", 0, REG_SZ, (const BYTE*) descr, wcslen(descr) * sizeof(WCHAR)) != ERROR_SUCCESS) { WMSG1(" WARNING: unable to set description to '%ls'\n", descr); } 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"", nil, &type, (LPBYTE) buf, &size); if (res == ERROR_SUCCESS && size > 1) { WMSG1(" Command already exists, not altering ('%ls')\n", 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) { WMSG1(" Set command to '%ls'\n", openCmd); } else { WMSG1(" WARNING: unable to set open cmd '%ls'\n", openCmd); } } } } } RegCloseKey(hCommandKey); RegCloseKey(hOpenKey); RegCloseKey(hShellKey); } /* * Return the number of file type associations. */ int MyRegistry::GetNumFileAssocs(void) const { return NELEM(kFileTypeAssoc); } #if 0 /* * Return information on a file association. * * Check to see if we're the application that will be launched. * * Problem: the file must *actually exist* for this to work. */ void MyRegistry::GetFileAssoc(int idx, CString* pExt, CString* pHandler, bool* pOurs) const { char buf[MAX_PATH]; *pExt = kFileTypeAssoc[idx].ext; *pHandler = ""; *pOurs = false; HINSTANCE res = FindExecutable(*pExt, "\\", buf); if ((long) res > 32) { WMSG1("Executable is '%s'\n", buf); *pHandler = buf; } else { WMSG1("FindExecutable failed (err=%d)\n", res); *pHandler = kNoAssociation; } } #endif /* * Return information on a file association. * * We check to see if the file extension is associated with one of our * application ID strings. We don't bother to check whether the appID * strings 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. */ void MyRegistry::GetFileAssoc(int idx, CString* pExt, CString* pHandler, bool* pOurs) const { ASSERT(idx >= 0 && idx < NELEM(kFileTypeAssoc)); long res; *pExt = kFileTypeAssoc[idx].ext; *pHandler = ""; CString appID; HKEY hExtKey = nil; res = RegOpenKeyEx(HKEY_CLASSES_ROOT, *pExt, 0, KEY_READ, &hExtKey); if (res == ERROR_SUCCESS) { WCHAR buf[260]; DWORD type; DWORD size = sizeof(buf); // size in bytes res = RegQueryValueEx(hExtKey, L"", nil, &type, (LPBYTE)buf, &size); if (res == ERROR_SUCCESS) { WMSG1(" Got '%ls'\n", buf); appID = buf; if (GetAssocAppName(appID, pHandler) != 0) *pHandler = appID; } else { WMSG1("RegQueryValueEx failed on '%ls'\n", (LPCWSTR) *pExt); } } else { WMSG1(" RegOpenKeyEx failed on '%ls'\n", *pExt); } *pOurs = false; if (pHandler->IsEmpty()) { *pHandler = kNoAssociation; } else { *pOurs = IsOurAppID(appID); } RegCloseKey(hExtKey); } /* * Given an application ID, determine the application's name. * * This requires burrowing down into HKEY_CLASSES_ROOT\\shell\open\. */ int MyRegistry::GetAssocAppName(const CString& appID, CString* pCmd) const { CString keyName; WCHAR buf[260]; HKEY hAppKey = nil; long res; int result = -1; keyName = appID + L"\\shell\\open\\command"; res = RegOpenKeyEx(HKEY_CLASSES_ROOT, keyName, 0, KEY_READ, &hAppKey); if (res == ERROR_SUCCESS) { DWORD type; DWORD size = sizeof(buf); // size in bytes res = RegQueryValueEx(hAppKey, L"", nil, &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 { WMSG1("Unable to open shell\\open\\command for '%ls'\n", appID); } } else { CString errBuf; GetWin32ErrorString(res, &errBuf); WMSG2("Unable to open AppID key '%ls' (%ls)\n", keyName, (LPCWSTR) errBuf); } RegCloseKey(hAppKey); return result; } /* * Reduce a compound string to just its first token. */ 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); } /* * 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. */ int MyRegistry::SetFileAssoc(int idx, bool wantIt) const { const WCHAR* ext; bool weOwnIt; int result = 0; ASSERT(idx >= 0 && idx < NELEM(kFileTypeAssoc)); ext = kFileTypeAssoc[idx].ext; weOwnIt = GetAssocState(ext); WMSG3("SetFileAssoc: ext='%ls' own=%d want=%d\n", ext, weOwnIt, wantIt); if (weOwnIt && !wantIt) { /* reset it */ WMSG1(" SetFileAssoc: clearing '%ls'\n", ext); result = DisownExtension(ext); } else if (!weOwnIt && wantIt) { /* take it */ WMSG1(" SetFileAssoc: taking '%ls'\n", ext); result = OwnExtension(ext, kFileTypeAssoc[idx].appID); } else { WMSG1(" SetFileAssoc: do nothing with '%ls'\n", ext); /* do nothing */ } return 0; } /* * Determine whether or not the filetype described by "ext" is one that we * currently manage. * * Returns "true" if so, "false" if not. Returns "false" on any errors * encountered. */ bool MyRegistry::GetAssocState(const WCHAR* ext) const { WCHAR buf[260]; HKEY hExtKey = nil; int res; bool result = false; res = RegOpenKeyEx(HKEY_CLASSES_ROOT, ext, 0, KEY_READ, &hExtKey); if (res == ERROR_SUCCESS) { DWORD type; DWORD size = sizeof(buf); // size in bytes res = RegQueryValueEx(hExtKey, L"", nil, &type, (LPBYTE) buf, &size); if (res == ERROR_SUCCESS && type == REG_SZ) { /* compare it to known appID values */ WMSG2(" Found '%ls', testing '%ls'\n", ext, buf); if (IsOurAppID((WCHAR*)buf)) result = true; } } RegCloseKey(hExtKey); return result; } /* * Drop ownership of a file extension. * * We assume we own it. * * Returns 0 on success, -1 on error. */ int MyRegistry::DisownExtension(const WCHAR* ext) const { ASSERT(ext != nil); ASSERT(ext[0] == '.'); if (ext == nil || wcslen(ext) < 2) return -1; if (RegDeleteKeyNT(HKEY_CLASSES_ROOT, ext) == ERROR_SUCCESS) { WMSG1(" HKCR\\%ls subtree deleted\n", ext); } else { WMSG1(" Failed deleting HKCR\\'%ls'\n", ext); return -1; } return 0; } /* * Take ownership of a file extension. * * Returns 0 on success, -1 on error. */ int MyRegistry::OwnExtension(const WCHAR* ext, const WCHAR* appID) const { ASSERT(ext != nil); ASSERT(ext[0] == '.'); if (ext == nil || wcslen(ext) < 2) return -1; HKEY hExtKey = nil; DWORD dw; int res, result = -1; /* delete the old key (which might be a hierarchy) */ res = RegDeleteKeyNT(HKEY_CLASSES_ROOT, ext); if (res == ERROR_SUCCESS) { WMSG1(" HKCR\\%ls subtree deleted\n", ext); } else if (res == ERROR_FILE_NOT_FOUND) { WMSG1(" No HKCR\\%ls subtree to delete\n", ext); } else { WMSG1(" Failed deleting HKCR\\'%ls'\n", ext); goto bail; } /* set the new key */ if (RegCreateKeyEx(HKEY_CLASSES_ROOT, ext, 0, REG_NONE, REG_OPTION_NON_VOLATILE, KEY_WRITE|KEY_READ, NULL, &hExtKey, &dw) == ERROR_SUCCESS) { res = RegSetValueEx(hExtKey, L"", 0, REG_SZ, (LPBYTE) appID, wcslen(appID) * sizeof(WCHAR)); if (res == ERROR_SUCCESS) { WMSG2(" Set '%ls' to '%ls'\n", ext, appID); result = 0; } else { WMSG3("Failed setting '%ls' to '%ls' (res=%d)\n", ext, appID, res); goto bail; } } bail: RegCloseKey(hExtKey); return result; } // (This comes from the MSDN sample sources.) // // 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; // 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; }