/*++

Copyright (c) 1996 Microsoft Corporation

Module Name:

    merge.c

Abstract:

    Registry merge code

Author:

    Jim Schmidt (jimschm) 17-Feb-1997

Revision History:

    jimschm     23-Sep-1998 String mapping mechanism
    jimschm     24-Mar-1998 Added more complex hkcr processing

--*/

#include "pch.h"
#include "mergep.h"

static PCTSTR g_InfFileName;

BOOL g_ProcessRenameTable = FALSE;
DWORD g_ProgressBarCounter;
HKEY g_DuHandle;

BOOL
pForceCopy (
    HINF InfFile
    );

BOOL
pForceCopyFromMemDb (
    VOID
    );

BOOL
pCreateRenameTable (
    IN  HINF InfFile,
    OUT PVOID *RenameTablePtr
    );

BOOL
pProcessRenameTable (
    IN PVOID RenameTable
    );

BOOL
pSpecialConversion (
    IN  HINF InfFile,
    IN  PCTSTR User,
    IN  PVOID RenameTable
    );

BOOL
pProcessSuppressList (
    IN  HINF InfFile,
    IN  PCTSTR SectionName
    );

BOOL
pProcessHardwareSuppressList (
    IN  HINF InfFile
    );

BOOL
pSuppressNTDefaults (
    IN  HINF InfFile,
    IN  PCTSTR SectionName
    );

BOOL
pDontCombineWithDefaults (
    IN  HINF InfFile,
    IN  PCTSTR SectionName
    );

BOOL
pForceNTDefaults (
    IN  HINF InfFile,
    IN  PCTSTR SectionName
    );

BOOL
pForceNTDefaultsHack (
    IN  HINF InfFile,
    IN  PCTSTR SectionName
    );

BOOL
pMergeWin95WithUser (
    IN  PVOID RenameTable
    );

BOOL
pSpecialConversionNT (
    IN  HINF InfFile,
    IN  PCTSTR User,
    IN  BOOL PerUser
    );

BOOL
pMergeNTDefaultsWithUser (
    IN  HINF InfFile
    );

BOOL
pCopyWin95ToSystem (
    VOID
    );

BOOL
pMergeWin95WithSystem (
    VOID
    );

BOOL
pDeleteAfterMigration (
    IN HINF InfFile
    );

FILTERRETURN
SuppressFilter95 (
    IN      CPDATAOBJECT SrcObjectPtr,
    IN      CPDATAOBJECT DestObjectPtr,     OPTIONAL
    IN      FILTERTYPE FilterType,
    IN      PVOID DontCare
    );

//
// Globals
//

POOLHANDLE g_TempPool;
POOLHANDLE g_RenamePool;
PMAPSTRUCT g_CompleteMatchMap;
PMAPSTRUCT g_SubStringMap;

BOOL
WINAPI
Merge_Entry (
    IN HINSTANCE hinstDLL,
    IN DWORD dwReason,
    IN PVOID lpv
    )

/*++

Routine Description:

  DllMain is called after the C runtime is initialized, and its purpose
  is to initialize the globals for this process.

Arguments:

  hinstDLL  - (OS-supplied) Instance handle for the DLL
  dwReason  - (OS-supplied) Type of initialization or termination
  lpv       - (OS-supplied) Unused

Return Value:

  TRUE because DLL always initializes properly.

--*/

{
    switch (dwReason)
    {
    case DLL_PROCESS_ATTACH:
        if(!pSetupInitializeUtils()) {
            return FALSE;
        }
        g_CompleteMatchMap = CreateStringMapping();
        g_SubStringMap = CreateStringMapping();
        break;


    case DLL_PROCESS_DETACH:
        DestroyStringMapping (g_CompleteMatchMap);
        DestroyStringMapping (g_SubStringMap);
        pSetupUninitializeUtils();
        break;
    }

    return TRUE;
}



BOOL
MergeRegistry (
    IN  PCTSTR FileName,
    IN  PCTSTR User
    )
{
    HINF hInf;                      // handle to the INF being processed
    BOOL b = FALSE;                 // Return value
    PVOID RenameTable = NULL;
    BOOL LogonAccount = FALSE;
    BOOL DefaultUserAccount = FALSE;

    g_ProgressBarCounter = 0;

    //
    // Open the INF
    //

    g_InfFileName = FileName;
    hInf = InfOpenInfFile (FileName);

    if (hInf == INVALID_HANDLE_VALUE) {
        LOG ((LOG_ERROR, "MergeRegistry: SetupOpenInfFile failed for %s", FileName));
        return FALSE;
    }

    g_TempPool = PoolMemInitNamedPool ("Merge: temp pool");
    g_RenamePool = PoolMemInitNamedPool ("Merge: Rename pool");

    if (!g_TempPool || !g_RenamePool) {
        DEBUGMSG ((DBG_ERROR, "MergeRegistry: Can't init pool"));
        goto c0;
    }

    PoolMemSetMinimumGrowthSize (g_TempPool, 16384);

    if (User) {
        SetCurrentUserW (g_FixedUserName);
    }

    //
    // Perform forced copy of Win95 registry, build rename table,
    // execute registry value conversions, convert types, and mark
    // specified keys as suppressed.
    //

    if (!pForceCopy (hInf)) {
        goto c0;
    }

    if (!User) {
        if (!pForceCopyFromMemDb ()) {
            goto c0;
        }
    }

    if (!pCreateRenameTable (hInf, &RenameTable)) {
        goto c0;
    }

    //
    // Identify the logon account or default user account
    //

    if (User) {
        if (*User == 0 || StringIMatch (User, S_DOT_DEFAULT)) {

            DEBUGMSG ((DBG_NAUSEA, "The logon user account is indicated by user name '%s'", User));
            LogonAccount = TRUE;

        } else if (StringIMatch (User, S_DEFAULT_USER)) {

            DEBUGMSG ((DBG_NAUSEA, "The default user account template is indicated by user name '%s'", User));
            DefaultUserAccount = TRUE;
        }
    }

    //
    // Prepare flags for registry merging
    //

    if (!pProcessSuppressList (hInf, S_MERGE_WIN9X_SUPPRESS)) {
        goto c0;
    }

    if (User) {
        //
        // These functions read usermig.inf and set flags
        // for the keys, key trees or values, as specified in
        // the INF.
        //

        if (!pSuppressNTDefaults (hInf, S_MERGE_WINNT_SUPPRESS)) {
            goto c0;
        }

        if (!pDontCombineWithDefaults (hInf, S_MERGE_DONT_COMBINE_WITH_DEFAULT)) {
            goto c0;
        }

        if (!pForceNTDefaults (hInf, S_MERGE_FORCE_NT_DEFAULTS)) {
            goto c0;
        }

        if (LogonAccount) {
            if (!pProcessSuppressList (hInf, S_MERGE_WIN9X_SUPPRESS_LU)) {
                goto c0;
            }
        }

        if (DefaultUserAccount) {
            if (!pProcessSuppressList (hInf, S_MERGE_WIN9X_SUPPRESS_DU)) {
                goto c0;
            }
        }

        g_DuHandle = OpenRegKeyStr (L"hklm\\" S_MAPPED_DEFAULT_USER_KEY);

    } else {
        if (!pForceNTDefaults (hInf, S_MERGE_FORCE_NT_DEFAULTS)) {
            goto c0;
        }

        if (!pProcessHardwareSuppressList (hInf)) {
            goto c0;
        }
    }

    if (!pSpecialConversion (hInf, User, RenameTable)) {
        goto c0;
    }

    //
    // Perform merge
    //

    if (User) {
        // User merge
        if (!pMergeWin95WithUser (RenameTable)) {
            goto c0;
        }

        if (!pSpecialConversionNT (hInf, User, TRUE)) {
            goto c0;
        }

        if (!LogonAccount && !DefaultUserAccount) {
            // Non-default user, not logon prompt account
            if (!pMergeNTDefaultsWithUser (hInf)) {
                goto c0;
            }
        }
    }
    else {
        // Workstation merge
        if (!pCopyWin95ToSystem()) {
            goto c0;
        }

        if (!pForceNTDefaultsHack (hInf, S_MERGE_FORCE_NT_DEFAULTS)) {
            goto c0;
        }

        if (!pMergeWin95WithSystem()) {
            goto c0;
        }

        if (!CopyHardwareProfiles (hInf)) {
            goto c0;
        }
        TickProgressBar ();

        if (!pSpecialConversionNT (hInf, NULL, FALSE)) {
            goto c0;
        }
        TickProgressBar ();
    }

    g_ProcessRenameTable = TRUE;

    b = pProcessRenameTable (RenameTable);
    TickProgressBar ();

    g_ProcessRenameTable = FALSE;

    //
    // Once we are done with the complete registry merge, process the special section
    // [Delete After Migration]
    //

    if (!pDeleteAfterMigration (hInf)) {
        LOG((LOG_ERROR,"Registry Merge: Delete After Migration failed."));
        goto c0;
    }

c0:
    if (RenameTable) {
        pSetupStringTableDestroy (RenameTable);
    }

    if (User) {
        SetCurrentUserW (NULL);
    }

    if (g_TempPool) {
        PoolMemDestroyPool (g_TempPool);
    }
    if (g_RenamePool) {
        PoolMemDestroyPool (g_RenamePool);
    }

    if (g_DuHandle) {
        CloseRegKey (g_DuHandle);
        g_DuHandle = NULL;
    }

    InfCloseInfFile (hInf);

    return b;
}


PTSTR
pGetStringFromObjectData (
    IN  CPDATAOBJECT ObPtr
    )
{
    PTSTR p;
    PTSTR end;

    //
    // Value type has to be a registry object
    //

    if (!DoesObjectHaveValue (ObPtr) ||
        !IsRegistryTypeSpecified (ObPtr)
        ) {
        return NULL;
    }

    if (ObPtr->Type == REG_DWORD) {
        return NULL;
    }

    if (ObPtr->Value.Size & 1) {
        return NULL;
    }

    p = (PTSTR) ObPtr->Value.Buffer;
    end = (PTSTR) ((PBYTE) p + ObPtr->Value.Size);

    if ((end - p) >= MAX_PATH) {
        return NULL;
    }

    if (ObPtr->Type == REG_SZ || ObPtr->Type == REG_EXPAND_SZ) {
        return p;
    }

    //
    // For REG_NONE and REG_BINARY, give it a try by looking for a terminated string
    //

    if (*(end - 1)) {
        return NULL;
    }

    return p;
}


BOOL
SetObjectStringFlag (
    IN  PCTSTR ObjectStr,
    IN  DWORD   Flag,
    IN  DWORD   RemoveFlag
    )
{
    DWORD Val;

    if (!MemDbGetValue (ObjectStr, &Val)) {
        Val = 0;
    }

    if (Val & RemoveFlag) {
        if (Val & RemoveFlag & (~Flag)) {
            DEBUGMSG ((DBG_WARNING, "SetObjectStringFlag: Removing flag %x from val %x in %s",
                       Val & RemoveFlag, Val, ObjectStr));
            Val = Val & (~RemoveFlag);
        }
    }

    Val |= Flag;
    return MemDbSetValue (ObjectStr, Val);
}


BOOL
SetObjectStructFlag (
    IN  CPDATAOBJECT ObPtr,
    DWORD Flag,
    DWORD RemoveFlag
    )
{
    TCHAR EncodedObject[MAX_ENCODED_RULE];

    CreateObjectString (ObPtr, EncodedObject);
    return SetObjectStringFlag (EncodedObject, Flag, RemoveFlag);
}


BOOL
CreateRenamedObjectStruct (
    IN      PVOID RenameTable,
    IN      PDATAOBJECT InObPtr,
    OUT     PDATAOBJECT OutObPtr
    )

// returns TRUE when OutObPtr is different than InObPtr

{
    LONG rc;
    PCTSTR NewPtr;
    PTSTR p;
    PCTSTR Tail;
    PCTSTR RealValueName;
    TCHAR EncodedObject[MAX_ENCODED_RULE];
    TCHAR CopyOfEncodedObject[MAX_ENCODED_RULE];
    PTSTR NewEncodedObject;
    BOOL b = FALSE;

    ZeroMemory (OutObPtr, sizeof (DATAOBJECT));

    if (InObPtr->KeyPtr) {
        // Look for HKR\sub\key
        InObPtr->ObjectType &= ~(OT_TREE);
        RealValueName = InObPtr->ValueName;
        InObPtr->ValueName = NULL;

        CreateObjectString (InObPtr, EncodedObject);
        StringCopy (CopyOfEncodedObject, EncodedObject);

        InObPtr->ValueName = RealValueName;

        rc = pSetupStringTableLookUpStringEx (RenameTable,
                                        EncodedObject,
                                        STRTAB_CASE_INSENSITIVE,
                                        (PBYTE) &NewPtr,
                                        sizeof (NewPtr)
                                        );
        if (rc != -1) {
            CreateObjectStruct (NewPtr, OutObPtr, WINNTOBJECT);
            b = TRUE;
        } else if (*EncodedObject) {
            // Look for HKR\sub\key\*, HKR\sub\* and HKR\*
            p = GetEndOfString (EncodedObject);
            do {
                StringCopy (p, TEXT("\\*"));

                rc = pSetupStringTableLookUpStringEx (RenameTable,
                                                EncodedObject,
                                                STRTAB_CASE_INSENSITIVE,
                                                (PBYTE) &NewPtr,
                                                sizeof (NewPtr)
                                                );
                if (rc != -1) {
                    Tail = CopyOfEncodedObject + (p - EncodedObject);
                    NewEncodedObject = JoinPaths (NewPtr, Tail);
                    CreateObjectStruct (NewEncodedObject, OutObPtr, WINNTOBJECT);
                    FreePathString (NewEncodedObject);
                    b = TRUE;
                    break;
                }

                do {
                    // _tcsdec is fixed in strings.h
                    p = _tcsdec2 (EncodedObject, p);
                } while (p && _tcsnextc (p) != TEXT('\\'));
            } while (p);
        }
    }

    if (InObPtr->ValueName) {
        if (InObPtr->KeyPtr) {
            // Look for HKR\sub\key\[value]
            CreateObjectString (InObPtr, EncodedObject);

            rc = pSetupStringTableLookUpStringEx (RenameTable,
                                            EncodedObject,
                                            STRTAB_CASE_INSENSITIVE,
                                            (PBYTE) &NewPtr,
                                            sizeof (NewPtr)
                                            );
            if (rc != -1) {
                CreateObjectStruct (NewPtr, OutObPtr, WINNTOBJECT);
                b = TRUE;
            }
        }
    }

    if (!b) {
        // If rename not found, copy in object to out object
        CopyMemory (OutObPtr, InObPtr, sizeof (DATAOBJECT));
    }

    return b;
}


BOOL
CreateRenamedObjectString (
    IN      PVOID RenameTable,
    IN      PCTSTR InObStr,
    OUT     PTSTR OutObStr
    )
{
    DATAOBJECT InObject, OutObject;
    BOOL b;

    if (!CreateObjectStruct (InObStr, &InObject, WIN95OBJECT)) {
        return FALSE;
    }

    b = CreateRenamedObjectStruct (RenameTable, &InObject, &OutObject);

    CreateObjectString (&OutObject, OutObStr);

    FreeObjectStruct (&InObject);
    if (b) {
        FreeObjectStruct (&OutObject);
    }

    return b;
}


BOOL
pForceCopy (
    HINF InfFile
    )
{
    INFCONTEXT ic;
    TCHAR SrcObjectStr[MAX_ENCODED_RULE];
    TCHAR DestObjectStr[MAX_ENCODED_RULE];
    DATAOBJECT SrcObject, DestObject, DupObject;
    BOOL b = TRUE;
    FILTERRETURN fr;

    //
    // Look in INF for [ForceCopy] section
    //

    if (SetupFindFirstLine (InfFile, S_MERGE_FORCECOPY, NULL, &ic)) {
        //
        // For each line in this section, get the encoded object in
        // field 0 (the source) and copy it to the encoded object in
        // field 1 (the destination).
        //
        do {
            *DestObjectStr = 0;
            if (SetupGetStringField (&ic, 0, SrcObjectStr, MAX_ENCODED_RULE, NULL) &&
                SetupGetStringField (&ic, 1, DestObjectStr, MAX_ENCODED_RULE, NULL)
                ) {
                if (!(*DestObjectStr)) {
                    StringCopy (DestObjectStr, SrcObjectStr);
                }

                if (!CreateObjectStruct (SrcObjectStr, &SrcObject, WIN95OBJECT)) {
                    DEBUGMSG ((DBG_WARNING, "pForceCopy: Source object invalid (Section line %u of %s)",
                              ic.Line, g_InfFileName));
                    continue;
                }

                if (!CreateObjectStruct (DestObjectStr, &DestObject, WINNTOBJECT)) {
                    DEBUGMSG ((DBG_WARNING, "pForceCopy: Destination object invalid (Section line %u of %s)",
                              ic.Line, g_InfFileName));
                    FreeObjectStruct (&SrcObject);
                    continue;
                }

                if (b = DuplicateObjectStruct (&DupObject, &SrcObject)) {
                    if (b = CombineObjectStructs (&DupObject, &DestObject)) {
                        //
                        // Copy source to dest
                        //

                        fr = CopyObject (&SrcObject, &DupObject, NULL, NULL);
                        if (fr == FILTER_RETURN_FAIL) {
                            LOG ((LOG_ERROR, "Force Copy: CopyObject failed for %s=%s in %s", SrcObjectStr, DestObjectStr, g_InfFileName));
                            b = FALSE;
                        }
                    }

                    FreeObjectStruct (&DupObject);
                }

                FreeObjectStruct (&SrcObject);
                FreeObjectStruct (&DestObject);
            } else {
                LOG ((LOG_ERROR, "Force Copy: syntax error in line %u of section %s in %s",
                          ic.Line, S_MERGE_FORCECOPY, g_InfFileName));
            }

            TickProgressBar ();

        } while (b && SetupFindNextLine (&ic, &ic));
    }

    return TRUE;
}

BOOL
pForceCopyFromMemDb (
    VOID
    )
{
    MEMDB_ENUM e;
    TCHAR key [MEMDB_MAX];
    TCHAR SrcObjectStr[MAX_ENCODED_RULE];
    TCHAR DestObjectStr[MAX_ENCODED_RULE];
    DATAOBJECT SrcObject, DestObject, DupObject;
    BOOL b = TRUE;
    FILTERRETURN fr;

    //
    // Look in MemDb for ForceCopy tree
    //
    MemDbBuildKey (key, MEMDB_CATEGORY_FORCECOPY, TEXT("*"), NULL, NULL);
    if (MemDbEnumFirstValue (&e, key, MEMDB_ALL_SUBLEVELS, MEMDB_ENDPOINTS_ONLY)) {
        //
        // For each key here the offset points to the destination
        //
        do {
            if (e.dwValue != 0) {
                StringCopy (SrcObjectStr, e.szName);

                if (MemDbBuildKeyFromOffset (e.dwValue, DestObjectStr, 1, NULL)) {

                    if (!(*DestObjectStr)) {
                        StringCopy (DestObjectStr, SrcObjectStr);
                    }

                    if (!CreateObjectStruct (SrcObjectStr, &SrcObject, WIN95OBJECT)) {
                        DEBUGMSG ((DBG_WARNING, "pForceCopyFromMemDb: Source object invalid %s",
                                  SrcObjectStr));
                        continue;
                    }

                    if (!CreateObjectStruct (DestObjectStr, &DestObject, WINNTOBJECT)) {
                        DEBUGMSG ((DBG_WARNING, "pForceCopyFromMemDb: Destination object invalid %s",
                                  DestObjectStr));
                        FreeObjectStruct (&SrcObject);
                        continue;
                    }

                    if (b = DuplicateObjectStruct (&DupObject, &SrcObject)) {
                        if (b = CombineObjectStructs (&DupObject, &DestObject)) {
                            //
                            // Copy source to dest
                            //

                            fr = CopyObject (&SrcObject, &DupObject, NULL, NULL);
                            if (fr == FILTER_RETURN_FAIL) {
                                LOG ((LOG_ERROR, "Force Copy from MemDb: CopyObject failed for %s=%s", SrcObjectStr, DestObjectStr));
                                b = FALSE;
                            }
                        }

                        FreeObjectStruct (&DupObject);
                    }

                    FreeObjectStruct (&SrcObject);
                    FreeObjectStruct (&DestObject);
                }
            }
            TickProgressBar ();

        } while (b && MemDbEnumNextValue (&e));
    }

    return TRUE;
}


#define S_MERGE_DELETEAFTERMIGRATION TEXT("Delete After Migration")

BOOL
pDeleteAfterMigration (
    IN HINF InfFile
    )
{
    BOOL rSuccess = TRUE;
    TCHAR objectString[MAX_ENCODED_RULE];
    DATAOBJECT object;
    INFCONTEXT ic;
    HKEY key;

    //
    // Look in INF for [DeleteAfterMigration] section
    //

    if (SetupFindFirstLine (InfFile, S_MERGE_DELETEAFTERMIGRATION, NULL, &ic)) {

        //
        // For each line in this section, get the encoded object in
        // field 0 and delete it from the registry.
        //

        do {

            if (SetupGetStringField(&ic,0,objectString,MAX_ENCODED_RULE,NULL)) {

                FixUpUserSpecifiedObject(objectString);

                if (!CreateObjectStruct(objectString,&object,WINNTOBJECT)) {
                    LOG((
                        LOG_ERROR,
                        "Delete After Migration: ObjectString invalid. (Section line %u of %s)",
                        ic.Line,
                        g_InfFileName
                        ));

                    continue;
                }

                //
                // We have a good object. Delete it!
                //

                if (object.ValueName) {

                    //
                    // Value is specified. Delete it.
                    //
                    if (!RegDeleteValue(object.KeyPtr->OpenKey,object.ValueName)) {
                        DEBUGMSG((DBG_WARNING,"pDeleteAfterMigration: RegDeleteValue failed for %s [%s]",
                            object.KeyPtr->KeyString,
                            object.ValueName ? object.ValueName : TEXT("<DEFAULT>")
                            ));
                    }
                }
                else {

                    key = GetRootKeyFromOffset (object.RootItem);
                    pSetupRegistryDelnode (key == HKEY_ROOT ? g_hKeyRootNT : key, object.KeyPtr->KeyString);

                }


                //
                // Free our resources.
                //
                FreeObjectStruct(&object);
            }

        } while (SetupFindNextLine(&ic,&ic));
     }

    return rSuccess;
}


BOOL
pCreateRenameTable (
    IN  HINF InfFile,
    OUT PVOID *RenameTablePtr
    )
{
    INFCONTEXT ic;
    TCHAR SrcObjectStr[MAX_ENCODED_RULE];
    TCHAR DestObjectStr[MAX_ENCODED_RULE];
    LONG rc;
    DATAOBJECT OrgOb;
    DATAOBJECT NewOb;
    PCTSTR DestStr;

    //
    // Look in INF for [Rename] section
    //

    if (SetupFindFirstLine (InfFile, S_MERGE_RENAME, NULL, &ic)) {
        //
        // Create string table
        //
        *RenameTablePtr = pSetupStringTableInitializeEx (sizeof (PCTSTR), 0);
        if (!(*RenameTablePtr)) {
            LOG ((LOG_ERROR, "Create Rename Table: Cannot allocate a string table"));
            return FALSE;
        }

        do {
            if (SetupGetStringField (&ic, 0, SrcObjectStr, MAX_ENCODED_RULE, NULL) &&
                SetupGetStringField (&ic, 1, DestObjectStr, MAX_ENCODED_RULE, NULL)
                ) {
                // Ignore bad lines

                FixUpUserSpecifiedObject (SrcObjectStr);
                FixUpUserSpecifiedObject (DestObjectStr);

                if (!CreateObjectStruct (SrcObjectStr, &OrgOb, WIN95OBJECT)) {
                    DEBUGMSG ((DBG_WARNING, "pCreateRenameTable: Source object invalid (Section line %u of %s)",
                              ic.Line, g_InfFileName));
                    continue;
                }

                if (!CreateObjectStruct (DestObjectStr, &NewOb, WINNTOBJECT)) {
                    FreeObjectStruct (&OrgOb);
                    DEBUGMSG ((DBG_WARNING, "pCreateRenameTable: Dest object invalid (Section line %u of %s)",
                              ic.Line, g_InfFileName));
                    continue;
                }

                //
                // Convert DestObjectStr into complete object string
                //

                if (!CombineObjectStructs (&OrgOb, &NewOb)) {
                    FreeObjectStruct (&NewOb);
                    FreeObjectStruct (&OrgOb);

                    DEBUGMSG ((DBG_WARNING, "pCreateRenameTable: Can't perform the rename (Section line %u in %s)",
                              ic.Line, g_InfFileName));
                    continue;
                }

                // Disable tree for destination object
                OrgOb.ObjectType &= ~OT_TREE;

                CreateObjectString (&OrgOb, DestObjectStr);
                FreeObjectStruct (&NewOb);
                FreeObjectStruct (&OrgOb);

                DestStr = PoolMemDuplicateString (g_RenamePool, DestObjectStr);
                if (!DestStr) {
                    break;
                }

                rc = pSetupStringTableAddStringEx (
                            *RenameTablePtr,
                            (PTSTR) SrcObjectStr,
                            STRTAB_CASE_INSENSITIVE,
                            (PBYTE) &DestStr,
                            sizeof (PCTSTR)
                            );

                if (rc == -1) {
                    SetLastError (rc);
                    LOG ((LOG_ERROR, "Create Rename Table: Cannot add string to string table"));
                    break;
                }

                SetObjectStringFlag (SrcObjectStr, REGMERGE_95_RENAME, REGMERGE_95_RENAME);
                SetObjectStringFlag (DestStr, REGMERGE_NT_SUPPRESS, REGMERGE_NT_MASK);
            } else {
                LOG ((LOG_ERROR, "Create Rename Table: syntax error in line %u of section %s in %s",
                          ic.Line, S_MERGE_RENAME, g_InfFileName));
            }

            TickProgressBar ();

        } while (SetupFindNextLine (&ic, &ic));
    } else {
        return FALSE;
    }

    return TRUE;
}

BOOL
CopyRenameTableEntry (
    PVOID   StringTable,
    LONG    StringID,
    PCTSTR  SrcObjectStr,
    PVOID   ExtraData,
    UINT    ExtraDataSize,
    LPARAM  lParam
    )
{
    PCTSTR DestObjectStr = *((PCTSTR *) ExtraData);
    DATAOBJECT SrcOb, DestOb;
    FILTERRETURN fr = FILTER_RETURN_FAIL;
    DWORD Val;

    // See if src has been processed
    if (MemDbGetValue (SrcObjectStr, &Val) && (Val & REGMERGE_95_RENAME_SUPPRESS)) {
        return TRUE;
    }

    // If not, copy Win95 src to WinNT dest
    if (CreateObjectStruct (SrcObjectStr, &SrcOb, WIN95OBJECT)) {
        if (CreateObjectStruct (DestObjectStr, &DestOb, WINNTOBJECT)) {
            fr = CopyObject (&SrcOb, &DestOb, SuppressFilter95, NULL);
            FreeObjectStruct (&DestOb);
        }
        FreeObjectStruct (&SrcOb);
    }

    return fr != FILTER_RETURN_FAIL;
}

BOOL
pProcessRenameTable (
    IN PVOID RenameTable
    )
{
    PCTSTR DataBuf;

    return pSetupStringTableEnum (RenameTable, (PVOID) &DataBuf, sizeof (DataBuf), CopyRenameTableEntry, 0);
}



BOOL
pSpecialConversion (
    IN  HINF InfFile,
    IN  PCTSTR User,
    IN  PVOID RenameTable
    )
{
    INFCONTEXT ic;
    TCHAR FunctionStr[MAX_ENCODED_RULE];
    TCHAR SrcObjectStr[MAX_ENCODED_RULE];
    TCHAR RenamedObjectStr[MAX_ENCODED_RULE];
    PROCESSINGFN Fn;
    PVOID Arg;

    //
    // Look in INF for [SpecialConversion] section
    //

    if (SetupFindFirstLine (InfFile, S_MERGE_WIN9X_CONVERSION, NULL, &ic)) {
        //
        // For each line, get the function and the source object, then call
        // the function.
        //

        do {
            if (SetupGetStringField (&ic, 0, FunctionStr, MAX_ENCODED_RULE, NULL) &&
                SetupGetStringField (&ic, 1, SrcObjectStr, MAX_ENCODED_RULE, NULL)
                ) {
                FixUpUserSpecifiedObject (SrcObjectStr);

                Fn = RuleHlpr_GetFunctionAddr (FunctionStr, &Arg);
                if (!Fn) {
                    LOG ((LOG_ERROR, "Special Conversion: Invalid function %s in %s", FunctionStr, g_InfFileName));
                    continue;
                }

                CreateRenamedObjectString (RenameTable, SrcObjectStr, RenamedObjectStr);

                if (!Fn (SrcObjectStr, RenamedObjectStr, User, Arg)) {
                    if (GetLastError () == ERROR_SUCCESS) {
                        continue;
                    }

                    LOG ((LOG_ERROR, "Processing of Special Conversion was aborted because %s failed.", FunctionStr));
                    break;
                }

                SetObjectStringFlag (
                    SrcObjectStr,
                    REGMERGE_95_SUPPRESS|REGMERGE_95_RENAME_SUPPRESS,
                    REGMERGE_95_SUPPRESS|REGMERGE_95_RENAME_SUPPRESS
                    );

                SetObjectStringFlag (RenamedObjectStr, REGMERGE_NT_SUPPRESS, REGMERGE_NT_MASK);

            } else {
                LOG ((LOG_ERROR, "Special Conversion: syntax error in line %u of section %s in %s",
                          ic.Line, S_MERGE_WIN9X_CONVERSION, g_InfFileName));
            }

            TickProgressBar ();

        } while (SetupFindNextLine (&ic, &ic));
    }

    return TRUE;
}


BOOL
SetFlagsForObject (
    IN  HINF InfFile,
    IN  PCTSTR Section,
    IN  DWORD Flag,
    IN  DWORD RemoveFlag
    )
{
    INFCONTEXT ic;
    TCHAR SrcObjectStr[MAX_ENCODED_RULE];

    //
    // Look in INF for section
    //

    if (SetupFindFirstLine (InfFile, Section, NULL, &ic)) {
        //
        // For each line, get the object and mark it as suppressed.
        //

        do {
            if (SetupGetStringField (&ic, 1, SrcObjectStr, MAX_ENCODED_RULE, NULL)
                ) {
                FixUpUserSpecifiedObject (SrcObjectStr);
                SetObjectStringFlag (SrcObjectStr, Flag, RemoveFlag);
            } else {
                LOG ((LOG_ERROR, "Set Flags For Object: syntax error in line %u of section %s in %s",
                          ic.Line, Section, g_InfFileName));
            }
            TickProgressBar ();
        } while (SetupFindNextLine (&ic, &ic));
    } else {
        DEBUGMSG ((DBG_VERBOSE, "SetFlagsForObject: Section %s can't be found", Section));
    }

    return TRUE;
}

BOOL
pProcessSuppressList (
    IN  HINF InfFile,
    IN  PCTSTR SectionName
    )
{
    return SetFlagsForObject (
                InfFile,
                SectionName,
                REGMERGE_95_SUPPRESS,
                REGMERGE_95_SUPPRESS
                );
}


BOOL
pProcessHardwareSuppressList (
    IN  HINF InfFile
    )
{
    return SetFlagsForObject (InfFile, S_MERGE_WIN9X_SUPPRESS_HW, REGMERGE_95_SUPPRESS, REGMERGE_95_SUPPRESS);
}


BOOL
pSuppressNTDefaults (
    IN  HINF InfFile,
    IN  PCTSTR SectionName
    )
{
    //
    // The objects listed in Suppress WinNT Settings are enumerated,
    // and they are blocked from being transferred from the NT default
    // user to the new user.
    //

    return SetFlagsForObject (
                InfFile,
                SectionName,
                REGMERGE_NT_SUPPRESS,
                REGMERGE_NT_SUPPRESS
                );
}

BOOL
pDontCombineWithDefaults (
    IN  HINF InfFile,
    IN  PCTSTR SectionName
    )
{
    //
    // The objects listed in Merge WinNT with Win9x are enumerated,
    // and they are blocked from being transferred from the NT default
    // user to the new user.  In addition, they are put in a list
    // to be processed at the end of user registry migration.  This
    // last step uses CombineFilter to make sure the NT values do
    // not overwrite the 9x values.
    //

    return SetFlagsForObject (
                InfFile,
                SectionName,
                REGMERGE_NT_IGNORE_DEFAULTS,
                REGMERGE_NT_IGNORE_DEFAULTS
                );
}

BOOL
pForceNTDefaults (
    IN  HINF InfFile,
    IN  PCTSTR SectionName
    )
{
    //
    // The objects listed in Force WinNT Settings are enumerated,
    // and they are blocked from being processed during the general
    // 9x to NT copy.  In addition, they are put in a list to be
    // processed at the end of user registry migration.  This last
    // step forces the entire key to be copied from the default
    // user to the new user, overwriting any previously migrated
    // settings.
    //
    // It is important to note that the special conversion functions
    // are not suppressed here, but the converted settings may be
    // overwritten.
    //

    return SetFlagsForObject (
                InfFile,
                SectionName,
                REGMERGE_NT_PRIORITY_NT|REGMERGE_95_SUPPRESS,
                REGMERGE_NT_PRIORITY_NT|REGMERGE_95_SUPPRESS
                );
}

BOOL
pForceNTDefaultsHack (
    IN  HINF InfFile,
    IN  PCTSTR SectionName
    )
{
    //
    // Take away the REGMERGE_95_SUPPRESS flag now, because the general
    // 9x merge has completed, but we get confused between an actual
    // suppress and a suppress done for the priority-nt case.
    //

    return SetFlagsForObject (
                InfFile,
                SectionName,
                REGMERGE_NT_PRIORITY_NT,
                REGMERGE_NT_PRIORITY_NT|REGMERGE_95_SUPPRESS
                );
}


FILTERRETURN
SuppressFilter95 (
    IN      CPDATAOBJECT SrcObjectPtr,
    IN      CPDATAOBJECT DestObjectPtr,     OPTIONAL
    IN      FILTERTYPE FilterType,
    IN      PVOID DontCare
    )
{
    TCHAR ObStr[MAX_ENCODED_RULE];
    DWORD Val;
    PTSTR p, q, r;
    TCHAR Node[MEMDB_MAX];

    if (FilterType == FILTER_CREATE_KEY) {
        //
        // Check if this tree is suppressed
        //

        MYASSERT (SrcObjectPtr->ObjectType & OT_TREE);
        MYASSERT (SrcObjectPtr->KeyPtr);
        MYASSERT (!(SrcObjectPtr->ValueName));

        // Query setting for HKR\Sub\Key\*
        CreateObjectString (SrcObjectPtr, ObStr);
        if (MemDbGetValue (ObStr, &Val)) {
            if (Val & REGMERGE_95_SUPPRESS) {
                return FILTER_RETURN_DONE;
            }

            if (!g_ProcessRenameTable && (Val & REGMERGE_95_RENAME)) {
                return FILTER_RETURN_DONE;
            }
        }

        // If key is a GUID and GUID is suppressed, suppress the tree
        p = (PTSTR) SrcObjectPtr->ChildKey;
        if (p && _tcsnextc (p) == TEXT('{')) {
            // Look for matching curly brace
            q = _tcschr (p, TEXT('}'));
            if (q) {
                q = _tcsinc (q);

                // Create GUIDS\{a-b-c-d-e}
                *Node = 0;
                r = _tcsappend (Node, MEMDB_CATEGORY_GUIDS);
                r = _tcsappend (r, TEXT("\\"));
                StringCopyAB (r, p, q);

                // Look for match
                if (MemDbGetValue (Node, NULL)) {
                    DEBUGMSG ((DBG_VERBOSE, "Suppressed %s found in %s", Node, ObStr));
                    return FILTER_RETURN_DONE;
                }
            }
        }
    }

    else if (FilterType == FILTER_PROCESS_VALUES) {
        //
        // Check if this node is suppressed
        //

        MYASSERT (!(SrcObjectPtr->ObjectType & OT_TREE));
        MYASSERT (SrcObjectPtr->KeyPtr);
        MYASSERT (!(SrcObjectPtr->ValueName));
        CreateObjectString (SrcObjectPtr, ObStr);

        // Query setting for HKR\Sub\Key
        if (!MemDbGetValue (ObStr, &Val)) {
            Val = 0;
        }

        if (Val & REGMERGE_95_SUPPRESS) {
            return FILTER_RETURN_HANDLED;
        }

        if (!g_ProcessRenameTable && (Val & REGMERGE_95_RENAME)) {
            return FILTER_RETURN_HANDLED;
        }

    }

    else if (FilterType == FILTER_VALUENAME_ENUM) {
        //
        // Check if this value is suppressed
        //

        MYASSERT (!(SrcObjectPtr->ObjectType & OT_TREE));
        MYASSERT (SrcObjectPtr->KeyPtr);
        MYASSERT (SrcObjectPtr->ValueName);
        CreateObjectString (SrcObjectPtr, ObStr);

        // If value name is a GUID and GUID is suppressed, suppress the value
        p = (PTSTR) SrcObjectPtr->ValueName;
        if (_tcsnextc (p) == TEXT('{')) {
            MemDbBuildKey (Node, MEMDB_CATEGORY_GUIDS, NULL, NULL, p);
            if (MemDbGetValue (Node, NULL)) {
                return FILTER_RETURN_HANDLED;
            }
        }

        if (!MemDbGetValue (ObStr, &Val)) {
            Val = 0;
        }

        if (Val & REGMERGE_95_SUPPRESS) {
            return FILTER_RETURN_HANDLED;
        }

        if (!g_ProcessRenameTable && (Val & REGMERGE_95_RENAME)) {
            return FILTER_RETURN_HANDLED;
        }

    }

    else if (FilterType == FILTER_VALUE_COPY) {
        //
        // Don't copy if value has a suppressed GUID
        //

        p = pGetStringFromObjectData (SrcObjectPtr);

        if (p && _tcsnextc (p) == TEXT('{')) {
            MemDbBuildKey (Node, MEMDB_CATEGORY_GUIDS, NULL, NULL, p);
            if (MemDbGetValue (Node, NULL)) {
                return FILTER_RETURN_HANDLED;
            }
        }
    }

    return FILTER_RETURN_CONTINUE;
}


FILTERRETURN
SuppressFilterNT (
    IN      CPDATAOBJECT SrcObjectPtr,
    IN      CPDATAOBJECT DestObjectPtr,         OPTIONAL
    IN      FILTERTYPE FilterType,
    IN      PVOID DontCare
    )
{
    TCHAR ObStr[MAX_ENCODED_RULE];
    DWORD Val;
    PTSTR p;

    if (FilterType == FILTER_CREATE_KEY) {
        //
        // Check if this tree is suppressed
        //

        MYASSERT (DestObjectPtr->ObjectType & OT_TREE);
        MYASSERT (DestObjectPtr->KeyPtr);
        MYASSERT (!(DestObjectPtr->ValueName));
        CreateObjectString (DestObjectPtr, ObStr);

        if (MemDbGetValue (ObStr, &Val) && (Val & REGMERGE_NT_MASK)) {
            return FILTER_RETURN_DONE;
        }
    }


    else if (FilterType == FILTER_PROCESS_VALUES) {
        DATAOBJECT CopyOfDestOb;
        DWORD rc;
        DWORD ValueCount;

        //
        // Does destination already exist?
        //

        CopyMemory (&CopyOfDestOb, DestObjectPtr, sizeof (DATAOBJECT));
        if (OpenObject (&CopyOfDestOb)) {
            //
            // Does it have values?
            //

            MYASSERT (!IsWin95Object (&CopyOfDestOb));

            rc = RegQueryInfoKey (
                    CopyOfDestOb.KeyPtr->OpenKey,
                    NULL,                           // class
                    NULL,                           // class size
                    NULL,                           // reserved
                    NULL,                           // subkey count
                    NULL,                           // max subkey length
                    NULL,                           // max class length
                    &ValueCount,
                    NULL,                           // max value name size
                    NULL,                           // max value size
                    NULL,                           // security
                    NULL                            // last changed time
                    );

            if (rc == ERROR_SUCCESS && ValueCount > 0) {
                CloseObject (&CopyOfDestOb);
                return FILTER_RETURN_HANDLED;
            }
        }


        //
        // Check if this node is suppressed
        //

        MYASSERT (DestObjectPtr->ObjectType & OT_TREE);
        MYASSERT (DestObjectPtr->KeyPtr);
        MYASSERT (!(DestObjectPtr->ValueName));

        CreateObjectString (DestObjectPtr, ObStr);
        p = _tcsrchr (ObStr, TEXT('\\'));
        if (p) {
            *p = 0;
        }


        if (MemDbGetValue (ObStr, &Val) && (Val & REGMERGE_NT_MASK)) {
            return FILTER_RETURN_HANDLED;
        }
    }

    else if (FilterType == FILTER_VALUENAME_ENUM) {
        //
        // Check if this value is suppressed
        //

        MYASSERT (!(DestObjectPtr->ObjectType & OT_TREE));
        MYASSERT (DestObjectPtr->KeyPtr);
        MYASSERT (DestObjectPtr->ValueName);
        CreateObjectString (DestObjectPtr, ObStr);

        if (MemDbGetValue (ObStr, &Val) && (Val & REGMERGE_NT_MASK)) {
            return FILTER_RETURN_HANDLED;
        }
    }

    return FILTER_RETURN_CONTINUE;
}


FILTERRETURN
CombineFilter (
    IN      CPDATAOBJECT SrcObjectPtr,
    IN      CPDATAOBJECT DestObjectPtr,         OPTIONAL
    IN      FILTERTYPE FilterType,
    IN      PVOID DontCare
    )
{
    BOOL b;

    if (FilterType == FILTER_VALUE_COPY) {
        //
        // Check if destination already exists in the registry
        //

        MYASSERT (!(SrcObjectPtr->ObjectType & OT_TREE));
        MYASSERT (SrcObjectPtr->KeyPtr);
        MYASSERT (SrcObjectPtr->ValueName);
        MYASSERT (!(DestObjectPtr->ObjectType & OT_TREE));
        MYASSERT (DestObjectPtr->KeyPtr);
        MYASSERT (DestObjectPtr->ValueName);

        b = CheckIfNtKeyExists (DestObjectPtr);

        if (b) {
            return FILTER_RETURN_HANDLED;
        } else if (GetLastError() != ERROR_SUCCESS) {
            return FILTER_RETURN_FAIL;
        }
    }

    return FILTER_RETURN_CONTINUE;
}


FILTERRETURN
pSuppressDefaultUserFilter (
    IN      CPDATAOBJECT SrcObjectPtr,
    IN      CPDATAOBJECT DestObjectPtr,         OPTIONAL
    IN      FILTERTYPE FilterType,
    IN      PVOID DontCare
    )
{
    TCHAR ObStr[MAX_ENCODED_RULE];
    DWORD Val;
    PTSTR p;

    if (FilterType == FILTER_CREATE_KEY) {
        //
        // Check if this tree is suppressed
        //

        MYASSERT (DestObjectPtr->ObjectType & OT_TREE);
        MYASSERT (DestObjectPtr->KeyPtr);
        MYASSERT (!(DestObjectPtr->ValueName));



        CreateObjectString (DestObjectPtr, ObStr);

        if (MemDbGetValue (ObStr, &Val) && (Val & REGMERGE_NT_IGNORE_DEFAULTS)) {
            return FILTER_RETURN_DONE;
        }
    }

    else if (FilterType == FILTER_PROCESS_VALUES) {
        //
        // Check if this node is suppressed
        //

        MYASSERT (DestObjectPtr->ObjectType & OT_TREE);
        MYASSERT (DestObjectPtr->KeyPtr);
        MYASSERT (!(DestObjectPtr->ValueName));

        CreateObjectString (DestObjectPtr, ObStr);
        p = _tcsrchr (ObStr, TEXT('\\'));
        if (p) {
            *p = 0;
        }


        if (MemDbGetValue (ObStr, &Val) && (Val & REGMERGE_NT_IGNORE_DEFAULTS)) {
            return FILTER_RETURN_HANDLED;
        }

    }

    else if (FilterType == FILTER_VALUENAME_ENUM) {
        //
        // Check if this value is suppressed
        //

        MYASSERT (!(DestObjectPtr->ObjectType & OT_TREE));
        MYASSERT (DestObjectPtr->KeyPtr);
        MYASSERT (DestObjectPtr->ValueName);



        CreateObjectString (DestObjectPtr, ObStr);

        if (MemDbGetValue (ObStr, &Val) && (Val & REGMERGE_NT_IGNORE_DEFAULTS)) {
            return FILTER_RETURN_HANDLED;
        }
    }

    return CombineFilter (SrcObjectPtr, DestObjectPtr, FilterType, DontCare);
}


FILTERRETURN
CopyNoOverwriteFilter (
    IN      CPDATAOBJECT SrcObjectPtr,
    IN      CPDATAOBJECT DestObjectPtr,         OPTIONAL
    IN      FILTERTYPE FilterType,
    IN      PVOID DontCare
    )
{
    FILTERRETURN fr;

    fr = SuppressFilter95 (SrcObjectPtr, DestObjectPtr, FilterType, DontCare);
    if (fr != FILTER_RETURN_CONTINUE) {
        return fr;
    }

    return CombineFilter (SrcObjectPtr, DestObjectPtr, FilterType, DontCare);
}


BOOL
pMergeWin95WithUser (
    IN  PVOID RenameTable
    )
{
    DATAOBJECT SrcOb, DestOb;
    BOOL b;
    FILTERRETURN fr;

    //
    // Copy unsuppressed Win95 keys to NT user hive
    //

    b = CreateObjectStruct (TEXT("HKR\\*"), &SrcOb, WIN95OBJECT);
    MYASSERT (b);

    b = CreateObjectStruct (TEXT("HKR\\*"), &DestOb, WINNTOBJECT);
    MYASSERT (b);

    fr = CopyObject (&SrcOb, &DestOb, SuppressFilter95, NULL);

    FreeObjectStruct (&SrcOb);
    FreeObjectStruct (&DestOb);

    if (fr == FILTER_RETURN_FAIL) {
        LOG ((LOG_ERROR, "Merge Win95 With User: CopyObject failed"));
        return FALSE;
    }

    return TRUE;
}

VOID
RegistryCombineWorker (
    DWORD Flag,
    FILTERFUNCTION FilterFn,
    PCTSTR MemDbRoot,
    PCTSTR SrcRoot,
    DWORD SrcObjectType
    )
{
    MEMDB_ENUM e;
    TCHAR SrcRegKey[MEMDB_MAX];
    TCHAR DestRegKey[MEMDB_MAX];
    PTSTR SrcPtr, DestPtr;
    DATAOBJECT SrcOb, DestOb;
    FILTERRETURN fr;
    TCHAR Pattern[32];

    wsprintf (Pattern, TEXT("%s\\*"), MemDbRoot);

    //
    // Enumerate all keys in memdb and call CopyObject for them
    //

    *SrcRegKey = 0;
    *DestRegKey = 0;

    SrcPtr = _tcsappend (SrcRegKey, SrcRoot);
    SrcPtr = _tcsappend (SrcPtr, TEXT("\\"));

    DestPtr = _tcsappend (DestRegKey, MemDbRoot);
    DestPtr = _tcsappend (DestPtr, TEXT("\\"));

    if (MemDbEnumFirstValue (
            &e,
            Pattern,
            MEMDB_ALL_SUBLEVELS,
            MEMDB_ENDPOINTS_ONLY
            )) {

        do {
            if ((e.dwValue & REGMERGE_NT_MASK) & Flag) {
                StringCopy (SrcPtr, e.szName);
                StringCopy (DestPtr, e.szName);

                if (!CreateObjectStruct (SrcRegKey, &SrcOb, SrcObjectType)) {
                    LOG ((LOG_ERROR, "Merge NT Defaults With User: Can't create object for %s", SrcRegKey));
                    continue;
                }

                if (!CreateObjectStruct (DestRegKey, &DestOb, WINNTOBJECT)) {
                    FreeObjectStruct (&SrcOb);
                    LOG ((LOG_ERROR, "Merge NT Defaults With User: Can't create object for %s", SrcRegKey));
                    continue;
                }

                fr = CopyObject (&SrcOb, &DestOb, FilterFn, NULL);
                if (fr == FILTER_RETURN_FAIL) {
                    LOG ((LOG_ERROR, "Merge NT Defaults With User: Can't copy %s to %s", SrcRegKey, DestRegKey));
                }

                FreeObjectStruct (&SrcOb);
                FreeObjectStruct (&DestOb);
            }

            TickProgressBar ();

        } while (MemDbEnumNextValue (&e));
    }
}


BOOL
pMergeNTDefaultsWithUser (
    HINF hInf
    )
{
    DATAOBJECT SrcOb, DestOb;
    FILTERRETURN fr;
    BOOL b;

    //
    // Copy unsuppressed NT defaults to NT user hive
    //

    b = CreateObjectStruct (
            TEXT("HKLM\\") S_MAPPED_DEFAULT_USER_KEY TEXT("\\*"),
            &SrcOb,
            WINNTOBJECT
            );

    MYASSERT (b);

    b = CreateObjectStruct (TEXT("HKR\\*"), &DestOb, WINNTOBJECT);
    MYASSERT (b);

    __try {
        b = FALSE;

        fr = CopyObject (&SrcOb, &DestOb, SuppressFilterNT, NULL);

        if (fr == FILTER_RETURN_FAIL) {
            LOG ((LOG_ERROR, "Merge NT Defaults With User: CopyObject failed"));
            __leave;
        }

        //
        // Copy forced NT defaults to NT user hive, then copy all NT defaults
        // that need to be combined with Win95 settings.
        //

        RegistryCombineWorker (
            REGMERGE_NT_PRIORITY_NT,
            NULL,
            TEXT("HKR"),
            TEXT("HKLM\\") S_MAPPED_DEFAULT_USER_KEY,
            WINNTOBJECT
            );

        fr = CopyObject (&SrcOb, &DestOb, pSuppressDefaultUserFilter, NULL);

        if (fr == FILTER_RETURN_FAIL) {
            LOG ((LOG_ERROR, "Combine NT Defaults With User: CopyObject failed"));
            __leave;
        }

        b = TRUE;
    }

    __finally {
        FreeObjectStruct (&SrcOb);
        FreeObjectStruct (&DestOb);
    }

    return b;
}


BOOL
pCopyWin9xValuesNotInNt (
    HINF hInf
    )
{
    DATAOBJECT SrcOb, DestOb;
    FILTERRETURN fr;
    BOOL b;

    //
    // Copy Win9x values that NT does not have
    //

    b = CreateObjectStruct (
            TEXT("HKLM\\*"),
            &SrcOb,
            WIN95OBJECT
            );

    MYASSERT (b);

    b = CreateObjectStruct (TEXT("HKR\\*"), &DestOb, WINNTOBJECT);
    MYASSERT (b);

    __try {
        b = FALSE;

        fr = CopyObject (&SrcOb, &DestOb, SuppressFilterNT, NULL);

        if (fr == FILTER_RETURN_FAIL) {
            LOG ((LOG_ERROR, "Merge NT Defaults With User: CopyObject failed"));
            __leave;
        }

        //
        // Copy forced NT defaults to NT user hive, then copy all NT defaults
        // that need to be combined with Win95 settings.
        //

        RegistryCombineWorker (
            REGMERGE_NT_PRIORITY_NT,
            NULL,
            TEXT("HKR"),
            TEXT("HKLM\\") S_MAPPED_DEFAULT_USER_KEY,
            WINNTOBJECT
            );

        fr = CopyObject (&SrcOb, &DestOb, pSuppressDefaultUserFilter, NULL);

        if (fr == FILTER_RETURN_FAIL) {
            LOG ((LOG_ERROR, "Combine NT Defaults With User: CopyObject failed"));
            __leave;
        }

        b = TRUE;
    }

    __finally {
        FreeObjectStruct (&SrcOb);
        FreeObjectStruct (&DestOb);
    }

    return b;
}


BOOL
pMergeWin95WithSystem (
    VOID
    )

/*++

Routine Description:

  pMergeWin95WithSystem copies the Win95 registry to NT, skipping values
  that already exist on NT.

Arguments:

  none

Return Value:

  TRUE if success, FALSE if failure.

--*/

{
    RegistryCombineWorker (
        REGMERGE_NT_PRIORITY_NT,
        CopyNoOverwriteFilter,
        TEXT("HKLM"),               // memdb root and dest root
        TEXT("HKLM"),               // source root
        WIN95OBJECT
        );

    return TRUE;
}


BOOL
pCopyWin95ToSystem (
    VOID
    )

/*++

Routine Description:

  pCopyWin95ToSystem copies all Win95 settings to NT, unless the setting
  is supressed.  This achieves a copy with overwrite capability.

Arguments:

  none

Return Value:

  TRUE if success, FALSE if failure.

--*/

{
    DATAOBJECT SrcOb, DestOb;
    BOOL b;
    FILTERRETURN fr;

    b = CreateObjectStruct (TEXT("HKLM\\*"), &SrcOb, WIN95OBJECT);
    MYASSERT (b);

    b = CreateObjectStruct (TEXT("HKLM\\*"), &DestOb, WINNTOBJECT);
    MYASSERT (b);

    fr = CopyObject (&SrcOb, &DestOb, SuppressFilter95, NULL);

    FreeObjectStruct (&SrcOb);
    FreeObjectStruct (&DestOb);

    if (fr == FILTER_RETURN_FAIL) {
        LOG ((LOG_ERROR, "Copy Win95 To System: CopyObject failed"));
        return FALSE;
    }

    return TRUE;
}


BOOL
pSpecialConversionNT (
    IN  HINF InfFile,
    IN  PCTSTR User,
    IN  BOOL PerUser
    )
{
    INFCONTEXT ic;
    DATAOBJECT SrcOb, DestOb;
    TCHAR FunctionStr[MAX_ENCODED_RULE];
    TCHAR SrcObjectStr[MAX_ENCODED_RULE];
    TCHAR DestObjectStr[MAX_ENCODED_RULE];
    PROCESSINGFN Fn;
    PVOID Arg;

    //
    // Look in INF for [SpecialConversionNT] section
    //

    if (SetupFindFirstLine (InfFile, S_MERGE_WINNT_CONVERSION, NULL, &ic)) {
        //
        // For each line, get the function and the source object, then call
        // the function.
        //

        do {
            if (SetupGetStringField (&ic, 0, FunctionStr, MAX_ENCODED_RULE, NULL) &&
                SetupGetStringField (&ic, 1, DestObjectStr, MAX_ENCODED_RULE, NULL)
                ) {
                FixUpUserSpecifiedObject (DestObjectStr);

                Fn = RuleHlpr_GetFunctionAddr (FunctionStr, &Arg);

                if (!Fn) {
                    LOG ((LOG_ERROR, "Special Conversion: Invalid function %s in %s", FunctionStr, g_InfFileName));
                    continue;
                }

                if (PerUser) {
                    //
                    // Make source off of HKLM\MappedDefaultUser
                    //

                    if (!CreateObjectStruct (DestObjectStr, &SrcOb, WINNTOBJECT)) {
                        continue;
                    }

                    if (!(SrcOb.RootItem)) {
                        LOG ((LOG_ERROR, "Special Conversion NT: Invalid function object %s", DestObjectStr));
                        FreeObjectStruct (&SrcOb);
                        continue;
                    }

                    CreateObjectStruct (TEXT("HKLM"), &DestOb, WINNTOBJECT);
                    CombineObjectStructs (&SrcOb, &DestOb);

                    StringCopy (SrcObjectStr, S_MAPPED_DEFAULT_USER_KEY TEXT("\\"));
                    CreateObjectString (&SrcOb, GetEndOfString (SrcObjectStr));

                    FreeObjectStruct (&DestOb);
                    FreeObjectStruct (&SrcOb);

                } else {

                    if (!CreateObjectStruct (DestObjectStr, &SrcOb, WINNTOBJECT)) {
                        continue;
                    }

                    if (!(SrcOb.RootItem)) {
                        LOG ((LOG_ERROR, "Special Conversion NT: Invalid function object %s", DestObjectStr));
                        FreeObjectStruct (&SrcOb);
                        continue;
                    }


                    CreateObjectString (&SrcOb, SrcObjectStr);
                    FreeObjectStruct (&SrcOb);

                }

                if (!Fn (SrcObjectStr, PerUser ? DestObjectStr : SrcObjectStr, User, Arg)) {
                    if (GetLastError () == ERROR_SUCCESS) {
                        continue;
                    }

                    LOG ((LOG_ERROR, "Processing of Special Conversion was aborted because %s failed.", FunctionStr));
                    break;
                }

                SetObjectStringFlag (SrcObjectStr, REGMERGE_NT_SUPPRESS, REGMERGE_NT_MASK);
            } else {
                LOG ((LOG_ERROR, "Special Conversion NT: syntax error in line %u of section %s in %s",
                          ic.Line, S_MERGE_WINNT_CONVERSION, g_InfFileName));
            }

            TickProgressBar ();

        } while (SetupFindNextLine (&ic, &ic));
    }

    return TRUE;
}



BOOL
SuppressWin95Object (
    IN  PCTSTR ObjectStr
    )
{
    return SetObjectStringFlag (ObjectStr, REGMERGE_95_SUPPRESS, REGMERGE_95_SUPPRESS);
}



BOOL
CheckIfNtKeyExists (
    IN      CPDATAOBJECT SrcObjectPtr
    )

/*++

Routine Description:

  CheckIfNtKeyExists takes a 9x object and tests to see if the same NT
  setting exists.  The 9x object must have a key and value name.

Arguments:

  SrcObjectPtr - Specifies the 9x object to test.

Return Value:

  TRUE if the object exists in NT, FALSE if it doesn't or if an error occurs.
  GetLastError indicates the error (if any).

--*/

{
    DATAOBJECT NtObject;
    BOOL b;
    PCSTR value1;
    PCSTR value2;
    PCWSTR value3;
    PCWSTR oldValueName;
    HKEY oldRoot;

    if (!DuplicateObjectStruct (&NtObject, SrcObjectPtr)) {
        LOG ((LOG_ERROR, "Combine Filter: destination is invalid"));
        return FALSE;
    }

    SetPlatformType (&NtObject, FALSE);

    b = OpenObject (&NtObject);

    if (!b && g_DuHandle) {

        oldRoot = GetRegRoot();
        SetRegRoot (g_DuHandle);

        b = OpenObject (&NtObject);

        SetRegRoot (oldRoot);
    }

    if (b) {
        b = ReadObject (&NtObject);

        if (!b) {
            if (OurGetACP() == 932) {
                //
                // Katakana special case
                //
                oldValueName = NtObject.ValueName;
                value1 = ConvertWtoA (NtObject.ValueName);
                value2 = ConvertSBtoDB (NULL, value1, NULL);
                value3 = ConvertAtoW (value2);
                NtObject.ValueName = value3;
                FreeObjectVal (&NtObject);
                b = ReadObject (&NtObject);
                FreeConvertedStr (value3);
                FreePathStringA (value2);
                FreeConvertedStr (value1);
                NtObject.ValueName = oldValueName;
            }
        }
    }

    FreeObjectStruct (&NtObject);
    SetLastError (ERROR_SUCCESS);

    return b;
}