/*
 * diStorm3 JNI wrapper.
 * Gil Dabah, October 2010.
 */

#include "jdistorm.h"
#include "../../include/distorm.h"

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

#pragma comment(lib, "../../distorm.lib")

static struct _CodeInfoIds {
	jclass jCls;
	jfieldID ID_CodeOffset;
	jfieldID ID_Code;
	jfieldID ID_DecodeType;
	jfieldID ID_Features;
} g_CodeInfoIds;

static struct _DecodedResultIds {
	jclass jCls;
	jfieldID ID_Instructions;
	jfieldID ID_MaxInstructions;
} g_DecodedResultIds;

static struct _DecodedInstIds {
	jclass jCls;
	jfieldID ID_Mnemonic;
	jfieldID ID_Operands;
	jfieldID ID_Hex;
	jfieldID ID_Size;
	jfieldID ID_Offset;
} g_DecodedInstIds;

static struct _DecomposedResultIds {
	jclass jCls;
	jfieldID ID_Instructions;
	jfieldID ID_MaxInstructions;
} g_DecomposedResultIds;

static struct _DecomposedInstIds {
	jclass jCls;
	jfieldID ID_Address;
	jfieldID ID_Size;
	jfieldID ID_Flags;
	jfieldID ID_Segment;
	jfieldID ID_Base;
	jfieldID ID_Scale;
	jfieldID ID_Opcode;
	jfieldID ID_Operands;
	jfieldID ID_Disp;
	jfieldID ID_Imm;
	jfieldID ID_UnusedPrefixesMask;
	jfieldID ID_Meta;
	jfieldID ID_RegistersMask;
} g_DecomposedInstIds;

static struct _OperandIds {
	jclass jCls;
	jfieldID ID_Type;
	jfieldID ID_Index;
	jfieldID ID_Size;
} g_OperandIds;

static struct _ImmIds {
	jclass jCls;
	jfieldID ID_Value;
	jfieldID ID_Size;
} g_ImmIds;

static struct _DispIds {
	jclass jCls;
	jfieldID ID_Displacement;
	jfieldID ID_Size;
} g_DispIds;

void JThrowByName(JNIEnv* env, const char *name, const char* msg)
{
    jclass cls = (*env)->FindClass(env, name);
    if (cls != NULL) {
        (*env)->ThrowNew(env, cls, msg);
    }
    (*env)->DeleteLocalRef(env, cls);
}

_CodeInfo* AcquireCodeInfoStruct(JNIEnv *env, jobject jciObj)
{
	jobject jCodeObj = NULL;
	_CodeInfo* ci = (_CodeInfo*)malloc(sizeof(_CodeInfo));
	if (ci == NULL) {
		JThrowByName(env, "java/lang/OutOfMemoryError", NULL);
		return NULL;
	}
	memset(ci, 0, sizeof(_CodeInfo));

	ci->codeOffset = (*env)->GetLongField(env, jciObj, g_CodeInfoIds.ID_CodeOffset);

	jCodeObj = (*env)->GetObjectField(env, jciObj, g_CodeInfoIds.ID_Code);
	ci->code = (uint8_t*) (*env)->GetDirectBufferAddress(env, jCodeObj);
	ci->codeLen = (int)(*env)->GetDirectBufferCapacity(env, jCodeObj);

	ci->dt = (*env)->GetIntField(env, jciObj, g_CodeInfoIds.ID_DecodeType);

	ci->features = (*env)->GetIntField(env, jciObj, g_CodeInfoIds.ID_Features);

	return ci;
}

jobject CreateDecodedInstObj(JNIEnv* env, const _DecodedInst* inst)
{
	jobject jInst = (*env)->AllocObject(env, g_DecodedInstIds.jCls);
	if (jInst == NULL) return NULL;
	(*env)->SetObjectField(env, jInst, g_DecodedInstIds.ID_Mnemonic, (*env)->NewStringUTF(env, (const char*)inst->mnemonic.p));
	(*env)->SetObjectField(env, jInst, g_DecodedInstIds.ID_Operands, (*env)->NewStringUTF(env, (const char*)inst->operands.p));
	(*env)->SetObjectField(env, jInst, g_DecodedInstIds.ID_Hex, (*env)->NewStringUTF(env, (const char*)inst->instructionHex.p));
	(*env)->SetIntField(env, jInst, g_DecodedInstIds.ID_Size, inst->size);
	(*env)->SetLongField(env, jInst, g_DecodedInstIds.ID_Offset, inst->offset);
	return jInst;
}

JNIEXPORT void JNICALL Java_diStorm3_distorm3_Decode
  (JNIEnv *env, jobject thiz, jobject jciObj, jobject jdrObj)
{
	jarray jInsts = NULL;
	jobject jInst = NULL;
	_CodeInfo* ci = NULL;
	_DecodedInst* insts = NULL;
	jint maxInstructions = 0;
	unsigned int usedInstructionsCount = 0, i = 0;

	thiz; /* Unused. */

	ci = AcquireCodeInfoStruct(env, jciObj);
	if (ci == NULL) {
		JThrowByName(env, "java/lang/OutOfMemoryError", NULL);
		return;
	}

	maxInstructions = (*env)->GetIntField(env, jdrObj, g_DecodedResultIds.ID_MaxInstructions);

	insts = (_DecodedInst*)malloc(maxInstructions * sizeof(_DecodedInst));
	if (insts == NULL) goto Cleanup;

	distorm_decode(ci->codeOffset, ci->code, ci->codeLen, ci->dt, insts, maxInstructions, &usedInstructionsCount);

	jInsts = (*env)->NewObjectArray(env, usedInstructionsCount, g_DecodedInstIds.jCls, NULL);
	if (jInsts == NULL) goto Cleanup;

	for (i = 0; i < usedInstructionsCount; i++) {
		jInst = CreateDecodedInstObj(env, &insts[i]);
		if (jInst == NULL) goto Cleanup;
		(*env)->SetObjectArrayElement(env, jInsts, i, jInst);
	}

	(*env)->SetObjectField(env, jdrObj, g_DecodedResultIds.ID_Instructions, jInsts);

Cleanup:
	/* In case of an error, jInsts will get cleaned automatically. */
	if (ci != NULL) free(ci);
	if (insts != NULL) free(insts);
}

JNIEXPORT void JNICALL Java_diStorm3_distorm3_Decompose
  (JNIEnv *env, jobject thiz, jobject jciObj, jobject jdrObj)
{
	jarray jInsts = NULL, jOperands = NULL;
	jobject jInst = NULL, jOperand = NULL, jImm = NULL, jDisp = NULL;
	_CodeInfo* ci = NULL;
	_DInst* insts = NULL;
	jint maxInstructions = 0;
	unsigned int usedInstructionsCount = 0, i = 0, j = 0, operandsNo = 0;
	int success = 0;

	thiz; /* Unused. */

	ci = AcquireCodeInfoStruct(env, jciObj);
	if (ci == NULL) {
		JThrowByName(env, "java/lang/OutOfMemoryError", NULL);
		return;
	}

	maxInstructions = (*env)->GetIntField(env, jdrObj, g_DecomposedResultIds.ID_MaxInstructions);

	insts = (_DInst*)malloc(maxInstructions * sizeof(_DInst));
	if (insts == NULL) goto Cleanup;

	distorm_decompose(ci, insts, maxInstructions, &usedInstructionsCount);

	jInsts = (*env)->NewObjectArray(env, usedInstructionsCount, g_DecomposedInstIds.jCls, NULL);
	if (jInsts == NULL) goto Cleanup;

	for (i = 0; i < usedInstructionsCount; i++) {
		jInst = (*env)->AllocObject(env, g_DecomposedInstIds.jCls);
		if (jInst == NULL) goto Cleanup;

		/* Simple fields: */
		(*env)->SetLongField(env, jInst, g_DecomposedInstIds.ID_Address, insts[i].addr);
		(*env)->SetIntField(env, jInst, g_DecomposedInstIds.ID_Flags, insts[i].flags);
		(*env)->SetIntField(env, jInst, g_DecomposedInstIds.ID_Size, insts[i].size);
		(*env)->SetIntField(env, jInst, g_DecomposedInstIds.ID_Segment, insts[i].segment);
		(*env)->SetIntField(env, jInst, g_DecomposedInstIds.ID_Base, insts[i].base);
		(*env)->SetIntField(env, jInst, g_DecomposedInstIds.ID_Scale, insts[i].scale);
		(*env)->SetIntField(env, jInst, g_DecomposedInstIds.ID_Opcode, insts[i].opcode);
		(*env)->SetIntField(env, jInst, g_DecomposedInstIds.ID_UnusedPrefixesMask, insts[i].unusedPrefixesMask);
		(*env)->SetIntField(env, jInst, g_DecomposedInstIds.ID_Meta, insts[i].meta);
		(*env)->SetIntField(env, jInst, g_DecomposedInstIds.ID_RegistersMask, insts[i].usedRegistersMask);

		/* Immediate variant. */
		jImm = (*env)->AllocObject(env, g_ImmIds.jCls);
		if (jImm == NULL) goto Cleanup;
		(*env)->SetLongField(env, jImm, g_ImmIds.ID_Value, insts[i].imm.qword);
		/* The size of the immediate is in one of the operands, if at all. Look for it below. Zero by default. */
		(*env)->SetIntField(env, jImm, g_ImmIds.ID_Size, 0);

		/* Count operands. */
		for (operandsNo = 0; operandsNo < OPERANDS_NO; operandsNo++) {
			if (insts[i].ops[operandsNo].type == O_NONE) break;
		}

		jOperands = (*env)->NewObjectArray(env, operandsNo, g_OperandIds.jCls, NULL);
		if (jOperands == NULL) goto Cleanup;

		for (j = 0; j < operandsNo; j++) {
			if (insts[i].ops[j].type == O_IMM) {
				/* Set the size of the immediate operand. */
				(*env)->SetIntField(env, jImm, g_ImmIds.ID_Size, insts[i].ops[j].size);
			}

			jOperand = (*env)->AllocObject(env, g_OperandIds.jCls);
			if (jOperand == NULL) goto Cleanup;
			(*env)->SetIntField(env, jOperand, g_OperandIds.ID_Type, insts[i].ops[j].type);
			(*env)->SetIntField(env, jOperand, g_OperandIds.ID_Index, insts[i].ops[j].index);
			(*env)->SetIntField(env, jOperand, g_OperandIds.ID_Size, insts[i].ops[j].size);
			(*env)->SetObjectArrayElement(env, jOperands, j, jOperand);
		}
		(*env)->SetObjectField(env, jInst, g_DecomposedInstIds.ID_Operands, jOperands);

		/* Attach the immediate variant. */
		(*env)->SetObjectField(env, jInst, g_DecomposedInstIds.ID_Imm, jImm);

		/* Displacement variant. */
		jDisp = (*env)->AllocObject(env, g_DispIds.jCls);
		if (jDisp == NULL) goto Cleanup;
		(*env)->SetLongField(env, jDisp, g_DispIds.ID_Displacement, insts[i].disp);
		(*env)->SetIntField(env, jDisp, g_DispIds.ID_Size, insts[i].dispSize);
		(*env)->SetObjectField(env, jInst, g_DecomposedInstIds.ID_Disp, jDisp);

		(*env)->SetObjectArrayElement(env, jInsts, i, jInst);
	}

	(*env)->SetObjectField(env, jdrObj, g_DecodedResultIds.ID_Instructions, jInsts);

Cleanup:
	/* In case of an error, jInsts will get cleaned automatically. */
	if (ci != NULL) free(ci);
	if (insts != NULL) free(insts);
}

JNIEXPORT jobject JNICALL Java_diStorm3_distorm3_Format
  (JNIEnv *env, jobject thiz, jobject jciObj, jobject jdiObj)
{
	_CodeInfo* ci = NULL;
	_DInst input = {0};
	_DecodedInst output = {0};
	jobject ret = NULL, jOperands = NULL, jOp = NULL, jTmp = NULL;
	jsize i, opsCount;

	thiz; /* Unused. */

	ci = AcquireCodeInfoStruct(env, jciObj);
	if (ci == NULL) {
		JThrowByName(env, "java/lang/OutOfMemoryError", NULL);
		return NULL;
	}

	input.addr = (*env)->GetLongField(env, jdiObj, g_DecomposedInstIds.ID_Address);
	input.flags = (uint16_t) (*env)->GetIntField(env, jdiObj, g_DecomposedInstIds.ID_Flags);
	input.size = (uint8_t) (*env)->GetIntField(env, jdiObj, g_DecomposedInstIds.ID_Size);
	input.segment = (uint8_t) (*env)->GetIntField(env, jdiObj, g_DecomposedInstIds.ID_Segment);
	input.base = (uint8_t) (*env)->GetIntField(env, jdiObj, g_DecomposedInstIds.ID_Base);
	input.scale = (uint8_t) (*env)->GetIntField(env, jdiObj, g_DecomposedInstIds.ID_Scale);
	input.opcode = (uint16_t) (*env)->GetIntField(env, jdiObj, g_DecomposedInstIds.ID_Opcode);
	/* unusedPrefixesMask is unused indeed, lol. */
	input.meta = (uint8_t) (*env)->GetIntField(env, jdiObj, g_DecomposedInstIds.ID_Meta);
	/* Nor usedRegistersMask. */

	jOperands = (*env)->GetObjectField(env, jdiObj, g_DecomposedInstIds.ID_Operands);
	if (jOperands != NULL) {
		opsCount = (*env)->GetArrayLength(env, jOperands);
		for (i = 0; i < opsCount; i++) {
			jOp = (*env)->GetObjectArrayElement(env, jOperands, i);
			if (jOp != NULL) {
				input.ops[i].index = (uint8_t) (*env)->GetIntField(env, jOp, g_OperandIds.ID_Index);
				input.ops[i].type = (uint8_t) (*env)->GetIntField(env, jOp, g_OperandIds.ID_Type);
				input.ops[i].size = (uint16_t) (*env)->GetIntField(env, jOp, g_OperandIds.ID_Size);
			}
		}
	}

	jTmp = (*env)->GetObjectField(env, jdiObj, g_DecomposedInstIds.ID_Imm);
	if (jTmp != NULL) {
		input.imm.qword = (uint64_t) (*env)->GetLongField(env, jTmp, g_ImmIds.ID_Value);
	}

	jTmp = (*env)->GetObjectField(env, jdiObj, g_DecomposedInstIds.ID_Disp);
	if (jTmp != NULL) {
		input.disp = (uint64_t) (*env)->GetLongField(env, jTmp, g_DispIds.ID_Displacement);
		input.dispSize = (uint8_t) (*env)->GetIntField(env, jTmp, g_DispIds.ID_Size);
	}

	distorm_format(ci, &input, &output);

	ret = CreateDecodedInstObj(env, &output);

	if (ci != NULL) free(ci);
	return ret;
}

/* Cache all ID's and classes! Release in unload. */
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
	jclass jCls = NULL;
	JNIEnv* env = NULL;

	if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
		return JNI_VERSION_1_6;
	}

	jCls = (*env)->FindClass(env, PACKAGE_PREFIX "CodeInfo");
	g_CodeInfoIds.jCls = (*env)->NewWeakGlobalRef(env, jCls);
	g_CodeInfoIds.ID_CodeOffset = (*env)->GetFieldID(env, jCls, "mCodeOffset", "J");
	g_CodeInfoIds.ID_Code = (*env)->GetFieldID(env, jCls, "mCode", "Ljava/nio/ByteBuffer;");
	g_CodeInfoIds.ID_DecodeType = (*env)->GetFieldID(env, jCls, "mDecodeType", "I");
	g_CodeInfoIds.ID_Features = (*env)->GetFieldID(env, jCls, "mFeatures", "I");

	jCls = (*env)->FindClass(env, PACKAGE_PREFIX "DecodedResult");
	g_DecodedResultIds.jCls = (*env)->NewWeakGlobalRef(env, jCls);
	g_DecodedResultIds.ID_MaxInstructions = (*env)->GetFieldID(env, jCls, "mMaxInstructions", "I");
	g_DecodedResultIds.ID_Instructions = (*env)->GetFieldID(env, jCls, "mInstructions", "[L" PACKAGE_PREFIX "DecodedInst;");

	jCls = (*env)->FindClass(env, PACKAGE_PREFIX "DecodedInst");
	g_DecodedInstIds.jCls = (*env)->NewWeakGlobalRef(env, jCls);
	g_DecodedInstIds.ID_Mnemonic = (*env)->GetFieldID(env, jCls, "mMnemonic", "Ljava/lang/String;");
	g_DecodedInstIds.ID_Operands = (*env)->GetFieldID(env, jCls, "mOperands", "Ljava/lang/String;");
	g_DecodedInstIds.ID_Hex = (*env)->GetFieldID(env, jCls, "mHex", "Ljava/lang/String;");
	g_DecodedInstIds.ID_Size = (*env)->GetFieldID(env, jCls, "mSize", "I");
	g_DecodedInstIds.ID_Offset = (*env)->GetFieldID(env, jCls, "mOffset", "J");

	jCls = (*env)->FindClass(env, PACKAGE_PREFIX "DecomposedResult");
	g_DecomposedResultIds.jCls = (*env)->NewWeakGlobalRef(env, jCls);
	g_DecomposedResultIds.ID_Instructions = (*env)->GetFieldID(env, jCls, "mInstructions", "[L" PACKAGE_PREFIX "DecomposedInst;");
	g_DecomposedResultIds.ID_MaxInstructions = (*env)->GetFieldID(env, jCls, "mMaxInstructions", "I");

	jCls = (*env)->FindClass(env, PACKAGE_PREFIX "DecomposedInst");
	g_DecomposedInstIds.jCls = (*env)->NewWeakGlobalRef(env, jCls);
	g_DecomposedInstIds.ID_Address = (*env)->GetFieldID(env, jCls, "mAddr", "J");
	g_DecomposedInstIds.ID_Size = (*env)->GetFieldID(env, jCls, "mSize", "I");
	g_DecomposedInstIds.ID_Flags = (*env)->GetFieldID(env, jCls, "mFlags", "I");
	g_DecomposedInstIds.ID_Segment = (*env)->GetFieldID(env, jCls, "mSegment", "I");
	g_DecomposedInstIds.ID_Base = (*env)->GetFieldID(env, jCls, "mBase", "I");
	g_DecomposedInstIds.ID_Scale = (*env)->GetFieldID(env, jCls, "mScale", "I");
	g_DecomposedInstIds.ID_Opcode = (*env)->GetFieldID(env, jCls, "mOpcode", "I");
	g_DecomposedInstIds.ID_Operands = (*env)->GetFieldID(env, jCls, "mOperands", "[L" PACKAGE_PREFIX "Operand;");
	g_DecomposedInstIds.ID_Disp = (*env)->GetFieldID(env, jCls, "mDisp", "L" PACKAGE_PREFIX "DecomposedInst$DispVariant;");
	g_DecomposedInstIds.ID_Imm = (*env)->GetFieldID(env, jCls, "mImm", "L" PACKAGE_PREFIX "DecomposedInst$ImmVariant;");
	g_DecomposedInstIds.ID_UnusedPrefixesMask = (*env)->GetFieldID(env, jCls, "mUnusedPrefixesMask", "I");
	g_DecomposedInstIds.ID_Meta = (*env)->GetFieldID(env, jCls, "mMeta", "I");
	g_DecomposedInstIds.ID_RegistersMask = (*env)->GetFieldID(env, jCls, "mRegistersMask", "I");

	jCls = (*env)->FindClass(env, PACKAGE_PREFIX "Operand");
	g_OperandIds.jCls = (*env)->NewWeakGlobalRef(env, jCls);
	g_OperandIds.ID_Type = (*env)->GetFieldID(env, jCls, "mType", "I");
	g_OperandIds.ID_Index = (*env)->GetFieldID(env, jCls, "mIndex", "I");
	g_OperandIds.ID_Size = (*env)->GetFieldID(env, jCls, "mSize", "I");

	jCls = (*env)->FindClass(env, PACKAGE_PREFIX "DecomposedInst$ImmVariant");
	g_ImmIds.jCls = (*env)->NewWeakGlobalRef(env, jCls);
	g_ImmIds.ID_Value = (*env)->GetFieldID(env, jCls, "mValue", "J");
	g_ImmIds.ID_Size = (*env)->GetFieldID(env, jCls, "mSize", "I");

	jCls = (*env)->FindClass(env, PACKAGE_PREFIX "DecomposedInst$DispVariant");
	g_DispIds.jCls = (*env)->NewWeakGlobalRef(env, jCls);
	g_DispIds.ID_Displacement = (*env)->GetFieldID(env, jCls, "mDisplacement", "J");
	g_DispIds.ID_Size = (*env)->GetFieldID(env, jCls, "mSize", "I");

	return JNI_VERSION_1_6;
}


JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved)
{
	/* Free global weak refs. */
}