/*
 * Copyright 2004-2020 Sandboxie Holdings, LLC 
 * Copyright 2020 David Xanatos, xanasoft.com
 *
 * This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

//---------------------------------------------------------------------------
// WinSxS Services
//---------------------------------------------------------------------------


#include "dll.h"
#include "common/my_version.h"
#include <stdlib.h>
#include <stdio.h>


//---------------------------------------------------------------------------
// Structures and Types
//---------------------------------------------------------------------------


typedef struct _SXS_ARGS {

    ULONG ArchId;
    ULONG LangId;
    WCHAR *LangNames;
    WCHAR *Directory;
    WCHAR *SourcePath;
    UCHAR *ManifestText;
    WCHAR *ConfigPath;
    UCHAR *ConfigText;

} SXS_ARGS;


#define SXS_PATH_LEN    (2 * 1024)

#define SXS_TEXT_LEN    (512 * 1024)


//---------------------------------------------------------------------------
// Functions
//---------------------------------------------------------------------------


static BOOLEAN Sxs_AllocOrFreeBuffers(SXS_ARGS *args, BOOLEAN alloc);

static BOOLEAN Sxs_GetLanguage(ACTCTX *ActCtx, SXS_ARGS *args);

static BOOL Sxs_GetManifestResource_Helper(
    HMODULE hModule, LPCTSTR lpszType, LPTSTR lpszName, LONG_PTR lParam);

static BOOL Sxs_GetManifestResource(
    HMODULE hModule, ULONG *ResourceId, HRSRC *hResource);

static BOOL Sxs_ReadTextAndClose(HANDLE hFile, UCHAR *Text);

static void Sxs_ConvertUnicodeToAscii(UCHAR *Text);

static BOOLEAN Sxs_GetPathAndText(ACTCTX *ActCtx, SXS_ARGS *args);

static void *Sxs_CallService(SXS_ARGS *args, BOOLEAN *UseAltCreateActCtx);

static void Sxs_ActivationContextNotificationRoutine(
    ULONG Operation,
    HANDLE ActivationContext,
    ULONG_PTR MappedViewBaseAddress,
    ULONG_PTR Unknown1,
    ULONG_PTR Unknown2,
    BOOLEAN *pSuccess);

static HANDLE Sxs_CreateActCtxW_Alt(ACTCTX *ActCtx);

static BOOL Sxs_QueryActCtxW(DWORD dwFlags, HANDLE hActCtx,
    void *pvSubInstance, ULONG ulInfoClass, void *pvBuffer,
    SIZE_T cbBuffer, SIZE_T *pcbWrittenOrRequired);

static void Sxs_QueryActCtxW_2(ULONG *p_len, WCHAR **p_path);

static BOOL Sxs_SxsInstallW(void *info);

static BOOLEAN Sxs_CheckCompatLayer(
    const WCHAR *DosPath, const WCHAR *Setting);

static BOOLEAN Sxs_CheckCompatLayer_2(
    const WCHAR *DosPath, BOOLEAN User, WCHAR *Text, ULONG Length);

static BOOLEAN Sxs_CheckCompatLayer_3(
    const WCHAR *Text, const WCHAR *Setting);


//---------------------------------------------------------------------------

static NTSTATUS Sxs_NtSetInformationThread(
    HANDLE          ThreadHandle,
    THREADINFOCLASS ThreadInformationClass,
    PVOID           ThreadInformation,
    ULONG           ThreadInformationLength);

static NTSTATUS Sxs_NtCreateTransaction(
    HANDLE *TransactionHandle,
    ACCESS_MASK DesiredAccess,
    OBJECT_ATTRIBUTES *ObjectAttributes,
    void *UnknownParameter04,
    void *UnknownParameter05,
    void *UnknownParameter06,
    void *UnknownParameter07,
    void *UnknownParameter08,
    void *UnknownParameter09,
    void *UnknownParameter10);

static NTSTATUS Sxs_NtOpenTransaction(
    HANDLE *TransactionHandle,
    ACCESS_MASK DesiredAccess,
    OBJECT_ATTRIBUTES *ObjectAttributes,
    void *UnknownParameter04,
    void *UnknownParameter05);

static NTSTATUS Sxs_NtCommitTransaction(
    HANDLE TransactionHandle,
    ULONG_PTR UnknownParameter02);

static NTSTATUS Sxs_NtRollbackTransaction(
    HANDLE TransactionHandle,
    ULONG_PTR UnknownParameter02);

static void Sxs_RtlRaiseException(EXCEPTION_RECORD *ExceptionRecord);

static BOOL Sxs_CheckTokenMembership(
    HANDLE hToken, void *pSid, BOOL *IsMember);


//---------------------------------------------------------------------------


typedef HANDLE (*P_CreateActCtx)(void *pActCtx);

typedef BOOL (*P_QueryActCtx)(DWORD dwFlags, HANDLE hActCtx,
    void *pvSubInstance, ULONG ulInfoClass, void *pvBuffer,
    SIZE_T cbBuffer, SIZE_T *pcbWrittenOrRequired);

typedef BOOL (*P_SxsInstall)(void *info);

typedef NTSTATUS (*P_RtlCreateActivationContext)(
    ULONG_PTR Zero1,
    void *ActivationContextMappedBase,
    ULONG_PTR Zero2,
    ULONG_PTR CallbackRoutine,
    ULONG_PTR CallbackRoutineData,
    HANDLE *OutActCtx);

typedef LANGID (*P_GetUserDefaultUILanguage)(void);

typedef LANGID (*P_SetThreadUILanguage)(WORD LangId);

typedef NTSTATUS (*P_RtlGetThreadPreferredUILanguages)(
    ULONG_PTR Flags, ULONG *Count, WCHAR *Buffer, ULONG *Length);

typedef NTSTATUS (*P_RtlSetThreadPreferredUILanguages)(
    ULONG_PTR Flags, WCHAR *Buffer, ULONG *Count);

typedef void (*P_RtlRaiseException)(EXCEPTION_RECORD *ExceptionRecord);


//---------------------------------------------------------------------------


static P_CreateActCtx               __sys_CreateActCtxW             = NULL;

static P_QueryActCtx                __sys_QueryActCtxW              = NULL;

static P_RtlCreateActivationContext
                                   __sys_RtlCreateActivationContext = NULL;

static P_RtlGetThreadPreferredUILanguages
                             __sys_RtlGetThreadPreferredUILanguages = NULL;

static P_RtlSetThreadPreferredUILanguages
                             __sys_RtlSetThreadPreferredUILanguages = NULL;

static P_SetThreadUILanguage        __sys_SetThreadUILanguage       = NULL;

static P_GetUserDefaultUILanguage   __sys_GetUserDefaultUILanguage  = NULL;

P_NtSetInformationThread            __sys_NtSetInformationThread    = NULL;
static P_NtCreateTransaction        __sys_NtCreateTransaction       = NULL;
static P_NtOpenTransaction          __sys_NtOpenTransaction         = NULL;
static P_NtCommitTransaction        __sys_NtCommitTransaction       = NULL;
static P_NtRollbackTransaction      __sys_NtRollbackTransaction     = NULL;

static P_RtlRaiseException          __sys_RtlRaiseException         = NULL;

static P_SxsInstall                 __sys_SxsInstallW               = NULL;

extern P_NtOpenKey            __sys_NtOpenKey;

//---------------------------------------------------------------------------
// Variables
//---------------------------------------------------------------------------


static BOOLEAN Sxs_UseAltCreateActCtx = FALSE;

static void *Sxs_ActivateDefaultManifest_ImageBase = NULL;

static const WCHAR *Sxs_manifest = L".manifest";
static const WCHAR *Sxs_config   = L".config";

static const WCHAR *Sxs_QueueName = L"RPCSS_SXS";

static const WCHAR *Sxs_Manifest_Length = L"Manifest Length (%d)";

extern const WCHAR *File_BQQB;


//---------------------------------------------------------------------------
// Sxs_AllocOrFreeBuffers
//---------------------------------------------------------------------------


_FX BOOLEAN Sxs_AllocOrFreeBuffers(SXS_ARGS *args, BOOLEAN alloc)
{
    if (alloc) {

        ULONG size = /* SourcePath   */  SXS_PATH_LEN * sizeof(WCHAR)
                   + /* ManifestText */  SXS_TEXT_LEN * sizeof(UCHAR)
                   + /* ConfigPath   */  SXS_PATH_LEN * sizeof(WCHAR)
                   + /* ConfigText   */  SXS_TEXT_LEN * sizeof(UCHAR);

        UCHAR *buf = VirtualAlloc(0, size,
            MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN, PAGE_READWRITE);

        if (! buf)
            return FALSE;

        args->SourcePath = (WCHAR *)buf;
        buf += SXS_PATH_LEN * sizeof(WCHAR);    // skip SourcePath
        args->ManifestText = buf;
        buf += SXS_TEXT_LEN * sizeof(UCHAR);    // skip ManifestText
        args->ConfigPath = (WCHAR *)buf;
        buf += SXS_PATH_LEN * sizeof(WCHAR);    // skip ConfigPath
        args->ConfigText = buf;

    } else {

        if (args->SourcePath)
            VirtualFree(args->SourcePath, 0, MEM_RELEASE);
    }

    return TRUE;
}


//---------------------------------------------------------------------------
// Sxs_GetLanguage
//---------------------------------------------------------------------------


_FX BOOLEAN Sxs_GetLanguage(ACTCTX *ActCtx, SXS_ARGS *args)
{
    BOOLEAN ok;

    //
    // basic parameter checking
    //

    if ((ActCtx->dwFlags & ACTCTX_FLAG_LANGID_VALID) && (! ActCtx->wLangId))
        return FALSE;

    //
    // get language code or language name
    //

    ok = TRUE;

    if (Dll_OsBuild >= 6000) {

        //
        // Windows Vista and later
        //

        ULONG count, len = 256;
        BOOLEAN ok = TRUE;

        args->LangNames = Dll_AllocTemp(len * sizeof(WCHAR));

        if ((! __sys_RtlGetThreadPreferredUILanguages)    ||
            (! __sys_RtlSetThreadPreferredUILanguages)    ||
            (! __sys_SetThreadUILanguage)) {

            return FALSE;
        }

        if (ActCtx->dwFlags & ACTCTX_FLAG_LANGID_VALID) {

            ULONG OrigCount;
            WCHAR *OrigLangNames = Dll_AllocTemp(len * sizeof(WCHAR));

            --len;

            if (__sys_RtlGetThreadPreferredUILanguages(
                    0x08, &OrigCount, OrigLangNames, &len) != 0) {

                ok = FALSE;

            } else {

                if (__sys_SetThreadUILanguage(ActCtx->wLangId) == 0) {

                    ok = FALSE;

                } else {

                    if (__sys_RtlGetThreadPreferredUILanguages(
                            0x38, &count, args->LangNames, &len) != 0) {

                        ok = FALSE;
                    }

                    __sys_RtlSetThreadPreferredUILanguages(
                        0x08, OrigLangNames, &OrigCount);
                }

            }

            Dll_Free(OrigLangNames);

        } else {

            if (__sys_RtlGetThreadPreferredUILanguages(
                    0x38, &count, args->LangNames, &len) != 0) {

                ok = FALSE;
            }
        }

        if (ok) {

            for (count = 0; count < len; ++count) {
                if (args->LangNames[count] == L'\0')
                    args->LangNames[count] = L'_';
            }
            args->LangNames[len] = L'\0';
        }

    } else {

        //
        // Windows XP
        //

        if (ActCtx->dwFlags & ACTCTX_FLAG_LANGID_VALID) {

            args->LangId = ActCtx->wLangId;

        } else {

            args->LangId = __sys_GetUserDefaultUILanguage();
        }
    }

    return ok;
}


//---------------------------------------------------------------------------
// Sxs_GetManifestResource_Helper
//---------------------------------------------------------------------------


_FX BOOL Sxs_GetManifestResource_Helper(
    HMODULE hModule, LPCTSTR lpszType, LPTSTR lpszName, LONG_PTR lParam)
{
    if (lpszType != RT_MANIFEST || (! lpszName))
        return TRUE;
    if (lParam) {
        ULONG_PTR *args = (ULONG_PTR *)lParam;
        if (IS_INTRESOURCE(lpszName))
            args[0] = (ULONG_PTR)lpszName;
        else if (*lpszName == L'#')
            args[0] = (USHORT)_wtoi(lpszName);
        args[1] = (ULONG_PTR) FindResource(hModule, lpszName, lpszType);
    }
    return FALSE;
}


//---------------------------------------------------------------------------
// Sxs_GetManifestResource
//---------------------------------------------------------------------------


_FX BOOL Sxs_GetManifestResource(
    HMODULE hModule, ULONG *ResourceId, HRSRC *hResource)
{
    ULONG_PTR args[2] = { 0, 0 };
    EnumResourceNames(hModule, RT_MANIFEST,
                      Sxs_GetManifestResource_Helper, (LONG_PTR)args);
    if (args[1]) {

        if (ResourceId)
            *ResourceId = (ULONG)args[0];
        if (hResource)
            *hResource = (HRSRC)args[1];

        SetLastError(ERROR_SUCCESS);
        return TRUE;

    } else {

        if (ResourceId)
            *ResourceId = 0;
        if (hResource)
            *hResource = 0;

        SetLastError(ERROR_RESOURCE_TYPE_NOT_FOUND);
        return FALSE;
    }
}


//---------------------------------------------------------------------------
// Sxs_ReadTextAndClose
//---------------------------------------------------------------------------


_FX BOOL Sxs_ReadTextAndClose(HANDLE hFile, UCHAR *Text)
{
    ULONG LastError;
    ULONG len;
    BOOL ok = ReadFile(hFile, Text, SXS_TEXT_LEN, &len, NULL);

    if (ok && ((! len) || (len >= SXS_TEXT_LEN))) {

        if (len >= SXS_TEXT_LEN)
            SbieApi_Log(2205, Sxs_Manifest_Length, len);
        LastError = ERROR_SXS_MANIFEST_FORMAT_ERROR;
        ok = FALSE;

    } else
        LastError = GetLastError();

    CloseHandle(hFile);

    if (ok)
        Text[len] = L'\0';
    else
        SetLastError(LastError);

    return ok;
}


//---------------------------------------------------------------------------
// Sxs_GetPathAndText
//---------------------------------------------------------------------------


_FX void Sxs_ConvertUnicodeToAscii(UCHAR *Text)
{
    UCHAR *ptri, *ptro;
    BOOLEAN warn = FALSE;

    if (Text[0] != 0xFF || Text[1] != 0xFE)
        return;

    ptro = Text;
    ptri = Text + 2;
    while (*ptri) {

        if (ptri[1] != '\0')
            warn = TRUE;

        *ptro = *ptri;
        ++ptro;
        ++ptri;
        ++ptri;
    }
    *ptro = '\0';

    ptri = strstr(Text, "encoding=\"");
    if (ptri) {
        ptri += 10;
        ptro = strchr(ptri, '\"');
        if (ptro && (ptro - ptri) == 5)
            memcpy(ptri, "UTF-8", 5);
        else
            warn = TRUE;
    }

    if (warn)
        SbieApi_Log(2205, L"SXS/UNICODE");
}


//---------------------------------------------------------------------------
// Sxs_GetPathAndText
//---------------------------------------------------------------------------


_FX BOOLEAN Sxs_GetPathAndText(ACTCTX *ActCtx, SXS_ARGS *args)
{
    ULONG LastError;
    ULONG len;
    HANDLE hFile;

    ULONG ResourceId = 0;
    void *ImageBase = NULL;
    BOOLEAN ShouldUnmap = FALSE;
    BOOLEAN IsExe = FALSE;

    if (ActCtx->dwFlags & ACTCTX_FLAG_HMODULE_VALID) {

        //
        // if ACTCTX_FLAG_HMODULE_VALID was passed, we're clearly
        // dealing with an image file
        //

        ImageBase = ActCtx->hModule;

        if (! GetModuleFileName(
                        ImageBase, args->SourcePath, SXS_PATH_LEN - 2))
            return FALSE;

        args->SourcePath[SXS_PATH_LEN - 2] = L'\0';

        if (Sxs_ActivateDefaultManifest_ImageBase == ImageBase) {

            //
            // when called from Sxs_ActivateDefaultManifest, we need to
            // emulate the behavior of the Windows loader and disregard
            // the resource number for external manifest/config files
            //

            IsExe = TRUE;
        }

    } else if (! ActCtx->lpSource) {

        //
        // without the ACTCTX_FLAG_HMODULE_VALID, we require lpSource
        //

        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;

    } else {

        //
        // open lpSource and try to map it as an image
        //

        HANDLE hSection;

        len = wcslen(ActCtx->lpSource);
        if (len > (SXS_PATH_LEN - 2))
            len = SXS_PATH_LEN - 2;
        wmemcpy(args->SourcePath, ActCtx->lpSource, len);
        args->SourcePath[len] = L'\0';

        hFile = CreateFile(
            args->SourcePath, FILE_READ_DATA, FILE_SHARE_READ, NULL,
            OPEN_EXISTING, 0, NULL);

        //
        // we can get a file not found error if lpSource does not specify
        // a path, in which case we retry with lpAssemblyDirectory
        //

        if ((hFile == INVALID_HANDLE_VALUE)
                && (ActCtx->dwFlags & ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID)
                && ActCtx->lpAssemblyDirectory) {

            ULONG dir_len = wcslen(ActCtx->lpAssemblyDirectory);
            if ((dir_len < SXS_PATH_LEN)
                            && (dir_len + len <= (SXS_PATH_LEN - 2))) {

                wmemcpy(args->SourcePath,
                        ActCtx->lpAssemblyDirectory, dir_len);
                args->SourcePath[dir_len] = L'\\';
                wmemcpy(&args->SourcePath[dir_len + 1],
                        ActCtx->lpSource, len);
                args->SourcePath[dir_len + 1 + len] = L'\0';

                hFile = CreateFile(
                    args->SourcePath, FILE_READ_DATA, FILE_SHARE_READ, NULL,
                    OPEN_EXISTING, 0, NULL);
            }
        }

        if (hFile == INVALID_HANDLE_VALUE)
            return FALSE;

        hSection = CreateFileMapping(
                        hFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL);

        if (! hSection) {

            //
            // map failed, which is acceptable if the file is not an image
            //

            LastError = GetLastError();

            if ((LastError != ERROR_BAD_EXE_FORMAT) ||
                    (ActCtx->dwFlags & ACTCTX_FLAG_RESOURCE_NAME_VALID)) {

                CloseHandle(hFile);

                if (LastError == ERROR_BAD_EXE_FORMAT)
                    RtlNtStatusToDosError(STATUS_INVALID_IMAGE_FORMAT);

                SetLastError(LastError);
                return FALSE;

            } else {

                //
                // this is probably a text file
                //

                if (! Sxs_ReadTextAndClose(hFile, args->ManifestText))
                    return FALSE;
            }

        } else {

            //
            // this is an EXE or a DLL file
            //

            CloseHandle(hFile);

            ImageBase = MapViewOfFileEx(
                                    hSection, FILE_MAP_READ, 0, 0, 0, NULL);

            LastError = GetLastError();

            CloseHandle(hSection);

            if (! ImageBase) {

                SetLastError(LastError);
                return FALSE;
            }

            ShouldUnmap = TRUE;
        }
    }

    //
    // for an image source, find resource ID
    //

    if (ImageBase) {

        HRSRC hResource = NULL;
        BOOLEAN ok = FALSE;
        BOOLEAN CheckExternalManifest = FALSE;

        if (ActCtx->dwFlags & ACTCTX_FLAG_RESOURCE_NAME_VALID) {

            if (! ActCtx->lpResourceName) {

                SetLastError(ERROR_INVALID_PARAMETER);
                goto unmap_and_return_false;

            } else {

                if (IS_INTRESOURCE(ActCtx->lpResourceName))
                    ResourceId = (ULONG)(ULONG_PTR)ActCtx->lpResourceName;

                hResource = FindResource(
                    ImageBase, ActCtx->lpResourceName, RT_MANIFEST);

                LastError = GetLastError();
            }

        } else {

            Sxs_GetManifestResource(ImageBase, &ResourceId, &hResource);
            LastError = GetLastError();
        }

        //
        // decide if we should check for an external manifest file
        //

        if (Dll_OsBuild >= 6000) {

            //
            // Windows Vista only looks for external manifest file for
            // the main EXE, and if it has an embedded resource id != 1,
            // or if there is no embedded manifest at all
            //
            // note that behavior should be same as XP if the setting
            // PreferExternalManifest is enabled, but we ignore that
            //

            if (IsExe && ResourceId != 1)
                CheckExternalManifest = TRUE;

        } else {

            //
            // Windows XP:
            // for EXEs, always look for an external manifest file
            // for DLLs, only we found or were given a manifest resource id
            //

            if (ResourceId || IsExe)
                CheckExternalManifest = TRUE;
        }

        //
        // external manifest file can override embedded manifest
        // note that we use ConfigPath as temporary storage
        //

        if (CheckExternalManifest) {

            wcscpy(args->ConfigPath, args->SourcePath);
            args->ConfigPath[SXS_PATH_LEN - 10] = L'\0';

            if ((! IsExe) && ResourceId > 1) {
                len = wcslen(args->ConfigPath);
                args->ConfigPath[len] = L'.';
                _itow(ResourceId, &args->ConfigPath[len + 1], 10);
            }

            wcscat(args->ConfigPath, Sxs_manifest);

            hFile = CreateFile(
                args->ConfigPath, FILE_READ_DATA, FILE_SHARE_READ, NULL,
                OPEN_EXISTING, 0, NULL);

            if (hFile != INVALID_HANDLE_VALUE) {

                if (! Sxs_ReadTextAndClose(hFile, args->ManifestText))
                    goto unmap_and_return_false;

                wcscpy(args->SourcePath, args->ConfigPath);

                hResource = NULL;
                ok = TRUE;
            }
        }

        if (hResource) {

            len = SizeofResource(ImageBase, hResource);
            if ((! len) || (len >= SXS_TEXT_LEN)) {

                if (len >= SXS_TEXT_LEN)
                    SbieApi_Log(2205, Sxs_Manifest_Length, len);
                LastError = ERROR_SXS_MANIFEST_FORMAT_ERROR;

            } else {

                HGLOBAL hGlobal = LoadResource(ImageBase, hResource);
                if (hGlobal) {
                    memcpy(args->ManifestText, LockResource(hGlobal), len);
                    ok = TRUE;
                }
            }
        }

        if (! ok) {
            SetLastError(LastError);
            goto unmap_and_return_false;
        }

        //
        // if we were called from Sxs_ActivateDefaultManifest, then neither
        // ACTCTX_FLAG_RESOURCE_NAME_VALID nor lpResourceName were given.
        // we fill these missing fields now, in case Sxs_CallService fails,
        // and we end up calling the system CreateActCtx.
        //

        if (Sxs_ActivateDefaultManifest_ImageBase == ImageBase) {

            ULONG ActCtxResourceId = ResourceId ? ResourceId : 1;
            ActCtx->lpResourceName = (WCHAR *)(ULONG_PTR)ActCtxResourceId;
            ActCtx->dwFlags |= ACTCTX_FLAG_RESOURCE_NAME_VALID;
        }
    }

    Sxs_ConvertUnicodeToAscii(args->ManifestText);

    //
    // read config file
    //

    wcscpy(args->ConfigPath, args->SourcePath);
    args->ConfigPath[SXS_PATH_LEN - 10] = L'\0';

    if (ImageBase) {

        if ((! IsExe) && ResourceId > 1) {
            len = wcslen(args->ConfigPath);
            args->ConfigPath[len] = L'.';
            _itow(ResourceId, &args->ConfigPath[len + 1], 10);
        }

        wcscat(args->ConfigPath, Sxs_config);

    } else {

        WCHAR *dot = wcsrchr(args->ConfigPath, L'.');
        if (dot && _wcsicmp(dot, Sxs_manifest) == 0)
            wcscpy(dot, Sxs_config);
        else
            wcscat(args->ConfigPath, Sxs_config);
    }

    hFile = CreateFile(
        args->ConfigPath, FILE_READ_DATA, FILE_SHARE_READ, NULL,
        OPEN_EXISTING, 0, NULL);

    if (hFile == INVALID_HANDLE_VALUE)
        *args->ConfigPath = L'\0';

    else if (! Sxs_ReadTextAndClose(hFile, args->ConfigText))
        goto unmap_and_return_false;

    Sxs_ConvertUnicodeToAscii(args->ConfigText);

    //
    // successful exit
    //

    if (ShouldUnmap)
        UnmapViewOfFile(ImageBase);

    SetLastError(ERROR_SUCCESS);
    return TRUE;

    //
    // error exit
    //

unmap_and_return_false:

    if (ShouldUnmap) {
        LastError = GetLastError();
        UnmapViewOfFile(ImageBase);
        SetLastError(LastError);
    }

    return FALSE;
}


//---------------------------------------------------------------------------
// Sxs_CallService
//---------------------------------------------------------------------------


_FX void *Sxs_CallService(SXS_ARGS *args, BOOLEAN *UseAltCreateActCtx)
{
    void *MappedBase = NULL;

    ULONG len1 = sizeof(ULONG)          // signature
               + sizeof(ULONG)
               + sizeof(ULONG)          // ArchId
               + sizeof(ULONG)          // LangId
               + (wcslen(args->LangNames) + 1) * sizeof(WCHAR)
               + (wcslen(args->Directory) + 1) * sizeof(WCHAR)
               + (wcslen(args->SourcePath) + 1) * sizeof(WCHAR)
               + (wcslen(args->ConfigPath) + 1) * sizeof(WCHAR)
               + (strlen(args->ManifestText) + 1) * sizeof(UCHAR)
               + (strlen(args->ConfigText) + 1) * sizeof(UCHAR)
               + sizeof(UCHAR);         // final '*'

    ULONG *buf1 = Dll_AllocTemp(len1);

    WCHAR *ptr2;
    UCHAR *ptr1;

    HANDLE handle;
    ULONG RequestId;
    ULONG status;
    ULONG retries;

    ULONG len2;
    ULONG *buf2;
    LARGE_INTEGER i64;
    ACCESS_MASK DesiredAccess;

    //
    // prepare memory block that will be read by the RpcSs SxS service
    // see Sxs_Generate in RpcSs/sxs.c
    //

    *(ULONG *)(buf1 + 0)   = tzuk;      // signature
    *(ULONG *)(buf1 + 1)   = 0;

    *(ULONG *)(buf1 + 2)   = args->ArchId;
    *(ULONG *)(buf1 + 3)   = args->LangId;

    ptr2 = (WCHAR *)(buf1 + 4);
    wcscpy(ptr2, args->LangNames);
    ptr2 += wcslen(ptr2) + 1;

    wcscpy(ptr2, args->Directory);
    ptr2 += wcslen(ptr2) + 1;

    wcscpy(ptr2, args->SourcePath);
    ptr2 += wcslen(ptr2) + 1;
    wcscpy(ptr2, args->ConfigPath);
    ptr2 += wcslen(ptr2) + 1;

    ptr1 = (UCHAR *)ptr2;
    strcpy(ptr1, args->ManifestText);
    ptr1 += strlen(ptr1) + 1;
    strcpy(ptr1, args->ConfigText);
    ptr1 += strlen(ptr1) + 1;

    *ptr1 = '*';

    //
    // send the request message through an SbieSvc queue
    //

    for (retries = 0; retries < 3; ++retries) {

        status = SbieDll_QueuePutReq(
                            Sxs_QueueName, buf1, len1, &RequestId, &handle);

        if (status == STATUS_OBJECT_NAME_NOT_FOUND) {

            if (Dll_ProcessFlags & SBIE_FLAG_FORCED_PROCESS) {

                status = STATUS_BAD_INITIAL_PC;     // see below
                break;

            } else {

                WCHAR EvtName[128];
                HANDLE EvtHandle;
                wcscpy(EvtName, SBIE_BOXED_);
                wcscat(EvtName, Sxs_QueueName);
                wcscat(EvtName, L"_READY");
                EvtHandle = CreateEvent(NULL, TRUE, FALSE, EvtName);

                SbieDll_StartCOM(FALSE);

                WaitForSingleObject(EvtHandle, 30 * 1000);
                CloseHandle(EvtHandle);

                continue;
            }

        } else if (status != 0)
            break;

        status = WaitForSingleObject(handle, 30 * 1000);
        CloseHandle(handle);
        if (status != 0)
            continue;

        status = SbieDll_QueueGetRpl(Sxs_QueueName, RequestId, &buf2, &len2);

        break;
    }

    Dll_Free(buf1);

    if (status != 0) {

        if (status == STATUS_PRIVILEGE_NOT_HELD) {

            //
            // SbieSvc could not open our caller process, for instance Avira
            // blocks access to its avscan.exe process
            //

            Sxs_UseAltCreateActCtx = TRUE;
            *UseAltCreateActCtx = TRUE;

        } else if (status == STATUS_BAD_INITIAL_PC) {

            //
            // if we are a forced process then we probably don't rely on
            // SXS assemblies in the sandbox, so if the RPCSS/SXS service
            // is not yet ready, then just use the system CreateActCtx
            //

            *UseAltCreateActCtx = TRUE;

        } else {

            SbieApi_Log(2203, L"%S - %S [%08X]",
                        Sxs_QueueName, Dll_ImageName, status);
        }

        return NULL;
    }

    if (len2 == sizeof(ULONG) && *(ULONG *)buf2 == -1) {
        Dll_Free(buf2);
        return NULL;
    }

    //
    // check for special and esoteric case where SandboxieRpcSs
    // is telling us to call the system CreateActCtx instead.
    // see also Sxs_Request and Sxs_GenerateHelper in apps/com/RpcSs/sxs.c
    //

    if (len2 == 20 && memcmp(buf2, "*UseAltCreateActCtx*", 20) == 0) {

        *UseAltCreateActCtx = TRUE;
        Dll_Free(buf2);
        return NULL;
    }

    //
    // create a section and fill it with the data from the reply
    //

    i64.QuadPart = len2;
    DesiredAccess = STANDARD_RIGHTS_REQUIRED
                  | SECTION_QUERY | SECTION_MAP_WRITE | SECTION_MAP_READ;
    status = NtCreateSection(&handle, DesiredAccess, NULL, &i64,
                             PAGE_READWRITE, SEC_COMMIT, NULL);

    if (NT_SUCCESS(status)) {

        const ULONG xViewShare = 1;

        SIZE_T ViewSize = len2;
        i64.QuadPart = 0;

        status = NtMapViewOfSection(
            handle, NtCurrentProcess(), &MappedBase, 0, 0,
            &i64, &ViewSize, xViewShare, MEM_PHYSICAL, PAGE_READWRITE);

        if (NT_SUCCESS(status)) {

            memcpy(MappedBase, buf2, len2);

            NtUnmapViewOfSection(NtCurrentProcess(), MappedBase);

            MappedBase = NULL;
            ViewSize = 0;
            i64.QuadPart = 0;

            status = NtMapViewOfSection(
                handle, NtCurrentProcess(), &MappedBase, 0, 0,
                &i64, &ViewSize, xViewShare, MEM_PHYSICAL, PAGE_READONLY);

            if (! NT_SUCCESS(status))
                MappedBase = 0;
        }

        NtClose(handle);
    }

    Dll_Free(buf2);
    return MappedBase;
}


//---------------------------------------------------------------------------
// Sxs_ActivationContextNotificationRoutine
//---------------------------------------------------------------------------


_FX void Sxs_ActivationContextNotificationRoutine(
    ULONG Operation,
    HANDLE ActivationContext,
    ULONG_PTR MappedViewBaseAddress,
    ULONG_PTR Unknown1,
    ULONG_PTR Unknown2,
    BOOLEAN *pSuccess)
{
    //
    // ideally we would pass kernel32!BasepSxsActivationContextNotification
    // to RtlCreateActivationContext, however that is not an exported symbol
    // and the simplest solution is to duplicate its functionality
    //

    if (Operation == 1) {
        NtUnmapViewOfSection(
            NtCurrentProcess(), (void *)MappedViewBaseAddress);
    } else
        *pSuccess = TRUE;
}


//---------------------------------------------------------------------------
// Sxs_CreateActCtxW
//---------------------------------------------------------------------------


_FX HANDLE Sxs_CreateActCtxW(ACTCTX *ActCtx)
{
    static const WCHAR *_Empty = L"";
    SXS_ARGS args;
    ULONG LastError;
    ULONG_PTR *pActivationContextData = NULL;
    HANDLE hActCtx = INVALID_HANDLE_VALUE;

    //
    // special processing if we are running in the context of SandboxieRpcSs
    //

    if (Sxs_UseAltCreateActCtx)
        return Sxs_CreateActCtxW_Alt(ActCtx);

    //
    // otherwise normal processing
    //

    memzero(&args, sizeof(args));

    //
    // processor architecture
    //

    if (ActCtx->dwFlags & ACTCTX_FLAG_PROCESSOR_ARCHITECTURE_VALID) {

        args.ArchId = ActCtx->wProcessorArchitecture;
        if (args.ArchId != PROCESSOR_ARCHITECTURE_INTEL         &&
            args.ArchId != PROCESSOR_ARCHITECTURE_MSIL          &&
            args.ArchId != PROCESSOR_ARCHITECTURE_AMD64         &&
            args.ArchId != PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 &&
            args.ArchId != PROCESSOR_ARCHITECTURE_UNKNOWN) {

            LastError = ERROR_INVALID_PARAMETER;
            goto finish;
        }

    } else {

#ifdef _WIN64
        args.ArchId = PROCESSOR_ARCHITECTURE_AMD64;
#else
        args.ArchId = (Dll_IsWow64 ? PROCESSOR_ARCHITECTURE_IA32_ON_WIN64
                                   : PROCESSOR_ARCHITECTURE_INTEL);
#endif _WIN64
    }

    //
    // language id and name
    //

    args.LangNames = (WCHAR *)_Empty;

    if (! Sxs_GetLanguage(ActCtx, &args)) {

        LastError = ERROR_INVALID_PARAMETER;
        goto finish;
    }

    //
    // source file and manifest text
    //

    if (! Sxs_AllocOrFreeBuffers(&args, TRUE)) {

        LastError = STATUS_INSUFFICIENT_RESOURCES;
        goto finish;
    }

    if (! Sxs_GetPathAndText(ActCtx, &args)) {

        LastError = GetLastError();
        goto finish;
    }

    //
    // assembly store directory
    //

    if (ActCtx->dwFlags & ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID) {

        args.Directory = (WCHAR *)ActCtx->lpAssemblyDirectory;

    } else {

        WCHAR *ptr;

        ULONG len = wcslen(args.SourcePath);
        args.Directory = Dll_AllocTemp((len + 4) * sizeof(WCHAR));
        wmemcpy(args.Directory, args.SourcePath, len + 1);

        ptr = wcsrchr(args.Directory, L'\\');
        if (ptr)
            *ptr = L'\0';
        else {

            LastError = ERROR_INVALID_PARAMETER;
            goto finish;
        }
    }

    //
    // prepare to set process default activation context
    //

    if (ActCtx->dwFlags & ACTCTX_FLAG_SET_PROCESS_DEFAULT) {

#ifdef _WIN64
        pActivationContextData = (ULONG_PTR *)(NtCurrentPeb() + 0x2F8);
#else
        pActivationContextData = (ULONG_PTR *)(NtCurrentPeb() + 0x1F8);
#endif _WIN64

        if (*pActivationContextData) {

            LastError = ERROR_SXS_PROCESS_DEFAULT_ALREADY_SET;
            goto finish;
        }
    }

    //
    // generate activation context data and create a handle to it
    //

    if (! __sys_RtlCreateActivationContext) {

        LastError = ERROR_PROC_NOT_FOUND;
        goto finish;

    } else {

        BOOLEAN UseAltCreateActCtx = FALSE;
        void *MappedBase = Sxs_CallService(&args, &UseAltCreateActCtx);

        if (MappedBase) {

            //
            // process result from SXS service in SandboxieRpcSs
            //

            NTSTATUS status = __sys_RtlCreateActivationContext(
                0, MappedBase, 0,
                (ULONG_PTR)Sxs_ActivationContextNotificationRoutine, 0,
                &hActCtx);

            if (! NT_SUCCESS(status)) {

                NtUnmapViewOfSection(NtCurrentProcess(), (void *)MappedBase);

            } else if (pActivationContextData) {

                *pActivationContextData = (ULONG_PTR)MappedBase;
            }

        } else if (UseAltCreateActCtx) {

            //
            // we get here if Sxs_CallService cannot talk to RPCSS/SXS
            //

            hActCtx = Sxs_CreateActCtxW_Alt(ActCtx);
            LastError = GetLastError();
            goto finish;
        }
    }

    if (hActCtx == INVALID_HANDLE_VALUE) {

        LastError = ERROR_SXS_CANT_GEN_ACTCTX;
        goto finish;
    }

    //
    // finish
    //

finish:

    if (args.LangNames && args.LangNames != _Empty)
        Dll_Free(args.LangNames);

    Sxs_AllocOrFreeBuffers(&args, FALSE);

    if (args.Directory &&
                    args.Directory != (WCHAR *)ActCtx->lpAssemblyDirectory)
        Dll_Free(args.Directory);

    SetLastError(LastError);
    return hActCtx;
}


//---------------------------------------------------------------------------
// Sxs_CreateActCtxW_Alt
//---------------------------------------------------------------------------


_FX HANDLE Sxs_CreateActCtxW_Alt(ACTCTX *ActCtx)
{
    //
    // the SandboxieRpcSs process implements our SXS service, so it is
    // not reliable to use our SXS service from the SandboxieRpcSs process
    // for at least two reasons.
    //
    // - the service pipe may not be ready for use yet
    // - even if the service pipe (and related thread) are ready,
    // some other thread in the SandboxieRpcSs process may block the
    // request.  for example, by holding the loader lock.
    //
    // workaround:  in the context of RpcSs, use the real SXS from CSRSS.
    //

    THREAD_DATA *TlsData = Dll_GetTlsData(NULL);
    ACTCTX MyActCtx;
    WCHAR *MySource = NULL;
    HANDLE hActCtx;
    ULONG LastError;

    //
    // get the real path to lpSource if it specifies a file in the sandbox
    //

    if (ActCtx->lpSource) {

        HANDLE hFile = CreateFileW(ActCtx->lpSource, GENERIC_READ,
                   FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);

        if (hFile != INVALID_HANDLE_VALUE) {

            NTSTATUS status;
            BOOLEAN IsBoxedPath;

            MySource = Dll_AllocTemp(sizeof(WCHAR) * 8192);

            status = SbieDll_GetHandlePath(hFile, MySource, &IsBoxedPath);

            CloseHandle(hFile);

            if (NT_SUCCESS(status) && IsBoxedPath) {

                if (SbieDll_TranslateNtToDosPath(MySource)) {

                    memcpy(&MyActCtx, ActCtx, sizeof(ACTCTX));
                    MyActCtx.lpSource = MySource;
                    ActCtx = &MyActCtx;

                    ++TlsData->proc_create_process;
                }
            }
        }
    }

    //
    // invoke the system service and finish.  note that we use the
    // proc_create_process flag to prevent File_RtlGetFullPathName_U
    // from changing the adjusted lpSource back into true path form
    //

    hActCtx = __sys_CreateActCtxW(ActCtx);

    LastError = GetLastError();

    if (ActCtx == &MyActCtx)
        --TlsData->proc_create_process;

    if (MySource)
        Dll_Free(MySource);

    SetLastError(LastError);
    return hActCtx;
}


//---------------------------------------------------------------------------
// Sxs_QueryActCtxW
//---------------------------------------------------------------------------


_FX BOOL Sxs_QueryActCtxW(DWORD dwFlags, HANDLE hActCtx,
    void *pvSubInstance, ULONG ulInfoClass, void *pvBuffer,
    SIZE_T cbBuffer, SIZE_T *pcbWrittenOrRequired)
{
    ULONG err;
    BOOL ok;

    ok = __sys_QueryActCtxW(dwFlags, hActCtx, pvSubInstance, ulInfoClass,
                            pvBuffer, cbBuffer, pcbWrittenOrRequired);
    err = GetLastError();

    if (ok && (! dwFlags) && (! hActCtx) && (! pvSubInstance) &&
            ulInfoClass == ActivationContextDetailedInformation) {

        ACTIVATION_CONTEXT_DETAILED_INFORMATION *info =
            (ACTIVATION_CONTEXT_DETAILED_INFORMATION *)pvBuffer;

        Sxs_QueryActCtxW_2( (ULONG *)&info->ulRootManifestPathChars,
                           (WCHAR **)&info->lpRootManifestPath);

        Sxs_QueryActCtxW_2( (ULONG *)&info->ulRootConfigurationPathChars,
                           (WCHAR **)&info->lpRootConfigurationPath);

        Sxs_QueryActCtxW_2( (ULONG *)&info->ulAppDirPathChars,
                           (WCHAR **)&info->lpAppDirPath);
    }

    SetLastError(err);
    return ok;
}


//---------------------------------------------------------------------------
// Sxs_QueryActCtxW_2
//---------------------------------------------------------------------------


_FX void Sxs_QueryActCtxW_2(ULONG *p_len, WCHAR **p_path)
{
    if (*p_len && *p_path) {

        WCHAR *TruePath = File_GetTruePathForBoxedPath(*p_path, TRUE);
        if (TruePath) {

            if ((*p_path)[*p_len - 1] == L'\\') {

                ULONG TruePath_len = wcslen(TruePath);
                if (TruePath_len && TruePath[TruePath_len - 1] != L'\\') {

                    WCHAR *TruePath2 =
                        Dll_AllocTemp((TruePath_len + 2) * sizeof(WCHAR));
                    wmemcpy(TruePath2, TruePath, TruePath_len);
                    TruePath2[TruePath_len] = L'\\';
                    TruePath2[TruePath_len + 1] = L'\0';

                    Dll_Free(TruePath);
                    TruePath = TruePath2;
                }
            }
        }

        if (TruePath) {

            ULONG TruePath_len = wcslen(TruePath);
            if (TruePath_len <= *p_len) {

                wmemcpy(*p_path, TruePath, TruePath_len + 1);
                *p_len = TruePath_len;
            }

            Dll_Free(TruePath);
        }
    }
}


//---------------------------------------------------------------------------
// Sxs_NtSetInformationThread
//---------------------------------------------------------------------------


_FX NTSTATUS Sxs_NtSetInformationThread(
    HANDLE          ThreadHandle,
    THREADINFOCLASS ThreadInformationClass,
    PVOID           ThreadInformation,
    ULONG           ThreadInformationLength)
{
    return __sys_NtSetInformationThread(ThreadHandle,
        ThreadInformationClass,
        ThreadInformation,
        ThreadInformationLength);
}


//---------------------------------------------------------------------------
// Sxs_NtCreateTransaction
//---------------------------------------------------------------------------


_FX NTSTATUS Sxs_NtCreateTransaction(
    HANDLE *TransactionHandle,
    ACCESS_MASK DesiredAccess,
    OBJECT_ATTRIBUTES *ObjectAttributes,
    void *UnknownParameter04,
    void *UnknownParameter05,
    void *UnknownParameter06,
    void *UnknownParameter07,
    void *UnknownParameter08,
    void *UnknownParameter09,
    void *UnknownParameter10)
{
    *TransactionHandle = NULL;
    return STATUS_SUCCESS;
}



//---------------------------------------------------------------------------
// Sxs_NtOpenTransaction
//---------------------------------------------------------------------------


_FX NTSTATUS Sxs_NtOpenTransaction(
    HANDLE *TransactionHandle,
    ACCESS_MASK DesiredAccess,
    OBJECT_ATTRIBUTES *ObjectAttributes,
    void *UnknownParameter04,
    void *UnknownParameter05)
{
    *TransactionHandle = NULL;
    return STATUS_SUCCESS;
}


//---------------------------------------------------------------------------
// Sxs_NtCommitTransaction
//---------------------------------------------------------------------------


_FX NTSTATUS Sxs_NtCommitTransaction(
    HANDLE TransactionHandle,
    ULONG_PTR UnknownParameter02)
{
    return STATUS_SUCCESS;
}


//---------------------------------------------------------------------------
// Sxs_NtRollbackTransaction
//---------------------------------------------------------------------------


_FX NTSTATUS Sxs_NtRollbackTransaction(
    HANDLE TransactionHandle,
    ULONG_PTR UnknownParameter02)
{
    return STATUS_SUCCESS;
}


//---------------------------------------------------------------------------
// Sxs_RtlRaiseException
//---------------------------------------------------------------------------


_FX void Sxs_RtlRaiseException(EXCEPTION_RECORD *ExceptionRecord)
{
    ULONG code = ExceptionRecord->ExceptionCode;
    if ((code & 0xFFFF0000) != 0x40010000)  // ignore debug exceptions
        SbieApi_Log(2205, L"TrustedInstaller %08X", code);
    __sys_RtlRaiseException(ExceptionRecord);
}


//---------------------------------------------------------------------------
// Sxs_CheckTokenMembership
//---------------------------------------------------------------------------


_FX BOOL Sxs_CheckTokenMembership(
    HANDLE hToken, void *pSid, BOOL *IsMember)
{
    *IsMember = TRUE;
    SetLastError(ERROR_SUCCESS);
    return TRUE;
}


//---------------------------------------------------------------------------
// Sxs_SxsInstallW
//---------------------------------------------------------------------------


_FX BOOL Sxs_SxsInstallW(void *info)
{
    BOOL ok = __sys_SxsInstallW(info);
    if (! ok) {
        SbieApi_Log(2205, L"SxsInstallW");
        SetLastError(0);
        ok = TRUE;
    }
    return ok;
}


//---------------------------------------------------------------------------
// Sxs_InitKernel32
//---------------------------------------------------------------------------


_FX BOOLEAN Sxs_InitKernel32(void)
{
    HMODULE module;
    void *CreateActCtxW;
    void *NtSetInformationThread;
    void *NtCreateTransaction;
    void *NtOpenTransaction;
    void *NtCommitTransaction;
    void *NtRollbackTransaction;
    void *RtlRaiseException;

    //
    // CreateActCtx function is only supported in Windows XP, so hook
    // only if we find it in kernel32.dll (kernelbase.dll on Windows 8)
    //
    // note that the real CreateActCtxA internally calls CreateActCtxW
    // after doing ANSI-to-UNICODE conversion, so we need not hook it
    //

    if (Dll_OsBuild >= 8400)
        module = Dll_KernelBase;
    else
        module = Dll_Kernel32;

    CreateActCtxW = GetProcAddress(module, "CreateActCtxW");

    if (CreateActCtxW) {

        //
        // import stuff that CreateActCtx is going to need
        //

#define SXS_IMPORT(b,a) __sys_##a = (P_##a) GetProcAddress(b, #a);

        SXS_IMPORT(Dll_Ntdll, RtlCreateActivationContext);

        SXS_IMPORT(Dll_Kernel32, GetUserDefaultUILanguage);

        if (Dll_OsBuild >= 6000) {

            SXS_IMPORT(Dll_Ntdll, RtlGetThreadPreferredUILanguages);

            SXS_IMPORT(Dll_Ntdll, RtlSetThreadPreferredUILanguages);

            SXS_IMPORT(Dll_Kernel32, SetThreadUILanguage);
        }

#undef  SXS_IMPORT

        //
        // disable in-sandbox SXS resolutions in three cases:
        //
        // if this is the SandboxieRpcSs process which itself implements
        // the in-sandbox SXS service
        //
        // if the sandbox setting DisableBoxedWinSxS=y
        //

        if (Dll_ImageType == DLL_IMAGE_SANDBOXIE_RPCSS ||

                Config_GetSettingsForImageName_bool(L"DisableBoxedWinSxS", FALSE)) {

            Sxs_UseAltCreateActCtx = TRUE;
        }

        //
        // hook CreateActCtx
        //

        SBIEDLL_HOOK(Sxs_,CreateActCtxW);
    }

    //
    // hook QueryActCtx in the context of a forced program because because
    // the activation context might have been with sandboxed paths
    //

    if (Dll_ProcessFlags & SBIE_FLAG_FORCED_PROCESS) {

        void *QueryActCtxW = GetProcAddress(module, "QueryActCtxW");
        if (QueryActCtxW) {

            SBIEDLL_HOOK(Sxs_,QueryActCtxW);
        }
    }

    module = Dll_Ntdll;

    //
    // Opera hooks NtSetInformationThread. SboxDll calls __sys_NtSetInformationThread to bypass Opera hook.
    // See the comment about Thread_SetInformationThread_ChangeNotifyToken in Gui_ConnectToWindowStationAndDesktop
    //

    NtSetInformationThread = GetProcAddress(Dll_Ntdll, "NtSetInformationThread");
    if (NtSetInformationThread) {
        SBIEDLL_HOOK(Sxs_, NtSetInformationThread);
    }

    //
    // place ntdll.dll hooks only if TrustedInstaller
    //

    if (Dll_ImageType != DLL_IMAGE_TRUSTED_INSTALLER)
        return TRUE;

    NtCreateTransaction   = GetProcAddress(module, "NtCreateTransaction");
    NtOpenTransaction     = GetProcAddress(module, "NtOpenTransaction");
    NtCommitTransaction   = GetProcAddress(module, "NtCommitTransaction");
    NtRollbackTransaction = GetProcAddress(module, "NtRollbackTransaction");
    RtlRaiseException     = GetProcAddress(module, "RtlRaiseException");

    if (NtCreateTransaction) {
        SBIEDLL_HOOK(Sxs_,NtCreateTransaction);
    }
    if (NtOpenTransaction) {
        SBIEDLL_HOOK(Sxs_,NtOpenTransaction);
    }
    if (NtCommitTransaction) {
        SBIEDLL_HOOK(Sxs_,NtCommitTransaction);
    }
    if (NtRollbackTransaction) {
        SBIEDLL_HOOK(Sxs_,NtRollbackTransaction);
    }
    if (RtlRaiseException) {
        SBIEDLL_HOOK(Sxs_,RtlRaiseException);
    }

    //
    // In Windows 7, the TrustedInstaller service mounts HKLM\COMPONENTS
    // if the registry key/hive is not present.  Which means it will not
    // mount the hive when the corresponding sandbox key has been created.
    // to work around this, we try to explicitly mount the hive here.
    //

    if (Dll_OsBuild >= 7600) {

        OBJECT_ATTRIBUTES TargetObjectAttributes;
        OBJECT_ATTRIBUTES SourceObjectAttributes;
        UNICODE_STRING TargetObjectName;
        UNICODE_STRING SourceObjectName;

        WCHAR buf[MAX_PATH + 48];
        GetSystemWindowsDirectory(buf + 4, MAX_PATH);
        wmemcpy(buf, File_BQQB, 4);
        wcscat(buf, L"\\System32\\config\\COMPONENTS");
        RtlInitUnicodeString(&SourceObjectName, buf);
        RtlInitUnicodeString(&TargetObjectName,
            L"\\REGISTRY\\MACHINE\\COMPONENTS");

        InitializeObjectAttributes(&SourceObjectAttributes,
            &SourceObjectName, OBJ_CASE_INSENSITIVE, NULL, NULL);
        InitializeObjectAttributes(&TargetObjectAttributes,
            &TargetObjectName, OBJ_CASE_INSENSITIVE, NULL, NULL);

        NtLoadKey(&TargetObjectAttributes, &SourceObjectAttributes);
    }

    //
    // on Windows 8, hook kernelbase!CheckTokenMembership which is
    // called from TiWorker!AclCheckTrustedInstallerAccess
    //

    if (Dll_OsBuild >= 8400) {

        module = Dll_KernelBase;

        typedef BOOL (*P_CheckTokenMembership)(
            HANDLE hToken, void *pSid, BOOL *IsMember);
        P_CheckTokenMembership CheckTokenMembership =
            (P_CheckTokenMembership) GetProcAddress(
                Dll_KernelBase, "CheckTokenMembership");
        P_CheckTokenMembership __sys_CheckTokenMembership;

        SBIEDLL_HOOK(Sxs_,CheckTokenMembership);
    }

    return TRUE;
}


//---------------------------------------------------------------------------
// Sxs_Init
//---------------------------------------------------------------------------


_FX BOOLEAN Sxs_Init(HMODULE module)
{
    P_SxsInstall SxsInstallW;

    SxsInstallW = (P_SxsInstall)GetProcAddress(module, "SxsInstallW");

    SBIEDLL_HOOK(Sxs_,SxsInstallW);

    return TRUE;
}


//---------------------------------------------------------------------------
// Sxs_ActivateDefaultManifest
//---------------------------------------------------------------------------


_FX void Sxs_ActivateDefaultManifest(void *ImageBase)
{
    if (! __sys_CreateActCtxW)
        return;

    if (Dll_ProcessFlags & SBIE_FLAG_FORCED_PROCESS) {

        //
        // if we are a forced process then csrss already applied the
        // process default activation context
        //

        return;
    }

    //
    // reset the process default activation context that was created
    // by our dummy manifest/config files.  but note that don't do this
    // if we are a ForcedProcessComServer where the activaton context
    // was already set by csrss.
    //

    if (! Sxs_UseAltCreateActCtx) {

        ULONG_PTR *pActivationContextData;

#ifdef _WIN64
        pActivationContextData = (ULONG_PTR *)(NtCurrentPeb() + 0x2F8);
#else
        pActivationContextData = (ULONG_PTR *)(NtCurrentPeb() + 0x1F8);
#endif _WIN64

        *pActivationContextData = 0;
    }

    //
    // create and activate process default activation context
    //

    if (! Sxs_UseAltCreateActCtx) {

        ACTCTX ActCtx;

        WCHAR *DosPath =
            Dll_Alloc((wcslen(Ldr_ImageTruePath) + 4) * sizeof(WCHAR));
        wcscpy(DosPath, Ldr_ImageTruePath);
        SbieDll_TranslateNtToDosPath(DosPath);

        memzero(&ActCtx, sizeof(ACTCTX));
        ActCtx.cbSize = sizeof(ACTCTX);
        ActCtx.dwFlags = ACTCTX_FLAG_HMODULE_VALID
                       | ACTCTX_FLAG_SET_PROCESS_DEFAULT;
        ActCtx.hModule = ImageBase;
        ActCtx.lpSource = DosPath;

        Sxs_ActivateDefaultManifest_ImageBase = ImageBase;

        Sxs_CreateActCtxW(&ActCtx);

        Sxs_ActivateDefaultManifest_ImageBase = NULL;

        Dll_Free(DosPath);
    }
}


//---------------------------------------------------------------------------
// Sxs_CheckManifestForElevation
//---------------------------------------------------------------------------


_FX ULONG Sxs_CheckManifestForElevation(
    const WCHAR* DosPath, 
    BOOLEAN *pAsInvoker, 
    BOOLEAN *pRequireAdministrator, 
    BOOLEAN *pHighestAvailable)
{
    ACTCTX ActCtx;
    SXS_ARGS args;
    ULONG rc;

    if (Dll_OsBuild < 6000)
        return STATUS_NOT_IMPLEMENTED;

    //
    // invoke Sxs_GetPathAndText to get the manifest text
    //

    memzero(&args, sizeof(args));

    if (! Sxs_AllocOrFreeBuffers(&args, TRUE))
        return STATUS_INSUFFICIENT_RESOURCES;

    memzero(&ActCtx, sizeof(ACTCTX));
    ActCtx.cbSize = sizeof(ACTCTX);
    ActCtx.lpSource = DosPath;

    rc = STATUS_UNSUCCESSFUL;

    if (Sxs_GetPathAndText(&ActCtx, &args)) {

        rc = STATUS_SUCCESS; // manifest found

        _strlwr(args.ManifestText);

        if (pAsInvoker) *pAsInvoker = 
            (strstr(args.ManifestText, "level='asinvoker'") 
            || strstr(args.ManifestText, "level=\"asinvoker\""));

        if (pRequireAdministrator) *pRequireAdministrator = 
            (strstr(args.ManifestText, "level='requireadministrator'")
            || strstr(args.ManifestText, "level=\"requireadministrator\""));

        if (pHighestAvailable) *pHighestAvailable = 
            (strstr(args.ManifestText, "level='highestavailable'")
            || strstr(args.ManifestText, "level=\"highestavailable\""));
    }

    Sxs_AllocOrFreeBuffers(&args, FALSE);

    return rc;
}


//---------------------------------------------------------------------------
// Sxs_CheckManifestForCreateProcess
//---------------------------------------------------------------------------


_FX ULONG Sxs_CheckManifestForCreateProcess(const WCHAR *DosPath)
{
    THREAD_DATA *TlsData = Dll_GetTlsData(NULL);
    ULONG rc, ElvType;
    BOOLEAN AsInvoker, RequireAdministrator, HighestAvailable;

    //
    // Windows Vista UAC auto-elevates program names that includes words
    // like setup/installer/update.  a program may specify
    //              requestedExecutionLevel level="asInvoker"
    // in its manifest to inhibit auto-elevation.  but since we generally
    // use an empty manifest file, it interferes with the asInvoker request.
    //
    // in this function we examine the real manifest, and if it specifies
    // asInvoker then we will set a flag to use alternate manifest/config
    // files that also specify asInvoker.
    //

    TlsData->proc_create_process_as_invoker = FALSE;

    if (! __sys_CreateActCtxW)
        return 0;
    if (Dll_OsBuild < 6000)
        return 0;

    //
    // if we are already administrator then pretend asInvoker
    // was used, to prevent any more interference from UAC
    //

    ElvType = SbieDll_GetTokenElevationType();

    if (ElvType == TokenElevationTypeFull) {
        TlsData->proc_create_process_as_invoker = TRUE;
        return 0;
    }

    rc = Sxs_CheckManifestForElevation(DosPath, &AsInvoker, &RequireAdministrator, &HighestAvailable);

    if (NT_SUCCESS(rc)) {

        //
        // asInvoker means to use alternate manifest files in
        // Sxs_FileCallback, see there
        //
        // highestAvailable or requireAdministrator means we tell
        // our Proc_CreateProcess caller to use SH32_DoRunAs
        //

        if (AsInvoker)
            TlsData->proc_create_process_as_invoker = TRUE;

        if (RequireAdministrator ||
                (HighestAvailable && ElvType != TokenElevationTypeDefault)) {

            //
            // if we are running in the SbieSvc UAC elevation process
            // then we cannot return ERROR_ELEVATION_REQUIRED because
            // that would cause an infinite loop
            //

            if (Dll_ImageType != DLL_IMAGE_SANDBOXIE_SBIESVC) {

                if (Sxs_CheckCompatLayer(DosPath, L"RunAsInvoker"))
                    TlsData->proc_create_process_as_invoker = TRUE;
                else
                    rc = ERROR_ELEVATION_REQUIRED;
            }
        }
    }

    return rc;
}


//---------------------------------------------------------------------------
// Sxs_PreferExternal
//---------------------------------------------------------------------------


_FX BOOLEAN Sxs_PreferExternal(THREAD_DATA *TlsData)
{
    if (!TlsData->proc_image_path)
        return FALSE;

    //
    // KB5014019 breaks Edge, it seems that making Edge to start its child processes with
    // PreferExternalManifest fixes the issue, but the main process must be loaded normally
    // hence we have ExternalManifestHack that checks the parent and not the target
    //

    if (Config_GetSettingsForImageName_bool(L"ExternalManifestHack", FALSE))
        return TRUE;

    WCHAR *ptr1 = wcsrchr(TlsData->proc_image_path, L'\\');

    WCHAR value[16];
    SbieDll_GetSettingsForName(NULL, ptr1 + 1, L"PreferExternalManifest", value, sizeof(value), NULL);
    return Config_String2Bool(value, FALSE);
}


//---------------------------------------------------------------------------
// Sxs_KeyCallback
//---------------------------------------------------------------------------


_FX BOOLEAN Sxs_KeyCallback(const WCHAR *path, HANDLE *out_handle)
{
    //
    // this callback is invoked by Key_NtCreateKey when it is used during
    // CreateProcess.  this allows us to intercept the open for SideBySide
    // key so we can redirect to the SbieSvc service key which includes
    // a pre-set value for PreferExternalManifest
    //

    THREAD_DATA *TlsData = Dll_GetTlsData(NULL);

    if (!Sxs_PreferExternal(TlsData))
        return FALSE;

    if (_wcsnicmp(path, L"\\Registry\\Machine\\", 18) == 0) {

        BOOLEAN redirect = FALSE;

        path += 18;
        if (_wcsnicmp(path, L"Components", 10) == 0)
            redirect = TRUE;

        else if (_wcsnicmp(path, L"Software\\", 9) == 0) {

            path += 9;

            if (_wcsnicmp(path, L"Wow6432Node\\", 12) == 0)
                path += 12;

            if (0 == _wcsicmp(path,
                    L"Microsoft\\Windows\\CurrentVersion\\SideBySide"))
                redirect = TRUE;
        }

        if (redirect) {

            const WCHAR *ValueName = L"PreferExternalManifest";
            extern WCHAR *Support_SbieSvcKeyPath;

            UNICODE_STRING objname;
            OBJECT_ATTRIBUTES objattrs;
            HANDLE handle;

            InitializeObjectAttributes(
                &objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, NULL);

            RtlInitUnicodeString(&objname, Support_SbieSvcKeyPath);

            if (0 != __sys_NtOpenKey(&handle, KEY_READ, &objattrs))
                handle = NULL;

            if (handle) {

                union {
                    KEY_VALUE_PARTIAL_INFORMATION kvpi;
                    ULONG space[6];
                } info;
                ULONG len;

                RtlInitUnicodeString(&objname, ValueName);

                if (0 != NtQueryValueKey(
                        handle, &objname, KeyValuePartialInformation,
                        &info, sizeof(info), &len))
                    info.kvpi.Type = 0;

                if (info.kvpi.Type == REG_DWORD             &&
                    info.kvpi.DataLength == sizeof(ULONG)   &&
                    *(ULONG *)info.kvpi.Data != 0) {

                    //WCHAR txt[1024];
                    //Sbie_snwprintf(txt, 1024, L"REDIR KEY - %s\n", path);
                    //OutputDebugString(txt);

                    *out_handle = handle;
                    return TRUE;
                }

                CloseHandle(handle);
            }

            SbieApi_Log(2205, ValueName);
        }
    }

    return FALSE;
}


//---------------------------------------------------------------------------
// Sxs_FileCallback
//---------------------------------------------------------------------------


_FX BOOLEAN Sxs_FileCallback(const WCHAR *path, HANDLE *out_handle)
{
    //
    // this callback is invoked by File_NtCreateFile when it is used during
    // CreateProcess.  this allows us to intercept any opens for .manifest
    // and .config files and redirect them to our dummy/empty manifest from
    // our installation home directory
    //

    THREAD_DATA *TlsData = Dll_GetTlsData(NULL);

    if (!Sxs_PreferExternal(TlsData))
        return FALSE;

    const WCHAR *_Manifest_Txt = L"\\Manifest1.txt";
    const WCHAR *_Config_Txt   = L"\\Manifest2.txt";
    const WCHAR *_Empty_Txt    = L"\\Manifest0.txt";
    const WCHAR *FileName = NULL;
    ULONG  FileSize = 0;

    WCHAR *ptr1 = wcsrchr(TlsData->proc_image_path, L'\\');
    WCHAR *ptr2 = wcsrchr(path, L'\\');
    ULONG len = 0;
    if (ptr1)
        len = wcslen(ptr1);

    if (len && ptr2 && _wcsnicmp(ptr1, ptr2, len) == 0) {

        if (_wcsicmp(ptr2 + len, Sxs_manifest) == 0) {
            FileName = _Manifest_Txt;
            FileSize = 364;

        } else if (_wcsicmp(ptr2 + len, Sxs_config) == 0) {
            FileName = _Config_Txt;
            FileSize = 92;
        }
    }

    if (FileName && (! TlsData->proc_create_process_as_invoker)) {

        //
        // if the EXE does not explicitly say in its manifest:
        //          requestedExecutionLevel level="asInvoker"
        // then we use the empty manifest/config files, to allow
        // UAC to use its auto-elevation heuristics
        //

        FileName = _Empty_Txt;
        FileSize = 2;
    }

    if (FileName) {

        HANDLE handle;

        WCHAR *FilePath = Dll_AllocTemp(MAX_PATH * 2 * sizeof(WCHAR));
        wcscpy(FilePath, Dll_HomeDosPath);
        wcscat(FilePath, FileName);

        //OutputDebugString(L"*** *** ***\n");
        //OutputDebugString(FilePath);
        //OutputDebugString(L"*** *** ***\n");

        handle = CreateFile(FilePath, FILE_READ_DATA, FILE_SHARE_READ,
                            NULL, OPEN_EXISTING, 0, NULL);

        Dll_Free(FilePath);

        if (handle == INVALID_HANDLE_VALUE)
            handle = NULL;

        if (handle) {

            IO_STATUS_BLOCK IoStatusBlock;
            FILE_NETWORK_OPEN_INFORMATION open_info;

            NTSTATUS status = NtQueryInformationFile(
                handle, &IoStatusBlock, &open_info,
                sizeof(FILE_NETWORK_OPEN_INFORMATION),
                FileNetworkOpenInformation);

            if (NT_SUCCESS(status) &&
                            open_info.EndOfFile.QuadPart == FileSize) {

                //WCHAR txt[1024];
                //Sbie_snwprintf(txt, 1024, L"REDIR FILE - %s\n", path);
                //OutputDebugString(txt);

                *out_handle = handle;
                return TRUE;
            }

            CloseHandle(handle);
        }

        SbieApi_Log(2205, FileName + 1);
    }

    return FALSE;
}


//---------------------------------------------------------------------------
// Sxs_CheckCompatLayer
//---------------------------------------------------------------------------


_FX BOOLEAN Sxs_CheckCompatLayer(const WCHAR *DosPath, const WCHAR *Setting)
{
    BOOLEAN HaveSetting = FALSE;

    WCHAR *text = Dll_AllocTemp(1024 * sizeof(WCHAR));

    text[0] = 0;
    GetEnvironmentVariable(L"__COMPAT_LAYER", text, 1020);
    if (text[0]) {
        if (Sxs_CheckCompatLayer_3(text, Setting))
            HaveSetting = TRUE;
    }

    if (! HaveSetting) {

        if (Sxs_CheckCompatLayer_2(DosPath, TRUE, text, 1020)) {
            if (Sxs_CheckCompatLayer_3(text, Setting))
                HaveSetting = TRUE;
        }
    }

    if (! HaveSetting) {

        if (Sxs_CheckCompatLayer_2(DosPath, FALSE, text, 1020)) {
            if (Sxs_CheckCompatLayer_3(text, Setting))
                HaveSetting = TRUE;
        }
    }

    Dll_Free(text);
    return HaveSetting;
}


//---------------------------------------------------------------------------
// Sxs_CheckCompatLayer_2
//---------------------------------------------------------------------------


_FX BOOLEAN Sxs_CheckCompatLayer_2(
    const WCHAR *DosPath, BOOLEAN User, WCHAR *Text, ULONG Length)
{
    extern const WCHAR *Key_Registry;
    extern const WCHAR *Key_UserCurrent;

    UNICODE_STRING objname;
    OBJECT_ATTRIBUTES objattrs;
    HANDLE handle;
    BOOLEAN HaveKey = FALSE;

    wcscpy(Text, Key_Registry);
    if (User)
        wcscat(Text, Key_UserCurrent);
    else
        wcscat(Text, L"\\machine");
    wcscat(Text, L"\\software\\Microsoft\\Windows NT"
                 L"\\CurrentVersion\\AppCompatFlags\\Layers");

    RtlInitUnicodeString(&objname, Text);

    InitializeObjectAttributes(
        &objattrs, &objname, OBJ_CASE_INSENSITIVE, NULL, NULL);

    if (0 == __sys_NtOpenKey(&handle, KEY_READ, &objattrs)) {

        RtlInitUnicodeString(&objname, DosPath);
        Length *= sizeof(WCHAR);

        if (0 == NtQueryValueKey(
                    handle, &objname, KeyValuePartialInformation,
                    Text, Length, &Length)) {

            KEY_VALUE_PARTIAL_INFORMATION *kvpi =
                                (KEY_VALUE_PARTIAL_INFORMATION *)Text;
            Length = kvpi->DataLength / sizeof(WCHAR);
            wmemmove(Text, (WCHAR *)kvpi->Data, Length);
            Text[Length] = L'\0';

            HaveKey = TRUE;
        }

        NtClose(handle);
    }

    return HaveKey;
}


//---------------------------------------------------------------------------
// Sxs_CheckCompatLayer_3
//---------------------------------------------------------------------------


_FX BOOLEAN Sxs_CheckCompatLayer_3(const WCHAR *Text, const WCHAR *Setting)
{
    ULONG SettingLen = wcslen(Setting);
    const WCHAR *TextPtr = Text;
    while (1) {
        while (*TextPtr == L' ')
            ++TextPtr;
        if (! *TextPtr)
            return FALSE;
        if (_wcsnicmp(TextPtr, Setting, SettingLen) == 0
                && (   TextPtr[SettingLen] == L' '
                    || TextPtr[SettingLen] == L'\0')) {
            return TRUE;
        }
        while (*TextPtr != L' ') {
            if (! *TextPtr)
                return FALSE;
            ++TextPtr;
        }
    }
    return FALSE;
}
