/** @file
  Copyright (c) 2021, Marvin Häuser. All rights reserved.
  Copyright (c) 2022, Mikhail Krichanov. All rights reserved.
  SPDX-License-Identifier: BSD-3-Clause
**/

#include "ImageTool.h"

#include "DynamicBuffer.h"
#include "PeEmitCommon.h"

#if PE_ARCH == 32

typedef UINT32                           EFI_IMAGE_NT_BASE_ADDRESS;
typedef EFI_IMAGE_NT_HEADERS32           EFI_IMAGE_NT_HEADERS;
#define EFI_IMAGE_NT_OPTIONAL_HDR_MAGIC  EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC

#elif PE_ARCH == 64

typedef UINT64                           EFI_IMAGE_NT_BASE_ADDRESS;
typedef EFI_IMAGE_NT_HEADERS64           EFI_IMAGE_NT_HEADERS;
#define EFI_IMAGE_NT_OPTIONAL_HDR_MAGIC  EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC

#endif

#define PE_SUFFIX__(Name, Arch)  Name##Arch
#define PE_SUFFIX_(Name, Arch)   PE_SUFFIX__ (Name, Arch)
#define PE_SUFFIX(Name)          PE_SUFFIX_ (Name, PE_ARCH)

typedef struct {
  EFI_IMAGE_DEBUG_DIRECTORY_ENTRY      Dir;
  EFI_IMAGE_DEBUG_CODEVIEW_NB10_ENTRY  Nb10;
} image_tool_debug_dir_t;

#define SIZE_OF_DATA_DIRECTORY  \
  EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES * sizeof (EFI_IMAGE_DATA_DIRECTORY)

#define SIZE_OF_OPTIONAL_HEADER                                             \
  sizeof (EFI_IMAGE_NT_HEADERS) - sizeof (EFI_IMAGE_NT_HEADERS_COMMON_HDR)  \
    + SIZE_OF_DATA_DIRECTORY

static
bool
InternalFinalizeExtraSection (
  image_tool_dynamic_buffer  *Buffer,
  EFI_IMAGE_NT_HEADERS       *PeHdr,
  EFI_IMAGE_SECTION_HEADER   *SectionHeader,
  const char                 *Name,
  uint32_t                   Size,
  uint32_t                   Offset
  )
{
  uint32_t  BufferOffset;

  assert (Size == ImageToolBufferGetSize (Buffer) - Offset);

  strncpy (
    (char *)SectionHeader->Name,
    Name,
    sizeof (SectionHeader->Name)
    );
  SectionHeader->Name[ARRAY_SIZE (SectionHeader->Name) - 1] = 0;

  SectionHeader->VirtualSize      = ALIGN_VALUE (Size, PeHdr->SectionAlignment);
  SectionHeader->VirtualAddress   = PeHdr->SizeOfImage;
  SectionHeader->SizeOfRawData    = ALIGN_VALUE (Size, PeHdr->FileAlignment);
  SectionHeader->PointerToRawData = Offset;
  SectionHeader->Characteristics  = EFI_IMAGE_SCN_CNT_INITIALIZED_DATA | EFI_IMAGE_SCN_MEM_READ;

  PeHdr->SizeOfInitializedData += SectionHeader->VirtualSize;
  PeHdr->SizeOfImage           += SectionHeader->VirtualSize;

  BufferOffset = ImageToolBufferAppendReserve (
                   Buffer,
                   SectionHeader->SizeOfRawData - Size
                   );
  return BufferOffset != MAX_UINT32;
}

#define ToolImageEmitPeHiiTable  PE_SUFFIX (ToolImageEmitPeHiiTable)
static
bool
ToolImageEmitPeHiiTable (
  image_tool_dynamic_buffer      *Buffer,
  EFI_IMAGE_NT_HEADERS           *PeHdr,
  EFI_IMAGE_SECTION_HEADER       *SectionHeader,
  const image_tool_image_info_t  *Image
  )
{
  bool      Success;
  uint32_t  HiiTableOffset;
  uint32_t  HiiTableSize;
  void      *HiiTable;
  uint32_t  Offset;

  HiiTableOffset = ImageToolBufferAppendReserve (
                     Buffer,
                     mHiiResourceSectionHeaderSize
                     );
  if (HiiTableOffset == MAX_UINT32) {
    DEBUG_RAISE ();
    return false;
  }

  assert (IS_ALIGNED (HiiTableOffset, PeHdr->FileAlignment));

  HiiTable = ImageToolBufferGetPointer (Buffer, HiiTableOffset);
  InitializeHiiResouceSectionHeader (
    HiiTable,
    SectionHeader->VirtualAddress,
    Image->HiiInfo.DataSize
    );
  HiiTable = NULL;

  Offset = ImageToolBufferAppend (
             Buffer,
             Image->HiiInfo.Data,
             Image->HiiInfo.DataSize
             );
  if (Offset == MAX_UINT32) {
    DEBUG_RAISE ();
    return false;
  }

  HiiTableSize = ImageToolBufferGetSize (Buffer) - HiiTableOffset;

  Success = InternalFinalizeExtraSection (
              Buffer,
              PeHdr,
              SectionHeader,
              ".rsrc",
              HiiTableSize,
              HiiTableOffset
              );
  if (!Success) {
    DEBUG_RAISE ();
    return false;
  }

  PeHdr->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress = SectionHeader->VirtualAddress;
  PeHdr->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_RESOURCE].Size           = HiiTableSize;

  return true;
}

#define ToolImageEmitPeRelocTable  PE_SUFFIX (ToolImageEmitPeRelocTable)
static
bool
ToolImageEmitPeRelocTable (
  image_tool_dynamic_buffer      *Buffer,
  EFI_IMAGE_NT_HEADERS           *PeHdr,
  EFI_IMAGE_SECTION_HEADER       *SectionHeader,
  const image_tool_image_info_t  *Image
  )
{
  bool                             Success;
  uint32_t                         RelocTableSize;
  EFI_IMAGE_BASE_RELOCATION_BLOCK  RelocBlock;
  uint32_t                         BlockAddress;
  uint32_t                         Index;
  uint32_t                         RelocAddress;
  uint16_t                         Relocation;
  uint32_t                         RelocTableOffset;
  uint32_t                         RelocTableEnd;
  uint32_t                         RelocBlockOffset;
  uint32_t                         NewRelocBlockOffset;
  uint32_t                         Offset;

  RelocBlockOffset = ImageToolBufferAppendReserve (Buffer, sizeof (RelocBlock));
  if (RelocBlockOffset == MAX_UINT32) {
    DEBUG_RAISE ();
    return false;
  }

  RelocTableOffset = RelocBlockOffset;

  assert (IS_ALIGNED (RelocTableOffset, PeHdr->FileAlignment));

  BlockAddress = PAGE (Image->RelocInfo.Relocs[0].Target);

  for (Index = 0; Index < Image->RelocInfo.NumRelocs; ++Index) {
    RelocAddress = PAGE (Image->RelocInfo.Relocs[Index].Target);
    if (RelocAddress != BlockAddress) {
      Offset = ImageToolBufferAppendReserveAlign (
                 Buffer,
                 ALIGNOF (EFI_IMAGE_BASE_RELOCATION_BLOCK)
                 );
      if (Offset == MAX_UINT32) {
        DEBUG_RAISE ();
        return false;
      }

      NewRelocBlockOffset = ImageToolBufferAppendReserve (
                              Buffer,
                              sizeof (RelocBlock)
                              );
      if (NewRelocBlockOffset == MAX_UINT32) {
        DEBUG_RAISE ();
        return false;
      }

      RelocBlock.VirtualAddress = BlockAddress;
      RelocBlock.SizeOfBlock    = NewRelocBlockOffset - RelocBlockOffset;
      ImageToolBufferWrite (
        Buffer,
        RelocBlockOffset,
        &RelocBlock,
        sizeof (RelocBlock)
        );

      RelocBlockOffset = NewRelocBlockOffset;

      BlockAddress = RelocAddress;
    }

    Relocation  = PAGE_OFF(Image->RelocInfo.Relocs[Index].Target);
    Relocation |= ((uint16_t)Image->RelocInfo.Relocs[Index].Type) << 12U;

    Offset = ImageToolBufferAppend (Buffer, &Relocation, sizeof (Relocation));
    if (Offset == MAX_UINT32) {
      DEBUG_RAISE ();
      return false;
    }
  }

  Offset = ImageToolBufferAppendReserveAlign (
             Buffer,
             ALIGNOF (EFI_IMAGE_BASE_RELOCATION_BLOCK)
             );
  if (Offset == MAX_UINT32) {
    DEBUG_RAISE ();
    return false;
  }

  RelocTableEnd = ImageToolBufferGetSize (Buffer);

  RelocBlock.VirtualAddress = BlockAddress;
  RelocBlock.SizeOfBlock    = RelocTableEnd - RelocBlockOffset;
  ImageToolBufferWrite (
    Buffer,
    RelocBlockOffset,
    &RelocBlock,
    sizeof (RelocBlock)
    );

  RelocTableSize = RelocTableEnd - RelocTableOffset;

  Success = InternalFinalizeExtraSection (
              Buffer,
              PeHdr,
              SectionHeader,
              ".reloc",
              RelocTableSize,
              RelocTableOffset
              );
  if (!Success) {
    DEBUG_RAISE ();
    return false;
  }
  SectionHeader->Characteristics |= EFI_IMAGE_SCN_MEM_DISCARDABLE;

  PeHdr->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress = SectionHeader->VirtualAddress;
  PeHdr->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC].Size           = RelocTableSize;

  return true;
}

#define ToolImageEmitPeDebugTable  PE_SUFFIX (ToolImageEmitPeDebugTable)
static
bool
ToolImageEmitPeDebugTable (
  image_tool_dynamic_buffer      *Buffer,
  EFI_IMAGE_NT_HEADERS           *PeHdr,
  EFI_IMAGE_SECTION_HEADER       *SectionHeader,
  const image_tool_image_info_t  *Image
  )
{
  bool                    Success;
  uint32_t                DebugDirOffset;
  uint32_t                DebugDirSize;
  image_tool_debug_dir_t  *Data;
  uint32_t                Offset;

  assert (Image->DebugInfo.SymbolsPath != NULL);

  DebugDirOffset = ImageToolBufferAppendReserve (Buffer, sizeof (*Data));
  if (DebugDirOffset == MAX_UINT32) {
    DEBUG_RAISE ();
    return false;
  }

  assert (IS_ALIGNED (DebugDirOffset, PeHdr->FileAlignment));

  Offset = ImageToolBufferAppend (
             Buffer,
             Image->DebugInfo.SymbolsPath,
             Image->DebugInfo.SymbolsPathLen + 1
             );
  if (Offset == MAX_UINT32) {
    DEBUG_RAISE ();
    return false;
  }

  DebugDirSize = ImageToolBufferGetSize (Buffer) - DebugDirOffset;

  Success = InternalFinalizeExtraSection (
              Buffer,
              PeHdr,
              SectionHeader,
              ".debug",
              DebugDirSize,
              DebugDirOffset
              );
  if (!Success) {
    DEBUG_RAISE ();
    return false;
  }
  SectionHeader->Characteristics |= EFI_IMAGE_SCN_MEM_DISCARDABLE;

  PeHdr->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress = SectionHeader->VirtualAddress;
  PeHdr->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_DEBUG].Size           = sizeof (Data->Dir);

  Data = ImageToolBufferGetPointer (Buffer, DebugDirOffset);

  memset (Data, 0, sizeof (*Data));
  Data->Dir.Type       = EFI_IMAGE_DEBUG_TYPE_CODEVIEW;
  Data->Dir.SizeOfData = DebugDirSize - OFFSET_OF (image_tool_debug_dir_t, Nb10);
  Data->Dir.RVA        = SectionHeader->VirtualAddress + OFFSET_OF (image_tool_debug_dir_t, Nb10);
  Data->Dir.FileOffset = SectionHeader->PointerToRawData + OFFSET_OF (image_tool_debug_dir_t, Nb10);
  Data->Nb10.Signature = CODEVIEW_SIGNATURE_NB10;

  Data = NULL;

  return true;
}

static
bool
ToolImagePeHiiTableRequired (
  const image_tool_image_info_t  *Image
  )
{
  return Image->HiiInfo.DataSize != 0;
}

static
bool
ToolImagePeRelocTableRequired (
  const image_tool_image_info_t  *Image
  )
{
  return Image->RelocInfo.NumRelocs != 0;
}

static
bool
ToolImagePeDebugTableRequired (
  const image_tool_image_info_t  *Image
  )
{
  return Image->DebugInfo.SymbolsPath != NULL;
}

#define ToolImageEmitPeSection  PE_SUFFIX (ToolImageEmitPeSection)
static
bool
ToolImageEmitPeSection (
  image_tool_dynamic_buffer      *Buffer,
  EFI_IMAGE_NT_HEADERS           *PeHdr,
  EFI_IMAGE_SECTION_HEADER       *SectionHeader,
  const image_tool_image_info_t  *Image,
  bool                           Xip,
  const image_tool_segment_t     *Segment
  )
{
  uint32_t  SectionOffset;
  uint32_t  SizeOfRawData;
  uint32_t  Offset;

  SectionOffset = ImageToolBufferAppend (
                    Buffer,
                    Segment->Data,
                    Segment->UnpaddedSize
                    );
  if (SectionOffset == MAX_UINT32) {
    DEBUG_RAISE ();
    return false;
  }

  assert (IS_ALIGNED (SectionOffset, PeHdr->FileAlignment));

  if (!Xip) {
    SizeOfRawData = Segment->UnpaddedSize;
  } else {
    SizeOfRawData = Segment->ImageSize;
  }

  if (Segment->Name != NULL) {
    strncpy (
      (char *)SectionHeader->Name,
      Segment->Name,
      sizeof (SectionHeader->Name)
      );
    SectionHeader->Name[ARRAY_SIZE (SectionHeader->Name) - 1] = 0;
  }

  SectionHeader->VirtualSize      = Segment->ImageSize;
  SectionHeader->VirtualAddress   = Segment->ImageAddress;
  SectionHeader->SizeOfRawData    = ALIGN_VALUE (SizeOfRawData, PeHdr->FileAlignment);
  SectionHeader->PointerToRawData = SectionOffset;

  assert (SectionHeader->Characteristics == 0);

  if (Segment->Read) {
    SectionHeader->Characteristics |= EFI_IMAGE_SCN_MEM_READ;
  }

  if (Segment->Write) {
    SectionHeader->Characteristics |= EFI_IMAGE_SCN_MEM_WRITE;
  }

  if (Segment->Execute) {
    SectionHeader->Characteristics |= EFI_IMAGE_SCN_MEM_EXECUTE | EFI_IMAGE_SCN_CNT_CODE;

    PeHdr->SizeOfCode += Segment->ImageSize;
    if (PeHdr->BaseOfCode == 0) {
      PeHdr->BaseOfCode = Segment->ImageAddress;
    }
  } else {
    SectionHeader->Characteristics |= EFI_IMAGE_SCN_CNT_INITIALIZED_DATA;

    PeHdr->SizeOfInitializedData += Segment->ImageSize;
#if PE_ARCH == 32
    if (PeHdr->BaseOfData == 0) {
      PeHdr->BaseOfData = Segment->ImageAddress;
    }
#endif
  }

  assert (PeHdr->SizeOfImage == SectionHeader->VirtualAddress);
  PeHdr->SizeOfImage += SectionHeader->VirtualSize;

  Offset = ImageToolBufferAppendReserve (
             Buffer,
             SectionHeader->SizeOfRawData - Segment->UnpaddedSize
             );
  return Offset != MAX_UINT32;
}

#define ToolImageEmitPeSections  PE_SUFFIX (ToolImageEmitPeSections)
static
bool
ToolImageEmitPeSections (
  image_tool_dynamic_buffer      *Buffer,
  EFI_IMAGE_NT_HEADERS           *PeHdr,
  EFI_IMAGE_SECTION_HEADER       *SectionHeaders,
  const image_tool_image_info_t  *Image,
  bool                           Xip
  )
{
  bool                        Success;
  uint16_t                    Index;
  const image_tool_segment_t  *Segment;
  EFI_IMAGE_SECTION_HEADER    *SectionHeader;

  if (PeHdr->SizeOfImage != Image->SegmentInfo.Segments[0].ImageAddress) {
    DEBUG_RAISE ();
    return false;
  }

  for (Index = 0; Index < Image->SegmentInfo.NumSegments; ++Index) {
    Segment       = &Image->SegmentInfo.Segments[Index];
    SectionHeader = &SectionHeaders[Index];

    Success = ToolImageEmitPeSection (
                Buffer,
                PeHdr,
                SectionHeader,
                Image,
                Xip,
                Segment
                );
    if (!Success) {
      DEBUG_RAISE ();
      return false;
    }
  }

  if (ToolImagePeHiiTableRequired (Image)) {
    Success = ToolImageEmitPeHiiTable (Buffer, PeHdr, &SectionHeaders[Index], Image);
    if (!Success) {
      DEBUG_RAISE ();
      return false;
    }

    ++Index;
  }

  if (ToolImagePeRelocTableRequired (Image)) {
    Success = ToolImageEmitPeRelocTable (Buffer, PeHdr, &SectionHeaders[Index], Image);
    if (!Success) {
      DEBUG_RAISE ();
      return false;
    }

    ++Index;
  }

  if (ToolImagePeDebugTableRequired (Image)) {
    Success = ToolImageEmitPeDebugTable (Buffer, PeHdr, &SectionHeaders[Index], Image);
    if (!Success) {
      DEBUG_RAISE ();
      return false;
    }

    ++Index;
  }

  assert (Index == PeHdr->CommonHeader.FileHeader.NumberOfSections);

  if (Image->HeaderInfo.FixedAddress) {
    for (Index = 0; Index < PeHdr->CommonHeader.FileHeader.NumberOfSections; ++Index) {
      if ((SectionHeaders[Index].Characteristics & EFI_IMAGE_SCN_MEM_EXECUTE) == 0) {
        WriteUnaligned64 (
          (VOID *)&SectionHeaders[Index].PointerToRelocations,
          PeHdr->ImageBase
          );
        break;
      }
    }

    if (Index == PeHdr->CommonHeader.FileHeader.NumberOfSections) {
      DEBUG_RAISE ();
      return false;
    }
  }

  return true;
}

STATIC CONST EFI_IMAGE_DOS_HEADER mDosHdr = {
  .e_magic  = EFI_IMAGE_DOS_SIGNATURE,
  .e_lfanew = sizeof (mDosHdr)
};

#define ToolImageEmitPeFile  PE_SUFFIX (ToolImageEmitPeFile)
static
bool
ToolImageEmitPeFile (
  image_tool_dynamic_buffer      *Buffer,
  const image_tool_image_info_t  *Image,
  bool                           Xip
  )
{
  bool                      Success;
  uint32_t                  FileAlignment;
  uint32_t                  PeOffset;
  EFI_IMAGE_NT_HEADERS      *PeHdr;
  uint16_t                  NumSections;
  EFI_IMAGE_SECTION_HEADER  *SectionHeaders;
  uint32_t                  SectionHeadersOffset;
  uint32_t                  SectionHeadersSize;
  uint32_t                  SizeOfPeHeaders;
  uint32_t                  SizeOfHeaders;
  uint32_t                  AlignedSizeOfHeaders;
  void                      *BufferPeHdr;
  uint32_t                  Offset;

  if (!Xip) {
    FileAlignment = 64;
  } else {
    FileAlignment = Image->SegmentInfo.SegmentAlignment;
  }

  NumSections  = Image->SegmentInfo.NumSegments;
  NumSections += ToolImagePeHiiTableRequired (Image) ? 1 : 0;
  NumSections += ToolImagePeRelocTableRequired (Image) ? 1 : 0;
  NumSections += ToolImagePeDebugTableRequired (Image) ? 1 : 0;

  Offset = ImageToolBufferAppend (Buffer, &mDosHdr, sizeof (mDosHdr));
  if (Offset == MAX_UINT32) {
    DEBUG_RAISE ();
    return false;
  }

  SectionHeadersOffset = sizeof (*PeHdr) + SIZE_OF_DATA_DIRECTORY;
  SectionHeadersSize   = NumSections * sizeof (EFI_IMAGE_SECTION_HEADER);
  SizeOfPeHeaders      = SectionHeadersOffset + SectionHeadersSize;
  SizeOfHeaders        = sizeof (mDosHdr) + SizeOfPeHeaders;
  AlignedSizeOfHeaders = ALIGN_VALUE (SizeOfHeaders, FileAlignment);
  //
  // Necessary adjustment for '-D EDK2_REDUCE_FW_SIZE' build option.
  //
  if (AlignedSizeOfHeaders < Image->SegmentInfo.Segments[0].ImageAddress) {
    AlignedSizeOfHeaders = (uint32_t)Image->SegmentInfo.Segments[0].ImageAddress;
  }

  PeOffset = ImageToolBufferAppendReserve (
               Buffer,
               AlignedSizeOfHeaders - sizeof (mDosHdr)
               );
  if (PeOffset == MAX_UINT32) {
    DEBUG_RAISE ();
    return false;
  }

  PeHdr = AllocateZeroPool (SizeOfPeHeaders);
  if (PeHdr == NULL) {
    DEBUG_RAISE ();
    return false;
  }

  SectionHeaders = (EFI_IMAGE_SECTION_HEADER *)((UINT8 *)PeHdr + SectionHeadersOffset);

  PeHdr->CommonHeader.Signature                       = EFI_IMAGE_NT_SIGNATURE;
  PeHdr->CommonHeader.FileHeader.Machine              = Image->HeaderInfo.Machine;
  PeHdr->CommonHeader.FileHeader.NumberOfSections     = NumSections;
  PeHdr->CommonHeader.FileHeader.SizeOfOptionalHeader = SIZE_OF_OPTIONAL_HEADER;
  PeHdr->CommonHeader.FileHeader.Characteristics =
    EFI_IMAGE_FILE_EXECUTABLE_IMAGE | EFI_IMAGE_FILE_LINE_NUMS_STRIPPED |
    EFI_IMAGE_FILE_LOCAL_SYMS_STRIPPED | EFI_IMAGE_FILE_LARGE_ADDRESS_AWARE;

  if (Image->RelocInfo.RelocsStripped) {
    PeHdr->CommonHeader.FileHeader.Characteristics |= EFI_IMAGE_FILE_RELOCS_STRIPPED;
  }

  PeHdr->Magic               = EFI_IMAGE_NT_OPTIONAL_HDR_MAGIC;
  PeHdr->AddressOfEntryPoint = Image->HeaderInfo.EntryPointAddress;
  PeHdr->ImageBase           = (EFI_IMAGE_NT_BASE_ADDRESS)Image->HeaderInfo.BaseAddress;
  PeHdr->SectionAlignment    = Image->SegmentInfo.SegmentAlignment;
  PeHdr->FileAlignment       = FileAlignment;
  PeHdr->SizeOfImage         = AlignedSizeOfHeaders;
  PeHdr->SizeOfHeaders       = AlignedSizeOfHeaders;
  PeHdr->Subsystem           = Image->HeaderInfo.Subsystem;
  PeHdr->NumberOfRvaAndSizes = EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES;

  Success = ToolImageEmitPeSections (Buffer, PeHdr, SectionHeaders, Image, Xip);
  if (!Success) {
    FreePool (PeHdr);
    DEBUG_RAISE ();
    return false;
  }

  BufferPeHdr = ImageToolBufferGetPointer (Buffer, PeOffset);
  memmove (BufferPeHdr, PeHdr, SizeOfPeHeaders);
  FreePool (PeHdr);
  BufferPeHdr = NULL;

  return true;
}

#define ToolImageEmitPe  PE_SUFFIX (ToolImageEmitPe)
void *
ToolImageEmitPe (
  const image_tool_image_info_t  *Image,
  uint32_t                       *FileSize,
  bool                           Xip
  )
{
  bool                       Success;
  image_tool_dynamic_buffer  Buffer;
  void                       *FileBuffer;

  assert (Image    != NULL);
  assert (FileSize != NULL);

  ImageInitUnpaddedSize (Image);

  ImageToolBufferInit (&Buffer);

  Success = ToolImageEmitPeFile (&Buffer, Image, Xip);
  if (!Success) {
    DEBUG_RAISE ();
    ImageToolBufferFree (&Buffer);
    return NULL;
  }

  FileBuffer = ImageToolBufferDump (FileSize, &Buffer);

  ImageToolBufferFree (&Buffer);

  if (FileBuffer == NULL) {
    DEBUG_RAISE ();
    return NULL;
  }

  return FileBuffer;
}
