/**
 * PROJECT:         ExectOS
 * COPYRIGHT:       See COPYING.md in the top level directory
 * FILE:            xtldr/modules/pecoff/pecoff.cc
 * DESCRIPTION:     Basic PE/COFF executable file format support module
 * DEVELOPERS:      Rafal Kupiec <belliash@codingworkshop.eu.org>
 */

#include <pecoff.hh>


/* PE/COFF_O module information */
MODULE_AUTHOR(L"Rafal Kupiec <belliash@codingworkshop.eu.org>");
MODULE_DESCRIPTION(L"Basic PE/COFF executable file format support");
MODULE_LICENSE(L"GPLv3");
MODULE_VERSION(L"0.1");


/**
 * Returns the address of the entry point.
 *
 * @param ImagePointer
 *        Supplies a pointer to the PE/COFF context structure representing the loaded image.
 *
 * @param EntryPoint
 *        Supplies a pointer to the memory area where address of the image entry point will be stored.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
PeCoff::GetEntryPoint(IN PVOID ImagePointer,
                      OUT PVOID *EntryPoint)
{
    PPECOFF_IMAGE_CONTEXT Image;

    /* Get PE/COFF image pointer*/
    Image = (PPECOFF_IMAGE_CONTEXT)ImagePointer;

    /* Validate input data */
    if(!Image || !Image->PeHeader)
    {
        /* Invalid parameter passed */
        return STATUS_EFI_INVALID_PARAMETER;
    }

    /* Check PE/COFF image type */
    if(Image->PeHeader->OptionalHeader32.Magic == PECOFF_IMAGE_PE_OPTIONAL_HDR64_MAGIC)
    {
        /* Get entry point from 64-bit optional header */
        *EntryPoint = (PUCHAR)Image->VirtualAddress + Image->PeHeader->OptionalHeader64.AddressOfEntryPoint;
    }
    else
    {
        /* Get entry point from 32-bit optional header */
        *EntryPoint = (PUCHAR)Image->VirtualAddress + Image->PeHeader->OptionalHeader32.AddressOfEntryPoint;
    }

    /* Return success */
    return STATUS_EFI_SUCCESS;
}

/**
 * Returns the size of the loaded PE/COFF file.
 *
 * @param ImagePointer
 *        Supplies a pointer to the PE/COFF context structure representing the loaded image.
 *
 * @param ImageSize
 *        Supplies a pointer to the memory area where file size will be stored.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
PeCoff::GetFileSize(IN PVOID ImagePointer,
                    OUT PULONGLONG FileSize)
{
    PPECOFF_IMAGE_CONTEXT Image;

    /* Get PE/COFF image pointer*/
    Image = (PPECOFF_IMAGE_CONTEXT)ImagePointer;

    /* Validate input data */
    if(!Image || !Image->ImageSize)
    {
        /* Invalid parameter passed */
        return STATUS_EFI_INVALID_PARAMETER;
    }

    /* Get image size and return success */
    *FileSize = Image->FileSize;
    return STATUS_EFI_NOT_FOUND;
}

/**
 * Returns the size of the loaded PE/COFF image.
 *
 * @param ImagePointer
 *        Supplies a pointer to the PE/COFF context structure representing the loaded image.
 *
 * @param ImageSize
 *        Supplies a pointer to the memory area where image size will be stored.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
PeCoff::GetImageSize(IN PVOID ImagePointer,
                     OUT PUINT ImageSize)
{
    PPECOFF_IMAGE_CONTEXT Image;

    /* Get PE/COFF image pointer*/
    Image = (PPECOFF_IMAGE_CONTEXT)ImagePointer;

    /* Validate input data */
    if(!Image || !Image->ImageSize)
    {
        /* Invalid parameter passed */
        return STATUS_EFI_INVALID_PARAMETER;
    }

    /* Get image size and return success */
    *ImageSize = Image->ImageSize;
    return STATUS_EFI_NOT_FOUND;
}

/**
 * Returns the machine type of the PE/COFF image.
 *
 * @param ImagePointer
 *        Supplies a pointer to the PE/COFF context structure representing the loaded image.
 *
 * @param MachineType
 *        Supplies a pointer to the memory area where a value defined for the 'machine' field will be stored.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
PeCoff::GetMachineType(IN PVOID ImagePointer,
                       OUT PUSHORT MachineType)
{
    PPECOFF_IMAGE_CONTEXT Image;

    /* Get PE/COFF image pointer*/
    Image = (PPECOFF_IMAGE_CONTEXT)ImagePointer;

    /* Validate input data */
    if(!Image || !Image->PeHeader)
    {
        /* Invalid parameter passed */
        return STATUS_EFI_INVALID_PARAMETER;
    }

    /* Get image machine type and return success */
    *MachineType = Image->PeHeader->FileHeader.Machine;
    return STATUS_EFI_SUCCESS;
}

/**
 * Returns an address to the specified section in the PE/COFF image.
 *
 * @param ImagePointer
 *        Supplies a pointer to the PE/COFF context structure representing the loaded image.
 *
 * @param SectionName
 *        Supplies a name of the requested section.
 *
 * @param RawData
 *        Supplies a pointer to the memory area where the address of the requested section will be stored.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
PeCoff::GetSection(IN PVOID ImagePointer,
                   IN PCHAR SectionName,
                   OUT PULONG *RawData)
{
    PPECOFF_IMAGE_SECTION_HEADER SectionHeader;
    PPECOFF_IMAGE_CONTEXT Image;
    SIZE_T SectionNameLength;
    USHORT SectionIndex;

    /* Get PE/COFF image pointer*/
    Image = (PPECOFF_IMAGE_CONTEXT)ImagePointer;

    /* Validate input data */
    if(!Image || !Image->PeHeader)
    {
        /* Invalid parameter passed */
        return STATUS_EFI_INVALID_PARAMETER;
    }

    /* Check PE/COFF image type */
    if(Image->PeHeader->OptionalHeader32.Magic == PECOFF_IMAGE_PE_OPTIONAL_HDR64_MAGIC)
    {
        /* Find section header in 64-bit optional header */
        SectionHeader = (PPECOFF_IMAGE_SECTION_HEADER)((PUCHAR)&Image->PeHeader->OptionalHeader64 +
                                                       Image->PeHeader->FileHeader.SizeOfOptionalHeader);
    }
    else
    {
        /* Find section header in 32-bit optional header */
        SectionHeader = (PPECOFF_IMAGE_SECTION_HEADER)((PUCHAR)&Image->PeHeader->OptionalHeader32 +
                                                       Image->PeHeader->FileHeader.SizeOfOptionalHeader);
    }

    /* Get section name length */
    SectionNameLength = XtLdrProtocol->String.Length(SectionName, 0);

    /* Iterate through all image sections */
    for(SectionIndex = 0; SectionIndex < Image->PeHeader->FileHeader.NumberOfSections; SectionIndex++)
    {
        /* Check section name */
        if(XtLdrProtocol->String.Compare((PCHAR)SectionHeader[SectionIndex].Name, SectionName, SectionNameLength) == 0)
        {
            /* Store section address and return */
            *RawData = (PULONG)((PUCHAR)Image->Data + SectionHeader[SectionIndex].PointerToRawData);
            return STATUS_EFI_SUCCESS;
        }
    }

    /* Section not found if reached here */
    return STATUS_EFI_NOT_FOUND;
}

/**
 * Returns an information about subsystem that is required to run PE/COFF image.
 *
 * @param ImagePointer
 *        Supplies a pointer to the PE/COFF context structure representing the loaded image.
 *
 * @param SubSystem
 *        Supplies a pointer to the memory area storing a value defined for the 'subsystem' field of the image.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
PeCoff::GetSubSystem(IN PVOID ImagePointer,
                     OUT PUSHORT SubSystem)
{
    PPECOFF_IMAGE_CONTEXT Image;

    /* Get PE/COFF image pointer*/
    Image = (PPECOFF_IMAGE_CONTEXT)ImagePointer;

    /* Validate input data */
    if(!Image || !Image->PeHeader)
    {
        /* Invalid parameter passed */
        return STATUS_EFI_INVALID_PARAMETER;
    }

    /* Check PE/COFF image type */
    if(Image->PeHeader->OptionalHeader32.Magic == PECOFF_IMAGE_PE_OPTIONAL_HDR64_MAGIC)
    {
        /* Get image subsystem from 64-bit optional header */
        *SubSystem = Image->PeHeader->OptionalHeader64.Subsystem;
    }
    else
    {
        /* Get image subsystem from 32-bit optional header */
        *SubSystem = Image->PeHeader->OptionalHeader32.Subsystem;
    }

    /* Return success */
    return STATUS_EFI_SUCCESS;
}

/**
 * Returns an information about major image version.
 *
 * @param ImagePointer
 *        Supplies a pointer to the PE/COFF context structure representing the loaded image.
 *
 * @param Version
 *        Supplies a pointer to the memory area storing a major image version.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
PeCoff::GetVersion(IN PVOID ImagePointer,
                   OUT PUSHORT Version)
{
    PPECOFF_IMAGE_CONTEXT Image;

    /* Get PE/COFF image pointer*/
    Image = (PPECOFF_IMAGE_CONTEXT)ImagePointer;

    /* Validate input data */
    if(!Image || !Image->PeHeader)
    {
        /* Invalid parameter passed */
        return STATUS_EFI_INVALID_PARAMETER;
    }

    /* Check PE/COFF image type */
    if(Image->PeHeader->OptionalHeader32.Magic == PECOFF_IMAGE_PE_OPTIONAL_HDR64_MAGIC)
    {
        /* Get image major version from 64-bit optional header */
        *Version = Image->PeHeader->OptionalHeader64.MajorImageVersion;
    }
    else
    {
        /* Get image major version from 32-bit optional header */
        *Version = Image->PeHeader->OptionalHeader32.MajorImageVersion;
    }

    /* Return success */
    return STATUS_EFI_SUCCESS;
}

/**
 * Initializes PECOFF module by opening XTLDR protocol and installing PECOFF protocol.
 *
 * @param ImageHandle
 *        Firmware-allocated handle that identifies the image.
 *
 * @param SystemTable
 *        Provides the EFI system table.
 *
 * @return This routine returns status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
PeCoff::InitializeModule(IN EFI_HANDLE ImageHandle,
                         IN PEFI_SYSTEM_TABLE SystemTable)
{
    EFI_GUID Guid = XT_PECOFF_IMAGE_PROTOCOL_GUID;
    EFI_STATUS Status;

    /* Open the XTLDR protocol */
    Status = BlGetXtLdrProtocol(SystemTable, ImageHandle, &XtLdrProtocol);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Failed to open loader protocol */
        return STATUS_EFI_PROTOCOL_ERROR;
    }

    /* Set routines available via PE/COFF image protocol */
    PeProtocol.GetEntryPoint = GetEntryPoint;
    PeProtocol.GetFileSize = GetFileSize;
    PeProtocol.GetImageSize = GetImageSize;
    PeProtocol.GetMachineType = GetMachineType;
    PeProtocol.GetSection = GetSection;
    PeProtocol.GetSubSystem = GetSubSystem;
    PeProtocol.GetVersion = GetVersion;
    PeProtocol.LoadImage = LoadImage;
    PeProtocol.RelocateImage = RelocateImage;
    PeProtocol.UnloadImage = UnloadImage;
    PeProtocol.VerifyImage = VerifyImage;

    /* Register PE/COFF protocol */
    return XtLdrProtocol->Protocol.Install(&PeProtocol, &Guid);
}

/**
 * Loads a PE/COFF image file.
 *
 * @param FileHandle
 *        The handle of the opened portable executable (PE) file.
 *
 * @param MemoryType
 *        Supplies the type of memory to be assigned to the memory descriptor.
 *
 * @param VirtualAddress
 *        Optional virtual address pointing to the memory area where PE/COFF file will be loaded.
 *
 * @param Image
 *        Supplies pointer to the memory area where loaded PE/COFF image context will be stored.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
PeCoff::LoadImage(IN PEFI_FILE_HANDLE FileHandle,
                  IN LOADER_MEMORY_TYPE MemoryType,
                  IN PVOID VirtualAddress,
                  OUT PVOID *ImagePointer)
{
    EFI_GUID FileInfoGuid = EFI_FILE_INFO_PROTOCOL_GUID;
    PPECOFF_IMAGE_SECTION_HEADER SectionHeader;
    PPECOFF_IMAGE_CONTEXT ImageData;
    EFI_PHYSICAL_ADDRESS Address;
    PEFI_FILE_INFO FileInfo;
    UINT_PTR ReadSize;
    EFI_STATUS Status;
    UINT SectionSize;
    SIZE_T Pages;
    PUCHAR Data;
    UINT Index;

    /* Set required size for getting file information */
    ReadSize = sizeof(EFI_FILE_INFO) + 32;

    /* Allocate necessary amount of memory */
    Status = XtLdrProtocol->Memory.AllocatePool(ReadSize, (PVOID *)&FileInfo);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Memory allocation failure */
        XtLdrProtocol->Debug.Print(L"ERROR: Memory allocation failure (Status Code: 0x%zX)\n", Status);
        return Status;
    }

    /* First attempt to get file information */
    Status = FileHandle->GetInfo(FileHandle, &FileInfoGuid, &ReadSize, FileInfo);
    if(Status == STATUS_EFI_BUFFER_TOO_SMALL)
    {
        /* Buffer it too small, but EFI tells the required size, let's reallocate */
        XtLdrProtocol->Memory.FreePool(&FileInfo);
        Status = XtLdrProtocol->Memory.AllocatePool(ReadSize, (PVOID *)&FileInfo);
        if(Status != STATUS_EFI_SUCCESS)
        {
            /* Memory allocation failure */
            XtLdrProtocol->Debug.Print(L"ERROR: Memory allocation failure (Status Code: 0x%zX)\n", Status);
            return Status;
        }

        /* Second attempt to get file information */
        Status = FileHandle->GetInfo(FileHandle, &FileInfoGuid, &ReadSize, FileInfo);
    }
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Unable to get file information */
        XtLdrProtocol->Debug.Print(L"ERROR: Failed to get PE/COFF file information (Status Code: 0x%zX)\n", Status);
        return Status;
    }

    /* Allocate memory for storing image data */
    Status = XtLdrProtocol->Memory.AllocatePool(sizeof(PECOFF_IMAGE_CONTEXT), (PVOID *)&ImageData);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Memory allocation failure */
        XtLdrProtocol->Debug.Print(L"ERROR: Memory allocation failure (Status Code: 0x%zX)\n", Status);
        return Status;
    }

    /* Store file size and memory type, nullify data and free up memory */
    ImageData->Data = NULLPTR;
    ImageData->FileSize = FileInfo->FileSize;
    ImageData->MemoryType = MemoryType;
    XtLdrProtocol->Memory.FreePool(FileInfo);

    /* Calculate number of pages */
    Pages = EFI_SIZE_TO_PAGES(ImageData->FileSize);

    /* Allocate pages */
    Status = XtLdrProtocol->Memory.AllocatePages(AllocateAnyPages, Pages, &Address);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Pages allocation failure */
        XtLdrProtocol->Debug.Print(L"ERROR: Pages allocation failure (Status Code: 0x%zX)\n", Status);
        XtLdrProtocol->Memory.FreePool(ImageData);
        return Status;
    }

    /* Read PE/COFF image */
    ReadSize = Pages * EFI_PAGE_SIZE;
    Data = (PUCHAR)(UINT_PTR)Address;
    Status = FileHandle->Read(FileHandle, &ReadSize, Data);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Failed to read data */
        XtLdrProtocol->Debug.Print(L"ERROR: Failed to read PE/COFF image file (Status Code: 0x%zX)\n", Status);
        XtLdrProtocol->Memory.FreePages(Pages, (EFI_PHYSICAL_ADDRESS)(UINT_PTR)Data);
        XtLdrProtocol->Memory.FreePool(ImageData);
        return Status;
    }

    /* Extract DOS and PE headers */
    ImageData->DosHeader = (PPECOFF_IMAGE_DOS_HEADER)Data;
    ImageData->PeHeader = (PPECOFF_IMAGE_PE_HEADER)((PUCHAR)Data + ImageData->DosHeader->PeHeaderOffset);

    /* Validate headers */
    Status = PeCoff::VerifyImage(ImageData);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Header validation failed, probably broken or invalid PE/COFF image */
        XtLdrProtocol->Debug.Print(L"ERROR: Invalid PE/COFF image headers (Status Code: 0x%zX)\n", Status);
        XtLdrProtocol->Memory.FreePages(Pages, (EFI_PHYSICAL_ADDRESS)(UINT_PTR)Data);
        XtLdrProtocol->Memory.FreePool(ImageData);
        return Status;
    }

    /* Make sure image is executable */
    if (!(ImageData->PeHeader->FileHeader.Characteristics & PECOFF_IMAGE_FILE_EXECUTABLE_IMAGE))
    {
        /* Loaded image is not executable */
        XtLdrProtocol->Debug.Print(L"ERROR: Non-executable PE/COFF image loaded\n");
        XtLdrProtocol->Memory.FreePages(Pages, (EFI_PHYSICAL_ADDRESS)(UINT_PTR)Data);
        XtLdrProtocol->Memory.FreePool(ImageData);
        return STATUS_EFI_LOAD_ERROR;
    }

    /* Store image size depending on the PE/COFF image type */
    if(ImageData->PeHeader->OptionalHeader32.Magic == PECOFF_IMAGE_PE_OPTIONAL_HDR64_MAGIC)
    {
        /* Store 64-bit image size */
        ImageData->ImageSize = ImageData->PeHeader->OptionalHeader64.SizeOfImage;
    }
    else
    {
        /* Store 32-bit image size */
        ImageData->ImageSize = ImageData->PeHeader->OptionalHeader32.SizeOfImage;
    }

    /* Calculate number of image pages */
    ImageData->ImagePages = EFI_SIZE_TO_PAGES(ImageData->ImageSize);

    /* Allocate image pages */
    Status = XtLdrProtocol->Memory.AllocatePages(AllocateAnyPages, ImageData->ImagePages, &Address);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Pages reallocation failure */
        XtLdrProtocol->Debug.Print(L"ERROR: Pages reallocation failure (Status Code: 0x%zX)\n", Status);
        XtLdrProtocol->Memory.FreePool(ImageData);
        return Status;
    }

    /* Store image data and virtual address */
    ImageData->Data = (PUCHAR)(UINT_PTR)Address;
    ImageData->PhysicalAddress = (PVOID)(UINT_PTR)Address;
    if(VirtualAddress)
    {
        /* Virtual address passed to this routine */
        ImageData->VirtualAddress = VirtualAddress;
    }
    else
    {
        /* Virtual address not specified, use physical address */
        ImageData->VirtualAddress = (PVOID)(UINT_PTR)Address;
    }

    /* Check the PE/COFF image type */
    if(ImageData->PeHeader->OptionalHeader32.Magic == PECOFF_IMAGE_PE_OPTIONAL_HDR64_MAGIC)
    {
        /* Copy all PE32+ sections */
        XtLdrProtocol->Memory.CopyMemory(ImageData->Data, Data, ImageData->PeHeader->OptionalHeader64.SizeOfHeaders);

        /* Find PE32+ section header */
        SectionHeader = (PPECOFF_IMAGE_SECTION_HEADER)((PUCHAR)&ImageData->PeHeader->OptionalHeader64 +
                                                       ImageData->PeHeader->FileHeader.SizeOfOptionalHeader);
    }
    else
    {
        /* Copy all PE32 sections */
        XtLdrProtocol->Memory.CopyMemory(ImageData->Data, Data, ImageData->PeHeader->OptionalHeader64.SizeOfHeaders);

        /* Find PE32 section header */
        SectionHeader = (PPECOFF_IMAGE_SECTION_HEADER)((PUCHAR)&ImageData->PeHeader->OptionalHeader64 +
                                                       ImageData->PeHeader->FileHeader.SizeOfOptionalHeader);
    }

    /* Load each section into memory */
    for(Index = 0; Index < ImageData->PeHeader->FileHeader.NumberOfSections; Index++)
    {
        /* Check section raw data size and section virtual size */
        if(SectionHeader[Index].SizeOfRawData < SectionHeader[Index].Misc.VirtualSize)
        {
            /* Use raw data size if it is smaller than virtual size */
            SectionSize = SectionHeader[Index].SizeOfRawData;
        }
        else
        {
            /* User virtual size otherwise */
            SectionSize = SectionHeader[Index].Misc.VirtualSize;
        }

        /* Make sure section is available */
        if(SectionSize > 0 && SectionHeader[Index].PointerToRawData != 0)
        {
            /* Copy section */
            XtLdrProtocol->Memory.CopyMemory((PUCHAR)ImageData->Data + SectionHeader[Index].VirtualAddress,
                                             Data + SectionHeader[Index].PointerToRawData, SectionSize);
        }

        /* Check if raw size is shorter than virtual size */
        if(SectionSize < SectionHeader[Index].Misc.VirtualSize)
        {
            /* Fill remaining space with zeroes */
            XtLdrProtocol->Memory.ZeroMemory((PUCHAR)ImageData->Data + SectionHeader[Index].VirtualAddress + SectionSize,
                                             SectionHeader[Index].Misc.VirtualSize - SectionSize);
        }
    }

    /* Free pages */
    XtLdrProtocol->Memory.FreePages((EFI_PHYSICAL_ADDRESS)(UINT_PTR)Data, Pages);

    /* Perform relocation fixups */
    Status = PeCoff::RelocateLoadedImage(ImageData);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Failed to relocate image */
        XtLdrProtocol->Debug.Print(L"ERROR: PE/COFF image relocation failed (Status Code: 0x%zX)\n", Status);
        return Status;
    }

    /* Store image data */
    *ImagePointer = ImageData;

    /* Return SUCCESS */
    return STATUS_EFI_SUCCESS;
}

/**
 * Relocates PE/COFF image to the specified address.
 *
 * @param ImagePointer
 *        Supplies a pointer to the PE/COFF context structure representing the loaded image.
 *
 * @param Address
 *        Specifies destination address of memory region, where image should be relocated.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
PeCoff::RelocateImage(IN PVOID ImagePointer,
                      IN EFI_VIRTUAL_ADDRESS Address)
{
    PPECOFF_IMAGE_CONTEXT Image;
    ULONGLONG ImageBase, OldVirtualAddress;
    EFI_STATUS Status;

    /* Get PE/COFF image pointer*/
    Image = (PPECOFF_IMAGE_CONTEXT)ImagePointer;

    /* Store original virtual address */
    OldVirtualAddress = (UINT_PTR)Image->VirtualAddress;

    /* Check PE/COFF image type */
    if(Image->PeHeader->OptionalHeader32.Magic == PECOFF_IMAGE_PE_OPTIONAL_HDR64_MAGIC)
    {
        /* This is 64-bit PE32+, store its image base address */
        ImageBase = Image->PeHeader->OptionalHeader64.ImageBase;
    }
    else
    {
        /* This is 32-bit PE32, store its image base address */
        ImageBase = Image->PeHeader->OptionalHeader32.ImageBase;
    }

    /* Overwrite virtual address and relocate image once again */
    Image->VirtualAddress = (PVOID)(Address - OldVirtualAddress + ImageBase);
    Status = PeCoff::RelocateLoadedImage(Image);
    if(Status != STATUS_EFI_SUCCESS)
    {
        /* Relocation failed */
        return Status;
    }

    /* Store new image virtual address */
    Image->VirtualAddress = (PVOID)Address;

    /* Return success */
    return STATUS_EFI_SUCCESS;
}

/**
 * Relocates a loaded PE/COFF image.
 *
 * @param Image
 *        Supplies a pointer to the PE/COFF context structure representing the loaded image.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
PeCoff::RelocateLoadedImage(IN PPECOFF_IMAGE_CONTEXT Image)
{
    PPECOFF_IMAGE_BASE_RELOCATION RelocationDir, RelocationEnd;
    PPECOFF_IMAGE_DATA_DIRECTORY DataDirectory;
    USHORT Offset, Type, Count;
    PUSHORT TypeOffset;
    ULONGLONG ImageBase;
    PUINT Address;
    PULONGLONG LongPtr;
    PUINT ShortPtr;

    /* Make sure image is not stripped */
    if(Image->PeHeader->FileHeader.Characteristics & PECOFF_IMAGE_FILE_RELOCS_STRIPPED)
    {
        /* No relocation information found */
        XtLdrProtocol->Debug.Print(L"WARNING: PE/COFF image is stripped and contains no information about relocations\n");
        return STATUS_EFI_SUCCESS;
    }

    /* Check PE/COFF image type */
    if(Image->PeHeader->OptionalHeader32.Magic == PECOFF_IMAGE_PE_OPTIONAL_HDR64_MAGIC)
    {
        /* Set relocation data directory and image base address */
        DataDirectory = &Image->PeHeader->OptionalHeader64.DataDirectory[PECOFF_IMAGE_DIRECTORY_ENTRY_BASERELOC];
        ImageBase = Image->PeHeader->OptionalHeader64.ImageBase;

        /* Check if loaded 64-bit PE32+ image should be relocated */
        if(Image->PeHeader->OptionalHeader64.NumberOfRvaAndSizes <= PECOFF_IMAGE_DIRECTORY_ENTRY_BASERELOC ||
        DataDirectory->VirtualAddress == 0 || DataDirectory->Size < sizeof(PECOFF_IMAGE_BASE_RELOCATION))
        {
            /* No need to relocate the image */
            return STATUS_EFI_SUCCESS;
        }
    }
    else
    {
        /* Set relocation data directory and image base address */
        DataDirectory = &Image->PeHeader->OptionalHeader32.DataDirectory[PECOFF_IMAGE_DIRECTORY_ENTRY_BASERELOC];
        ImageBase = Image->PeHeader->OptionalHeader32.ImageBase;

        /* Check if loaded 32-bit PE32 image should be relocated */
        if(Image->PeHeader->OptionalHeader32.NumberOfRvaAndSizes <= PECOFF_IMAGE_DIRECTORY_ENTRY_BASERELOC ||
        DataDirectory->VirtualAddress == 0 || DataDirectory->Size < sizeof(PECOFF_IMAGE_BASE_RELOCATION))
        {
            /* No need to relocate the image */
            return STATUS_EFI_SUCCESS;
        }
    }

    /* Set relocation pointers */
    RelocationDir = (PPECOFF_IMAGE_BASE_RELOCATION)((ULONG_PTR)Image->Data + DataDirectory->VirtualAddress);
    RelocationEnd = (PPECOFF_IMAGE_BASE_RELOCATION)((ULONG_PTR)RelocationDir + DataDirectory->Size);

    /* Do relocations */
    while(RelocationDir < RelocationEnd && RelocationDir->SizeOfBlock > 0)
    {
        /* Calculate number of relocations needed, address and type offset */
        Count = (RelocationDir->SizeOfBlock - sizeof(PECOFF_IMAGE_BASE_RELOCATION)) / sizeof(USHORT);
        Address = (PUINT)((PUCHAR)Image->Data + RelocationDir->VirtualAddress);
        TypeOffset = (PUSHORT)((PUCHAR)RelocationDir + sizeof(PECOFF_IMAGE_BASE_RELOCATION));

        /* Do relocations */
        while(Count--)
        {
            /* Calculate offset and relocation type */
            Offset = *TypeOffset & 0xFFF;
            Type = *TypeOffset >> 12;

            /* Check if end of the loaded address reached */
            if((PVOID)(PUSHORT)(Address + Offset) >= (PUCHAR)Image->Data + Image->ImageSize)
            {
                /* Do not relocate after the end of loaded image */
                break;
            }

            /* Make sure we are not going to relocate into .reloc section */
            if((ULONG_PTR)(Address + Offset) < (ULONG_PTR)RelocationDir ||
               (ULONG_PTR)(Address + Offset) >= (ULONG_PTR)RelocationEnd)
            {
                /* Apply relocation fixup */
                switch (Type)
                {
                    case PECOFF_IMAGE_REL_BASED_ABSOLUTE:
                        /* No relocation required */
                        break;
                    case PECOFF_IMAGE_REL_BASED_DIR64:
                        /* 64-bit relocation */
                        LongPtr = (PULONGLONG)((PUCHAR)Address + Offset);
                        *LongPtr = *LongPtr - ImageBase + (UINT_PTR)Image->VirtualAddress;
                        break;
                    case PECOFF_IMAGE_REL_BASED_HIGHLOW:
                        /* 32-bit relocation of hight and low half of address */
                        ShortPtr = (PUINT)((PUCHAR)Address + Offset);
                        *ShortPtr = *ShortPtr - ImageBase + (UINT_PTR)Image->VirtualAddress;
                        break;
                    default:
                        /* Unknown or unsupported relocation type */
                        return STATUS_EFI_UNSUPPORTED;
                }
            }
            /* Increment the type offset */
            TypeOffset++;
        }

        /* Next relocation */
        RelocationDir = (PPECOFF_IMAGE_BASE_RELOCATION)((PUCHAR)RelocationDir + RelocationDir->SizeOfBlock);
    }

    /* Return SUCCESS */
    return STATUS_EFI_SUCCESS;
}

/**
 * Unloads a PE/COFF image file and frees allocated memory.
 *
 * @param ImagePointer
 *        Supplies a pointer to the PE/COFF context structure representing the loaded image.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
PeCoff::UnloadImage(IN PVOID ImagePointer)
{
    PPECOFF_IMAGE_CONTEXT Image;
    EFI_STATUS Status;

    /* Get PE/COFF image pointer*/
    Image = (PPECOFF_IMAGE_CONTEXT)ImagePointer;

    /* Validate input data */
    if(!Image || !Image->Data)
    {
        /* Invalid parameter passed */
        return STATUS_EFI_INVALID_PARAMETER;
    }

    /* Free memory allocated for the image */
    Status = XtLdrProtocol->Memory.FreePages(Image->ImagePages, (EFI_PHYSICAL_ADDRESS)(UINT_PTR)Image->Data);
    Status |= XtLdrProtocol->Memory.FreePool(Image);

    /* Return status */
    return Status;
}

/**
 * Validates a PE/COFF image headers.
 *
 * @param ImagePointer
 *        Supplies a pointer to the PE/COFF context structure representing the loaded image.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
PeCoff::VerifyImage(IN PVOID ImagePointer)
{
    PPECOFF_IMAGE_CONTEXT Image;

    /* Get PE/COFF image pointer*/
    Image = (PPECOFF_IMAGE_CONTEXT)ImagePointer;

    /* Validate input data */
    if(!Image || !Image->PeHeader)
    {
        /* Invalid parameter passed */
        return STATUS_EFI_INVALID_PARAMETER;
    }

    /* Validate file size */
    if(Image->FileSize < sizeof(PECOFF_IMAGE_DOS_HEADER))
    {
        /* PE/COFF image shorter than DOS header, return error*/
        return STATUS_EFI_END_OF_FILE;
    }

    /* Validate DOS header */
    if(Image->DosHeader->Magic != PECOFF_IMAGE_DOS_SIGNATURE)
    {
        /* Invalid DOS signature, return error */
        return STATUS_EFI_INCOMPATIBLE_VERSION;
    }

    /* Validate PE header */
    if(Image->PeHeader->Signature != PECOFF_IMAGE_NT_SIGNATURE &&
       Image->PeHeader->Signature != PECOFF_IMAGE_XT_SIGNATURE)
    {
        /* Invalid PE signature, return error */
        return STATUS_EFI_INCOMPATIBLE_VERSION;
    }

    /* Validate optional header */
    if(Image->PeHeader->OptionalHeader32.Magic != PECOFF_IMAGE_PE_OPTIONAL_HDR32_MAGIC &&
       Image->PeHeader->OptionalHeader64.Magic != PECOFF_IMAGE_PE_OPTIONAL_HDR64_MAGIC)
    {
        /* Invalid optional header signature, return error */
        return STATUS_EFI_INCOMPATIBLE_VERSION;
    }

    /* Return success */
    return STATUS_EFI_SUCCESS;
}

/**
 * This routine is the entry point of the XT EFI boot loader module.
 *
 * @param ImageHandle
 *        Firmware-allocated handle that identifies the image.
 *
 * @param SystemTable
 *        Provides the EFI system table.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTCDECL
EFI_STATUS
XtLdrModuleMain(IN EFI_HANDLE ImageHandle,
                IN PEFI_SYSTEM_TABLE SystemTable)
{
    /* Initialize PECOFF module */
    return PeCoff::InitializeModule(ImageHandle, SystemTable);
}
