#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "rsym.h"
#include "rsym64.h"
#include "dwarf2.h"

char DoPrint = 0;
ULONG g_ehframep;

#define DPRINT if(DoPrint) printf

struct {char *name; char regnt;} regs[] =
{ {"rax", REG_RAX}, {"rdx", REG_RDX}, {"rcx", REG_RCX}, {"rbx", REG_RBX},
  {"rsi", REG_RSI}, {"rdi", REG_RDI}, {"rbp", REG_RBP}, {"rsp", REG_RSP},
  {"r8",  REG_R8},  {"r9",  REG_R9},  {"r10", REG_R10}, {"r11", REG_R11},
  {"r12", REG_R12}, {"r13", REG_R13}, {"r14", REG_R14}, {"r15", REG_R15},
  {"xmm0", REG_XMM0}, {"xmm1", REG_XMM1}, {"xmm2", REG_XMM2}, {"xmm3", REG_XMM3},
  {"xmm4", REG_XMM4}, {"xmm5", REG_XMM5}, {"xmm6", REG_XMM6}, {"xmm7", REG_XMM7},
  {"xmm8", REG_XMM8}, {"xmm9", REG_XMM9}, {"xmm10",REG_XMM10},{"xmm11",REG_XMM11},
  {"xmm12",REG_XMM12},{"xmm13",REG_XMM13},{"xmm14",REG_XMM14},{"xmm15",REG_XMM15},
//      "st0", "st1", "st2", "st3",
//      "st4", "st5", "st6", "st7",
//      "mm0", "mm1", "mm2", "mm3",
//      "mm4", "mm5", "mm6", "mm7"
};

/** Functions for DWARF2 ******************************************************/

unsigned long
DwDecodeUleb128(unsigned long *pResult, char *pc)
{
	unsigned long ulResult = 0;
	unsigned long ulShift = 0;
	unsigned char current;
	unsigned long ulSize = 0;

	do
	{
		current = pc[ulSize];
		ulSize++;
		ulResult |= (current & 0x7f) << ulShift;
		ulShift += 7;
	}
	while (current & 0x80);

    *pResult = ulResult;
	return ulSize;
}

unsigned long
DwDecodeSleb128(long *pResult, char *pc)
{
	long lResult = 0;
	unsigned long ulShift = 0;
	unsigned char current;
	unsigned long ulSize = 0;

	do
	{
		current = pc[ulSize];
		ulSize++;
		lResult |= (current & 0x7f) << ulShift;
		ulShift += 7;
	}
	while (current & 0x80);

	if (current & 0x40)
		lResult |= - (1 << (ulShift));

    *pResult = lResult;

	return ulSize;
}

unsigned long
DwDecodeCie(PDW2CIE Cie, char *pc)
{
    Cie->Length = *(ULONG*)pc;
    Cie->Next = pc + 4 + Cie->Length;
    Cie->CieId = *(ULONG*)(pc + 4);
    Cie->Version = pc[8];
    Cie->AugString = pc + 9;
    Cie->AugStringLength = strlen(Cie->AugString);
    pc = Cie->AugString + Cie->AugStringLength + 1;
    pc += DwDecodeUleb128(&Cie->CodeAlign, pc);
    pc += DwDecodeSleb128(&Cie->DataAlign, pc);
    pc += DwDecodeUleb128(&Cie->ReturnAddressRegister, pc);
    pc += DwDecodeUleb128(&Cie->AugLength, pc);
    Cie->AugData = pc;
    pc += Cie->AugLength;
    Cie->Instructions = pc;

    return Cie->Length + 4;
}

unsigned long
DwDecodeFde(PDW2FDE Fde, char *pc)
{
    Fde->Length = *(ULONG*)pc;
    Fde->Next = pc + 4 + Fde->Length;
    Fde->CiePointer = pc + 4 - *(ULONG*)(pc + 4);
    Fde->PcBegin = *(ULONG*)(pc + 8);
    Fde->PcRange = *(ULONG*)(pc + 12);
    pc += 16;
    pc += DwDecodeUleb128(&Fde->AugLength, pc);
    Fde->AugData = pc;
    Fde->Instructions = Fde->AugData + Fde->AugLength;

    return Fde->Length + 4;
}

unsigned long
DwExecIntruction(PDW2CFSTATE State, char *pc)
{
    unsigned char Code;
    unsigned long Length;
    unsigned long PrevFramePtr = State->FramePtr;

    State->Scope = 0;
    State->IsUwop = 0;
    State->Code = Code = *pc;
    Length = 1;
    if ((Code & 0xc0) == DW_CFA_advance_loc)
    {
        State->Code = DW_CFA_advance_loc;
        State->Location += Code & 0x3f;
    }
    else if ((Code & 0xc0) == DW_CFA_offset)
    {
        State->Code = DW_CFA_offset;
        State->Reg = Code & 0x3f;
        Length += DwDecodeUleb128((unsigned long*)&State->Offset, pc + 1);
        State->Offset *= 8; // fixme data alignment
        State->IsUwop = 1;
    }
    else if ((Code & 0xc0) == DW_CFA_restore)
    {
        State->Code = DW_CFA_restore;
        State->Reg = Code & 0x3f;
    }
    else switch (Code)
    {
        case DW_CFA_nop:
            break;
        case DW_CFA_set_loc:
            Length = 9; // address
            State->Location = *(DWORD*)(pc + 1);
            break;
        case DW_CFA_advance_loc1:
            Length = 2;
            State->Location += pc[1];
            break;
        case DW_CFA_advance_loc2:
            Length = 3;
//            printf("Found a DW_CFA_advance_loc2 : 0x%lx ->", *(WORD*)(pc + 1));
            State->Location += *(WORD*)(pc + 1);
//            printf(" 0x%lx\n", State->Location);
            break;
        case DW_CFA_advance_loc4:
            Length = 5;
//            printf("Found a DW_CFA_advance_loc4 : 0x%lx ->", *(DWORD*)(pc + 1));
            State->Location += *(DWORD*)(pc + 1);
//            printf(" 0x%lx\n", State->Location);
            break;
        case DW_CFA_offset_extended:
            Length += DwDecodeUleb128(&State->Reg, pc + Length);
            Length += DwDecodeUleb128((unsigned long*)&State->Offset, pc + Length);
            State->IsUwop = 1;
            break;
        case DW_CFA_offset_extended_sf:
            Length += DwDecodeUleb128(&State->Reg, pc + Length);
            Length += DwDecodeSleb128(&State->Offset, pc + Length);
            State->IsUwop = 1;
            break;
        case DW_CFA_restore_extended:
            Length += DwDecodeUleb128(&State->Reg, pc + Length);
            break;
        case DW_CFA_undefined:
            Length += DwDecodeUleb128(&State->Reg, pc + Length);
            break;
        case DW_CFA_same_value:
            Length += DwDecodeUleb128(&State->Reg, pc + Length);
            break;
        case DW_CFA_register:
            Length += DwDecodeUleb128(&State->Reg, pc + Length);
            Length += DwDecodeUleb128(&State->Reg2, pc + Length);
            break;
        case DW_CFA_remember_state:
            break;
        case DW_CFA_restore_state:
            break;
        case DW_CFA_def_cfa:
            Length += DwDecodeUleb128(&State->Reg, pc + Length);
            Length += DwDecodeUleb128((unsigned long*)&State->FramePtr, pc + Length);
            State->IsUwop = 1;
            break;
        case DW_CFA_def_cfa_register:
            Length += DwDecodeUleb128(&State->Reg, pc + Length);
            break;
        case DW_CFA_def_cfa_offset:
            Length += DwDecodeUleb128((unsigned long*)&State->FramePtr, pc + Length);
            State->IsUwop = 1;
            break;
        case DW_CFA_def_cfa_sf:
            Length += DwDecodeUleb128(&State->Reg, pc + Length);
            Length += DwDecodeSleb128(&State->FramePtr, pc + Length);
            State->FramePtr *= 8; // data alignment
            State->IsUwop = 1;
            break;
        case DW_CFA_GNU_args_size:
        {
            unsigned long argsize;
            printf("Warning, DW_CFA_GNU_args_size is unimplemented\n");
            Length += DwDecodeUleb128(&argsize, pc + Length);
            break;
        }
        /* PSEH */
        case 0x21:
        {
            unsigned long SehType;

//            printf("found 0x21 at %lx\n", State->Location);
            Length += DwDecodeUleb128(&SehType, pc + Length);
            switch (SehType)
            {
                case 1: /* Begin Try */
                    State->TryLevel++;
                    if (State->TryLevel >= 20)
                    {
                        printf("WTF? Trylevel of 20 exceeded...\n");
                        exit(1);
                    }
                    State->SehBlock[State->TryLevel-1].BeginTry = State->Location;
//                    printf("Found begintry at 0x%lx\n", State->Location);
                    State->Scope = 1;
                    break;

                case 2: /* End Try */
                    State->SehBlock[State->TryLevel-1].EndTry = State->Location;
                    State->Scope = 2;
                    break;

                case 3: /* Jump target */
                    State->SehBlock[State->TryLevel-1].Target = State->Location;
                    State->Scope = 3;
                    break;

                case 4: /* SEH End */
                    if (State->TryLevel == 20)
                    {
                        printf("Ooops, end of SEH with trylevel at 0!\n");
                        exit(1);
                    }
                    State->SehBlock[State->TryLevel-1].End = State->Location;
                    State->TryLevel--;
                    State->cScopes++;
                    State->Scope = 0;
                    break;

                case 5: /* Constant filter */
                {
                    unsigned long value;
                    Length += DwDecodeUleb128(&value, pc + Length);
                    State->SehBlock[State->TryLevel-1].Handler = value;
//                     printf("Found a constant filter at 0x%lx\n", State->Location);
                    break;
                }

               /* These work differently. We are in a new function.
                 * We have to parse a lea opcode to find the adress of
                 * the jump target. This is the reference to find the
                 * appropriate C_SCOPE_TABLE. */
                case 6: /* Filter func */
//                    printf("Found a filter func at 0x%lx\n", State->Location);
                    break;

                case 7: /* Finally func */
                {
//                     printf("Found a finally func at 0x%lx\n", State->Location);
                    break;
                }

                default:
                    printf("Found unknow PSEH code 0x%lx\n", SehType);
                    exit(1);
            }
            break;
        }
        default:
            fprintf(stderr, "unknown instruction 0x%x at 0x%p\n", Code, pc);
            exit(1);
    }

    State->FramePtrDiff = State->FramePtr - PrevFramePtr;
    DPRINT("@%p: code=%x, Loc=%lx, offset=%lx, reg=0x%lx:%s\n",
        (void*)((ULONG)pc - g_ehframep), Code, State->Location, State->Offset, State->Reg, regs[State->Reg].name);
    return Length;
}

/** Windows unwind data functions *********************************************/

ULONG
StoreUnwindCodes(PUNWIND_INFO Info, PDW2CFSTATE State, ULONG FunctionStart)
{
    ULONG cCodes = 0;
    ULONG AllocSize;
    UNWIND_CODE Code[3];
    int i;

    Code[0].CodeOffset = State->Location - FunctionStart;

    switch (State->Code)
    {
        case DW_CFA_offset:
        case DW_CFA_offset_extended:
            // save register at offset
            Code[0].OpInfo = regs[State->Reg].regnt;
            if (State->Offset <= 0x7FFF8)
            {
                Code[0].UnwindOp = UWOP_SAVE_NONVOL;
                Code[1].FrameOffset = State->Offset / 8;
                cCodes = 2;
            }
            else
            {
                Code[0].UnwindOp = UWOP_SAVE_NONVOL_FAR;
                Code[1].FrameOffset = (State->Offset / 8);
                Code[2].FrameOffset = (State->Offset / 8) >> 16;
                cCodes = 3;
            }
            break;

        case DW_CFA_def_cfa:
        //case DW_CFA_def_cfa_register:
        case DW_CFA_def_cfa_offset:
        case DW_CFA_def_cfa_sf:
            AllocSize = State->FramePtrDiff;
            if (AllocSize <= 128)
            {
                Code[0].UnwindOp = UWOP_ALLOC_SMALL;
                Code[0].OpInfo = (AllocSize / 8) - 1;
                cCodes = 1;
            }
            else if (AllocSize <= 0x7FFF8)
            {
                Code[0].UnwindOp = UWOP_ALLOC_LARGE;
                Code[0].OpInfo = 0;
                Code[1].FrameOffset = AllocSize / 8;
                cCodes = 2;
            }
            else // if (AllocSize > 0x7FFF8)
            {
                Code[0].UnwindOp = UWOP_ALLOC_LARGE;
                Code[0].OpInfo = 1;
                Code[1].FrameOffset = (USHORT)AllocSize;
                Code[2].FrameOffset = (USHORT)(AllocSize >> 16);
                cCodes = 3;
            }
            break;
    }

    if (Info)
    {
        /* Move old codes */
        for (i = Info->CountOfCodes - 1; i >= 0; i--)
        {
            Info->UnwindCode[i + cCodes] = Info->UnwindCode[i];
        }

        /* Copy new codes */
        for (i = 0; i < cCodes; i++)
        {
            Info->UnwindCode[i] = Code[i];
        }

        Info->CountOfCodes += cCodes;
    }

    return cCodes;
}

#define GetxdataSize(cFuncs, cUWOP, cScopes) \
    ( cFuncs * (sizeof(UNWIND_INFO) + 2 + 4 + 4) \
    + cUWOP * sizeof(UNWIND_CODE) \
    + cScopes * sizeof(C_SCOPE_TABLE_ENTRY) )

ULONG
StoreUnwindInfo(PUNWIND_INFO Info, PDW2FDE pFde, ULONG FunctionStart)
{
    ULONG cbSize;
    DW2CFSTATE State;
    char *pInst;
    ULONG c;
    DW2CIE Cie;

    cbSize = 4; // sizeof(UNWIND_INFO);
    Info->Version = 1;
    Info->Flags = 0;
    Info->SizeOfProlog = 0;
    Info->CountOfCodes = 0;
    Info->FrameRegister = 0;
    Info->FrameOffset = 0;

    /* Decode the CIE */
    DwDecodeCie(&Cie, pFde->CiePointer);

    /* Initialize state */
    State.Location = FunctionStart;
    State.FramePtr = 0;
    State.TryLevel = 0;
    State.cScopes = 0;

    /* Parse the CIE's initial instructions */
    pInst = Cie.Instructions;
    while (pInst < Cie.Next)
    {
        pInst += DwExecIntruction(&State, pInst);
    }

    /* Parse the FDE instructions */
    pInst = pFde->Instructions;
    while (pInst < pFde->Next)
    {
        pInst += DwExecIntruction(&State, pInst);

        if (State.IsUwop)
        {
            c = StoreUnwindCodes(Info, &State, FunctionStart);
            cbSize += c * sizeof(UNWIND_CODE);
            Info->SizeOfProlog = State.Location - FunctionStart;
        }
    }
    cbSize = ROUND_UP(cbSize, 4);

    /* Do we have scope table to write? */
    if (State.cScopes > 0)
    {
        unsigned long i;
        ULONG *pExceptionHandler;
        PC_SCOPE_TABLE pScopeTable;

        /* Set flag for exception handler */
        Info->Flags |= UNW_FLAG_EHANDLER;

        /* Store address of handler and number of scope tables */
        pExceptionHandler = (ULONG*)((char*)Info + cbSize);
        // HACK for testing purpose
        *pExceptionHandler = FunctionStart; // _C_specific_handler

        pScopeTable = (PC_SCOPE_TABLE)(pExceptionHandler + 1);
        pScopeTable->NumEntries = State.cScopes;

        /* Store the scope table entries */
        for (i = 0; i < State.cScopes; i++)
        {
            pScopeTable->Entry[i].Begin = State.SehBlock[i].BeginTry;
            pScopeTable->Entry[i].End = State.SehBlock[i].EndTry;
            pScopeTable->Entry[i].Handler = 1;//State.SehBlock[i].Handler;
            pScopeTable->Entry[i].Target = State.SehBlock[i].Target;
        }

        /* Update size */
        cbSize += 8 + State.cScopes * sizeof(C_SCOPE_TABLE_ENTRY);
    }

    return cbSize;
}

void
CountUnwindData(PFILE_INFO File)
{
    DW2CIEFDE *p;
    DW2FDE Fde;
    char *pInst, *pmax;
    DW2CFSTATE State;

    File->cFuncs = 0;
    File->cScopes = 0;
    File->cUWOP = 0;
    State.FramePtr = 0;
    State.TryLevel = 0;

    p = File->eh_frame.p;
    pmax = (char*)p + File->eh_frame.psh->Misc.VirtualSize;
    for (; p->Length && (char*)p < pmax; p = NextCIE(p))
    {
        /* Is this an FDE? */
        if (p->CiePointer != 0)
        {
            File->cFuncs++;
            DwDecodeFde(&Fde, (char*)p);

            pInst = Fde.Instructions;
            while (pInst < Fde.Next)
            {
                pInst += DwExecIntruction(&State, pInst);
                File->cUWOP += StoreUnwindCodes(NULL, &State, 0);
                File->cScopes += State.Scope ? 1 : 0;
            }
        }
    }

    return;
}

int CompFunc(const void *p1, const void *p2)
{
    PRUNTIME_FUNCTION prf1 = (void*)p1, prf2 = (void*)p2;
    return (prf1->FunctionStart > prf2->FunctionStart ? 1 : -1);
}

void
GeneratePData(PFILE_INFO File)
{
    DW2CIEFDE *p;
    DW2FDE Fde;
    PIMAGE_DATA_DIRECTORY Dir;
    ULONG i, Offset;
    void * eh_frame;
    PRUNTIME_FUNCTION pdata;
    ULONG xdata_va;
    char *xdata_p;
    ULONG cbSize;
    PIMAGE_SECTION_HEADER pshp, pshx;
    ULONG FileAlignment;
    char *pmax;

    FileAlignment = File->OptionalHeader->FileAlignment;

    /* Get pointer to eh_frame section */
    eh_frame = File->eh_frame.p;
    g_ehframep = (ULONG)eh_frame;

    /* Get sizes */
    CountUnwindData(File);
//    printf("cFuncs = %ld, cUWOPS = %ld, cScopes = %ld\n",
//        File->cFuncs, File->cUWOP, File->cScopes);

    /* Initialize section header for .pdata */
    i = File->pdata.idx = File->UsedSections;
    pshp = File->pdata.psh = &File->NewSectionHeaders[i];
    memcpy(pshp->Name, ".pdata", 7);
    pshp->Misc.VirtualSize = (File->cFuncs + 1) * sizeof(RUNTIME_FUNCTION);
    pshp->VirtualAddress = File->NewSectionHeaders[i - 1].VirtualAddress +
                           File->NewSectionHeaders[i - 1].SizeOfRawData;
    pshp->SizeOfRawData = ROUND_UP(pshp->Misc.VirtualSize, FileAlignment);
    pshp->PointerToRawData = File->NewSectionHeaders[i - 1].PointerToRawData +
                           File->NewSectionHeaders[i - 1].SizeOfRawData;
    pshp->PointerToRelocations = 0;
    pshp->PointerToLinenumbers = 0;
    pshp->NumberOfRelocations = 0;
    pshp->NumberOfLinenumbers = 0;
    pshp->Characteristics = IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_NOT_PAGED |
                            IMAGE_SCN_CNT_INITIALIZED_DATA;

    /* Allocate .pdata buffer */
    pdata = File->pdata.p = malloc(pshp->SizeOfRawData);
    memset(File->pdata.p, 0, pshp->SizeOfRawData);

    /* Init exception data dir */
    Dir = &File->OptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION];
    Dir->VirtualAddress = pshp->VirtualAddress;
    Dir->Size = pshp->Misc.VirtualSize;

    /* Initialize section header for .xdata */
    File->xdata.idx = File->pdata.idx + 1;
    pshx = File->xdata.psh = &File->NewSectionHeaders[File->xdata.idx];
    memcpy(pshx->Name, ".xdata", 7);
    pshx->Misc.VirtualSize = GetxdataSize(File->cFuncs, File->cUWOP, File->cScopes);
    pshx->VirtualAddress = pshp->VirtualAddress + pshp->SizeOfRawData;
    pshx->SizeOfRawData = ROUND_UP(pshx->Misc.VirtualSize, FileAlignment);
    pshx->PointerToRawData = pshp->PointerToRawData + pshp->SizeOfRawData;
    pshx->PointerToRelocations = 0;
    pshx->PointerToLinenumbers = 0;
    pshx->NumberOfRelocations = 0;
    pshx->NumberOfLinenumbers = 0;
    pshx->Characteristics = IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_NOT_PAGED |
                            IMAGE_SCN_CNT_INITIALIZED_DATA;

    /* Allocate .xdata buffer */
    File->xdata.p = malloc(pshx->SizeOfRawData);
    memset(File->xdata.p, 0, pshx->SizeOfRawData);

    i = 0;
    Offset = File->eh_frame.psh->VirtualAddress;
    xdata_va = pshx->VirtualAddress;
    xdata_p = File->xdata.p;
    pmax = (char*)eh_frame + File->eh_frame.psh->Misc.VirtualSize - 100;

    for (p = eh_frame; p->Length && (char*)p < pmax; p = NextCIE(p))
    {
        /* Is this an FDE? */
        if (p->CiePointer != 0)
        {
            DwDecodeFde(&Fde, (char*)p);
            pdata[i].FunctionStart = Offset + 8 + Fde.PcBegin;
            pdata[i].FunctionEnd = pdata[i].FunctionStart + Fde.PcRange;
            pdata[i].UnwindInfo = xdata_va;

//            printf("%ld: RUNTIME_FUNCTION: {0x%lx, 0x%lx, 0x%lx}\n", i, pdata[i].FunctionStart, pdata[i].FunctionEnd, pdata[i].UnwindInfo);

            cbSize = StoreUnwindInfo((void*)xdata_p, &Fde, pdata[i].FunctionStart);
            xdata_va += cbSize;
            xdata_p += cbSize;
            i++;
        }
        Offset += 4 + p->Length;
    }

    /* Sort the RUNTIME_FUNCTIONS */
    qsort(pdata, i, sizeof(RUNTIME_FUNCTION), CompFunc);

}

/** Functions for COFF ********************************************************/


WORD
CalculateChecksum(DWORD Start, void *pFile, ULONG cbSize)
{
    WORD *Ptr = pFile;
    DWORD i;
    DWORD checksum = Start;

    for (i = 0; i < (cbSize + 1) / sizeof(WORD); i++)
    {
        checksum += Ptr[i];
        checksum = (checksum + (checksum >> 16)) & 0xffff;
    }

    return checksum ;
}

void
WriteOutFile(FILE *handle, PFILE_INFO File)
{
    int ret, Size, Pos = 0;
    DWORD CheckSum;
    ULONG i, Alignment;

    Alignment = File->OptionalHeader->FileAlignment;

    /* Update section count */
    File->FileHeader->NumberOfSections = File->UsedSections + 2; // FIXME!!!

    /* Update SizeOfImage */
    Size = File->xdata.psh->VirtualAddress
           + File->xdata.psh->SizeOfRawData;
    File->OptionalHeader->SizeOfImage = Size;

    /* Recalculate checksum */
    CheckSum = CalculateChecksum(0, File->FilePtr, File->HeaderSize);
    for (i = 0; i < File->AllSections; i++)
    {
        if (File->UseSection[i])
        {
            Size = File->SectionHeaders[i].SizeOfRawData;
            if (Size)
            {
                void *p;
                p = File->FilePtr + File->SectionHeaders[i].PointerToRawData;
                CheckSum = CalculateChecksum(CheckSum, p, Size);
            }
        }
    }
    Size = File->pdata.psh->Misc.VirtualSize;
    CheckSum = CalculateChecksum(CheckSum, File->pdata.p, Size);
    Size = File->xdata.psh->Misc.VirtualSize;
    CheckSum = CalculateChecksum(CheckSum, File->xdata.p, Size);
    CheckSum += File->HeaderSize;
    CheckSum += File->pdata.psh->Misc.VirtualSize;
    CheckSum += File->xdata.psh->Misc.VirtualSize;
    File->OptionalHeader->CheckSum = CheckSum;

    /* Write file header */
    Size = File->HeaderSize;
    ret = fwrite(File->DosHeader, 1, Size, handle);
    Pos = Size;

    /* Write Section headers */
    Size = File->NewSectionHeaderSize;
    ret = fwrite(File->NewSectionHeaders, 1, Size, handle);
    Pos += Size;

    /* Fill up to next alignement */
    Size = ROUND_UP(Pos, Alignment) - Pos;
    ret = fwrite(File->AlignBuf, 1, Size, handle);
    Pos += Size;

    /* Write sections */
    for (i = 0; i < File->AllSections; i++)
    {
        if (File->UseSection[i])
        {
            void *p;
            Size = File->SectionHeaders[i].SizeOfRawData;
            if (Size)
            {
                p = File->FilePtr + File->SectionHeaders[i].PointerToRawData;
                ret = fwrite(p, 1, Size, handle);
                Pos += Size;
            }
        }
    }

    /* Write .pdata section */
    Size = File->pdata.psh->SizeOfRawData;
    ret = fwrite(File->pdata.p, 1, Size, handle);
    Pos += Size;

    /* Write .xdata section */
    Size = File->xdata.psh->SizeOfRawData;
    ret = fwrite(File->xdata.p, 1, Size, handle);
    Pos += Size;

}


int
ParsePEHeaders(PFILE_INFO File)
{
    DWORD OldChecksum, Checksum;
    ULONG Alignment, CurrentPos;
    int i, j;

    /* Check if MZ header exists  */
    File->DosHeader = (PIMAGE_DOS_HEADER)File->FilePtr;
    if ((File->DosHeader->e_magic != IMAGE_DOS_MAGIC) ||
        (File->DosHeader->e_lfanew == 0L))
    {
        perror("Input file is not a PE image.\n");
        return -1;
    }

    /* Locate PE file header  */
    File->FileHeader = (PIMAGE_FILE_HEADER)(File->FilePtr +
                               File->DosHeader->e_lfanew + sizeof(ULONG));

    /* Check for x64 image */
    if (File->FileHeader->Machine != IMAGE_FILE_MACHINE_AMD64)
    {
        perror("Input file is not an x64 image.\n");
        return -1;
    }

    /* Locate optional header */
    File->OptionalHeader = (PIMAGE_OPTIONAL_HEADER64)(File->FileHeader + 1);

    /* Check if checksum is correct */
    OldChecksum = File->OptionalHeader->CheckSum;
    File->OptionalHeader->CheckSum = 0;
    Checksum = CalculateChecksum(0, File->FilePtr, File->cbInFileSize);
    Checksum += File->cbInFileSize;
    if ((Checksum & 0xffff) != (OldChecksum & 0xffff))
    {
        fprintf(stderr, "Input file has incorrect PE checksum: 0x%lx (calculated: 0x%lx)\n",
            OldChecksum, Checksum);
//        return 0;
    }

    /* Locate PE section headers  */
    File->SectionHeaders = (PIMAGE_SECTION_HEADER)((char*)File->OptionalHeader
                           + File->FileHeader->SizeOfOptionalHeader);

    File->HeaderSize = File->DosHeader->e_lfanew
                       + sizeof(ULONG)
                       + sizeof(IMAGE_FILE_HEADER)
                       + File->FileHeader->SizeOfOptionalHeader;

    /* Create some shortcuts */
    File->ImageBase = File->OptionalHeader->ImageBase;
    File->Symbols = File->FilePtr + File->FileHeader->PointerToSymbolTable;
    File->Strings = (char*)File->Symbols + File->FileHeader->NumberOfSymbols * 18;

    /* Check section names */
    File->AllSections = File->FileHeader->NumberOfSections;
    Alignment = File->OptionalHeader->FileAlignment;
    File->NewSectionHeaders = malloc((File->AllSections+2) * sizeof(IMAGE_SECTION_HEADER));
    File->UsedSections = 0;
    File->eh_frame.idx = -1;

    /* Allocate array of chars, specifiying whether to copy the section */
    File->UseSection = malloc(File->AllSections);

    for (i = 0; i < File->AllSections; i++)
    {
        char *pName = (char*)File->SectionHeaders[i].Name;
        File->UseSection[i] = 1;

        /* Check for long name */
        if (pName[0] == '/')
        {
            unsigned long index = strtoul(pName+1, 0, 10);
            pName = File->Strings + index;

            // Hack, simply remove all sections with long names
            File->UseSection[i] = 0;
        }

        /* Chek if we have the eh_frame section */
        if (strcmp(pName, ".eh_frame") == 0)
        {
            File->eh_frame.psh = &File->SectionHeaders[i];
            File->eh_frame.idx = i;
            File->eh_frame.p = File->FilePtr + File->eh_frame.psh->PointerToRawData;
        }

        /* Increase number of used sections */
        if (File->UseSection[i])
            File->UsedSections = i+1;

    }

    /* This is the actual size of the new section headers */
    File->NewSectionHeaderSize =
        (File->UsedSections+2) * sizeof(IMAGE_SECTION_HEADER);

    /* Calculate the position to start writing the sections to */
    CurrentPos = File->HeaderSize + File->NewSectionHeaderSize;
    CurrentPos = ROUND_UP(CurrentPos, Alignment);

    /* Create new section headers */
    for (i = 0, j = 0; i < File->UsedSections; i++)
    {
        /* Copy section header */
        File->NewSectionHeaders[j] = File->SectionHeaders[i];

        /* Shall we strip the section? */
        if (File->UseSection[i] == 0)
        {
            /* Make it a bss section */
            File->NewSectionHeaders[j].PointerToRawData = 0;
            File->NewSectionHeaders[j].SizeOfRawData = 0;
            File->NewSectionHeaders[j].Characteristics = 0xC0500080;
        }

        /* Fix Offset into File */
        File->NewSectionHeaders[j].PointerToRawData =
              File->NewSectionHeaders[j].PointerToRawData ? CurrentPos : 0;
        CurrentPos += File->NewSectionHeaders[j].SizeOfRawData;
        j++;
    }

    if (File->eh_frame.idx == -1)
    {
        //fprintf(stderr, "No .eh_frame section found\n");
        return 0;
    }

    return 1;
}

int main(int argc, char* argv[])
{
    char* pszInFile;
    char* pszOutFile;
    FILE_INFO File;
    FILE* outfile;
    int ret;
    int arg, argstate = 0;
    char *SourcePath = NULL;

    for (arg = 1; arg < argc; arg++)
    {
        switch (argstate)
        {
            default:
                argstate = -1;
                break;

            case 0:
                if (!strcmp(argv[arg], "-s"))
                {
                    argstate = 1;
                }
                else
                {
                    argstate = 2;
                    pszInFile = convert_path(argv[arg]);
                }
            break;

            case 1:
                free(SourcePath);
                SourcePath = strdup(argv[arg]);
                argstate = 0;
                break;

            case 2:
                pszOutFile = convert_path(argv[arg]);
                argstate = 3;
                break;
        }
    }

    if (argstate != 3)
    {
        fprintf(stderr, "Usage: rsym [-s <sources>] <input> <output>\n");
        exit(1);
    }

    File.FilePtr = load_file(pszInFile, &File.cbInFileSize);
    if (!File.FilePtr)
    {
        fprintf(stderr, "An error occured loading '%s'\n", pszInFile);
        exit(1);
    }

    ret = ParsePEHeaders(&File);
    if (ret != 1)
    {
        free(File.FilePtr);
        exit(ret == -1 ? 1 : 0);
    }

    File.AlignBuf = malloc(File.OptionalHeader->FileAlignment);
    memset(File.AlignBuf, 0, File.OptionalHeader->FileAlignment);

    GeneratePData(&File);

    outfile = fopen(pszOutFile, "wb");
    if (outfile == NULL)
    {
        perror("Cannot open output file");
        free(File.FilePtr);
        exit(1);
    }

    WriteOutFile(outfile, &File);

    fclose(outfile);

    return 0;
}