#include "svcpack.h"

//
// The module instance and name
//
HINSTANCE   hDllInstance;

//
// The path to the OS Source
//
TCHAR OsSourcePath[MAX_PATH];


//
// Function declarations
//
BOOL
DoPhaseOneWork(VOID);

BOOL
DoPhaseTwoWork(VOID);

BOOL
DoPhaseThreeWork(VOID);

BOOL
DoPhaseFourWork(VOID);

BOOL
InitializeSourcePath(
    PTSTR SourcePath,
    HINF  hInf
    );

BOOL
MyInstallProductCatalog(
    LPCTSTR PathToCatalog,
    LPCTSTR CatalogNoPath
    );

LPTSTR
CombinePaths(
    IN  LPTSTR ParentPath,
    IN  LPCTSTR ChildPath,
    OUT LPTSTR  TargetPath   // can be same as ParentPath if want to append
    );

BOOL
SpawnProcessAndWaitForItToComplete(
    IN  LPTSTR CommandLine,
    OUT PDWORD ReturnCode OPTIONAL
    );

BOOL
RunInfProcesses(
    IN     HINF     hInf
    );

BOOL
GetInfValue(
    IN  HINF   hInf,
    IN  LPTSTR SectionName,
    IN  LPTSTR KeyName,
    OUT PDWORD pdwValue
    );

BOOL
DoesInfVersionInfoMatch(
    IN     HINF     hInf
    );


BOOL
CALLBACK
SvcPackCallbackRoutine(
    IN  DWORD dwSetupInterval,
    IN  DWORD dwParam1,
    IN  DWORD dwParam2,
    IN  DWORD dwParam3
    )   

{

    switch ( dwSetupInterval ) {
        case SVCPACK_PHASE_1:
             //
             // install catalogs, etc.
             // 
             DoPhaseOneWork();
        case SVCPACK_PHASE_2:
        case SVCPACK_PHASE_3:
             break;

        case SVCPACK_PHASE_4:
             //
             // Do registry changes, etc.
             //
             DoPhaseFourWork();
             break;

    }

    return TRUE;

}




BOOL
WINAPI
DllMain (HINSTANCE hInstance, DWORD fdwReason, PVOID pvResreved)
{

    switch (fdwReason) {
        case DLL_PROCESS_ATTACH:
            //
            // Save the module instance and name
            //
            hDllInstance = hInstance;

            break;

        case DLL_THREAD_DETACH:
            break;
        case DLL_PROCESS_DETACH:
            break;
        case DLL_THREAD_ATTACH:
        default:
            break;
    }
    return TRUE;
}

BOOL
DoPhaseOneWork(
    VOID
    )
/*++

Routine Description:

    Routine installs the catalogs listed in the svcpack.inf's
    [ProductCatalogsToInstall] section.  It is assumed that these
    catalogs are present at the os source path.
    

Arguments:

    None.


Return Value:

    TRUE if the catalogs were successfully installed.

--*/
{
    HINF hInf;
    TCHAR CatalogSourcePath[MAX_PATH];
    INFCONTEXT InfContext;
    BOOL RetVal = TRUE;
    
    //
    // Open the svcpack.inf so we can install items from it.
    //
    hInf = SetupOpenInfFile(
                        TEXT("SVCPACK.INF"),
                        NULL,
                        INF_STYLE_WIN4,
                        NULL);
    if (hInf == INVALID_HANDLE_VALUE) {
        return(FALSE);
    }

    //
    // Make sure the INF has matching version info
    // Return TRUE even if the versions don't match so setup doesn't barf.
    //
    if (!DoesInfVersionInfoMatch(hInf)) {
        goto e0;        
    }

    //
    // Initialize the source path global variable and save it off for later.
    //
    if (!InitializeSourcePath(OsSourcePath,hInf)) {
        RetVal = FALSE;
        goto e0;        
    }


    //
    // see if we actually have any catalogs to install
    //
    if (SetupFindFirstLine(
                        hInf,
                        TEXT("ProductCatalogsToInstall"),
                        NULL,
                        &InfContext)) {
        UINT Count,Total;
        //
        // we have catalogs in the section, so let's install them.
        //
        Total = SetupGetLineCount(hInf, TEXT("ProductCatalogsToInstall"));

        for (Count = 0; Count < Total; Count++) {
            PCTSTR CatalogNoPath;

             //
             // retrieve a catalog name
             //
             if(SetupGetLineByIndex(
                            hInf, 
                            TEXT("ProductCatalogsToInstall"),
                            Count,
                            &InfContext)) {
                 CatalogNoPath = pSetupGetField(&InfContext,1);


                 //
                 // build the full path to the catalog
                 //
                 _tcscpy(CatalogSourcePath,OsSourcePath);
                 CombinePaths(
                         CatalogSourcePath,
                         CatalogNoPath,
                         CatalogSourcePath);

                 //
                 // now install the catalog
                 // 
                 if (!MyInstallProductCatalog(
                                    CatalogSourcePath,
                                    CatalogNoPath)) {
                    RetVal = FALSE;
                 }
             } else {
                 RetVal = FALSE;
             }
        }        
    }

e0:
    SetupCloseInfFile( hInf );    
    return(RetVal);
}

BOOL 
MyInstallProductCatalog(
    LPCTSTR PathToCatalog,
    LPCTSTR CatalogSourceNoPath
    )
/*++

Routine Description:

    Routine installs the specified catalog with the given source name.
    
    The routine will copy (and if necessary, expand) the catalog file.
    It then validates and installs the catalog.
    

Arguments:

    PathToCatalog - full path to catalog
    CatalogSourceNoPath - just the filename part of the catalog, which we use
                          as the filename of the catalog to be installed.


Return Value:

    TRUE if the catalogs were successfully installed.

--*/

{
    TCHAR CatalogDestPath[MAX_PATH];
    TCHAR CatalogDestWithPath[MAX_PATH];
    BOOL RetVal = FALSE;
    SetupapiVerifyProblem Problem = SetupapiVerifyCatalogProblem;

    //
    // we need to copy (and potentially expand) the catalog from the source,
    // and we use %windir% as a working directory.
    //
    if(GetWindowsDirectory(
                    CatalogDestPath, 
                    sizeof(CatalogDestPath)/sizeof(CatalogDestPath[0]))
        && GetTempFileName(
                    CatalogDestPath, 
                    TEXT("SETP"), 
                    0, 
                    CatalogDestWithPath)) {

        //
        // assume that media is already present -- since product catalogs
        // we installed just prior to this, we know that media was present
        // just a few moments ago
        //
        if ((SetupDecompressOrCopyFile(
                                PathToCatalog,
                                CatalogDestWithPath,
                                NULL) == NO_ERROR)

            && (pSetupVerifyCatalogFile(CatalogDestWithPath) == NO_ERROR)
            && (pSetupInstallCatalog(
                        CatalogDestWithPath,
                        CatalogSourceNoPath,
                        NULL) == NO_ERROR)) {
            RetVal = TRUE;
        }

        //
        // cleanup the temp file.
        //
        DeleteFile(CatalogDestWithPath);

    }

    return(RetVal);

}

BOOL
InitializeSourcePath(
    PTSTR SourcePath,
    HINF hInf
    )
/*++

Routine Description:

    Routine retrieves the os source path from the registry, then appends
    the subdirectory in the specified inf.    

Arguments:

    None.


Return Value:

    TRUE if the catalogs were successfully installed.

--*/

{
    HKEY hKey = NULL;
    TCHAR TempPath[MAX_PATH];
    TCHAR MyAnswerFile[MAX_PATH];
    DWORD Type,Size = MAX_PATH;
    INFCONTEXT InfContext;
    BOOL RetVal = FALSE;
    
    //
    // if it was already initialized to something, just return TRUE.
    //
    if (*SourcePath != (TCHAR)TEXT('\0')) {
        RetVal = TRUE;
        goto e0;
    }

        GetSystemDirectory(MyAnswerFile, MAX_PATH);
        CombinePaths( MyAnswerFile, TEXT("$winnt$.inf"), MyAnswerFile );

        GetPrivateProfileString( TEXT("Data"),
                                 TEXT("DosPath"),
                                 TEXT(""),
                                 TempPath,
                                 sizeof(TempPath)/sizeof(TCHAR),
                                 MyAnswerFile );
        _tcscpy(SourcePath,TempPath);
        RetVal = TRUE;

        //
        // now append the subdirectory specified in the inf (if any)
        //
        if (hInf && SetupFindFirstLine(
                            hInf,
                            TEXT("SetupData"),
                            TEXT("CatalogSubDir"),
                            &InfContext)) {
            PCTSTR p = pSetupGetField(&InfContext,1);

            CombinePaths(
                SourcePath,
                p,
                SourcePath);                        
        } 

e0:
    return(RetVal);
}


BOOL
DoPhaseFourWork(VOID)
{

    BOOL    Success = TRUE;
    HINF    hInf = NULL;

    //
    // Attempt to open the SVCPACK.INF file.
    // If found, and no problems with it, do
    // the associated work.
    //
    hInf = SetupOpenInfFile (
                TEXT("SVCPACK.INF"),
                NULL,
                INF_STYLE_WIN4,
                NULL
                );

    if (( hInf == NULL ) || ( hInf == INVALID_HANDLE_VALUE )) {
        Success = FALSE;
        goto exit0;
    }

    //
    // Make sure the INF has matching version info.
    // Return TRUE even if the versions don't match so setup doesn't barf.
    //
    if (!DoesInfVersionInfoMatch(hInf)) {
        goto exit1;
    }

    Success = RunInfProcesses( hInf );

exit1:
    SetupCloseInfFile( hInf );
exit0:
    return Success;

}


BOOL
SpawnProcessAndWaitForItToComplete(
    IN  LPTSTR CommandLine,
    OUT PDWORD ReturnCode OPTIONAL
    )
    {
    LPTSTR InternalCommandLine = NULL;
    PROCESS_INFORMATION ProcessInfo;
    STARTUPINFO StartupInfo;
    BOOL Success;

    //
    //  CreateProcess needs a non-const command line buffer because it likes
    //  to party on it.
    //
    InternalCommandLine = malloc( MAX_PATH );

    if ( InternalCommandLine == NULL ) {
        return FALSE;
    }

    _tcscpy( InternalCommandLine, CommandLine );

    ZeroMemory( &StartupInfo, sizeof( StartupInfo ));
    StartupInfo.cb = sizeof( StartupInfo );

    Success = CreateProcess(
                  NULL,
                  InternalCommandLine,
                  NULL,
                  NULL,
                  FALSE,
                  0,
                  NULL,
                  NULL,
                  &StartupInfo,
                  &ProcessInfo
                  );

    if ( ! Success ) {
        free( InternalCommandLine );
        return FALSE;
        }

    WaitForSingleObject( ProcessInfo.hProcess, INFINITE );

    if ( ReturnCode != NULL ) {
        GetExitCodeProcess( ProcessInfo.hProcess, ReturnCode );
        }

    CloseHandle( ProcessInfo.hProcess );
    CloseHandle( ProcessInfo.hThread );
    free( InternalCommandLine );

    return TRUE;
    }


LPTSTR
CombinePaths(
    IN  LPTSTR ParentPath,
    IN  LPCTSTR ChildPath,
    OUT LPTSTR  TargetPath   // can be same as ParentPath if want to append
    )
    {
    ULONG ParentLength = _tcslen( ParentPath );
    LPTSTR p;

    if ( ParentPath != TargetPath ) {
        memcpy( TargetPath, ParentPath, ParentLength * sizeof(TCHAR) );
        }

    p = TargetPath + ParentLength;

    if (( ParentLength > 0 )   &&
        ( *( p - 1 ) != '\\' ) &&
        ( *( p - 1 ) != '/'  )) {
        *p++ = '\\';
        }

    _tcscpy( p, ChildPath );

    return TargetPath;
    }



BOOL
RunInfProcesses(
    IN     HINF     hInf
    )
{

    LPTSTR  SectionName = TEXT("SetupHotfixesToRun");
    LPTSTR  szFileName;
    LPTSTR  szFullPath;
    INFCONTEXT InfContext;
    BOOL Success = TRUE;

    //
    // Loop through all the lines in the SetupHotfixesToRun section,
    // spawning off each one.
    //
    szFileName = malloc( MAX_PATH );
    if (szFileName == NULL) {
       Success = FALSE;
       goto exit0;
    }

    szFullPath = malloc( MAX_PATH );
    if (szFullPath == NULL) {
       Success = FALSE;
       goto exit1;
    }
    
    Success = SetupFindFirstLine( hInf, SectionName, NULL, &InfContext ) &&
             SetupGetLineText( &InfContext, NULL, NULL, NULL, szFileName, MAX_PATH, NULL );
    
    while ( Success ) {
    
       *szFullPath = 0;
       CombinePaths( OsSourcePath, szFileName, szFullPath );
    
       //
       // OK, spawn the EXE, and ignore any errors returned
       //
       SpawnProcessAndWaitForItToComplete( szFullPath, NULL );
    
       Success = SetupFindNextLine( &InfContext, &InfContext ) &&
                 SetupGetLineText( &InfContext, NULL, NULL, NULL, szFileName, MAX_PATH, NULL );
    }
    
    Success = TRUE;
    free( (PVOID)szFullPath );         
exit1:
    free( (PVOID)szFileName );    
exit0:
    return Success;

}


BOOL
GetInfValue(
    IN  HINF   hInf,
    IN  LPTSTR SectionName,
    IN  LPTSTR KeyName,
    OUT PDWORD pdwValue
    )
    {
    BOOL Success;
    TCHAR TextBuffer[MAX_PATH];

    Success = SetupGetLineText(
                  NULL,
                  hInf,
                  SectionName,
                  KeyName,
                  TextBuffer,
                  sizeof( TextBuffer ),
                  NULL
                  );

    *pdwValue = _tcstoul( TextBuffer, NULL, 0 );

    return Success;
    }



BOOL
DoesInfVersionInfoMatch(
    IN     HINF     hInf
    )
{

    DWORD dwBuildNumber, dwMajorVersion, dwMinorVersion;
    OSVERSIONINFOEX OsVersionInfo;

    if (( ! GetInfValue( hInf, TEXT("Version"), TEXT("BuildNumber"),  &dwBuildNumber )) ||
        ( ! GetInfValue( hInf, TEXT("Version"), TEXT("MajorVersion"), &dwMajorVersion )) ||
        ( ! GetInfValue( hInf, TEXT("Version"), TEXT("MinorVersion"), &dwMinorVersion ))) {

        return FALSE;
    }

    OsVersionInfo.dwOSVersionInfoSize = sizeof( OsVersionInfo );
    if (!GetVersionEx( (LPOSVERSIONINFO) &OsVersionInfo )) {

        return FALSE;
    }

    if ((OsVersionInfo.dwBuildNumber  != dwBuildNumber) ||
        (OsVersionInfo.dwMajorVersion != dwMajorVersion) ||
        (OsVersionInfo.dwMinorVersion != dwMinorVersion)) {

        return FALSE;
    }

    return TRUE;
}