package obj

import (
    `encoding/binary`
    `io`
    `unsafe`
)

type MachOHeader struct {
    Magic      uint32
    CPUType    uint32
    CPUSubType uint32
    FileType   uint32
    CmdCount   uint32
    CmdSize    uint32
    Flags      uint32
    _          uint32
}

type SegmentCommand struct {
    Cmd          uint32
    Size         uint32
    Name         [16]byte
    VMAddr       uint64
    VMSize       uint64
    FileOffset   uint64
    FileSize     uint64
    MaxProtect   uint32
    InitProtect  uint32
    SectionCount uint32
    Flags        uint32
}

type SegmentSection struct {
    Name      [16]byte
    SegName   [16]byte
    Addr      uint64
    Size      uint64
    Offset    uint32
    Align     uint32
    RelOffset uint32
    RelCount  uint32
    Flags     uint32
    _         [3]uint32
}

type Registers struct {
    RAX    uint64
    RBX    uint64
    RCX    uint64
    RDX    uint64
    RDI    uint64
    RSI    uint64
    RBP    uint64
    RSP    uint64
    R8     uint64
    R9     uint64
    R10    uint64
    R11    uint64
    R12    uint64
    R13    uint64
    R14    uint64
    R15    uint64
    RIP    uint64
    RFLAGS uint64
    CS     uint64
    FS     uint64
    GS     uint64
}

type UnixThreadCommand struct {
    Cmd    uint32
    Size   uint32
    Flavor uint32
    Count  uint32
    Regs   Registers
}

const (
    _MH_MAGIC_64 = 0xfeedfacf
    _MH_EXECUTE  = 0x02
    _MH_NOUNDEFS = 0x01
)

const (
    _CPU_TYPE_I386  = 0x00000007
    _CPU_ARCH_ABI64 = 0x01000000
)

const (
    _CPU_SUBTYPE_LIB64    = 0x80000000
    _CPU_SUBTYPE_I386_ALL = 0x00000003
)

const (
    _LC_SEGMENT_64 = 0x19
    _LC_UNIXTHREAD = 0x05
)

const (
    _VM_PROT_READ    = 0x01
    _VM_PROT_WRITE   = 0x02
    _VM_PROT_EXECUTE = 0x04
)

const (
    _S_ATTR_SOME_INSTRUCTIONS = 0x00000400
    _S_ATTR_PURE_INSTRUCTIONS = 0x80000000
)

const (
    _x86_THREAD_STATE64          = 0x04
    _x86_EXCEPTION_STATE64_COUNT = 42
)

const (
    _MACHO_SIZE      = uint32(unsafe.Sizeof(MachOHeader{}))
    _SEGMENT_SIZE    = uint32(unsafe.Sizeof(SegmentCommand{}))
    _SECTION_SIZE    = uint32(unsafe.Sizeof(SegmentSection{}))
    _UNIXTHREAD_SIZE = uint32(unsafe.Sizeof(UnixThreadCommand{}))
)

const (
    _IMAGE_SIZE = 4096
    _IMAGE_BASE = 0x04000000
)

const (
    _HDR_SIZE  = _MACHO_SIZE + _SEGMENT_SIZE * 2 + _SECTION_SIZE + _UNIXTHREAD_SIZE
    _ZERO_SIZE = (_IMAGE_SIZE - _HDR_SIZE %_IMAGE_SIZE) % _IMAGE_SIZE
)

var (
    zeroBytes = [_ZERO_SIZE]byte{}
)

func assembleMachO(w io.Writer, code []byte, base uint64, entry uint64) error {
    var p0name [16]byte
    var txname [16]byte
    var tsname [16]byte

    /* segment names */
    copy(tsname[:], "__text")
    copy(txname[:], "__TEXT")
    copy(p0name[:], "__PAGEZERO")

    /* calculate size of code */
    clen := uint64(len(code))
    hlen := uint64(_HDR_SIZE + _ZERO_SIZE)

    /* Mach-O does not allow image base at zero */
    if base == 0 {
        base = _IMAGE_BASE
        entry += _IMAGE_BASE
    }

    /* Page-0 Segment */
    p0 := SegmentCommand {
        Cmd    : _LC_SEGMENT_64,
        Size   : _SEGMENT_SIZE,
        Name   : p0name,
        VMSize : base,
    }

    /* TEXT Segment */
    text := SegmentCommand {
        Cmd          : _LC_SEGMENT_64,
        Size         : _SEGMENT_SIZE + _SECTION_SIZE,
        Name         : txname,
        VMAddr       : base,
        VMSize       : hlen + clen,
        FileSize     : hlen + clen,
        MaxProtect   : _VM_PROT_READ | _VM_PROT_WRITE | _VM_PROT_EXECUTE,
        InitProtect  : _VM_PROT_READ | _VM_PROT_EXECUTE,
        SectionCount : 1,
    }

    /* __TEXT.__text section */
    tsec := SegmentSection {
        Name    : tsname,
        SegName : txname,
        Addr    : base + hlen,
        Size    : clen,
        Offset  : uint32(hlen),
        Flags   : _S_ATTR_SOME_INSTRUCTIONS | _S_ATTR_PURE_INSTRUCTIONS,
    }

    /* UNIX Thread Metadata */
    unix := UnixThreadCommand {
        Cmd    : _LC_UNIXTHREAD,
        Size   : _UNIXTHREAD_SIZE,
        Flavor : _x86_THREAD_STATE64,
        Count  : _x86_EXCEPTION_STATE64_COUNT,
        Regs   : Registers { RIP: hlen + entry },
    }

    /* Mach-O Header */
    macho := MachOHeader {
        Magic      : _MH_MAGIC_64,
        CPUType    : _CPU_ARCH_ABI64 | _CPU_TYPE_I386,
        CPUSubType : _CPU_SUBTYPE_LIB64 | _CPU_SUBTYPE_I386_ALL,
        FileType   : _MH_EXECUTE,
        CmdCount   : 3,
        CmdSize    : _SEGMENT_SIZE * 2 + _SECTION_SIZE + _UNIXTHREAD_SIZE,
        Flags      : _MH_NOUNDEFS,
    }

    /* write the headers */
    if err := binary.Write(w, binary.LittleEndian, &macho)     ; err != nil { return err }
    if err := binary.Write(w, binary.LittleEndian, &p0)        ; err != nil { return err }
    if err := binary.Write(w, binary.LittleEndian, &text)      ; err != nil { return err }
    if err := binary.Write(w, binary.LittleEndian, &tsec)      ; err != nil { return err }
    if err := binary.Write(w, binary.LittleEndian, &unix)      ; err != nil { return err }
    if err := binary.Write(w, binary.LittleEndian, &zeroBytes) ; err != nil { return err }

    /* write the code */
    if n, err := w.Write(code); err != nil {
        return err
    } else if n != len(code) {
        return io.ErrShortWrite
    } else {
        return nil
    }
}
