/*
Module : HKLM.CPP
Purpose: Implementation for a class to provide simple access to the HKEY_LOCAL_MACHINE\Software\Company\MyApp
				 registry key similiar to the functions MFC provides to access the same settings in HKEY_CURRENT_USER
Created: PJN / 27-07-1998
History: PJN / 03-10-1999 Addition of GetProfileStringArray and WriteProfileStringArray methods
         PJN / 09-08-2001 1. Updated copyright message
                          2. Fixed an issue where the class fails to read any key that is readonly. 
                          This is a problem since Win2000 defaults access to HKLM for "normal" 
                          users to readonly. Thanks to Hans-Georg Ulrich for spotting this problem
                          3. Removed an unreferenced function for the hklm header file
                          4. Fixed a memory overwrite problem in CHKLM::SetStringArrayIntoRegistry.
                          Thanks to BoundsChecker on spotting this problem.
                          5. Fixed a problem in CHKLM::SetStringArrayIntoRegistry with the 
                          calculation of the size to be used in the call to RegSetValueEx
                          6. GetStringArrayFromRegistry now use RegQueryValueEx to get the size of 
                          the data it contains.
                          7. Sample app now uses CWinApp to make it easier for testing.
         PJN / 12-10-2001 1. Now allows application name to be overridden using new method namely, 
                          SetProductNameRegistryKey. Thanks to Rene M. Laviolette for this suggestion.
         PJN / 23-07-2004 1. Fixed a simple 64 bit INT_PTR compilation warning when compiled in
                          VC 7.1
         PJN / 29-10-2004 1. Fixed a compiler warning when Force conformance in for loop option is
                          set in VC 7.1.
                          2. Addition of a HKLM_EXT_CLASS preprocessor macro which allows the class
                          to be easily added to an extension dll
         PJN / 26-03-2005 1. Addition of a m_bProfileWriteFlush variable to the clas which dictates if
                          the registry settings should be commited immediately as oppossed to relying 
                          on the lazy writer.
         PJN / 11-06-2006 1. Fixed a bug in CHKLM::WriteProfileBinary where the optional flushing was
                          being done after the registry key was closed.
                          2. Updated copyright details.
                          3. Updated the documentation to use the same style as the web site.
         PJN / 30-07-2006 1. Code now uses newer C++ style casts instead of C style casts.
                          2. Updated the code to clean compile on VC 2005

Copyright (c) 1998 - 2006 by PJ Naughter (Web: www.naughter.com, Email: pjna@naughter.com)

All rights reserved.

Copyright / Usage Details:

You are allowed to include the source code in any product (commercial, shareware, freeware or otherwise) 
when your product is released in binary form. You are allowed to modify the source code in any way you want 
except you cannot modify the copyright details at the top of each module. If you want to distribute source 
code with your application, then you are only allowed to distribute versions released by the author. This is 
to maintain a single distribution point for the source code. 

*/



///////////////////////////////// Includes //////////////////////////////////

#include "stdafx.h"
#include "hklm.h"
 


//////////////// Macros //////////////////////////////////////////////

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif



//////////////// Implementation //////////////////////////////////////

CHKLM::CHKLM() : m_bProfileWriteFlush(TRUE) //Default to the slower but safer form of registry writing
{
}

void CHKLM::SetRegistryKey(LPCTSTR lpszRegistryKey)
{
	m_sRegistryKey = lpszRegistryKey;
}

void CHKLM::SetProductNameRegistryKey(LPCTSTR lpszProductNameRegistryKey)
{
  m_sProductNameRegistryKey = lpszProductNameRegistryKey;
}

void CHKLM::SetProductNameRegistryKey(UINT nIDProductNameRegistryKey)
{
  CString sKey;
	VERIFY(sKey.LoadString(nIDProductNameRegistryKey));
	SetProductNameRegistryKey(sKey);
}

CString CHKLM::GetProductNameRegistryKey() const
{
  return m_sProductNameRegistryKey;
}

void CHKLM::SetRegistryKey(UINT nIDRegistryKey)
{
  CString sKey;
	VERIFY(sKey.LoadString(nIDRegistryKey));
	SetRegistryKey(sKey);
}

// returns key for HKEY_LOCAL_MACHINE\"Software"\RegistryKey\ProfileName
// creating it if it doesn't exist
// responsibility of the caller to call RegCloseKey() on the returned HKEY
HKEY CHKLM::GetAppRegistryKey(BOOL bReadOnly)
{
	ASSERT(m_sRegistryKey.GetLength());

	HKEY hAppKey = NULL;
	HKEY hSoftKey = NULL;
	HKEY hCompanyKey = NULL;

  //Setup the access type
  REGSAM samDesired = KEY_WRITE | KEY_READ;
  if (bReadOnly)
    samDesired = KEY_READ;

	if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("Software"), 0, samDesired,	&hSoftKey) == ERROR_SUCCESS)
	{
		DWORD dw;
		if (RegCreateKeyEx(hSoftKey, m_sRegistryKey, 0, REG_NONE,	REG_OPTION_NON_VOLATILE, samDesired, NULL, &hCompanyKey, &dw) == ERROR_SUCCESS)
		{
      if (m_sProductNameRegistryKey.GetLength())
			  RegCreateKeyEx(hCompanyKey, m_sProductNameRegistryKey, 0, REG_NONE,	REG_OPTION_NON_VOLATILE, samDesired, NULL, &hAppKey, &dw);
      else
      {
        ASSERT(AfxGetApp());
			  RegCreateKeyEx(hCompanyKey, AfxGetApp()->m_pszAppName, 0, REG_NONE,	REG_OPTION_NON_VOLATILE, samDesired, NULL, &hAppKey, &dw);
      }
		}
	}
	if (hSoftKey != NULL)
		RegCloseKey(hSoftKey);
	if (hCompanyKey != NULL)
		RegCloseKey(hCompanyKey);

	return hAppKey;
}

// returns key for:
//      HKEY_LOCAL_MACHINE\"Software"\RegistryKey\AppName\lpszSection
// creating it if it doesn't exist.
// responsibility of the caller to call RegCloseKey() on the returned HKEY
HKEY CHKLM::GetSectionKey(LPCTSTR lpszSection, BOOL bReadOnly)
{
	ASSERT(lpszSection != NULL);

	HKEY hSectionKey = NULL;
	HKEY hAppKey = GetAppRegistryKey(bReadOnly);
	if (hAppKey == NULL)
		return NULL;

  //Setup the access type
  REGSAM samDesired = KEY_WRITE | KEY_READ;
  if (bReadOnly)
    samDesired = KEY_READ;

	DWORD dw;
	RegCreateKeyEx(hAppKey, lpszSection, 0, REG_NONE, REG_OPTION_NON_VOLATILE, samDesired, NULL, &hSectionKey, &dw);
	RegCloseKey(hAppKey);
	return hSectionKey;
}

UINT CHKLM::GetProfileInt(LPCTSTR lpszSection, LPCTSTR lpszEntry,	int nDefault)
{
	ASSERT(lpszSection != NULL);
	ASSERT(lpszEntry != NULL);

	HKEY hSecKey = GetSectionKey(lpszSection, TRUE);
	if (hSecKey == NULL)
		return nDefault;
	DWORD dwValue;
	DWORD dwType;
	DWORD dwCount = sizeof(DWORD);
	LONG lResult = RegQueryValueEx(hSecKey, lpszEntry, NULL, &dwType, reinterpret_cast<LPBYTE>(&dwValue), &dwCount);
	RegCloseKey(hSecKey);
	if (lResult == ERROR_SUCCESS)
	{
		ASSERT(dwType == REG_DWORD);
		ASSERT(dwCount == sizeof(dwValue));
		return static_cast<UINT>(dwValue);
	}
	return nDefault;
}

CString CHKLM::GetProfileString(LPCTSTR lpszSection, LPCTSTR lpszEntry,	LPCTSTR lpszDefault)
{
	ASSERT(lpszSection != NULL);
	ASSERT(lpszEntry != NULL);

	HKEY hSecKey = GetSectionKey(lpszSection, TRUE);
	if (hSecKey == NULL)
		return lpszDefault;
	CString strValue;
	DWORD dwType, dwCount;
	LONG lResult = RegQueryValueEx(hSecKey, lpszEntry, NULL, &dwType, NULL, &dwCount);
	if (lResult == ERROR_SUCCESS)
	{
		ASSERT(dwType == REG_SZ);
		lResult = RegQueryValueEx(hSecKey, lpszEntry, NULL, &dwType, reinterpret_cast<LPBYTE>(strValue.GetBuffer(dwCount/sizeof(TCHAR))), &dwCount);
		strValue.ReleaseBuffer();
	}
	RegCloseKey(hSecKey);
	if (lResult == ERROR_SUCCESS)
	{
		ASSERT(dwType == REG_SZ);
		return strValue;
	}
	return lpszDefault;
}

BOOL CHKLM::GetProfileBinary(LPCTSTR lpszSection, LPCTSTR lpszEntry, BYTE** ppData, UINT* pBytes)
{
	ASSERT(lpszSection != NULL);
	ASSERT(lpszEntry != NULL);
	ASSERT(ppData != NULL);
	ASSERT(pBytes != NULL);
	*ppData = NULL;
	*pBytes = 0;

	HKEY hSecKey = GetSectionKey(lpszSection, TRUE);
	if (hSecKey == NULL)
		return FALSE;

	DWORD dwType, dwCount;
	LONG lResult = RegQueryValueEx(hSecKey, lpszEntry, NULL, &dwType, NULL, &dwCount);
	*pBytes = dwCount;
	if (lResult == ERROR_SUCCESS)
	{
		ASSERT(dwType == REG_BINARY);
		*ppData = new BYTE[*pBytes];
		lResult = RegQueryValueEx(hSecKey, lpszEntry, NULL, &dwType, *ppData, &dwCount);
	}
	RegCloseKey(hSecKey);
	if (lResult == ERROR_SUCCESS)
	{
		ASSERT(dwType == REG_BINARY);
		return TRUE;
	}
	else
	{
		delete [] *ppData;
		*ppData = NULL;
	}
	return FALSE;
}

BOOL CHKLM::GetProfileStringArray(LPCTSTR lpszSection, LPCTSTR lpszEntry, CStringArray& array)
{
	ASSERT(lpszSection != NULL);

  //Empty out the array before we go any further
  array.RemoveAll();

	HKEY hSecKey = GetSectionKey(lpszSection, TRUE);
	if (hSecKey == NULL)
		return FALSE;
	BOOL bSuccess = GetStringArrayFromRegistry(hSecKey, lpszEntry, array);
	RegCloseKey(hSecKey);
	return bSuccess;
}

BOOL CHKLM::WriteProfileInt(LPCTSTR lpszSection, LPCTSTR lpszEntry,	int nValue)
{
	ASSERT(lpszSection != NULL);
	ASSERT(lpszEntry != NULL);

	HKEY hSecKey = GetSectionKey(lpszSection);
	if (hSecKey == NULL)
		return FALSE;
	LONG lResult = RegSetValueEx(hSecKey, lpszEntry, NULL, REG_DWORD, reinterpret_cast<const BYTE*>(&nValue), sizeof(nValue));
	if (m_bProfileWriteFlush)
		RegFlushKey(hSecKey);
	RegCloseKey(hSecKey);
	return lResult == ERROR_SUCCESS;
}

BOOL CHKLM::WriteProfileString(LPCTSTR lpszSection, LPCTSTR lpszEntry, LPCTSTR lpszValue)
{
	ASSERT(lpszSection != NULL);

	LONG lResult;
	if (lpszEntry == NULL) //delete whole section
	{
		HKEY hAppKey = GetAppRegistryKey();
		if (hAppKey == NULL)
			return FALSE;
		lResult = ::RegDeleteKey(hAppKey, lpszSection);
	  if (m_bProfileWriteFlush)
		  RegFlushKey(hAppKey);
		RegCloseKey(hAppKey);
	}
	else if (lpszValue == NULL)
	{
		HKEY hSecKey = GetSectionKey(lpszSection);
		if (hSecKey == NULL)
			return FALSE;
		// necessary to cast away const below
		lResult = ::RegDeleteValue(hSecKey, lpszEntry);
	  if (m_bProfileWriteFlush)
		  RegFlushKey(hSecKey);
		RegCloseKey(hSecKey);
	}
	else
	{
		HKEY hSecKey = GetSectionKey(lpszSection);
		if (hSecKey == NULL)
			return FALSE;
		lResult = RegSetValueEx(hSecKey, lpszEntry, NULL, REG_SZ,	reinterpret_cast<const BYTE*>(lpszValue), (lstrlen(lpszValue)+1)*sizeof(TCHAR));
	  if (m_bProfileWriteFlush)
		  RegFlushKey(hSecKey);
		RegCloseKey(hSecKey);
	}
	return lResult == ERROR_SUCCESS;
}

BOOL CHKLM::WriteProfileBinary(LPCTSTR lpszSection, LPCTSTR lpszEntry, LPBYTE pData, UINT nBytes)
{
	ASSERT(lpszSection != NULL);

	LONG lResult;
	HKEY hSecKey = GetSectionKey(lpszSection);
	if (hSecKey == NULL)
		return FALSE;
	lResult = RegSetValueEx(hSecKey, lpszEntry, NULL, REG_BINARY,	pData, nBytes);
	if (m_bProfileWriteFlush)
		RegFlushKey(hSecKey);
	RegCloseKey(hSecKey);
	return lResult == ERROR_SUCCESS;
}

BOOL CHKLM::WriteProfileStringArray(LPCTSTR lpszSection, LPCTSTR lpszEntry, const CStringArray& array)
{
	ASSERT(lpszSection != NULL);
	HKEY hSecKey = GetSectionKey(lpszSection);
	if (hSecKey == NULL)
		return FALSE;
	BOOL bSuccess = SetStringArrayIntoRegistry(hSecKey, lpszEntry, array);
	if (m_bProfileWriteFlush)
		RegFlushKey(hSecKey);
	RegCloseKey(hSecKey);
	return bSuccess;
}

BOOL CHKLM::GetStringArrayFromRegistry(HKEY hKey, LPCTSTR lpszEntry, CStringArray& array)
{
	ASSERT(hKey != NULL);
	ASSERT(lpszEntry != NULL);

  //Assume the worst
  BOOL bSuccess = FALSE;

  //Empty the array before we go any further
  array.RemoveAll();

	DWORD dwType, dwCount;
	LONG lResult = RegQueryValueEx(hKey, lpszEntry, NULL, &dwType, NULL, &dwCount);
	if (lResult == ERROR_SUCCESS)
	{
		ASSERT(dwType == REG_MULTI_SZ);
		LPBYTE lpBuffer = new BYTE[dwCount];
		lResult = RegQueryValueEx(hKey, lpszEntry, NULL, &dwType, lpBuffer, &dwCount);
    if (lResult == ERROR_SUCCESS)
    {
      LPTSTR lpszStrings = reinterpret_cast<LPTSTR>(lpBuffer);
      while (lpszStrings[0] != 0)
      {
        array.Add(lpszStrings);
        lpszStrings += (_tcslen(lpszStrings) + 1);
      }

      bSuccess = TRUE;
    }

    //Tidy up the heap memory we have used
    delete [] lpBuffer;
	}

  return bSuccess;
}

BOOL CHKLM::SetStringArrayIntoRegistry(HKEY hKey, LPCTSTR lpszEntry, const CStringArray& array)
{   
  //Validate our input parameters
  ASSERT(hKey);
  ASSERT(lpszEntry != NULL);

  //Work out the size of the buffer we will need
  DWORD dwSize = 0;
  
#if (_MFC_VER >= 0x700)
  INT_PTR nStrings = array.GetSize();
  INT_PTR i;
  for (i=0; i<nStrings; i++)
    dwSize += array.GetAt(i).GetLength() + 1; //1 extra for each NULL terminator
#else  
  int nStrings = array.GetSize();
  int i;
  for (i=0; i<nStrings; i++)
    dwSize += array.GetAt(i).GetLength() + 1; //1 extra for each NULL terminator
#endif
  //Need one second NULL for the double NULL at the end
  dwSize++;

  //Convert from BYTE size to character size
  dwSize *= sizeof(TCHAR);

  //Allocate the memory we want
  BYTE* lpBuffer = new BYTE[dwSize];

  //Now copy the strings into the buffer
  int nCurOffset = 0;
  LPTSTR lpszString = reinterpret_cast<LPTSTR>(lpBuffer);
  for (i=0; i<nStrings; i++)
  {
    CString sText = array.GetAt(i);
    int nCurrentStringLength = sText.GetLength();
  #if (_MSC_VER >= 1400)  
    _tcscpy_s(&lpszString[nCurOffset], nCurrentStringLength+1, sText);
  #else
    _tcscpy(&lpszString[nCurOffset], sText);
  #endif
    nCurOffset += nCurrentStringLength;
    nCurOffset++;
  }
  //Don't forgot to doubly NULL terminate
  lpszString[nCurOffset] = _T('\0');

  //Finally write it into the registry
	BOOL bSuccess = (::RegSetValueEx(hKey, lpszEntry, NULL, REG_MULTI_SZ, lpBuffer, dwSize) == ERROR_SUCCESS);

  //free up the memory we used
  delete [] lpBuffer;

  return bSuccess;
}
