# -*- coding: utf-8 -*-
import sys
import collections
import struct
import binascii
import operator


# py3
is_py3 = (sys.version_info.major >= 3)
if is_py3:
    basestring = str
    int_types = int
else:
    int_types = (int, long)

# https://documentation-service.arm.com/static/67581b3355451e3c38d97c22
# Chapter C4: A64 Instruction Set Encoding: :

## C2.1.3
# 32-bit variant (sf = 0).
# 64-bit variant (sf = 1).

## C2.1.5
# The following symbol conventions are used:
# <Xn> The 64-bit name of a general-purpose register (X0-X30) or the zero register (XZR).
# <Wn> The 32-bit name of a general-purpose register (W0-W30) or the zero register (WZR).
# <Xn|SP> The 64-bit name of a general-purpose register (X0-X30) or the current stack pointer (SP).
# <Wn|WSP> The 32-bit name of a general-purpose register (W0-W30) or the current stack pointer (WSP).
# <Bn>, <Hn>, <Sn>, <Dn>, <Qn>
# The 8, 16, 32, 64, or 128-bit name of a SIMD and floating-point register in a scalar context, as
# described in Register names.
# <Vn> The name of a SIMD and floating-point register in a vector context, as described in Register names.
# <Zn> The name of an SVE scalable vector register, as described in Treatment of SVE scalable vector
# registers.
# <Pn> The name of an SVE scalable predicate register, as described in Vector predication


# Make a special memoryview that match what is show in the ARM Chapter C4 ?

# A lot of hardcoded bits are filled this way:

## self.bits[24:32] = reversed(bytearray(XXX))

# This allow to write the bits in the same order as the ARM manual which is describe with most significant bit first
# Whereas our internal structure is reverse for simplicity of mapping it on list index

XREGISTER = {'X0', 'X1', 'X2', 'X3', 'X4', 'X5', 'X6', 'X7', 'X8', 'X9', 'X10', 'X11', 'X12', 'X13', 'X14', 'X15', 'X16', 'X17', 'X18', 'X19', 'X20', 'X21', 'X22', 'X23', 'X24', 'X25', 'X26', 'X27', 'X28', 'X29', 'X30'}
WREGISTER = {'W0', 'W1', 'W2', 'W3', 'W4', 'W5', 'W6', 'W7', 'W8', 'W9', 'W10', 'W11', 'W12', 'W13', 'W14', 'W15', 'W16', 'W17', 'W18', 'W19', 'W20', 'W21', 'W22', 'W23', 'W24', 'W25', 'W26', 'W27', 'W28', 'W29', 'W30'}
ALL_REGISTER = XREGISTER | WREGISTER

SP = "SP"
WSP = "WSP"

ZR = "XZR"
WZR = "WZR"
# Special register name for simple_arm64 that do not setup bitness
# Bitness will depends on others registers in the instruction
ZERO = "ZERO"

SPECIAL_X31 = set((SP, WSP, ZR, WZR, ZERO))

# Argument class
class Shift(object):
    """Represent a shift parameter of an instruction.
    Allow to JIT shift at instruction crafting time without string manipulation for the #XXX"""
    def __init__(self, type, value):
        assert isinstance(type, str)
        assert isinstance(value, int)
        type = type.upper()
        assert type in ("LSL", "LSR", "ASR", "ROR")
        self.type = type
        self.value = value

    def __eq__(self, other):
        if not isinstance(other, Shift): # Allow compare to tuple & iterable
            return ((len(other) == 2) and
                    (self.type == other[0]) and
                    (self.value == other[1]))
        return (self.type == other.type) and (self.value == other.value)

    def __repr__(self):
        return """{0}(type={1}, value={2})""".format(type(self).__name__, self.type, self.value)

    @classmethod
    def parse(cls, shiftstr):
        if not isinstance(shiftstr, str):
            return None
        if not shiftstr.count(" ") == 1:
            return None
        stype, svalue = shiftstr.split(" ", 1)
        stype = stype.upper()
        if stype not in ("LSL", "LSR", "ASR", "ROR"):
            return None
        if len(svalue) <= 1:
            return None
        if not svalue.startswith("#"):
            return None
        try:
            intvalue = int(svalue[1:])
        except ValueError:
            return None
        return cls(stype, intvalue)
# instruction Encoding

class InstructionEncoding(object):
    # Sub classes can force 32/64 only instrs by setting this to 32 or 64
    BITNESS = None

    def __init__(self):
        super(InstructionEncoding, self).__init__()
        # Bits are in
        # 0 1 2 3 4 ... 31
        # Translation to real little-endian is done last
        self.bytearray = bytearray(32)
        self.bits = memoryview(self.bytearray)

        self.bitness = self.BITNESS

    @classmethod
    def is_register(self, arg, accept_sp=False):
        arg = arg.upper()
        return (arg in ALL_REGISTER or
                (accept_sp and (arg in [SP, WSP])) or
                (not accept_sp and (arg in [ZR, WZR, ZERO])))

    @classmethod
    def is_imm(self, arg):
        try:
            value = int(arg)
        except (ValueError, TypeError):
            return False
        return True # Check size max ?

    @classmethod
    def is_shift(self, arg):
        return (arg is None) or isinstance(arg, Shift) or Shift.parse(arg)


    @classmethod
    def gen(cls, **encoding_array):
        class GeneratedEncodingCls(cls):
            ENCODING_VALUES = encoding_array
        return GeneratedEncodingCls

    # Instruction filing at instanciation

    def setup_fixed_values(self):
        # Setup the values registered by InstructionEncoding.gen(x=1, y=2)
        for name, value in self.ENCODING_VALUES.items():
            assert isinstance(value, int)
            self.setup_immediat(getattr(self, name), value)

    def binencode_imm(self, immediat, outsize):
        binstr = "{:0{outsize}b}".format(immediat, outsize=outsize)
        if len(binstr) != outsize:
            raise ValueError("Could not encode immediat {0} in {1} bits. Value take {2} bits".format(immediat, outsize, len(binstr)))

        binlist = [int(c) for c in reversed(binstr)]
        return bytearray(binlist)

    def setup_bitness(self, bitness):
        assert bitness in (32, 64)
        if self.bitness is None:
            self.bitness = bitness
            if bitness == 32:
                self.sf[:] = b"\x00"
            else: # bitness == 64:
                self.sf[:] = b"\x01"
        if self.bitness != bitness:
            raise ValueError("Bitness mismatch on <{0}> encoding, instruction is alredy {1} cannot set as {2}".format(type(self).__name__, self.bitness, bitness))

    def encode_register(self, register, outsize=5):
        register = register.upper()
        if register in SPECIAL_X31:
            if register == ZERO:
                pass # Special simple_arm64 value that do not setup bitness
            elif register.startswith("W"):
               self.setup_bitness(32)
            else:
                self.setup_bitness(64)
            return self.binencode_imm(31, outsize)

        assert register in ALL_REGISTER
        if register in XREGISTER:
            self.setup_bitness(64)
        else:
            self.setup_bitness(32)
        return self.binencode_imm(int(register[1:]), outsize)

    def setup_register(self, regfield, register):
        encoded = self.encode_register(register)
        regfield[:] = encoded

    # Instruction filing at instanciation
    def setup_immediat(self, immfield, value):
        immsize = len(immfield)
        immfield[:] = self.binencode_imm(value, immsize)
        return True



# C4.1.93 Data Processing - Immediate

class DataProcessingImmediate(InstructionEncoding):
    def __init__(self):
        super(DataProcessingImmediate, self).__init__()
        self.bits[26:29] = bytearray((0,0,1))
        self.op0 = self.bits[29:31]
        self.op1 = self.bits[22:26]

class AddSubtractImmediate(DataProcessingImmediate):
    def __init__(self, argsdict):
        super(AddSubtractImmediate, self).__init__()
        self.sf = self.bits[31:32] # Keep it a memoryview
        self.op = self.bits[30:31] # Keep it a memoryview
        self.S = self.bits[29:30] # Keep it a memoryview
        self.bits[23:29] = bytearray((0, 1, 0, 0, 0, 1))
        self.sh = self.bits[22:23]
        self.imm12 = self.bits[10:22]
        self.rn = self.bits[5:10]
        self.rd = self.bits[0:5]


        self.setup_fixed_values()
        # Change instruction based of parameter
        self.setup_register(self.rd, argsdict[0])
        self.setup_register(self.rn, argsdict[1])
        self.setup_immediat(self.imm12, argsdict[2])

        shift = Shift.parse(argsdict.get(3))
        if not shift:
            return

        if shift not in [("LSL", 0), ("LSL", 12)]:
            raise ValueError("Invalid shift for instruction: {0}".format(shift))
        if shift == ("LSL", 12):
            self.sh[:] = bytearray((1,))


    @classmethod
    def accept_arg(cls, argsdict):
        return (cls.is_register(argsdict[0], accept_sp=True) and
                cls.is_register(argsdict[1], accept_sp=True) and
                cls.is_imm(argsdict[2]) and
                cls.is_shift(argsdict.get(3)))


# C4.1.93.6 Logical (immediate)
# Wtf : https://kddnewton.com/2022/08/11/aarch64-bitmask-immediates.html

class DataProcessingLogicalImmediate(DataProcessingImmediate):
    def __init__(self, argsdict):
        super(DataProcessingLogicalImmediate, self).__init__()
        self.sf = self.bits[31:32]
        self.opc = self.bits[29:31]
        self.bits[23:29] = bytearray(reversed((1, 0, 0, 1, 0, 0)))
        self.N = self.bits[22:23]
        self.immr = self.bits[16:22]
        self.imms = self.bits[10:16]
        self.rn = self.bits[5:10]
        self.rd = self.bits[0:5]

        self.setup_fixed_values()
        # Change instruction based of parameter
        self.setup_register(self.rd, argsdict[0])
        self.setup_register(self.rn, argsdict[1])
        self.setup_bitmask_imm(self.imm12, argsdict[2])

    @classmethod
    def accept_arg(cls, argsdict):
        return (cls.is_register(argsdict[0], accept_sp=True) and
                cls.is_register(argsdict[1], accept_sp=True) and
                cls.is_bitmask_imm(argsdict[2]))

    @classmethod
    def is_bitmask_imm(*args, **kwargs):
        raise NotImplementedError("is_bitmask_imm")

    def setup_bitmask_imm(*args, **kwargs):
        raise NotImplementedError("setup_bitmask_imm")


class MovWideImmediat(DataProcessingImmediate):
    def __init__(self, argsdict):
        super(MovWideImmediat, self).__init__()
        self.sf = self.bits[31:32]
        self.opc = self.bits[29:31]
        self.bits[23:29] = bytearray(reversed((1, 0, 0, 1, 0, 1)))
        self.hw = self.bits[21:23]
        self.imm16 = self.bits[5:21]
        self.rd = self.bits[0:5]


        self.setup_fixed_values()
        # Change instruction based of parameter
        self.setup_register(self.rd, argsdict[0])
        self.setup_immediat(self.imm16, argsdict[1])

        shift = Shift.parse(argsdict.get(2))
        if not shift:
            return
        if shift.type != "LSL":
            raise ValueError("Invalid shift type for {0} : {1}".format(type(self).__name__, shift.value))
        if shift.value not in (0, 16 ,32, 48):
            raise ValueError("Invalid shift value for {0} : {1}".format(type(self).__name__, shift.value))
        if self.bitness == 32 and shift.value > 16:
            raise ValueError("Invalid shift value for 32bits encoding of {0} : {1}".format(type(self).__name__, shift.value))

        self.setup_immediat(self.hw, shift.value // 16)


    @classmethod
    def accept_arg(cls, argsdict):
        return (cls.is_register(argsdict[0], accept_sp=True) and
                cls.is_imm(argsdict[1]) and
                cls.is_shift(argsdict.get(2)))



### C4.1.94.13 Unconditional branch (register)

class UnconditionalBranchRegister(InstructionEncoding):
    BITNESS = 64

    def __init__(self, argsdict):
        super(UnconditionalBranchRegister, self).__init__()
        # Allow to fill it in the same order as the ARM manual
        self.bits[25:32] = bytearray(reversed((1, 1, 0, 1, 0, 1, 1)))
        self.opc = self.bits[21:25]
        self.op2 = self.bits[16:21]
        self.op3 = self.bits[10:16]
        self.rn = self.bits[5:10]
        self.op4 = self.bits[0:5]

        self.setup_fixed_values()
        self.setup_register(self.rn, argsdict[0])

    @classmethod
    def accept_arg(cls, argsdict):
        return (cls.is_register(argsdict[0], accept_sp=True))


class RetEncoding(UnconditionalBranchRegister.gen(opc=0b10, op2=0b11111, op3=0, op4=0)):
    # Ret can accept no register and default to X30
    def __init__(self, argsdict):
        if not argsdict:
            argsdict[0] = "X30"
        super(RetEncoding, self).__init__(argsdict)

    @classmethod
    def accept_arg(cls, argsdict):
        return not argsdict or cls.is_register(argsdict[0], accept_sp=True)


# C4.1.95 Data Processing – Register

class DataProcessingRegister(InstructionEncoding):
    def __init__(self):
        super(DataProcessingRegister, self).__init__()
        self.op0 = self.bits[30:31]
        self.op1 = self.bits[28:29]
        self.bits[25:28] = bytearray(reversed((1, 0, 1)))
        self.op2 = self.bits[21:25]
        self.op3 = self.bits[10:16]

class DataProcessingLogicalShiftedRegister(DataProcessingRegister):
    def __init__(self, argsdict):
        super(DataProcessingLogicalShiftedRegister, self).__init__()
        self.sf = self.bits[31:32]
        self.opc = self.bits[29:31]
        self.bits[24:29] = bytearray(reversed((0, 1, 0, 1, 0)))
        self.shift = self.bits[22:24]
        self.N = self.bits[21:22]
        self.rm = self.bits[16:21]
        self.imm6 = self.bits[10:16]
        self.rn = self.bits[5:10]
        self.rd = self.bits[0:5]

        self.setup_fixed_values()
        # Change instruction based of parameter
        self.setup_register(self.rd, argsdict[0])
        self.setup_register(self.rn, argsdict[1])
        self.setup_register(self.rm, argsdict[2])

        shift = Shift.parse(argsdict.get(3))
        if not shift:
            return
        # Is this mapping generic ? Store ir somewhere ?
        # Is the shift size logic repeatable and factorisable ?
        if self.bitness == 32 and shift.value > 31:
            raise ValueError("Invalid shift value for 32bits encoding of {0} : {1}".format(type(self).__name__, shift.value))

        SHIFT_MAPPING = {"LSL": 0b00, "LSR": 0b01, "ASR": 0b10, "ROR": 0b11}
        self.setup_immediat(self.shift, SHIFT_MAPPING[shift.type])
        self.setup_immediat(self.imm6, shift.value)


    @classmethod
    def accept_arg(cls, argsdict):
        return (cls.is_register(argsdict[0]) and
                cls.is_register(argsdict[1]) and
                cls.is_register(argsdict[2]) and
                cls.is_shift(argsdict.get(3)))

# An instruction is a Name that can have multiple encoding
# It's the class we instanciate to assemble instructions
# C6.2.270 ORR (immediate)
# C6.2.271 ORR (shifted register)

# there also seem to exist "alias instructions" like "mov"
# That just map to others instruction when specific condition are met on the params


class Instruction(object):
    encoding = []

    def __init__(self, *args):
        argsdict = dict(enumerate(args)) # Like a list but allow arg.get(4)
        for i, encodcls in enumerate(self.encoding):
            # Late rewrite of GeneratedEncodingCls classname for better message error
            if encodcls.__name__ == "GeneratedEncodingCls":
                encodcls.__name__ = "{0}Encoding{1}".format(type(self).__name__, i)


            if encodcls.accept_arg(argsdict):
                self.encoded = encodcls(argsdict)
                return
        raise ValueError("Cannot encode <{0} {1}>:(".format(type(self).__name__, args))

    def get_code(self):
        intlist = list(self.encoded.bits)
        if not is_py3:
            intlist = [ord(x) for x in intlist]
        # Our encoding to real little-endian
        encoding_getter = operator.itemgetter(7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 23, 22, 21, 20, 19, 18, 17, 16, 31, 30, 29, 28, 27, 26, 25, 24)
        dword = 0
        for bit in encoding_getter(intlist):
            assert bit in (0, 1), "Unexpected bite value in encoding of {0} : {1} in {2}".format(type(self).__name__, bit, intlist)
            dword = (dword << 1) | bit
        return struct.pack(">I", dword) # We already have handled endianess



        # Fix endianned


class Add(Instruction):
    encoding = [AddSubtractImmediate.gen(op=0, S=0)]

class Subs(Instruction):
    encoding = [AddSubtractImmediate.gen(op=1, S=1)]

### C6.2.307 RET (page 2203) (11010110010111110000000000000000)

class Ret(Instruction):
    encoding = [RetEncoding]

#  C6.2.254

class Movz(Instruction):
    encoding = [MovWideImmediat.gen(opc=0b10)]

class Movk(Instruction):
    encoding = [MovWideImmediat.gen(opc=0b11)]

# The encoding for "mov reg, reg" :D
# C6.2.271
# Todo: Instruction like "mov" that dispatch to other instruction encoding based on more precise condition on param ?
class Orr(Instruction):
    encoding = [DataProcessingLogicalShiftedRegister.gen(opc=0b01)]


# Meta Instruction that can dispatch to others Instruction/Encoding based on special case
# Ex: "mov reg1, re2" -> "orr reg1, xzr, reg2"

class Mov(Instruction): # VirtualInstruction ?
    def __new__(self, *args):
        argsdict = dict(enumerate(args)) # Like a list but allow arg.get(4)
        if (set(argsdict) == {0, 1} and  # Only 2 args
                InstructionEncoding.is_register(argsdict[0]) and
                InstructionEncoding.is_register(argsdict[1])):
            return Orr(argsdict[0], ZERO, argsdict[1])

        raise ValueError("Cannot encode <{0} {1}>:(".format(type(self).__name__, args))


class MultipleInstr(object):
    INSTRUCTION_SIZE = 4

    def __init__(self, init_instrs=()):
        self.instrs = {}
        self.labels = {}
        self.expected_labels = {}
        # List of all labeled jump already resolved
        # Will be used for 'relocation'
        self.computed_jump = []
        self.size = 0
        for i in init_instrs:
            self += i

    def get_code(self):
        if self.expected_labels:
            raise ValueError("Unresolved labels: {0}".format(self.expected_labels.keys()))
        return b"".join([x[1].get_code() for x in sorted(self.instrs.items())])

    def add_instruction(self, instruction):
        # if isinstance(instruction, Label):
        #     return self.add_label(instruction)
        # # Change DelayedJump to LabeledJump ?
        # if isinstance(instruction, DelayedJump):
        #     return self.add_delayed_jump(instruction)
        if isinstance(instruction, Instruction):
            self.instrs[self.size] = instruction
            self.size += self.INSTRUCTION_SIZE
            return
        raise ValueError("Don't know what to do with {0} of type {1}".format(instruction, type(instruction)))

    def add_label(self, label):
        if label.name not in self.expected_labels:
            # Label that have no jump before definition
            # Just registed the address of the label
            self.labels[label.name] = self.size
            return
        # Label with jmp before definition
        # Lot of stuff todo:
            # Find all delayed jump that refer to this jump
            # Replace them with real jump
            # If size of jump < JUMP_SIZE: relocate everything we can
            # Update expected_labels
        for jump_to_label in self.expected_labels[label.name]:
            if jump_to_label.offset in self.instrs:
                raise ValueError("WTF REPLACE EXISTING INSTR...")
            distance = self.size - jump_to_label.offset
            real_jump = jump_to_label.type(distance)
            self.instrs[jump_to_label.offset] = real_jump
            self.computed_jump.append((jump_to_label.offset, self.size))
            for i in range(self.JUMP_SIZE - len(real_jump.get_code())):
                self.instrs[jump_to_label.offset + len(real_jump.get_code()) + i] = _NopArtifact()
        del self.expected_labels[label.name]
        self.labels[label.name] = self.size
        if not self.expected_labels:
            # No more un-resolved label (for now): time to reduce the shellcode
            self._reduce_shellcode()

    def add_delayed_jump(self, jump):
        dst = jump.label
        if dst in self.labels:
            # Jump to already defined labels
            # Nothing fancy: get offset of label and jump to it !
            distance = self.size - self.labels[dst]
            jump_instruction = jump.type(-distance)
            self.computed_jump.append((self.size, self.labels[dst]))
            return self.add_instruction(jump_instruction)
        # Jump to undefined label
        # Add label to expected ones
        # Add jump info -> offset of jump | type
        # Reserve space for call !
        jump.offset = self.size
        self.expected_labels.setdefault(dst, []).append(jump)
        self.size += self.JUMP_SIZE
        return

    def merge_shellcode(self, other):
        shared_labels = set(self.labels) & set(other.labels)
        if shared_labels:
            raise ValueError("Cannot merge shellcode: shared labels {0}".format(shared_labels))
        for offset, instr in sorted(other.instrs.items()):
            for label_name in [name for name, label_offset in other.labels.items() if label_offset == offset]:
                self.add_instruction(Label(label_name))
            self.add_instruction(instr)

    def __iadd__(self, other):
        if isinstance(other, MultipleInstr):
            self.merge_shellcode(other)
        elif isinstance(other, basestring):
            self.assemble(other)
        else:
            self.add_instruction(other)
        return self

    def assemble(self, code):
        for instr in assemble_instructions_generator(code):
            self.add_instruction(instr)


def split_in_instruction(str):
    for line in str.split("\n"):
        if not line:
            continue
        for instr in line.split(";"):
            if not instr:
                continue
            yield instr.strip()

def assemble_instructions_generator(str):
    for instr in split_in_instruction(str):
        data = instr.split(" ", 1)
        mnemo, args_raw = data[0], data[1:]
        try:
            instr_object = globals()[mnemo.capitalize()]
        except:
            raise ValueError("Unknow mnemonic <{0}>".format(mnemo))

        # if issubclass(instr_object, Raw):
        #     # Raw should received the raw buffer as it expect encoded hex
        #     # The transformation may transform 'raw 9090' (nopnop) as 0n9090
        #     # If other fake-instr need this : make a class attribute
        #     yield instr_object(*args_raw)
        #     continue

        args = []
        if args_raw:
            for arg in args_raw[0].split(","):
                arg = arg.strip()
                try:
                    arg = int(arg, 0)
                except ValueError:
                    pass
                args.append(arg)
        yield instr_object(*args)

def assemble(str):
    """Play test"""
    shellcode = MultipleInstr()
    shellcode += str
    return shellcode.get_code()