Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2715 lines
68 KiB

/*******************************************************************************
*
* (C) COPYRIGHT MICROSOFT CORP., 1993-1994
*
* TITLE: REGPORTE.C
*
* VERSION: 5.00
*
* AUTHOR: Tracy Sharpe
*
* DATE: 06 Apr 1994
*
* Histry: Zeyong Xu modified it in March 1999
*
* .REG format file import and export engine routines for the Registry Editor.
*
*******************************************************************************/
#include "stdafx.h"
#include "reg.h"
#include "reg1632.h"
#include "regdef.h"
#include "regdebug.h"
#include "regporte.h"
#include "malloc.h"
// Association between the ASCII name and the handle of the registry key.
const REGISTRY_ROOT g_RegistryRoots[] = {
TEXT("HKEY_CLASSES_ROOT"), HKEY_CLASSES_ROOT,
TEXT("HKEY_CURRENT_USER"), HKEY_CURRENT_USER,
TEXT("HKEY_LOCAL_MACHINE"), HKEY_LOCAL_MACHINE,
TEXT("HKEY_USERS"), HKEY_USERS,
TEXT("HKEY_CURRENT_CONFIG"), HKEY_CURRENT_CONFIG,
TEXT("HKEY_DYN_DATA"), HKEY_DYN_DATA
};
const TCHAR s_RegistryHeader[] = TEXT("REGEDIT");
const TCHAR s_OldWin31RegFileRoot[] = TEXT(".classes");
const TCHAR s_Win40RegFileHeader[] = TEXT("REGEDIT4\n\n");
TCHAR g_ValueNameBuffer[MAXVALUENAME_LENGTH];
#define IsRegStringType(x) (((x) == REG_SZ) || ((x) == REG_EXPAND_SZ) || ((x) == REG_MULTI_SZ))
#define ExtraAllocLen(Type) (IsRegStringType((Type)) ? sizeof(TCHAR) : 0)
#ifdef UNICODE
//
// New header is required for version 5.0 because the version detection code
// in Win 4.0 regedit wasn't very good (See comments in ImportRegFile for
// details)
//
const WORD s_UnicodeByteOrderMark = 0xFEFF;
const TCHAR s_WinNT50RegFileHeader[] = TEXT("Windows Registry Editor Version");
const TCHAR s_WinNT50RegFileVersion[] = TEXT("5.00");
#endif
const TCHAR s_HexPrefix[] = TEXT("hex");
const TCHAR s_DwordPrefix[] = TEXT("dword:");
const TCHAR g_HexConversion[16] = {TEXT('0'), TEXT('1'), TEXT('2'), TEXT('3'), TEXT('4'),
TEXT('5'), TEXT('6'), TEXT('7'), TEXT('8'), TEXT('9'),
TEXT('a'), TEXT('b'), TEXT('c'), TEXT('d'), TEXT('e'), TEXT('f')};
const TCHAR s_FileLineBreak[] = TEXT(",\\\n ");
//REARCHITECT - we upped the size of this buffer from 512 to 64K to reduce the chance of hitting the bug
//where a DBCS character is split across two buffers. The true fix was too risky at the time.
//Changed for NT5 RC2
#define SIZE_FILE_IO_BUFFER 0x10000 //64K
typedef struct _FILE_IO{
#ifdef UNICODE
//
// Space for unicode/ansi conversions, assumes worst case
// where every unicode char is a double-byte char
//
CHAR ConversionBuffer[SIZE_FILE_IO_BUFFER*2];
#endif
TCHAR Buffer[SIZE_FILE_IO_BUFFER];
FILE_HANDLE hFile;
int BufferOffset;
int CurrentColumn;
int CharsAvailable;
DWORD FileSizeDiv100;
DWORD FileOffset;
UINT LastPercentage;
#ifdef DEBUG
BOOL fValidateUngetChar;
#endif
} FILE_IO;
FILE_IO s_FileIo;
UINT g_FileErrorStringID;
UINT g_ImportFileVersion;
DWORD g_dwTotalKeysSaved = 0;
BOOL s_fTreatFileAsUnicode = TRUE;
VOID NEAR PASCAL ImportWin31RegFile( VOID );
VOID
NEAR PASCAL
ImportNewerRegFile( VOID );
VOID ParseHeader( LPHKEY lphKey );
VOID
NEAR PASCAL
ParseValue(
HKEY hKey,
LPCTSTR lpszValueName
);
VOID
NEAR PASCAL
ParseValuename(
HKEY hKey
);
BOOL
NEAR PASCAL
ParseString(LPPORTVALUEPARAM pPortValueParam);
BOOL
NEAR PASCAL
ParseHexSequence(LPPORTVALUEPARAM pPortValueParam);
BOOL
NEAR PASCAL
ParseHexDword(
LPDWORD lpDword
);
BOOL
NEAR PASCAL
ParseHexByte(
LPBYTE lpByte
);
BOOL
NEAR PASCAL
ParseHexDigit(
LPBYTE lpDigit
);
BOOL
NEAR PASCAL
ParseEndOfLine(
VOID
);
VOID
NEAR PASCAL
SkipWhitespace(
VOID
);
VOID
NEAR PASCAL
SkipPastEndOfLine(
VOID
);
BOOL
NEAR PASCAL
GetChar(
PTCHAR lpChar
);
VOID
NEAR PASCAL
UngetChar(
VOID
);
BOOL
NEAR PASCAL
MatchChar(
TCHAR CharToMatch
);
BOOL
NEAR PASCAL
IsWhitespace(
TCHAR Char
);
BOOL
NEAR PASCAL
IsNewLine(
TCHAR Char
);
VOID
NEAR PASCAL
PutBranch(
HKEY hKey,
LPTSTR lpKeyName
);
VOID
NEAR PASCAL
PutLiteral(
LPCTSTR lpString
);
VOID
NEAR PASCAL
PutString(
LPCTSTR lpString
);
VOID
NEAR PASCAL
PutBinary(
CONST BYTE FAR* lpBuffer,
DWORD Type,
DWORD cbBytes
);
VOID
NEAR PASCAL
PutDword(
DWORD Dword,
BOOL fLeadingZeroes
);
VOID
NEAR PASCAL
PutChar(
TCHAR Char
);
VOID
NEAR PASCAL
FlushIoBuffer(
VOID
);
/*******************************************************************************
*
* EditRegistryKey
*
* DESCRIPTION:
* Parses the pFullKeyName string and creates a handle to the registry key.
*
* PARAMETERS:
* lphKey, location to store handle to registry key.
* lpFullKeyName, string of form "HKEY_LOCAL_MACHINE\Subkey1\Subkey2".
* fCreate, TRUE if key should be created, else FALSE for open only.
* (returns), ERROR_SUCCESS, no errors occurred, phKey is valid,
* ERROR_CANTOPEN, registry access error of some form,
* ERROR_BADKEY, incorrectly formed pFullKeyName.
*
*******************************************************************************/
DWORD
PASCAL
EditRegistryKey(
LPHKEY lphKey,
LPTSTR lpFullKeyName,
UINT uOperation
)
{
LPTSTR lpSubKeyName = NULL;
TCHAR PrevChar = L'\0';
HKEY hRootKey;
UINT Counter;
DWORD Result;
if ((lpSubKeyName = (LPTSTR) StrChr(lpFullKeyName, TEXT('\\'))) != NULL) {
PrevChar = *lpSubKeyName;
*lpSubKeyName++ = 0;
}
CharUpper(lpFullKeyName);
hRootKey = NULL;
for (Counter = 0; Counter < NUMBER_REGISTRY_ROOTS; Counter++) {
if (lstrcmp(g_RegistryRoots[Counter].lpKeyName, lpFullKeyName) == 0) {
hRootKey = g_RegistryRoots[Counter].hKey;
break;
}
}
if (hRootKey) {
Result = ERROR_CANTOPEN;
switch (uOperation)
{
case ERK_CREATE:
//
// If trying to open one of these keys just return OK
// When lpSubKeyName is NULL, you recreate the parent key
// Since these keys are usually in use this will fail
// This code path only occurs when restoring a whole root key
// from a .reg file.
//
if (((hRootKey == HKEY_LOCAL_MACHINE) || (hRootKey == HKEY_USERS))
&& lpSubKeyName == NULL) {
Result = ERROR_SUCCESS;
}
else if (RegCreateKey(hRootKey, lpSubKeyName, lphKey) == ERROR_SUCCESS)
Result = ERROR_SUCCESS;
break;
case ERK_OPEN:
//
// Used when exporting.
//
if(RegOpenKeyEx(hRootKey,lpSubKeyName,0,KEY_ENUMERATE_SUB_KEYS|KEY_QUERY_VALUE,lphKey) == ERROR_SUCCESS)
Result = ERROR_SUCCESS;
break;
case ERK_DELETE:
RegDeleteKeyRecursive(hRootKey, lpSubKeyName);
// asssume success... don't care if this fails
Result = ERROR_SUCCESS;
*lphKey = NULL;
break;
}
}
else
{
Result = ERROR_BADKEY;
}
if (lpSubKeyName != NULL) {
lpSubKeyName--;
*lpSubKeyName = PrevChar;
}
return Result;
}
/*******************************************************************************
*
* ImportRegFileWorker
*
* DESCRIPTION:
*
* PARAMETERS:
* lpFileName, address of name of file to be imported.
*
*******************************************************************************/
VOID
PASCAL
ImportRegFileWorker(
LPTSTR lpFileName
)
{
TCHAR Char;
LPCTSTR lpHeader = NULL;
BOOL fNewRegistryFile;
#ifdef UNICODE
UINT Temp, i;
TCHAR StrToIntBuf[2];
LPCTSTR lp50Header = NULL;
#endif // UNICODE
DWORD cch;
TCHAR tchBuffer[MAX_PATH] = {0};
LPTSTR lpFilePart = NULL;
g_FileErrorStringID = IDS_IMPFILEERRSUCCESS;
// OPENREADFILE used to be OpenFile(), but there isn't any Unicode version
// of that API, so now it's CreateFile(). But OpenFile searched the path
// automatically, whereas CreateFile does not. Corel's 'Perfect Office v6'
// install app depends on the path being searched, so do it manually.
cch = SearchPath(NULL, // pointer to search path
lpFileName, // pointer to filename
NULL, // pointer to extension
ARRAYSIZE(tchBuffer), // size, in characters, of buffer
(TCHAR*)tchBuffer, // pointer to buffer for found filename
&lpFilePart); // pointer to pointer to file component);
if ((cch != 0) && (cch <= MAX_PATH) && OPENREADFILE((TCHAR*)tchBuffer, s_FileIo.hFile))
{
WORD wBOM;
DWORD NumberOfBytesRead;
s_FileIo.FileSizeDiv100 = GetFileSize(s_FileIo.hFile, NULL) / 100;
s_FileIo.FileOffset = 0;
s_FileIo.CharsAvailable = 0;
s_FileIo.LastPercentage = 0;
//
// Read the first two bytes. If it's the Unicode byte order mark,
// set a flag so all the rest of the file will be interpreted
// as ANSI or Unicode text properly.
//
if (!READFILE(s_FileIo.hFile, &wBOM,
sizeof(wBOM), &NumberOfBytesRead)) {
g_FileErrorStringID = IDS_IMPFILEERRFILEREAD;
goto exit_gracefully;
}
if (wBOM == s_UnicodeByteOrderMark)
s_fTreatFileAsUnicode = TRUE;
else {
s_fTreatFileAsUnicode = FALSE;
// We probably just read "RE" from "REGEDIT4". Back up the file
// position so the ANSI import routines get what they expect
SetFilePointer(s_FileIo.hFile, -2, NULL, FILE_CURRENT);
}
//
// The following will force GetChar to read in the first block of data.
//
s_FileIo.BufferOffset = 0;
SkipWhitespace();
lpHeader = s_RegistryHeader;
g_ImportFileVersion = 0;
# if 0
Sit back, and I will tell ye a tale of woe.
Win95 and NT 4 shipped with regedit compiled ANSI. There are a couple
of registry types on NT (namely REG_EXPAND_SZ and REG_MULTI_SZ) that
weren't on Win95, and which regedit doesn't really understand. regedit
treats these registry types as hex binary streams.
You can probably see where this is going.
If you exported, say your user TEMP environment variable on NT 4
using regedit, you'd get something that looked like this:
REGEDIT4
[HKEY_CURRENT_USER\Environment]
"TEMP"=hex(2):25,53,59,53,54,45,4d,44,52,49,56,45,25,5c,53,48,54,65,6d,70,00
...a nice, null-terminated ANSI string. Nice, that is, until we decided
to compile regedit UNICODE for NT 5. A unicode regedit exports your
user TEMP variable like this:
REGEDIT4
[HKEY_CURRENT_USER\Environment]
"TEMP"=hex(2):25,00,53,00,59,00,53,00,54,00,45,00,4d,00,44,00,52,00,49,00,56,\
00,45,00,25,00,5c,00,53,00,48,00,54,00,65,00,6d,00,70,00,00,00
...mmmm. Unicode. Of course, a unicode regedit also expects anything
it imports to have all those interspersed zeroes, too. Otherwise,
it dumps garbage into your registry. All it takes is a -DUNICODE, and
regedit is suddenly incompatible with the thousdands of existing .reg
files out there.
So just bump the version in the header to REGEDIT5 and be done with
it, right? Wrong. The regedit on Win95 and NT 4 looks at the first
character after the string "REGEDIT" and compares it to the digit "4".
If that character is anything other than the digit "4", the parser
assumes it is looking at a Windows 3.1 file. Yep. There will only
ever be two formats, right? Just Win95 and Win3.1. That's all the
world needs.
So a completely new .reg file header had to be invented, so that the
older, regedits of the world would simply regect the new,
unicodized .reg files outright. An NT 5 .reg file, exporting your user
TEMP variable, looks like this:
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Environment]
"TEMP"=hex(2):25,00,53,00,59,00,53,00,54,00,45,00,4d,00,44,00,52,00,49,00,56,\
00,45,00,25,00,5c,00,53,00,48,00,54,00,65,00,6d,00,70,00,00,00
The parser is still not very good, but it does bother to convert that 5.00
into a version number, so that future generations can bump it to 5.50 or
6.00, and the regedit 5.00 that shipped with NT 5.00 will properly reject
the files.
#endif // 0
#ifdef UNICODE
//
// Compare to the new .reg file header
//
lp50Header = s_WinNT50RegFileHeader;
while (*lp50Header != 0) {
if (MatchChar(*lp50Header))
lp50Header = CharNext(lp50Header);
else
break;
}
//
// If the above loop pushed lp50Header to its terminating null
// character, then the header matches.
//
if (0 == *lp50Header) {
SkipWhitespace();
//
// Now, decode the version number into a hex, _WIN32_WINNT
// style version number.
//
StrToIntBuf[1] = 0;
//
// Any number of digits can come before the decimal point
//
while (!MatchChar(TEXT('.'))) {
if (!GetChar(StrToIntBuf) || !IsCharAlphaNumeric(*StrToIntBuf)) {
g_FileErrorStringID = IDS_IMPFILEERRFORMATBAD;
goto exit_gracefully;
}
Temp = StrToInt(StrToIntBuf);
// Hex version number, so move left four bits.
g_ImportFileVersion <<= 4;
g_ImportFileVersion += Temp;
}
//
// Fixed at two digits after the decimal point
//
for (i = 0; i < 2; i++) {
if (!GetChar(StrToIntBuf) || !IsCharAlphaNumeric(*StrToIntBuf)) {
g_FileErrorStringID = IDS_IMPFILEERRFORMATBAD;
goto exit_gracefully;
}
Temp = StrToInt(StrToIntBuf);
// Hex version number, so move left four bits.
g_ImportFileVersion <<= 4;
g_ImportFileVersion += Temp;
}
//
// For NT 5, reject any version number that isn't
// 5. This can be expanded into a switch statement
// when the version number is bumped later.
//
if (0x0500 != g_ImportFileVersion) {
g_FileErrorStringID = IDS_IMPFILEERRVERBAD;
goto exit_gracefully;
}
else {
SkipWhitespace();
ImportNewerRegFile();
}
} // if (0 == *lp50Header)
//
// It doesn't use the new .reg file header, so
// it's not an NT 5.0+ registry file, so use the
// older algorithm to see if it's a valid older registry file
//
else {
#endif // UNICODE
while (*lpHeader != 0) {
if (MatchChar(*lpHeader))
lpHeader = CharNext(lpHeader);
else
break;
}
if (*lpHeader == 0) {
//
// Win95's and NT 4's regedit shipped with this line
// of code. It is the cause of all of the suffering above.
// Notice the incorrect assumption: "If the very next
// character isn't a '4', then we must be reading
// a Windows 3.1 registry file!" Of course there won't
// be a version 5 of regedit. Version 4 was perfect!
//
fNewRegistryFile = MatchChar(TEXT('4'));
SkipWhitespace();
if (GetChar(&Char) && IsNewLine(Char)) {
if (fNewRegistryFile) {
g_ImportFileVersion = 0x0400;
ImportNewerRegFile();
}
else {
g_ImportFileVersion = 0x0310;
ImportWin31RegFile();
}
}
}
else
{
g_FileErrorStringID = IDS_IMPFILEERRFORMATBAD;
}
#ifdef UNICODE
}
#endif // UNICODE
} // if (OPENREADFILE...
else
{
{
TCHAR buff[250];
StringCchPrintf(buff, ARRAYSIZE(buff), L"REGEDIT: CreateFile failed, GetLastError() = %d\n",
GetLastError());
OutputDebugString(buff);
}
s_FileIo.hFile = NULL;
g_FileErrorStringID = IDS_IMPFILEERRFILEOPEN;
}
#ifdef UNICODE // Urefd labels generate warnings
exit_gracefully:
#endif // UNICODE
if (s_FileIo.hFile) {
CloseHandle(s_FileIo.hFile);
}
}
/*******************************************************************************
*
* ImportWin31RegFile
*
* DESCRIPTION:
* Imports the contents of a Windows 3.1 style registry file into the
* registry.
*
* We scan over the file looking for lines of the following type:
* HKEY_CLASSES_ROOT\keyname = value_data
* HKEY_CLASSES_ROOT\keyname =value_data
* HKEY_CLASSES_ROOT\keyname value_data
* HKEY_CLASSES_ROOT\keyname (null value data)
*
* In all cases, any number of spaces may follow 'keyname'. Although we
* only document the first syntax, the Windows 3.1 Regedit handled all of
* these formats as valid, so this version will as well (fortunately, it
* doesn't make the parsing any more complex!).
*
* Note, we also support replacing HKEY_CLASSES_ROOT with \.classes above
* which must come from some early releases of Windows.
*
* PARAMETERS:
* (none).
*
*******************************************************************************/
VOID
NEAR PASCAL
ImportWin31RegFile(
VOID
)
{
HKEY hKey;
TCHAR Char;
BOOL fSuccess;
LPCTSTR lpClassesRoot;
TCHAR KeyName[MAXKEYNAME];
UINT Index;
//
// Keep an open handle to the classes root. We may prevent some
// unneccessary flushing.
//
if(RegOpenKeyEx(HKEY_CLASSES_ROOT,NULL,0,KEY_SET_VALUE,&hKey) != ERROR_SUCCESS) {
g_FileErrorStringID = IDS_IMPFILEERRREGOPEN;
return;
}
for (;;) {
//
// Check for the end of file condition.
//
if (!GetChar(&Char))
break;
UngetChar(); // Not efficient, but works for now.
//
// Match the beginning of the line against one of the two aliases for
// HKEY_CLASSES_ROOT.
//
if (MatchChar(TEXT('\\')))
lpClassesRoot = s_OldWin31RegFileRoot;
else
lpClassesRoot = g_RegistryRoots[INDEX_HKEY_CLASSES_ROOT].lpKeyName;
fSuccess = TRUE;
while (*lpClassesRoot != 0) {
if (!MatchChar(*lpClassesRoot++)) {
fSuccess = FALSE;
break;
}
}
//
// Make sure that we have a backslash seperating one of the aliases
// from the keyname.
//
if (fSuccess)
fSuccess = MatchChar(TEXT('\\'));
if (fSuccess) {
//
// We've found one of the valid aliases, so read in the keyname.
//
// fSuccess = TRUE; // Must be TRUE if we're in this block
Index = 0;
while (GetChar(&Char)) {
if (Char == TEXT(' ') || IsNewLine(Char))
break;
//
// Make sure that the keyname buffer doesn't overflow. We must
// leave room for a terminating null.
//
if (Index >= (ARRAYSIZE(KeyName)) - 1)
{
fSuccess = FALSE;
break;
}
KeyName[Index++] = Char;
}
if (fSuccess)
{
UINT cMaxDataLength = ALLOCATION_INCR;
PBYTE pbValueDataBuffer;
KeyName[Index] = 0;
//
// Now see if we have a value to assign to this keyname.
//
SkipWhitespace();
if (MatchChar(TEXT('=')))
MatchChar(TEXT(' '));
// fSuccess = TRUE; // Must be TRUE if we're in this block
Index = 0;
pbValueDataBuffer = LocalAlloc(LPTR, cMaxDataLength);
fSuccess = (pbValueDataBuffer != NULL);
while (GetChar(&Char) && fSuccess)
{
if (IsNewLine(Char))
break;
//
// Make sure that the value data buffer doesn't overflow.
// Because this is always string data, we must leave room
// for a terminating null.
//
if (Index >= cMaxDataLength - 1)
{
PBYTE pbValueData =
LocalReAlloc(pbValueDataBuffer, cMaxDataLength + ALLOCATION_INCR, LMEM_MOVEABLE);
fSuccess = (pbValueData != NULL);
if (!fSuccess)
{
break;
}
else
{
pbValueDataBuffer = pbValueData;
cMaxDataLength += ALLOCATION_INCR;
}
}
((PTSTR)pbValueDataBuffer)[Index++] = Char;
}
if (fSuccess)
{
((PTSTR)pbValueDataBuffer)[Index] = 0;
if (RegSetValue(hKey, KeyName, REG_SZ, (LPCTSTR)pbValueDataBuffer,
Index*sizeof(TCHAR)) != ERROR_SUCCESS)
g_FileErrorStringID = IDS_IMPFILEERRREGSET;
}
else
{
g_FileErrorStringID = IDS_NOMEMORY;
}
if (pbValueDataBuffer)
{
LocalFree(pbValueDataBuffer);
}
}
}
//
// Somewhere along the line, we had a parsing error, so resynchronize
// on the next line.
//
if (!fSuccess)
SkipPastEndOfLine();
}
RegFlushKey(hKey);
RegCloseKey(hKey);
}
/*******************************************************************************
*
* ImportNewerRegFile
*
* DESCRIPTION:
*
* PARAMETERS:
*
*******************************************************************************/
VOID
NEAR PASCAL
ImportNewerRegFile(
VOID
)
{
HKEY hLocalMachineKey = NULL;
HKEY hUsersKey = NULL;
HKEY hKey = NULL;
TCHAR Char;
#ifdef WINNT
hLocalMachineKey = NULL;
hUsersKey = NULL;
#else
//
// Keep open handles for the predefined roots to prevent the registry
// library from flushing after every single RegOpenKey/RegCloseKey
// operation.
//
RegOpenKey(HKEY_LOCAL_MACHINE, NULL, &hLocalMachineKey);
RegOpenKey(HKEY_USERS, NULL, &hUsersKey);
#ifdef DEBUG
if (hLocalMachineKey == NULL)
DbgPrintf(("Unable to open HKEY_LOCAL_MACHINE\n\r"));
if (hUsersKey == NULL)
DbgPrintf(("Unable to open HKEY_USERS\n\r"));
#endif
#endif
hKey = NULL;
for (;;) {
SkipWhitespace();
//
// Check for the end of file condition.
//
if (!GetChar(&Char))
break;
switch (Char) {
case TEXT('['):
//
// If a registry key is currently open, we must close it first.
// If ParseHeader happens to fail (for example, no closing
// bracket), then hKey will be NULL and any values that we
// parse must be ignored.
//
if (hKey != NULL) {
RegCloseKey(hKey);
hKey = NULL;
}
ParseHeader(&hKey);
break;
case TEXT('"'):
//
// As noted above, if we don't have an open registry key, then
// just skip the line.
//
if (hKey != NULL)
ParseValuename(hKey);
else
SkipPastEndOfLine();
break;
case TEXT('@'):
//
//
//
if (hKey != NULL)
ParseValue(hKey, NULL);
else
SkipPastEndOfLine();
break;
case TEXT(';'):
//
// This line is a comment so just dump the rest of it.
//
SkipPastEndOfLine();
break;
default:
if (IsNewLine(Char))
break;
SkipPastEndOfLine();
break;
}
}
if (hKey != NULL)
RegCloseKey(hKey);
if (hUsersKey != NULL)
RegCloseKey(hUsersKey);
if (hLocalMachineKey != NULL)
RegCloseKey(hLocalMachineKey);
}
/*******************************************************************************
*
* ParseHeader
*
* DESCRIPTION:
*
* PARAMETERS:
*
*******************************************************************************/
// REARCHITECT - each subkeyname can be MAXKEYNAME in size
// ideally - we should handle unlimited size names
// let's at least handle bigger names for now
// at least a depth of 10 with maximum length subkey names
#define SIZE_FULL_KEYNAME ((MAXKEYNAME + 40)*10)
VOID NEAR PASCAL ParseHeader( LPHKEY lphKey )
{
TCHAR FullKeyName[SIZE_FULL_KEYNAME];
int CurrentIndex;
int LastRightBracketIndex;
TCHAR Char;
UINT uOperation = ERK_CREATE;
CurrentIndex = 0;
LastRightBracketIndex = -1;
if (!GetChar(&Char))
return;
if (Char == TEXT('-')) {
if (!GetChar(&Char))
return;
uOperation = ERK_DELETE;
}
do {
if (IsNewLine(Char))
break;
if (Char == TEXT(']'))
LastRightBracketIndex = CurrentIndex;
FullKeyName[CurrentIndex++] = Char;
if (CurrentIndex == SIZE_FULL_KEYNAME) {
do {
if (Char == TEXT(']'))
LastRightBracketIndex = -1;
if (IsNewLine(Char))
break;
} while (GetChar(&Char));
break;
}
} while (GetChar(&Char));
if (LastRightBracketIndex != -1)
{
FullKeyName[LastRightBracketIndex] = 0;
switch (EditRegistryKey(lphKey, FullKeyName, uOperation))
{
//
// Be afraid of adding code to handle more error cases here.
//
// We broke Picture Publisher 8.0 by adding an ERROR_BADKEY
// case. As part of their setup, they run regedit on a v4
// reg file that has a bad section, which EditRegistryKey
// will fail to parse with ERROR_BADKEY. We need to keep
// chugging along in that case like Win2K did, or else we
// break their setup.
//
case ERROR_CANTOPEN:
g_FileErrorStringID = IDS_IMPFILEERRREGOPEN;
break;
}
}
}
/*******************************************************************************
*
* ParseValuename
*
* DESCRIPTION:
*
* PARAMETERS:
*
*******************************************************************************/
VOID
NEAR PASCAL
ParseValuename(
HKEY hKey
)
{
PORTVALUEPARAM PortValueParam;
PortValueParam.cbData = MAXVALUENAME_LENGTH * sizeof(TCHAR);
PortValueParam.pbData = LocalAlloc(LPTR, PortValueParam.cbData);
if (PortValueParam.pbData)
{
if (ParseString(&PortValueParam))
{
ParseValue(hKey, (PTSTR)PortValueParam.pbData);
}
else
{
SkipPastEndOfLine();
}
LocalFree(PortValueParam.pbData);
}
}
VOID
NEAR PASCAL
ParseValue(
HKEY hKey,
LPCTSTR lpszValueName
)
{
BOOL fSuccess = TRUE;
BOOL fSkipPastLine = FALSE;
DWORD Type = 0;
DWORD cbData = 0;
DWORD cbMaxData = ALLOCATION_INCR;
LPCTSTR lpPrefix;
PBYTE pbValueDataBuffer;
SkipWhitespace();
if (!MatchChar(TEXT('=')))
{
fSuccess = FALSE;
fSkipPastLine = TRUE;
}
else
{
SkipWhitespace();
pbValueDataBuffer = LocalAlloc(LPTR, cbMaxData);
if (!pbValueDataBuffer)
{
g_FileErrorStringID = IDS_IMPFILEERRREGSET;
fSuccess = FALSE;
}
else
{
//
// REG_SZ.
//
// "ValueName" = "string of text"
//
if (MatchChar(TEXT('"')))
{
// FEATURE: Line continuations for strings?
PORTVALUEPARAM PortValueParam;
PortValueParam.pbData = pbValueDataBuffer;
PortValueParam.cbData = cbMaxData;
if (!ParseString(&PortValueParam) || !ParseEndOfLine())
{
fSuccess = FALSE;
fSkipPastLine = TRUE;
}
// pointer might have been swapped for one with more memory
pbValueDataBuffer = PortValueParam.pbData;
cbData = PortValueParam.cbData;
Type = REG_SZ;
}
//
// REG_DWORD.
//
// "ValueName" = dword: 12345678
//
else if (MatchChar(s_DwordPrefix[0])) {
lpPrefix = &s_DwordPrefix[1];
while (*lpPrefix != 0)
{
if (!MatchChar(*lpPrefix++))
{
fSuccess = FALSE;
fSkipPastLine = TRUE;
}
}
if (fSuccess)
{
SkipWhitespace();
if (!ParseHexDword((LPDWORD) pbValueDataBuffer) || !ParseEndOfLine())
{
fSuccess = FALSE;
fSkipPastLine = TRUE;
}
Type = REG_DWORD;
cbData = sizeof(DWORD);
}
}
else if (MatchChar('-'))
{
if (!ParseEndOfLine())
{
fSuccess = FALSE;
fSkipPastLine = TRUE;
}
else
{
RegDeleteValue(hKey, lpszValueName);
fSuccess = FALSE;
}
}
//
// REG_BINARY and other.
//
// "ValueName" = hex: 00 , 11 , 22
// "ValueName" = hex(12345678): 00, 11, 22
//
else {
lpPrefix = s_HexPrefix;
while (*lpPrefix != 0)
{
if (!MatchChar(*lpPrefix++))
{
fSuccess = FALSE;
fSkipPastLine = TRUE;
}
}
if (fSuccess)
{
//
// Check if this is a type of registry data that we don't directly
// support. If so, then it's just a dump of hex data of the specified
// type.
//
if (MatchChar(TEXT('(')))
{
if (!ParseHexDword(&Type) || !MatchChar(TEXT(')')))
{
fSuccess = FALSE;
fSkipPastLine = TRUE;
}
}
else
Type = REG_BINARY;
if (fSuccess)
{
PORTVALUEPARAM PortValueParam;
PortValueParam.pbData = pbValueDataBuffer;
PortValueParam.cbData = cbMaxData;
if (!MatchChar(TEXT(':')) || !ParseHexSequence(&PortValueParam) ||
!ParseEndOfLine())
{
fSuccess = FALSE;
fSkipPastLine = TRUE;
}
// pointer might have been swapped for one with more memory
pbValueDataBuffer = PortValueParam.pbData;
cbData = PortValueParam.cbData;
}
}
}
if (fSuccess)
{
#ifdef UNICODE
//
// If we're compiled UNICODE and we're reading an older, ANSI .reg
// file, we have to write all of the data to the registry using
// RegSetValueExA, because it was read from the registry using
// RegQueryValueExA.
//
if ((g_ImportFileVersion < 0x0500) && ((REG_EXPAND_SZ == Type) || (REG_MULTI_SZ == Type)))
{
CHAR AnsiValueName[MAXVALUENAME_LENGTH];
AnsiValueName[0] = 0;
//
// It's much easier to convert the value name to ANSI
// and call RegSetValueExA than to try to convert
// a REG_MULTI_SZ to Unicode before calling RegSetValueExW.
// We don't lose anything because this is coming from a
// downlevel .reg file that could only contain ANSI characters
// to begin with.
//
WideCharToMultiByte(
CP_THREAD_ACP,
0,
lpszValueName,
-1,
AnsiValueName,
ARRAYSIZE(AnsiValueName),
NULL,
NULL
);
if (RegSetValueExA(
hKey,
AnsiValueName,
0,
Type,
pbValueDataBuffer,
cbData)
!= ERROR_SUCCESS)
g_FileErrorStringID = IDS_IMPFILEERRREGSET;
}
else
{
#endif // UNICODE
if (RegSetValueEx(hKey, lpszValueName, 0, Type, pbValueDataBuffer, cbData) != ERROR_SUCCESS)
g_FileErrorStringID = IDS_IMPFILEERRREGSET;
#ifdef UNICODE
}
#endif // UNICODE
}
LocalFree(pbValueDataBuffer);
}
}
if (fSkipPastLine)
{
SkipPastEndOfLine();
}
}
/*******************************************************************************
*
* ParseString
*
* DESCRIPTION:
*
* PARAMETERS:
*
*******************************************************************************/
BOOL
NEAR PASCAL
ParseString(LPPORTVALUEPARAM pPortValueParam)
{
TCHAR Char;
DWORD cbMaxStringData;
DWORD cbStringData;
LPTSTR psz = (LPTSTR)pPortValueParam->pbData; // this one is incremented
cbMaxStringData = pPortValueParam->cbData;
cbStringData = sizeof(TCHAR); // Account for the null terminator
while (GetChar(&Char))
{
if (cbStringData >= cbMaxStringData)
{
// allocate a bigger buffer
PBYTE pbValueData =
LocalReAlloc(pPortValueParam->pbData, cbMaxStringData + ALLOCATION_INCR, LMEM_MOVEABLE);
if (pbValueData)
{
pPortValueParam->pbData = pbValueData;
// incr psz to next char in new buffer
psz = (LPTSTR)(pPortValueParam->pbData + (cbMaxStringData - sizeof(TCHAR)));
cbMaxStringData += ALLOCATION_INCR;
}
else
{
break;
}
}
switch (Char) {
case TEXT('\\'):
if (!GetChar(&Char))
return FALSE;
switch (Char) {
case TEXT('\\'):
*psz++ = TEXT('\\');
break;
case TEXT('"'):
*psz++ = TEXT('"');
break;
default:
DebugPrintf(("ParseString: Invalid escape sequence"));
return FALSE;
}
break;
case TEXT('"'):
*psz = 0;
pPortValueParam->cbData = cbStringData;
return TRUE;
default:
if (IsNewLine(Char))
return FALSE;
*psz++ = Char;
break;
}
cbStringData += sizeof(TCHAR);
}
return FALSE;
}
/*******************************************************************************
*
* ParseHexSequence
*
* DESCRIPTION:
*
* PARAMETERS:
*
*******************************************************************************/
BOOL
NEAR PASCAL
ParseHexSequence(LPPORTVALUEPARAM pPortValueParam)
{
BOOL fSuccess = TRUE;
DWORD cbHexData = 0;
DWORD cbMaxStringData = pPortValueParam->cbData;
LPBYTE lpHexData = pPortValueParam->pbData;
do
{
if (cbHexData >= cbMaxStringData)
{
// allocate a bigger buffer
PBYTE pbValueData = LocalReAlloc(pPortValueParam->pbData,
cbMaxStringData + ALLOCATION_INCR, LMEM_MOVEABLE);
if (pbValueData)
{
pPortValueParam->pbData = pbValueData;
// incr psz to next char in new buffer
lpHexData = pPortValueParam->pbData + cbMaxStringData;
cbMaxStringData += ALLOCATION_INCR;
}
else
{
fSuccess = FALSE;
break;
}
}
SkipWhitespace();
if (MatchChar(TEXT('\\')) && !ParseEndOfLine())
{
fSuccess = FALSE;
break;
}
SkipWhitespace();
if (!ParseHexByte(lpHexData++))
break;
cbHexData++;
SkipWhitespace();
} while (MatchChar(TEXT(',')));
pPortValueParam->cbData = cbHexData;
return fSuccess;
}
/*******************************************************************************
*
* ParseHexDword
*
* DESCRIPTION:
* Parses a one dword hexadecimal string from the registry file stream and
* converts it to a binary number. A maximum of eight hex digits will be
* parsed from the stream.
*
* PARAMETERS:
* lpByte, location to store binary number.
* (returns), TRUE if a hexadecimal dword was parsed, else FALSE.
*
*******************************************************************************/
BOOL
NEAR PASCAL
ParseHexDword(
LPDWORD lpDword
)
{
UINT CountDigits;
DWORD Dword;
BYTE Byte;
Dword = 0;
CountDigits = 0;
for (;;) {
if (!ParseHexDigit(&Byte))
break;
Dword = (Dword << 4) + (DWORD) Byte;
if (++CountDigits == 8)
break;
}
*lpDword = Dword;
return CountDigits != 0;
}
/*******************************************************************************
*
* ParseHexByte
*
* DESCRIPTION:
* Parses a one byte hexadecimal string from the registry file stream and
* converts it to a binary number.
*
* PARAMETERS:
* lpByte, location to store binary number.
* (returns), TRUE if a hexadecimal byte was parsed, else FALSE.
*
*******************************************************************************/
BOOL
NEAR PASCAL
ParseHexByte(
LPBYTE lpByte
)
{
BYTE SecondDigit;
if (ParseHexDigit(lpByte)) {
if (ParseHexDigit(&SecondDigit))
*lpByte = (BYTE) ((*lpByte << 4) | SecondDigit);
return TRUE;
}
else
return FALSE;
}
/*******************************************************************************
*
* ParseHexDigit
*
* DESCRIPTION:
* Parses a hexadecimal character from the registry file stream and converts
* it to a binary number.
*
* PARAMETERS:
* lpDigit, location to store binary number.
* (returns), TRUE if a hexadecimal digit was parsed, else FALSE.
*
*******************************************************************************/
BOOL
NEAR PASCAL
ParseHexDigit(
LPBYTE lpDigit
)
{
TCHAR Char;
BYTE Digit;
if (GetChar(&Char)) {
if (Char >= TEXT('0') && Char <= TEXT('9'))
Digit = (BYTE) (Char - TEXT('0'));
else if (Char >= TEXT('a') && Char <= TEXT('f'))
Digit = (BYTE) (Char - TEXT('a') + 10);
else if (Char >= TEXT('A') && Char <= TEXT('F'))
Digit = (BYTE) (Char - TEXT('A') + 10);
else {
UngetChar();
return FALSE;
}
*lpDigit = Digit;
return TRUE;
}
return FALSE;
}
/*******************************************************************************
*
* ParseEndOfLine
*
* DESCRIPTION:
*
* PARAMETERS:
*
*******************************************************************************/
BOOL
NEAR PASCAL
ParseEndOfLine(
VOID
)
{
TCHAR Char;
BOOL fComment;
BOOL fFoundOneEndOfLine;
BOOL fEOF;
fComment = FALSE;
fFoundOneEndOfLine = FALSE;
fEOF = TRUE;
while (GetChar(&Char)) {
if (IsWhitespace(Char))
continue;
if (IsNewLine(Char)) {
fComment = FALSE;
fFoundOneEndOfLine = TRUE;
}
//
// Like .INIs and .INFs, comments begin with a semicolon character.
//
else if (Char == TEXT(';'))
fComment = TRUE;
else if (!fComment) {
UngetChar();
fEOF = FALSE;
break;
}
}
return fFoundOneEndOfLine || fEOF;
}
/*******************************************************************************
*
* SkipWhitespace
*
* DESCRIPTION:
* Advances the registry file pointer to the first character past any
* detected whitespace.
*
* PARAMETERS:
* (none).
*
*******************************************************************************/
VOID
NEAR PASCAL
SkipWhitespace(
VOID
)
{
TCHAR Char;
while (GetChar(&Char)) {
if (!IsWhitespace(Char)) {
UngetChar();
break;
}
}
}
/*******************************************************************************
*
* SkipPastEndOfLine
*
* DESCRIPTION:
* Advances the registry file pointer to the first character past the first
* detected new line character.
*
* PARAMETERS:
* (none).
*
*******************************************************************************/
VOID
NEAR PASCAL
SkipPastEndOfLine(
VOID
)
{
TCHAR Char;
while (GetChar(&Char)) {
if (IsNewLine(Char))
break;
}
while (GetChar(&Char)) {
if (!IsNewLine(Char)) {
UngetChar();
break;
}
}
}
/*******************************************************************************
*
* GetChar
*
* DESCRIPTION:
*
* PARAMETERS:
*
*******************************************************************************/
BOOL
NEAR PASCAL
GetChar(
PTCHAR lpChar
)
{
DWORD NumberOfBytesRead;
UINT NewPercentage;
// If we're at the end of the buffer, read some more.
// Initially BufferOffset and CharsAvailable will be 0
if (s_FileIo.BufferOffset == s_FileIo.CharsAvailable) {
if (TRUE == s_fTreatFileAsUnicode)
{
if (!READFILE(s_FileIo.hFile, s_FileIo.Buffer, SIZE_FILE_IO_BUFFER, &NumberOfBytesRead))
{
g_FileErrorStringID = IDS_IMPFILEERRFILEREAD;
return FALSE;
}
s_FileIo.CharsAvailable = ((int) NumberOfBytesRead / 2);
}
else
{
if (!READFILE(s_FileIo.hFile, s_FileIo.ConversionBuffer, SIZE_FILE_IO_BUFFER, &NumberOfBytesRead))
{
g_FileErrorStringID = IDS_IMPFILEERRFILEREAD;
return FALSE;
}
{
int i;
i = MultiByteToWideChar(
CP_THREAD_ACP,
MB_PRECOMPOSED,
s_FileIo.ConversionBuffer,
NumberOfBytesRead,
s_FileIo.Buffer,
ARRAYSIZE(s_FileIo.Buffer)
);
s_FileIo.CharsAvailable = i;
}
}
s_FileIo.BufferOffset = 0;
s_FileIo.FileOffset += NumberOfBytesRead;
if (s_FileIo.FileSizeDiv100 != 0) {
NewPercentage = ((UINT) (s_FileIo.FileOffset /
s_FileIo.FileSizeDiv100));
if (NewPercentage > 100)
NewPercentage = 100;
}
else
NewPercentage = 100;
if (s_FileIo.LastPercentage != NewPercentage) {
s_FileIo.LastPercentage = NewPercentage;
// ImportRegFileUICallback(NewPercentage);
}
}
if (s_FileIo.BufferOffset >= s_FileIo.CharsAvailable)
return FALSE;
*lpChar = s_FileIo.Buffer[s_FileIo.BufferOffset++];
return TRUE;
}
/*******************************************************************************
*
* UngetChar
*
* DESCRIPTION:
*
* PARAMETERS:
*
*******************************************************************************/
VOID
NEAR PASCAL
UngetChar(
VOID
)
{
#ifdef DEBUG
if (s_FileIo.fValidateUngetChar)
DebugPrintf(("REGEDIT ERROR: Too many UngetChar's called!\n\r"));
#endif
s_FileIo.BufferOffset--;
}
/*******************************************************************************
*
* MatchChar
*
* DESCRIPTION:
*
* PARAMETERS:
*
*******************************************************************************/
BOOL
NEAR PASCAL
MatchChar(
TCHAR CharToMatch
)
{
BOOL fMatch;
TCHAR NextChar;
fMatch = FALSE;
if (GetChar(&NextChar)) {
if (CharToMatch == NextChar)
fMatch = TRUE;
else
UngetChar();
}
return fMatch;
}
/*******************************************************************************
*
* IsWhitespace
*
* DESCRIPTION:
* Checks if the given character is whitespace.
*
* PARAMETERS:
* Char, character to check.
* (returns), TRUE if character is whitespace, else FALSE.
*
*******************************************************************************/
BOOL
NEAR PASCAL
IsWhitespace(
TCHAR Char
)
{
return Char == TEXT(' ') || Char == TEXT('\t');
}
/*******************************************************************************
*
* IsNewLine
*
* DESCRIPTION:
* Checks if the given character is a new line character.
*
* PARAMETERS:
* Char, character to check.
* (returns), TRUE if character is a new line, else FALSE.
*
*******************************************************************************/
BOOL
NEAR PASCAL
IsNewLine(
TCHAR Char
)
{
return Char == TEXT('\n') || Char == TEXT('\r');
}
/*******************************************************************************
*
* ExportWinNT50RegFile
*
* DESCRIPTION:
* Exports an NT 5.0, unicode registry file. Use this export function
* for all future .reg file writing.
*
* PARAMETERS:
*
*******************************************************************************/
VOID ExportWinNT50RegFile(LPTSTR lpFileName, LPTSTR lpSelectedPath)
{
HKEY hKey = INVALID_HANDLE_VALUE;
TCHAR SelectedPath[SIZE_SELECTED_PATH];
g_FileErrorStringID = IDS_EXPFILEERRSUCCESS;
if (lpSelectedPath != NULL &&
EditRegistryKey(&hKey, lpSelectedPath, ERK_OPEN) != ERROR_SUCCESS)
{
g_FileErrorStringID = IDS_EXPFILEERRBADREGPATH;
return;
}
g_dwTotalKeysSaved = 0;
if (OPENWRITEFILE(lpFileName, s_FileIo.hFile))
{
DWORD dwNumberOfBytesWritten;
s_FileIo.BufferOffset = 0;
s_FileIo.CurrentColumn = 0;
WRITEFILE(s_FileIo.hFile, &s_UnicodeByteOrderMark, sizeof(s_UnicodeByteOrderMark), &dwNumberOfBytesWritten);
PutLiteral(s_WinNT50RegFileHeader);
PutLiteral(TEXT(" "));
PutLiteral(s_WinNT50RegFileVersion);
PutLiteral(TEXT("\n\n"));
if (lpSelectedPath != NULL)
{
StringCchCopy(SelectedPath, ARRAYSIZE(SelectedPath), lpSelectedPath);
PutBranch(hKey, SelectedPath);
}
else
{
StringCchCopy(SelectedPath, ARRAYSIZE(SelectedPath), g_RegistryRoots[INDEX_HKEY_LOCAL_MACHINE].lpKeyName);
PutBranch(HKEY_LOCAL_MACHINE, SelectedPath);
StringCchCopy(SelectedPath, ARRAYSIZE(SelectedPath), g_RegistryRoots[INDEX_HKEY_USERS].lpKeyName);
PutBranch(HKEY_USERS, SelectedPath);
}
FlushIoBuffer();
CloseHandle(s_FileIo.hFile);
}
else
g_FileErrorStringID = IDS_EXPFILEERRFILEOPEN;
if (lpSelectedPath != NULL)
RegCloseKey(hKey);
}
/*******************************************************************************
*
* PutBranch
*
* DESCRIPTION:
* Writes out all of the value names and their data and recursively calls
* this routine for all of the key's subkeys to the registry file stream.
*
* PARAMETERS:
* hKey, registry key to write to file.
* lpFullKeyName, string that gives the full path, including the root key
* name, of the hKey.
*
*******************************************************************************/
VOID
NEAR PASCAL
PutBranch(
HKEY hKey,
LPTSTR lpFullKeyName
)
{
HKEY hSubKey;
LONG RegError;
DWORD EnumIndex;
DWORD cchValueName;
DWORD cbValueData;
DWORD Type;
LPTSTR lpSubKeyName;
LPTSTR lpTempFullKeyName;
BOOL bFlag = FALSE;
int nLenTempFullKey;
int nLenFullKey;
//
// Write out the section header.
//
PutChar(TEXT('['));
PutLiteral(lpFullKeyName);
PutLiteral(TEXT("]\n"));
//
// Write out all of the value names and their data.
//
EnumIndex = 0;
for (;;)
{
PBYTE pbValueData;
cchValueName = ARRAYSIZE(g_ValueNameBuffer);
// VALUE DATA
// Query for data size
RegError = RegEnumValue(hKey, EnumIndex++, g_ValueNameBuffer,
&cchValueName, NULL, &Type, NULL, &cbValueData);
if (RegError != ERROR_SUCCESS)
{
break;
}
// allocate memory for data
g_FileErrorStringID = IDS_EXPFILEERRSUCCESS;
pbValueData = LocalAlloc(LPTR, cbValueData+ExtraAllocLen(Type));
if (pbValueData)
{
if (RegQueryValueEx(hKey, g_ValueNameBuffer,
NULL, &Type, pbValueData, &cbValueData) !=
ERROR_SUCCESS)
{
g_FileErrorStringID = IDS_EXPFILEERRFILEWRITE;
}
else
{
//
// If cbValueName is zero, then this is the default value of
// the key, or the Windows 3.1 compatible key value.
//
if (cchValueName)
PutString(g_ValueNameBuffer);
else
PutChar(TEXT('@'));
PutChar(TEXT('='));
switch (Type)
{
case REG_SZ:
PutString((LPTSTR) pbValueData);
break;
case REG_DWORD:
if (cbValueData == sizeof(DWORD))
{
PutLiteral(s_DwordPrefix);
PutDword(*((LPDWORD) pbValueData), TRUE);
break;
}
// FALL THROUGH
case REG_BINARY:
default:
PutBinary((LPBYTE) pbValueData, Type, cbValueData);
break;
}
PutChar(TEXT('\n'));
}
LocalFree(pbValueData);
}
else
{
g_FileErrorStringID = IDS_EXPFILEERRFILEWRITE;
}
if (g_FileErrorStringID == IDS_EXPFILEERRFILEWRITE)
return;
if ( g_FileErrorStringID == IDS_EXPFILEERRSUCCESS )
{
bFlag = TRUE;
}
}
PutChar(TEXT('\n'));
if (RegError != ERROR_NO_MORE_ITEMS)
g_FileErrorStringID = IDS_EXPFILEERRREGENUM;
if ( bFlag == TRUE )
{
g_dwTotalKeysSaved++;
}
//
// Write out all of the subkeys and recurse into them.
//
//copy the existing key into a new buffer with enough room for the next key
nLenFullKey = lstrlen(lpFullKeyName);
nLenTempFullKey = nLenFullKey + MAXKEYNAME;
__try
{
lpTempFullKeyName = (LPTSTR) alloca(nLenTempFullKey * sizeof(TCHAR));
}
__except(GetExceptionCode() == STATUS_STACK_OVERFLOW)
{
_resetstkoflw();
return;
}
StringCchCopy(lpTempFullKeyName, nLenTempFullKey, lpFullKeyName);
lpSubKeyName = lpTempFullKeyName + nLenFullKey;
*lpSubKeyName++ = TEXT('\\');
*lpSubKeyName = 0;
EnumIndex = 0;
for(;;)
{
if ((RegError = RegEnumKey(hKey, EnumIndex++, lpSubKeyName, MAXKEYNAME-1)) != ERROR_SUCCESS)
break;
if(RegOpenKeyEx(hKey,lpSubKeyName,0,KEY_ENUMERATE_SUB_KEYS|KEY_QUERY_VALUE,&hSubKey) == ERROR_SUCCESS)
{
// reset the previous error
g_FileErrorStringID = IDS_EXPFILEERRSUCCESS;
PutBranch(hSubKey, lpTempFullKeyName);
RegCloseKey(hSubKey);
if ( bFlag == FALSE && g_FileErrorStringID == IDS_EXPFILEERRSUCCESS )
{
g_dwTotalKeysSaved++;
bFlag = TRUE;
}
if (g_FileErrorStringID == IDS_EXPFILEERRFILEWRITE)
return;
}
else
g_FileErrorStringID = IDS_EXPFILEERRREGOPEN;
}
if (RegError != ERROR_NO_MORE_ITEMS)
g_FileErrorStringID = IDS_EXPFILEERRREGENUM;
}
/*******************************************************************************
*
* PutLiteral
*
* DESCRIPTION:
* Writes a literal string to the registry file stream. No special handling
* is done for the string-- it is written out as is.
*
* PARAMETERS:
* lpLiteral, null-terminated literal to write to file.
*
*******************************************************************************/
VOID
NEAR PASCAL
PutLiteral(
LPCTSTR lpLiteral
)
{
while (*lpLiteral != 0)
PutChar(*lpLiteral++);
}
/*******************************************************************************
*
* PutString
*
* DESCRIPTION:
* Writes a string to the registry file stream. A string is surrounded by
* double quotes and some characters may be translated to escape sequences
* to enable a parser to read the string back in.
*
* PARAMETERS:
* lpString, null-terminated string to write to file.
*
*******************************************************************************/
VOID
NEAR PASCAL
PutString(
LPCTSTR lpString
)
{
TCHAR Char;
PutChar(TEXT('"'));
while ((Char = *lpString++) != 0) {
switch (Char) {
case TEXT('\\'):
case TEXT('"'):
PutChar(TEXT('\\'));
// FALL THROUGH
default:
PutChar(Char);
break;
}
}
PutChar(TEXT('"'));
}
/*******************************************************************************
*
* PutBinary
*
* DESCRIPTION:
* Writes a sequence of hexadecimal bytes to the registry file stream. The
* output is formatted such that it doesn't exceed a defined line length.
*
* PARAMETERS:
* lpBuffer, bytes to write to file.
* Type, value data type.
* cbBytes, number of bytes to write.
*
*******************************************************************************/
VOID
NEAR PASCAL
PutBinary(
CONST BYTE FAR* lpBuffer,
DWORD Type,
DWORD cbBytes
)
{
BOOL fFirstByteOnLine;
BYTE Byte;
// If we're writing one of the string formats that regedit doesn't write
// natively (but rather converts to a string of hex digits for streaming
// out), AND we're writing in downlevel/ANSI/REGEDIT4 format, we aren't
// going to write out the high byte of each (internally Unicode) character.
// So we will be writing half as many characters as the buffer byte size.
// if ((g_RegEditData.uExportFormat == FILE_TYPE_REGEDIT4) &&
// ((Type == REG_EXPAND_SZ) || (Type == REG_MULTI_SZ))) {
// cbBytes = cbBytes / 2;
// }
PutLiteral(s_HexPrefix);
if (Type != REG_BINARY) {
PutChar(TEXT('('));
PutDword(Type, FALSE);
PutChar(TEXT(')'));
}
PutChar(TEXT(':'));
fFirstByteOnLine = TRUE;
while (cbBytes--) {
if (s_FileIo.CurrentColumn > 75 && !fFirstByteOnLine) {
PutLiteral(s_FileLineBreak);
fFirstByteOnLine = TRUE;
}
if (!fFirstByteOnLine)
PutChar(TEXT(','));
Byte = *lpBuffer++;
// If we're writing one of the string formats that regedit doesn't
// write natively (REG_EXPAND_SZ and REG_MULTI_SZ values get converted
// to a string of hex digits for streaming out), AND we're writing in
// downlevel/ANSI/REGEDIT4 format, we don't want to write out the high
// byte of each (internally Unicode) character. So in those cases, we
// advance another byte to get to the next ANSI character. Yes, this
// will lose data on non-SBCS characters, but that's what you get for
// saving in the downlevel format.
// if ((g_RegEditData.uExportFormat == FILE_TYPE_REGEDIT4) &&
// ((Type == REG_EXPAND_SZ) || (Type == REG_MULTI_SZ))) {
// lpBuffer++;
// }
PutChar(g_HexConversion[Byte >> 4]);
PutChar(g_HexConversion[Byte & 0x0F]);
fFirstByteOnLine = FALSE;
}
}
/*******************************************************************************
*
* PutChar
*
* DESCRIPTION:
* Writes a 32-bit word to the registry file stream.
*
* PARAMETERS:
* Dword, dword to write to file.
*
*******************************************************************************/
VOID
NEAR PASCAL
PutDword(
DWORD Dword,
BOOL fLeadingZeroes
)
{
int CurrentNibble;
TCHAR Char;
BOOL fWroteNonleadingChar;
fWroteNonleadingChar = fLeadingZeroes;
for (CurrentNibble = 7; CurrentNibble >= 0; CurrentNibble--) {
Char = g_HexConversion[(Dword >> (CurrentNibble * 4)) & 0x0F];
if (fWroteNonleadingChar || Char != TEXT('0')) {
PutChar(Char);
fWroteNonleadingChar = TRUE;
}
}
//
// We need to write at least one character, so if we haven't written
// anything yet, just spit out one zero.
//
if (!fWroteNonleadingChar)
PutChar(TEXT('0'));
}
/*******************************************************************************
*
* PutChar
*
* DESCRIPTION:
* Writes one character to the registry file stream using an intermediate
* buffer.
*
* PARAMETERS:
* Char, character to write to file.
*
*******************************************************************************/
VOID
NEAR PASCAL
PutChar(
TCHAR Char
)
{
//
// Keep track of what column we're currently at. This is useful in cases
// such as writing a large binary registry record. Instead of writing one
// very long line, the other Put* routines can break up their output.
//
if (Char != TEXT('\n'))
s_FileIo.CurrentColumn++;
else {
//
// Force a carriage-return, line-feed sequence to keep things like, oh,
// Notepad happy.
//
PutChar(TEXT('\r'));
s_FileIo.CurrentColumn = 0;
}
s_FileIo.Buffer[s_FileIo.BufferOffset++] = Char;
if (s_FileIo.BufferOffset == SIZE_FILE_IO_BUFFER)
FlushIoBuffer();
}
/*******************************************************************************
*
* FlushIoBuffer
*
* DESCRIPTION:
* Flushes the contents of the registry file stream to the disk and resets
* the buffer pointer.
*
* PARAMETERS:
* (none).
*
*******************************************************************************/
VOID
NEAR PASCAL
FlushIoBuffer(
VOID
)
{
DWORD NumberOfBytesWritten;
if (s_FileIo.BufferOffset)
{
// if (g_RegEditData.uExportFormat == FILE_TYPE_REGEDIT4)
// {
// //
// // Convert Unicode to ANSI before writing.
// //
//
// int i;
//
// i = WideCharToMultiByte(
// CP_THREAD_ACP,
// 0,
// s_FileIo.Buffer,
// s_FileIo.BufferOffset,
// s_FileIo.ConversionBuffer,
// sizeof(s_FileIo.ConversionBuffer), // Number of bytes...
// NULL,
// NULL
// );
//
// if (!WRITEFILE(s_FileIo.hFile, s_FileIo.ConversionBuffer, i, &NumberOfBytesWritten)
// || (DWORD) i != NumberOfBytesWritten)
//
// g_FileErrorStringID = IDS_EXPFILEERRFILEWRITE;
// }
// else
{
//
// Write Unicode text
//
if (!WRITEFILE(s_FileIo.hFile, s_FileIo.Buffer, s_FileIo.BufferOffset * sizeof(WCHAR),
&NumberOfBytesWritten)
|| (DWORD) (s_FileIo.BufferOffset * sizeof(WCHAR)) != NumberOfBytesWritten)
{
g_FileErrorStringID = IDS_EXPFILEERRFILEWRITE;
}
}
}
s_FileIo.BufferOffset = 0;
}
// RegDeleteKeyRecursive
// DESCRIPTION:
// Adapted from \\kernel\razzle3,mvdm\wow32\wshell.c,WOWRegDeleteKey().
// The Windows 95 implementation of RegDeleteKey recursively deletes all
// the subkeys of the specified registry branch, but the NT implementation
// only deletes leaf keys.
LONG RegDeleteKeyRecursive(HKEY hKey,
LPCTSTR lpszSubKey)
/*++
Routine Description:
There is a significant difference between the Win3.1 and Win32
behavior of RegDeleteKey when the key in question has subkeys.
The Win32 API does not allow you to delete a key with subkeys,
while the Win3.1 API deletes a key and all its subkeys.
This routine is a recursive worker that enumerates the subkeys
of a given key, applies itself to each one, then deletes itself.
It specifically does not attempt to deal rationally with the
case where the caller may not have access to some of the subkeys
of the key to be deleted. In this case, all the subkeys which
the caller can delete will be deleted, but the api will still
return ERROR_ACCESS_DENIED.
Arguments:
hKey - Supplies a handle to an open registry key.
lpszSubKey - Supplies the name of a subkey which is to be deleted
along with all of its subkeys.
Return Value:
ERROR_SUCCESS - entire subtree successfully deleted.
ERROR_ACCESS_DENIED - given subkey could not be deleted.
--*/
{
DWORD i;
HKEY Key;
LONG Status;
DWORD ClassLength=0;
DWORD SubKeys;
DWORD MaxSubKey;
DWORD MaxClass;
DWORD Values;
DWORD MaxValueName;
DWORD MaxValueData;
DWORD SecurityLength;
FILETIME LastWriteTime;
LPTSTR NameBuffer;
//
// First open the given key so we can enumerate its subkeys
//
Status = RegOpenKeyEx(hKey,
lpszSubKey,
0,
KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE,
&Key);
if (Status != ERROR_SUCCESS)
{
//
// possibly we have delete access, but not enumerate/query.
// So go ahead and try the delete call, but don't worry about
// any subkeys. If we have any, the delete will fail anyway.
//
return(RegDeleteKey(hKey,lpszSubKey));
}
//
// Use RegQueryInfoKey to determine how big to allocate the buffer
// for the subkey names.
//
Status = RegQueryInfoKey(Key,
NULL,
&ClassLength,
0,
&SubKeys,
&MaxSubKey,
&MaxClass,
&Values,
&MaxValueName,
&MaxValueData,
&SecurityLength,
&LastWriteTime);
if ((Status != ERROR_SUCCESS) &&
(Status != ERROR_MORE_DATA) &&
(Status != ERROR_INSUFFICIENT_BUFFER))
{
RegCloseKey(Key);
return(Status);
}
NameBuffer = (LPTSTR) LocalAlloc(LPTR, (MaxSubKey + 1)*sizeof(TCHAR));
if (NameBuffer == NULL)
{
RegCloseKey(Key);
return(ERROR_NOT_ENOUGH_MEMORY);
}
//
// Enumerate subkeys and apply ourselves to each one.
//
i=0;
do
{
Status = RegEnumKey(Key,
i,
NameBuffer,
MaxSubKey+1);
if (Status == ERROR_SUCCESS)
{
Status = RegDeleteKeyRecursive(Key,NameBuffer);
}
if (Status != ERROR_SUCCESS)
{
//
// Failed to delete the key at the specified index. Increment
// the index and keep going. We could probably bail out here,
// since the api is going to fail, but we might as well keep
// going and delete everything we can.
//
++i;
}
} while ( (Status != ERROR_NO_MORE_ITEMS) &&
(i < SubKeys) );
LocalFree((HLOCAL) NameBuffer);
RegCloseKey(Key);
return(RegDeleteKey(hKey,lpszSubKey));
}