/*******************************************************************************
*
*  (C) COPYRIGHT MICROSOFT CORP., 1993-2000
*
*  TITLE:       REGPORTE.C
*
*  VERSION:     5.00
*
*  AUTHOR:      Tracy Sharpe
*
*  DATE:        06 Apr 1994
*
*  File import and export engine routines for the Registry Editor.
*
*******************************************************************************/

#include "pch.h"
#include "regresid.h"
#include "reg1632.h"
#include "regedit.h"
#include "regkey.h"
#include "regfile.h"
#include "regedit.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, PREDEFINE_KEY_CLASSES_ROOT,
    TEXT("HKEY_CURRENT_USER"), HKEY_CURRENT_USER, PREDEFINE_KEY_CURRENT_USER,
    TEXT("HKEY_LOCAL_MACHINE"), HKEY_LOCAL_MACHINE, PREDEFINE_KEY_LOCAL_MACHINE,
    TEXT("HKEY_USERS"), HKEY_USERS, PREDEFINE_KEY_USERS,
    TEXT("HKEY_CURRENT_CONFIG"), HKEY_CURRENT_CONFIG, PREDEFINE_KEY_CURRENT_CONFIG,
    TEXT("HKEY_DYN_DATA"), HKEY_DYN_DATA, 0
};

const TCHAR s_RegistryHeader[] = TEXT("REGEDIT");

const TCHAR s_OldWin31RegFileRoot[] = TEXT(".classes");

const TCHAR s_Win40RegFileHeader[] = TEXT("REGEDIT4\n\n");

#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  ");

extern BOOL g_fMultiLineStrings;

//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;

BOOL s_fTreatFileAsUnicode = TRUE;

VOID ImportWin31RegFile(HTREEITEM hComputerItem);

VOID
NEAR PASCAL
ImportNewerRegFile(HTREEITEM hComputerItem);

VOID ParseHeader(LPHKEY lphKey, HTREEITEM hComputerItem);

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
    );

//------------------------------------------------------------------------------
//  Regedit_GetRootKeyFromComputer
//
//  DESCRIPTION: Invokes the security editor for the currently selected item.
//
//  PARAMETERS:  hWnd - handle to the current window
//------------------------------------------------------------------------------
HKEY Regedit_GetRootKeyFromComputer(HTREEITEM hComputerItem, PTSTR pszFullKeyName)
{
    HKEY hRootKey = NULL;

    if (hComputerItem == NULL)
    {
        int i;
        for (i = 0; i < NUMBER_REGISTRY_ROOTS; i++)
        {
            if (STRCMP(g_RegistryRoots[i].lpKeyName, pszFullKeyName) == 0)
            {
                hRootKey = g_RegistryRoots[i].hKey;
                break;
            }

        }
    }
    else
    {
        TCHAR acComputerName[MAXKEYNAME];
        TV_ITEM TVItem;

        // walk through each of the registry roots to see if this key
        // belongs to it
        HTREEITEM hRegistryRoot =
            TreeView_GetChild(g_RegEditData.hKeyTreeWnd, hComputerItem);

        TVItem.mask = TVIF_TEXT | TVIF_PARAM;
        TVItem.hItem = hRegistryRoot;
        TVItem.pszText = acComputerName;
        TVItem.cchTextMax = sizeof(acComputerName)/sizeof(TCHAR);

        while (hRegistryRoot != NULL)
        {
            TreeView_GetItem(g_RegEditData.hKeyTreeWnd, &TVItem);

            if (STRCMP(acComputerName, pszFullKeyName) == 0)
            {
                hRootKey = (HKEY)TVItem.lParam;
                break;
            }

            hRegistryRoot =
                TreeView_GetNextSibling(g_RegEditData.hKeyTreeWnd, hRegistryRoot);
            TVItem.hItem = hRegistryRoot;
        }
    }

    return hRootKey;
}

/*******************************************************************************
*
*  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 EditRegistryKey(HTREEITEM hComputerItem, LPHKEY lphKey, LPTSTR lpFullKeyName, UINT uOperation)
{
    LPTSTR lpSubKeyName;
    TCHAR PrevChar;
    UINT Counter;
    DWORD Result;
    HKEY hRootKey = NULL;

    if ((lpSubKeyName = (LPTSTR) STRCHR(lpFullKeyName, TEXT('\\'))) != NULL) {

        PrevChar = *lpSubKeyName;
        *lpSubKeyName++ = 0;

    }

    CHARUPPERSTRING(lpFullKeyName);

    hRootKey = Regedit_GetRootKeyFromComputer(hComputerItem, lpFullKeyName);

    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;

}

/*******************************************************************************
*
*  ImportRegFile
*
*  DESCRIPTION:
*
*  PARAMETERS:
*     lpFileName, address of name of file to be imported.
*
*******************************************************************************/

VOID
PASCAL
ImportRegFile(HWND hWnd, LPTSTR lpFileName, HTREEITEM hComputerItem)
{

    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.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(hComputerItem);
            }

        } // 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(hComputerItem);
                    }
                    else {
                        g_ImportFileVersion = 0x0310;
                        ImportWin31RegFile(hComputerItem);
                    }
                }
            }
            else if (!hWnd)
            {
                // only registry script files can be imported without the
                // regedit window open
                g_FileErrorStringID = IDS_IMPFILEERRNOTASCRPT;
            }
            else if (!hComputerItem || (hComputerItem ==
                RegEdit_GetComputerItem(TreeView_GetSelection(g_RegEditData.hKeyTreeWnd))))
            {
                // It's not a registry file, but since the selected key is on the computer
                // they choose, try to import it as a binary registy key (regedt32 type)

                // remove the progress dialog
                if (g_hRegProgressWnd != NULL)
                {
                    EnableWindow(hWnd, TRUE);
                    DestroyWindow(g_hRegProgressWnd);
                    g_hRegProgressWnd = NULL;
                }

                RestoreBinaryKeyFile(hWnd, lpFileName);
            }
            else
            {
                g_FileErrorStringID = IDS_IMPFILEERRFORMATBAD;
            }
#ifdef UNICODE
        }
#endif // UNICODE

    } // if (OPENREADFILE...

    else {
        { TCHAR buff[250];
          wsprintf(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) {
        CLOSEFILE(s_FileIo.hFile);
    }

}

//------------------------------------------------------------------------------
//  ImportBinaryKeyFile
//
//  DESCRIPTION:  Loads a binary key file at the currently selected key
//                This is the format of regedt32.exe saved keys
//
//  PARAMETERS:   LPTSTR lpFileName - file with the binary key
//------------------------------------------------------------------------------
void RestoreBinaryKeyFile(HWND hWnd, LPTSTR lpFileName)
{
    TCHAR achKeyName[MAXKEYNAME];
    HTREEITEM hSelectedTreeItem = TreeView_GetSelection(g_RegEditData.hKeyTreeWnd);
    KeyTree_GetKeyName(hSelectedTreeItem, achKeyName, ARRAYSIZE(achKeyName));

    // confirm that they want to replace the registry key
    if (InternalMessageBox(g_hInstance, hWnd,
        MAKEINTRESOURCE(IDS_CONFIRMRESTOREKEY), MAKEINTRESOURCE(IDS_CONFIRMRESKEYTITLE),
        MB_ICONQUESTION | MB_YESNO , achKeyName) == IDYES)
    {
        HRESULT hr;

        RegEdit_SetPrivilege(SE_RESTORE_NAME, TRUE);

        // attempt to load the file as a registry key
        if ((hr = RegRestoreKey(g_RegEditData.hCurrentSelectionKey, lpFileName,
            REG_FORCE_RESTORE)) != ERROR_SUCCESS)
        {
            switch (hr)
            {
            case ERROR_PRIVILEGE_NOT_HELD:
                g_FileErrorStringID = IDS_IMPFILEERRNOPRIV;
                break;

            case ERROR_FILE_NOT_FOUND:
                g_FileErrorStringID = IDS_IMPFILEERRNOFILE;
                break;

            case ERROR_INVALID_HANDLE:
                g_FileErrorStringID = IDS_IMPFILEERRINVALID;
                break;

            default:
                g_FileErrorStringID = IDS_IMPFILEERRFORMATBAD;
                break;
            }
        }

        RegEdit_SetPrivilege(SE_RESTORE_NAME, FALSE);
    }
    else
    {
        g_FileErrorStringID = IDS_IMPFILEERRORCANCEL;
    }
}


/*******************************************************************************
*
*  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: HTREEITEM hComputerItem -  computer item to receive imported key
*
*******************************************************************************/

VOID ImportWin31RegFile(HTREEITEM hComputerItem)
{

    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.
    //
    hKey = Regedit_GetRootKeyFromComputer(hComputerItem,
        g_RegistryRoots[INDEX_HKEY_CLASSES_ROOT].lpKeyName);

    if (RegOpenKeyEx(hKey, 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)
            {
                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(HTREEITEM hComputerItem)
{

    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)
        DebugPrintf(("Unable to open HKEY_LOCAL_MACHINE\n\r"));
    if (hUsersKey == NULL)
        DebugPrintf(("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, hComputerItem);

                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 ParseHeader(LPHKEY lphKey, HTREEITEM hComputerItem)
{

    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(hComputerItem, 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;
    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,
                        MAXVALUENAME_LENGTH,
                        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;

                    case TEXT('r'):
                        if(g_fMultiLineStrings) 
                        {
                            *psz++ = TEXT('\r');
                            break;
                        }
                        else 
                        {
                            DebugPrintf(("ParseString:  Invalid escape sequence"));
                            return FALSE;
                        }

                    case TEXT('n'):
                        if(g_fMultiLineStrings) 
                        {
                            *psz++ = TEXT('\n');
                            break;
                        }
                        else 
                        {
                            DebugPrintf(("ParseString:  Invalid escape sequence"));
                            return FALSE;
                        }

                    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;

    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;
    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
    )
{

    FILE_NUMBYTES 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,
                        SIZE_FILE_IO_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;
    TCHAR SelectedPath[SIZE_SELECTED_PATH];
    HTREEITEM hSelectedTreeItem = TreeView_GetSelection(g_RegEditData.hKeyTreeWnd);

    g_FileErrorStringID = IDS_EXPFILEERRSUCCESS;

    if (lpSelectedPath != NULL && EditRegistryKey(RegEdit_GetComputerItem(hSelectedTreeItem),
        &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
        {
            HTREEITEM hComputerItem = RegEdit_GetComputerItem(hSelectedTreeItem);

            STRCPY(SelectedPath,
                g_RegistryRoots[INDEX_HKEY_LOCAL_MACHINE].lpKeyName);

            PutBranch(Regedit_GetRootKeyFromComputer(hComputerItem, SelectedPath), SelectedPath);

            STRCPY(SelectedPath,
                g_RegistryRoots[INDEX_HKEY_USERS].lpKeyName);
            PutBranch(Regedit_GetRootKeyFromComputer(hComputerItem, SelectedPath), SelectedPath);

        }

        FlushIoBuffer();

        CLOSEFILE(s_FileIo.hFile);

    }

    else
        g_FileErrorStringID = IDS_EXPFILEERRFILEOPEN;

    if (lpSelectedPath != NULL)
        RegCloseKey(hKey);

}


//------------------------------------------------------------------------------
// ExportRegedt32File
//
// DESCRIPTION: This function exports file in the format of Regedt32.
//              The binary is key is stored to a file without any key path info
//
// PARAMETERS:  LPTSTR lpFileName
//              LPTSTR lpSelectedPath
//------------------------------------------------------------------------------
void ExportRegedt32File(LPTSTR lpFileName, LPTSTR lpSelectedPath)
{
    HKEY hKey = NULL;
    HTREEITEM hSelectedTreeItem = TreeView_GetSelection(g_RegEditData.hKeyTreeWnd);

    g_FileErrorStringID = IDS_EXPFILEERRSUCCESS;

    if (lpSelectedPath == NULL || EditRegistryKey(RegEdit_GetComputerItem(hSelectedTreeItem),
        &hKey, lpSelectedPath, ERK_OPEN) == ERROR_SUCCESS)
    {
        HRESULT hr;

        RegEdit_SetPrivilege(SE_BACKUP_NAME, TRUE);

        //
        // If a file with the same name already exists, we need to delete
        // it ourselves before calling RegSaveKey or that call will fail.
        //
        DeleteFile(lpFileName);

        if ((hr = RegSaveKey(hKey, lpFileName, NULL)) != ERROR_SUCCESS)
        {
            switch (hr)
            {
            case ERROR_PRIVILEGE_NOT_HELD:
                g_FileErrorStringID = IDS_EXPFILEERRNOPRIV;
                break;

            case ERROR_INVALID_HANDLE:
                g_FileErrorStringID = IDS_EXPFILEERRINVALID;
                break;

            default:
                g_FileErrorStringID = IDS_EXPFILEERRFILEWRITE;
                break;
            }

            // RegSaveKey created an empty file
            DeleteFile(lpFileName);
        }

        RegEdit_SetPrivilege(SE_BACKUP_NAME, FALSE);
    }
    else
    {
        g_FileErrorStringID = IDS_EXPFILEERRBADREGPATH;
    }
}


/*******************************************************************************
*
*  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 ExportWin40RegFile(LPTSTR lpFileName, LPTSTR lpSelectedPath)
{
    HKEY hKey;
    TCHAR SelectedPath[SIZE_SELECTED_PATH];
    HTREEITEM hSelectedTreeItem = TreeView_GetSelection(g_RegEditData.hKeyTreeWnd);

    g_FileErrorStringID = IDS_EXPFILEERRSUCCESS;

    if (lpSelectedPath != NULL && EditRegistryKey(RegEdit_GetComputerItem(hSelectedTreeItem),
        &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 cchValueName;
    DWORD cbValueData;
    DWORD Type;
    LPTSTR lpSubKeyName;
    HKEY hSubKey;
    int nLenFullKey;
    LPTSTR lpTempFullKeyName;

    //
    //  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)
    {
        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
        pbValueData =  LocalAlloc(LPTR, cbValueData+ExtraAllocLen(Type));
        if (pbValueData)
        {
            if (RegEdit_QueryValueEx(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;

    }

    PutChar(TEXT('\n'));

    if (RegError != ERROR_NO_MORE_ITEMS)
        g_FileErrorStringID = IDS_EXPFILEERRREGENUM;

    //
    //  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);
    lpTempFullKeyName = (LPTSTR) alloca( (nLenFullKey+MAXKEYNAME)*sizeof(TCHAR));
    lstrcpy(lpTempFullKeyName, lpFullKeyName);
    lpSubKeyName = lpTempFullKeyName + nLenFullKey;
    *lpSubKeyName++ = TEXT('\\');
    *lpSubKeyName = 0;

    EnumIndex = 0;

    while (TRUE) {

        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) {

            PutBranch(hSubKey, lpTempFullKeyName);

            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('\r'):
                if(g_fMultiLineStrings) 
                {
                    PutChar('\\');
                    PutChar('r');
                }
                else 
                {
                    PutChar(Char);
                }
                break;

            case TEXT('\n'):
                if(g_fMultiLineStrings) 
                {
                    PutChar('\\');
                    PutChar('n');
                }
                else 
                {
                    PutChar(Char);
                }
                break;
            
            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
    )
{

    FILE_NUMBYTES 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),
                    NULL,
                    NULL
                    );

            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;

}
