// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.

#include "precomp.h"

static DNCSTATE vstate = { };


// internal function declarations

static HRESULT LoadModulePaths(
    __in DNCSTATE* pState
    );
static HRESULT LoadDncConfiguration(
    __in DNCSTATE* pState,
    __in const BOOTSTRAPPER_CREATE_ARGS* pArgs
    );
static HRESULT LoadRuntime(
    __in DNCSTATE* pState
    );
static HRESULT LoadManagedBootstrapperApplicationFactory(
    __in DNCSTATE* pState
    );
static HRESULT CreatePrerequisiteBA(
    __in DNCSTATE* pState,
    __in IBootstrapperEngine* pEngine,
    __in const BOOTSTRAPPER_CREATE_ARGS* pArgs,
    __inout BOOTSTRAPPER_CREATE_RESULTS* pResults
    );


// function definitions

extern "C" BOOL WINAPI DllMain(
    IN HINSTANCE hInstance,
    IN DWORD dwReason,
    IN LPVOID /* pvReserved */
    )
{
    switch (dwReason)
    {
    case DLL_PROCESS_ATTACH:
        ::DisableThreadLibraryCalls(hInstance);
        vstate.hInstance = hInstance;
        break;

    case DLL_PROCESS_DETACH:
        vstate.hInstance = NULL;
        break;
    }

    return TRUE;
}

extern "C" HRESULT WINAPI BootstrapperApplicationCreate(
    __in const BOOTSTRAPPER_CREATE_ARGS* pArgs,
    __inout BOOTSTRAPPER_CREATE_RESULTS* pResults
    )
{
    HRESULT hr = S_OK;
    IBootstrapperEngine* pEngine = NULL;

    hr = BalInitializeFromCreateArgs(pArgs, &pEngine);
    ExitOnFailure(hr, "Failed to initialize Bal.");

    if (!vstate.fInitialized)
    {
        hr = XmlInitialize();
        BalExitOnFailure(hr, "Failed to initialize XML.");

        hr = LoadModulePaths(&vstate);
        BalExitOnFailure(hr, "Failed to get the host base path.");

        hr = LoadDncConfiguration(&vstate, pArgs);
        BalExitOnFailure(hr, "Failed to get the dnc configuration.");

        vstate.fInitialized = TRUE;
    }

    if (vstate.prereqData.fAlwaysInstallPrereqs && !vstate.prereqData.fCompleted)
    {
        BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Loading prerequisite bootstrapper application since it's configured to always run before loading the runtime.");

        hr = CreatePrerequisiteBA(&vstate, pEngine, pArgs, pResults);
        BalExitOnFailure(hr, "Failed to create the pre-requisite bootstrapper application.");

        ExitFunction();
    }

    if (!vstate.fInitializedRuntime)
    {
        hr = LoadRuntime(&vstate);

        vstate.fInitializedRuntime = SUCCEEDED(hr);
    }

    if (vstate.fInitializedRuntime)
    {
        if (!vstate.pAppFactory)
        {
            hr = LoadManagedBootstrapperApplicationFactory(&vstate);
            BalExitOnFailure(hr, "Failed to create the .NET Core bootstrapper application factory.");
        }

        BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Loading .NET Core %ls bootstrapper application.", DNCHOSTTYPE_FDD == vstate.type ? L"FDD" : L"SCD");

        hr = vstate.pAppFactory->Create(pArgs, pResults);
        BalExitOnFailure(hr, "Failed to create the .NET Core bootstrapper application.");
    }
    else // fallback to the prerequisite BA.
    {
        if (DNCHOSTTYPE_SCD == vstate.type)
        {
            vstate.prereqData.hrFatalError = E_DNCHOST_SCD_RUNTIME_FAILURE;
            BalLogError(hr, "The self-contained .NET Core runtime failed to load. This is an unrecoverable error.");
        }
        else if (vstate.prereqData.fCompleted)
        {
            hr = E_PREREQBA_INFINITE_LOOP;
            BalLogError(hr, "The prerequisites were already installed. The bootstrapper application will not be reloaded to prevent an infinite loop.");
            vstate.prereqData.hrFatalError = hr;
        }
        else
        {
            vstate.prereqData.hrFatalError = S_OK;
        }
        BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Loading prerequisite bootstrapper application because .NET Core host could not be loaded, error: 0x%08x.", hr);

        hr = CreatePrerequisiteBA(&vstate, pEngine, pArgs, pResults);
        BalExitOnFailure(hr, "Failed to create the pre-requisite bootstrapper application.");
    }

LExit:
    ReleaseNullObject(pEngine);

    return hr;
}

extern "C" void WINAPI BootstrapperApplicationDestroy(
    __in const BOOTSTRAPPER_DESTROY_ARGS* pArgs,
    __in BOOTSTRAPPER_DESTROY_RESULTS* pResults
    )
{
    BOOTSTRAPPER_DESTROY_RESULTS childResults = { };

    childResults.cbSize = sizeof(BOOTSTRAPPER_DESTROY_RESULTS);

    if (vstate.hMbapreqModule)
    {
        PFN_BOOTSTRAPPER_APPLICATION_DESTROY pfnDestroy = reinterpret_cast<PFN_BOOTSTRAPPER_APPLICATION_DESTROY>(::GetProcAddress(vstate.hMbapreqModule, "PrereqBootstrapperApplicationDestroy"));
        if (pfnDestroy)
        {
            (*pfnDestroy)(pArgs, &childResults);
        }

        ::FreeLibrary(vstate.hMbapreqModule);
        vstate.hMbapreqModule = NULL;
    }

    BalUninitialize();

    // Need to keep track of state between reloads.
    pResults->fDisableUnloading = TRUE;
}

static HRESULT LoadModulePaths(
    __in DNCSTATE* pState
    )
{
    HRESULT hr = S_OK;

    hr = PathForCurrentProcess(&pState->sczModuleFullPath, pState->hInstance);
    BalExitOnFailure(hr, "Failed to get the full host path.");

    hr = PathGetDirectory(pState->sczModuleFullPath, &pState->sczAppBase);
    BalExitOnFailure(hr, "Failed to get the directory of the full process path.");

LExit:
    return hr;
}

static HRESULT LoadDncConfiguration(
    __in DNCSTATE* pState,
    __in const BOOTSTRAPPER_CREATE_ARGS* pArgs
    )
{
    HRESULT hr = S_OK;
    IXMLDOMDocument* pixdManifest = NULL;
    IXMLDOMNode* pixnHost = NULL;
    LPWSTR sczPayloadName = NULL;
    DWORD dwBool = 0;
    BOOL fXmlFound = FALSE;

    hr = XmlLoadDocumentFromFile(pArgs->pCommand->wzBootstrapperApplicationDataPath, &pixdManifest);
    BalExitOnFailure(hr, "Failed to load BalManifest '%ls'", pArgs->pCommand->wzBootstrapperApplicationDataPath);

    hr = XmlSelectSingleNode(pixdManifest, L"/BootstrapperApplicationData/WixBalBAFactoryAssembly", &pixnHost);
    BalExitOnRequiredXmlQueryFailure(hr, "Failed to get WixBalBAFactoryAssembly element.");

    hr = XmlGetAttributeEx(pixnHost, L"FilePath", &sczPayloadName);
    BalExitOnRequiredXmlQueryFailure(hr, "Failed to get WixBalBAFactoryAssembly/@FilePath.");

    hr = PathConcatRelativeToBase(pArgs->pCommand->wzBootstrapperWorkingFolder, sczPayloadName, &pState->sczBaFactoryAssemblyPath);
    BalExitOnFailure(hr, "Failed to create BaFactoryAssemblyPath.");

    LPCWSTR wzFileName = PathFile(pState->sczBaFactoryAssemblyPath);
    LPCWSTR wzExtension = PathExtension(pState->sczBaFactoryAssemblyPath);
    if (!wzExtension)
    {
        BalExitOnFailure(hr = E_FAIL, "BaFactoryAssemblyPath has no extension.");
    }

    hr = StrAllocString(&pState->sczBaFactoryAssemblyName, wzFileName, wzExtension - wzFileName);
    BalExitOnFailure(hr, "Failed to copy BAFactoryAssembly payload Name.");

    hr = StrAllocString(&pState->sczBaFactoryDepsJsonPath, pState->sczBaFactoryAssemblyPath, wzExtension - pState->sczBaFactoryAssemblyPath);
    BalExitOnFailure(hr, "Failed to initialize deps json path.");

    hr = StrAllocString(&pState->sczBaFactoryRuntimeConfigPath, pState->sczBaFactoryDepsJsonPath, 0);
    BalExitOnFailure(hr, "Failed to initialize runtime config path.");

    hr = StrAllocConcat(&pState->sczBaFactoryDepsJsonPath, L".deps.json", 0);
    BalExitOnFailure(hr, "Failed to concat extension to deps json path.");

    hr = StrAllocConcat(&pState->sczBaFactoryRuntimeConfigPath, L".runtimeconfig.json", 0);
    BalExitOnFailure(hr, "Failed to concat extension to runtime config path.");

    hr = XmlSelectSingleNode(pixdManifest, L"/BootstrapperApplicationData/WixMbaPrereqOptions", &pixnHost);
    BalExitOnOptionalXmlQueryFailure(hr, fXmlFound, "Failed to find WixMbaPrereqOptions element in bootstrapper application config.");

    if (fXmlFound)
    {
        hr = XmlGetAttributeNumber(pixnHost, L"AlwaysInstallPrereqs", reinterpret_cast<DWORD*>(&pState->prereqData.fAlwaysInstallPrereqs));
        BalExitOnOptionalXmlQueryFailure(hr, fXmlFound, "Failed to get AlwaysInstallPrereqs value.");
    }

    pState->prereqData.fPerformHelp = !pState->prereqData.fAlwaysInstallPrereqs;

    pState->type = DNCHOSTTYPE_FDD;

    hr = XmlSelectSingleNode(pixdManifest, L"/BootstrapperApplicationData/WixDncOptions", &pixnHost);
    BalExitOnOptionalXmlQueryFailure(hr, fXmlFound, "Failed to find WixDncOptions element in bootstrapper application config.");

    if (!fXmlFound)
    {
        ExitFunction();
    }

    hr = XmlGetAttributeNumber(pixnHost, L"SelfContainedDeployment", &dwBool);
    BalExitOnOptionalXmlQueryFailure(hr, fXmlFound, "Failed to get SelfContainedDeployment value.");

    if (fXmlFound && dwBool)
    {
        pState->type = DNCHOSTTYPE_SCD;
    }

LExit:
    ReleaseStr(sczPayloadName);
    ReleaseObject(pixnHost);
    ReleaseObject(pixdManifest);

    return hr;
}

static HRESULT LoadRuntime(
    __in DNCSTATE* pState
    )
{
    HRESULT hr = S_OK;

    hr = DnchostLoadRuntime(
        &pState->hostfxrState,
        pState->sczModuleFullPath,
        pState->sczBaFactoryAssemblyPath,
        pState->sczBaFactoryDepsJsonPath,
        pState->sczBaFactoryRuntimeConfigPath);

    return hr;
}

static HRESULT LoadManagedBootstrapperApplicationFactory(
    __in DNCSTATE* pState
    )
{
    HRESULT hr = S_OK;

    hr = DnchostCreateFactory(
        &pState->hostfxrState,
        pState->sczBaFactoryAssemblyName,
        &pState->pAppFactory);

    return hr;
}

static HRESULT CreatePrerequisiteBA(
    __in DNCSTATE* pState,
    __in IBootstrapperEngine* pEngine,
    __in const BOOTSTRAPPER_CREATE_ARGS* pArgs,
    __inout BOOTSTRAPPER_CREATE_RESULTS* pResults
    )
{
    HRESULT hr = S_OK;
    LPWSTR sczDncpreqPath = NULL;
    HMODULE hModule = NULL;

    hr = PathConcat(pState->sczAppBase, L"dncpreq.dll", &sczDncpreqPath);
    BalExitOnFailure(hr, "Failed to get path to pre-requisite BA.");

    hModule = ::LoadLibraryExW(sczDncpreqPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
    BalExitOnNullWithLastError(hModule, hr, "Failed to load pre-requisite BA DLL.");

    PFN_PREQ_BOOTSTRAPPER_APPLICATION_CREATE pfnCreate = reinterpret_cast<PFN_PREQ_BOOTSTRAPPER_APPLICATION_CREATE>(::GetProcAddress(hModule, "PrereqBootstrapperApplicationCreate"));
    BalExitOnNullWithLastError(pfnCreate, hr, "Failed to get PrereqBootstrapperApplicationCreate entry-point from: %ls", sczDncpreqPath);

    hr = pfnCreate(&pState->prereqData, pEngine, pArgs, pResults);
    BalExitOnFailure(hr, "Failed to create prequisite bootstrapper app.");

    pState->hMbapreqModule = hModule;
    hModule = NULL;

LExit:
    if (hModule)
    {
        ::FreeLibrary(hModule);
    }
    ReleaseStr(sczDncpreqPath);

    return hr;
}