/*
 * Copyright (c) 2020 Bitdefender
 * SPDX-License-Identifier: Apache-2.0
 */
#define _SIGNAL_H
#include <Python.h>
#include "pydis.h"


static char module_docstring[] = "This module provides an interface for bddisasm.";

static char pydis_decode_ex_docstring[] = "Disasemble at the provided address.";
static char pydis_decode_ex2_docstring[] = "Disasemble at the provided address.";
static char pydis_decode_docstring[] = "Disasemble at the provided address.";
static char pydis_to_text_docstring[] = "Disasemble at the provided address and give back only the text.";


static char *OperandTypeToString(ND_OPERAND_TYPE Type)
{
    switch (Type)
    {
    case ND_OP_NOT_PRESENT: return "not_present";
    case ND_OP_REG: return "reg";
    case ND_OP_MEM: return "mem";
    case ND_OP_IMM: return "imm";
    case ND_OP_OFFS: return "offs";
    case ND_OP_ADDR: return "addr";
    case ND_OP_CONST: return "const";
    case ND_OP_BANK: return "bank";
    }

    return "unknown";
}


static char *OperandEncodingToString(ND_OPERAND_ENCODING Encoding)
{
    switch (Encoding)
    {
    case ND_OPE_NP: return "NP";
    case ND_OPE_R: return "R";
    case ND_OPE_M: return "M";
    case ND_OPE_V: return "V";
    case ND_OPE_D: return "D";
    case ND_OPE_O: return "O";
    case ND_OPE_I: return "I";
    case ND_OPE_C: return "C";
    case ND_OPE_1: return "1";
    case ND_OPE_L: return "L";
    case ND_OPE_A: return "A";
    case ND_OPE_E: return "E";
    case ND_OPE_S: return "S";
    }

    return "unknown";
}


static char *RegTypeToString(ND_REG_TYPE RegType)
{
    switch (RegType)
    {
    case ND_REG_NOT_PRESENT: return "NOT_PRESENT";
    case ND_REG_GPR: return "GPR";
    case ND_REG_SEG: return "SEG";
    case ND_REG_FPU: return "FPU";
    case ND_REG_MMX: return "MMX";
    case ND_REG_SSE: return "SSE";
    case ND_REG_CR: return "CR";
    case ND_REG_DR: return "DR";
    case ND_REG_TR: return "TR";
    case ND_REG_BND: return "BND";
    case ND_REG_MSK: return "MSK";
    case ND_REG_TILE: return "TILE";
    case ND_REG_MSR: return "MSR";
    case ND_REG_XCR: return "XCR";
    case ND_REG_SYS: return "SYS";
    case ND_REG_X87: return "X87";
    case ND_REG_MXCSR: return "MXCSR";
    case ND_REG_PKRU: return "PKRU";
    case ND_REG_SSP: return "SSP";
    case ND_REG_FLG: return "FLG";
    case ND_REG_RIP: return "RIP";
    }

    return "unknown";
}


static char *TupleTypeToString(ND_TUPLE TupleType)
{
    switch (TupleType)
    {
    case ND_TUPLE_None: return "None";
    case ND_TUPLE_FV: return "FV";
    case ND_TUPLE_HV: return "HV";
    case ND_TUPLE_T1S8: return "T1S8";
    case ND_TUPLE_T1S16: return "T1S16";
    case ND_TUPLE_T1S: return "T1S";
    case ND_TUPLE_T1F: return "T1F";
    case ND_TUPLE_T2: return "T2";
    case ND_TUPLE_T4: return "T4";
    case ND_TUPLE_T8: return "T8";
    case ND_TUPLE_FVM: return "FVM";
    case ND_TUPLE_HVM: return "HVM";
    case ND_TUPLE_QVM: return "QVM";
    case ND_TUPLE_OVM: return "OVM";
    case ND_TUPLE_M128: return "M128";
    case ND_TUPLE_DUP: return "DUP";
    case ND_TUPLE_T1_4X: return "4X";
    }

    return "unknown";
}


static char *RoundingModeToString(ND_ROUNDING RoundingMode)
{
    switch (RoundingMode)
    {
    case ND_RND_RNE: return "RNE";
    case ND_RND_RD: return "RD";
    case ND_RND_RU: return "RU";
    case ND_RND_RZ: return "RZ";
    }

    return "unknown";
}


static PyObject *_pydis_build_rex(ND_REX *rex)
{
    return Py_BuildValue("{s,B, s,B, s,B, s,B, s,B}",
                         "Rex", rex->Rex,
                         "b", rex->b,
                         "x", rex->x,
                         "r", rex->r,
                         "w", rex->w);
}


static PyObject *_pydis_build_modrm(ND_MODRM *modrm)
{
    return Py_BuildValue("{s,B, s,B, s,B, s,B}",
                         "ModRm", modrm->ModRm,
                         "rm", modrm->rm,
                         "reg", modrm->reg,
                         "mod", modrm->mod);
}


static PyObject *_pydis_build_sib(ND_SIB *sib)
{
    return Py_BuildValue("{s,B, s,B, s,B, s,B}",
                         "Sib", sib->Sib,
                         "base", sib->base,
                         "index", sib->index,
                         "scale", sib->scale);
}


static PyObject *_pydis_build_drex(ND_DREX *drex)
{
    return Py_BuildValue("{s,B, s,B, s,B, s,B, s,B, s,B, s,B}",
                         "Drex", drex->Drex,
                         "b", drex->b,
                         "x", drex->x,
                         "r", drex->r,
                         "oc0", drex->oc0,
                         "vd", drex->vd,
                         "d", drex->d);
}


static PyObject *_pydis_build_op_access(ND_OPERAND_ACCESS *access)
{
    return Py_BuildValue("{s,B, s,B, s,B, s,B, s,B}",
                         "Access", access->Access,
                         "Read", access->Read,
                         "Write", access->Write,
                         "CondRead", access->CondRead,
                         "CondWrite", access->CondWrite);
}


static PyObject *_pydis_build_op_flags(ND_OPERAND_FLAGS *flags)
{
    return Py_BuildValue("{s,B, s,B, s,B, s,B}",
                         "Flags", flags->Flags,
                         "IsDefault", flags->IsDefault,
                         "SignExtendedOp1", flags->SignExtendedOp1,
                         "SignExtendedDws", flags->SignExtendedDws);
}


static PyObject *_pydis_build_op_reg(ND_OPDESC_REGISTER *reg)
{
    return Py_BuildValue("{s,s, s,B, s,I, s,I}",
                         "Type", RegTypeToString(reg->Type),
                         "Size", reg->Size,
                         "Reg", reg->Reg,
                         "Count", reg->Count);
}


static PyObject *_pydis_build_op_memory(ND_OPDESC_MEMORY *mem)
{
    PyObject *nd_vsib = Py_BuildValue("{s,B, s,B, s,B}",
                                      "IndexSize", mem->Vsib.IndexSize,
                                      "ElemSize", mem->Vsib.ElemSize,
                                      "ElemCount", mem->Vsib.ElemCount);

    PyObject *p = Py_BuildValue("{"
                                "s,B,"     // HasSeg
                                "s,B,"     // HasBase
                                "s,B,"     // HasIndex
                                "s,B,"     // HasDisp
                                "s,B,"     // HasCompDisp
                                "s,B,"     // HasBroadcast

                                "s,B,"     // IsRipRel
                                "s,B,"     // IsStack
                                "s,B,"     // IsShadowStack
                                "s,B,"     // IsDirect
                                "s,B,"     // IsBitbase
                                "s,B,"     // IsAG
                                "s,B,"     // IsMib
                                "s,B,"     // IsVsib

                                "s,B,"     // BaseSize
                                "s,B,"     // IndexSize
                                "s,B,"     // DispSize
                                "s,B,"     // CompDispSize

                                "s,O,"     // Vsib

                                "s,B,"     // Seg
                                "s,B,"     // Base
                                "s,B,"     // Index
                                "s,B,"     // Scale

                                "s,K"      // Disp
                                "}",

                                "HasSeg", mem->HasSeg,
                                "HasBase", mem->HasBase,
                                "HasIndex", mem->HasIndex,
                                "HasDisp", mem->HasDisp,
                                "HasCompDisp", mem->HasCompDisp,
                                "HasBroadcast", mem->HasBroadcast,

                                "IsRipRel", mem->IsRipRel,
                                "IsStack", mem->IsStack,
                                "IsShadowStack", mem->IsShadowStack,
                                "IsDirect", mem->IsDirect,
                                "IsBitbase", mem->IsBitbase,
                                "IsAG", mem->IsAG,
                                "IsMib", mem->IsMib,
                                "IsVsib", mem->IsVsib,

                                "BaseSize", mem->BaseSize,
                                "IndexSize", mem->IndexSize,
                                "DispSize", mem->DispSize,
                                "CompDispSize", mem->CompDispSize,

                                "Vsib", nd_vsib,

                                "Seg", mem->Seg,
                                "Base", mem->Base,
                                "Index", mem->Index,
                                "Scale", mem->Index,

                                "Disp", mem->Disp);
    Py_XDECREF(nd_vsib);

    return p;
}


static PyObject *_pydis_build_op_immediate(ND_OPDESC_IMMEDIATE *imm)
{
    return Py_BuildValue("{s,K}", "Imm", imm->Imm);
}


static PyObject *_pydis_build_op_rel_offset(ND_OPDESC_RELOFFSET *rel_offset)
{
    return Py_BuildValue("{s,K}", "Rel", rel_offset->Rel);
}


static PyObject *_pydis_build_op_address(ND_OPDESC_ADDRESS *address)
{
    return Py_BuildValue("{s,H, s,K}",
                         "BaseSeg", address->BaseSeg,
                         "Offset", address->Offset);
}


static PyObject *_pydis_build_op_const(ND_OPDESC_CONSTANT *constant)
{
    return Py_BuildValue("{s,K}", "Const", constant->Const);
}


static PyObject *_pydis_build_operand(ND_OPERAND *operand)
{
    PyObject *nd_access = _pydis_build_op_access(&operand->Access);
    PyObject *nd_flags = _pydis_build_op_flags(&operand->Flags);

    PyObject *nd_operand = NULL;

    switch (operand->Type)
    {
    case ND_OP_REG:
        nd_operand = _pydis_build_op_reg(&operand->Info.Register);
        break;
    case ND_OP_MEM:
        nd_operand = _pydis_build_op_memory(&operand->Info.Memory);
        break;
    case ND_OP_IMM:
        nd_operand = _pydis_build_op_immediate(&operand->Info.Immediate);
        break;
    case ND_OP_OFFS:
        nd_operand = _pydis_build_op_rel_offset(&operand->Info.RelativeOffset);
        break;
    case ND_OP_ADDR:
        nd_operand = _pydis_build_op_address(&operand->Info.Address);
        break;
    case ND_OP_CONST:
        nd_operand = _pydis_build_op_const(&operand->Info.Constant);
        break;
    case ND_OP_BANK:
        nd_operand = Py_BuildValue("{}");
        break;
    default:
        PyErr_SetString(PyExc_RuntimeError, "invalid operand type... Talk with @csirb to update pydis...");
        Py_RETURN_NONE;
    }

    PyObject *p = Py_BuildValue("{"
                                "s,s,"     // Type
                                "s,s,"     // Encoding
                                "s,I,"     // Size
                                "s,I,"     // RawSize
                                "s,O,"     // Access
                                "s,O,"     // Flags
                                "s,O"      // Operand
                                "}",
                                "Type", OperandTypeToString(operand->Type),
                                "Encoding", OperandEncodingToString(operand->Encoding),
                                "Size", operand->Size,
                                "RawSize", operand->RawSize,
                                "Access", nd_access,
                                "Flags", nd_flags,
                                "Operand", nd_operand);

    Py_XDECREF(nd_access);
    Py_XDECREF(nd_flags);
    Py_XDECREF(nd_operand);

    return p;
}


static PyObject *_pydis_build_operands(ND_OPERAND *operands, size_t count)
{
    char op_str_format[400] = {'[', 0};
    size_t last = 1;

    PyObject *nd_operands[ND_MAX_OPERAND] = {0};
    size_t op;

    for (op = 0; op < count && last < sizeof(op_str_format); op++)
    {
        if (op > 0)
        {
            op_str_format[last++] = ',';
        }

        op_str_format[last++] = 'O';

        nd_operands[op] = _pydis_build_operand(&operands[op]);
    }

    op_str_format[last] = ']';

    PyObject *p = Py_BuildValue(op_str_format,
                                nd_operands[0],
                                nd_operands[1],
                                nd_operands[2],
                                nd_operands[3],
                                nd_operands[4],
                                nd_operands[5],
                                nd_operands[6],
                                nd_operands[7],
                                nd_operands[8],
                                nd_operands[9]);

    for (size_t o = 0; o < op; o++)
    {
        Py_XDECREF(nd_operands[o]);
    }

    return p;
}


static PyObject *_pydis_build_exs(INSTRUX *instr)
{
    return Py_BuildValue("{"
                         "s,B,"         // w
                         "s,B,"         // r
                         "s,B,"         // x
                         "s,B,"         // b
                         "s,B,"         // rp
                         "s,B,"         // p
                         "s,B,"         // m
                         "s,B,"         // l
                         "s,B,"         // v
                         "s,B,"         // vp
                         "s,B,"         // bm
                         "s,B,"         // e
                         "s,B,"         // z
                         "s,B,"         // k
                         "s,B"          // s
                         "}",
                         "w", instr->Exs.w,
                         "r", instr->Exs.r,
                         "x", instr->Exs.x,
                         "b", instr->Exs.b,
                         "rp", instr->Exs.rp,
                         "p", instr->Exs.p,
                         "m", instr->Exs.m,
                         "l", instr->Exs.l,
                         "v", instr->Exs.v,
                         "vp", instr->Exs.vp,
                         "bm", instr->Exs.bm,
                         "e", instr->Exs.e,
                         "z", instr->Exs.z,
                         "k", instr->Exs.k,
                         "s", instr->Exs.s);
}


static PyObject *_pydis_build_address(INSTRUX *instr)
{
    return Py_BuildValue("{s,I, s,H}",
                         "Ip", instr->Address.Ip,
                         "Cs", instr->Address.Cs);
}


static PyObject *_pydis_build_cpuid_flag(ND_CPUID_FLAG *cpuid_flag)
{
    return Py_BuildValue("{"
                         "s,K,"         // Flag
                         "s,I,"         // Leaf
                         "s,I,"         // SubLeaf
                         "s,B,"         // Reg
                         "s,B"          // Bit
                         "}",
                         "Flag", cpuid_flag->Flag,
                         "Leaf", cpuid_flag->Leaf,
                         "SubLeaf", cpuid_flag->SubLeaf,
                         "Reg", cpuid_flag->Reg,
                         "Bit", cpuid_flag->Bit);
}


static PyObject *_pydis_build_flags_access(INSTRUX *instr)
{
    return Py_BuildValue("{"
                         "s,B,"        // RegAccess
                         "s,I,"        // Tested
                         "s,I,"        // Modified
                         "s,I,"        // Set
                         "s,I,"        // Cleared
                         "s,I"         // Undefined
                         "}",
                         "RegAccess", instr->FlagsAccess.RegAccess,
                         "Tested", instr->FlagsAccess.Tested,
                         "Modified", instr->FlagsAccess.Modified,
                         "Set", instr->FlagsAccess.Set,
                         "Cleared", instr->FlagsAccess.Cleared,
                         "Undefined", instr->FlagsAccess.Undefined);
}


static PyObject *_pydis_build_valid_modes(ND_VALID_MODES *valid_modes)
{
    return Py_BuildValue("{"
                         "s,H,"         // Raw

                         "s,B,"         // Ring0
                         "s,B,"         // Ring1
                         "s,B,"         // Ring2
                         "s,B,"         // Ring3

                         "s,B,"         // Real
                         "s,B,"         // V8086
                         "s,B,"         // Protected
                         "s,B,"         // Compatibility
                         "s,B,"         // Long
                         "s,B,"         // Smm

                         "s,B,"         // Sgx
                         "s,B,"         // Tsx

                         "s,B,"         // VmxRoot
                         "s,B,"         // VmxNonRoot
                         "s,B"          // VmxOff
                         "}",

                         "Raw", valid_modes->Raw,

                         "Ring0", valid_modes->Ring0,
                         "Ring1", valid_modes->Ring1,
                         "Ring2", valid_modes->Ring2,
                         "Ring3", valid_modes->Ring3,

                         "Real", valid_modes->Real,
                         "V8086", valid_modes->V8086,
                         "Protected", valid_modes->Protected,
                         "Compatibility", valid_modes->Compat,
                         "Long", valid_modes->Long,
                         "Smm", valid_modes->Smm,

                         "Sgx", valid_modes->Sgx,
                         "Tsx", valid_modes->Tsx,

                         "VmxRoot", valid_modes->VmxRoot,
                         "VmxNonRoot", valid_modes->VmxNonRoot,
                         "VmxOff", valid_modes->VmxOff);
}


static PyObject *_pydis_build_valid_prefixes(ND_VALID_PREFIXES *valid_prefixes)
{
    return Py_BuildValue("{"
                         "s,H,"         // Raw
                         "s,B,"         // Rep,
                         "s,B,"         // RepCond,
                         "s,B,"         // Lock,
                         "s,B,"         // Hle,
                         "s,B,"         // Xacquire,
                         "s,B,"         // Xrelease,
                         "s,B,"         // Bnd,
                         "s,B,"         // Bhint,
                         "s,B,"         // HleNoLock,
                         "s,B"          // Dnt,
                         "}",

                         "Raw", valid_prefixes->Raw,
                         "Rep", valid_prefixes->Rep,
                         "RepCond", valid_prefixes->RepCond,
                         "Lock", valid_prefixes->Lock,
                         "Hle", valid_prefixes->Hle,
                         "Xacquire", valid_prefixes->Xacquire,
                         "Xrelease", valid_prefixes->Xrelease,
                         "Bnd", valid_prefixes->Bnd,
                         "Bhint", valid_prefixes->Bhint,
                         "HleNoLock", valid_prefixes->HleNoLock,
                         "Dnt", valid_prefixes->Dnt);
}


static PyObject *_pydis_build_valid_decorators(ND_VALID_DECORATORS *valid_deorators)
{
    return Py_BuildValue("{"
                         "s,H,"         // Raw
                         "s,B,"         // Er
                         "s,B,"         // Sae
                         "s,B,"         // Zero
                         "s,B,"         // Mask
                         "s,B"          // Broadcast
                         "}",

                         "Raw", valid_deorators->Raw,
                         "Er", valid_deorators->Er,
                         "Sae", valid_deorators->Sae,
                         "Zero", valid_deorators->Zero,
                         "Mask", valid_deorators->Mask,
                         "Broadcast", valid_deorators->Broadcast);
}


static PyObject *_pydis_build_instr_dict(INSTRUX *instr, uint64_t rip)
{
    PyObject *nd_rex = _pydis_build_rex(&instr->Rex);

    PyObject *nd_modrm = _pydis_build_modrm(&instr->ModRm);

    PyObject *nd_sib = _pydis_build_sib(&instr->Sib);

    PyObject *nd_drex = _pydis_build_drex(&instr->Drex);

    PyObject *nd_op_array = _pydis_build_operands(instr->Operands, instr->OperandsCount);

    PyObject *nd_exs = _pydis_build_exs(instr);

    PyObject *nd_address = _pydis_build_address(instr);

    PyObject *nd_cpuid_flag = _pydis_build_cpuid_flag(&instr->CpuidFlag);

    PyObject *nd_valid_modes = _pydis_build_valid_modes(&instr->ValidModes);
    PyObject *nd_valid_prefixes = _pydis_build_valid_prefixes(&instr->ValidPrefixes);
    PyObject *nd_valid_decorators = _pydis_build_valid_decorators(&instr->ValidDecorators);

    char instr_text[ND_MIN_BUF_SIZE] = {0};
    NDSTATUS status = nd_to_text(instr, rip, sizeof(instr_text), instr_text);
    if (!ND_SUCCESS(status))
    {
        strcpy(instr_text, "<error decoding>");
    }

    PyObject *nd_flags_access = _pydis_build_flags_access(instr);

    PyObject *p = Py_BuildValue("{"
                                "s,B,"         // DefCode
                                "s,B,"         // DefData
                                "s,B,"         // DefStack
                                "s,B,"         // EncMode
                                "s,B,"         // VexMode
                                "s,B,"         // AddrMode,
                                "s,B,"         // OpMode,
                                "s,B,"         // EfOpMode,
                                "s,B,"         // VecMode,
                                "s,B,"         // EfVecMode,

                                "s,B,"         // HasRex,
                                "s,B,"         // HasVex,
                                "s,B,"         // HasXop,
                                "s,B,"         // HasEvex,
                                "s,B,"         // HasOpSize,
                                "s,B,"         // HasAddrSize,
                                "s,B,"         // HasLock,
                                "s,B,"         // HasRepnzXacquireBnd,
                                "s,B,"         // HasRepRepzXrelease,
                                "s,B,"         // HasSeg,

                                "s,B,"         // IsRepeated,
                                "s,B,"         // IsXacquireEnabled,
                                "s,B,"         // IsXreleaseEnabled,
                                "s,B,"         // IsRipRelative,
                                "s,B,"         // IsCetTracked,

                                "s,B,"         // HasModRm,
                                "s,B,"         // HasSib,
                                "s,B,"         // HasDrex,
                                "s,B,"         // HasDisp,
                                "s,B,"         // HasAddr,
                                "s,B,"         // HasMoffset,
                                "s,B,"         // HasImm1,
                                "s,B,"         // HasImm2,
                                "s,B,"         // HasImm3,
                                "s,B,"         // HasRelOffs,
                                "s,B,"         // HasSseImm,
                                "s,B,"         // HasCompDisp,
                                "s,B,"         // HasBroadcast,
                                "s,B,"         // HasMask,
                                "s,B,"         // HasZero,
                                "s,B,"         // HasEr,
                                "s,B,"         // HasSae,

                                "s,B,"         // SignDisp,

                                "s,B,"         // HasMandatory66,
                                "s,B,"         // HasMandatoryF2,
                                "s,B,"         // HasMandatoryF3,

                                "s,B,"         // Length,

                                "s,B,"         // WordLength,
                                "s,B,"         // PrefLength,

                                "s,B,"         // OpLength,
                                "s,B,"         // DispLength,
                                "s,B,"         // AddrLength,
                                "s,B,"         // MoffsetLength,
                                "s,B,"         // Imm1Length,
                                "s,B,"         // Imm2Length,
                                "s,B,"         // Imm3Length,
                                "s,B,"         // RelOffsLength,

                                "s,B,"          // OpOffset,
                                "s,B,"          // MainOpOffset,
                                "s,B,"          // DispOffset,
                                "s,B,"          // AddrOffset,
                                "s,B,"          // MoffsetOffset,
                                "s,B,"          // Imm1Offset,
                                "s,B,"          // Imm2Offset,
                                "s,B,"          // Imm3Offset,

                                "s,B,"         // RelOffsOffset,
                                "s,B,"         // SseImmOffset,
                                "s,B,"         // ModRmOffset,

                                "s,B,"         // StackWords,

                                "s,B,"         // Rep,
                                "s,B,"         // Seg,

                                "s,O,"         // Rex,
                                "s,O,"         // ModRm,
                                "s,O,"         // Sib,
                                "s,O,"         // Drex,

                                "s,O,"         // Exs,

                                "s,O,"         // Address,

                                "s,K,"         // Moffset,
                                "s,I,"         // Displacement,
                                "s,I,"         // RelativeOffset,
                                "s,K,"         // Immediate1,
                                "s,B,"         // Immediate2,
                                "s,B,"         // Immediate3,
                                "s,B,"         // SseImmediate,
                                "s,B,"         // SseCondition,
                                "s,B,"         // Condition,
                                "s,B,"         // Predicate,

                                "s,B,"         // OperandsCount,
                                "s,B,"         // ExpOperandsCount,

                                "s,H,"         // OperandsEncodingMap,
                                "s,O,"         // Operands,

                                "s,B,"         // RipAccess,
                                "s,B,"         // StackAccess,
                                "s,B,"         // MemoryAccess,

                                "s,O,"         // FlagsAccess,

                                "s,s,"         // TupleType,
                                "s,s,"         // RoundingMode,

                                "s,I,"         // Attributes,

                                "s,I,"         // Iclass,
                                "s,I,"         // Category,
                                "s,I,"         // IsaSet,
                                "s,O,"         // CpuidFlag,

                                "s,O,"         // ValidModes,
                                "s,O,"         // ValidPrefixes,
                                "s,O,"         // ValidDecorators,

                                "s,s,"         // Mnemonic,
                                "s,y#,"        // OpCodeBytes,
                                "s,B,"         // PrimaryOpCode,
                                "s,y#,"        // InstructionBytes,
                                "s,s"          // NdToText
                                "}",

                                "DefCode", instr->DefCode,
                                "DefData", instr->DefData,
                                "DefStack", instr->DefStack,
                                "EncMode", instr->EncMode,
                                "VexMode", instr->VexMode,
                                "AddrMode", instr->AddrMode,
                                "OpMode", instr->OpMode,
                                "EfOpMode", instr->EfOpMode,
                                "VecMode", instr->VecMode,
                                "EfVecMode", instr->EfVecMode,

                                "HasRex", instr->HasRex,
                                "HasVex", instr->HasVex,
                                "HasXop", instr->HasXop,
                                "HasEvex", instr->HasEvex,
                                "HasOpSize", instr->HasOpSize,
                                "HasAddrSize", instr->HasAddrSize,
                                "HasLock", instr->HasLock,
                                "HasRepnzXacquireBnd", instr->HasRepnzXacquireBnd,
                                "HasRepRepzXrelease", instr->HasRepRepzXrelease,
                                "HasSeg", instr->HasSeg,

                                "IsRepeated", instr->IsRepeated,
                                "IsXacquireEnabled", instr->IsXacquireEnabled,
                                "IsXreleaseEnabled", instr->IsXreleaseEnabled,
                                "IsRipRelative", instr->IsRipRelative,
                                "IsCetTracked", instr->IsCetTracked,

                                "HasModRm", instr->HasModRm,
                                "HasSib", instr->HasSib,
                                "HasDrex", instr->HasDrex,
                                "HasDisp", instr->HasDisp,
                                "HasAddr", instr->HasAddr,
                                "HasMoffset", instr->HasMoffset,
                                "HasImm1", instr->HasImm1,
                                "HasImm2", instr->HasImm2,
                                "HasImm3", instr->HasImm3,
                                "HasRelOffs", instr->HasRelOffs,
                                "HasSseImm", instr->HasSseImm,
                                "HasCompDisp", instr->HasCompDisp,
                                "HasBroadcast", instr->HasBroadcast,
                                "HasMask", instr->HasMask,
                                "HasZero", instr->HasZero,
                                "HasEr", instr->HasEr,
                                "HasSae", instr->HasSae,

                                "SignDisp", instr->SignDisp,

                                "HasMandatory66", instr->HasMandatory66,
                                "HasMandatoryF2", instr->HasMandatoryF2,
                                "HasMandatoryF3", instr->HasMandatoryF3,

                                "Length", instr->Length,

                                "WordLength", instr->WordLength,
                                "PrefLength", instr->PrefLength,

                                "OpLength", instr->OpLength,
                                "DispLength", instr->DispLength,
                                "AddrLength", instr->AddrLength,
                                "MoffsetLength", instr->MoffsetLength,
                                "Imm1Length", instr->Imm1Length,
                                "Imm2Length", instr->Imm2Length,
                                "Imm3Length", instr->Imm3Length,
                                "RelOffsLength", instr->RelOffsLength,

                                "OpOffset", instr->OpOffset,
                                "MainOpOffset", instr->MainOpOffset,
                                "DispOffset", instr->DispOffset,
                                "AddrOffset", instr->AddrOffset,
                                "MoffsetOffset", instr->MoffsetOffset,
                                "Imm1Offset", instr->Imm1Offset,
                                "Imm2Offset", instr->Imm2Offset,
                                "Imm3Offset", instr->Imm3Offset,

                                "RelOffsOffset", instr->RelOffsOffset,
                                "SseImmOffset", instr->SseImmOffset,
                                "ModRmOffset", instr->ModRmOffset,

                                "StackWords", instr->StackWords,

                                "Rep", instr->Rep,
                                "Seg", instr->Seg,
                                "Rex", nd_rex,
                                "ModRm", nd_modrm,
                                "Sib", nd_sib,
                                "Drex", nd_drex,

                                "Exs", nd_exs,

                                "Address", nd_address,

                                "Moffset", instr->Moffset,
                                "Displacement", instr->Displacement,
                                "RelativeOffset", instr->RelativeOffset,
                                "Immediate1", instr->Immediate1,
                                "Immediate2", instr->Immediate2,
                                "Immediate3", instr->Immediate3,
                                "SseImmediate", instr->SseImmediate,
                                "SseCondition", instr->SseCondition,
                                "Condition", instr->Condition,
                                "Predicate", instr->Predicate,

                                "OperandsCount", instr->OperandsCount,
                                "ExpOperandsCount", instr->ExpOperandsCount,

                                "OperandsEncodingMap", instr->OperandsEncodingMap,
                                "Operands", nd_op_array,

                                "RipAccess", instr->RipAccess,
                                "StackAccess", instr->StackAccess,
                                "MemoryAccess", instr->MemoryAccess,

                                "FlagsAccess", nd_flags_access,

                                "TupleType", TupleTypeToString(instr->TupleType),
                                "RoundingMode", RoundingModeToString(instr->RoundingMode),

                                "Attributes", instr->Attributes,

                                "Iclass", instr->Iclass,
                                "Category", instr->Category,
                                "IsaSet", instr->IsaSet,
                                "CpuidFlag", nd_cpuid_flag,

                                "ValidModes", nd_valid_modes,
                                "ValidPrefixes", nd_valid_prefixes,
                                "ValidDecorators", nd_valid_decorators,

                                "Mnemonic", instr->Mnemonic,
                                "OpCodeBytes", instr->OpCodeBytes, sizeof(instr->OpCodeBytes),
                                "PrimaryOpCode", instr->PrimaryOpCode,
                                "InstructionBytes", instr->InstructionBytes, instr->Length,
                                "Text", instr_text);

    Py_XDECREF(nd_rex);
    Py_XDECREF(nd_modrm);
    Py_XDECREF(nd_sib);
    Py_XDECREF(nd_drex);
    Py_XDECREF(nd_op_array);
    Py_XDECREF(nd_exs);
    Py_XDECREF(nd_address);
    Py_XDECREF(nd_cpuid_flag);
    Py_XDECREF(nd_valid_modes);
    Py_XDECREF(nd_valid_prefixes);
    Py_XDECREF(nd_valid_decorators);
    Py_XDECREF(nd_flags_access);

    return p;
}


static uint8_t _pydis_py_code_to_disasm(uint8_t code)
{
    if (16 == code)
    {
        return ND_CODE_16;
    }
    else if (32 == code)
    {
        return ND_CODE_32;
    }
    else if (64 == code)
    {
        return ND_CODE_64;
    }

    PyErr_Format(PyExc_ValueError, "Invalid code: %d (expected 16|32|64)\n", code);
    return 0;
}


static uint8_t _pydis_py_data_to_disasm(uint8_t data)
{
    if (16 == data)
    {
        return ND_DATA_16;
    }
    else if (32 == data)
    {
        return ND_DATA_32;
    }
    else if (64 == data)
    {
        return ND_DATA_64;
    }

    PyErr_Format(PyExc_ValueError, "Invalid data: %d (expected 16|32|64)\n", data);
    return 0;
}


static uint8_t _pydis_py_stack_to_disasm(uint8_t stack)
{
    if (16 == stack)
    {
        return ND_STACK_16;
    }
    else if (32 == stack)
    {
        return ND_STACK_32;
    }
    else if (64 == stack)
    {
        return ND_STACK_64;
    }

    PyErr_Format(PyExc_ValueError, "Invalid stack: %d (expected 16|32|64)\n", stack);
    return 0;
}


static PyObject *pydis_decode_ex(PyObject *self, PyObject *args)
{
    char *buf = NULL;
    Py_ssize_t bufsize = 0;
    uint64_t rip = 0;
    uint8_t code, data;

    (void)self;

    if (!PyArg_ParseTuple(args, "s#BB|K", &buf, &bufsize, &code, &data, &rip))
    {
        PyErr_SetString(PyExc_ValueError, "invalid arguments. expected: <buffer[bytes]> <bufsize> <code> <data> <rip>");
        Py_RETURN_NONE;
    }

    code = _pydis_py_code_to_disasm(code);
    data = _pydis_py_data_to_disasm(data);

    if (PyErr_Occurred())
    {
        Py_RETURN_NONE;
    }

    INSTRUX instr;

    NDSTATUS status = nd_decode_ex(&instr, buf, bufsize, code, data);
    if (!ND_SUCCESS(status))
    {
        Py_RETURN_NONE;
    }

    return _pydis_build_instr_dict(&instr, rip);
}


static PyObject *pydis_decode_ex2(PyObject *self, PyObject *args)
{
    char *buf = NULL;
    char *str_vend = NULL;
    Py_ssize_t bufsize = 0;
    uint64_t rip = 0;
    uint8_t code, data, stack, vend;

    (void)self;

    if (!PyArg_ParseTuple(args, "s#BBBs|K", &buf, &bufsize, &code, &data, &stack, &str_vend, &rip))
    {
        PyErr_SetString(PyExc_ValueError, "invalid arguments. expected: <buffer[bytes]> <bufsize> <code> <data> <stack> <vendor> <rip>");
        Py_RETURN_NONE;
    }

    if (0 == strcmp(str_vend, "any"))
    {
        vend = ND_VEND_ANY;
    }
    else if (0 == strcmp(str_vend, "intel"))
    {
        vend = ND_VEND_INTEL;
    }
    else if (0 == strcmp(str_vend, "amd"))
    {
        vend = ND_VEND_AMD;
    }
    else if (0 == strcmp(str_vend, "geode"))
    {
        vend = ND_VEND_GEODE;
    }
    else if (0 == strcmp(str_vend, "cyrix"))
    {
        vend = ND_VEND_CYRIX;
    }
    else
    {
        PyErr_Format(PyExc_ValueError, "invalid vendor: '%s'", str_vend);
        Py_RETURN_NONE;
    }

    code = _pydis_py_code_to_disasm(code);
    data = _pydis_py_data_to_disasm(data);
    stack = _pydis_py_stack_to_disasm(stack);

    if (PyErr_Occurred())
    {
        Py_RETURN_NONE;
    }

    INSTRUX instr;

    NDSTATUS status = nd_decode_ex2(&instr, buf, bufsize, code, data, stack, vend);
    if (!ND_SUCCESS(status))
    {
        Py_RETURN_NONE;
    }

    return _pydis_build_instr_dict(&instr, rip);
}


static PyObject *pydis_to_text(PyObject *self, PyObject *args)
{
    char *buf = NULL;
    Py_ssize_t bufsize = 0;
    uint64_t rip = 0;
    uint8_t code, data;

    (void)self;

    if (!PyArg_ParseTuple(args, "s#BB|K", &buf, &bufsize, &code, &data, &rip))
    {
        PyErr_SetString(PyExc_ValueError, "invalid arguments. expected: <buffer[bytes]> <bufsize> <code> <data> <rip>");
        Py_RETURN_NONE;
    }

    code = _pydis_py_code_to_disasm(code);
    data = _pydis_py_data_to_disasm(data);

    if (PyErr_Occurred())
    {
        Py_RETURN_NONE;
    }

    INSTRUX instr;

    NDSTATUS status = nd_decode_ex(&instr, buf, bufsize, code, data);
    if (!ND_SUCCESS(status))
    {
        Py_RETURN_NONE;
    }

    char instr_text[ND_MIN_BUF_SIZE] = {0};

    status = nd_to_text(&instr, rip, sizeof(instr_text), instr_text);
    if (!ND_SUCCESS(status))
    {
        Py_RETURN_NONE;
    }

    return Py_BuildValue("{s,s,s,y#}", "text", instr_text, "bytes", instr.InstructionBytes, instr.Length);
}


static PyMethodDef module_methods[] = {
    {"nd_decode_ex", pydis_decode_ex, METH_VARARGS, pydis_decode_ex_docstring},
    {"nd_decode_ex2", pydis_decode_ex2, METH_VARARGS, pydis_decode_ex2_docstring},
    {"nd_decode", pydis_decode_ex, METH_VARARGS, pydis_decode_docstring},
    {"nd_to_text", pydis_to_text, METH_VARARGS, pydis_to_text_docstring},
    {NULL, NULL, 0, NULL}
};


static struct PyModuleDef PyDis =
{
    PyModuleDef_HEAD_INIT,
    "pydis",                               // name of module
    module_docstring,
    -1,                                    // size of per-interpreter state of the module, or -1 if the module keeps state in global variables
    module_methods
};

PyMODINIT_FUNC PyInit__pydis(void)
{
    return PyModule_Create(&PyDis);
}
