/*++

Copyright (c) 1997 Microsoft Corporation

Module Name:

    appdiff.c

Abstract:

    Implements a stub tool that is designed to run with Win9x-side
    upgrade code.

Author:

    Jim Schmidt (jimschm) 26-Feb-1998

Revision History:

    <alias> <date> <comments>

--*/

#include "pch.h"


#define S_FILES         TEXT("Files")
#define S_REG           TEXT("Reg")
#define S_INIFILES      TEXT("IniFiles")
#define S_EXCLUDE       TEXT("Exclude")
#define S_PATHS         TEXT("Paths")
#define S_REGISTRY      TEXT("Registry")
#define S_SUBSTITUTIONS TEXT("Substitutions")
#define S_SRC           TEXT("Src")
#define S_DEST          TEXT("Dest")
#define S_ADDED         TEXT("Added")
#define S_CHANGED       TEXT("Changed")
#define S_ZERO          TEXT("0")

typedef struct {
    BOOL SnapMode;
    BOOL DiffMode;
    BOOL CheckBits;
    PCTSTR SnapFile;
    PCTSTR AppFile;
    PCTSTR Name;
    PCTSTR OutputFile;
    PCTSTR RegRoot;
    PCTSTR FileSysRoot;
    BOOL UseAppDiffInf;
    BOOL NoRoots;
    BOOL QuietMode;
} OPTIONS, *POPTIONS;

BOOL g_Quiet;
BOOL g_Thorough;


typedef struct {
    DWORD dwFileAttributes;
    FILETIME ftCreationTime;
    FILETIME ftLastWriteTime;
    DWORD    nFileSizeHigh;
    DWORD    nFileSizeLow;
} FILEINFO, *PFILEINFO;

BOOL
DoSnapMode (
    POPTIONS Options
    );

BOOL
DoDiffMode (
    POPTIONS Options
    );

HANDLE g_hHeap;
HINSTANCE g_hInst;

BOOL
WINAPI
MigUtil_Entry (
    HINSTANCE hInstance,
    DWORD dwReason,
    LPVOID lpReserved
    );

BOOL
WINAPI
MemDb_Entry (
    HINSTANCE hInstance,
    DWORD dwReason,
    LPVOID lpReserved
    );



BOOL
Init (
    VOID
    )
{
    HINSTANCE hInstance;
    DWORD dwReason;
    PVOID lpReserved;

    //
    // Simulate DllMain
    //

    hInstance = GetModuleHandle (NULL);
    dwReason = DLL_PROCESS_ATTACH;
    lpReserved = NULL;

    g_hInst = hInstance;
    g_hHeap = GetProcessHeap();

    MigUtil_Entry (
        hInstance,
        dwReason,
        lpReserved
        );

    MemDb_Entry (
        hInstance,
        dwReason,
        lpReserved
        );

    return TRUE;
}

VOID
Terminate (
    VOID
    )
{
    HINSTANCE hInstance;
    DWORD dwReason;
    PVOID lpReserved;

    //
    // Simulate DllMain
    //

    hInstance = GetModuleHandle (NULL);
    dwReason = DLL_PROCESS_DETACH;
    lpReserved = NULL;

    MemDb_Entry (
        hInstance,
        dwReason,
        lpReserved
        );

    MigUtil_Entry (
        hInstance,
        dwReason,
        lpReserved
        );
}


VOID
HelpAndExit (
    VOID
    )
{
    printf ("Command line syntax:\n\n"
            "appdiff -s[:snapfile] [-r:<regroot>] [-f:<fileroot>]\n"
            "appdiff -d[:snapfile] [-a:appfilelist] [-n:name] [-o:outfile]\n"
            "appdiff -s[:snapfile] -d [-n:name] [-o:outfile] [-r:<regroot>]\n"
            "        [-f:<fileroot>]\n"
            "\n"
            "-s         Specifies snapshot mode, where snapfile is the name of\n"
            "           the memdb output file, and is snap.dat by default.\n"
            "\n"
            "-d         Specifies diff mode, where snapfile is the name of a\n"
            "           previously generated snapshot file, and is snap.dat by\n"
            "           default.\n"
            "\n"
            "-a         Specifies the application file list, as generated by\n"
            "           migfiles.exe.\n"
            "\n"
            "-n         Specifies the application section name, and the default\n"
            "           is Application.\n"
            "\n"
            "-o         Specifies the name of the output INF fragment, and the\n"
            "           default is output.inf\n"
            "\n"
            "-r         Specifies a registry root to compare.  If specified,\n"
            "           only the registry is scanned, unless -f is also specified.\n"
            "\n"
            "-f         Specifies a file system root to compare.  If specified,\n"
            "           only the file system is scanned, unless -r is also\n"
            "           specified.\n"
            "\n"
            "Additional Options:"
            "\n"
            "-q         Quiet mode -- disables stderr output.\n"
            "-u         Use appdiff.inf and output.inf (for generation of uninstall\n"
            "           sections)\n"
            "-t         Thorough checks (computes checksums for all data)\n"
            "\n"
            "APPDIFF.INF specifies the registry and file system roots to scan on\n"
            "a per-app basis, and is used to generate uninstall sections for\n"
            "migdb.inf.  See \\\\jimschm-dev\\team\\tools\\appdiff.inf for info.\n"
            "\n"
            "OUTPUT.INF is generated by this tool, and is designed to be cut &\n"
            "pasted into migdb.inf.\n"
            );

    exit(0);
}



BOOL
pParseCommandLine (
    IN      INT ArgCount,
    IN      PTSTR ArgArray[],
    OUT     POPTIONS Options
    )
{
    INT i;

    ZeroMemory (Options, sizeof (OPTIONS));

    Options->NoRoots = TRUE;

    for (i = 0 ; i < ArgCount ; i++) {

        if (ArgArray[i][0] == TEXT('-') || ArgArray[i][0] == TEXT('/')) {

            switch (_totlower (ArgArray[i][1])) {

            case TEXT('s'):
                Options->SnapMode = TRUE;

                if (ArgArray[i][2] == TEXT(':')) {
                    if (Options->SnapFile) {
                        return FALSE;
                    }

                    Options->SnapFile = &ArgArray[i][3];

                    if (Options->SnapFile[0] == 0) {
                        return FALSE;
                    }
                }

                break;

            case TEXT('t'):
                if (g_Thorough) {
                    return FALSE;
                }

                Options->CheckBits = TRUE;
                g_Thorough = TRUE;

                break;

            case TEXT('d'):
                Options->DiffMode = TRUE;

                if (ArgArray[i][2] == TEXT(':')) {
                    if (Options->SnapFile) {
                        return FALSE;
                    }

                    Options->SnapFile = &ArgArray[i][3];

                    if (Options->SnapFile[0] == 0) {
                        return FALSE;
                    }
                }

                break;

            case TEXT('r'):
                Options->NoRoots = FALSE;

                if (Options->RegRoot) {
                    return FALSE;
                }

                if (ArgArray[i][2] == TEXT(':')) {
                    Options->RegRoot = &ArgArray[i][3];
                } else if (i + 1 < ArgCount) {
                    i++;
                    Options->RegRoot = ArgArray[i];
                } else {
                    Options->RegRoot = &ArgArray[i][2];
                }

                if (Options->RegRoot[0] == 0) {
                    return FALSE;
                }

                break;

            case TEXT('f'):
                Options->NoRoots = FALSE;

                if (Options->FileSysRoot) {
                    return FALSE;
                }

                if (ArgArray[i][2] == TEXT(':')) {
                    Options->FileSysRoot = &ArgArray[i][3];
                } else if (i + 1 < ArgCount) {
                    i++;
                    Options->FileSysRoot = ArgArray[i];
                } else {
                    Options->FileSysRoot = &ArgArray[i][2];
                }

                if (Options->FileSysRoot[0] == 0) {
                    return FALSE;
                }

                break;

            case TEXT('q'):
                if (g_Quiet) {
                    return FALSE;
                }

                Options->QuietMode = TRUE;
                g_Quiet = TRUE;
                break;

            case TEXT('u'):
                if (Options->UseAppDiffInf) {
                    return FALSE;
                }

                Options->UseAppDiffInf = TRUE;
                break;

            case TEXT('a'):
                if (Options->AppFile) {
                    return FALSE;
                }

                if (ArgArray[i][2] == TEXT(':')) {
                    Options->AppFile = &ArgArray[i][3];
                } else if (i + 1 < ArgCount) {
                    i++;
                    Options->AppFile = ArgArray[i];
                } else {
                    Options->AppFile = &ArgArray[i][2];
                }

                if (Options->AppFile[0] == 0) {
                    return FALSE;
                }

                break;

            case TEXT('n'):
                if (Options->Name) {
                    return FALSE;
                }

                if (ArgArray[i][2] == TEXT(':')) {
                    Options->Name = &ArgArray[i][3];
                } else if (i + 1 < ArgCount) {
                    i++;
                    Options->Name = ArgArray[i];
                } else {
                    Options->Name = &ArgArray[i][2];
                }

                if (Options->Name[0] == 0) {
                    return FALSE;
                }

                break;

            case TEXT('o'):
                if (Options->OutputFile) {
                    return FALSE;
                }

                if (ArgArray[i][2] == TEXT(':')) {
                    Options->OutputFile = &ArgArray[i][3];
                } else if (i + 1 < ArgCount) {
                    i++;
                    Options->OutputFile = ArgArray[i];
                } else {
                    Options->OutputFile = &ArgArray[i][2];
                }

                if (Options->OutputFile[0] == 0) {
                    return FALSE;
                }

                break;

            default:
                return FALSE;
            }
        }

        else {
            return FALSE;
        }
    }

    if (!Options->SnapMode && !Options->DiffMode) {
        return FALSE;
    }

    if (!Options->SnapFile) {
        Options->SnapFile = TEXT("snap.dat");
    }

    if (!Options->OutputFile && Options->UseAppDiffInf) {
        Options->OutputFile = TEXT("output.inf");
    }

    if (!Options->Name) {
        Options->Name = TEXT("Application");
    }

    if (!g_Quiet) {
        _ftprintf (stderr, TEXT("Snap file:        %s\n"), Options->SnapFile);

        if (Options->OutputFile) {
            _ftprintf (stderr, TEXT("Output file:      %s\n"), Options->OutputFile);
        }

        _ftprintf (stderr, TEXT("Thorough checks:  %s\n"), Options->CheckBits ? "ENABLED" : "DISABLED");

        _ftprintf (stderr, TEXT("Application Name: %s\n\n"), Options->Name);
    }

    return TRUE;
}



INT
__cdecl
_tmain (
    INT argc,
    TCHAR *argv[]
    )
{
    OPTIONS Options;

    if (!pParseCommandLine (argc - 1, &argv[1], &Options)) {
        HelpAndExit();
    }

    if (!Init()) {
        printf ("Unable to initialize!\n");
        return 255;
    }

    //
    // Snap Mode: Gather the directory, registry and INI files
    //

    if (Options.SnapMode) {
        DoSnapMode (&Options);
    }

    //
    // Diff Mode: Gather another snapshot, then compare against
    //            original
    //

    if (Options.DiffMode) {
        if (Options.SnapMode) {
            _ftprintf (stderr, TEXT("Do your thing, then hit Enter.\n"));
            getchar();
            _ftprintf (stderr, TEXT("\n"));
        }

        DoDiffMode (&Options);
    }

    Terminate();

    return 0;
}


BOOL
pCompareData (
    IN      PCBYTE Src,
    IN      PCBYTE Dest,
    IN      UINT Size
    )
{
    PCWSTR p, q;

    if (Size >= sizeof (WCHAR)) {

        p = (PCWSTR) (Src + Size - sizeof (WCHAR));
        q = (PCWSTR) (Dest + Size - sizeof (WCHAR));

        if (*p == 0 && *q == 0) {
            if (StringIMatchW ((PCWSTR) Src, (PCWSTR) Dest)) {
                return TRUE;
            }

            return FALSE;
        }
    }

    return memcmp (Src, Dest, Size) == 0;
}


VOID
pSetMemDbKey (
    IN      BOOL DiffMode,
    IN      PCTSTR Group,
    IN      PCTSTR Key,
    IN      PBYTE Data,
    IN      DWORD DataSize
    )
{
    PCBYTE OrgData;
    DWORD OrgSize;
    TCHAR Node[MEMDB_MAX];

    wsprintf (Node, TEXT("%s\\%s"), S_EXCLUDE, Key);

    if (MemDbGetValue (Node, NULL)) {
        return;
    }

    if (!DiffMode) {

        MemDbSetBinaryValueEx (
            S_ZERO,
            Group,
            Key,
            Data,
            DataSize,
            NULL
            );
    }

    else {
        //
        // Compare against original data
        //

        wsprintf (Node, TEXT("0\\%s\\%s"), Group, Key);
        OrgData = MemDbGetBinaryValue (Node, &OrgSize);

        if (!OrgData) {
            //
            // Data has been added
            //

            wsprintf (Node, TEXT("%s\\%s\\%s"), S_ADDED, Group, Key);
            MemDbSetValue (Node, 0);
        }

        else {

            //
            // Delete memdb key, so remaining items will provide list of data
            // that was deleted.
            //

            if (OrgSize != DataSize || !pCompareData (OrgData, Data, DataSize)) {
                //
                // Data has changed
                //

                MemDbDeleteValue (Node);

                wsprintf (Node, TEXT("%s\\%s\\%s"), S_CHANGED, Group, Key);
                MemDbSetValue (Node, 0);
            } else {
                //
                // Data has not changed
                //

                MemDbDeleteValue (Node);
            }
        }
    }
}



VOID
pConvertWin32FindData (
    IN      PWIN32_FIND_DATA Data,
    OUT     PFILEINFO Info
    )
{
    Info->dwFileAttributes = Data->dwFileAttributes;
    Info->ftCreationTime = Data->ftCreationTime;
    Info->ftLastWriteTime = Data->ftLastWriteTime;
    Info->nFileSizeHigh = Data->nFileSizeHigh;
    Info->nFileSizeLow = Data->nFileSizeLow;

}



VOID
pSetRegDataAndFreePtrs (
    BOOL DiffMode,
    PBYTE Data,         OPTIONAL
    PBYTE Data2,        OPTIONAL
    DWORD Size,
    PCTSTR Key,
    PCTSTR Value        OPTIONAL
    )
{
    PCTSTR Node;

    if (Data2) {
        Node = CreateEncodedRegistryString (Key, Value);

        pSetMemDbKey (
            DiffMode,
            S_REG,
            Node,
            Data2,
            Size
            );

        FreeEncodedRegistryString (Node);

        if (Data) {
            MemFree (g_hHeap, 0, Data);
        }

        MemFree (g_hHeap, 0, Data2);
    }
}


BOOL
pRegSnap (
    BOOL DiffMode,
    PCTSTR Root
    )

{
    REGTREE_ENUM Reg;
    REGVALUE_ENUM RegVal;
    PBYTE Data;
    PBYTE Data2;
    DWORD Size = 0;
    TCHAR SkipTree[MEMDB_MAX];
    TCHAR TempNode[MEMDB_MAX];
    UINT SkipTreeBytes = 0;

    SkipTree[0] = 0;

    wsprintf (TempNode, TEXT("%s\\%s"), S_EXCLUDE, Root);
    if (MemDbGetValue (TempNode, NULL)) {
        return TRUE;
    }

    if (!g_Quiet) {
        _ftprintf (stderr, TEXT("Taking snapshot of %s\n"), Root);
    }

    if (EnumFirstRegKeyInTree (&Reg, Root)) {
        do {
            //
            // Key/key tree exclude processing
            //

            if (SkipTree[0]) {
                if (StringIMatchByteCount (SkipTree, Reg.FullKeyName, SkipTreeBytes)) {
                    continue;
                }

                SkipTree[0] = 0;
            }

            wsprintf (TempNode, TEXT("%s\\%s"), S_EXCLUDE, Reg.FullKeyName);
            if (MemDbGetValue (TempNode, NULL)) {
                StringCopy (SkipTree, Reg.FullKeyName);
                SkipTreeBytes = ByteCount (SkipTree);
                continue;
            }

            //
            // Non-excluded key
            //

            Data = NULL;

            if (EnumFirstRegValue (&RegVal, Reg.CurrentKey->KeyHandle)) {
                do {
                    Data = GetRegValueData (RegVal.KeyHandle, RegVal.ValueName);
                    Data2 = NULL;

                    if (Data) {
                        Size = RegVal.DataSize + sizeof (DWORD);
                        Data2 = MemAlloc (g_hHeap, 0, Size);
                        MYASSERT (Data2);

                        CopyMemory ((PDWORD) Data2 + 1, Data, RegVal.DataSize);
                        *((PDWORD) Data2) = RegVal.Type;
                    }

                    pSetRegDataAndFreePtrs (DiffMode, Data, Data2, Size, Reg.FullKeyName, RegVal.ValueName);

                } while (EnumNextRegValue (&RegVal));

            } else {
                Size = sizeof (DWORD);
                Data2 = MemAlloc (g_hHeap, 0, Size);
                MYASSERT (Data2);
                *((PDWORD) Data2) = 0xffffffff;

                pSetRegDataAndFreePtrs (DiffMode, Data, Data2, Size, Reg.FullKeyName, RegVal.ValueName);
            }

        } while (EnumNextRegKeyInTree (&Reg));
    }

    return TRUE;
}


DWORD
pComputeChecksum (
    PCTSTR FullPath
    )
{
    HANDLE File;
    HANDLE Map;
    PBYTE Data;
    UINT Size;
    UINT u;
    DWORD Checksum = 0;

    Data = MapFileIntoMemory (FullPath, &File, &Map);
    if (!Data) {
        return 0xFFFFFFFF;
    }

    Size = GetFileSize (File, NULL);

    for (u = 0 ; u < Size ; u++) {
        Checksum = _rotl (Checksum, 3);
        Checksum ^= Data[u];
    }

    UnmapFile (Data, Map, File);

    return Checksum;
}


BOOL
pDirAndIniSnap (
    BOOL DiffMode,
    PCTSTR Root
    )
{
    TREE_ENUM Dir;
    PCTSTR p, q, r;
    TCHAR SectionNames[32768];
    TCHAR KeyNames[32768];
    TCHAR KeyValue[4096];
    TCHAR Node[MEMDB_MAX];
    TCHAR ExcludeNode[MEMDB_MAX];
    UINT Count;
    FILEINFO fi;
    TCHAR SkipTree[MEMDB_MAX];
    UINT SkipTreeBytes = 0;
    DWORD Checksum;

    SkipTree[0] = 0;

    wsprintf (ExcludeNode, TEXT("%s\\%s"), S_EXCLUDE, Root);
    if (MemDbGetValue (ExcludeNode, NULL)) {
        return TRUE;
    }

    //
    // Take a snapshot of all dirs in drive specified by Root
    //

    if (!g_Quiet) {
        _ftprintf (stderr, TEXT("Taking snapshot of %s\n"), Root);
    }

    if (EnumFirstFileInTree (&Dir, Root, NULL, TRUE)) {
        do {
            //
            // Exclude processing
            //

            if (SkipTree[0]) {
                if (StringIMatchByteCount (SkipTree, Dir.FullPath, SkipTreeBytes)) {
                    continue;
                }

                SkipTree[0] = 0;
            }

            if (Dir.Directory) {
                wsprintf (ExcludeNode, TEXT("%s\\%s"), S_EXCLUDE, Dir.FullPath);
                if (MemDbGetValue (ExcludeNode, NULL)) {
                    StringCopy (SkipTree, Dir.FullPath);
                    AppendWack (SkipTree);
                    SkipTreeBytes = ByteCount (SkipTree);
                    continue;
                }
            }

            //
            // Non-excluded file
            //

            if (g_Thorough) {
                Checksum = pComputeChecksum (Dir.FullPath);

                pSetMemDbKey (
                    DiffMode,
                    S_FILES,
                    Dir.FullPath,
                    (PBYTE) &Checksum,
                    sizeof (Checksum)
                    );

            } else {
                pConvertWin32FindData (Dir.FindData, &fi);

                pSetMemDbKey (
                    DiffMode,
                    S_FILES,
                    Dir.FullPath,
                    (PBYTE) &fi,
                    sizeof (FILEINFO)
                    );
            }

            p = _tcsrchr (Dir.Name, TEXT('.'));
            if (p) {
                p = _tcsinc (p);
                if (StringIMatch (p, TEXT("INI"))) {
                    //
                    // Found INI file, take a snapshot of it
                    //

                    if (!g_Quiet) {
                        _ftprintf (stderr, TEXT("    Taking snapshot of %s\n"), Dir.FullPath);
                    }

                    Count = GetPrivateProfileString (NULL, NULL, TEXT("\0"), SectionNames, 32768, Dir.FullPath);
                    SectionNames[Count] = 0;
                    SectionNames[Count + 1] = 0;

                    p = SectionNames;
                    while (*p) {
                        //
                        // Filter out dup sections
                        //

                        r = SectionNames;
                        while (r < p) {
                            if (StringIMatch (p, r)) {
                                break;
                            }

                            r = GetEndOfString (r) + 1;
                        }

                        if (r < p) {
                            if (!g_Quiet) {
                                _ftprintf (stderr, TEXT("        ***Duplicate section ignored: [%s]\n"), p);
                            }

                            p = GetEndOfString (p) + 1;
                            continue;
                        }

                        //
                        // Process each key in the section
                        //

                        Count = GetPrivateProfileString (
                                    p,
                                    NULL,
                                    TEXT("\0"),
                                    KeyNames,
                                    32768,
                                    Dir.FullPath
                                    );
                        KeyNames[Count] = 0;
                        KeyNames[Count + 1] = 0;

                        q = KeyNames;

                        while (*q) {
                            //
                            // Ignore duplicate value names
                            //

                            r = KeyNames;
                            while (r < q) {
                                if (StringIMatch (q, r)) {
                                    break;
                                }

                                r = GetEndOfString (r) + 1;
                            }

                            if (r < q) {
                                if (!g_Quiet) {
                                    _ftprintf (stderr, TEXT("        ***Duplicate key ignored: [%s] %s\n"), p, q);
                                }

                                q = GetEndOfString (q) + 1;
                                continue;
                            }

                            GetPrivateProfileString (
                                p,
                                q,
                                TEXT(""),
                                KeyValue,
                                4096,
                                Dir.FullPath
                                );

                            wsprintf (Node, TEXT("%s\\[%s]\\%s"), Dir.FullPath, p, q);
                            pSetMemDbKey (
                                DiffMode,
                                S_INIFILES,
                                Node,
                                (PBYTE) KeyValue,
                                ByteCount (KeyValue) + sizeof (TCHAR)
                                );

                            q = GetEndOfString (q) + 1;
                        }

                        p = GetEndOfString (p) + 1;
                    }
                }
            }

        } while (EnumNextFileInTree (&Dir));
    }

    return TRUE;
}


VOID
pCreateSubst (
    IN      PCTSTR Src,
    IN      PCTSTR Dest
    )
{
    DWORD Offset;

    MemDbSetValueEx (S_SUBSTITUTIONS, S_DEST, Dest, NULL, 0, &Offset);
    MemDbSetValueEx (S_SUBSTITUTIONS, S_SRC, Src, NULL, Offset, NULL);
}

BOOL
pTakeSnapshot (
    POPTIONS Options,
    BOOL DiffMode
    )
{
    HINF Inf;
    INFSTRUCT is = INITINFSTRUCT_POOLHANDLE;
    TCHAR Path[MAX_TCHAR_PATH];
    PTSTR p, q;
    TCHAR Section[256];
    UINT Dirs = 0;
    UINT RegRoots = 0;
    TCHAR WinDir[MAX_TCHAR_PATH];
    TCHAR SystemDir[MAX_TCHAR_PATH];
    TCHAR System32Dir[MAX_TCHAR_PATH];
    TCHAR SystemDrive[8];
    TCHAR ProgramFilesDir[MAX_TCHAR_PATH];

    GetWindowsDirectory (WinDir, MAX_TCHAR_PATH);
    StringCopy (SystemDir, WinDir);
    StringCopy (AppendWack (SystemDir), TEXT("system"));
    StringCopy (System32Dir, SystemDir);
    StringCat (System32Dir, TEXT("32"));
    SystemDrive[0] = SystemDir[0];
    SystemDrive[1] = TEXT(':');
    SystemDrive[2] = 0;
    StringCopy (ProgramFilesDir, SystemDrive);
    StringCopy (AppendWack (ProgramFilesDir), TEXT("Program Files"));

    pCreateSubst (WinDir, TEXT("%%WINDIR%%"));
    pCreateSubst (SystemDir, TEXT("%%SYSTEMDIR%%"));
    pCreateSubst (System32Dir, TEXT("%%SYSTEM32DIR%%"));
    pCreateSubst (SystemDrive, TEXT("%%SYSTEMDRIVE%%"));
    pCreateSubst (ProgramFilesDir, TEXT("%%PROGRAMFILES%%"));


    if (Options->UseAppDiffInf) {
        GetModuleFileName (NULL, Path, MAX_TCHAR_PATH);
        p = _tcsrchr (Path, TEXT('\\'));
        MYASSERT (p);
        StringCopy (p + 1, TEXT("appdiff.inf"));

        Inf = InfOpenInfFile (Path);
    } else {
        Inf = INVALID_HANDLE_VALUE;
    }

    if (Inf == INVALID_HANDLE_VALUE) {
        //
        // Take snapshot of file system and INI files
        //

        if (Options->FileSysRoot) {
            pDirAndIniSnap (DiffMode, Options->FileSysRoot);
        } else if (Options->NoRoots) {
            pDirAndIniSnap (DiffMode, TEXT("C:\\"));
        }

        //
        // Take snapshot of registry
        //

        if (Options->RegRoot) {
            pRegSnap (DiffMode, Options->RegRoot);
        } else if (Options->NoRoots) {
            pRegSnap (DiffMode, TEXT("HKLM"));
            pRegSnap (DiffMode, TEXT("HKU"));
        }
    }

    else {
        //
        // Fill in the [Exclude] section
        //

        if (InfFindFirstLine (Inf, S_EXCLUDE, NULL, &is)) {
            do {
                p = InfGetLineText (&is);
                MemDbSetValueEx (S_EXCLUDE, p, NULL, NULL, 0, NULL);
            } while (InfFindNextLine (&is));
        }

        InfResetInfStruct (&is);

        if (Options->Name) {
            wsprintf (Section, TEXT("%s.%s"), Options->Name, S_EXCLUDE);

            if (InfFindFirstLine (Inf, Section, NULL, &is)) {
                do {
                    p = InfGetLineText (&is);
                    MemDbSetValueEx (S_EXCLUDE, p, NULL, NULL, 0, NULL);
                } while (InfFindNextLine (&is));
            }

            InfResetInfStruct (&is);
        }

        //
        // Fill in the [Substitutions] section
        //

        if (InfFindFirstLine (Inf, S_SUBSTITUTIONS, NULL, &is)) {
            do {
                p = InfGetStringField (&is, 0);
                q = InfGetStringField (&is, 1);

                pCreateSubst (p, q);

            } while (InfFindNextLine (&is));
        }

        InfResetInfStruct (&is);

        if (Options->Name) {
            wsprintf (Section, TEXT("%s.%s"), Options->Name, S_EXCLUDE);

            if (InfFindFirstLine (Inf, Section, NULL, &is)) {
                do {
                    p = InfGetLineText (&is);
                    MemDbSetValueEx (S_EXCLUDE, p, NULL, NULL, 0, NULL);
                } while (InfFindNextLine (&is));
            }

            InfResetInfStruct (&is);
        }

        //
        // Enumerate the [Paths] section, use c:\ by default
        //

        if (InfFindFirstLine (Inf, S_PATHS, NULL, &is)) {
            do {
                p = InfGetLineText (&is);
                pDirAndIniSnap (DiffMode, p);
                Dirs++;
            } while (InfFindNextLine (&is));

            InfResetInfStruct (&is);
        }

        if (Options->Name) {
            wsprintf (Section, TEXT("%s.%s"), Options->Name, S_PATHS);

            if (InfFindFirstLine (Inf, Section, NULL, &is)) {
                do {
                    p = InfGetLineText (&is);
                    pDirAndIniSnap (DiffMode, p);
                    Dirs++;
                } while (InfFindNextLine (&is));
            }

            InfResetInfStruct (&is);
        }

        if (!Dirs) {
            pDirAndIniSnap (DiffMode, TEXT("C:\\"));
        }

        //
        // Enumerate the [Registry] section, use HKLM and HKU by default
        //

        if (InfFindFirstLine (Inf, S_REGISTRY, NULL, &is)) {
            do {
                p = InfGetLineText (&is);
                pRegSnap (DiffMode, p);
                RegRoots++;
            } while (InfFindNextLine (&is));

            InfResetInfStruct (&is);
        }

        if (Options->Name) {
            wsprintf (Section, TEXT("%s.%s"), Options->Name, S_REGISTRY);

            if (InfFindFirstLine (Inf, Section, NULL, &is)) {
                do {
                    p = InfGetLineText (&is);
                    pRegSnap (DiffMode, p);
                    RegRoots++;
                } while (InfFindNextLine (&is));

                InfResetInfStruct (&is);
            }
        }

        if (!RegRoots) {
            pRegSnap (DiffMode, TEXT("HKLM"));
            pRegSnap (DiffMode, TEXT("HKU"));
        }

        InfCloseInfFile (Inf);
    }

    InfCleanUpInfStruct (&is);
    return TRUE;
}


PCTSTR
pPerformSubstitution (
    PGROWLIST EnvVars,
    PCTSTR OrgStr
    )
{
    PCTSTR PathStr;
    PCTSTR NewPathString;
    UINT Count;
    UINT u;
    PCTSTR Src;
    PCTSTR Dest;

    Count = GrowListGetSize (EnvVars);

    PathStr = DuplicatePathString (OrgStr, 0);
    MYASSERT (PathStr);

    for (u = 0 ; u < Count ; u += 2) {
        Src = GrowListGetString (EnvVars, u);
        Dest = GrowListGetString (EnvVars, u + 1);

        NewPathString = StringSearchAndReplace (PathStr, Src, Dest);
        if (NewPathString) {
            FreePathString (PathStr);
            PathStr = NewPathString;
        }
    }

    return PathStr;
}


VOID
pCreateEnvVars (
    PGROWLIST EnvVars
    )
{
    MEMDB_ENUM e;
    TCHAR Dest[MEMDB_MAX];
    UINT Count;
    UINT u;
    UINT Len;

    //
    // Enumerate source strings
    //

    Count = 0;

    if (MemDbGetValueEx (&e, S_SUBSTITUTIONS, S_SRC, NULL)) {
        do {
            MemDbBuildKeyFromOffset (e.dwValue, Dest, 2, NULL);

            Len = ByteCount (e.szName);

            for (u = 0 ; u < Count ; u += 2) {
                if (ByteCount (GrowListGetString (EnvVars, u)) < Len) {
                    break;
                }
            }

            if (u < Count) {
                GrowListInsertString (EnvVars, u, e.szName);
                GrowListInsertString (EnvVars, u + 1, Dest);
            } else {
                GrowListAppendString (EnvVars, e.szName);
                GrowListAppendString (EnvVars, Dest);
            }

            Count += 2;

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


VOID
pDecodeRegStr (
    IN      PCTSTR RegStr,
    OUT     PTSTR Key,
    OUT     PCTSTR *ValuePtr
    )
{
    PTSTR p;
    PCTSTR Val = NULL;

    StringCopy (Key, RegStr);

    p = _tcschr (Key, TEXT('['));
    if (p) {
        Val = _tcsinc (p);
        p = _tcsdec2 (Key, p);
        while (p) {
            if (_tcsnextc (p) != TEXT(' ')) {
                break;
            }

            p = _tcsdec2 (Key, p);
        }

        *p = 0;
    }

    *ValuePtr = Val;
}

BOOL
pAreAllValuesInMemDb (
    IN      PCTSTR RegStr,
    IN      BOOL Encoded,
    IN      HKEY KeyHandle      OPTIONAL
    )
{
    BOOL WeOpen = FALSE;
    REGVALUE_ENUM e;
    TCHAR Key[MAX_REGISTRY_KEY];
    PCTSTR Value;
    BOOL b = TRUE;

    //
    // If encoded, decode first.
    //

    if (Encoded) {
        pDecodeRegStr (RegStr, Key, &Value);
    } else {
        StringCopy (Key, RegStr);
        Value = NULL;
    }

    //
    // If key not open, open now
    //

    if (!KeyHandle) {
        KeyHandle = OpenRegKeyStr (Key);
        WeOpen = TRUE;

        if (!KeyHandle) {
           return TRUE;
        }
    }

    //
    // if there is at least one value remaining, fail
    //

    b = !EnumFirstRegValue (&e, KeyHandle);

    if (WeOpen) {
        CloseRegKey (KeyHandle);
    }

    return b;
}


BOOL
pIsEntireSubKeyGone (
    IN      PCTSTR RegStr,
    IN      BOOL Encoded
    )
{
    TCHAR Key[MAX_REGISTRY_KEY];
    PCTSTR Value;
    HKEY KeyHandle;

    //
    // If encoded, decode now
    //

    if (Encoded) {
        pDecodeRegStr (RegStr, Key, &Value);
    } else {
        StringCopy (Key, RegStr);
        Value = NULL;
    }

    //
    // Open key
    //

    KeyHandle = OpenRegKeyStr (Key);
    if (!KeyHandle) {
       return TRUE;
    }

    CloseRegKey (KeyHandle);
    return FALSE;
}

VOID
pAppendThingsToDelete (
    POPTIONS Options,
    HANDLE File
    )
{
    MEMDB_ENUM e;
    PCTSTR p;
    GROWLIST EnvVars = GROWLIST_INIT;
    TCHAR SkipKey[MEMDB_MAX];
    UINT SkipKeyBytes = 0;
    BOOL RegFlag;
    BOOL AppendStar;
    BOOL RemoveVal;
    PCTSTR OutLine;
    TCHAR KeyBuf[MAX_REGISTRY_KEY];
    PCTSTR DontCare;

    SkipKey[0] = 0;

    //
    // Generate substitution mapping
    //

    pCreateEnvVars (&EnvVars);

    //
    // Write section name
    //

    if (!Options->Name) {
        return;
    }

    WriteFileString (File, TEXT("["));
    WriteFileString (File, Options->Name);
    WriteFileString (File, TEXT("]\r\n"));

    //
    // Write all the things in the deleted key
    //

    if (MemDbGetValueEx (&e, S_ZERO, NULL, NULL)) {
        do {
            p = _tcschr (e.szName, TEXT('\\'));
            MYASSERT (p);

            if (StringIMatchAB (S_REG, e.szName, p)) {
                RegFlag = TRUE;
            } else {
                RegFlag = FALSE;
            }

            //
            // Skip if this node is a subkey of a deleted key
            //

            p = _tcsinc (p);

            if (SkipKey[0]) {
                if (StringIMatchByteCount (SkipKey, p, SkipKeyBytes)) {
                    continue;
                }

                SkipKey[0] = 0;
            }

            RemoveVal = FALSE;
            AppendStar = FALSE;

            OutLine = p;

            if (RegFlag) {
                //
                // If this is a registry key, and everything in
                // the registry key has been deleted, then
                // just write the one key with a star after it.
                //

                if (pIsEntireSubKeyGone (p, TRUE)) {
                    RemoveVal = TRUE;
                    AppendStar = TRUE;
                }

                //
                // If it's a registry key, and all the subvalues
                // are deleted, then just write the one key, but
                // without a star.
                //

                else if (pAreAllValuesInMemDb (p, TRUE, NULL)) {
                    RemoveVal = TRUE;
                }
            }

            //
            // The value spec needs to be removed from the reg key
            //

            if (RemoveVal) {
                pDecodeRegStr (p, KeyBuf, &DontCare);
                OutLine = CreateEncodedRegistryString (KeyBuf, NULL);

                //
                // Workaround: CreateEncodedRegistryString always appends
                // an asterisk, and we want to control when the asterisk
                // appears.
                //

                p = _tcsrchr (OutLine, TEXT('*'));
                if (p && p[1] == 0) {
                    p = _tcsdec2 (OutLine, p);
                    if (p) {
                        *((PTSTR) p) = 0;
                    }
                }

                //
                // If this entire key is going to be deleted, then
                // turn on SkipKey so the memdb nodes will be skipped.
                //

                if (AppendStar && SkipKey[0] == 0) {
                    StringCopy (SkipKey, OutLine);
                    AppendWack (SkipKey);
                    SkipKeyBytes = ByteCount (SkipKey);
                }
            }

            //
            // Perform substitution on the string
            //

            p = pPerformSubstitution (&EnvVars, OutLine);
            MYASSERT (p);

            if (RemoveVal) {
                FreeEncodedRegistryString (OutLine);
            }

            //
            // Write the file/reg key to the file
            //

            WriteFileString (File, p);

            if (AppendStar) {
                WriteFileString (File, TEXT("\\*"));
            }

            WriteFileString (File, TEXT("\r\n"));

            FreePathString (p);

        } while (MemDbEnumNextValue (&e));
    }

    //
    // Write blank line at the end
    //

    WriteFileString (File, TEXT("\r\n"));

    FreeGrowList (&EnvVars);
}


BOOL
pDumpDiffs (
    VOID
    )
{
    MEMDB_ENUM e;
    BOOL Changes = FALSE;

    if (MemDbGetValueEx (&e, S_ZERO, NULL, NULL)) {
        _tprintf (TEXT("Deleted Items:\n"));
        Changes = TRUE;

        do {
            _tprintf (TEXT("  %s\n"), e.szName);
        } while (MemDbEnumNextValue (&e));
    }

    if (MemDbGetValueEx (&e, S_ADDED, NULL, NULL)) {
        _tprintf (TEXT("Added Items:\n"));

        do {
            _tprintf (TEXT("  %s\n"), e.szName);
        } while (MemDbEnumNextValue (&e));
    }

    if (MemDbGetValueEx (&e, S_CHANGED, NULL, NULL)) {
        _tprintf (TEXT("Changed Items:\n"));

        do {
            _tprintf (TEXT("  %s\n"), e.szName);
        } while (MemDbEnumNextValue (&e));
    }

    return Changes;
}


BOOL
pGenerateInf (
    POPTIONS Options
    )
{
    HANDLE File;
    BOOL DelChanges;

    //
    // Dump changes to stdout
    //

    DelChanges = pDumpDiffs();

    if (Options->OutputFile) {
        //
        // Write a section to our output file
        //

        File = CreateFile (
                    Options->OutputFile,
                    GENERIC_WRITE,
                    0,
                    NULL,
                    CREATE_ALWAYS,
                    FILE_ATTRIBUTE_NORMAL,
                    NULL
                    );

        if (File == INVALID_HANDLE_VALUE) {
            _ftprintf (stderr, TEXT("Cannot generate %s, error %u\n"), Options->OutputFile, GetLastError());
            return FALSE;
        }

        if (DelChanges) {
            pAppendThingsToDelete (Options, File);
        }

        CloseHandle (File);
    }

    return TRUE;
}

BOOL
DoSnapMode (
    POPTIONS Options
    )
{
    DWORD Start;

    Start = GetTickCount();

    if (!pTakeSnapshot (Options, FALSE)) {
        return FALSE;
    }

    MemDbSave (Options->SnapFile);

    if (!g_Quiet) {
        _ftprintf (stderr, TEXT("Run time: %u seconds\n"), (GetTickCount() - Start) / 1000);
    }

    return TRUE;
}



BOOL
DoDiffMode (
    POPTIONS Options
    )
{
    DWORD Start;

    Start = GetTickCount();

    if (GetFileAttributes (Options->SnapFile) == 0xffffffff) {
        _ftprintf (stderr, TEXT("Bogus file arg: %s\n"), Options->SnapFile);
    }

    if (!Options->SnapMode) {
        MemDbLoad (Options->SnapFile);
    }

    if (!pTakeSnapshot (Options, TRUE)) {
        return FALSE;
    }

    pGenerateInf (Options);

    if (!g_Quiet) {
        _ftprintf (stderr, TEXT("Run time: %u seconds\n"), (GetTickCount() - Start) / 1000);
    }

    return TRUE;
}