//////////////////////////////////////////////////////////////////////////////
//
//  Image manipulation functions (image.cpp of detours.lib)
//
//  Microsoft Research Detours Package, Version 4.0.1
//
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//
//  Used for for payloads, byways, and imports.
//

#if _MSC_VER < 1299
#pragma warning(disable: 4710)
#endif

// #define DETOUR_DEBUG 1
#define DETOURS_INTERNAL
#include "detours.h"

#if DETOURS_VERSION != 0x4c0c1   // 0xMAJORcMINORcPATCH
#error detours.h version mismatch
#endif

namespace Detour
{
//////////////////////////////////////////////////////////////////////////////
//
#ifndef _STRSAFE_H_INCLUDED_
_Must_inspect_result_
static inline HRESULT StringCchLengthA(
    _In_reads_or_z_(cchMax) LPCSTR psz,
    _In_
    _In_range_(1, STRSAFE_MAX_CCH) size_t cchMax,
    _Out_opt_
    _Deref_out_range_(<, cchMax)
    _Deref_out_range_(<=, _String_length_(psz))
    _Out_ size_t* pcch)
{
    HRESULT hr = S_OK;
    size_t cchMaxPrev = cchMax;

    if (cchMax > 2147483647) {
        return ERROR_INVALID_PARAMETER;
    }

    while (cchMax && (*psz != '\0')) {
        psz++;
        cchMax--;
    }

    if (cchMax == 0) {
        // the string is longer than cchMax
        hr = ERROR_INVALID_PARAMETER;
    }

    if (SUCCEEDED(hr) && pcch) {
        *pcch = cchMaxPrev - cchMax;
    }

    return hr;
}

_Must_inspect_result_
static inline HRESULT StringCchCopyA(
    _Out_writes_(cchDest) _Always_(_Post_z_) LPSTR pszDest,
    _In_ size_t cchDest,
    _In_ LPCSTR pszSrc)
{
    HRESULT hr = S_OK;

    if (cchDest == 0) {
        // can not null terminate a zero-byte dest buffer
        hr = ERROR_INVALID_PARAMETER;
    }
    else {
        while (cchDest && (*pszSrc != '\0')) {
            *pszDest++ = *pszSrc++;
            cchDest--;
        }

        if (cchDest == 0) {
            // we are going to truncate pszDest
            pszDest--;
            hr = ERROR_INVALID_PARAMETER;
        }

        *pszDest= '\0';
    }

    return hr;
}

_Must_inspect_result_
static inline HRESULT StringCchCatA(
    _Out_writes_(cchDest) _Always_(_Post_z_) LPSTR pszDest,
    _In_ size_t cchDest,
    _In_ LPCSTR pszSrc)
{
    HRESULT hr;
    size_t cchDestCurrent;

    if (cchDest > 2147483647){
        return ERROR_INVALID_PARAMETER;
    }

    hr = StringCchLengthA(pszDest, cchDest, &cchDestCurrent);

    if (SUCCEEDED(hr) && cchDestCurrent < cchDest) {
        hr = StringCchCopyA(pszDest + cchDestCurrent,
                            cchDest - cchDestCurrent,
                            pszSrc);
    }

    return hr;
}

#endif

///////////////////////////////////////////////////////////////////////////////
//
class CImageData
{
    friend class CImage;

public:
    CImageData(PBYTE pbData, DWORD cbData);
    ~CImageData();

    PBYTE                   Enumerate(GUID *pGuid, DWORD *pcbData, DWORD *pnIterator);
    PBYTE                   Find(REFGUID rguid, DWORD *pcbData);
    PBYTE                   Set(REFGUID rguid, PBYTE pbData, DWORD cbData);

    BOOL                    Delete(REFGUID rguid);
    BOOL                    Purge();

    BOOL                    IsEmpty()           { return m_cbData == 0; }
    BOOL                    IsValid();

protected:
    BOOL                    SizeTo(DWORD cbData);

protected:
    _Field_size_(m_cbAlloc)
    PBYTE                   m_pbData;
    DWORD                   m_cbData;
    DWORD                   m_cbAlloc;
};

class CImageImportName;

class CImageImportFile
{
    friend class CImage;
    friend class CImageImportName;

public:
    CImageImportFile();
    ~CImageImportFile();

public:
    CImageImportFile *      m_pNextFile;
    BOOL                    m_fByway;

    _Field_size_(m_nImportNames)
    CImageImportName *      m_pImportNames;
    DWORD                   m_nImportNames;

    DWORD                   m_rvaOriginalFirstThunk;
    DWORD                   m_rvaFirstThunk;

    DWORD                   m_nForwarderChain;
    LPCSTR                  m_pszOrig;
    LPCSTR                  m_pszName;
};

class CImageImportName
{
    friend class CImage;
    friend class CImageImportFile;

public:
    CImageImportName();
    ~CImageImportName();

public:
    WORD        m_nHint;
    ULONG       m_nOrig;
    ULONG       m_nOrdinal;
    LPCSTR      m_pszOrig;
    LPCSTR      m_pszName;
};

class CImage
{
    friend class CImageThunks;
    friend class CImageChars;
    friend class CImageImportFile;
    friend class CImageImportName;

public:
    CImage();
    ~CImage();

    static CImage *         IsValid(PDETOUR_BINARY pBinary);

public:                                                 // File Functions
    BOOL                    Read(HANDLE hFile);
    BOOL                    Write(HANDLE hFile);
    BOOL                    Close();

public:                                                 // Manipulation Functions
    PBYTE                   DataEnum(GUID *pGuid, DWORD *pcbData, DWORD *pnIterator);
    PBYTE                   DataFind(REFGUID rguid, DWORD *pcbData);
    PBYTE                   DataSet(REFGUID rguid, PBYTE pbData, DWORD cbData);
    BOOL                    DataDelete(REFGUID rguid);
    BOOL                    DataPurge();

    BOOL                    EditImports(PVOID pContext,
                                        PF_DETOUR_BINARY_BYWAY_CALLBACK pfBywayCallback,
                                        PF_DETOUR_BINARY_FILE_CALLBACK pfFileCallback,
                                        PF_DETOUR_BINARY_SYMBOL_CALLBACK pfSymbolCallback,
                                        PF_DETOUR_BINARY_COMMIT_CALLBACK pfCommitCallback);

protected:
    BOOL                    WriteFile(HANDLE hFile,
                                      LPCVOID lpBuffer,
                                      DWORD nNumberOfBytesToWrite,
                                      LPDWORD lpNumberOfBytesWritten);
    BOOL                    CopyFileData(HANDLE hFile, DWORD nOldPos, DWORD cbData);
    BOOL                    ZeroFileData(HANDLE hFile, DWORD cbData);
    BOOL                    AlignFileData(HANDLE hFile);

    BOOL                    SizeOutputBuffer(DWORD cbData);
    PBYTE                   AllocateOutput(DWORD cbData, DWORD *pnVirtAddr);

    PVOID                   RvaToVa(ULONG_PTR nRva);
    DWORD                   RvaToFileOffset(DWORD nRva);

    DWORD                   FileAlign(DWORD nAddr);
    DWORD                   SectionAlign(DWORD nAddr);

    BOOL                    CheckImportsNeeded(DWORD *pnTables,
                                               DWORD *pnThunks,
                                               DWORD *pnChars);

    CImageImportFile *      NewByway(_In_ LPCSTR pszName);

private:
    DWORD                   m_dwValidSignature;
    CImageData *            m_pImageData;               // Read & Write

    HANDLE                  m_hMap;                     // Read & Write
    PBYTE                   m_pMap;                     // Read & Write

    DWORD                   m_nNextFileAddr;            // Write
    DWORD                   m_nNextVirtAddr;            // Write

    IMAGE_DOS_HEADER        m_DosHeader;                // Read & Write
    IMAGE_NT_HEADERS        m_NtHeader;                 // Read & Write
    IMAGE_SECTION_HEADER    m_SectionHeaders[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];

    DWORD                   m_nPrePE;
    DWORD                   m_cbPrePE;
    DWORD                   m_cbPostPE;

    DWORD                   m_nPeOffset;
    DWORD                   m_nSectionsOffset;
    DWORD                   m_nExtraOffset;
    DWORD                   m_nFileSize;

    DWORD                   m_nOutputVirtAddr;
    DWORD                   m_nOutputVirtSize;
    DWORD                   m_nOutputFileAddr;

    _Field_size_(m_cbOutputBuffer)
    PBYTE                   m_pbOutputBuffer;
    DWORD                   m_cbOutputBuffer;

    CImageImportFile *      m_pImportFiles;
    DWORD                   m_nImportFiles;

    BOOL                    m_fHadDetourSection;

private:
    enum {
        DETOUR_IMAGE_VALID_SIGNATURE = 0xfedcba01,      // "Dtr\0"
    };
};

//////////////////////////////////////////////////////////////////////////////
//
static BYTE s_rbDosCode[0x10] = {
    0x0E,0x1F,0xBA,0x0E,0x00,0xB4,0x09,0xCD,
    0x21,0xB8,0x01,0x4C,0xCD,0x21,'*','*'
};

static inline DWORD Max(DWORD a, DWORD b)
{
    return a > b ? a : b;
}

static inline DWORD Align(DWORD a, DWORD size)
{
    size--;
    return (a + size) & ~size;
}

static inline DWORD QuadAlign(DWORD a)
{
    return Align(a, 8);
}

static LPCSTR DuplicateString(_In_ LPCSTR pszIn)
{
    if (pszIn == NULL) {
        return NULL;
    }

    size_t cch;
    HRESULT hr = StringCchLengthA(pszIn, 8192, &cch);
    if (FAILED(hr)) {
        SetLastError(ERROR_INVALID_PARAMETER);
        return NULL;
    }

    PCHAR pszOut = new NOTHROW CHAR [cch + 1];
    if (pszOut == NULL) {
        SetLastError(ERROR_OUTOFMEMORY);
        return NULL;
    }

    hr = StringCchCopyA(pszOut, cch + 1, pszIn);
    if (FAILED(hr)) {
        delete[] pszOut;
        return NULL;
    }

    return pszOut;
}

static VOID ReleaseString(_In_opt_ LPCSTR psz)
{
    if (psz != NULL) {
        delete[] psz;
    }
}

//////////////////////////////////////////////////////////////////////////////
//
CImageImportFile::CImageImportFile()
{
    m_pNextFile = NULL;
    m_fByway = FALSE;

    m_pImportNames = NULL;
    m_nImportNames = 0;

    m_rvaOriginalFirstThunk = 0;
    m_rvaFirstThunk = 0;

    m_nForwarderChain = (UINT)0;
    m_pszName = NULL;
    m_pszOrig = NULL;
}

CImageImportFile::~CImageImportFile()
{
    if (m_pNextFile) {
        delete m_pNextFile;
        m_pNextFile = NULL;
    }
    if (m_pImportNames) {
        delete[] m_pImportNames;
        m_pImportNames = NULL;
        m_nImportNames = 0;
    }
    if (m_pszName) {
        delete[] m_pszName;
        m_pszName = NULL;
    }
    if (m_pszOrig) {
        delete[] m_pszOrig;
        m_pszOrig = NULL;
    }
}

CImageImportName::CImageImportName()
{
    m_nOrig = 0;
    m_nOrdinal = 0;
    m_nHint = 0;
    m_pszName = NULL;
    m_pszOrig = NULL;
}

CImageImportName::~CImageImportName()
{
    if (m_pszName) {
        delete[] m_pszName;
        m_pszName = NULL;
    }
    if (m_pszOrig) {
        delete[] m_pszOrig;
        m_pszOrig = NULL;
    }
}

//////////////////////////////////////////////////////////////////////////////
//
CImageData::CImageData(PBYTE pbData, DWORD cbData)
{
    m_pbData = pbData;
    m_cbData = cbData;
    m_cbAlloc = 0;
}

CImageData::~CImageData()
{
    IsValid();

    if (m_cbAlloc == 0) {
        m_pbData = NULL;
    }
    if (m_pbData) {
        delete[] m_pbData;
        m_pbData = NULL;
    }
    m_cbData = 0;
    m_cbAlloc = 0;
}

BOOL CImageData::SizeTo(DWORD cbData)
{
    IsValid();

    if (cbData <= m_cbAlloc) {
        return TRUE;
    }

    PBYTE pbNew = new NOTHROW BYTE [cbData];
    if (pbNew == NULL) {
        SetLastError(ERROR_OUTOFMEMORY);
        return FALSE;
    }

    if (m_pbData) {
        CopyMemory(pbNew, m_pbData, m_cbData);
        if (m_cbAlloc > 0) {
            delete[] m_pbData;
        }
        m_pbData = NULL;
    }
    m_pbData = pbNew;
    m_cbAlloc = cbData;

    IsValid();

    return TRUE;
}

BOOL CImageData::Purge()
{
    m_cbData = 0;

    IsValid();

    return TRUE;
}

BOOL CImageData::IsValid()
{
    if (m_pbData == NULL) {
        return TRUE;
    }

    PBYTE pbBeg = m_pbData;
    PBYTE pbEnd = m_pbData + m_cbData;

    for (PBYTE pbIter = pbBeg; pbIter < pbEnd;) {
        PDETOUR_SECTION_RECORD pRecord = (PDETOUR_SECTION_RECORD)pbIter;

        if (pRecord->cbBytes < sizeof(DETOUR_SECTION_RECORD)) {
            return FALSE;
        }
        if (pRecord->nReserved != 0) {
            return FALSE;
        }

        pbIter += pRecord->cbBytes;
    }
    return TRUE;
}

PBYTE CImageData::Enumerate(GUID *pGuid, DWORD *pcbData, DWORD *pnIterator)
{
    IsValid();

    if (pnIterator == NULL ||
        m_cbData < *pnIterator + sizeof(DETOUR_SECTION_RECORD)) {

        if (pcbData) {
            *pcbData = 0;
        }
        if (pGuid) {
            ZeroMemory(pGuid, sizeof(*pGuid));
        }
        return NULL;
    }

    PDETOUR_SECTION_RECORD pRecord = (PDETOUR_SECTION_RECORD)(m_pbData + *pnIterator);

    if (pGuid) {
        *pGuid = pRecord->guid;
    }
    if (pcbData) {
        *pcbData = pRecord->cbBytes - sizeof(DETOUR_SECTION_RECORD);
    }
    *pnIterator = (LONG)(((PBYTE)pRecord - m_pbData) + pRecord->cbBytes);

    return (PBYTE)(pRecord + 1);
}

PBYTE CImageData::Find(REFGUID rguid, DWORD *pcbData)
{
    IsValid();

    DWORD cbBytes = sizeof(DETOUR_SECTION_RECORD);
    for (DWORD nOffset = 0; nOffset < m_cbData; nOffset += cbBytes) {
        PDETOUR_SECTION_RECORD pRecord = (PDETOUR_SECTION_RECORD)(m_pbData + nOffset);

        cbBytes = pRecord->cbBytes;
        if (cbBytes > m_cbData) {
            break;
        }
        if (cbBytes < sizeof(DETOUR_SECTION_RECORD)) {
            continue;
        }

        if (DetourAreSameGuid(pRecord->guid, rguid)) {
            *pcbData = cbBytes - sizeof(DETOUR_SECTION_RECORD);
            return (PBYTE)(pRecord + 1);
        }
    }

    if (pcbData) {
        *pcbData = 0;
    }
    return NULL;
}

BOOL CImageData::Delete(REFGUID rguid)
{
    IsValid();

    PBYTE pbFound = NULL;
    DWORD cbFound = 0;

    pbFound = Find(rguid, &cbFound);
    if (pbFound == NULL) {
        SetLastError(ERROR_MOD_NOT_FOUND);
        return FALSE;
    }

    pbFound -= sizeof(DETOUR_SECTION_RECORD);
    cbFound += sizeof(DETOUR_SECTION_RECORD);

    PBYTE pbRestData = pbFound + cbFound;
    DWORD cbRestData = m_cbData - (LONG)(pbRestData - m_pbData);

    if (cbRestData) {
        MoveMemory(pbFound, pbRestData, cbRestData);
    }
    m_cbData -= cbFound;

    IsValid();
    return TRUE;
}

PBYTE CImageData::Set(REFGUID rguid, PBYTE pbData, DWORD cbData)
{
    IsValid();
    Delete(rguid);

    DWORD cbAlloc = QuadAlign(cbData);

    if (!SizeTo(m_cbData + cbAlloc + sizeof(DETOUR_SECTION_RECORD))) {
        return NULL;
    }

    PDETOUR_SECTION_RECORD pRecord = (PDETOUR_SECTION_RECORD)(m_pbData + m_cbData);
    pRecord->cbBytes = cbAlloc + sizeof(DETOUR_SECTION_RECORD);
    pRecord->nReserved = 0;
    pRecord->guid = rguid;

    PBYTE pbDest = (PBYTE)(pRecord + 1);
    if (pbData) {
        CopyMemory(pbDest, pbData, cbData);
        if (cbData < cbAlloc) {
            ZeroMemory(pbDest + cbData, cbAlloc - cbData);
        }
    }
    else {
        if (cbAlloc > 0) {
            ZeroMemory(pbDest, cbAlloc);
        }
    }

    m_cbData += cbAlloc + sizeof(DETOUR_SECTION_RECORD);

    IsValid();
    return pbDest;
}

//////////////////////////////////////////////////////////////////////////////
//
class CImageThunks
{
private:
    CImage *            m_pImage;
    PIMAGE_THUNK_DATA   m_pThunks;
    DWORD               m_nThunks;
    DWORD               m_nThunksMax;
    DWORD               m_nThunkVirtAddr;

public:
    CImageThunks(CImage *pImage, DWORD nThunksMax, DWORD *pnAddr)
    {
        m_pImage = pImage;
        m_nThunks = 0;
        m_nThunksMax = nThunksMax;
        m_pThunks = (PIMAGE_THUNK_DATA)
            m_pImage->AllocateOutput(sizeof(IMAGE_THUNK_DATA) * nThunksMax,
                                     &m_nThunkVirtAddr);
        *pnAddr = m_nThunkVirtAddr;
    }

    PIMAGE_THUNK_DATA Current(DWORD *pnVirtAddr)
    {
        if (m_nThunksMax > 1) {
            *pnVirtAddr = m_nThunkVirtAddr;
            return m_pThunks;
        }
        *pnVirtAddr = 0;
        return NULL;
    }

    PIMAGE_THUNK_DATA Allocate(ULONG_PTR nData, DWORD *pnVirtAddr)
    {
        if (m_nThunks < m_nThunksMax) {
            *pnVirtAddr = m_nThunkVirtAddr;

            m_nThunks++;
            m_nThunkVirtAddr += sizeof(IMAGE_THUNK_DATA);
            m_pThunks->u1.Ordinal = nData;
            return m_pThunks++;
        }
        *pnVirtAddr = 0;
        return NULL;
    }

    DWORD   Size()
    {
        return m_nThunksMax * sizeof(IMAGE_THUNK_DATA);
    }
};

//////////////////////////////////////////////////////////////////////////////
//
class CImageChars
{
private:
    CImage *        m_pImage;
    PCHAR           m_pChars;
    DWORD           m_nChars;
    DWORD           m_nCharsMax;
    DWORD           m_nCharVirtAddr;

public:
    CImageChars(CImage *pImage, _In_ DWORD nCharsMax, _Out_ DWORD *pnAddr)
    {
        m_pImage = pImage;
        m_nChars = 0;
        m_nCharsMax = nCharsMax;
        m_pChars = (PCHAR)m_pImage->AllocateOutput(nCharsMax, &m_nCharVirtAddr);
        *pnAddr = m_nCharVirtAddr;
    }

    LPCSTR Allocate(_In_ LPCSTR pszString, _Out_ DWORD *pnVirtAddr)
    {
        DWORD nLen = (DWORD)strlen(pszString) + 1;
        nLen += (nLen & 1);

        if (m_nChars + nLen > m_nCharsMax) {
            *pnVirtAddr = 0;
            return NULL;
        }

        *pnVirtAddr = m_nCharVirtAddr;
        HRESULT hrRet = StringCchCopyA(m_pChars, m_nCharsMax, pszString);

        if (FAILED(hrRet)) {
            return NULL;
        }

        pszString = m_pChars;

        m_pChars += nLen;
        m_nChars += nLen;
        m_nCharVirtAddr += nLen;

        return pszString;
    }

    LPCSTR Allocate(_In_ LPCSTR pszString, _In_ DWORD nHint, _Out_ DWORD *pnVirtAddr)
    {
        DWORD nLen = (DWORD)strlen(pszString) + 1 + sizeof(USHORT);
        nLen += (nLen & 1);

        if (m_nChars + nLen > m_nCharsMax) {
            *pnVirtAddr = 0;
            return NULL;
        }

        *pnVirtAddr = m_nCharVirtAddr;
        *(USHORT *)m_pChars = (USHORT)nHint;

        HRESULT hrRet = StringCchCopyA(m_pChars + sizeof(USHORT), m_nCharsMax, pszString);
        if (FAILED(hrRet)) {
            return NULL;
        }

        pszString = m_pChars + sizeof(USHORT);

        m_pChars += nLen;
        m_nChars += nLen;
        m_nCharVirtAddr += nLen;

        return pszString;
    }

    DWORD Size()
    {
        return m_nChars;
    }
};

//////////////////////////////////////////////////////////////////////////////
//
CImage * CImage::IsValid(PDETOUR_BINARY pBinary)
{
    if (pBinary) {
        CImage *pImage = (CImage *)pBinary;

        if (pImage->m_dwValidSignature == DETOUR_IMAGE_VALID_SIGNATURE) {
            return pImage;
        }
    }
    SetLastError(ERROR_INVALID_HANDLE);
    return NULL;
}

CImage::CImage()
{
    m_dwValidSignature = (DWORD)DETOUR_IMAGE_VALID_SIGNATURE;

    m_hMap = NULL;
    m_pMap = NULL;

    m_nPeOffset = 0;
    m_nSectionsOffset = 0;

    m_pbOutputBuffer = NULL;
    m_cbOutputBuffer = 0;

    m_pImageData = NULL;

    m_pImportFiles = NULL;
    m_nImportFiles = 0;

    m_fHadDetourSection = FALSE;
}

CImage::~CImage()
{
    Close();
    m_dwValidSignature = 0;
}

BOOL CImage::Close()
{
    if (m_pImportFiles) {
        delete m_pImportFiles;
        m_pImportFiles = NULL;
        m_nImportFiles = 0;
    }

    if (m_pImageData) {
        delete m_pImageData;
        m_pImageData = NULL;
    }

    if (m_pMap != NULL) {
        UnmapViewOfFile(m_pMap);
        m_pMap = NULL;
    }

    if (m_hMap) {
        CloseHandle(m_hMap);
        m_hMap = NULL;
    }

    if (m_pbOutputBuffer) {
        delete[] m_pbOutputBuffer;
        m_pbOutputBuffer = NULL;
        m_cbOutputBuffer = 0;
    }
    return TRUE;
}

//////////////////////////////////////////////////////////////////////////////
//
PBYTE CImage::DataEnum(GUID *pGuid, DWORD *pcbData, DWORD *pnIterator)
{
    if (m_pImageData == NULL) {
        return NULL;
    }
    return m_pImageData->Enumerate(pGuid, pcbData, pnIterator);
}

PBYTE CImage::DataFind(REFGUID rguid, DWORD *pcbData)
{
    if (m_pImageData == NULL) {
        return NULL;
    }
    return m_pImageData->Find(rguid, pcbData);
}

PBYTE CImage::DataSet(REFGUID rguid, PBYTE pbData, DWORD cbData)
{
    if (m_pImageData == NULL) {
        return NULL;
    }
    return m_pImageData->Set(rguid, pbData, cbData);
}

BOOL CImage::DataDelete(REFGUID rguid)
{
    if (m_pImageData == NULL) {
        return FALSE;
    }
    return m_pImageData->Delete(rguid);
}

BOOL CImage::DataPurge()
{
    if (m_pImageData == NULL) {
        return TRUE;
    }
    return m_pImageData->Purge();
}

//////////////////////////////////////////////////////////////////////////////
//
BOOL CImage::SizeOutputBuffer(DWORD cbData)
{
    if (m_cbOutputBuffer < cbData) {
        if (cbData < 1024) {//65536
            cbData = 1024;
        }
        cbData = FileAlign(cbData);

        PBYTE pOutput = new NOTHROW BYTE [cbData];
        if (pOutput == NULL) {
            SetLastError(ERROR_OUTOFMEMORY);
            return FALSE;
        }

        if (m_pbOutputBuffer) {
            CopyMemory(pOutput, m_pbOutputBuffer, m_cbOutputBuffer);

            delete[] m_pbOutputBuffer;
            m_pbOutputBuffer = NULL;
        }

        ZeroMemory(pOutput + m_cbOutputBuffer, cbData - m_cbOutputBuffer),

        m_pbOutputBuffer = pOutput;
        m_cbOutputBuffer = cbData;
    }
    return TRUE;
}

PBYTE CImage::AllocateOutput(DWORD cbData, DWORD *pnVirtAddr)
{
    cbData = QuadAlign(cbData);

    PBYTE pbData = m_pbOutputBuffer + m_nOutputVirtSize;

    *pnVirtAddr = m_nOutputVirtAddr + m_nOutputVirtSize;
    m_nOutputVirtSize += cbData;

    if (m_nOutputVirtSize > m_cbOutputBuffer) {
        SetLastError(ERROR_OUTOFMEMORY);
        return NULL;
    }

    ZeroMemory(pbData, cbData);

    return pbData;
}

//////////////////////////////////////////////////////////////////////////////
//
DWORD CImage::FileAlign(DWORD nAddr)
{
    return Align(nAddr, m_NtHeader.OptionalHeader.FileAlignment);
}

DWORD CImage::SectionAlign(DWORD nAddr)
{
    return Align(nAddr, m_NtHeader.OptionalHeader.SectionAlignment);
}

//////////////////////////////////////////////////////////////////////////////
//
PVOID CImage::RvaToVa(ULONG_PTR nRva)
{
    if (nRva == 0) {
        return NULL;
    }

    for (DWORD n = 0; n < m_NtHeader.FileHeader.NumberOfSections; n++) {
        DWORD vaStart = m_SectionHeaders[n].VirtualAddress;
        DWORD vaEnd = vaStart + m_SectionHeaders[n].SizeOfRawData;

        if (nRva >= vaStart && nRva < vaEnd) {
            return (PBYTE)m_pMap
                + m_SectionHeaders[n].PointerToRawData
                + nRva - m_SectionHeaders[n].VirtualAddress;
        }
    }
    return NULL;
}

DWORD CImage::RvaToFileOffset(DWORD nRva)
{
    DWORD n;
    for (n = 0; n < m_NtHeader.FileHeader.NumberOfSections; n++) {
        DWORD vaStart = m_SectionHeaders[n].VirtualAddress;
        DWORD vaEnd = vaStart + m_SectionHeaders[n].SizeOfRawData;

        if (nRva >= vaStart && nRva < vaEnd) {
            return m_SectionHeaders[n].PointerToRawData
                + nRva - m_SectionHeaders[n].VirtualAddress;
        }
    }
    return 0;
}

//////////////////////////////////////////////////////////////////////////////
//
BOOL CImage::WriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
                       LPDWORD lpNumberOfBytesWritten)
{
    return ::WriteFile(hFile,
                       lpBuffer,
                       nNumberOfBytesToWrite,
                       lpNumberOfBytesWritten,
                       NULL);
}


BOOL CImage::CopyFileData(HANDLE hFile, DWORD nOldPos, DWORD cbData)
{
    DWORD cbDone = 0;
    return WriteFile(hFile, m_pMap + nOldPos, cbData, &cbDone);
}

BOOL CImage::ZeroFileData(HANDLE hFile, DWORD cbData)
{
    if (!SizeOutputBuffer(4096)) {
        return FALSE;
    }

    ZeroMemory(m_pbOutputBuffer, 4096);

    for (DWORD cbLeft = cbData; cbLeft > 0;) {
        DWORD cbStep = cbLeft > sizeof(m_pbOutputBuffer)
            ? sizeof(m_pbOutputBuffer) : cbLeft;
        DWORD cbDone = 0;

        if (!WriteFile(hFile, m_pbOutputBuffer, cbStep, &cbDone)) {
            return FALSE;
        }
        if (cbDone == 0) {
            break;
        }

        cbLeft -= cbDone;
    }
    return TRUE;
}

BOOL CImage::AlignFileData(HANDLE hFile)
{
    DWORD nLastFileAddr = m_nNextFileAddr;

    m_nNextFileAddr = FileAlign(m_nNextFileAddr);
    m_nNextVirtAddr = SectionAlign(m_nNextVirtAddr);

    if (hFile != INVALID_HANDLE_VALUE) {
        if (m_nNextFileAddr > nLastFileAddr) {
            if (SetFilePointer(hFile, nLastFileAddr, NULL, FILE_BEGIN) == ~0u) {
                return FALSE;
            }
            return ZeroFileData(hFile, m_nNextFileAddr - nLastFileAddr);
        }
    }
    return TRUE;
}

BOOL CImage::Read(HANDLE hFile)
{
    DWORD n;
    PBYTE pbData = NULL;
    DWORD cbData = 0;

    if (hFile == INVALID_HANDLE_VALUE) {
        SetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }

    ///////////////////////////////////////////////////////// Create mapping.
    //
    m_nFileSize = GetFileSize(hFile, NULL);
    if (m_nFileSize == (DWORD)-1) {
        return FALSE;
    }

    m_hMap = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
    if (m_hMap == NULL) {
        return FALSE;
    }

    m_pMap = (PBYTE)MapViewOfFileEx(m_hMap, FILE_MAP_READ, 0, 0, 0, NULL);
    if (m_pMap == NULL) {
        return FALSE;
    }

    ////////////////////////////////////////////////////// Process DOS Header.
    //
    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)m_pMap;
    if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
        SetLastError(ERROR_BAD_EXE_FORMAT);
        return FALSE;
    }
    m_nPeOffset = pDosHeader->e_lfanew;
    m_nPrePE = 0;
    m_cbPrePE = QuadAlign(pDosHeader->e_lfanew);

    if (m_nPeOffset > m_nFileSize ||
        m_nPeOffset + sizeof(m_NtHeader) > m_nFileSize) {

        SetLastError(ERROR_BAD_EXE_FORMAT);
        return FALSE;
    }

    CopyMemory(&m_DosHeader, m_pMap + m_nPrePE, sizeof(m_DosHeader));

    /////////////////////////////////////////////////////// Process PE Header.
    //
    CopyMemory(&m_NtHeader, m_pMap + m_nPeOffset, sizeof(m_NtHeader));
    if (m_NtHeader.Signature != IMAGE_NT_SIGNATURE) {
        SetLastError(ERROR_INVALID_EXE_SIGNATURE);
        return FALSE;
    }
    if (m_NtHeader.FileHeader.SizeOfOptionalHeader == 0) {
        SetLastError(ERROR_EXE_MARKED_INVALID);
        return FALSE;
    }
    m_nSectionsOffset = m_nPeOffset
        + sizeof(m_NtHeader.Signature)
        + sizeof(m_NtHeader.FileHeader)
        + m_NtHeader.FileHeader.SizeOfOptionalHeader;

    ///////////////////////////////////////////////// Process Section Headers.
    //
    if (m_NtHeader.FileHeader.NumberOfSections > ARRAYSIZE(m_SectionHeaders)) {
        SetLastError(ERROR_EXE_MARKED_INVALID);
        return FALSE;
    }
    CopyMemory(&m_SectionHeaders,
               m_pMap + m_nSectionsOffset,
               sizeof(m_SectionHeaders[0]) * m_NtHeader.FileHeader.NumberOfSections);

    /////////////////////////////////////////////////// Parse .detour Section.
    //
    DWORD rvaOriginalImageDirectory = 0;
    DWORD rvaDetourBeg = 0;
    DWORD rvaDetourEnd = 0;

    _Analysis_assume_(m_NtHeader.FileHeader.NumberOfSections <= ARRAYSIZE(m_SectionHeaders));

    for (n = 0; n < m_NtHeader.FileHeader.NumberOfSections; n++) {
        if (strcmp((PCHAR)m_SectionHeaders[n].Name, ".detour") == 0) {
            DETOUR_SECTION_HEADER dh;
            CopyMemory(&dh,
                       m_pMap + m_SectionHeaders[n].PointerToRawData,
                       sizeof(dh));

            rvaOriginalImageDirectory = dh.nOriginalImportVirtualAddress;
            if (dh.cbPrePE != 0) {
                m_nPrePE = m_SectionHeaders[n].PointerToRawData + sizeof(dh);
                m_cbPrePE = dh.cbPrePE;
            }
            rvaDetourBeg = m_SectionHeaders[n].VirtualAddress;
            rvaDetourEnd = rvaDetourBeg + m_SectionHeaders[n].SizeOfRawData;
        }
    }

    //////////////////////////////////////////////////////// Get Import Table.
    //
    DWORD rvaImageDirectory = m_NtHeader.OptionalHeader
        .DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
    PIMAGE_IMPORT_DESCRIPTOR iidp
        = (PIMAGE_IMPORT_DESCRIPTOR)RvaToVa(rvaImageDirectory);
    PIMAGE_IMPORT_DESCRIPTOR oidp
        = (PIMAGE_IMPORT_DESCRIPTOR)RvaToVa(rvaOriginalImageDirectory);

    if (oidp == NULL) {
        oidp = iidp;
    }
    if (iidp == NULL || oidp == NULL) {
        SetLastError(ERROR_EXE_MARKED_INVALID);
        return FALSE;
    }

    DWORD nFiles = 0;
    for (; iidp[nFiles].OriginalFirstThunk != 0 || iidp[nFiles].FirstThunk != 0; nFiles++) {
    }

    CImageImportFile **ppLastFile = &m_pImportFiles;
    m_pImportFiles = NULL;

    for (n = 0; n < nFiles; n++, iidp++) {
        ULONG_PTR rvaName = iidp->Name;
        PCHAR pszName = (PCHAR)RvaToVa(rvaName);
        if (pszName == NULL) {
            SetLastError(ERROR_EXE_MARKED_INVALID);
            goto fail;
        }

        CImageImportFile *pImportFile = new NOTHROW CImageImportFile;
        if (pImportFile == NULL) {
            SetLastError(ERROR_OUTOFMEMORY);
            goto fail;
        }

        *ppLastFile = pImportFile;
        ppLastFile = &pImportFile->m_pNextFile;
        m_nImportFiles++;

        pImportFile->m_pszName = DuplicateString(pszName);
        if (pImportFile->m_pszName == NULL) {
            goto fail;
        }

        pImportFile->m_rvaOriginalFirstThunk = iidp->OriginalFirstThunk;
        pImportFile->m_rvaFirstThunk = iidp->FirstThunk;
        pImportFile->m_nForwarderChain = iidp->ForwarderChain;
        pImportFile->m_pImportNames = NULL;
        pImportFile->m_nImportNames = 0;
        pImportFile->m_fByway = FALSE;

        if ((ULONG)iidp->FirstThunk >= rvaDetourBeg &&
            (ULONG)iidp->FirstThunk < rvaDetourEnd) {

            pImportFile->m_pszOrig = NULL;
            pImportFile->m_fByway = TRUE;
            continue;
        }

        rvaName = oidp->Name;
        pszName = (PCHAR)RvaToVa(rvaName);
        if (pszName == NULL) {
            SetLastError(ERROR_EXE_MARKED_INVALID);
            goto fail;
        }
        pImportFile->m_pszOrig = DuplicateString(pszName);
        if (pImportFile->m_pszOrig == NULL) {
            goto fail;
        }

        DWORD rvaThunk = iidp->OriginalFirstThunk;
        if( !rvaThunk ) {
            rvaThunk = iidp->FirstThunk;
        }
        PIMAGE_THUNK_DATA pAddrThunk = (PIMAGE_THUNK_DATA)RvaToVa(rvaThunk);
        rvaThunk = oidp->OriginalFirstThunk;
        if( !rvaThunk ) {
            rvaThunk = oidp->FirstThunk;
        }
        PIMAGE_THUNK_DATA pLookThunk = (PIMAGE_THUNK_DATA)RvaToVa(rvaThunk);

        DWORD nNames = 0;
        if (pAddrThunk) {
            for (; pAddrThunk[nNames].u1.Ordinal; nNames++) {
            }
        }

        if (pAddrThunk && nNames) {
            pImportFile->m_nImportNames = nNames;
            pImportFile->m_pImportNames = new NOTHROW CImageImportName [nNames];
            if (pImportFile->m_pImportNames == NULL) {
                SetLastError(ERROR_OUTOFMEMORY);
                goto fail;
            }

            CImageImportName *pImportName = &pImportFile->m_pImportNames[0];

            for (DWORD f = 0; f < nNames; f++, pImportName++) {
                pImportName->m_nOrig = 0;
                pImportName->m_nOrdinal = 0;
                pImportName->m_nHint = 0;
                pImportName->m_pszName = NULL;
                pImportName->m_pszOrig = NULL;

                rvaName = pAddrThunk[f].u1.Ordinal;
                if (rvaName & IMAGE_ORDINAL_FLAG) {
                    pImportName->m_nOrig = (ULONG)IMAGE_ORDINAL(rvaName);
                    pImportName->m_nOrdinal = pImportName->m_nOrig;
                }
                else {
                    PIMAGE_IMPORT_BY_NAME pName
                        = (PIMAGE_IMPORT_BY_NAME)RvaToVa(rvaName);
                    if (pName) {
                        pImportName->m_nHint = pName->Hint;
                        pImportName->m_pszName = DuplicateString((PCHAR)pName->Name);
                        if (pImportName->m_pszName == NULL) {
                            goto fail;
                        }
                    }

                    rvaName = pLookThunk[f].u1.Ordinal;
                    if (rvaName & IMAGE_ORDINAL_FLAG) {
                        pImportName->m_nOrig = (ULONG)IMAGE_ORDINAL(rvaName);
                        pImportName->m_nOrdinal = (ULONG)IMAGE_ORDINAL(rvaName);
                    }
                    else {
                        pName = (PIMAGE_IMPORT_BY_NAME)RvaToVa(rvaName);
                        if (pName) {
                            pImportName->m_pszOrig
                                = DuplicateString((PCHAR)pName->Name);
                            if (pImportName->m_pszOrig == NULL) {
                                goto fail;
                            }
                        }
                    }
                }
            }
        }
        oidp++;
    }

    ////////////////////////////////////////////////////////// Parse Sections.
    //
    m_nExtraOffset = 0;
    for (n = 0; n < m_NtHeader.FileHeader.NumberOfSections; n++) {
        m_nExtraOffset = Max(m_SectionHeaders[n].PointerToRawData +
                             m_SectionHeaders[n].SizeOfRawData,
                             m_nExtraOffset);

        if (strcmp((PCHAR)m_SectionHeaders[n].Name, ".detour") == 0) {
            DETOUR_SECTION_HEADER dh;
            CopyMemory(&dh,
                       m_pMap + m_SectionHeaders[n].PointerToRawData,
                       sizeof(dh));

            if (dh.nDataOffset == 0) {
                dh.nDataOffset = dh.cbHeaderSize;
            }

            cbData = dh.cbDataSize - dh.nDataOffset;
            pbData = (m_pMap +
                      m_SectionHeaders[n].PointerToRawData +
                      dh.nDataOffset);

            m_nExtraOffset = Max(m_SectionHeaders[n].PointerToRawData +
                                 m_SectionHeaders[n].SizeOfRawData,
                                 m_nExtraOffset);

            m_NtHeader.FileHeader.NumberOfSections--;

            m_NtHeader.OptionalHeader
                .DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress
                = dh.nOriginalImportVirtualAddress;
            m_NtHeader.OptionalHeader
                .DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size
                = dh.nOriginalImportSize;

            m_NtHeader.OptionalHeader
                .DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].VirtualAddress
                = dh.nOriginalBoundImportVirtualAddress;
            m_NtHeader.OptionalHeader
                .DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].Size
                = dh.nOriginalBoundImportSize;

            m_NtHeader.OptionalHeader
                .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress
                = dh.nOriginalIatVirtualAddress;
            m_NtHeader.OptionalHeader
                .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size
                = dh.nOriginalIatSize;

            m_NtHeader.OptionalHeader.CheckSum = 0;
            m_NtHeader.OptionalHeader.SizeOfImage
                = dh.nOriginalSizeOfImage;

            m_fHadDetourSection = TRUE;
        }
    }

    m_pImageData = new NOTHROW CImageData(pbData, cbData);
    if (m_pImageData == NULL) {
        SetLastError(ERROR_OUTOFMEMORY);
    }
    return TRUE;

fail:
    return FALSE;
}

static inline BOOL strneq(_In_ LPCSTR pszOne, _In_ LPCSTR pszTwo)
{
    if (pszOne == pszTwo) {
        return FALSE;
    }
    if (!pszOne || !pszTwo) {
        return TRUE;
    }
    return (strcmp(pszOne, pszTwo) != 0);
}

BOOL CImage::CheckImportsNeeded(DWORD *pnTables, DWORD *pnThunks, DWORD *pnChars)
{
    DWORD nTables = 0;
    DWORD nThunks = 0;
    DWORD nChars = 0;
    BOOL fNeedDetourSection = FALSE;

    for (CImageImportFile *pImportFile = m_pImportFiles;
         pImportFile != NULL; pImportFile = pImportFile->m_pNextFile) {

        nChars += (int)strlen(pImportFile->m_pszName) + 1;
        nChars += nChars & 1;

        if (pImportFile->m_fByway) {
            fNeedDetourSection = TRUE;
            nThunks++;
        }
        else {
            if (!fNeedDetourSection &&
                strneq(pImportFile->m_pszName, pImportFile->m_pszOrig)) {

                fNeedDetourSection = TRUE;
            }
            for (DWORD n = 0; n < pImportFile->m_nImportNames; n++) {
                CImageImportName *pImportName = &pImportFile->m_pImportNames[n];

                if (!fNeedDetourSection &&
                    strneq(pImportName->m_pszName, pImportName->m_pszOrig)) {

                    fNeedDetourSection = TRUE;
                }

                if (pImportName->m_pszName) {
                    nChars += sizeof(WORD);             // Hint
                    nChars += (int)strlen(pImportName->m_pszName) + 1;
                    nChars += nChars & 1;
                }
                nThunks++;
            }
        }
        nThunks++;
        nTables++;
    }
    nTables++;

    *pnTables = nTables;
    *pnThunks = nThunks;
    *pnChars = nChars;

    return fNeedDetourSection;
}

//////////////////////////////////////////////////////////////////////////////
//
CImageImportFile * CImage::NewByway(_In_ LPCSTR pszName)
{
    CImageImportFile *pImportFile = new NOTHROW CImageImportFile;
    if (pImportFile == NULL) {
        SetLastError(ERROR_OUTOFMEMORY);
        goto fail;
    }

    pImportFile->m_pNextFile = NULL;
    pImportFile->m_fByway = TRUE;

    pImportFile->m_pszName = DuplicateString(pszName);
    if (pImportFile->m_pszName == NULL) {
        goto fail;
    }

    pImportFile->m_rvaOriginalFirstThunk = 0;
    pImportFile->m_rvaFirstThunk = 0;
    pImportFile->m_nForwarderChain = (UINT)0;
    pImportFile->m_pImportNames = NULL;
    pImportFile->m_nImportNames = 0;

    m_nImportFiles++;
    return pImportFile;

fail:
    if (pImportFile) {
        delete pImportFile;
        pImportFile = NULL;
    }
    return NULL;
}

BOOL CImage::EditImports(PVOID pContext,
                         PF_DETOUR_BINARY_BYWAY_CALLBACK pfBywayCallback,
                         PF_DETOUR_BINARY_FILE_CALLBACK pfFileCallback,
                         PF_DETOUR_BINARY_SYMBOL_CALLBACK pfSymbolCallback,
                         PF_DETOUR_BINARY_COMMIT_CALLBACK pfCommitCallback)
{
    CImageImportFile *pImportFile = NULL;
    CImageImportFile **ppLastFile = &m_pImportFiles;

    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);

    while ((pImportFile = *ppLastFile) != NULL) {

        if (pfBywayCallback != NULL) {
            LPCSTR pszFile = NULL;
            if (!(*pfBywayCallback)(pContext, NULL, &pszFile)) {
                goto fail;
            }

            if (pszFile != NULL) {
                // Insert a new Byway.
                CImageImportFile *pByway = NewByway(pszFile);
                if (pByway == NULL) {
                    return FALSE;
                }

                pByway->m_pNextFile = pImportFile;
                *ppLastFile = pByway;
                ppLastFile = &pByway->m_pNextFile;
                continue;                               // Retry after Byway.
            }
        }

        if (pImportFile->m_fByway) {
            if (pfBywayCallback != NULL) {
                LPCSTR pszFile = NULL;

                if (!(*pfBywayCallback)(pContext, pImportFile->m_pszName, &pszFile)) {
                    goto fail;
                }

                if (pszFile != NULL) {
                    // Replace? Byway
                    if (pszFile != pImportFile->m_pszName) {
                        LPCSTR pszLast = pImportFile->m_pszName;
                        pImportFile->m_pszName = DuplicateString(pszFile);
                        ReleaseString(pszLast);

                        if (pImportFile->m_pszName == NULL) {
                            goto fail;
                        }
                    }
                }
                else {                                  // Delete Byway
                    *ppLastFile = pImportFile->m_pNextFile;
                    pImportFile->m_pNextFile = NULL;
                    delete pImportFile;
                    m_nImportFiles--;
                    continue;                           // Retry after delete.
                }
            }
        }
        else {
            if (pfFileCallback != NULL) {
                LPCSTR pszFile = NULL;

                if (!(*pfFileCallback)(pContext,
                                       pImportFile->m_pszOrig,
                                       pImportFile->m_pszName,
                                       &pszFile)) {
                    goto fail;
                }

                if (pszFile != NULL) {
                    if (pszFile != pImportFile->m_pszName) {
                        LPCSTR pszLast = pImportFile->m_pszName;
                        pImportFile->m_pszName = DuplicateString(pszFile);
                        ReleaseString(pszLast);

                        if (pImportFile->m_pszName == NULL) {
                            goto fail;
                        }
                    }
                }
            }

            if (pfSymbolCallback != NULL) {
                for (DWORD n = 0; n < pImportFile->m_nImportNames; n++) {
                    CImageImportName *pImportName = &pImportFile->m_pImportNames[n];

                    LPCSTR pszName = NULL;
                    ULONG nOrdinal = 0;
                    if (!(*pfSymbolCallback)(pContext,
                                             pImportName->m_nOrig,
                                             pImportName->m_nOrdinal,
                                             &nOrdinal,
                                             pImportName->m_pszOrig,
                                             pImportName->m_pszName,
                                             &pszName)) {
                        goto fail;
                    }

                    if (pszName != NULL) {
                        if (pszName != pImportName->m_pszName) {
                            pImportName->m_nOrdinal = 0;

                            LPCSTR pszLast = pImportName->m_pszName;
                            pImportName->m_pszName = DuplicateString(pszName);
                            ReleaseString(pszLast);

                            if (pImportName->m_pszName == NULL) {
                                goto fail;
                            }
                        }
                    }
                    else if (nOrdinal != 0) {
                        pImportName->m_nOrdinal = nOrdinal;

                        if (pImportName->m_pszName != NULL) {
                            delete[] pImportName->m_pszName;
                            pImportName->m_pszName = NULL;
                        }
                    }
                }
            }
        }

        ppLastFile = &pImportFile->m_pNextFile;
        pImportFile = pImportFile->m_pNextFile;
    }

    for (;;) {
        if (pfBywayCallback != NULL) {
            LPCSTR pszFile = NULL;
            if (!(*pfBywayCallback)(pContext, NULL, &pszFile)) {
                goto fail;
            }
            if (pszFile != NULL) {
                // Insert a new Byway.
                CImageImportFile *pByway = NewByway(pszFile);
                if (pByway == NULL) {
                    return FALSE;
                }

                pByway->m_pNextFile = pImportFile;
                *ppLastFile = pByway;
                ppLastFile = &pByway->m_pNextFile;
                continue;                               // Retry after Byway.
            }
        }
        break;
    }

    if (pfCommitCallback != NULL) {
        if (!(*pfCommitCallback)(pContext)) {
            goto fail;
        }
    }

    SetLastError(NO_ERROR);
    return TRUE;

  fail:
    return FALSE;
}

BOOL CImage::Write(HANDLE hFile)
{
    DWORD cbDone;

    if (hFile == INVALID_HANDLE_VALUE) {
        SetLastError(ERROR_INVALID_HANDLE);
        return FALSE;
    }

    m_nNextFileAddr = 0;
    m_nNextVirtAddr = 0;

    DWORD nTables = 0;
    DWORD nThunks = 0;
    DWORD nChars = 0;
    BOOL fNeedDetourSection = CheckImportsNeeded(&nTables, &nThunks, &nChars);

    //////////////////////////////////////////////////////////// Copy Headers.
    //
    if (SetFilePointer(hFile, 0, NULL, FILE_BEGIN) == ~0u) {
        return FALSE;
    }
    if (!CopyFileData(hFile, 0, m_NtHeader.OptionalHeader.SizeOfHeaders)) {
        return FALSE;
    }

    if (fNeedDetourSection || !m_pImageData->IsEmpty()) {
        // Replace the file's DOS header with our own.
        m_nPeOffset = sizeof(m_DosHeader) + sizeof(s_rbDosCode);
        m_nSectionsOffset = m_nPeOffset
            + sizeof(m_NtHeader.Signature)
            + sizeof(m_NtHeader.FileHeader)
            + m_NtHeader.FileHeader.SizeOfOptionalHeader;
        m_DosHeader.e_lfanew = m_nPeOffset;

        if (SetFilePointer(hFile, 0, NULL, FILE_BEGIN) == ~0u) {
            return FALSE;
        }
        if (!WriteFile(hFile, &m_DosHeader, sizeof(m_DosHeader), &cbDone)) {
            return FALSE;
        }
        if (!WriteFile(hFile, &s_rbDosCode, sizeof(s_rbDosCode), &cbDone)) {
            return FALSE;
        }
    }
    else {
        // Restore the file's original DOS header.
        if (m_nPrePE != 0) {
            m_nPeOffset = m_cbPrePE;
            m_nSectionsOffset = m_nPeOffset
                + sizeof(m_NtHeader.Signature)
                + sizeof(m_NtHeader.FileHeader)
                + m_NtHeader.FileHeader.SizeOfOptionalHeader;
            m_DosHeader.e_lfanew = m_nPeOffset;


            if (SetFilePointer(hFile, 0, NULL, FILE_BEGIN) == ~0u) {
                return FALSE;
            }
            if (!CopyFileData(hFile, m_nPrePE, m_cbPrePE)) {
                return FALSE;
            }
        }
    }

    m_nNextFileAddr = m_NtHeader.OptionalHeader.SizeOfHeaders;
    m_nNextVirtAddr = 0;
    if (!AlignFileData(hFile)) {
        return FALSE;
    }

    /////////////////////////////////////////////////////////// Copy Sections.
    //
    DWORD n = 0;
    for (; n < m_NtHeader.FileHeader.NumberOfSections; n++) {
        if (m_SectionHeaders[n].SizeOfRawData) {
            if (SetFilePointer(hFile,
                               m_SectionHeaders[n].PointerToRawData,
                               NULL, FILE_BEGIN) == ~0u) {
                return FALSE;
            }
            if (!CopyFileData(hFile,
                              m_SectionHeaders[n].PointerToRawData,
                              m_SectionHeaders[n].SizeOfRawData)) {
                return FALSE;
            }
        }
        m_nNextFileAddr = Max(m_SectionHeaders[n].PointerToRawData +
                              m_SectionHeaders[n].SizeOfRawData,
                              m_nNextFileAddr);
        // Old images have VirtualSize == 0 as a matter of course, e.g. NT 3.1.
        // In which case, use SizeOfRawData instead.
        m_nNextVirtAddr = Max(m_SectionHeaders[n].VirtualAddress +
                              (m_SectionHeaders[n].Misc.VirtualSize
                               ? m_SectionHeaders[n].Misc.VirtualSize
                               : SectionAlign(m_SectionHeaders[n].SizeOfRawData)),
                              m_nNextVirtAddr);

        m_nExtraOffset = Max(m_nNextFileAddr, m_nExtraOffset);

        if (!AlignFileData(hFile)) {
            return FALSE;
        }
    }

    if (fNeedDetourSection || !m_pImageData->IsEmpty()) {

        if (m_NtHeader.FileHeader.NumberOfSections >= ARRAYSIZE(m_SectionHeaders)) {
            SetLastError(ERROR_EXE_MARKED_INVALID);
            return FALSE;
        }

        ////////////////////////////////////////////// Insert .detour Section.
        //
        DWORD nSection = m_NtHeader.FileHeader.NumberOfSections++;
        DETOUR_SECTION_HEADER dh;

        ZeroMemory(&dh, sizeof(dh));
        ZeroMemory(&m_SectionHeaders[nSection], sizeof(m_SectionHeaders[nSection]));

        dh.cbHeaderSize = sizeof(DETOUR_SECTION_HEADER);
        dh.nSignature = DETOUR_SECTION_HEADER_SIGNATURE;

        dh.nOriginalImportVirtualAddress = m_NtHeader.OptionalHeader
            .DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
        dh.nOriginalImportSize = m_NtHeader.OptionalHeader
            .DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size;

        dh.nOriginalBoundImportVirtualAddress
            = m_NtHeader.OptionalHeader
            .DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].VirtualAddress;
        dh.nOriginalBoundImportSize = m_NtHeader.OptionalHeader
            .DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].Size;

        dh.nOriginalIatVirtualAddress = m_NtHeader.OptionalHeader
            .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress;
        dh.nOriginalIatSize = m_NtHeader.OptionalHeader
            .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size;

        dh.nOriginalSizeOfImage = m_NtHeader.OptionalHeader.SizeOfImage;

        DWORD clrAddr = m_NtHeader.OptionalHeader
            .DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress;
        DWORD clrSize = m_NtHeader.OptionalHeader
            .DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].Size;
        if (clrAddr && clrSize) {
            PDETOUR_CLR_HEADER pHdr = (PDETOUR_CLR_HEADER)RvaToVa(clrAddr);
            if (pHdr != NULL) {
                DETOUR_CLR_HEADER hdr;
                hdr = *pHdr;

                dh.nOriginalClrFlags = hdr.Flags;
            }
        }

        HRESULT hrRet = StringCchCopyA((PCHAR)m_SectionHeaders[nSection].Name, IMAGE_SIZEOF_SHORT_NAME , ".detour");
        if (FAILED(hrRet))
            return FALSE;

        m_SectionHeaders[nSection].Characteristics
            = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE;

        m_nOutputVirtAddr = m_nNextVirtAddr;
        m_nOutputVirtSize = 0;
        m_nOutputFileAddr = m_nNextFileAddr;

        dh.nDataOffset = 0;                     // pbData
        dh.cbDataSize = m_pImageData->m_cbData;
        dh.cbPrePE = m_cbPrePE;

        //////////////////////////////////////////////////////////////////////////
        //

        DWORD rvaImportTable = 0;
        DWORD rvaLookupTable = 0;
        DWORD rvaBoundTable = 0;
        DWORD rvaNameTable = 0;
        DWORD nImportTableSize = nTables * sizeof(IMAGE_IMPORT_DESCRIPTOR);

        if (!SizeOutputBuffer(QuadAlign(sizeof(dh))
                              + m_cbPrePE
                              + QuadAlign(m_pImageData->m_cbData)
                              + QuadAlign(sizeof(IMAGE_THUNK_DATA) * nThunks)
                              + QuadAlign(sizeof(IMAGE_THUNK_DATA) * nThunks)
                              + QuadAlign(nChars)
                              + QuadAlign(nImportTableSize))) {
            return FALSE;
        }

        DWORD vaHead = 0;
        PBYTE pbHead = NULL;
        DWORD vaPrePE = 0;
        PBYTE pbPrePE = NULL;
        DWORD vaData = 0;
        PBYTE pbData = NULL;

        if ((pbHead = AllocateOutput(sizeof(dh), &vaHead)) == NULL) {
            return FALSE;
        }

        if ((pbPrePE = AllocateOutput(m_cbPrePE, &vaPrePE)) == NULL) {
            return FALSE;
        }

        CImageThunks lookupTable(this, nThunks, &rvaLookupTable);
        CImageThunks boundTable(this, nThunks, &rvaBoundTable);
        CImageChars nameTable(this, nChars, &rvaNameTable);

        if ((pbData = AllocateOutput(m_pImageData->m_cbData, &vaData)) == NULL) {
            return FALSE;
        }

        dh.nDataOffset = vaData - vaHead;
        dh.cbDataSize = dh.nDataOffset + m_pImageData->m_cbData;
        CopyMemory(pbHead, &dh, sizeof(dh));
        CopyMemory(pbPrePE, m_pMap + m_nPrePE, m_cbPrePE);
        CopyMemory(pbData, m_pImageData->m_pbData, m_pImageData->m_cbData);

        PIMAGE_IMPORT_DESCRIPTOR piidDst = (PIMAGE_IMPORT_DESCRIPTOR)
            AllocateOutput(nImportTableSize, &rvaImportTable);
        if (piidDst == NULL) {
            return FALSE;
        }

        //////////////////////////////////////////////// Step Through Imports.
        //
        for (CImageImportFile *pImportFile = m_pImportFiles;
             pImportFile != NULL; pImportFile = pImportFile->m_pNextFile) {

            ZeroMemory(piidDst, sizeof(*piidDst));
            nameTable.Allocate(pImportFile->m_pszName, (DWORD *)&piidDst->Name);
            piidDst->TimeDateStamp = 0;
            piidDst->ForwarderChain = pImportFile->m_nForwarderChain;

            if (pImportFile->m_fByway) {
                ULONG rvaIgnored;

                lookupTable.Allocate(IMAGE_ORDINAL_FLAG+1,
                                     (DWORD *)&piidDst->OriginalFirstThunk);
                boundTable.Allocate(IMAGE_ORDINAL_FLAG+1,
                                    (DWORD *)&piidDst->FirstThunk);

                lookupTable.Allocate(0, &rvaIgnored);
                boundTable.Allocate(0, &rvaIgnored);
            }
            else {
                ULONG rvaIgnored;

                piidDst->FirstThunk = (ULONG)pImportFile->m_rvaFirstThunk;
                lookupTable.Current((DWORD *)&piidDst->OriginalFirstThunk);

                for (n = 0; n < pImportFile->m_nImportNames; n++) {
                    CImageImportName *pImportName = &pImportFile->m_pImportNames[n];

                    if (pImportName->m_pszName) {
                        ULONG nDstName = 0;

                        nameTable.Allocate(pImportName->m_pszName,
                                           pImportName->m_nHint,
                                           &nDstName);
                        lookupTable.Allocate(nDstName, &rvaIgnored);
                    }
                    else {
                        lookupTable.Allocate(IMAGE_ORDINAL_FLAG + pImportName->m_nOrdinal,
                                             &rvaIgnored);
                    }
                }
                lookupTable.Allocate(0, &rvaIgnored);
            }
            piidDst++;
        }
        ZeroMemory(piidDst, sizeof(*piidDst));

        //////////////////////////////////////////////////////////////////////////
        //
        m_nNextVirtAddr += m_nOutputVirtSize;
        m_nNextFileAddr += FileAlign(m_nOutputVirtSize);

        if (!AlignFileData(hFile)) {
            return FALSE;
        }

        //////////////////////////////////////////////////////////////////////////
        //
        m_SectionHeaders[nSection].VirtualAddress = m_nOutputVirtAddr;
        m_SectionHeaders[nSection].Misc.VirtualSize = m_nOutputVirtSize;
        m_SectionHeaders[nSection].PointerToRawData = m_nOutputFileAddr;
        m_SectionHeaders[nSection].SizeOfRawData = FileAlign(m_nOutputVirtSize);

        m_NtHeader.OptionalHeader
            .DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress
            = rvaImportTable;
        m_NtHeader.OptionalHeader
            .DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size
            = nImportTableSize;

        m_NtHeader.OptionalHeader
            .DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].VirtualAddress = 0;
        m_NtHeader.OptionalHeader
            .DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].Size = 0;

        //////////////////////////////////////////////////////////////////////////
        //
        if (SetFilePointer(hFile, m_SectionHeaders[nSection].PointerToRawData,
                           NULL, FILE_BEGIN) == ~0u) {
            return FALSE;
        }
        if (!WriteFile(hFile, m_pbOutputBuffer, m_SectionHeaders[nSection].SizeOfRawData,
                       &cbDone)) {
            return FALSE;
        }
    }

    ///////////////////////////////////////////////////// Adjust Extra Data.
    //
    LONG nExtraAdjust = m_nNextFileAddr - m_nExtraOffset;
    for (n = 0; n < m_NtHeader.FileHeader.NumberOfSections; n++) {
        if (m_SectionHeaders[n].PointerToRawData > m_nExtraOffset) {
            m_SectionHeaders[n].PointerToRawData += nExtraAdjust;
        }
        if (m_SectionHeaders[n].PointerToRelocations > m_nExtraOffset) {
            m_SectionHeaders[n].PointerToRelocations += nExtraAdjust;
        }
        if (m_SectionHeaders[n].PointerToLinenumbers > m_nExtraOffset) {
            m_SectionHeaders[n].PointerToLinenumbers += nExtraAdjust;
        }
    }
    if (m_NtHeader.FileHeader.PointerToSymbolTable > m_nExtraOffset) {
        m_NtHeader.FileHeader.PointerToSymbolTable += nExtraAdjust;
    }

    m_NtHeader.OptionalHeader.CheckSum = 0;
    m_NtHeader.OptionalHeader.SizeOfImage = m_nNextVirtAddr;

    ////////////////////////////////////////////////// Adjust Debug Directory.
    //
    DWORD debugAddr = m_NtHeader.OptionalHeader
        .DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress;
    DWORD debugSize = m_NtHeader.OptionalHeader
        .DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size;
    if (debugAddr && debugSize) {
        DWORD nFileOffset = RvaToFileOffset(debugAddr);
        if (SetFilePointer(hFile, nFileOffset, NULL, FILE_BEGIN) == ~0u) {
            return FALSE;
        }

        PIMAGE_DEBUG_DIRECTORY pDir = (PIMAGE_DEBUG_DIRECTORY)RvaToVa(debugAddr);
        if (pDir == NULL) {
            return FALSE;
        }

        DWORD nEntries = debugSize / sizeof(*pDir);
        for (n = 0; n < nEntries; n++) {
            IMAGE_DEBUG_DIRECTORY dir = pDir[n];

            if (dir.PointerToRawData > m_nExtraOffset) {
                dir.PointerToRawData += nExtraAdjust;
            }
            if (!WriteFile(hFile, &dir, sizeof(dir), &cbDone)) {
                return FALSE;
            }
        }
    }

    /////////////////////////////////////////////////////// Adjust CLR Header.
    //
    DWORD clrAddr = m_NtHeader.OptionalHeader
        .DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress;
    DWORD clrSize = m_NtHeader.OptionalHeader
        .DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].Size;
    if (clrAddr && clrSize && fNeedDetourSection) {
        DWORD nFileOffset = RvaToFileOffset(clrAddr);
        if (SetFilePointer(hFile, nFileOffset, NULL, FILE_BEGIN) == ~0u) {
            return FALSE;
        }

        PDETOUR_CLR_HEADER pHdr = (PDETOUR_CLR_HEADER)RvaToVa(clrAddr);
        if (pHdr == NULL) {
            return FALSE;
        }

        DETOUR_CLR_HEADER hdr;
        hdr = *pHdr;
        hdr.Flags &= 0xfffffffe;    // Clear the IL_ONLY flag.

        if (!WriteFile(hFile, &hdr, sizeof(hdr), &cbDone)) {
            return FALSE;
        }
    }

    ///////////////////////////////////////////////// Copy Left-over Data.
    //
    if (m_nFileSize > m_nExtraOffset) {
        if (SetFilePointer(hFile, m_nNextFileAddr, NULL, FILE_BEGIN) == ~0u) {
            return FALSE;
        }
        if (!CopyFileData(hFile, m_nExtraOffset, m_nFileSize - m_nExtraOffset)) {
            return FALSE;
        }
    }


    //////////////////////////////////////////////////// Finalize Headers.
    //

    if (SetFilePointer(hFile, m_nPeOffset, NULL, FILE_BEGIN) == ~0u) {
        return FALSE;
    }
    if (!WriteFile(hFile, &m_NtHeader, sizeof(m_NtHeader), &cbDone)) {
        return FALSE;
    }

    if (SetFilePointer(hFile, m_nSectionsOffset, NULL, FILE_BEGIN) == ~0u) {
        return FALSE;
    }
    if (!WriteFile(hFile, &m_SectionHeaders,
                   sizeof(m_SectionHeaders[0])
                   * m_NtHeader.FileHeader.NumberOfSections,
                   &cbDone)) {
        return FALSE;
    }

    m_cbPostPE = SetFilePointer(hFile, 0, NULL, FILE_CURRENT);
    if (m_cbPostPE == ~0u) {
        return FALSE;
    }
    m_cbPostPE = m_NtHeader.OptionalHeader.SizeOfHeaders - m_cbPostPE;

    return TRUE;
}

};                                                      // namespace Detour

//////////////////////////////////////////////////////////////////////////////
//
PDETOUR_BINARY WINAPI DetourBinaryOpen(_In_ HANDLE hFile)
{
    Detour::CImage *pImage = new NOTHROW
        Detour::CImage;
    if (pImage == NULL) {
        SetLastError(ERROR_OUTOFMEMORY);
        return FALSE;
    }

    if (!pImage->Read(hFile)) {
        delete pImage;
        return FALSE;
    }

    return (PDETOUR_BINARY)pImage;
}

BOOL WINAPI DetourBinaryWrite(_In_ PDETOUR_BINARY pdi,
                              _In_ HANDLE hFile)
{
    Detour::CImage *pImage = Detour::CImage::IsValid(pdi);
    if (pImage == NULL) {
        return FALSE;
    }

    return pImage->Write(hFile);
}

_Writable_bytes_(*pcbData)
_Readable_bytes_(*pcbData)
_Success_(return != NULL)
PVOID WINAPI DetourBinaryEnumeratePayloads(_In_ PDETOUR_BINARY pBinary,
                                           _Out_opt_ GUID *pGuid,
                                           _Out_ DWORD *pcbData,
                                           _Inout_ DWORD *pnIterator)
{
    Detour::CImage *pImage = Detour::CImage::IsValid(pBinary);
    if (pImage == NULL) {
        return FALSE;
    }

    return pImage->DataEnum(pGuid, pcbData, pnIterator);
}

_Writable_bytes_(*pcbData)
_Readable_bytes_(*pcbData)
_Success_(return != NULL)
PVOID WINAPI DetourBinaryFindPayload(_In_ PDETOUR_BINARY pBinary,
                                     _In_ REFGUID rguid,
                                     _Out_ DWORD *pcbData)
{
    Detour::CImage *pImage = Detour::CImage::IsValid(pBinary);
    if (pImage == NULL) {
        return FALSE;
    }

    return pImage->DataFind(rguid, pcbData);
}

PVOID WINAPI DetourBinarySetPayload(_In_ PDETOUR_BINARY pBinary,
                                    _In_ REFGUID rguid,
                                    _In_reads_opt_(cbData) PVOID pvData,
                                    _In_ DWORD cbData)
{
    Detour::CImage *pImage = Detour::CImage::IsValid(pBinary);
    if (pImage == NULL) {
        return NULL;
    }

    return pImage->DataSet(rguid, (PBYTE)pvData, cbData);
}

BOOL WINAPI DetourBinaryDeletePayload(_In_ PDETOUR_BINARY pBinary,
                                      _In_ REFGUID rguid)
{
    Detour::CImage *pImage = Detour::CImage::IsValid(pBinary);
    if (pImage == NULL) {
        return FALSE;
    }

    return pImage->DataDelete(rguid);
}

BOOL WINAPI DetourBinaryPurgePayloads(_In_ PDETOUR_BINARY pBinary)
{
    Detour::CImage *pImage = Detour::CImage::IsValid(pBinary);
    if (pImage == NULL) {
        return FALSE;
    }

    return pImage->DataPurge();
}

//////////////////////////////////////////////////////////////////////////////
//
static BOOL CALLBACK ResetBywayCallback(_In_opt_ PVOID pContext,
                                        _In_opt_ LPCSTR pszFile,
                                        _Outptr_result_maybenull_ LPCSTR *ppszOutFile)
{
    UNREFERENCED_PARAMETER(pContext);
    UNREFERENCED_PARAMETER(pszFile);

    *ppszOutFile = NULL;
    return TRUE;
}

static BOOL CALLBACK ResetFileCallback(_In_opt_ PVOID pContext,
                                       _In_ LPCSTR pszOrigFile,
                                       _In_ LPCSTR pszFile,
                                       _Outptr_result_maybenull_ LPCSTR *ppszOutFile)
{
    UNREFERENCED_PARAMETER(pContext);
    UNREFERENCED_PARAMETER(pszFile);

    *ppszOutFile = pszOrigFile;
    return TRUE;
}

static BOOL CALLBACK ResetSymbolCallback(_In_opt_ PVOID pContext,
                                         _In_ ULONG nOrigOrdinal,
                                         _In_ ULONG nOrdinal,
                                         _Out_ ULONG *pnOutOrdinal,
                                         _In_opt_ LPCSTR pszOrigSymbol,
                                         _In_opt_ LPCSTR pszSymbol,
                                         _Outptr_result_maybenull_ LPCSTR *ppszOutSymbol)
{
    UNREFERENCED_PARAMETER(pContext);
    UNREFERENCED_PARAMETER(nOrdinal);
    UNREFERENCED_PARAMETER(pszSymbol);

    *pnOutOrdinal = nOrigOrdinal;
    *ppszOutSymbol = pszOrigSymbol;
    return TRUE;
}

BOOL WINAPI DetourBinaryResetImports(_In_ PDETOUR_BINARY pBinary)
{
    Detour::CImage *pImage = Detour::CImage::IsValid(pBinary);
    if (pImage == NULL) {
        return FALSE;
    }

    return pImage->EditImports(NULL,
                               ResetBywayCallback,
                               ResetFileCallback,
                               ResetSymbolCallback,
                               NULL);
}

//////////////////////////////////////////////////////////////////////////////
//
BOOL WINAPI DetourBinaryEditImports(_In_ PDETOUR_BINARY pBinary,
                                    _In_opt_ PVOID pContext,
                                    _In_opt_ PF_DETOUR_BINARY_BYWAY_CALLBACK pfByway,
                                    _In_opt_ PF_DETOUR_BINARY_FILE_CALLBACK pfFile,
                                    _In_opt_ PF_DETOUR_BINARY_SYMBOL_CALLBACK pfSymbol,
                                    _In_opt_ PF_DETOUR_BINARY_COMMIT_CALLBACK pfCommit)
{
    Detour::CImage *pImage = Detour::CImage::IsValid(pBinary);
    if (pImage == NULL) {
        return FALSE;
    }

    return pImage->EditImports(pContext,
                               pfByway,
                               pfFile,
                               pfSymbol,
                               pfCommit);
}

BOOL WINAPI DetourBinaryClose(_In_ PDETOUR_BINARY pBinary)
{
    Detour::CImage *pImage = Detour::CImage::IsValid(pBinary);
    if (pImage == NULL) {
        return FALSE;
    }

    BOOL bSuccess = pImage->Close();
    delete pImage;
    pImage = NULL;

    return bSuccess;
}

//
///////////////////////////////////////////////////////////////// End of File.
