windows-nt/Source/XPSP1/NT/base/fs/utils/reg/regporte.c

2514 lines
56 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*******************************************************************************
*
* (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 "regeditp.h"
#include "reg1632.h"
#include "regdef.h"
#include "regdebug.h"
#include "regporte.h"
extern BOOL g_fSaveInDownlevelFormat;
TCHAR g_ValueNameBuffer[MAXVALUENAME_LENGTH];
BYTE g_ValueDataBuffer[MAXDATA_LENGTH * sizeof(TCHAR)];
// 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");
//
// New header is required for version 5.0 because the version detection code
// in Win 4.0 regedit was not quite correct (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");
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 ");
#define SIZE_FILE_IO_BUFFER 512
typedef struct _FILE_IO {
//
// Space for unicode/ansi conversions, assumes worst case
// where every unicode char is a double-byte char
//
CHAR ConversionBuffer[(SIZE_FILE_IO_BUFFER + 1)*sizeof(TCHAR)];
TCHAR Buffer[SIZE_FILE_IO_BUFFER + 1];
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;
BOOL s_fTreatFileAsUnicode = TRUE;
VOID
NEAR PASCAL
ImportWin31RegFile(
VOID
);
VOID
NEAR PASCAL
ImportNewerRegFile(
VOID
);
VOID
NEAR PASCAL
ParseHeader(
LPHKEY lphKey
);
VOID
NEAR PASCAL
ParseValuename(
HKEY hKey
);
VOID
NEAR PASCAL
ParseDefaultValue(
HKEY hKey
);
BOOL
NEAR PASCAL
ParseString(
LPTSTR lpString,
LPDWORD cbStringData
);
BOOL
NEAR PASCAL
ParseHexSequence(
LPBYTE lpHexData,
LPDWORD lpcbHexData
);
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;
TCHAR PrevChar;
HKEY hRootKey;
UINT Counter;
DWORD Result;
if ((lpSubKeyName = (LPTSTR) STRCHR(lpFullKeyName, TEXT('\\'))) != NULL) {
PrevChar = *lpSubKeyName;
*lpSubKeyName++ = 0;
}
CHARUPPERSTRING(lpFullKeyName);
hRootKey = NULL;
for (Counter = 0; Counter < NUMBER_REGISTRY_ROOTS; Counter++) {
if (STRCMP(g_RegistryRoots[Counter].lpKeyName, lpFullKeyName) == 0) {
hRootKey = g_RegistryRoots[Counter].hKey;
break;
}
}
if (hRootKey) {
Result = ERROR_CANTOPEN;
switch (uOperation) {
case ERK_CREATE:
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;
BOOL fNewRegistryFile;
#ifdef UNICODE
UINT Temp, i;
TCHAR StrToIntBuf[2];
LPCTSTR lp50Header;
#endif // UNICODE
DWORD cch;
TCHAR tchBuffer[MAX_PATH] = {0};
LPTSTR lpFilePart;
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) / 100;
s_FileIo.FileOffset = 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.
//
// Unfortunately, in the Unicode version, it's not so clean.
// SIZE_FILE_IO_BUFFER is in bytes, but s_FileIo.BufferOffset is in chars,
// so we divide according to whether we're reading ANSI or Unicode.
s_FileIo.BufferOffset = SIZE_FILE_IO_BUFFER / (s_fTreatFileAsUnicode ? sizeof(WCHAR) : 1);
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, brain damaged 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 brain-dead, 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);
Temp = _tcstoul(StrToIntBuf, NULL, 10);
// 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);
Temp = _tcstoul(StrToIntBuf, NULL, 10);
// 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 brain dead
// 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();
}
}
} // if (*lpHeader == 0)
else
g_FileErrorStringID = IDS_IMPFILEERRFORMATBAD;
#ifdef UNICODE
}
#endif // UNICODE
} // if (OPENREADFILE...
else {
{ TCHAR buff[250];
wsprintf(buff, TEXT("REGEDIT: CreateFile failed, GetLastError() = %d\n"), GetLastError());
OutputDebugString(buff);
}
s_FileIo.hFile = NULL;
g_FileErrorStringID = IDS_IMPFILEERRFILEOPEN;
}
exit_gracefully:
if (s_FileIo.hFile) {
CLOSEFILE(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;
}
while (TRUE) {
//
// 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 >= (sizeof(KeyName)/sizeof(TCHAR)) - 1) {
fSuccess = FALSE;
break;
}
KeyName[Index++] = Char;
}
if (fSuccess) {
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;
while (GetChar(&Char)) {
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 >= MAXDATA_LENGTH - 1) {
fSuccess = FALSE;
break;
}
((PTSTR)g_ValueDataBuffer)[Index++] = Char;
}
if (fSuccess) {
((PTSTR)g_ValueDataBuffer)[Index] = 0;
if (RegSetValue(hKey, KeyName, REG_SZ, (LPCTSTR)g_ValueDataBuffer,
Index*sizeof(TCHAR)) != ERROR_SUCCESS)
g_FileErrorStringID = IDS_IMPFILEERRREGSET;
}
}
}
//
// 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;
HKEY hUsersKey;
HKEY hKey;
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;
while (TRUE) {
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)
ParseDefaultValue(hKey);
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:
*
*******************************************************************************/
#define SIZE_FULL_KEYNAME (MAXKEYNAME + 40)
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)) {
case ERROR_CANTOPEN:
g_FileErrorStringID = IDS_IMPFILEERRREGOPEN;
break;
}
}
}
/*******************************************************************************
*
* ParseValuename
*
* DESCRIPTION:
*
* PARAMETERS:
*
*******************************************************************************/
VOID
NEAR PASCAL
ParseValuename(
HKEY hKey
)
{
DWORD Type;
DWORD dwResult = 0;
TCHAR ValueName[MAXVALUENAME_LENGTH];
DWORD cbData;
LPCTSTR lpPrefix;
cbData = sizeof(ValueName);
if (!ParseString(ValueName, &cbData))
goto ParseError;
SkipWhitespace();
if (!MatchChar(TEXT('=')))
goto ParseError;
SkipWhitespace();
//
// REG_SZ.
//
// "ValueName" = "string of text"
//
if (MatchChar(TEXT('"'))) {
// BUGBUG: Line continuations for strings?
cbData = MAXDATA_LENGTH;
if (!ParseString((PTSTR)g_ValueDataBuffer, &cbData) || !ParseEndOfLine())
goto ParseError;
Type = REG_SZ;
}
//
// REG_DWORD.
//
// "ValueName" = dword: 12345678
//
else if (MatchChar(s_DwordPrefix[0])) {
lpPrefix = &s_DwordPrefix[1];
while (*lpPrefix != 0)
if (!MatchChar(*lpPrefix++))
goto ParseError;
SkipWhitespace();
if (!ParseHexDword((LPDWORD) g_ValueDataBuffer) || !ParseEndOfLine())
goto ParseError;
Type = REG_DWORD;
cbData = sizeof(DWORD);
}
else if (MatchChar('-')) {
if (!ParseEndOfLine())
goto ParseError;
RegDeleteValue(hKey, ValueName);
return;
}
//
// 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++))
goto ParseError;
//
// 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(')')))
goto ParseError;
}
else
Type = REG_BINARY;
if (!MatchChar(TEXT(':')) || !ParseHexSequence(g_ValueDataBuffer, &cbData) ||
!ParseEndOfLine())
goto ParseError;
}
#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];
//
// 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_ACP,
0,
ValueName,
-1,
AnsiValueName,
MAXVALUENAME_LENGTH,
NULL,
NULL
);
if (RegSetValueExA(
hKey,
AnsiValueName,
0,
Type,
g_ValueDataBuffer,
cbData)
!= ERROR_SUCCESS)
g_FileErrorStringID = IDS_IMPFILEERRREGSET;
}
else {
#endif // UNICODE
dwResult = RegSetValueEx(hKey, ValueName, 0, Type, g_ValueDataBuffer, cbData);
if ( dwResult != ERROR_SUCCESS)
{
g_FileErrorStringID = IDS_IMPFILEERRREGSET;
SetLastError( dwResult );
}
#ifdef UNICODE
}
#endif // UNICODE
return;
ParseError:
SkipPastEndOfLine();
}
/*******************************************************************************
*
* ParseDefaultValue
*
* DESCRIPTION:
*
* PARAMETERS:
*
*******************************************************************************/
VOID
NEAR PASCAL
ParseDefaultValue(
HKEY hKey
)
{
BOOL fSuccess;
DWORD cbData;
fSuccess = FALSE;
SkipWhitespace();
if (MatchChar(TEXT('='))) {
SkipWhitespace();
if (MatchChar(TEXT('"'))) {
// BUGBUG: Line continuations for strings?
cbData = MAXDATA_LENGTH;
if (ParseString((PTSTR)g_ValueDataBuffer, &cbData) && ParseEndOfLine()) {
if (RegSetValue(hKey, NULL, REG_SZ, (LPCTSTR)g_ValueDataBuffer,
cbData) != ERROR_SUCCESS)
g_FileErrorStringID = IDS_IMPFILEERRREGSET;
fSuccess = TRUE;
}
}
}
if (!fSuccess)
SkipPastEndOfLine();
}
/*******************************************************************************
*
* ParseString
*
* DESCRIPTION:
*
* PARAMETERS:
*
*******************************************************************************/
BOOL
NEAR PASCAL
ParseString(
LPTSTR lpString,
LPDWORD lpcbStringData
)
{
TCHAR Char;
DWORD cbMaxStringData;
DWORD cbStringData;
cbMaxStringData = *lpcbStringData;
cbStringData = 1; // Account for the null terminator
while (GetChar(&Char)) {
if (cbStringData >= cbMaxStringData)
return FALSE;
switch (Char) {
case TEXT('\\'):
if (!GetChar(&Char))
return FALSE;
switch (Char) {
case TEXT('\\'):
*lpString++ = TEXT('\\');
break;
case TEXT('"'):
*lpString++ = TEXT('"');
break;
default:
DbgPrintf(("ParseString: Invalid escape sequence"));
return FALSE;
}
break;
case TEXT('"'):
*lpString = 0;
*lpcbStringData = cbStringData * sizeof(TCHAR);
return TRUE;
default:
if (IsNewLine(Char))
return FALSE;
*lpString++ = Char;
break;
}
cbStringData++;
}
return FALSE;
}
/*******************************************************************************
*
* ParseHexSequence
*
* DESCRIPTION:
*
* PARAMETERS:
*
*******************************************************************************/
BOOL
NEAR PASCAL
ParseHexSequence(
LPBYTE lpHexData,
LPDWORD lpcbHexData
)
{
DWORD cbHexData;
cbHexData = 0;
do {
if (cbHexData >= MAXDATA_LENGTH)
return FALSE;
SkipWhitespace();
if (MatchChar(TEXT('\\')) && !ParseEndOfLine())
return FALSE;
SkipWhitespace();
if (!ParseHexByte(lpHexData++))
break;
cbHexData++;
SkipWhitespace();
} while (MatchChar(TEXT(',')));
*lpcbHexData = cbHexData;
return TRUE;
}
/*******************************************************************************
*
* 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;
while (TRUE) {
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;
fComment = FALSE;
fFoundOneEndOfLine = FALSE;
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();
break;
}
}
return fFoundOneEndOfLine;
}
/*******************************************************************************
*
* 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
)
{
FILE_NUMBYTES NumberOfBytesRead;
UINT NewPercentage;
// If we're at the end of the buffer, read some more.
// SIZE_FILE_IO_BUFFER is in bytes, but s_FileIo.BufferOffset is in chars,
// so we multiply according to whether we're reading ANSI or Unicode.
if ((s_FileIo.BufferOffset * (s_fTreatFileAsUnicode ? sizeof(WCHAR) : 1)) == SIZE_FILE_IO_BUFFER) {
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;
#ifdef UNICODE
i = MultiByteToWideChar(
CP_ACP,
MB_PRECOMPOSED,
s_FileIo.ConversionBuffer,
NumberOfBytesRead,
s_FileIo.Buffer,
SIZE_FILE_IO_BUFFER
);
#else
lstrcpyn(s_FileIo.Buffer, s_FileIo.ConversionBuffer, NumberOfBytesRead + 1);
i = lstrlen(s_FileIo.Buffer);
#endif //UNICODE
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;
// no UI
// 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)
DbgPrintf(("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
PASCAL
ExportWinNT50RegFile(
LPTSTR lpFileName,
LPTSTR lpSelectedPath
)
{
HKEY hKey;
TCHAR SelectedPath[SIZE_SELECTED_PATH];
g_FileErrorStringID = IDS_EXPFILEERRSUCCESS;
if (lpSelectedPath != NULL && EditRegistryKey(&hKey, lpSelectedPath,
ERK_OPEN) != ERROR_SUCCESS) {
g_FileErrorStringID = IDS_EXPFILEERRBADREGPATH;
return;
}
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) {
STRCPY(SelectedPath, lpSelectedPath);
PutBranch(hKey, SelectedPath);
}
else {
STRCPY(SelectedPath,
g_RegistryRoots[INDEX_HKEY_LOCAL_MACHINE].lpKeyName);
PutBranch(HKEY_LOCAL_MACHINE, SelectedPath);
STRCPY(SelectedPath,
g_RegistryRoots[INDEX_HKEY_USERS].lpKeyName);
PutBranch(HKEY_USERS, SelectedPath);
}
FlushIoBuffer();
CLOSEFILE(s_FileIo.hFile);
}
else
g_FileErrorStringID = IDS_EXPFILEERRFILEOPEN;
if (lpSelectedPath != NULL)
RegCloseKey(hKey);
}
/*******************************************************************************
*
* ExportWin40RegFile
*
* DESCRIPTION:
* This function is only kept around to export old, ANSI, regedit 4 .reg
* files. Don't touch it except to fix bugs. Meddling with this code
* path will result in .reg files that can't be read by older verions
* of regedit, which is the whole reason this code path is here. Meddle
* with ExportWinNT50RegFile if you want to break backwards compatibility.
*
* PARAMETERS:
*
*******************************************************************************/
VOID
PASCAL
ExportWin40RegFile(
LPTSTR lpFileName,
LPTSTR lpSelectedPath
)
{
HKEY hKey;
TCHAR SelectedPath[SIZE_SELECTED_PATH];
g_FileErrorStringID = IDS_EXPFILEERRSUCCESS;
if (lpSelectedPath != NULL && EditRegistryKey(&hKey, lpSelectedPath,
ERK_OPEN) != ERROR_SUCCESS) {
g_FileErrorStringID = IDS_EXPFILEERRBADREGPATH;
return;
}
if (OPENWRITEFILE(lpFileName, s_FileIo.hFile)) {
s_FileIo.BufferOffset = 0;
s_FileIo.CurrentColumn = 0;
PutLiteral(s_Win40RegFileHeader);
if (lpSelectedPath != NULL) {
STRCPY(SelectedPath, lpSelectedPath);
PutBranch(hKey, SelectedPath);
}
else {
STRCPY(SelectedPath,
g_RegistryRoots[INDEX_HKEY_LOCAL_MACHINE].lpKeyName);
PutBranch(HKEY_LOCAL_MACHINE, SelectedPath);
STRCPY(SelectedPath,
g_RegistryRoots[INDEX_HKEY_USERS].lpKeyName);
PutBranch(HKEY_USERS, SelectedPath);
}
FlushIoBuffer();
CLOSEFILE(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
)
{
LONG RegError;
DWORD EnumIndex;
DWORD cbValueName;
DWORD cbValueData;
DWORD Type;
LPTSTR lpSubKeyName;
int MaximumSubKeyLength;
HKEY hSubKey;
//
// Write out the section header.
//
PutChar(TEXT('['));
PutLiteral(lpFullKeyName);
PutLiteral(TEXT("]\n"));
//
// Write out all of the value names and their data.
//
EnumIndex = 0;
while (TRUE) {
cbValueName = sizeof(g_ValueNameBuffer);
cbValueData = MAXDATA_LENGTH;
if ((RegError = RegEnumValue(hKey, EnumIndex++, g_ValueNameBuffer,
&cbValueName, NULL, &Type, g_ValueDataBuffer, &cbValueData))
!= ERROR_SUCCESS)
break;
//
// If cbValueName is zero, then this is the default value of
// the key, or the Windows 3.1 compatible key value.
//
if (cbValueName)
PutString(g_ValueNameBuffer);
else
PutChar(TEXT('@'));
PutChar(TEXT('='));
switch (Type) {
case REG_SZ:
PutString((LPTSTR) g_ValueDataBuffer);
break;
case REG_DWORD:
if (cbValueData == sizeof(DWORD)) {
PutLiteral(s_DwordPrefix);
PutDword(*((LPDWORD) g_ValueDataBuffer), TRUE);
break;
}
// FALL THROUGH
case REG_BINARY:
default:
PutBinary((LPBYTE) g_ValueDataBuffer, Type, cbValueData);
break;
}
PutChar(TEXT('\n'));
if (g_FileErrorStringID == IDS_EXPFILEERRFILEWRITE)
return;
}
PutChar(TEXT('\n'));
if (RegError != ERROR_NO_MORE_ITEMS)
g_FileErrorStringID = IDS_EXPFILEERRREGENUM;
//
// Write out all of the subkeys and recurse into them.
//
lpSubKeyName = lpFullKeyName + STRLEN(lpFullKeyName);
*lpSubKeyName++ = TEXT('\\');
MaximumSubKeyLength = MAXKEYNAME - STRLEN(lpSubKeyName);
EnumIndex = 0;
while (TRUE) {
if ((RegError = RegEnumKey(hKey, EnumIndex++, lpSubKeyName,
MaximumSubKeyLength)) != ERROR_SUCCESS)
break;
if(RegOpenKeyEx(hKey,lpSubKeyName,0,KEY_ENUMERATE_SUB_KEYS|KEY_QUERY_VALUE,&hSubKey) == ERROR_SUCCESS) {
PutBranch(hSubKey, lpFullKeyName);
RegCloseKey(hSubKey);
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;
#ifdef UNICODE
// 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_fSaveInDownlevelFormat &&
((Type == REG_EXPAND_SZ) || (Type == REG_MULTI_SZ))) {
cbBytes = cbBytes / 2;
}
#endif
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++;
#ifdef UNICODE
// 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_fSaveInDownlevelFormat &&
((Type == REG_EXPAND_SZ) || (Type == REG_MULTI_SZ))) {
lpBuffer++;
}
#endif
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 nTemp;
int CurrentNibble;
TCHAR Char;
BOOL fWroteNonleadingChar;
fWroteNonleadingChar = fLeadingZeroes;
for (CurrentNibble = 7; CurrentNibble >= 0; CurrentNibble--) {
nTemp = Dword;
Char = g_HexConversion[(nTemp >> (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
)
{
FILE_NUMBYTES NumberOfBytesWritten;
if (s_FileIo.BufferOffset) {
if (g_fSaveInDownlevelFormat)
{
//
// Convert Unicode to ANSI before writing.
//
int i;
#ifdef UNICODE
i = WideCharToMultiByte(
CP_ACP,
0,
s_FileIo.Buffer,
s_FileIo.BufferOffset,
s_FileIo.ConversionBuffer,
sizeof(s_FileIo.ConversionBuffer),
NULL,
NULL
);
#else
lstrcpyn(s_FileIo.ConversionBuffer, s_FileIo.Buffer, s_FileIo.BufferOffset + 1);
i = lstrlen(s_FileIo.ConversionBuffer);
#endif //UNICODE
if (!WRITEFILE(s_FileIo.hFile, s_FileIo.ConversionBuffer, i,
&NumberOfBytesWritten) || (FILE_NUMBYTES) i !=
NumberOfBytesWritten)
g_FileErrorStringID = IDS_EXPFILEERRFILEWRITE;
}
else
{
//
// Write Unicode text
//
if (!WRITEFILE(s_FileIo.hFile, s_FileIo.Buffer, s_FileIo.BufferOffset * sizeof(WCHAR),
&NumberOfBytesWritten) || (FILE_NUMBYTES) (s_FileIo.BufferOffset * sizeof(WCHAR)) !=
NumberOfBytesWritten)
g_FileErrorStringID = IDS_EXPFILEERRFILEWRITE;
}
}
s_FileIo.BufferOffset = 0;
}