/*
 * COPYRIGHT:       See COPYING.ARM in the top level directory
 * PROJECT:         ReactOS UEFI Boot Library
 * FILE:            boot/environ/lib/misc/image.c
 * PURPOSE:         Boot Library Image Routines
 * PROGRAMMER:      Alex Ionescu (alex.ionescu@reactos.org)
 */

/* INCLUDES ******************************************************************/

#include "bl.h"
#include <bcd.h>

/* DATA VARIABLES ************************************************************/

ULONG IapAllocatedTableEntries;
ULONG IapTableEntries;
PVOID* IapImageTable;

/* FUNCTIONS *****************************************************************/

NTSTATUS
ImgpGetFileSize (
    _In_ PBL_IMG_FILE File,
    _Out_ PULONG FileSize
    )
{
    NTSTATUS Status;
    ULONG Size;
    BL_FILE_INFORMATION FileInformation;

    /* Check if the file was memory mapped */
    if (File->Flags & BL_IMG_MEMORY_FILE)
    {
        /* Just read the size of the mapping */
        Size = File->FileSize;
    }
    else
    {
        /* Do file I/O to get the file size */
        Status = BlFileGetInformation(File->FileId,
                                      &FileInformation);
        if (!NT_SUCCESS(Status))
        {
            return Status;
        }

        /* We only support files less than 4GB in the Image Mapped */
        Size = FileInformation.Size;
        if (FileInformation.Size > ULONG_MAX)
        {
            return STATUS_NOT_SUPPORTED;
        }
    }

    /* Return the size and success */
    *FileSize = Size;
    return STATUS_SUCCESS;
}

NTSTATUS
ImgpReadAtFileOffset (
    _In_ PBL_IMG_FILE File,
    _In_ ULONG Size,
    _In_ ULONGLONG ByteOffset,
    _In_ PVOID Buffer,
    _Out_ PULONG BytesReturned
    )
{
    NTSTATUS Status;

    /* Check what if this is a mapped file or not */
    if (File->Flags & BL_IMG_MEMORY_FILE)
    {
        /* Check if the boundaries are within the file size */
        if ((ByteOffset + Size) <= File->FileSize)
        {
            /* Yep, copy into the caller-supplied buffer */
            RtlCopyMemory(Buffer,
                          (PVOID)((ULONG_PTR)File->BaseAddress + (ULONG_PTR)ByteOffset),
                          Size);

            /* If caller wanted to know, return the size copied */
            if (BytesReturned)
            {
                *BytesReturned = Size;
            }

            /* All good */
            Status = STATUS_SUCCESS;
        }
        else
        {
            /* Doesn't fit */
            Status = STATUS_INVALID_PARAMETER;
        }
    }
    else
    {
        /* Issue the file I/O instead */
        Status = BlFileReadAtOffsetEx(File->FileId,
                                      Size,
                                      ByteOffset,
                                      Buffer,
                                      BytesReturned,
                                      0);
    }

    /* Return the final status */
    return Status;
}

NTSTATUS
ImgpOpenFile (
    _In_ ULONG DeviceId,
    _In_ PWCHAR FileName,
    _In_ ULONG Flags,
    _Out_ PBL_IMG_FILE NewFile
    )
{
    NTSTATUS Status;
    ULONG FileSize;
    ULONGLONG RemoteFileSize;
    PVOID RemoteFileAddress;
    ULONG FileId;

    /* First, try to see if BD has this file remotely */
    Status = BlBdPullRemoteFile(FileName,
                                &RemoteFileAddress,
                                &RemoteFileSize);
    if (NT_SUCCESS(Status))
    {
        /* Yep, get the file size and make sure it's < 4GB */
        FileSize = RemoteFileSize;
        if (RemoteFileSize <= ULONG_MAX)
        {
            /* Remember this is a memory mapped remote file */
            NewFile->Flags |= (BL_IMG_MEMORY_FILE | BL_IMG_REMOTE_FILE);
            NewFile->FileSize = FileSize;
            NewFile->BaseAddress = RemoteFileAddress;
            goto Quickie;
        }
    }

    /* Use File I/O instead */
    Status = BlFileOpen(DeviceId,
                        FileName,
                        BL_FILE_READ_ACCESS,
                        &FileId);
    if (!NT_SUCCESS(Status))
    {
        /* Bail out on failure */
        return Status;
    }

    /* Make sure nobody thinks this is a memory file */
    NewFile->Flags &= ~BL_IMG_MEMORY_FILE;
    NewFile->FileId = FileId;

Quickie:
    /* Set common data for both memory and I/O based file */
    NewFile->Flags |= BL_IMG_VALID_FILE;
    NewFile->FileName = FileName;
    return Status;
}

NTSTATUS
ImgpCloseFile (
    _In_ PBL_IMG_FILE File
    )
{
    NTSTATUS Status;

    /* Make sure this is a valid file, otherwise no-op */
    Status = STATUS_SUCCESS;
    if (File->Flags & BL_IMG_VALID_FILE)
    {
        /* Is this a memory mapped file? */
        if (!(File->Flags & BL_IMG_MEMORY_FILE))
        {
            /* Nope, close the file handle */
            return BlFileClose(File->FileId);
        }

        /* Is this a remote file? */
        if (File->Flags & BL_IMG_REMOTE_FILE)
        {
            /* Then only free the memory in that scenario */
            EfiPrintf(L"TODO\r\n");
            //return MmPapFreePages(File->BaseAddress, TRUE);
        }
    }

    /* Return the final status */
    return Status;
}

NTSTATUS
BlImgUnallocateImageBuffer (
    _In_ PVOID ImageBase,
    _In_ ULONG ImageSize,
    _In_ ULONG ImageFlags
    )
{
    EfiPrintf(L"leaking the shit out of %p\r\n", ImageBase);
    return STATUS_NOT_IMPLEMENTED;
}

NTSTATUS
BlImgAllocateImageBuffer (
    _Inout_ PVOID* ImageBuffer,
    _In_ ULONG MemoryType,
    _In_ ULONGLONG ImageSize,
    _In_ ULONG Flags
    )
{
    ULONG Attributes;
    ULONGLONG Pages, Size;
    PVOID MappedBase, CurrentBuffer;
    NTSTATUS Status;
    PHYSICAL_ADDRESS PhysicalAddress;

    /* Read and reset the current buffer address */
    CurrentBuffer = *ImageBuffer;
    *ImageBuffer = NULL;

    /* Align the image size to page */
    Size = ROUND_TO_PAGES(ImageSize);

    /* Not sure what this attribute does yet */
    Attributes = 0;
    if (Flags & BL_LOAD_IMG_UNKNOWN_BUFFER_FLAG)
    {
        Attributes = 0x10000;
    }

    /* Check if the caller wants a virtual buffer */
    if (Flags & BL_LOAD_IMG_VIRTUAL_BUFFER)
    {
        /* Set the physical address to the current buffer */
        PhysicalAddress.QuadPart = (ULONG_PTR)CurrentBuffer;
        Pages = Size >> PAGE_SHIFT;

        /* Allocate the physical pages */
        Status = BlMmAllocatePhysicalPages(&PhysicalAddress,
                                           Pages,
                                           MemoryType,
                                           Attributes,
                                           0);
        if (!NT_SUCCESS(Status))
        {
            /* If that failed, remove allocation attributes */
            PhysicalAddress.QuadPart = 0;
            Attributes &= ~BlMemoryValidAllocationAttributeMask,
            Status = BlMmAllocatePhysicalPages(&PhysicalAddress,
                                               Pages,
                                               MemoryType,
                                               Attributes,
                                               0);
        }

        /* Check if either attempts succeeded */
        if (!NT_SUCCESS(Status))
        {
            return Status;
        }

        /* Now map the physical buffer at the address requested */
        MappedBase = (PVOID)PhysicalAddress.LowPart;
        Status = BlMmMapPhysicalAddressEx(&MappedBase,
                                          BlMemoryFixed,
                                          Size,
                                          PhysicalAddress);
        if (!NT_SUCCESS(Status))
        {
            /* Free on failure if needed */
            BlMmFreePhysicalPages(PhysicalAddress);
            return Status;
        }
    }
    else
    {
        /* Otherwise, allocate raw physical pages */
        MappedBase = CurrentBuffer;
        Pages = Size >> PAGE_SHIFT;
        Status = MmPapAllocatePagesInRange(&MappedBase,
                                           MemoryType,
                                           Pages,
                                           Attributes,
                                           0,
                                           NULL,
                                           0);
        if (!NT_SUCCESS(Status))
        {
            /* If that failed, try without allocation attributes */
            MappedBase = NULL;
            Attributes &= ~BlMemoryValidAllocationAttributeMask,
            Status = MmPapAllocatePagesInRange(&MappedBase,
                                               MemoryType,
                                               Pages,
                                               Attributes,
                                               0,
                                               NULL,
                                               0);
        }

        /* Check if either attempts succeeded */
        if (!NT_SUCCESS(Status))
        {
            return Status;
        }
    }

    /* Success path, returned allocated address */
    *ImageBuffer = MappedBase;
    return STATUS_SUCCESS;
}

NTSTATUS
BlImgLoadImageWithProgress2 (
    _In_ ULONG DeviceId,
    _In_ BL_MEMORY_TYPE MemoryType,
    _In_ PWCHAR FileName,
    _Inout_ PVOID* MappedBase,
    _Inout_ PULONG MappedSize,
    _In_ ULONG ImageFlags,
    _In_ BOOLEAN ShowProgress,
    _Out_opt_ PUCHAR* HashBuffer,
    _Out_opt_ PULONG HashSize
    )
{
    NTSTATUS Status;
    PVOID BaseAddress, Buffer;
    ULONG RemainingLength, CurrentSize, ImageSize, ReadSize;
    BOOLEAN ComputeSignature, ComputeHash, Completed;
    BL_IMG_FILE FileHandle;
    ULONGLONG ByteOffset;
    PHYSICAL_ADDRESS PhysicalAddress;

    /* Initialize variables */
    BaseAddress = 0;
    ImageSize = 0;
    Completed = FALSE;
    RtlZeroMemory(&FileHandle, sizeof(FileHandle));

    /* Check for missing parameters */
    if (!MappedBase)
    {
        Status = STATUS_INVALID_PARAMETER;
        goto Quickie;
    }
    if (!FileName)
    {
        Status = STATUS_INVALID_PARAMETER;
        goto Quickie;
    }
    if (!MappedSize)
    {
        Status = STATUS_INVALID_PARAMETER;
        goto Quickie;
    }

    /* Check if the image buffer is being provided */
    if (ImageFlags & BL_LOAD_IMG_EXISTING_BUFFER)
    {
        /* An existing base must already exist */
        if (!(*MappedBase))
        {
            Status = STATUS_INVALID_PARAMETER;
            goto Quickie;
        }
    }

    /* Check of a hash is being requested */
    if (ImageFlags & BL_LOAD_IMG_COMPUTE_HASH)
    {
        /* Make sure we can return the hash */
        if (!HashBuffer)
        {
            Status = STATUS_INVALID_PARAMETER;
            goto Quickie;
        }
        if (!HashSize)
        {
            Status = STATUS_INVALID_PARAMETER;
            goto Quickie;
        }
    }

    /* Check for invalid combination of parameters */
    if ((ImageFlags & BL_LOAD_IMG_COMPUTE_HASH) && (ImageFlags & 0x270))
    {
        Status = STATUS_INVALID_PARAMETER;
        goto Quickie;
    }

    /* Initialize hash if requested by caller */
    if (HashBuffer)
    {
        *HashBuffer = 0;
    }

    /* Do the same for the hash size */
    if (HashSize)
    {
        *HashSize = 0;
    }

    /* Open the image file */
    Status = ImgpOpenFile(DeviceId, FileName, DeviceId, &FileHandle);
    if (!NT_SUCCESS(Status))
    {
        EfiPrintf(L"Error opening file: %lx\r\n", Status);
        goto Quickie;
    }

    /* Get the size of the image */
    Status = ImgpGetFileSize(&FileHandle, &ImageSize);
    if (!NT_SUCCESS(Status))
    {
        EfiPrintf(L"Error getting file size: %lx\r\n", Status);
        goto Quickie;
    }

    /* Read the current base address */
    BaseAddress = *MappedBase;
    if (ImageFlags & BL_LOAD_IMG_EXISTING_BUFFER)
    {
        /* Check if the current buffer is too small */
        if (*MappedSize < ImageSize)
        {
            /* Return the required size of the buffer */
            *MappedSize = ImageSize;
            Status = STATUS_BUFFER_TOO_SMALL;
        }
    }
    else
    {
        /* A buffer was not provided, allocate one ourselves */
        Status = BlImgAllocateImageBuffer(&BaseAddress,
                                          MemoryType,
                                          ImageSize,
                                          ImageFlags);
    }

    /* Bail out if allocation failed */
    if (!NT_SUCCESS(Status))
    {
        goto Quickie;
    }

    /* Set the initial byte offset and length to read */
    RemainingLength = ImageSize;
    ByteOffset = 0;
    Buffer = BaseAddress;

    /* Update the initial progress */
    Completed = FALSE;
    if (ShowProgress)
    {
        BlUtlUpdateProgress(0, &Completed);
        ShowProgress &= (Completed != 0) - 1;
    }

    /* Set the chunk size for each read */
    ReadSize = 0x100000;
    if (ReadSize > ImageSize)
    {
        ReadSize = ImageSize;
    }

    /* Check if we should compute hash and/or signatures */
    ComputeSignature = ImageFlags & BL_LOAD_IMG_COMPUTE_SIGNATURE;
    if ((ComputeSignature) || (ImageFlags & BL_LOAD_IMG_COMPUTE_HASH))
    {
        ComputeHash = TRUE;
        // todo: crypto is hard
    }

    /* Begin the read loop */
    while (RemainingLength)
    {
        /* Check if we've got more than a chunk left to read */
        if (RemainingLength > ReadSize)
        {
            /* Read a chunk*/
            CurrentSize = ReadSize;
        }
        else
        {
            /* Read only what's left */
            CurrentSize = RemainingLength;
        }

        /* Read the chunk */
        Status = ImgpReadAtFileOffset(&FileHandle,
                                      CurrentSize,
                                      ByteOffset,
                                      Buffer,
                                      0);
        if (!NT_SUCCESS(Status))
        {
            goto Quickie;
        }

        /* Check if we need to compute the hash of this chunk */
        if (ComputeHash)
        {
            // todo: crypto is hard
        }

        /* Update our position and read information */
        Buffer = (PVOID)((ULONG_PTR)Buffer + CurrentSize);
        RemainingLength -= CurrentSize;
        ByteOffset += CurrentSize;

        /* Check if we should update the progress bar */
        if (ShowProgress)
        {
            /* Compute new percentage completed, check if we're done */
            BlUtlUpdateProgress(100 - 100 * RemainingLength / ImageSize,
                                &Completed);
            ShowProgress &= (Completed != 0) - 1;
        }
    }

    /* Is the read fully complete? We need to finalize the hash if requested */
    if (ComputeHash != RemainingLength)
    {
        // todo: CRYPTO IS HARD
    }

    /* Success path, return back the buffer and the size of the image */
    *MappedBase = BaseAddress;
    *MappedSize = ImageSize;

Quickie:
    /* Close the file handle */
    ImgpCloseFile(&FileHandle);

    /* Check if we failed and had allocated a buffer */
    if (!(NT_SUCCESS(Status)) &&
        (BaseAddress) &&
        !(ImageFlags & BL_LOAD_IMG_EXISTING_BUFFER))
    {
        /* Check what kind of buffer we had allocated */
        if (ImageFlags & BL_LOAD_IMG_VIRTUAL_BUFFER)
        {
            /* Unmap and free the virtual buffer */
            PhysicalAddress.QuadPart = (ULONG_PTR)BaseAddress;
            BlMmUnmapVirtualAddressEx(BaseAddress, ImageSize);
            BlMmFreePhysicalPages(PhysicalAddress);
        }
        else
        {
            /* Free the physical buffer */
            //MmPapFreePages(VirtualAddress, 1);
            EfiPrintf(L"Leaking memory\r\n");
        }
    }

    /* If we hadn't gotten to 100% yet, do it now */
    if (ShowProgress)
    {
        BlUtlUpdateProgress(100, &Completed);
    }

    /* Return the final status */
    return Status;
}

PIMAGE_SECTION_HEADER 
BlImgFindSection (
    _In_ PVOID ImageBase,
    _In_ ULONG ImageSize
    )
{
    PIMAGE_SECTION_HEADER FoundSection;
    ULONG i;
    PIMAGE_SECTION_HEADER SectionHeader;
    PIMAGE_NT_HEADERS NtHeader;
    NTSTATUS Status;

    /* Assume failure */
    FoundSection = NULL;

    /* Make sure the image is valid */
    Status = RtlImageNtHeaderEx(0, ImageBase, ImageSize, &NtHeader);
    if (NT_SUCCESS(Status))
    {
        /* Get the first section and loop through them all */
        SectionHeader = IMAGE_FIRST_SECTION(NtHeader);
        for (i = 0; i < NtHeader->FileHeader.NumberOfSections; i++)
        {
            /* Check if this is the resource section */
            if (!_stricmp((PCCH)SectionHeader->Name, ".rsrc"))
            {
                /* Yep, we're done */
                FoundSection = SectionHeader;
                break;
            }

            /* Nope, keep going */
            SectionHeader++;
        }
    }

    /* Return the matching section */
    return FoundSection;
}

VOID
BlImgQueryCodeIntegrityBootOptions (
    _In_ PBL_LOADED_APPLICATION_ENTRY ApplicationEntry,
    _Out_ PBOOLEAN IntegrityChecksDisabled, 
    _Out_ PBOOLEAN TestSigning
    )
{
    
    NTSTATUS Status;
    BOOLEAN Value;

    /* Check if /DISABLEINTEGRITYCHECKS is on */
    Status = BlGetBootOptionBoolean(ApplicationEntry->BcdData,
                                    BcdLibraryBoolean_DisableIntegrityChecks,
                                    &Value);
    *IntegrityChecksDisabled = NT_SUCCESS(Status) && (Value);

    /* Check if /TESTSIGNING is on */
    Status = BlGetBootOptionBoolean(ApplicationEntry->BcdData,
                                    BcdLibraryBoolean_AllowPrereleaseSignatures,
                                    &Value);
    *TestSigning = NT_SUCCESS(Status) && (Value);
}

NTSTATUS
BlImgUnLoadImage (
    _In_ PVOID ImageBase,
    _In_ ULONG ImageSize,
    _In_ ULONG ImageFlags
    )
{
    /* Check for missing parameters */
    if (!(ImageSize) || !(ImageBase))
    {
        /* Bail out */
        return STATUS_INVALID_PARAMETER;
    }

    /* Unallocate the image buffer */
    return BlImgUnallocateImageBuffer(ImageBase, ImageSize, ImageFlags);
}

NTSTATUS
ImgpLoadPEImage (
    _In_ PBL_IMG_FILE ImageFile,
    _In_ BL_MEMORY_TYPE MemoryType,
    _Inout_ PVOID* ImageBase,
    _Out_opt_ PULONG ImageSize,
    _Inout_opt_ PVOID Hash,
    _In_ ULONG Flags
    )
{
    NTSTATUS Status;
    ULONG FileSize, HeaderSize;
    BL_IMG_FILE LocalFileBuffer;
    PBL_IMG_FILE LocalFile;
    PVOID VirtualAddress, PreferredBase, ImageBuffer, CertBuffer, HashBuffer;
    ULONGLONG VirtualSize;
    PIMAGE_DATA_DIRECTORY CertDirectory;
    PHYSICAL_ADDRESS PhysicalAddress;
    PIMAGE_NT_HEADERS NtHeaders;
    USHORT SectionCount, CheckSum, PartialSum, FinalSum;
    PIMAGE_SECTION_HEADER Section;
    ULONG_PTR EndOfHeaders, SectionStart, Slack, SectionEnd;
    ULONG i, SectionSize, RawSize, BytesRead, RemainingLength, Offset, AlignSize;
    BOOLEAN First, ImageHashValid;
    UCHAR LocalBuffer[1024];
    UCHAR TrustedBootInformation[52];
    ULONG WorkaroundForBinutils;

    /* Initialize locals */
    WorkaroundForBinutils = 0;
    LocalFile = NULL;
    ImageBuffer = NULL;
    FileSize = 0;
    First = FALSE;
    VirtualAddress = NULL;
    CertBuffer = NULL;
    CertDirectory = NULL;
    HashBuffer = NULL;
    Offset = 0;
    VirtualSize = 0;
    ImageHashValid = FALSE;
    RtlZeroMemory(&TrustedBootInformation, sizeof(TrustedBootInformation));

    /* Get the size of the image */
    Status = ImgpGetFileSize(ImageFile, &FileSize);
    if (!NT_SUCCESS(Status))
    {
        return STATUS_FILE_INVALID;
    }

    /* Allocate a flat buffer for it */
    Status = BlImgAllocateImageBuffer(&ImageBuffer, BlLoaderData, FileSize, 0);
    if (!NT_SUCCESS(Status))
    {
        goto Quickie;
    }

    /* Read the whole file flat for now */
    Status = ImgpReadAtFileOffset(ImageFile, FileSize, 0, ImageBuffer, NULL);
    if (!NT_SUCCESS(Status))
    {
        goto Quickie;
    }

    /* Build a local file handle */
    LocalFile = &LocalFileBuffer;
    LocalFileBuffer.FileName = ImageFile->FileName;
    LocalFileBuffer.Flags = BL_IMG_MEMORY_FILE | BL_IMG_VALID_FILE;
    LocalFileBuffer.BaseAddress = ImageBuffer;
    LocalFileBuffer.FileSize = FileSize;

    /* Get the NT headers of the file */
    Status = RtlImageNtHeaderEx(0, ImageBuffer, FileSize, &NtHeaders);
    if (!NT_SUCCESS(Status))
    {
        goto Quickie;
    }

    /* Check if we should validate the machine type */
    if (Flags & BL_LOAD_PE_IMG_CHECK_MACHINE)
    {
        /* Is it different than our current machine type? */
#if _M_AMD64
        if (NtHeaders->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64)
#else
        if (NtHeaders->FileHeader.Machine != IMAGE_FILE_MACHINE_I386)
#endif
        {
            /* Is it x86 (implying we are x64) ? */
            if (NtHeaders->FileHeader.Machine == IMAGE_FILE_MACHINE_I386)
            {
                /* Return special error code */
                Status = STATUS_INVALID_IMAGE_WIN_32;
            }
            else if (NtHeaders->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64)
            {
                /* Otherwise, it's x64 but we are x86 */
                Status = STATUS_INVALID_IMAGE_WIN_64;
            }
            else
            {
                /* Or it's ARM or something... */
                Status = STATUS_INVALID_IMAGE_FORMAT;
            }

            /* Return with the distinguished error code */
            goto Quickie;
        }
    }

    /* Check if we should validate the subsystem */
    if (Flags & BL_LOAD_PE_IMG_CHECK_SUBSYSTEM)
    {
        /* It must be a Windows boot Application */
        if (NtHeaders->OptionalHeader.Subsystem !=
            IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION)
        {
            Status = STATUS_INVALID_IMAGE_FORMAT;
            goto Quickie;
        }
    }

    /* Check if we should validate the /INTEGRITYCHECK flag */
    if (Flags & BL_LOAD_PE_IMG_CHECK_FORCED_INTEGRITY)
    {
        /* Check if it's there */
        if (!(NtHeaders->OptionalHeader.DllCharacteristics &
              IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY))
        {
            /* Nope, fail otherwise */
            Status = STATUS_INVALID_IMAGE_FORMAT;
            goto Quickie;
        }
    }

    /* Check if we should compute the image hash */
    if ((Flags & BL_LOAD_PE_IMG_COMPUTE_HASH) || (Hash))
    {
        EfiPrintf(L"No hash support\r\n");
    }

    /* Read the current base address, if any */
    VirtualAddress = *ImageBase;

    /* Get the virtual size of the image */
    VirtualSize = NtHeaders->OptionalHeader.SizeOfImage;

    /* Safely align the virtual size to a page */
    Status = RtlULongLongAdd(VirtualSize,
                             PAGE_SIZE - 1,
                             &VirtualSize);
    if (!NT_SUCCESS(Status))
    {
        goto Quickie;
    }
    VirtualSize = ALIGN_DOWN_BY(VirtualSize, PAGE_SIZE);

    /* Make sure the image isn't larger than 4GB */
    if (VirtualSize > ULONG_MAX)
    {
        Status = STATUS_INVALID_IMAGE_FORMAT;
        goto Quickie;
    }

    /* Check if we have a buffer already */
    if (Flags & BL_LOAD_IMG_EXISTING_BUFFER)
    {
        /* Check if it's too small */
        if (*ImageSize < VirtualSize)
        {
            /* Fail, letting the caller know how big to make it */
            *ImageSize = VirtualSize;
            Status = STATUS_BUFFER_TOO_SMALL;
        }
    }
    else
    {
        /* Allocate the buffer with the flags and type the caller wants */
        Status = BlImgAllocateImageBuffer(&VirtualAddress,
                                          MemoryType,
                                          VirtualSize,
                                          Flags);
    }

    /* Bail out if allocation failed, or existing buffer is too small */
    if (!NT_SUCCESS(Status))
    {
        goto Quickie;
    }

    /* Read the size of the headers */
    HeaderSize = NtHeaders->OptionalHeader.SizeOfHeaders;
    if (VirtualSize < HeaderSize)
    {
        /* Bail out if they're bigger than the image! */
        Status = STATUS_INVALID_IMAGE_FORMAT;
        goto Quickie;
    }

    /* Now read the header into the buffer */
    Status = ImgpReadAtFileOffset(LocalFile, HeaderSize, 0, VirtualAddress, NULL);
    if (!NT_SUCCESS(Status))
    {
        goto Quickie;
    }

    /* Get the NT headers of the file */
    Status = RtlImageNtHeaderEx(0, VirtualAddress, HeaderSize, &NtHeaders);
    if (!NT_SUCCESS(Status))
    {
        goto Quickie;
    }

    First = FALSE;

    /* Record how many sections we have */
    SectionCount = NtHeaders->FileHeader.NumberOfSections;

    /* Capture the current checksum and reset it */
    CheckSum = NtHeaders->OptionalHeader.CheckSum;
    NtHeaders->OptionalHeader.CheckSum = 0;

    /* Calculate the checksum of the header, and restore the original one */
    PartialSum = BlUtlCheckSum(0,
                               VirtualAddress,
                               HeaderSize,
                               BL_UTL_CHECKSUM_COMPLEMENT |
                               BL_UTL_CHECKSUM_USHORT_BUFFER);
    NtHeaders->OptionalHeader.CheckSum = CheckSum;

    /* Record our current position (right after the headers) */
    EndOfHeaders = (ULONG_PTR)VirtualAddress + HeaderSize;
    EfiPrintf(L"here\r\n");

    /* Get the first section and iterate through each one */
    Section = IMAGE_FIRST_SECTION(NtHeaders);
    for (i = 0; i < SectionCount; i++)
    {
        /* Compute where this section starts */
        SectionStart = (ULONG_PTR)VirtualAddress + Section->VirtualAddress;

        /* Make sure that the section fits within the image */
        if ((VirtualSize < Section->VirtualAddress) ||
            ((PVOID)SectionStart < VirtualAddress))
        {
            EfiPrintf(L"fail 1\r\n");
            Status = STATUS_INVALID_IMAGE_FORMAT;
            goto Quickie;
        }

        /* Check if there's slack space between header end and the section */
        if (!(First) && (EndOfHeaders < SectionStart))
        {
            /* Zero it out */
            Slack = SectionStart - EndOfHeaders;
            RtlZeroMemory((PVOID)EndOfHeaders, Slack);
        }

        /* Get the section virtual size and the raw size */
        SectionSize = Section->Misc.VirtualSize;
        RawSize = Section->SizeOfRawData;

        /* Safely align the raw size by 2 */
        Status = RtlULongAdd(RawSize, 1, &AlignSize);
        if (!NT_SUCCESS(Status))
        {
            goto Quickie;
        }
        AlignSize = ALIGN_DOWN_BY(AlignSize, 2);

        /* IF we don't have a virtual size, use the raw size */
        if (!SectionSize)
        {
            SectionSize = RawSize;
        }

        /* If we don't have raw data, ignore the raw size */
        if (!Section->PointerToRawData)
        {
            RawSize = 0;
        }
        else if (SectionSize < RawSize)
        {
            /* And if the virtual size is smaller, use it as the final size */
            RawSize = SectionSize;
        }

        /* Make sure that the section doesn't overflow in memory */
        Status = RtlULongAdd(Section->VirtualAddress,
                             SectionSize,
                             &SectionEnd);
        if (!NT_SUCCESS(Status))
        {
            EfiPrintf(L"fail 21\r\n");
            Status = STATUS_INVALID_IMAGE_FORMAT;
            goto Quickie;
        }

        /* Make sure that it fits within the image */
        if (VirtualSize < SectionEnd)
        {
            Status = STATUS_INVALID_IMAGE_FORMAT;
            goto Quickie;
        }

        /* Make sure it doesn't overflow on disk */
        Status = RtlULongAdd(Section->VirtualAddress,
                             AlignSize,
                             &SectionEnd);
        if (!NT_SUCCESS(Status))
        {
            EfiPrintf(L"fail 31\r\n");
            Status = STATUS_INVALID_IMAGE_FORMAT;
            goto Quickie;
        }

        /* Make sure that it fits within the disk image as well */
        if (VirtualSize < SectionEnd)
        {
            Status = STATUS_INVALID_IMAGE_FORMAT;
            goto Quickie;
        }

        /* So does this section have a valid size after all? */
        if (RawSize)
        {
            /* Are we in the first iteration? */
            if (!First)
            {
                /* FUCK YOU BINUTILS */
                if ((*(PULONG)&Section->Name == 'ler.') && (RawSize < AlignSize))
                {
                    /* Piece of shit won't build relocations when you tell it to,
                     * either by using --emit-relocs or --dynamicbase. People online
                     * have found out that by using -pie-executable you can get this
                     * to happen, but then it turns out that the .reloc section is
                     * incorrectly sized, and results in a corrupt PE. However, they
                     * still compute the checksum using the correct value. What idiots.
                     */
                    WorkaroundForBinutils = AlignSize - RawSize;
                    AlignSize -= WorkaroundForBinutils;
                }

                /* Yes, read the section data */
                Status = ImgpReadAtFileOffset(LocalFile,
                                              AlignSize,
                                              Section->PointerToRawData,
                                              (PVOID)SectionStart,
                                              NULL);
                if (!NT_SUCCESS(Status))
                {
                    goto Quickie;
                }

                /* Update our current offset */
                Offset = AlignSize + Section->PointerToRawData;

                /* Update the checksum to include this section */
                PartialSum = BlUtlCheckSum(PartialSum,
                                           (PUCHAR)SectionStart,
                                           AlignSize,
                                           BL_UTL_CHECKSUM_COMPLEMENT |
                                           BL_UTL_CHECKSUM_USHORT_BUFFER);
                AlignSize += WorkaroundForBinutils;
            }
        }

        /* Are we in the first iteration? */
        if (!First)
        {
            /* Is there space at the end of the section? */
            if (RawSize < SectionSize)
            {
                /* Zero out the slack space that's there */
                Slack = SectionSize - RawSize;
                RtlZeroMemory((PVOID)(SectionStart + RawSize), Slack);
            }

            /* Update our tail offset */
            EndOfHeaders = SectionStart + SectionSize;
        }

        /* Move to the next section */
        Section++;
    }

    /* Are we in the first iteration? */
    if (!First)
    {
        /* Go to the end of the file */
        SectionStart = (ULONG_PTR)VirtualAddress + VirtualSize;

        /* Is there still some slack space left? */
        if (EndOfHeaders < SectionStart)
        {
            /* Zero it out */
            Slack = SectionStart - EndOfHeaders;
            RtlZeroMemory((PVOID)EndOfHeaders, Slack);
        }
    }

    /* Did the first iteration complete OK? */
    if ((NT_SUCCESS(Status)) && !(First))
    {
        /* Check how many non-image bytes are left in the file */
        RemainingLength = FileSize - Offset;
        while (RemainingLength)
        {
            /* See if the read will fit into our local buffer */
            if (RemainingLength >= sizeof(LocalBuffer))
            {
                /* Nope, cap it */
                BytesRead = sizeof(LocalBuffer);
            }
            else
            {
                /* Yes, but there's less to read */
                BytesRead = RemainingLength;
            }

            /* Read 1024 bytes into the local buffer */
            Status = ImgpReadAtFileOffset(LocalFile,
                                          BytesRead,
                                          Offset,
                                          LocalBuffer,
                                          &BytesRead);
            if (!(NT_SUCCESS(Status)) || !(BytesRead))
            {
                Status = STATUS_FILE_INVALID;
                goto Quickie;
            }

            /* Advance the offset and reduce the length */
            RemainingLength -= BytesRead;
            Offset += BytesRead;

            /* Compute the checksum of this leftover space */
            PartialSum = BlUtlCheckSum(PartialSum,
                                       LocalBuffer,
                                       BytesRead,
                                       BL_UTL_CHECKSUM_COMPLEMENT |
                                       BL_UTL_CHECKSUM_USHORT_BUFFER);
        }

        /* Finally, calculate the final checksum and compare it */
        FinalSum = FileSize + PartialSum + WorkaroundForBinutils;
        if ((FinalSum != CheckSum) && (PartialSum == 0xFFFF))
        {
            /* It hit overflow, so set it to the file size */
            FinalSum = FileSize;
        }

        /* If the checksum doesn't match, and caller is enforcing, bail out */
        if ((FinalSum != CheckSum) &&
            !(Flags & BL_LOAD_PE_IMG_IGNORE_CHECKSUM_MISMATCH))
        {
            Status = STATUS_IMAGE_CHECKSUM_MISMATCH;
            goto Quickie;
        }
    }

    /* Check if the .rsrc section should be checked with the filename */
    if (Flags & BL_LOAD_PE_IMG_VALIDATE_ORIGINAL_FILENAME)
    {
        EfiPrintf(L"Not yet supported\r\n");
        Status = 0xC0430007; // STATUS_SECUREBOOT_FILE_REPLACED
        goto Quickie;
    }

    /* Check if we should relocate */
    if (!(Flags & BL_LOAD_PE_IMG_SKIP_RELOCATIONS))
    {
        /* Check if we loaded at a different address */
        PreferredBase = (PVOID)NtHeaders->OptionalHeader.ImageBase;
        if (VirtualAddress != PreferredBase)
        {
            /* Yep -- do relocations */
            Status = LdrRelocateImage(VirtualAddress,
                                      "Boot Environment Library",
                                      STATUS_SUCCESS,
                                      STATUS_UNSUCCESSFUL,
                                      STATUS_INVALID_IMAGE_FORMAT);
            if (!NT_SUCCESS(Status))
            {
                /* That's bad */
                goto Quickie;
            }
        }
    }

#if BL_TPM_SUPPORT
    /* Check if the image hash  was valid */
    if (!ImageHashValid)
    {
        /* Send a TPM/SI notification without a context */
        BlEnNotifyEvent(0x10000002, NULL);
    }

    /* Now send a TPM/SI notification with the hash of the loaded image */
    BlMmTranslateVirtualAddress(VirtualAddress, &Context.ImageBase);
    Context.HashAlgorithm = HashAlgorithm;
    Context.HashSize = HashSize;
    Context.FileName = ImageFile->FileName;
    Context.ImageSize = VirtualSize;
    Context.HashValid = ImageHashValid;
    Context.Hash = Hash;
    BlEnNotifyEvent(0x10000002, &Context);
#endif

    /* Return the loaded address to the caller */
    *ImageBase = VirtualAddress;

    /* If the caller wanted the image size, return it too */
    if (ImageSize)
    {
        *ImageSize = VirtualSize;
    }

Quickie:
    /* Check if we computed the image hash OK */
    if (ImageHashValid)
    {
        /* Then free the information that ImgpValidateImageHash set up */
        EfiPrintf(L"leadking trusted boot\r\n");
        //ImgpDestroyTrustedBootInformation(&TrustedBootInformation);
    }

    /* Check if we had a hash buffer */
    if (HashBuffer)
    {
        /* Free it */
        EfiPrintf(L"Leadking hash: %p\r\n", HashBuffer);
        //MmPapFreePages(HashBuffer, TRUE);
    }

    /* Check if we have a certificate diretory */
    if ((CertBuffer) && (CertDirectory))
    {
        /* Free it */
        BlImgUnallocateImageBuffer(CertBuffer, CertDirectory->Size, 0);
    }

    /* Check if we had an image buffer allocated */
    if ((ImageBuffer) && (FileSize))
    {
        /* Free it */
        BlImgUnallocateImageBuffer(ImageBuffer, FileSize, 0);
    }

    /* Check if we had a local file handle */
    if (LocalFile)
    {
        /* Close it */
        ImgpCloseFile(LocalFile);
    }

    /* Check if this is the failure path */
    if (!NT_SUCCESS(Status))
    {
        /* Check if we had started mapping in the image already */
        if ((VirtualAddress) && !(Flags & BL_LOAD_PE_IMG_EXISTING_BUFFER))
        {
            /* Into a virtual buffer? */
            if (Flags & BL_LOAD_PE_IMG_VIRTUAL_BUFFER)
            {
                /* Unmap and free it */
                BlMmUnmapVirtualAddressEx(VirtualAddress, VirtualSize);
                PhysicalAddress.QuadPart = (ULONG_PTR)VirtualAddress;
                BlMmFreePhysicalPages(PhysicalAddress);
            }
            else
            {
                /* Into a physical buffer -- free it */
                EfiPrintf(L"Leaking physical pages\r\n");
               // MmPapFreePages(VirtualAddress, TRUE);
            }
        }
    }

    /* Return back to caller */
    return Status;
}

NTSTATUS
BlImgLoadPEImageEx (
    _In_ ULONG DeviceId,
    _In_ BL_MEMORY_TYPE MemoryType,
    _In_ PWCHAR Path,
    _Out_ PVOID* ImageBase,
    _Out_ PULONG ImageSize,
    _Out_ PVOID Hash,
    _In_ ULONG Flags
    )
{
    BL_IMG_FILE ImageFile;
    NTSTATUS Status;

    /* Initialize the image file structure */
    ImageFile.Flags = 0;
    ImageFile.FileName = NULL;

    /* Check if the required parameter are missing */
    if (!(ImageBase) || !(Path))
    {
        return STATUS_INVALID_PARAMETER;
    }

    /* If we are loading a pre-allocated image, make sure we have it */
    if ((Flags & BL_LOAD_IMG_EXISTING_BUFFER) && (!(*ImageBase) || !(ImageSize)))
    {
        return STATUS_INVALID_PARAMETER;
    }

    /* Load the file from disk */
    Status = ImgpOpenFile(DeviceId, Path, 0, &ImageFile);
    if (NT_SUCCESS(Status))
    {
        /* If that worked, do the PE parsing */
        Status = ImgpLoadPEImage(&ImageFile,
                                 MemoryType,
                                 ImageBase,
                                 ImageSize,
                                 Hash,
                                 Flags);
    }

    /* Close the image file and return back to caller */
    ImgpCloseFile(&ImageFile);
    return Status;
}

NTSTATUS
BlImgLoadBootApplication (
    _In_ PBL_LOADED_APPLICATION_ENTRY BootEntry,
    _Out_ PULONG AppHandle
    )
{
    NTSTATUS Status;
    PULONGLONG AllowedList;
    ULONGLONG AllowedCount;
    ULONG i, DeviceId, ImageSize, Flags, ListSize;
    LARGE_INTEGER Frequency;
    PVOID UnlockCode, ImageBase;
    PBL_DEVICE_DESCRIPTOR Device, BitLockerDevice;
    PWCHAR Path;
    PBL_APPLICATION_ENTRY AppEntry;
    PBL_IMG_FILE ImageFile;
    BOOLEAN DisableIntegrity, TestSigning;
    UCHAR Hash[64];
    PBL_IMAGE_APPLICATION_ENTRY ImageAppEntry;

    /* Initialize all locals */
    BitLockerDevice = NULL;
    UnlockCode = NULL;
    ImageFile = NULL;
    DeviceId = -1;
    Device = NULL;
    ImageAppEntry = NULL;
    Path = NULL;
    ImageSize = 0;
    ImageBase = NULL;

    /* Check for "allowed in-memory settings" */
    Status = BlpGetBootOptionIntegerList(BootEntry->BcdData,
                                         BcdLibraryIntegerList_AllowedInMemorySettings,
                                         &AllowedList,
                                         &AllowedCount,
                                         TRUE);
    if (Status == STATUS_SUCCESS)
    {
        /* Loop through the list of allowed setting */
        for (i = 0; i < AllowedCount; i++)
        {
            /* Find the super undocumented one */
            if (AllowedList[i] == BcdLibraryInteger_UndocumentedMagic)
            {
                /* If it's present, append the current perf frequence to it */
                BlTimeQueryPerformanceCounter(&Frequency);
                BlAppendBootOptionInteger(BootEntry,
                                          BcdLibraryInteger_UndocumentedMagic,
                                          Frequency.QuadPart);
            }
        }
    }

#if BL_BITLOCKER_SUPPORT
    /* Do bitlocker stuff */
    Status = BlFveSecureBootUnlockBootDevice(BootEntry, &BitLockerDevice, &UnlockCode);
    if (!NT_SUCCESS(Status))
    {
        goto Quickie;
    }
#endif

    /* Get the device on which this application is on*/
    Status = BlGetBootOptionDevice(BootEntry->BcdData,
                                   BcdLibraryDevice_ApplicationDevice,
                                   &Device,
                                   NULL);
    if (!NT_SUCCESS(Status))
    {
        goto Quickie;
    }

    /* Get the path of the application */
    Status = BlGetBootOptionString(BootEntry->BcdData,
                                   BcdLibraryString_ApplicationPath,
                                   &Path);
    if (!NT_SUCCESS(Status))
    {
        goto Quickie;
    }

    /* Open the device */
    Status = BlpDeviceOpen(Device,
                           BL_DEVICE_READ_ACCESS,
                           0,
                           &DeviceId);
    if (!NT_SUCCESS(Status))
    {
        goto Quickie;
    }

    /* Check for integrity BCD options */
    BlImgQueryCodeIntegrityBootOptions(BootEntry,
                                       &DisableIntegrity,
                                       &TestSigning);

#if BL_TPM_SUPPORT
    RtlZeroMemory(&Context, sizeof(Context);
    Context.BootEntry = BootEntry;
    BlEnNotifyEvent(0x10000003, &Context);
#endif

    /* Enable signing and hashing checks if integrity is enabled */
    Flags = 0;
    if (!DisableIntegrity)
    {
        Flags = 0x8070;
    }

    /* Now call the PE loader to load the image */
    Status = BlImgLoadPEImageEx(DeviceId,
                                BlLoaderMemory,
                                Path,
                                &ImageBase,
                                &ImageSize,
                                Hash,
                                Flags);
    if (!NT_SUCCESS(Status))
    {
        goto Quickie;
    }

#if BL_KD_SUPPORT
    /* Check if we should notify the debugger of load */
    if (BdDebugTransitions)
    {
        /* Initialize it */
        BdForceDebug = 1;
        Status = BlBdInitialize();
        if (NT_SUCCESS(Status))
        {
            /* Check if it's enabled */
            if (BlBdDebuggerEnabled())
            {
                /* Send it an image load notification */
                BdDebuggerNotPresent = FALSE;
                RtlInitUnicodeString(&PathString, Path);
                BlBdLoadImageSymbols(&PathString, ImageBase);
            }
        }
    }
#endif

#if BL_BITLOCKER_SUPPORT
    /* Do bitlocker stuff */
    Status = BlSecureBootCheckPolicyOnFveDevice(BitLockerDevice);
    if (!NT_SUCCESS(Status))
    {
        goto Quickie;
    }
#endif

#if BL_BITLOCKER_SUPPORT
    /* Do bitlocker stuff */
    Status = BlFveSecureBootCheckpointBootApp(BootEntry, BitLockerDevice, Hash, UnlockCode);
    if (!NT_SUCCESS(Status))
    {
        goto Quickie;
    }
#endif

    /* Get the BCD option size */
    ListSize = BlGetBootOptionListSize(BootEntry->BcdData);

    /* Allocate an entry with all the BCD options */
    AppEntry = BlMmAllocateHeap(ListSize + sizeof(*AppEntry));
    if (!AppEntry)
    {
        Status = STATUS_NO_MEMORY;
        goto Quickie;
    }

    /* Zero it out */
    RtlZeroMemory(AppEntry, sizeof(*AppEntry));

    /* Initialize it */
    strcpy(AppEntry->Signature, "BTAPENT");
    AppEntry->Guid = BootEntry->Guid;
    AppEntry->Flags = BootEntry->Flags;

    /* Copy the BCD options */
    RtlCopyMemory(&AppEntry->BcdData, BootEntry->BcdData, ListSize);

    /* Allocate the image entry */
    ImageAppEntry = BlMmAllocateHeap(sizeof(*ImageAppEntry));
    if (!ImageAppEntry)
    {
        Status = STATUS_NO_MEMORY;
        goto Quickie;
    }

    /* Initialize it */
    ImageAppEntry->ImageBase = ImageBase;
    ImageAppEntry->ImageSize = ImageSize;
    ImageAppEntry->AppEntry = AppEntry;

    /* Check if this is the first entry */
    if (!IapTableEntries)
    {
        /* Allocate two entries */
        IapAllocatedTableEntries = 0;
        IapTableEntries = 2;
        IapImageTable = BlMmAllocateHeap(IapTableEntries * sizeof(PVOID));
        if (!IapImageTable)
        {
            Status = STATUS_NO_MEMORY;
            goto Quickie;
        }

        /* Zero out the entries for now */
        RtlZeroMemory(IapImageTable, IapTableEntries * sizeof(PVOID));
    }

    /* Set this entry into the table */
    Status = BlTblSetEntry(&IapImageTable,
                           &IapTableEntries,
                           ImageAppEntry,
                           AppHandle,
                           TblDoNotPurgeEntry);

Quickie:
    /* Is the device open? Close it if so */
    if (DeviceId != 1)
    {
        BlDeviceClose(DeviceId);
    }

    /* Is there an allocated device? Free it */
    if (Device)
    {
        BlMmFreeHeap(Device);
    }

    /* Is there an allocated path? Free it */
    if (Path)
    {
        BlMmFreeHeap(Path);
    }

    /* Is there a bitlocker device? Free it */
    if (BitLockerDevice)
    {
        BlMmFreeHeap(BitLockerDevice);
    }

    /* Is there a bitlocker unlock code? Free it */
    if (UnlockCode)
    {
        BlMmFreeHeap(UnlockCode);
    }

    /* Did we succeed in creating an entry? */
    if (NT_SUCCESS(Status))
    {
        /* Remember there's one more in the table */
        IapAllocatedTableEntries++;

        /* Return success */
        return Status;
    }

    /* Did we load an image after all? */
    if (ImageBase)
    {
        /* Unload it */
        BlImgUnLoadImage(ImageBase, ImageSize, 0);
    }

    /* Did we allocate an app entry? Free it */
    if (AppEntry)
    {
        BlMmFreeHeap(AppEntry);
    }

    /* Do we have an image file entry?  Free it */
    if (ImageFile)
    {
        BlMmFreeHeap(ImageFile);
    }

    /* Do we no longer have a single entry in the table? */
    if (!(IapAllocatedTableEntries) && (IapImageTable))
    {
        /* Free and destroy the table */
        BlMmFreeHeap(IapImageTable);
        IapTableEntries = 0;
        IapImageTable = NULL;
    }

    /* Return the failure code */
    return Status;
}

NTSTATUS
BlpPdParseReturnArguments (
    _In_ PBL_RETURN_ARGUMENTS ReturnArguments
    )
{
    /* Check if any custom data was returned */
    if (ReturnArguments->DataPage == 0)
    {
        /* Nope, nothing to do */
        return STATUS_SUCCESS;
    }

    /* Yes, we have to parse it */
    EfiPrintf(L"Return arguments not supported\r\n");
    return STATUS_NOT_IMPLEMENTED;
}

NTSTATUS
ImgArchEfiStartBootApplication (
    _In_ PBL_APPLICATION_ENTRY AppEntry,
    _In_ PVOID ImageBase,
    _In_ ULONG ImageSize,
    _In_ PBL_RETURN_ARGUMENTS ReturnArguments
    )
{
    /* Not yet implemented. This is the last step! */
    EfiPrintf(L"EFI APPLICATION START!!!\r\n");
    EfiStall(100000000);
    return STATUS_NOT_IMPLEMENTED;
}

NTSTATUS
BlImgStartBootApplication (
    _In_ ULONG AppHandle,
    _Inout_opt_ PBL_RETURN_ARGUMENTS ReturnArguments
    )
{
    PBL_IMAGE_APPLICATION_ENTRY ImageAppEntry;
    BL_RETURN_ARGUMENTS LocalReturnArgs;
    PBL_FILE_SYSTEM_ENTRY FileSystem;
    PLIST_ENTRY NextEntry, ListHead;
    NTSTATUS Status;

    /* Check if we don't have an argument structure */
    if (!ReturnArguments)
    {
        /* Initialize a local copy and use it instead */
        LocalReturnArgs.Version = BL_RETURN_ARGUMENTS_VERSION;
        LocalReturnArgs.Status = STATUS_SUCCESS;
        LocalReturnArgs.Flags = 0;
        LocalReturnArgs.DataPage = 0;
        LocalReturnArgs.DataSize = 0;
        ReturnArguments = &LocalReturnArgs;
    }

    /* Make sure the handle index is valid */
    if (IapTableEntries <= AppHandle)
    {
        return STATUS_INVALID_PARAMETER;
    }

    /* Get the entry for this handle, making sure it exists */
    ImageAppEntry = IapImageTable[AppHandle];
    if (!ImageAppEntry)
    {
        return STATUS_INVALID_PARAMETER;
    }

    /* Loop the registered file systems */
    ListHead = &RegisteredFileSystems;
    NextEntry = RegisteredFileSystems.Flink;
    while (NextEntry != ListHead)
    {
        /* Get the filesystem entry */
        FileSystem = CONTAINING_RECORD(NextEntry,
                                       BL_FILE_SYSTEM_ENTRY,
                                       ListEntry);

        /* See if it has a purge callback */
        if (FileSystem->PurgeCallback)
        {
            /* Call it */
            FileSystem->PurgeCallback();
        }

        /* Move to the next entry */
        NextEntry = NextEntry->Flink;
    }

    /* TODO  -- flush the block I/O cache too */
    //BlockIoPurgeCache();

    /* Call into EFI land to start the boot application */
    Status = ImgArchEfiStartBootApplication(ImageAppEntry->AppEntry,
                                            ImageAppEntry->ImageBase,
                                            ImageAppEntry->ImageSize,
                                            ReturnArguments);

    /* Parse any arguments we got on the way back */
    BlpPdParseReturnArguments(ReturnArguments);

#if BL_BITLOCKER_SUPPORT
    /* Bitlocker stuff */
    FvebpCheckAllPartitions(TRUE);
#endif

#if BL_TPM_SUPPORT
    /* Notify a TPM/SI event */
    BlEnNotifyEvent(0x10000005, NULL);
#endif

    /* Reset the display */
    BlpDisplayReinitialize();

    /* TODO -- reset ETW */
    //BlpLogInitialize();

    /* All done */
    return Status;
}

NTSTATUS
BlImgUnloadBootApplication (
    _In_ ULONG AppHandle
    )
{
    PBL_IMAGE_APPLICATION_ENTRY ImageAppEntry;
    NTSTATUS Status;

    /* Make sure the handle index is valid */
    if (IapTableEntries <= AppHandle)
    {
        return STATUS_INVALID_PARAMETER;
    }

    /* Get the entry for this handle, making sure it exists */
    ImageAppEntry = IapImageTable[AppHandle];
    if (!ImageAppEntry)
    {
        return STATUS_INVALID_PARAMETER;
    }

    /* Unload the image */
    Status = BlImgUnLoadImage(ImageAppEntry->ImageBase,
                              ImageAppEntry->ImageSize,
                              0);
    if (NT_SUCCESS(Status))
    {
        /* Normalize the success code */
        Status = STATUS_SUCCESS;
    }
    else
    {
        /* Normalize the failure code */
        Status = STATUS_MEMORY_NOT_ALLOCATED;
    }

    /* Free the entry and the image entry as well */
    BlMmFreeHeap(ImageAppEntry->AppEntry);
    BlMmFreeHeap(ImageAppEntry);

    /* Clear the handle */
    IapImageTable[AppHandle] = NULL;

    /* Free one entry */
    if (!(--IapAllocatedTableEntries))
    {
        /* There are no more, so get rid of the table itself */
        BlMmFreeHeap(IapImageTable);
        IapImageTable = NULL;
        IapTableEntries = 0;
    }

    /* All good */
    return Status;
}
